Review feedback

This commit is contained in:
Ian VanSchooten 2024-10-18 11:19:16 -04:00
parent 0fde99a34f
commit e317cb5aec
1 changed files with 101 additions and 109 deletions

View File

@ -3,6 +3,12 @@ import MobileNebula
import os.log import os.log
import SwiftyJSON import SwiftyJSON
enum VPNStartError: Error {
case noManagers
case couldNotFindManager
case noTunFileDescriptor
}
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
private var networkMonitor: NWPathMonitor? private var networkMonitor: NWPathMonitor?
@ -13,61 +19,65 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var didSleep = false private var didSleep = false
private var cachedRouteDescription: String? private var cachedRouteDescription: String?
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { override func startTunnel(options: [String : NSObject]? = nil) async throws {
// There is currently no way to get initialization errors back to the UI via completionHandler here // 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 // `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
if options?["expectStart"] != nil { if options?["expectStart"] != nil {
// The system completion handler must be called before IPC will work // startTunnel must complete before IPC will work
completionHandler(nil)
return 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 // 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 // success/fail by the presence of an error or nil
start(completionHandler: completionHandler) try await start()
} }
private func start(completionHandler: @escaping (Error?) -> Void) { private func findManager() async throws -> NETunnelProviderManager {
// Load vpn configs from system, and find the manager matching the one being started
NETunnelProviderManager.loadAllFromPreferences() { managers, error in
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol; let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol;
let targetID = targetProtoConfig?.providerConfiguration!["id"] as? String; let targetID = targetProtoConfig?.providerConfiguration!["id"] as? String;
guard let managers = managers else { // Load vpn configs from system, and find the manager matching the one being started
self.log.error("No managers were loaded") let managers = try await NETunnelProviderManager.loadAllFromPreferences()
return for manager in managers {
let mgrProtoConfig = manager.protocolConfiguration as? NETunnelProviderProtocol;
let id = mgrProtoConfig?.providerConfiguration!["id"] as? String;
if (id == targetID) {
return manager
}
} }
for manager in managers { // If we didn't find anything, throw an error
throw VPNStartError.noManagers
}
private func start() async throws {
var manager: NETunnelProviderManager?
var config: Data var config: Data
var key: String var key: String
let mgr = manager.protocolConfiguration as? NETunnelProviderProtocol; manager = try await self.findManager()
let id = mgr?.providerConfiguration!["id"] as? String;
guard let foundManager = manager else {
throw VPNStartError.couldNotFindManager
}
if (id == targetID) {
do { do {
self.site = try Site(manager: manager) self.site = try Site(manager: foundManager)
config = try self.site!.getConfig() config = try self.site!.getConfig()
} catch { } catch {
//TODO: need a way to notify the app //TODO: need a way to notify the app
self.log.error("Failed to render config from vpn object") self.log.error("Failed to render config from vpn object")
return completionHandler(error) throw error
} }
let _site = self.site! let _site = self.site!
do {
key = try _site.getKey() key = try _site.getKey()
} catch {
return completionHandler(error)
}
let fileDescriptor = self.tunnelFileDescriptor guard let fileDescriptor = self.tunnelFileDescriptor else {
if fileDescriptor == nil { throw VPNStartError.noTunFileDescriptor
return completionHandler("Unable to locate the tun file descriptor")
} }
let tunFD = Int(fileDescriptor!) let tunFD = Int(fileDescriptor)
// This is set to 127.0.0.1 because it has to be something.. // This is set to 127.0.0.1 because it has to be something..
let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1") let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
@ -76,16 +86,16 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var err: NSError? var err: NSError?
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err) let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
if (err != nil) { if (err != nil) {
return completionHandler(err!) throw err!
} }
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR]) tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)] var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)]
// Add our unsafe routes // Add our unsafe routes
_site.unsafeRoutes.forEach { unsafeRoute in try _site.unsafeRoutes.forEach { unsafeRoute in
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err) let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
if (err != nil) { if (err != nil) {
return completionHandler(err!) throw err!
} }
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)) routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
} }
@ -93,31 +103,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
tunnelNetworkSettings.mtu = _site.mtu as NSNumber tunnelNetworkSettings.mtu = _site.mtu as NSNumber
self.setTunnelNetworkSettings(tunnelNetworkSettings, completionHandler: {(error:Error?) in try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
if (error != nil) { var nebulaErr: NSError?
return completionHandler(error!) self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &nebulaErr)
}
var err: NSError?
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &err)
self.startNetworkMonitor() self.startNetworkMonitor()
if err != nil { if nebulaErr != nil {
self.log.error("We had an error starting up: \(err, privacy: .public)") self.log.error("We had an error starting up: \(nebulaErr, privacy: .public)")
return completionHandler(err!) throw nebulaErr!
} }
self.nebula!.start() self.nebula!.start()
self.dnUpdater.updateSingleLoop(site: self.site!, onUpdate: self.handleDNUpdate) self.dnUpdater.updateSingleLoop(site: self.site!, onUpdate: self.handleDNUpdate)
completionHandler(nil)
})
// We're done looking through managers
continue
}
}
}
} }
private func handleDNUpdate(newSite: Site) { private func handleDNUpdate(newSite: Site) {
@ -180,10 +177,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return str.sorted().joined(separator: ", ") return str.sorted().joined(separator: ", ")
} }
override func handleAppMessage(_ data: Data, completionHandler: ((Data?) -> Void)? = nil) { override func handleAppMessage(_ data: Data) async -> Data? {
guard let call = try? JSONDecoder().decode(IPCRequest.self, from: data) else { guard let call = try? JSONDecoder().decode(IPCRequest.self, from: data) else {
log.error("Failed to decode IPCRequest from network extension") log.error("Failed to decode IPCRequest from network extension")
return return nil
} }
var error: Error? var error: Error?
@ -191,27 +188,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
// start command has special treatment due to needing to call two completers // start command has special treatment due to needing to call two completers
if call.command == "start" { if call.command == "start" {
self.start() { error in do {
// Notify the UI if we have a completionHandler try await self.start()
if completionHandler != nil {
if error == nil {
// No response data, this is expected on a clean start // No response data, this is expected on a clean start
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))) return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))
} catch {
} else { defer {
// We failed, notify and shutdown
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error!.localizedDescription))))
self.cancelTunnelWithError(error) self.cancelTunnelWithError(error)
} }
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error.localizedDescription)))
} }
} }
return
}
if nebula == nil { if nebula == nil {
// Respond with an empty success message in the event a command comes in before we've truly started // Respond with an empty success message in the event a command comes in before we've truly started
log.warning("Received command but do not have a nebula instance") log.warning("Received command but do not have a nebula instance")
return completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))) return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))
} }
//TODO: try catch over all this //TODO: try catch over all this
@ -227,9 +219,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
if (error != nil) { if (error != nil) {
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))) return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
} else { } else {
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))) return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))
} }
} }