Compare commits
No commits in common. "b0331ed1119b5037e885618b62277f8f77be8196" and "0fde99a34f4c425befabf0d6598ad2346b79f387" have entirely different histories.
b0331ed111
...
0fde99a34f
|
@ -3,13 +3,6 @@ import MobileNebula
|
||||||
import os.log
|
import os.log
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
enum VPNStartError: Error {
|
|
||||||
case noManagers
|
|
||||||
case couldNotFindManager
|
|
||||||
case noTunFileDescriptor
|
|
||||||
case noProviderConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private var networkMonitor: NWPathMonitor?
|
private var networkMonitor: NWPathMonitor?
|
||||||
|
|
||||||
|
@ -20,83 +13,111 @@ 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]? = nil) async throws {
|
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
|
// 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 {
|
||||||
// startTunnel must complete before IPC will work
|
// The system completion handler must be called 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
|
||||||
try await start()
|
start(completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func start() async throws {
|
private func start(completionHandler: @escaping (Error?) -> Void) {
|
||||||
var manager: NETunnelProviderManager?
|
// Load vpn configs from system, and find the manager matching the one being started
|
||||||
var config: Data
|
NETunnelProviderManager.loadAllFromPreferences() { managers, error in
|
||||||
var key: String
|
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol;
|
||||||
|
let targetID = targetProtoConfig?.providerConfiguration!["id"] as? String;
|
||||||
manager = try await self.findManager()
|
|
||||||
|
guard let managers = managers else {
|
||||||
guard let foundManager = manager else {
|
self.log.error("No managers were loaded")
|
||||||
throw VPNStartError.couldNotFindManager
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
for manager in managers {
|
||||||
self.site = try Site(manager: foundManager)
|
var config: Data
|
||||||
config = try self.site!.getConfig()
|
var key: String
|
||||||
} catch {
|
|
||||||
//TODO: need a way to notify the app
|
let mgr = manager.protocolConfiguration as? NETunnelProviderProtocol;
|
||||||
self.log.error("Failed to render config from vpn object")
|
let id = mgr?.providerConfiguration!["id"] as? String;
|
||||||
throw error
|
|
||||||
}
|
if (id == targetID) {
|
||||||
|
do {
|
||||||
let _site = self.site!
|
self.site = try Site(manager: manager)
|
||||||
key = try _site.getKey()
|
config = try self.site!.getConfig()
|
||||||
|
} catch {
|
||||||
guard let fileDescriptor = self.tunnelFileDescriptor else {
|
//TODO: need a way to notify the app
|
||||||
throw VPNStartError.noTunFileDescriptor
|
self.log.error("Failed to render config from vpn object")
|
||||||
}
|
return completionHandler(error)
|
||||||
let tunFD = Int(fileDescriptor)
|
}
|
||||||
|
|
||||||
// This is set to 127.0.0.1 because it has to be something..
|
let _site = self.site!
|
||||||
let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
|
|
||||||
|
do {
|
||||||
// Make sure our ip is routed to the tun device
|
key = try _site.getKey()
|
||||||
var err: NSError?
|
} catch {
|
||||||
let ipNet = MobileNebulaParseCIDR(_site.cert!.cert.details.ips[0], &err)
|
return completionHandler(error)
|
||||||
if (err != nil) {
|
}
|
||||||
throw err!
|
|
||||||
}
|
let fileDescriptor = self.tunnelFileDescriptor
|
||||||
tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: [ipNet!.ip], subnetMasks: [ipNet!.maskCIDR])
|
if fileDescriptor == nil {
|
||||||
var routes: [NEIPv4Route] = [NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR)]
|
return completionHandler("Unable to locate the tun file descriptor")
|
||||||
|
}
|
||||||
// Add our unsafe routes
|
let tunFD = Int(fileDescriptor!)
|
||||||
try _site.unsafeRoutes.forEach { unsafeRoute in
|
|
||||||
let ipNet = MobileNebulaParseCIDR(unsafeRoute.route, &err)
|
// This is set to 127.0.0.1 because it has to be something..
|
||||||
if (err != nil) {
|
let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
|
||||||
throw err!
|
|
||||||
|
// 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) {
|
||||||
|
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) {
|
||||||
|
return completionHandler(err!)
|
||||||
|
}
|
||||||
|
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
|
||||||
|
tunnelNetworkSettings.mtu = _site.mtu as NSNumber
|
||||||
|
|
||||||
|
self.setTunnelNetworkSettings(tunnelNetworkSettings, completionHandler: {(error:Error?) in
|
||||||
|
if (error != nil) {
|
||||||
|
return completionHandler(error!)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err: NSError?
|
||||||
|
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &err)
|
||||||
|
self.startNetworkMonitor()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
self.log.error("We had an error starting up: \(err, privacy: .public)")
|
||||||
|
return completionHandler(err!)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nebula!.start()
|
||||||
|
self.dnUpdater.updateSingleLoop(site: self.site!, onUpdate: self.handleDNUpdate)
|
||||||
|
|
||||||
|
completionHandler(nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
// We're done looking through managers
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
routes.append(NEIPv4Route(destinationAddress: ipNet!.network, subnetMask: ipNet!.maskCIDR))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelNetworkSettings.ipv4Settings!.includedRoutes = routes
|
|
||||||
tunnelNetworkSettings.mtu = _site.mtu as NSNumber
|
|
||||||
|
|
||||||
try await self.setTunnelNetworkSettings(tunnelNetworkSettings)
|
|
||||||
var nebulaErr: NSError?
|
|
||||||
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, tunFD, &nebulaErr)
|
|
||||||
self.startNetworkMonitor()
|
|
||||||
|
|
||||||
if nebulaErr != nil {
|
|
||||||
self.log.error("We had an error starting up: \(nebulaErr, privacy: .public)")
|
|
||||||
throw nebulaErr!
|
|
||||||
}
|
|
||||||
|
|
||||||
self.nebula!.start()
|
|
||||||
self.dnUpdater.updateSingleLoop(site: self.site!, onUpdate: self.handleDNUpdate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleDNUpdate(newSite: Site) {
|
private func handleDNUpdate(newSite: Site) {
|
||||||
|
@ -115,30 +136,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
// completionHandler()
|
// completionHandler()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private func findManager() async throws -> NETunnelProviderManager {
|
|
||||||
let targetProtoConfig = self.protocolConfiguration as? NETunnelProviderProtocol
|
|
||||||
guard let targetProviderConfig = targetProtoConfig?.providerConfiguration else {
|
|
||||||
throw VPNStartError.noProviderConfig
|
|
||||||
}
|
|
||||||
let targetID = targetProviderConfig["id"] as? String
|
|
||||||
|
|
||||||
// Load vpn configs from system, and find the manager matching the one being started
|
|
||||||
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
|
||||||
for manager in managers {
|
|
||||||
let mgrProtoConfig = manager.protocolConfiguration as? NETunnelProviderProtocol
|
|
||||||
guard let mgrProviderConfig = mgrProtoConfig?.providerConfiguration else {
|
|
||||||
throw VPNStartError.noProviderConfig
|
|
||||||
}
|
|
||||||
let id = mgrProviderConfig["id"] as? String
|
|
||||||
if (id == targetID) {
|
|
||||||
return manager
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't find anything, throw an error
|
|
||||||
throw VPNStartError.noManagers
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startNetworkMonitor() {
|
private func startNetworkMonitor() {
|
||||||
networkMonitor = NWPathMonitor()
|
networkMonitor = NWPathMonitor()
|
||||||
networkMonitor!.pathUpdateHandler = self.pathUpdate
|
networkMonitor!.pathUpdateHandler = self.pathUpdate
|
||||||
|
@ -183,10 +180,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
return str.sorted().joined(separator: ", ")
|
return str.sorted().joined(separator: ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleAppMessage(_ data: Data) async -> Data? {
|
override func handleAppMessage(_ data: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
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 nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var error: Error?
|
var error: Error?
|
||||||
|
@ -194,22 +191,27 @@ 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" {
|
||||||
do {
|
self.start() { error in
|
||||||
try await self.start()
|
// Notify the UI if we have a completionHandler
|
||||||
// No response data, this is expected on a clean start
|
if completionHandler != nil {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))
|
if error == nil {
|
||||||
} catch {
|
// No response data, this is expected on a clean start
|
||||||
defer {
|
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil)))
|
||||||
self.cancelTunnelWithError(error)
|
|
||||||
|
} else {
|
||||||
|
// We failed, notify and shutdown
|
||||||
|
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error!.localizedDescription))))
|
||||||
|
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 try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil))
|
return completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: try catch over all this
|
//TODO: try catch over all this
|
||||||
|
@ -225,9 +227,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error != nil) {
|
if (error != nil) {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error")))
|
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .error, message: JSON(error?.localizedDescription ?? "Unknown error"))))
|
||||||
} else {
|
} else {
|
||||||
return try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data))
|
completionHandler!(try? JSONEncoder().encode(IPCResponse.init(type: .success, message: data)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -489,8 +489,6 @@ struct IncomingSite: Codable {
|
||||||
// Stuff our details in the protocol
|
// Stuff our details in the protocol
|
||||||
let proto = manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
|
let proto = manager.protocolConfiguration as? NETunnelProviderProtocol ?? NETunnelProviderProtocol()
|
||||||
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension";
|
proto.providerBundleIdentifier = "net.defined.mobileNebula.NebulaNetworkExtension";
|
||||||
// WARN: If we stop setting providerConfiguration["id"] here, we'll need to use something else to match
|
|
||||||
// managers in PacketTunnelProvider.findManager
|
|
||||||
proto.providerConfiguration = ["id": self.id]
|
proto.providerConfiguration = ["id": self.id]
|
||||||
proto.serverAddress = "Nebula"
|
proto.serverAddress = "Nebula"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue