From b0d264889c9a4595d064e5991e45c3b4039b3ef5 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Fri, 24 Aug 2018 11:45:41 +0200 Subject: [PATCH] Extend PUSH_REPLY parsing - Topology - Routes Use the less confusing defaultGateway vs gatewayAddress. --- .../AppExtension/TunnelKitProvider.swift | 13 +- .../Sources/Core/SessionProxy+PushReply.swift | 147 ++++++++++++++++-- 2 files changed, 147 insertions(+), 13 deletions(-) diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 7706ab4..87136c1 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -459,7 +459,7 @@ extension TunnelKitProvider: SessionProxyDelegate { log.info("Returned ifconfig parameters:") log.info("\tRemote: \(remoteAddress)") log.info("\tLocal: \(reply.address)/\(reply.addressMask)") - log.info("\tGateway: \(reply.gatewayAddress)") + log.info("\tGateway: \(reply.defaultGateway)") log.info("\tDNS: \(reply.dnsServers)") bringNetworkUp(remoteAddress: remoteAddress, reply: reply) { (error) in @@ -493,10 +493,17 @@ extension TunnelKitProvider: SessionProxyDelegate { // route all traffic to VPN let defaultRoute = NEIPv4Route.default() - defaultRoute.gatewayAddress = reply.gatewayAddress + defaultRoute.gatewayAddress = reply.defaultGateway + + var routes: [NEIPv4Route] = [defaultRoute] + for r in reply.routes { + let ipv4Route = NEIPv4Route(destinationAddress: r.destination, subnetMask: r.mask) + ipv4Route.gatewayAddress = r.gateway ?? reply.defaultGateway + routes.append(ipv4Route) + } let ipv4Settings = NEIPv4Settings(addresses: [reply.address], subnetMasks: [reply.addressMask]) - ipv4Settings.includedRoutes = [defaultRoute] + ipv4Settings.includedRoutes = routes ipv4Settings.excludedRoutes = [] let dnsSettings = NEDNSSettings(servers: reply.dnsServers) diff --git a/TunnelKit/Sources/Core/SessionProxy+PushReply.swift b/TunnelKit/Sources/Core/SessionProxy+PushReply.swift index 2e55538..dabb4df 100644 --- a/TunnelKit/Sources/Core/SessionProxy+PushReply.swift +++ b/TunnelKit/Sources/Core/SessionProxy+PushReply.swift @@ -47,7 +47,10 @@ public protocol SessionReply { var addressMask: String { get } /// The address of the default gateway. - var gatewayAddress: String { get } + var defaultGateway: String { get } + + /// The additional routes. + var routes: [SessionProxy.Route] { get } /// The DNS servers set up for this session. var dnsServers: [String] { get } @@ -57,9 +60,42 @@ extension SessionProxy { // XXX: parsing is very optimistic + /// Represents a route in the routing table. + public struct Route { + + /// The destination host or subnet. + public let destination: String + + /// The address mask. + public let mask: String + + /// The address of the gateway (uses default gateway if not set). + public let gateway: String? + + fileprivate init(_ destination: String, _ mask: String?, _ gateway: String?) { + self.destination = destination + self.mask = mask ?? "255.255.255.255" + self.gateway = gateway + } + } + struct PushReply: SessionReply { + private enum Topology: String { + case net30 + + case p2p + + case subnet + } + + private static let topologyRegexp = try! NSRegularExpression(pattern: "topology (net30|p2p|subnet)", options: []) + private static let ifconfigRegexp = try! NSRegularExpression(pattern: "ifconfig [\\d\\.]+ [\\d\\.]+", options: []) + private static let gatewayRegexp = try! NSRegularExpression(pattern: "route-gateway [\\d\\.]+", options: []) + + private static let routeRegexp = try! NSRegularExpression(pattern: "route [\\d\\.]+( [\\d\\.]+){0,2}", options: []) + private static let dnsRegexp = try! NSRegularExpression(pattern: "dhcp-option DNS [\\d\\.]+", options: []) private static let authTokenRegexp = try! NSRegularExpression(pattern: "auth-token [a-zA-Z0-9/=+]+", options: []) @@ -70,7 +106,9 @@ extension SessionProxy { let addressMask: String - let gatewayAddress: String + let defaultGateway: String + + let routes: [Route] let dnsServers: [String] @@ -83,22 +121,84 @@ extension SessionProxy { return nil } - var ifconfigComponents: [String]? - var dnsServers = [String]() + var optTopologyComponents: [String]? + var optIfconfigComponents: [String]? + var optGatewayComponents: [String]? + + let address: String + let addressMask: String + let defaultGateway: String + var routes: [Route] = [] + var dnsServers: [String] = [] var authToken: String? var peerId: UInt32? + + // MARK: Routing + + PushReply.topologyRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + optTopologyComponents = match.components(separatedBy: " ") + } + guard let topologyComponents = optTopologyComponents, topologyComponents.count == 2 else { + throw SessionError.malformedPushReply + } + + // assumes "topology" to be always pushed to clients, even when not explicitly set (defaults to net30) + guard let topology = Topology(rawValue: topologyComponents[1]) else { + fatalError("Bad topology regexp, accepted unrecognized value: \(topologyComponents[1])") + } PushReply.ifconfigRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in guard let range = result?.range else { return } let match = (message as NSString).substring(with: range) - ifconfigComponents = match.components(separatedBy: " ") + optIfconfigComponents = match.components(separatedBy: " ") } - - guard let addresses = ifconfigComponents, addresses.count >= 2 else { + guard let ifconfigComponents = optIfconfigComponents, ifconfigComponents.count == 3 else { throw SessionError.malformedPushReply } + PushReply.gatewayRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + optGatewayComponents = match.components(separatedBy: " ") + } + + // + // excerpts from OpenVPN manpage + // + // "--ifconfig l rn": + // + // Set TUN/TAP adapter parameters. l is the IP address of the local VPN endpoint. For TUN devices in point-to-point mode, rn is the IP address of + // the remote VPN endpoint. For TAP devices, or TUN devices used with --topology subnet, rn is the subnet mask of the virtual network segment which + // is being created or connected to. + // + // "--topology mode": + // + // Note: Using --topology subnet changes the interpretation of the arguments of --ifconfig to mean "address netmask", no longer "local remote". + // + switch topology { + case .subnet: + + // default gateway required when topology is subnet + guard let gatewayComponents = optGatewayComponents, gatewayComponents.count == 2 else { + throw SessionError.malformedPushReply + } + address = ifconfigComponents[1] + addressMask = ifconfigComponents[2] + defaultGateway = gatewayComponents[1] + + default: + address = ifconfigComponents[1] + addressMask = "255.255.255.255" + defaultGateway = ifconfigComponents[2] + } + + // MARK: DNS + PushReply.dnsRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in guard let range = result?.range else { return } @@ -108,6 +208,32 @@ extension SessionProxy { dnsServers.append(dnsEntryComponents[2]) } + // MARK: Routes + + PushReply.routeRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in + guard let range = result?.range else { return } + + let match = (message as NSString).substring(with: range) + let routeEntryComponents = match.components(separatedBy: " ") + + let destination = routeEntryComponents[1] + let mask: String? + let gateway: String? + if routeEntryComponents.count > 2 { + mask = routeEntryComponents[2] + } else { + mask = nil + } + if routeEntryComponents.count > 3 { + gateway = routeEntryComponents[3] + } else { + gateway = defaultGateway + } + routes.append(Route(destination, mask, gateway)) + } + + // MARK: Authentication + PushReply.authTokenRegexp.enumerateMatches(in: message, options: [], range: NSMakeRange(0, message.count)) { (result, flags, _) in guard let range = result?.range else { return } @@ -130,10 +256,11 @@ extension SessionProxy { } } - address = addresses[1] - addressMask = "255.255.255.255" - gatewayAddress = addresses[2] + self.address = address + self.addressMask = addressMask + self.defaultGateway = defaultGateway self.dnsServers = dnsServers + self.routes = routes self.authToken = authToken self.peerId = peerId }