Run check config on app boot, fix early error reporting on android
This commit is contained in:
parent
50d50f690b
commit
1a4cbceda0
|
@ -2,11 +2,12 @@ package net.defined.mobile_nebula
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.VpnService
|
||||
import android.os.*
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.NonNull
|
||||
import com.google.gson.Gson
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
|
@ -28,9 +29,15 @@ class MainActivity: FlutterActivity() {
|
|||
|
||||
private var activeSiteId: String? = null
|
||||
|
||||
companion object {
|
||||
private var appContext: Context? = null
|
||||
fun getContext(): Context? { return appContext }
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
appContext = context
|
||||
//TODO: Initializing in the constructor leads to a context lacking info we need, figure out the right way to do this
|
||||
sites = Sites(context, flutterEngine)
|
||||
sites = Sites(flutterEngine)
|
||||
|
||||
// Bind against our service to detect which site is running on app boot
|
||||
val intent = Intent(this, NebulaVpnService::class.java)
|
||||
|
@ -153,7 +160,7 @@ class MainActivity: FlutterActivity() {
|
|||
intent.putExtra("id", siteContainer.site.id)
|
||||
onActivityResult(VPN_START_CODE, Activity.RESULT_OK, intent)
|
||||
}
|
||||
|
||||
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
|
|
|
@ -43,28 +43,37 @@ class NebulaVpnService : VpnService() {
|
|||
return Service.START_NOT_STICKY
|
||||
}
|
||||
|
||||
startVpn(intent?.getStringExtra("path"), intent?.getStringExtra("id"))
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
private fun startVpn(path: String?, id: String?) {
|
||||
val path = intent?.getStringExtra("path")
|
||||
val id = intent?.getStringExtra("id")
|
||||
|
||||
if (running) {
|
||||
return announceExit(id, "Trying to run nebula but it is already running")
|
||||
announceExit(id, "Trying to run nebula but it is already running")
|
||||
//TODO: can we signal failure?
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
//TODO: if we fail to start, android will attempt a restart lacking all the intent data we need.
|
||||
// Link active site config in Main to avoid this
|
||||
site = Site(File(path))
|
||||
var ipNet: CIDR
|
||||
|
||||
if (site!!.cert == null) {
|
||||
return announceExit(id, "Site is missing a certificate")
|
||||
announceExit(id, "Site is missing a certificate")
|
||||
//TODO: can we signal failure?
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
// We don't actually start here. In order to properly capture boot errors we wait until an IPC connection is made
|
||||
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
private fun startVpn() {
|
||||
var ipNet: CIDR
|
||||
|
||||
try {
|
||||
ipNet = mobileNebula.MobileNebula.parseCIDR(site!!.cert!!.cert.details.ips[0])
|
||||
} catch (err: Exception) {
|
||||
return announceExit(id, err.message ?: "$err")
|
||||
return announceExit(site!!.id, err.message ?: "$err")
|
||||
}
|
||||
|
||||
val builder = Builder()
|
||||
|
@ -91,7 +100,7 @@ class NebulaVpnService : VpnService() {
|
|||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Got an error $e")
|
||||
vpnInterface?.close()
|
||||
announceExit(id, e.message)
|
||||
announceExit(site!!.id, e.message)
|
||||
return stopSelf()
|
||||
}
|
||||
|
||||
|
@ -130,7 +139,7 @@ class NebulaVpnService : VpnService() {
|
|||
//TODO: how do we limit what can talk to us?
|
||||
//TODO: Make sure replyTo is actually a messenger
|
||||
when (msg.what) {
|
||||
MSG_REGISTER_CLIENT -> mClients.add(msg.replyTo)
|
||||
MSG_REGISTER_CLIENT -> register(msg)
|
||||
MSG_UNREGISTER_CLIENT -> mClients.remove(msg.replyTo)
|
||||
MSG_IS_RUNNING -> isRunning()
|
||||
MSG_LIST_HOSTMAP -> listHostmap(msg)
|
||||
|
@ -142,11 +151,29 @@ class NebulaVpnService : VpnService() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun register(msg: Message) {
|
||||
mClients.add(msg.replyTo)
|
||||
if (!running) {
|
||||
startVpn()
|
||||
}
|
||||
}
|
||||
|
||||
private fun protect(msg: Message): Boolean {
|
||||
if (nebula != null) {
|
||||
return false
|
||||
}
|
||||
|
||||
msg.replyTo.send(Message.obtain(null, msg.what))
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isRunning() {
|
||||
sendSimple(MSG_IS_RUNNING, if (running) 1 else 0)
|
||||
}
|
||||
|
||||
private fun listHostmap(msg: Message) {
|
||||
if (protect(msg)) { return }
|
||||
|
||||
val res = nebula!!.listHostmap(msg.what == MSG_LIST_PENDING_HOSTMAP)
|
||||
var m = Message.obtain(null, msg.what)
|
||||
m.data.putString("data", res)
|
||||
|
@ -154,6 +181,8 @@ class NebulaVpnService : VpnService() {
|
|||
}
|
||||
|
||||
private fun getHostInfo(msg: Message) {
|
||||
if (protect(msg)) { return }
|
||||
|
||||
val res = nebula!!.getHostInfoByVpnIp(msg.data.getString("vpnIp"), msg.data.getBoolean("pending"))
|
||||
var m = Message.obtain(null, msg.what)
|
||||
m.data.putString("data", res)
|
||||
|
@ -161,6 +190,8 @@ class NebulaVpnService : VpnService() {
|
|||
}
|
||||
|
||||
private fun setRemoteForTunnel(msg: Message) {
|
||||
if (protect(msg)) { return }
|
||||
|
||||
val res = nebula!!.setRemoteForTunnel(msg.data.getString("vpnIp"), msg.data.getString("addr"))
|
||||
var m = Message.obtain(null, msg.what)
|
||||
m.data.putString("data", res)
|
||||
|
@ -168,6 +199,8 @@ class NebulaVpnService : VpnService() {
|
|||
}
|
||||
|
||||
private fun closeTunnel(msg: Message) {
|
||||
if (protect(msg)) { return }
|
||||
|
||||
val res = nebula!!.closeTunnel(msg.data.getString("vpnIp"))
|
||||
var m = Message.obtain(null, msg.what)
|
||||
m.data.putBoolean("data", res)
|
||||
|
|
|
@ -15,7 +15,7 @@ data class SiteContainer(
|
|||
val updater: SiteUpdater
|
||||
)
|
||||
|
||||
class Sites(private var context: Context, private var engine: FlutterEngine) {
|
||||
class Sites(private var engine: FlutterEngine) {
|
||||
private var sites: HashMap<String, SiteContainer> = HashMap()
|
||||
|
||||
init {
|
||||
|
@ -23,6 +23,7 @@ class Sites(private var context: Context, private var engine: FlutterEngine) {
|
|||
}
|
||||
|
||||
fun refreshSites(activeSite: String? = null) {
|
||||
val context = MainActivity.getContext()!!
|
||||
val sitesDir = context.filesDir.resolve("sites")
|
||||
if (!sitesDir.isDirectory) {
|
||||
sitesDir.delete()
|
||||
|
@ -57,7 +58,7 @@ class Sites(private var context: Context, private var engine: FlutterEngine) {
|
|||
|
||||
fun deleteSite(id: String) {
|
||||
sites.remove(id)
|
||||
val siteDir = context.filesDir.resolve("sites").resolve(id)
|
||||
val siteDir = MainActivity.getContext()!!.filesDir.resolve("sites").resolve(id)
|
||||
siteDir.deleteRecursively()
|
||||
//TODO: make sure you stop the vpn
|
||||
//TODO: make sure you relink the active site if this is the active site
|
||||
|
@ -77,7 +78,6 @@ class SiteUpdater(private var site: Site, engine: FlutterEngine): EventChannel.S
|
|||
site.connected = connected
|
||||
site.status = status
|
||||
val d = mapOf("connected" to site.connected, "status" to site.status)
|
||||
|
||||
if (err != null) {
|
||||
eventSink?.error("", err, d)
|
||||
} else {
|
||||
|
@ -209,6 +209,14 @@ class Site {
|
|||
ca = arrayOf()
|
||||
errors.add("Error while loading certificate authorities: ${err.message}")
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
try {
|
||||
mobileNebula.MobileNebula.testConfig(config, getKey(MainActivity.getContext()!!))
|
||||
} catch (err: Exception) {
|
||||
errors.add("Config test error: ${err.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getKey(context: Context): String? {
|
||||
|
|
|
@ -173,9 +173,9 @@ struct Site: Codable {
|
|||
|
||||
init(proto: NETunnelProviderProtocol) throws {
|
||||
let dict = proto.providerConfiguration
|
||||
let rawConfig = dict?["config"] as? Data ?? Data()
|
||||
let config = dict?["config"] as? Data ?? Data()
|
||||
let decoder = JSONDecoder()
|
||||
let incoming = try decoder.decode(IncomingSite.self, from: rawConfig)
|
||||
let incoming = try decoder.decode(IncomingSite.self, from: config)
|
||||
self.init(incoming: incoming)
|
||||
}
|
||||
|
||||
|
@ -241,6 +241,22 @@ struct Site: Codable {
|
|||
logVerbosity = incoming.logVerbosity ?? "info"
|
||||
mtu = incoming.mtu ?? 1300
|
||||
logFile = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.net.defined.mobileNebula")?.appendingPathComponent(id).appendingPathExtension("log").path
|
||||
|
||||
if (errors.isEmpty) {
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
let rawConfig = try encoder.encode(incoming)
|
||||
let key = try getKey()
|
||||
let strConfig = String(data: rawConfig, encoding: .utf8)
|
||||
var err: NSError?
|
||||
MobileNebulaTestConfig(strConfig, key, &err)
|
||||
if (err != nil) {
|
||||
throw err!
|
||||
}
|
||||
} catch {
|
||||
errors.append("Config test error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the private key from the keystore, we don't always need it in memory
|
||||
|
|
|
@ -529,7 +529,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 0.0.31;
|
||||
MARKETING_VERSION = 0.0.33;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
@ -559,7 +559,7 @@
|
|||
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 0.0.31;
|
||||
MARKETING_VERSION = 0.0.33;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
|
@ -594,7 +594,7 @@
|
|||
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 0.0.31;
|
||||
MARKETING_VERSION = 0.0.33;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula.NebulaNetworkExtension;
|
||||
|
@ -626,7 +626,7 @@
|
|||
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 0.0.31;
|
||||
MARKETING_VERSION = 0.0.33;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula.NebulaNetworkExtension;
|
||||
|
@ -768,7 +768,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 0.0.31;
|
||||
MARKETING_VERSION = 0.0.33;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
@ -801,7 +801,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 0.0.31;
|
||||
MARKETING_VERSION = 0.0.33;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.defined.mobileNebula;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
|
|
@ -27,8 +27,6 @@
|
|||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
@ -38,8 +36,8 @@
|
|||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
@ -61,8 +59,6 @@
|
|||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
|
|
|
@ -3,17 +3,17 @@ package mobileNebula
|
|||
type config struct {
|
||||
PKI configPKI `yaml:"pki"`
|
||||
StaticHostmap map[string][]string `yaml:"static_host_map"`
|
||||
Lighthouse configLighthouse `yaml:"lighthouse"`
|
||||
Listen configListen `yaml:"listen"`
|
||||
Punchy configPunchy `yaml:"punchy"`
|
||||
Cipher string `yaml:"cipher"`
|
||||
LocalRange string `yaml:"local_range"`
|
||||
SSHD configSSHD `yaml:"sshd"`
|
||||
Tun configTun `yaml:"tun"`
|
||||
Logging configLogging `yaml:"logging"`
|
||||
Stats configStats `yaml:"stats"`
|
||||
Handshakes configHandshakes `yaml:"handshakes"`
|
||||
Firewall configFirewall `yaml:"firewall"`
|
||||
Lighthouse configLighthouse `yaml:"lighthouse"`
|
||||
Listen configListen `yaml:"listen"`
|
||||
Punchy configPunchy `yaml:"punchy"`
|
||||
Cipher string `yaml:"cipher"`
|
||||
LocalRange string `yaml:"local_range"`
|
||||
SSHD configSSHD `yaml:"sshd"`
|
||||
Tun configTun `yaml:"tun"`
|
||||
Logging configLogging `yaml:"logging"`
|
||||
Stats configStats `yaml:"stats"`
|
||||
Handshakes configHandshakes `yaml:"handshakes"`
|
||||
Firewall configFirewall `yaml:"firewall"`
|
||||
}
|
||||
|
||||
func newConfig() *config {
|
||||
|
|
|
@ -45,8 +45,10 @@ func NewNebula(configData string, key string, logFile string, tunFd int) (*Nebul
|
|||
if err != nil {
|
||||
switch v := err.(type) {
|
||||
case nebula.ContextualError:
|
||||
return nil, v.RealError
|
||||
v.Log(l)
|
||||
return nil, v.Unwrap()
|
||||
default:
|
||||
l.WithError(err).Error("Failed to start")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +83,7 @@ func (n *Nebula) ListHostmap(pending bool) (string, error) {
|
|||
}
|
||||
|
||||
func (n *Nebula) GetHostInfoByVpnIp(vpnIp string, pending bool) (string, error) {
|
||||
b, err := json.Marshal(n.c.GetHostInfoByVpnIp(stringIpToInt(vpnIp), pending))
|
||||
b, err := json.Marshal(n.c.GetHostInfoByVpnIP(stringIpToInt(vpnIp), pending))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -113,4 +115,4 @@ func stringIpToInt(ip string) uint32 {
|
|||
return binary.BigEndian.Uint32(n[12:16])
|
||||
}
|
||||
return binary.BigEndian.Uint32(n)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a h1:Bt1IVPhiCDMqwGrc2nnbIN4QKvJGx6SK2NzWBmW00ao=
|
||||
github.com/vishvananda/netlink v1.0.1-0.20190522153524-00009fb8606a/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
|
@ -156,3 +157,4 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package mobileNebula
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/slackhq/nebula"
|
||||
"github.com/slackhq/nebula/cert"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
|
@ -108,6 +110,39 @@ func RenderConfig(configData string, key string) (string, error) {
|
|||
return string(finalConfig), nil
|
||||
}
|
||||
|
||||
func TestConfig(configData string, key string) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("Recovered in f", r)
|
||||
}
|
||||
}()
|
||||
|
||||
yamlConfig, err := RenderConfig(configData, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := nebula.NewConfig()
|
||||
err = config.LoadString(yamlConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config: %s", err)
|
||||
}
|
||||
|
||||
// We don't want to leak the config into the system logs
|
||||
l := logrus.New()
|
||||
l.SetOutput(bytes.NewBuffer([]byte{}))
|
||||
_, err = nebula.Main(config, true, "", l, nil)
|
||||
if err != nil {
|
||||
switch v := err.(type) {
|
||||
case nebula.ContextualError:
|
||||
return v.Unwrap()
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetConfigSetting(configData string, setting string) string {
|
||||
config := nebula.NewConfig()
|
||||
config.LoadString(configData)
|
||||
|
|
Loading…
Reference in New Issue