diff --git a/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt b/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt index 051450a..a456687 100644 --- a/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt +++ b/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt @@ -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) } diff --git a/android/app/src/main/kotlin/net/defined/mobile_nebula/NebulaVpnService.kt b/android/app/src/main/kotlin/net/defined/mobile_nebula/NebulaVpnService.kt index 32357e6..db83e72 100644 --- a/android/app/src/main/kotlin/net/defined/mobile_nebula/NebulaVpnService.kt +++ b/android/app/src/main/kotlin/net/defined/mobile_nebula/NebulaVpnService.kt @@ -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) diff --git a/android/app/src/main/kotlin/net/defined/mobile_nebula/Sites.kt b/android/app/src/main/kotlin/net/defined/mobile_nebula/Sites.kt index 51e6b8e..5046744 100644 --- a/android/app/src/main/kotlin/net/defined/mobile_nebula/Sites.kt +++ b/android/app/src/main/kotlin/net/defined/mobile_nebula/Sites.kt @@ -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 = 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? { diff --git a/ios/NebulaNetworkExtension/Site.swift b/ios/NebulaNetworkExtension/Site.swift index 815533b..66a97bb 100644 --- a/ios/NebulaNetworkExtension/Site.swift +++ b/ios/NebulaNetworkExtension/Site.swift @@ -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 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 507580d..8b6accd 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -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"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..fb2dffc 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + - -