VPN: Bring up the tunnel

The app figures out all settings and passes them in the
'options' parameter of startTunnel().

The network extension just takes them as is and just plugs the supplied
values into the right places.

Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
Roopesh Chander
2018-10-27 19:07:16 +05:30
parent a08e08fe2e
commit 793bf63989
5 changed files with 306 additions and 71 deletions
@@ -6,7 +6,9 @@ import NetworkExtension
import os.log
enum PacketTunnelProviderError: Error {
case tunnelSetupFailed
case invalidOptions
case couldNotStartWireGuard
case coultNotSetNetworkSettings
}
/// A packet tunnel provider object.
@@ -17,81 +19,100 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var wgHandle: Int32?
private var wgContext: WireGuardContext?
private var config: NETunnelProviderProtocol {
return self.protocolConfiguration as! NETunnelProviderProtocol // swiftlint:disable:this force_cast
}
private var interfaceName: String {
return config.providerConfiguration![PCKeys.title.rawValue]! as! String // swiftlint:disable:this force_cast
}
private var settings: String {
return config.providerConfiguration![PCKeys.settings.rawValue]! as! String // swiftlint:disable:this force_cast
}
// MARK: NEPacketTunnelProvider
/// Begin the process of establishing the tunnel.
override func startTunnel(options: [String: NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
os_log("Starting tunnel", log: Log.general, type: .info)
override func startTunnel(options: [String: NSObject]?,
completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
os_log("Starting tunnel", log: OSLog.default, type: .info)
let validatedEndpoints = (config.providerConfiguration?[PCKeys.endpoints.rawValue] as? String ?? "").commaSeparatedToArray().compactMap { try? Endpoint(endpointString: String($0)) }.compactMap {$0}
let validatedAddresses = (config.providerConfiguration?[PCKeys.addresses.rawValue] as? String ?? "").commaSeparatedToArray().compactMap { try? CIDRAddress(stringRepresentation: String($0)) }.compactMap { $0 }
guard let firstEndpoint = validatedEndpoints.first else {
startTunnelCompletionHandler(PacketTunnelProviderError.tunnelSetupFailed)
guard let options = options else {
startTunnelCompletionHandler(PacketTunnelProviderError.invalidOptions)
return
}
guard let interfaceName = options[.interfaceName] as? String,
let wireguardSettings = options[.wireguardSettings] as? String,
let remoteAddress = options[.remoteAddress] as? String,
let dnsServers = options[.dnsServers] as? [String],
let mtu = options[.mtu] as? NSNumber,
// IPv4 settings
let ipv4Addresses = options[.ipv4Addresses] as? [String],
let ipv4SubnetMasks = options[.ipv4SubnetMasks] as? [String],
let ipv4IncludedRouteAddresses = options[.ipv4IncludedRouteAddresses] as? [String],
let ipv4IncludedRouteSubnetMasks = options[.ipv4IncludedRouteSubnetMasks] as? [String],
let ipv4ExcludedRouteAddresses = options[.ipv4ExcludedRouteAddresses] as? [String],
let ipv4ExcludedRouteSubnetMasks = options[.ipv4ExcludedRouteSubnetMasks] as? [String],
// IPv6 settings
let ipv6Addresses = options[.ipv6Addresses] as? [String],
let ipv6NetworkPrefixLengths = options[.ipv6NetworkPrefixLengths] as? [NSNumber],
let ipv6IncludedRouteAddresses = options[.ipv6IncludedRouteAddresses] as? [String],
let ipv6IncludedRouteNetworkPrefixLengths = options[.ipv6IncludedRouteNetworkPrefixLengths] as? [NSNumber],
let ipv6ExcludedRouteAddresses = options[.ipv6ExcludedRouteAddresses] as? [String],
let ipv6ExcludedRouteNetworkPrefixLengths = options[.ipv6ExcludedRouteNetworkPrefixLengths] as? [NSNumber]
else {
startTunnelCompletionHandler(PacketTunnelProviderError.invalidOptions)
return
}
configureLogger()
wgContext = WireGuardContext(packetFlow: self.packetFlow)
let handle = connect(interfaceName: interfaceName, settings: settings)
let handle = connect(interfaceName: interfaceName, settings: wireguardSettings, mtu: mtu.uint16Value)
if handle < 0 {
startTunnelCompletionHandler(PacketTunnelProviderError.tunnelSetupFailed)
startTunnelCompletionHandler(PacketTunnelProviderError.couldNotStartWireGuard)
return
}
wgHandle = handle
// We use the first endpoint for the ipAddress
let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: firstEndpoint.ipAddress)
newSettings.tunnelOverheadBytes = 80
// Network settings
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
// IPv4 settings
let validatedIPv4Addresses = validatedAddresses.filter { $0.addressType == .IPv4}
if validatedIPv4Addresses.count > 0 {
let ipv4Settings = NEIPv4Settings(addresses: validatedIPv4Addresses.map { $0.ipAddress }, subnetMasks: validatedIPv4Addresses.map { $0.subnetString })
ipv4Settings.includedRoutes = [NEIPv4Route.default()]
ipv4Settings.excludedRoutes = validatedEndpoints.filter { $0.addressType == .IPv4}.map {
NEIPv4Route(destinationAddress: $0.ipAddress, subnetMask: "255.255.255.255")}
newSettings.ipv4Settings = ipv4Settings
let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses, subnetMasks: ipv4SubnetMasks)
assert(ipv4IncludedRouteAddresses.count == ipv4IncludedRouteSubnetMasks.count)
ipv4Settings.includedRoutes = zip(ipv4IncludedRouteAddresses, ipv4IncludedRouteSubnetMasks).map {
NEIPv4Route(destinationAddress: $0.0, subnetMask: $0.1)
}
assert(ipv4ExcludedRouteAddresses.count == ipv4ExcludedRouteSubnetMasks.count)
ipv4Settings.excludedRoutes = zip(ipv4ExcludedRouteAddresses, ipv4ExcludedRouteSubnetMasks).map {
NEIPv4Route(destinationAddress: $0.0, subnetMask: $0.1)
}
networkSettings.ipv4Settings = ipv4Settings
// IPv6 settings
let validatedIPv6Addresses = validatedAddresses.filter { $0.addressType == .IPv6}
if validatedIPv6Addresses.count > 0 {
let ipv6Settings = NEIPv6Settings(addresses: validatedIPv6Addresses.map { $0.ipAddress }, networkPrefixLengths: validatedIPv6Addresses.map { NSNumber(value: $0.subnet) })
ipv6Settings.includedRoutes = [NEIPv6Route.default()]
ipv6Settings.excludedRoutes = validatedEndpoints.filter { $0.addressType == .IPv6 }.map { NEIPv6Route(destinationAddress: $0.ipAddress, networkPrefixLength: 128) }
let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses, networkPrefixLengths: ipv6NetworkPrefixLengths)
assert(ipv6IncludedRouteAddresses.count == ipv6IncludedRouteNetworkPrefixLengths.count)
ipv6Settings.includedRoutes = zip(ipv6IncludedRouteAddresses, ipv6IncludedRouteNetworkPrefixLengths).map {
NEIPv6Route(destinationAddress: $0.0, networkPrefixLength: $0.1)
}
assert(ipv6ExcludedRouteAddresses.count == ipv6ExcludedRouteNetworkPrefixLengths.count)
ipv6Settings.excludedRoutes = zip(ipv6ExcludedRouteAddresses, ipv6ExcludedRouteNetworkPrefixLengths).map {
NEIPv6Route(destinationAddress: $0.0, networkPrefixLength: $0.1)
}
networkSettings.ipv6Settings = ipv6Settings
newSettings.ipv6Settings = ipv6Settings
// DNS
networkSettings.dnsSettings = NEDNSSettings(servers: dnsServers)
// MTU
if (mtu == 0) {
// 0 imples automatic MTU, where we set overhead as 95 bytes,
// 80 for WireGuard and the 15 to make sure WireGuard's padding will work.
networkSettings.tunnelOverheadBytes = 95
} else {
networkSettings.mtu = mtu
}
if let dns = config.providerConfiguration?[PCKeys.dns.rawValue] as? String {
newSettings.dnsSettings = NEDNSSettings(servers: dns.commaSeparatedToArray())
}
if let mtu = config.providerConfiguration![PCKeys.mtu.rawValue] as? NSNumber, mtu.intValue > 0 {
newSettings.mtu = mtu
}
setTunnelNetworkSettings(newSettings) { (error) in
setTunnelNetworkSettings(networkSettings) { (error) in
if let error = error {
os_log("Error setting network settings: %s", log: Log.general, type: .error, error.localizedDescription)
startTunnelCompletionHandler(PacketTunnelProviderError.tunnelSetupFailed)
os_log("Error setting network settings: %s", log: OSLog.default, type: .error, error.localizedDescription)
startTunnelCompletionHandler(PacketTunnelProviderError.coultNotSetNetworkSettings)
} else {
startTunnelCompletionHandler(nil /* No errors */)
}
@@ -100,7 +121,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
/// Begin the process of stopping the tunnel.
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
os_log("Stopping tunnel", log: Log.general, type: .info)
os_log("Stopping tunnel", log: OSLog.default, type: .info)
if let handle = wgHandle {
wgTurnOff(handle)
}
@@ -108,22 +129,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler()
}
/// Handle IPC messages from the app.
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
let responseData: Data?
let message = ExtensionMessage(messageData)
switch message {
case ExtensionMessage.requestVersion:
responseData = (wgVersion().flatMap { String(cString: $0) } ?? "").data(using: .utf8)
default:
responseData = nil
}
completionHandler?(responseData)
}
private func configureLogger() {
wgSetLogger { (level, tagCStr, msgCStr) in
let logType: OSLogType
@@ -139,14 +144,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
}
let tag = (tagCStr != nil) ? String(cString: tagCStr!) : ""
let msg = (msgCStr != nil) ? String(cString: msgCStr!) : ""
os_log("wg log: %{public}s: %{public}s", log: Log.general, type: logType, tag, msg)
os_log("wg log: %{public}s: %{public}s", log: OSLog.default, type: logType, tag, msg)
}
}
private func connect(interfaceName: String, settings: String) -> Int32 { // swiftlint:disable:this cyclomatic_complexity
private func connect(interfaceName: String, settings: String, mtu: UInt16) -> Int32 { // swiftlint:disable:this cyclomatic_complexity
return withStringsAsGoStrings(interfaceName, settings) { (nameGoStr, settingsGoStr) -> Int32 in
return withUnsafeMutablePointer(to: &wgContext) { (wgCtxPtr) -> Int32 in
return wgTurnOn(nameGoStr, settingsGoStr, { (wgCtxPtr, buf, len) -> Int in
return wgTurnOn(nameGoStr, settingsGoStr, mtu, { (wgCtxPtr, buf, len) -> Int in
autoreleasepool {
// read_fn: Read from the TUN interface and pass it on to WireGuard
guard let wgCtxPtr = wgCtxPtr else { return 0 }