3
0
Fork 0

Rework ios ipc (#28)

This commit is contained in:
Nathan Brown 2021-04-27 10:29:28 -05:00 committed by GitHub
parent 4cad646a7c
commit 4c28cc196e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 259 additions and 290 deletions

View File

@ -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")
// 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
@ -38,17 +54,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
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)
@ -66,9 +77,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
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)]
@ -77,9 +86,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
_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))
}
@ -87,20 +94,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
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()
@ -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, Any>) -> (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, Any>) -> (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<String, Any>) -> (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)
}
}

View File

@ -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<String, Any>?
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<String, Any>
}
init(callbackId: String, type: String, arguments: Dictionary<String, Any>?) {
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<NEVPNStatus, String> = [
]
// 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()

View File

@ -32,12 +32,12 @@ target 'Runner' do
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'
pod 'SwiftyJSON', '~> 5.0'
end
post_install do |installer|

View File

@ -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

View File

@ -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 = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
437F72582469AAC500A0C4B9 /* Site.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Site.swift; sourceTree = "<group>"; };
437F725C2469AC5700A0C4B9 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
43871C9A2444DD39004F9075 /* MobileNebula.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MobileNebula.framework; sourceTree = "<group>"; };
@ -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 = "<group>"; };
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 = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
/* 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 = "<group>";
@ -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 = "<group>";
@ -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 = (

View File

@ -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<String, String> 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<String, String> 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<String, String> 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<String, Any> else { return result(NoArgumentsError()) }
func vpnRequest(command: String, arguments: Any?, result: @escaping FlutterResult) {
guard let args = arguments as? Dictionary<String, Any> 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))
if container == nil {
// No site for this id
return result(nil)
}
result(data)
}
if !(container!.site.connected ?? false) {
// Site isn't connected, no point in sending a command
return result(nil)
}
func activeSetRemoteForTunnel(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> 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))
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)
}
result(data)
}
//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"))
}
func activeCloseTunnel(call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? Dictionary<String, String> 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))
if res.type == .success {
return result(res.message?.object)
}
result(data as? Bool ?? false)
return result(CallFailedError(message: res.message?.debugDescription ?? "Failed to convert error"))
}
} catch {
return result(CallFailedError(message: error.localizedDescription))
}
func wormholeListener(msg: Any?) {
guard let call = msg as? IPCMessage else {
print("Failed to decode IPCMessage from network extension")
return
} else {
//TODO: we have a site without a manager, things have gone weird. How to handle since this shouldn't happen?
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)")
}
}
func wormholeRequestWithCallback(type: String, arguments: Dictionary<String, Any>?, 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)")
}
}
wormhole.passMessageObject(IPCRequest(callbackId: uuid, type: type, arguments: arguments), identifier: "app")
}
}

View File

@ -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<String, Any> = [
"connected": self.site.connected!,
@ -138,14 +150,7 @@ class SiteUpdater: NSObject, FlutterStreamHandler {
return nil
}
func setError(err: String) {
let d: Dictionary<String, Any> = [
"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<String, Any> = [
"connected": connected,

View File

@ -185,8 +185,7 @@ class Site {
try {
await platform.invokeMethod("startSite", <String, String>{"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();
}

View File

@ -50,7 +50,6 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
}
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<SiteDetailScreen> {
pendingHosts = null;
}
}
setState(() {});
}, onError: (err) {
setState(() {});
Utils.popError(context, "Error", err);
@ -88,7 +89,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
}),
refreshController: refreshController,
onRefresh: () async {
if (site.connected) {
if (site.connected && site.status == "Connected") {
await _listHostmap();
}
refreshController.refreshCompleted();