diff --git a/ios/NebulaNetworkExtension/PacketTunnelProvider.swift b/ios/NebulaNetworkExtension/PacketTunnelProvider.swift index 69c455f..4464f5f 100644 --- a/ios/NebulaNetworkExtension/PacketTunnelProvider.swift +++ b/ios/NebulaNetworkExtension/PacketTunnelProvider.swift @@ -1,7 +1,7 @@ import NetworkExtension import MobileNebula import os.log -import MMWormhole +import SwiftyJSON class PacketTunnelProvider: NEPacketTunnelProvider { private var networkMonitor: NWPathMonitor? @@ -9,16 +9,32 @@ class PacketTunnelProvider: NEPacketTunnelProvider { private var site: Site? private var _log = OSLog(subsystem: "net.defined.mobileNebula", category: "PacketTunnelProvider") - private var wormhole = MMWormhole(applicationGroupIdentifier: "group.net.defined.mobileNebula", optionalDirectory: "ipc") private var nebula: MobileNebulaNebula? private var didSleep = false + private var cachedRouteDescription: String? - private func log(_ message: StaticString, _ args: CVarArg...) { + // This is the system completionHandler, only set when we expect the UI to ask us to actually start so that errors can flow back to the UI + private var startCompleter: ((Error?) -> Void)? + + private func log(_ message: StaticString, _ args: Any...) { os_log(message, log: _log, args) } - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - NSKeyedUnarchiver.setClass(IPCRequest.classForKeyedUnarchiver(), forClassName: "Runner.IPCRequest") + override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { + // There is currently no way to get initialization errors back to the UI via completionHandler here + // `expectStart` is sent only via the UI which means we should wait for the real start command which has another completion handler the UI can intercept + // In the end we need to call this completionHandler to inform the system of our state + if options?["expectStart"] != nil { + startCompleter = completionHandler + return + } + + // VPN is being booted out of band of the UI. Use the system completion handler as there will be nothing to route initialization errors to but we still need to report + // success/fail by the presence of an error or nil + start(completionHandler: completionHandler) + } + + private func start(completionHandler: @escaping (Error?) -> Void) { let proto = self.protocolConfiguration as! NETunnelProviderProtocol var config: Data var key: String @@ -31,24 +47,19 @@ class PacketTunnelProvider: NEPacketTunnelProvider { log("Failed to render config from vpn object") return completionHandler(error) } - + let _site = site! _log = OSLog(subsystem: "net.defined.mobileNebula:\(_site.name)", category: "PacketTunnelProvider") - + do { key = try _site.getKey() } catch { - wormhole.passMessageObject(IPCMessage(id: _site.id, type: "error", message: error.localizedDescription), identifier: "nebula") return completionHandler(error) } - - startNetworkMonitor() - + let fileDescriptor = (self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as? Int32) ?? -1 if fileDescriptor < 0 { - let msg = IPCMessage(id: _site.id, type: "error", message: "Starting tunnel failed: Could not determine file descriptor") - wormhole.passMessageObject(msg, identifier: "nebula") - return completionHandler(NSError()) + return completionHandler("Starting tunnel failed: Could not determine file descriptor") } var ifnameSize = socklen_t(IFNAMSIZ) @@ -61,48 +72,41 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // This is set to 127.0.0.1 because it has to be something.. let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") - + // Make sure our ip is routed to the tun device var err: NSError? let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err) if (err != nil) { - let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.ParseCIDR - certificate") - self.wormhole.passMessageObject(msg, identifier: "nebula") - return completionHandler(err) + return completionHandler(err!) } tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR]) var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)] - + // Add our unsafe routes _site.unsafeRoutes.forEach { unsafeRoute in let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err) if (err != nil) { - let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.ParseCIDR - unsafe routes") - self.wormhole.passMessageObject(msg, identifier: "nebula") - return completionHandler(err) + return completionHandler(err!) } routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)) } - + tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes tunnelNetworkSettings.mtu = _site.mtu as NSNumber - wormhole.listenForMessage(withIdentifier: "app", listener: self.wormholeListener) self.setTunnelNetworkSettings(tunnelNetworkSettings, completionHandler: {(error:Error?) in if (error != nil) { - let msg = IPCMessage(id: _site.id, type: "error", message: error?.localizedDescription ?? "Unknown setTunnelNetworkSettings error") - self.wormhole.passMessageObject(msg, identifier: "nebula") - return completionHandler(error) + return completionHandler(error!) } - + var err: NSError? self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, Int(fileDescriptor), &err) + self.startNetworkMonitor() + if err != nil { - let msg = IPCMessage(id: _site.id, type: "error", message: err?.localizedDescription ?? "Unknown error from go MobileNebula.Main") - self.wormhole.passMessageObject(msg, identifier: "nebula") - return completionHandler(err) + return completionHandler(err!) } - + self.nebula!.start() completionHandler(nil) }) @@ -132,21 +136,80 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } private func pathUpdate(path: Network.NWPath) { - //TODO: we can likely be smarter here and enumerate all the interfaces and their current addresses, only rebind if things changed - nebula?.rebind("network change") + let routeDescription = collectAddresses(endpoints: path.gateways) + if routeDescription != cachedRouteDescription { + // Don't bother to rebind if we don't have any gateways + if routeDescription != "" { + nebula?.rebind("network change to: \(routeDescription); from: \(cachedRouteDescription ?? "none")") + } + cachedRouteDescription = routeDescription + } } - private func wormholeListener(msg: Any?) { - guard let call = msg as? IPCRequest else { + private func collectAddresses(endpoints: [Network.NWEndpoint]) -> String { + var str: [String] = [] + endpoints.forEach{ endpoint in + switch endpoint { + case let .hostPort(.ipv6(host), port): + str.append("[\(host)]:\(port)") + case let .hostPort(.ipv4(host), port): + str.append("\(host):\(port)") + default: + return + } + } + + return str.sorted().joined(separator: ", ") + } + + override func handleAppMessage(_ data: Data, completionHandler: ((Data?) -> Void)? = nil) { + guard let call = try? JSONDecoder().decode(IPCRequest.self, from: data) else { log("Failed to decode IPCRequest from network extension") return } var error: Error? - var data: Any? + var data: JSON? + + // start command has special treatment due to needing to call two completers + if call.command == "start" { + self.start() { error in + // Notify the system of our start result + if self.startCompleter != nil { + if error == nil { + // Clean boot, no errors + self.startCompleter!(nil) + + } else { + // We encountered an error, we can just pass NSError() here since ios throws it away + // But we will provide it in the event we can intercept the error without doing this workaround sometime in the future + self.startCompleter!(error!.localizedDescription) + } + } + + // Notify the UI if we have a completionHandler + if completionHandler != nil { + if error == nil { + // No response data, this is expected on a clean start + completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))) + + } else { + // Error response has + completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error!.localizedDescription)))) + } + } + } + return + } + + if nebula == nil { + // Respond with an empty success message in the event a command comes in before we've truly started + log("Received command but do not have a nebula instance") + return completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))) + } //TODO: try catch over all this - switch call.type { + switch call.command { case "listHostmap": (data, error) = listHostmap(pending: false) case "listPendingHostmap": (data, error) = listHostmap(pending: true) case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!) @@ -154,37 +217,37 @@ class PacketTunnelProvider: NEPacketTunnelProvider { case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!) default: - error = "Unknown IPC message type \(call.type)" + error = "Unknown IPC message type \(call.command)" } if (error != nil) { - self.wormhole.passMessageObject(IPCMessage(id: "", type: "error", message: error!.localizedDescription), identifier: call.callbackId) + completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))) } else { - self.wormhole.passMessageObject(IPCMessage(id: "", type: "success", message: data), identifier: call.callbackId) + completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))) } } - private func listHostmap(pending: Bool) -> (String?, Error?) { + private func listHostmap(pending: Bool) -> (JSON?, Error?) { var err: NSError? let res = nebula!.listHostmap(pending, error: &err) - return (res, err) + return (JSON(res), err) } - private func getHostInfo(args: Dictionary) -> (String?, Error?) { + private func getHostInfo(args: JSON) -> (JSON?, Error?) { var err: NSError? - let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"] as? String, pending: args["pending"] as! Bool, error: &err) - return (res, err) + let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"].string, pending: args["pending"].boolValue, error: &err) + return (JSON(res), err) } - private func setRemoteForTunnel(args: Dictionary) -> (String?, Error?) { + private func setRemoteForTunnel(args: JSON) -> (JSON?, Error?) { var err: NSError? - let res = nebula!.setRemoteForTunnel(args["vpnIp"] as? String, addr: args["addr"] as? String, error: &err) - return (res, err) + let res = nebula!.setRemoteForTunnel(args["vpnIp"].string, addr: args["addr"].string, error: &err) + return (JSON(res), err) } - private func closeTunnel(args: Dictionary) -> (Bool?, Error?) { - let res = nebula!.closeTunnel(args["vpnIp"] as? String) - return (res, nil) + private func closeTunnel(args: JSON) -> (JSON?, Error?) { + let res = nebula!.closeTunnel(args["vpnIp"].string) + return (JSON(res), nil) } } diff --git a/ios/NebulaNetworkExtension/Site.swift b/ios/NebulaNetworkExtension/Site.swift index 66a97bb..cf95aab 100644 --- a/ios/NebulaNetworkExtension/Site.swift +++ b/ios/NebulaNetworkExtension/Site.swift @@ -1,58 +1,36 @@ import NetworkExtension import MobileNebula +import SwiftyJSON extension String: Error {} -class IPCMessage: NSObject, NSCoding { - var id: String - var type: String - var message: Any? - - func encode(with aCoder: NSCoder) { - aCoder.encode(id, forKey: "id") - aCoder.encode(type, forKey: "type") - aCoder.encode(message, forKey: "message") - } +enum IPCResponseType: String, Codable { + case error = "error" + case success = "success" +} - required init(coder aDecoder: NSCoder) { - id = aDecoder.decodeObject(forKey: "id") as! String - type = aDecoder.decodeObject(forKey: "type") as! String - message = aDecoder.decodeObject(forKey: "message") as Any? - } +class IPCResponse: Codable { + var type: IPCResponseType + //TODO: change message to data? + var message: JSON? - init(id: String, type: String, message: Any) { - self.id = id + init(type: IPCResponseType, message: JSON?) { self.type = type self.message = message } } -class IPCRequest: NSObject, NSCoding { - var type: String - var callbackId: String - var arguments: Dictionary? +class IPCRequest: Codable { + var command: String + var arguments: JSON? - func encode(with aCoder: NSCoder) { - aCoder.encode(type, forKey: "type") - aCoder.encode(arguments, forKey: "arguments") - aCoder.encode(callbackId, forKey: "callbackId") - } - - required init(coder aDecoder: NSCoder) { - callbackId = aDecoder.decodeObject(forKey: "callbackId") as! String - type = aDecoder.decodeObject(forKey: "type") as! String - arguments = aDecoder.decodeObject(forKey: "arguments") as? Dictionary - } - - init(callbackId: String, type: String, arguments: Dictionary?) { - self.callbackId = callbackId - self.type = type + init(command: String, arguments: JSON?) { + self.command = command self.arguments = arguments } - init(callbackId: String, type: String) { - self.callbackId = callbackId - self.type = type + init(command: String) { + self.command = command } } @@ -135,7 +113,7 @@ let statusString: Dictionary = [ ] // Represents a site that was pulled out of the system configuration -struct Site: Codable { +class Site: Codable { // Stored in manager var name: String var id: String @@ -151,18 +129,17 @@ struct Site: Codable { var cipher: String var sortKey: Int var logVerbosity: String - var connected: Bool? + var connected: Bool? //TODO: active is a better name var status: String? var logFile: String? // A list of error encountered when trying to rehydrate a site from config var errors: [String] - // We initialize to avoid an error with Codable, there is probably a better way since manager must be present for a Site but is not codable - var manager: NETunnelProviderManager = NETunnelProviderManager() + var manager: NETunnelProviderManager? // Creates a new site from a vpn manager instance - init(manager: NETunnelProviderManager) throws { + convenience init(manager: NETunnelProviderManager) throws { //TODO: Throw an error and have Sites delete the site, notify the user instead of using ! let proto = manager.protocolConfiguration as! NETunnelProviderProtocol try self.init(proto: proto) @@ -171,7 +148,7 @@ struct Site: Codable { self.status = statusString[manager.connection.status] } - init(proto: NETunnelProviderProtocol) throws { + convenience init(proto: NETunnelProviderProtocol) throws { let dict = proto.providerConfiguration let config = dict?["config"] as? Data ?? Data() let decoder = JSONDecoder() diff --git a/ios/Podfile b/ios/Podfile index 0e7c4f0..f854d64 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -30,14 +30,14 @@ flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! - + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - pod 'MMWormhole', '~> 2.0.0' + pod 'SwiftyJSON', '~> 5.0' end target 'NebulaNetworkExtension' do - use_frameworks! - pod 'MMWormhole', '~> 2.0.0' + use_frameworks! + pod 'SwiftyJSON', '~> 5.0' end post_install do |installer| diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c04bec2..3757e9c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -39,9 +39,6 @@ PODS: - Flutter - FLAnimatedImage (1.0.12) - Flutter (1.0.0) - - MMWormhole (2.0.0): - - MMWormhole/Core (= 2.0.0) - - MMWormhole/Core (2.0.0) - MTBBarcodeScanner (5.0.11) - package_info (0.0.1): - Flutter @@ -54,6 +51,7 @@ PODS: - FLAnimatedImage (>= 1.0.11) - SDWebImage/Core (~> 5.6) - SwiftProtobuf (1.8.0) + - SwiftyJSON (5.0.1) - url_launcher (0.0.1): - Flutter @@ -61,9 +59,9 @@ DEPENDENCIES: - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - - MMWormhole (~> 2.0.0) - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) + - SwiftyJSON (~> 5.0) - url_launcher (from `.symlinks/plugins/url_launcher/ios`) SPEC REPOS: @@ -71,11 +69,11 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - FLAnimatedImage - - MMWormhole - MTBBarcodeScanner - SDWebImage - SDWebImageFLPlugin - SwiftProtobuf + - SwiftyJSON EXTERNAL SOURCES: barcode_scan: @@ -98,15 +96,15 @@ SPEC CHECKSUMS: file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c - MMWormhole: 0cd3fd35a9118b2e2d762b499f54eeaace0be791 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 SwiftProtobuf: 2cbd9409689b7df170d82a92a33443c8e3e14a70 + SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef -PODFILE CHECKSUM: e8d4fb1ed5b0713de2623a28dfae2585e15c0d00 +PODFILE CHECKSUM: 87c61886589bcc4c3c709db9ee22a607d81c4861 COCOAPODS: 1.10.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 122b8a9..2039150 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -23,10 +23,10 @@ 43AD63F424EB3802000FB47E /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AD63F324EB3802000FB47E /* Share.swift */; }; 4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 78E28476711DF3A9D186C429 /* Pods_NebulaNetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + E91B9DAD4A83866D0AF1DAE1 /* Pods_NebulaNetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -64,11 +64,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.debug.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 384887B4785D38431E800D3A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.debug.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.debug.xcconfig"; sourceTree = ""; }; 437F72582469AAC500A0C4B9 /* Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Site.swift; sourceTree = ""; }; 437F725C2469AC5700A0C4B9 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; 43871C9A2444DD39004F9075 /* MobileNebula.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MobileNebula.framework; sourceTree = ""; }; @@ -84,12 +84,14 @@ 43B66ECC245A146300B18C36 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 43B828DA249C08DC00CA229C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43E9BBD0251450C5000BFB8C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.profile.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.profile.xcconfig"; sourceTree = ""; }; + 5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NebulaNetworkExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8E4961BE2F06B97C8C693530 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NebulaNetworkExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.release.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -98,8 +100,6 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.release.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.release.xcconfig"; sourceTree = ""; }; - FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NebulaNetworkExtension.profile.xcconfig"; path = "Target Support Files/Pods-NebulaNetworkExtension/Pods-NebulaNetworkExtension.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -109,7 +109,7 @@ files = ( 43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */, 43871C9B2444DD39004F9075 /* MobileNebula.framework in Frameworks */, - 78E28476711DF3A9D186C429 /* Pods_NebulaNetworkExtension.framework in Frameworks */, + E91B9DAD4A83866D0AF1DAE1 /* Pods_NebulaNetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -135,7 +135,7 @@ 43B66ECA245A0C8400B18C36 /* CoreFoundation.framework */, 43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */, 384887B4785D38431E800D3A /* Pods_Runner.framework */, - 8EFDD7248CAE56012FE2608C /* Pods_NebulaNetworkExtension.framework */, + 5C0A96949A0B117C4ACE752C /* Pods_NebulaNetworkExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -217,9 +217,9 @@ C2D5198CF6975BF93E8A6F93 /* Pods-Runner.debug.xcconfig */, 6E7A71D8C71BF965D042667D /* Pods-Runner.release.xcconfig */, 8E4961BE2F06B97C8C693530 /* Pods-Runner.profile.xcconfig */, - 137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */, - E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */, - FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */, + 41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */, + 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */, + 53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -231,7 +231,7 @@ isa = PBXNativeTarget; buildConfigurationList = 43AA895D2444DA6500EDC39C /* Build configuration list for PBXNativeTarget "NebulaNetworkExtension" */; buildPhases = ( - D39D78EE128AD494ACEF8DC0 /* [CP] Check Pods Manifest.lock */, + 2C0A52E24BC9F327251CBAD2 /* [CP] Check Pods Manifest.lock */, 43AA89632444DAD100EDC39C /* ShellScript */, 43AA89502444DA6500EDC39C /* Sources */, 43AA89512444DA6500EDC39C /* Frameworks */, @@ -343,11 +343,11 @@ "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", "${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MMWormhole/MMWormhole.framework", "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImageFLPlugin/SDWebImageFLPlugin.framework", "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", @@ -359,11 +359,11 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MMWormhole.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageFLPlugin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework", @@ -375,6 +375,28 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 2C0A52E24BC9F327251CBAD2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NebulaNetworkExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -421,28 +443,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - D39D78EE128AD494ACEF8DC0 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-NebulaNetworkExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; FF0E0EB9A684F086443A8FBA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -579,7 +579,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = 576H3XS7FP; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -605,7 +605,7 @@ }; 43AA895E2444DA6500EDC39C /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 137DCAF9F91CD7AF6438A183 /* Pods-NebulaNetworkExtension.debug.xcconfig */; + baseConfigurationReference = 41927814D2E140A347A01067 /* Pods-NebulaNetworkExtension.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -614,7 +614,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = 576H3XS7FP; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -641,7 +641,7 @@ }; 43AA895F2444DA6500EDC39C /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E346A0DC829EBFB76D581AAD /* Pods-NebulaNetworkExtension.release.xcconfig */; + baseConfigurationReference = 9169E2D0D49FAF5172A6E7B8 /* Pods-NebulaNetworkExtension.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -650,7 +650,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = 576H3XS7FP; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -674,7 +674,7 @@ }; 43AA89602444DA6500EDC39C /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FA7B03A7901388BC39329544 /* Pods-NebulaNetworkExtension.profile.xcconfig */; + baseConfigurationReference = 53C42258A2092B55937DCF53 /* Pods-NebulaNetworkExtension.profile.xcconfig */; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -683,7 +683,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = NebulaNetworkExtension/NebulaNetworkExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = 576H3XS7FP; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -819,7 +819,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = 576H3XS7FP; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -852,7 +852,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_TEAM = 576H3XS7FP; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 5a344a8..6070a82 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,7 +2,7 @@ import UIKit import Flutter import MobileNebula import NetworkExtension -import MMWormhole +import SwiftyJSON enum ChannelName { static let vpn = "net.defined.mobileNebula/NebulaVpnService" @@ -15,7 +15,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { private var sites: Sites? - private var wormhole = MMWormhole(applicationGroupIdentifier: "group.net.defined.mobileNebula", optionalDirectory: "ipc") override func application( _ application: UIApplication, @@ -30,9 +29,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { sites = Sites(messenger: controller.binaryMessenger) let channel = FlutterMethodChannel(name: ChannelName.vpn, binaryMessenger: controller.binaryMessenger) - NSKeyedUnarchiver.setClass(IPCMessage.classForKeyedUnarchiver(), forClassName: "NebulaNetworkExtension.IPCMessage") - wormhole.listenForMessage(withIdentifier: "nebula", listener: self.wormholeListener) - channel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch call.method { case "nebula.parseCerts": return self.nebulaParseCerts(call: call, result: result) @@ -45,11 +41,11 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { case "startSite": return self.startSite(call: call, result: result) case "stopSite": return self.stopSite(call: call, result: result) - case "active.listHostmap": self.activeListHostmap(call: call, result: result) - case "active.listPendingHostmap": self.activeListPendingHostmap(call: call, result: result) - case "active.getHostInfo": self.activeGetHostInfo(call: call, result: result) - case "active.setRemoteForTunnel": self.activeSetRemoteForTunnel(call: call, result: result) - case "active.closeTunnel": self.activeCloseTunnel(call: call, result: result) + case "active.listHostmap": self.vpnRequest(command: "listHostmap", arguments: call.arguments, result: result) + case "active.listPendingHostmap": self.vpnRequest(command: "listPendingHostmap", arguments: call.arguments, result: result) + case "active.getHostInfo": self.vpnRequest(command: "getHostInfo", arguments: call.arguments, result: result) + case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result) + case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result) case "share": Share.share(call: call, result: result) case "shareFile": Share.shareFile(call: call, result: result) @@ -143,12 +139,14 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { func startSite(call: FlutterMethodCall, result: @escaping FlutterResult) { guard let args = call.arguments as? Dictionary else { return result(NoArgumentsError()) } guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } + #if targetEnvironment(simulator) let updater = self.sites?.getUpdater(id: id) updater?.update(connected: true) - #else - let manager = self.sites?.getSite(id: id)?.manager + let container = self.sites?.getContainer(id: id) + let manager = container?.site.manager + manager?.loadFromPreferences{ error in //TODO: Handle load error // This is silly but we need to enable the site each time to avoid situations where folks have multiple sites @@ -158,12 +156,13 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { manager?.loadFromPreferences{ error in //TODO: Handle load error do { - try manager?.connection.startVPNTunnel() + container?.updater.startFunc = {() -> Void in + return self.vpnRequest(command: "start", arguments: args, result: result) + } + try manager?.connection.startVPNTunnel(options: ["expectStart": NSNumber(1)]) } catch { return result(CallFailedError(message: "Could not start site", details: error.localizedDescription)) } - - return result(nil) } } } @@ -188,120 +187,46 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { #endif } - func activeListHostmap(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let args = call.arguments as? Dictionary else { return result(NoArgumentsError()) } - guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } - //TODO: match id for safety? - wormholeRequestWithCallback(type: "listHostmap", arguments: nil) { (data, err) -> () in - if (err != nil) { - return result(CallFailedError(message: err!.localizedDescription)) - } - - result(data) - } - } - - func activeListPendingHostmap(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let args = call.arguments as? Dictionary else { return result(NoArgumentsError()) } - guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } - //TODO: match id for safety? - wormholeRequestWithCallback(type: "listPendingHostmap", arguments: nil) { (data, err) -> () in - if (err != nil) { - return result(CallFailedError(message: err!.localizedDescription)) - } - - result(data) - } - } - - func activeGetHostInfo(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let args = call.arguments as? Dictionary else { return result(NoArgumentsError()) } + func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) { + guard let args = arguments as? Dictionary else { return result(NoArgumentsError()) } guard let id = args["id"] as? String else { return result(MissingArgumentError(message: "id is a required argument")) } - guard let vpnIp = args["vpnIp"] as? String else { return result(MissingArgumentError(message: "vpnIp is a required argument")) } - let pending = args["pending"] as? Bool ?? false + let container = sites?.getContainer(id: id) - //TODO: match id for safety? - wormholeRequestWithCallback(type: "getHostInfo", arguments: ["vpnIp": vpnIp, "pending": pending]) { (data, err) -> () in - if (err != nil) { - return result(CallFailedError(message: err!.localizedDescription)) - } - - result(data) - } - } - - func activeSetRemoteForTunnel(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let args = call.arguments as? Dictionary else { return result(NoArgumentsError()) } - guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } - guard let vpnIp = args["vpnIp"] else { return result(MissingArgumentError(message: "vpnIp is a required argument")) } - guard let addr = args["addr"] else { return result(MissingArgumentError(message: "addr is a required argument")) } - - //TODO: match id for safety? - wormholeRequestWithCallback(type: "setRemoteForTunnel", arguments: ["vpnIp": vpnIp, "addr": addr]) { (data, err) -> () in - if (err != nil) { - return result(CallFailedError(message: err!.localizedDescription)) - } - - result(data) - } - } - - func activeCloseTunnel(call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let args = call.arguments as? Dictionary else { return result(NoArgumentsError()) } - guard let id = args["id"] else { return result(MissingArgumentError(message: "id is a required argument")) } - guard let vpnIp = args["vpnIp"] else { return result(MissingArgumentError(message: "vpnIp is a required argument")) } - - //TODO: match id for safety? - wormholeRequestWithCallback(type: "closeTunnel", arguments: ["vpnIp": vpnIp]) { (data, err) -> () in - if (err != nil) { - return result(CallFailedError(message: err!.localizedDescription)) - } - - result(data as? Bool ?? false) - } - } - - func wormholeListener(msg: Any?) { - guard let call = msg as? IPCMessage else { - print("Failed to decode IPCMessage from network extension") - return + if container == nil { + // No site for this id + return result(nil) } - switch call.type { - case "error": - guard let updater = self.sites?.getUpdater(id: call.id) else { - return print("Could not find site to deliver error to \(call.id): \(String(describing: call.message))") - } - updater.setError(err: call.message as! String) - - default: - print("Unknown IPC message type \(call.type)") + if !(container!.site.connected ?? false) { + // Site isn't connected, no point in sending a command + return result(nil) } - } - - func wormholeRequestWithCallback(type: String, arguments: Dictionary?, completion: @escaping (Any?, Error?) -> ()) { - let uuid = UUID().uuidString - - wormhole.listenForMessage(withIdentifier: uuid) { msg -> () in - self.wormhole.stopListeningForMessage(withIdentifier: uuid) - - guard let call = msg as? IPCMessage else { - completion("", "Failed to decode IPCMessage callback from network extension") - return - } - - switch call.type { - case "error": - completion("", call.message as? String ?? "Failed to convert error") - case "success": - completion(call.message, nil) - - default: - completion("", "Unknown IPC message type \(call.type)") + + if let session = container!.site.manager?.connection as? NETunnelProviderSession { + do { + try session.sendProviderMessage(try JSONEncoder().encode(IPCRequest(command: command, arguments: JSON(args)))) { data in + if data == nil { + return result(nil) + } + + //print(String(decoding: data!, as: UTF8.self)) + guard let res = try? JSONDecoder().decode(IPCResponse.self, from: data!) else { + return result(CallFailedError(message: "Failed to decode response")) + } + + if res.type == .success { + return result(res.message?.object) + } + + return result(CallFailedError(message: res.message?.debugDescription ?? "Failed to convert error")) + } + } catch { + return result(CallFailedError(message: error.localizedDescription)) } + } else { + //TODO: we have a site without a manager, things have gone weird. How to handle since this shouldn't happen? + result(nil) } - - wormhole.passMessageObject(IPCRequest(callbackId: uuid, type: type, arguments: arguments), identifier: "app") } } diff --git a/ios/Runner/Sites.swift b/ios/Runner/Sites.swift index b653147..a8cf879 100644 --- a/ios/Runner/Sites.swift +++ b/ios/Runner/Sites.swift @@ -90,7 +90,7 @@ class Sites { try? fileManager.removeItem(at: sitePath) #else _ = KeyChain.delete(key: site.site.id) - site.site.manager.removeFromPreferences(completionHandler: callback) + site.site.manager!.removeFromPreferences(completionHandler: callback) #endif } @@ -105,6 +105,10 @@ class Sites { func getUpdater(id: String) -> SiteUpdater? { return self.sites[id]?.updater } + + func getContainer(id: String) -> SiteContainer? { + return self.sites[id] + } } class SiteUpdater: NSObject, FlutterStreamHandler { @@ -112,6 +116,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler { private var eventChannel: FlutterEventChannel; private var site: Site private var notification: Any? + public var startFunc: (() -> Void)? init(messenger: FlutterBinaryMessenger, site: Site) { eventChannel = FlutterEventChannel(name: "net.defined.nebula/\(site.id)", binaryMessenger: messenger) @@ -120,13 +125,20 @@ class SiteUpdater: NSObject, FlutterStreamHandler { eventChannel.setStreamHandler(self) } + /// onListen is called when flutter code attaches an event listener func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { eventSink = events; - self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager.connection , queue: nil) { _ in + self.notification = NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: site.manager!.connection , queue: nil) { n in + let connected = self.site.connected + self.site.status = statusString[self.site.manager!.connection.status] + self.site.connected = statusMap[self.site.manager!.connection.status] - self.site.status = statusString[self.site.manager.connection.status] - self.site.connected = statusMap[self.site.manager.connection.status] + // Check to see if we just moved to connected and if we have a start function to call when that happens + if self.site.connected! && connected != self.site.connected && self.startFunc != nil { + self.startFunc!() + self.startFunc = nil + } let d: Dictionary = [ "connected": self.site.connected!, @@ -137,15 +149,8 @@ class SiteUpdater: NSObject, FlutterStreamHandler { return nil } - - func setError(err: String) { - let d: Dictionary = [ - "connected": self.site.connected!, - "status": self.site.status!, - ] - self.eventSink?(FlutterError(code: "", message: err, details: d)) - } + /// onCancel is called when the flutter listener stops listening func onCancel(withArguments arguments: Any?) -> FlutterError? { if (self.notification != nil) { NotificationCenter.default.removeObserver(self.notification!) @@ -153,6 +158,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler { return nil } + /// update is a way to send information to the flutter listener and generally should not be used directly func update(connected: Bool) { let d: Dictionary = [ "connected": connected, diff --git a/lib/models/Site.dart b/lib/models/Site.dart index 06432a3..1d3606a 100644 --- a/lib/models/Site.dart +++ b/lib/models/Site.dart @@ -185,8 +185,7 @@ class Site { try { await platform.invokeMethod("startSite", {"id": id}); } on PlatformException catch (err) { - //TODO: fix this message - throw err.details ?? err.message ?? err.toString(); + throw err.message ?? err.toString(); } catch (err) { throw err.toString(); } diff --git a/lib/screens/SiteDetailScreen.dart b/lib/screens/SiteDetailScreen.dart index 7d8d84e..86b1600 100644 --- a/lib/screens/SiteDetailScreen.dart +++ b/lib/screens/SiteDetailScreen.dart @@ -50,7 +50,6 @@ class _SiteDetailScreenState extends State { } onChange = site.onChange().listen((_) { - setState(() {}); if (lastState != site.connected) { //TODO: connected is set before the nebula object exists leading to a crash race, waiting for "Connected" status is a gross hack but keeps it alive if (site.status == 'Connected') { @@ -62,6 +61,8 @@ class _SiteDetailScreenState extends State { pendingHosts = null; } } + setState(() {}); + }, onError: (err) { setState(() {}); Utils.popError(context, "Error", err); @@ -88,7 +89,7 @@ class _SiteDetailScreenState extends State { }), refreshController: refreshController, onRefresh: () async { - if (site.connected) { + if (site.connected && site.status == "Connected") { await _listHostmap(); } refreshController.refreshCompleted();