Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #7 from keeshux/enhance-routing
Browse files Browse the repository at this point in the history
Enhance routing
  • Loading branch information
keeshux authored Aug 30, 2018
2 parents 2f7d41a + b075841 commit 4dd9bd1
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 33 deletions.
4 changes: 4 additions & 0 deletions TunnelKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
0E1108B31F77B9F900A92462 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B21F77B9F900A92462 /* Assets.xcassets */; };
0E1108B61F77B9F900A92462 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B41F77B9F900A92462 /* LaunchScreen.storyboard */; };
0E245D6C2137F73600B012A2 /* CompressionFraming.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E245D6B2137F73600B012A2 /* CompressionFraming.h */; };
0E245D692135972800B012A2 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E245D682135972800B012A2 /* PushTests.swift */; };
0E3E0F212108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; };
0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; };
0E85A25A202CC5AF0059E9F9 /* AppExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */; };
Expand Down Expand Up @@ -188,6 +189,7 @@
0E1108B71F77B9F900A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0E17D7F91F730D9F009EE129 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0E245D6B2137F73600B012A2 /* CompressionFraming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressionFraming.h; sourceTree = "<group>"; };
0E245D682135972800B012A2 /* PushTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushTests.swift; sourceTree = "<group>"; };
0E3251C51F95770D00C108D9 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PushReply.swift"; sourceTree = "<group>"; };
0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -312,6 +314,7 @@
0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */,
0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */,
0EB2B45820F0BD9A004233D7 /* LinkTests.swift */,
0E245D682135972800B012A2 /* PushTests.swift */,
0EB2B45620F0BD16004233D7 /* RandomTests.swift */,
0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */,
0EB2B45A20F0BE4C004233D7 /* TestUtils.swift */,
Expand Down Expand Up @@ -813,6 +816,7 @@
0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */,
0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */,
0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */,
0E245D692135972800B012A2 /* PushTests.swift in Sources */,
0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */,
0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */,
0EE7A7A120F664AC00B42E6A /* DataPathEncryptionTests.swift in Sources */,
Expand Down
31 changes: 19 additions & 12 deletions TunnelKit/Sources/AppExtension/TunnelKitProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -451,18 +451,18 @@ extension TunnelKitProvider: SessionProxyDelegate {
// MARK: SessionProxyDelegate (tunnel queue)

/// :nodoc:
public func sessionDidStart(_ proxy: SessionProxy, remoteAddress: String, address: String, gatewayAddress: String, dnsServers: [String]) {
public func sessionDidStart(_ proxy: SessionProxy, remoteAddress: String, reply: SessionReply) {
reasserting = false

log.info("Session did start")

log.info("Returned ifconfig parameters:")
log.info("\tTunnel: \(remoteAddress)")
log.info("\tOwn address: \(address)")
log.info("\tGateway: \(gatewayAddress)")
log.info("\tDNS: \(dnsServers)")
log.info("\tRemote: \(remoteAddress)")
log.info("\tLocal: \(reply.address)/\(reply.addressMask)")
log.info("\tGateway: \(reply.defaultGateway)")
log.info("\tDNS: \(reply.dnsServers)")

bringNetworkUp(tunnel: remoteAddress, vpn: address, gateway: gatewayAddress, dnsServers: dnsServers) { (error) in
bringNetworkUp(remoteAddress: remoteAddress, reply: reply) { (error) in
if let error = error {
log.error("Failed to configure tunnel: \(error)")
self.pendingStartHandler?(error)
Expand All @@ -489,19 +489,26 @@ extension TunnelKitProvider: SessionProxyDelegate {
socket?.shutdown()
}

private func bringNetworkUp(tunnel: String, vpn: String, gateway: String, dnsServers: [String], completionHandler: @escaping (Error?) -> Void) {
private func bringNetworkUp(remoteAddress: String, reply: SessionReply, completionHandler: @escaping (Error?) -> Void) {

// route all traffic to VPN
let defaultRoute = NEIPv4Route.default()
defaultRoute.gatewayAddress = gateway
defaultRoute.gatewayAddress = reply.defaultGateway

let ipv4Settings = NEIPv4Settings(addresses: [vpn], subnetMasks: ["255.255.255.255"])
ipv4Settings.includedRoutes = [defaultRoute]
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 = routes
ipv4Settings.excludedRoutes = []

let dnsSettings = NEDNSSettings(servers: dnsServers)
let dnsSettings = NEDNSSettings(servers: reply.dnsServers)

let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: tunnel)
let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
newSettings.ipv4Settings = ipv4Settings
newSettings.dnsSettings = dnsSettings

Expand Down
167 changes: 158 additions & 9 deletions TunnelKit/Sources/Core/SessionProxy+PushReply.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,65 @@

import Foundation

/// Represents the reply of a successful session start.
public protocol SessionReply {

/// The obtained address.
var address: String { get }

/// The obtained address mask.
var addressMask: String { get }

/// The address of the default gateway.
var defaultGateway: String { get }

/// The additional routes.
var routes: [SessionProxy.Route] { get }

/// The DNS servers set up for this session.
var dnsServers: [String] { get }
}

extension SessionProxy {
struct PushReply {

// 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: [])
Expand All @@ -49,7 +104,11 @@ extension SessionProxy {

let address: String

let gatewayAddress: String
let addressMask: String

let defaultGateway: String

let routes: [Route]

let dnsServers: [String]

Expand All @@ -62,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 }

Expand All @@ -87,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 }

Expand All @@ -109,9 +256,11 @@ extension SessionProxy {
}
}

address = addresses[1]
gatewayAddress = addresses[2]
self.address = address
self.addressMask = addressMask
self.defaultGateway = defaultGateway
self.dnsServers = dnsServers
self.routes = routes
self.authToken = authToken
self.peerId = peerId
}
Expand Down
15 changes: 3 additions & 12 deletions TunnelKit/Sources/Core/SessionProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ public protocol SessionProxyDelegate: class {
Called after starting a session.
- Parameter remoteAddress: The address of the VPN server.
- Parameter address: The obtained address.
- Parameter gatewayAddress: The address of the gateway.
- Parameter dnsServers: The DNS servers set up for this session.
- Parameter reply: The compound `SessionReply` containing tunnel settings.
*/
func sessionDidStart(_: SessionProxy, remoteAddress: String, address: String, gatewayAddress: String, dnsServers: [String])
func sessionDidStart(_: SessionProxy, remoteAddress: String, reply: SessionReply)

/**
Called after stopping a session.
Expand Down Expand Up @@ -905,14 +903,7 @@ public class SessionProxy {
guard let remoteAddress = link?.remoteAddress else {
fatalError("Could not resolve link remote address")
}

delegate?.sessionDidStart(
self,
remoteAddress: remoteAddress,
address: reply.address,
gatewayAddress: reply.gatewayAddress,
dnsServers: reply.dnsServers
)
delegate?.sessionDidStart(self, remoteAddress: remoteAddress, reply: reply)

if let interval = configuration.keepAliveInterval {
queue.asyncAfter(deadline: .now() + interval) { [weak self] in
Expand Down
Loading

0 comments on commit 4dd9bd1

Please sign in to comment.