175 lines
7.7 KiB
Swift
175 lines
7.7 KiB
Swift
import NetworkExtension
|
|
import MobileNebula
|
|
import os.log
|
|
import MMWormhole
|
|
|
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
|
private var networkMonitor: NWPathMonitor?
|
|
private var ifname: String?
|
|
|
|
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 func log(_ message: StaticString, _ args: CVarArg...) {
|
|
os_log(message, log: _log, args)
|
|
}
|
|
|
|
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
|
NSKeyedUnarchiver.setClass(IPCRequest.classForKeyedUnarchiver(), forClassName: "Runner.IPCRequest")
|
|
let proto = self.protocolConfiguration as! NETunnelProviderProtocol
|
|
var config: Data
|
|
var key: String
|
|
|
|
do {
|
|
config = proto.providerConfiguration?["config"] as! Data
|
|
site = try Site(proto: proto)
|
|
} catch {
|
|
//TODO: need a way to notify the app
|
|
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)
|
|
}
|
|
|
|
self.networkMonitor = NWPathMonitor()
|
|
self.networkMonitor!.pathUpdateHandler = self.pathUpdate
|
|
self.networkMonitor!.start(queue: DispatchQueue(label: "NetworkMonitor"))
|
|
|
|
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())
|
|
}
|
|
|
|
var ifnameSize = socklen_t(IFNAMSIZ)
|
|
let ifnamePtr = UnsafeMutablePointer<CChar>.allocate(capacity: Int(ifnameSize))
|
|
ifnamePtr.initialize(repeating: 0, count: Int(ifnameSize))
|
|
if getsockopt(fileDescriptor, 2 /* SYSPROTO_CONTROL */, 2 /* UTUN_OPT_IFNAME */, ifnamePtr, &ifnameSize) == 0 {
|
|
self.ifname = String(cString: ifnamePtr)
|
|
}
|
|
ifnamePtr.deallocate()
|
|
|
|
// 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)
|
|
}
|
|
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)
|
|
}
|
|
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)
|
|
}
|
|
|
|
var err: NSError?
|
|
self.nebula = MobileNebulaNewNebula(String(data: config, encoding: .utf8), key, self.site!.logFile, Int(fileDescriptor), &err)
|
|
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)
|
|
}
|
|
|
|
self.nebula!.start()
|
|
completionHandler(nil)
|
|
})
|
|
}
|
|
|
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
nebula?.stop()
|
|
networkMonitor?.cancel()
|
|
networkMonitor = nil
|
|
completionHandler()
|
|
}
|
|
|
|
private func pathUpdate(path: Network.NWPath) {
|
|
nebula?.rebind()
|
|
}
|
|
|
|
private func wormholeListener(msg: Any?) {
|
|
guard let call = msg as? IPCRequest else {
|
|
log("Failed to decode IPCRequest from network extension")
|
|
return
|
|
}
|
|
|
|
var error: Error?
|
|
var data: Any?
|
|
|
|
//TODO: try catch over all this
|
|
switch call.type {
|
|
case "listHostmap": (data, error) = listHostmap(pending: false)
|
|
case "listPendingHostmap": (data, error) = listHostmap(pending: true)
|
|
case "getHostInfo": (data, error) = getHostInfo(args: call.arguments!)
|
|
case "setRemoteForTunnel": (data, error) = setRemoteForTunnel(args: call.arguments!)
|
|
case "closeTunnel": (data, error) = closeTunnel(args: call.arguments!)
|
|
|
|
default:
|
|
error = "Unknown IPC message type \(call.type)"
|
|
}
|
|
|
|
if (error != nil) {
|
|
self.wormhole.passMessageObject(IPCMessage(id: "", type: "error", message: error!.localizedDescription), identifier: call.callbackId)
|
|
} else {
|
|
self.wormhole.passMessageObject(IPCMessage(id: "", type: "success", message: data), identifier: call.callbackId)
|
|
}
|
|
}
|
|
|
|
private func listHostmap(pending: Bool) -> (String?, Error?) {
|
|
var err: NSError?
|
|
let res = nebula!.listHostmap(pending, error: &err)
|
|
return (res, err)
|
|
}
|
|
|
|
private func getHostInfo(args: Dictionary<String, Any>) -> (String?, Error?) {
|
|
var err: NSError?
|
|
let res = nebula!.getHostInfo(byVpnIp: args["vpnIp"] as? String, pending: args["pending"] as! Bool, error: &err)
|
|
return (res, err)
|
|
}
|
|
|
|
private func setRemoteForTunnel(args: Dictionary<String, Any>) -> (String?, Error?) {
|
|
var err: NSError?
|
|
let res = nebula!.setRemoteForTunnel(args["vpnIp"] as? String, addr: args["addr"] as? String, error: &err)
|
|
return (res, err)
|
|
}
|
|
|
|
private func closeTunnel(args: Dictionary<String, Any>) -> (Bool?, Error?) {
|
|
let res = nebula!.closeTunnel(args["vpnIp"] as? String)
|
|
return (res, nil)
|
|
}
|
|
}
|
|
|