Parse PUSH_REPLY options in OptionsBundle

- auth-token
- peer-id
- Routing

Reorganize options by semantic.

Reuse OptionsBundle in PushReply.
This commit is contained in:
Davide De Rosa 2019-04-03 10:48:25 +02:00
parent b9b9c4db60
commit 9876c81de5
5 changed files with 470 additions and 567 deletions

View File

@ -463,9 +463,9 @@ extension TunnelKitProvider: SessionProxyDelegate {
log.info("Returned ifconfig parameters:")
log.info("\tRemote: \(remoteAddress.maskedDescription)")
log.info("\tIPv4: \(reply.ipv4?.description ?? "not configured")")
log.info("\tIPv6: \(reply.ipv6?.description ?? "not configured")")
log.info("\tDNS: \(reply.dnsServers.map { $0.maskedDescription })")
log.info("\tIPv4: \(reply.options.ipv4?.description ?? "not configured")")
log.info("\tIPv6: \(reply.options.ipv6?.description ?? "not configured")")
log.info("\tDNS: \(reply.options.dnsServers.map { $0.maskedDescription })")
bringNetworkUp(remoteAddress: remoteAddress, reply: reply) { (error) in
if let error = error {
@ -477,7 +477,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
log.info("Tunnel interface is now UP")
proxy.setTunnel(tunnel: NETunnelInterface(impl: self.packetFlow, isIPv6: reply.ipv6 != nil))
proxy.setTunnel(tunnel: NETunnelInterface(impl: self.packetFlow, isIPv6: reply.options.ipv6 != nil))
self.pendingStartHandler?(nil)
self.pendingStartHandler = nil
@ -502,7 +502,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
// route all traffic to VPN
var ipv4Settings: NEIPv4Settings?
if let ipv4 = reply.ipv4 {
if let ipv4 = reply.options.ipv4 {
let defaultRoute = NEIPv4Route.default()
defaultRoute.gatewayAddress = ipv4.defaultGateway
@ -519,7 +519,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
}
var ipv6Settings: NEIPv6Settings?
if let ipv6 = reply.ipv6 {
if let ipv6 = reply.options.ipv6 {
let defaultRoute = NEIPv6Route.default()
defaultRoute.gatewayAddress = ipv6.defaultGateway
@ -535,7 +535,7 @@ extension TunnelKitProvider: SessionProxyDelegate {
ipv6Settings?.excludedRoutes = []
}
let dnsSettings = NEDNSSettings(servers: cfg.sessionConfiguration.dnsServers ?? reply.dnsServers)
let dnsSettings = NEDNSSettings(servers: cfg.sessionConfiguration.dnsServers ?? reply.options.dnsServers)
let newSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: remoteAddress)
newSettings.ipv4Settings = ipv4Settings

View File

@ -30,9 +30,9 @@ import __TunnelKitNative
private let log = SwiftyBeaver.self
public struct OptionsBundle {
private struct Regex {
struct Regex {
// shared
// MARK: General
static let cipher = NSRegularExpression("^cipher +[^,\\s]+")
@ -42,6 +42,8 @@ public struct OptionsBundle {
static let compress = NSRegularExpression("^compress.*")
static let keyDirection = NSRegularExpression("^key-direction +\\d")
static let ping = NSRegularExpression("^ping +\\d+")
static let renegSec = NSRegularExpression("^reneg-sec +\\d+")
@ -50,19 +52,7 @@ public struct OptionsBundle {
static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>")
static let keyDirection = NSRegularExpression("^key-direction +\\d")
static let gateway = NSRegularExpression("route-gateway [\\d\\.]+")
static let route = NSRegularExpression("route [\\d\\.]+( [\\d\\.]+){0,2}")
static let route6 = NSRegularExpression("route-ipv6 [\\da-fA-F:]+/\\d+( [\\da-fA-F:]+){0,2}")
static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+")
static let remoteRandom = NSRegularExpression("^remote-random")
// client
// MARK: Client
static let proto = NSRegularExpression("^proto +(udp6?|tcp6?)")
@ -72,19 +62,31 @@ public struct OptionsBundle {
static let eku = NSRegularExpression("^remote-cert-tls +server")
// server
static let remoteRandom = NSRegularExpression("^remote-random")
static let topology = NSRegularExpression("topology (net30|p2p|subnet)")
// MARK: Server
static let ifconfig = NSRegularExpression("ifconfig [\\d\\.]+ [\\d\\.]+")
static let authToken = NSRegularExpression("^auth-token +[a-zA-Z0-9/=+]+")
static let ifconfig6 = NSRegularExpression("ifconfig-ipv6 [\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")
static let peerId = NSRegularExpression("^peer-id +[0-9]+")
static let authToken = NSRegularExpression("auth-token [a-zA-Z0-9/=+]+")
// MARK: Routing
static let peerId = NSRegularExpression("peer-id [0-9]+")
static let topology = NSRegularExpression("^topology +(net30|p2p|subnet)")
// unsupported
static let ifconfig = NSRegularExpression("^ifconfig +[\\d\\.]+ [\\d\\.]+")
static let ifconfig6 = NSRegularExpression("^ifconfig-ipv6 +[\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")
static let route = NSRegularExpression("^route +[\\d\\.]+( +[\\d\\.]+){0,2}")
static let route6 = NSRegularExpression("^route-ipv6 +[\\da-fA-F:]+/\\d+( +[\\da-fA-F:]+){0,2}")
static let gateway = NSRegularExpression("^route-gateway +[\\d\\.]+")
static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+")
// MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+")
static let fragment = NSRegularExpression("^fragment")
@ -96,15 +98,19 @@ public struct OptionsBundle {
static let connection = NSRegularExpression("^<connection>")
}
private enum Topology: String {
case net30
case p2p
case subnet
}
public let strippedLines: [String]?
public let warning: OptionsError?
//
public let hostname: String?
public let remotes: [(String, UInt16, SocketType)]
// MARK: General
public let cipher: SessionProxy.Cipher?
@ -120,27 +126,43 @@ public struct OptionsBundle {
public let clientKey: CryptoContainer?
public let checksEKU: Bool
public let tlsWrap: SessionProxy.TLSWrap?
public let keepAliveSeconds: TimeInterval?
public let renegotiateAfterSeconds: TimeInterval?
public let tlsWrap: SessionProxy.TLSWrap?
// MARK: Client
public let dnsServers: [String]
public let hostname: String?
public let remotes: [(String, UInt16, SocketType)]
public let checksEKU: Bool
public let randomizeEndpoint: Bool
// MARK: Server
public let authToken: String?
public let peerId: UInt32?
// MARK: Routing
public let ipv4: IPv4Settings?
public let ipv6: IPv6Settings?
public let dnsServers: [String]
public init(from lines: [String], returnsStripped: Bool = false) throws {
var optStrippedLines: [String]? = returnsStripped ? [] : nil
var optWarning: OptionsError?
var unsupportedError: OptionsError?
var currentBlockName: String?
var currentBlock: [String] = []
var optHostname: String?
var optDefaultProto: SocketType?
var optDefaultPort: UInt16?
var optRemotes: [(String, UInt16?, SocketType?)] = []
var optCipher: SessionProxy.Cipher?
var optDigest: SessionProxy.Digest?
var optCompressionFraming: SessionProxy.CompressionFraming?
@ -148,16 +170,30 @@ public struct OptionsBundle {
var optCA: CryptoContainer?
var optClientCertificate: CryptoContainer?
var optClientKey: CryptoContainer?
var optChecksEKU: Bool?
var optKeepAliveSeconds: TimeInterval?
var optRenegotiateAfterSeconds: TimeInterval?
var optKeyDirection: StaticKey.Direction?
var optTLSKeyLines: [Substring]?
var optTLSStrategy: SessionProxy.TLSWrap.Strategy?
var optDnsServers: [String] = []
var optKeepAliveSeconds: TimeInterval?
var optRenegotiateAfterSeconds: TimeInterval?
//
var optHostname: String?
var optDefaultProto: SocketType?
var optDefaultPort: UInt16?
var optRemotes: [(String, UInt16?, SocketType?)] = [] // address, port, socket
var optChecksEKU: Bool?
var optRandomizeEndpoint: Bool?
var currentBlockName: String?
var currentBlock: [String] = []
//
var optAuthToken: String?
var optPeerId: UInt32?
//
var optTopology: String?
var optIfconfig4Arguments: [String]?
var optIfconfig6Arguments: [String]?
var optGateway4Arguments: [String]?
var optGateway6Arguments: [String]?
var optRoutes4: [(String, String, String?)] = [] // address, netmask, gateway
var optRoutes6: [(String, UInt8, String?)] = [] // destination, prefix, gateway
var optDNSServers: [String] = []
log.verbose("Configuration file:")
for line in lines {
@ -171,10 +207,26 @@ public struct OptionsBundle {
}
}
// MARK: Unsupported
// check blocks first
Regex.connection.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "<connection> blocks")
}
Regex.fragment.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "fragment")
}
Regex.proxy.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "proxy: \"\(line)\"")
}
Regex.externalFiles.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "external file: \"\(line)\"")
}
if line.contains("mtu") || line.contains("mssfix") {
isHandled = true
}
// MARK: Inline content
if unsupportedError == nil {
if currentBlockName == nil {
@ -232,47 +284,8 @@ public struct OptionsBundle {
continue
}
Regex.eku.enumerateComponents(in: line) { (_) in
optChecksEKU = true
}
Regex.proto.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
return
}
optDefaultProto = SocketType(protoString: str)
if optDefaultProto == nil {
unsupportedError = OptionsError.unsupportedConfiguration(option: "proto \(str)")
}
}
Regex.port.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
return
}
optDefaultPort = UInt16(str)
}
Regex.remote.enumerateArguments(in: line) {
isHandled = true
guard let hostname = $0.first else {
return
}
var port: UInt16?
var proto: SocketType?
var strippedComponents = ["remote", "<hostname>"]
if $0.count > 1 {
port = UInt16($0[1])
strippedComponents.append($0[1])
}
if $0.count > 2 {
proto = SocketType(protoString: $0[2])
strippedComponents.append($0[2])
}
optRemotes.append((hostname, port, proto))
// MARK: General
// replace private data
strippedLine = strippedComponents.joined(separator: " ")
}
Regex.cipher.enumerateArguments(in: line) {
isHandled = true
guard let rawValue = $0.first else {
@ -349,29 +362,111 @@ public struct OptionsBundle {
}
optRenegotiateAfterSeconds = TimeInterval(arg)
}
// MARK: Client
Regex.proto.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
return
}
optDefaultProto = SocketType(protoString: str)
if optDefaultProto == nil {
unsupportedError = OptionsError.unsupportedConfiguration(option: "proto \(str)")
}
}
Regex.port.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
return
}
optDefaultPort = UInt16(str)
}
Regex.remote.enumerateArguments(in: line) {
isHandled = true
guard let hostname = $0.first else {
return
}
var port: UInt16?
var proto: SocketType?
var strippedComponents = ["remote", "<hostname>"]
if $0.count > 1 {
port = UInt16($0[1])
strippedComponents.append($0[1])
}
if $0.count > 2 {
proto = SocketType(protoString: $0[2])
strippedComponents.append($0[2])
}
optRemotes.append((hostname, port, proto))
// replace private data
strippedLine = strippedComponents.joined(separator: " ")
}
Regex.eku.enumerateComponents(in: line) { (_) in
optChecksEKU = true
}
Regex.remoteRandom.enumerateComponents(in: line) { (_) in
optRandomizeEndpoint = true
}
// MARK: Server
Regex.authToken.enumerateArguments(in: line) {
optAuthToken = $0[0]
}
Regex.peerId.enumerateArguments(in: line) {
optPeerId = UInt32($0[0])
}
// MARK: Routing
Regex.topology.enumerateArguments(in: line) {
optTopology = $0.first
}
Regex.ifconfig.enumerateArguments(in: line) {
optIfconfig4Arguments = $0
}
Regex.ifconfig6.enumerateArguments(in: line) {
optIfconfig6Arguments = $0
}
Regex.route.enumerateArguments(in: line) {
let routeEntryArguments = $0
let address = routeEntryArguments[0]
let mask = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : "255.255.255.255"
let gateway = (routeEntryArguments.count > 2) ? routeEntryArguments[2] : nil // defaultGateway4
optRoutes4.append((address, mask, gateway))
}
Regex.route6.enumerateArguments(in: line) {
let routeEntryArguments = $0
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
guard destinationComponents.count == 2 else {
return
}
guard let prefix = UInt8(destinationComponents[1]) else {
return
}
let destination = destinationComponents[0]
let gateway = (routeEntryArguments.count > 1) ? routeEntryArguments[1] : nil // defaultGateway6
optRoutes6.append((destination, prefix, gateway))
}
Regex.gateway.enumerateArguments(in: line) {
optGateway4Arguments = $0
}
Regex.dns.enumerateArguments(in: line) {
isHandled = true
guard $0.count == 2 else {
return
}
optDnsServers.append($0[1])
}
Regex.remoteRandom.enumerateComponents(in: line) { (_) in
optRandomizeEndpoint = true
}
Regex.fragment.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "fragment")
}
Regex.proxy.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "proxy: \"\(line)\"")
}
Regex.externalFiles.enumerateComponents(in: line) { (_) in
unsupportedError = OptionsError.unsupportedConfiguration(option: "external file: \"\(line)\"")
}
if line.contains("mtu") || line.contains("mssfix") {
isHandled = true
optDNSServers.append($0[1])
}
//
if let error = unsupportedError {
throw error
}
@ -382,6 +477,39 @@ public struct OptionsBundle {
strippedLines = optStrippedLines
warning = optWarning
// MARK: General
cipher = optCipher
digest = optDigest
compressionFraming = optCompressionFraming
compressionAlgorithm = optCompressionAlgorithm
ca = optCA
clientCertificate = optClientCertificate
clientKey = optClientKey
if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy {
let optKey: StaticKey?
switch strategy {
case .auth:
optKey = StaticKey(lines: keyLines, direction: optKeyDirection)
case .crypt:
optKey = StaticKey(lines: keyLines, direction: .client)
}
if let key = optKey {
tlsWrap = SessionProxy.TLSWrap(strategy: strategy, key: key)
} else {
tlsWrap = nil
}
} else {
tlsWrap = nil
}
keepAliveSeconds = optKeepAliveSeconds
renegotiateAfterSeconds = optRenegotiateAfterSeconds
// MARK: Client
optDefaultProto = optDefaultProto ?? .udp
optDefaultPort = optDefaultPort ?? 1194
if !optRemotes.isEmpty {
@ -407,36 +535,94 @@ public struct OptionsBundle {
remotes = []
}
cipher = optCipher
digest = optDigest
compressionFraming = optCompressionFraming
compressionAlgorithm = optCompressionAlgorithm
ca = optCA
clientCertificate = optClientCertificate
clientKey = optClientKey
checksEKU = optChecksEKU ?? false
keepAliveSeconds = optKeepAliveSeconds
renegotiateAfterSeconds = optRenegotiateAfterSeconds
dnsServers = optDnsServers
randomizeEndpoint = optRandomizeEndpoint ?? false
if let keyLines = optTLSKeyLines, let strategy = optTLSStrategy {
let optKey: StaticKey?
switch strategy {
case .auth:
optKey = StaticKey(lines: keyLines, direction: optKeyDirection)
// MARK: Server
case .crypt:
optKey = StaticKey(lines: keyLines, direction: .client)
authToken = optAuthToken
peerId = optPeerId
// MARK: Routing
//
// 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".
//
if let ifconfig4Arguments = optIfconfig4Arguments {
guard ifconfig4Arguments.count == 2 else {
throw SessionError.malformedPushReply
}
if let key = optKey {
tlsWrap = SessionProxy.TLSWrap(strategy: strategy, key: key)
let address4: String
let addressMask4: String
let defaultGateway4: String
let topology = Topology(rawValue: optTopology ?? "") ?? .net30
switch topology {
case .subnet:
// default gateway required when topology is subnet
guard let gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else {
throw SessionError.malformedPushReply
}
address4 = ifconfig4Arguments[0]
addressMask4 = ifconfig4Arguments[1]
defaultGateway4 = gateway4Arguments[0]
default:
address4 = ifconfig4Arguments[0]
addressMask4 = "255.255.255.255"
defaultGateway4 = ifconfig4Arguments[1]
}
let routes4 = optRoutes4.map { IPv4Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway4) }
ipv4 = IPv4Settings(
address: address4,
addressMask: addressMask4,
defaultGateway: defaultGateway4,
routes: routes4
)
} else {
tlsWrap = nil
ipv4 = nil
}
if let ifconfig6Arguments = optIfconfig6Arguments {
guard ifconfig6Arguments.count == 2 else {
throw SessionError.malformedPushReply
}
let address6Components = ifconfig6Arguments[0].components(separatedBy: "/")
guard address6Components.count == 2 else {
throw SessionError.malformedPushReply
}
guard let addressPrefix6 = UInt8(address6Components[1]) else {
throw SessionError.malformedPushReply
}
let address6 = address6Components[0]
let defaultGateway6 = ifconfig6Arguments[1]
let routes6 = optRoutes6.map { IPv6Settings.Route($0.0, $0.1, $0.2 ?? defaultGateway6) }
ipv6 = IPv6Settings(
address: address6,
addressPrefixLength: addressPrefix6,
defaultGateway: defaultGateway6,
routes: routes6
)
} else {
tlsWrap = nil
ipv6 = nil
}
dnsServers = optDNSServers
}
private static func normalizeEncryptedPEMBlock(block: inout [String]) {
@ -453,6 +639,104 @@ public struct OptionsBundle {
}
}
/// Encapsulates the IPv4 settings for the tunnel.
public struct IPv4Settings: CustomStringConvertible {
/// Represents an IPv4 route in the routing table.
public struct Route: CustomStringConvertible {
/// 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
}
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "{\(destination.maskedDescription)/\(mask) \(gateway?.maskedDescription ?? "default")}"
}
}
/// The address.
let address: String
/// The address mask.
let addressMask: String
/// The address of the default gateway.
let defaultGateway: String
/// The additional routes.
let routes: [Route]
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "addr \(address.maskedDescription) netmask \(addressMask) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
}
}
/// Encapsulates the IPv6 settings for the tunnel.
public struct IPv6Settings: CustomStringConvertible {
/// Represents an IPv6 route in the routing table.
public struct Route: CustomStringConvertible {
/// The destination host or subnet.
public let destination: String
/// The address prefix length.
public let prefixLength: UInt8
/// The address of the gateway (uses default gateway if not set).
public let gateway: String?
fileprivate init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) {
self.destination = destination
self.prefixLength = prefixLength ?? 3
self.gateway = gateway
}
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "default")}"
}
}
/// The address.
public let address: String
/// The address prefix length.
public let addressPrefixLength: UInt8
/// The address of the default gateway.
public let defaultGateway: String
/// The additional routes.
public let routes: [Route]
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "addr \(address.maskedDescription)/\(addressPrefixLength) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
}
}
private extension SocketType {
init?(protoString: String) {
var str = protoString

View File

@ -37,133 +37,11 @@
import Foundation
/// Encapsulates the IPv4 settings for the tunnel.
public struct IPv4Settings: CustomStringConvertible {
/// Represents an IPv4 route in the routing table.
public struct Route: CustomStringConvertible {
/// 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
}
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "{\(destination.maskedDescription)/\(mask) \(gateway?.maskedDescription ?? "default")}"
}
}
/// The address.
let address: String
/// The address mask.
let addressMask: String
/// The address of the default gateway.
let defaultGateway: String
/// The additional routes.
let routes: [Route]
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "addr \(address.maskedDescription) netmask \(addressMask) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
}
}
/// Encapsulates the IPv6 settings for the tunnel.
public struct IPv6Settings: CustomStringConvertible {
/// Represents an IPv6 route in the routing table.
public struct Route: CustomStringConvertible {
/// The destination host or subnet.
public let destination: String
/// The address prefix length.
public let prefixLength: UInt8
/// The address of the gateway (uses default gateway if not set).
public let gateway: String?
fileprivate init(_ destination: String, _ prefixLength: UInt8?, _ gateway: String?) {
self.destination = destination
self.prefixLength = prefixLength ?? 3
self.gateway = gateway
}
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "{\(destination.maskedDescription)/\(prefixLength) \(gateway?.maskedDescription ?? "default")}"
}
}
/// The address.
public let address: String
/// The address prefix length.
public let addressPrefixLength: UInt8
/// The address of the default gateway.
public let defaultGateway: String
/// The additional routes.
public let routes: [Route]
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
return "addr \(address.maskedDescription)/\(addressPrefixLength) gw \(defaultGateway.maskedDescription) routes \(routes.map { $0.maskedDescription })"
}
}
/// Groups the parsed reply of a successfully started session.
public protocol SessionReply {
/// The IPv4 settings.
var ipv4: IPv4Settings? { get }
/// The IPv6 settings.
var ipv6: IPv6Settings? { get }
/// The DNS servers set up for this session.
var dnsServers: [String] { get }
/// The optional compression framing.
var compressionFraming: SessionProxy.CompressionFraming? { get }
/// The optional compression algorithm.
var compressionAlgorithm: SessionProxy.CompressionAlgorithm? { get }
/// The optional keep-alive interval.
var ping: Int? { get }
/// The optional authentication token.
var authToken: String? { get }
/// The optional 24-bit peer-id.
var peerId: UInt32? { get }
/// The negotiated cipher if any (NCP).
var cipher: SessionProxy.Cipher? { get }
/// The returned options.
var options: OptionsBundle { get }
}
extension SessionProxy {
@ -171,61 +49,11 @@ extension SessionProxy {
// XXX: parsing is very optimistic
struct PushReply: SessionReply, CustomStringConvertible {
private enum Topology: String {
case net30
case p2p
case subnet
}
private static let prefix = "PUSH_REPLY,"
private struct Regex {
static let topology = NSRegularExpression("topology (net30|p2p|subnet)")
static let ifconfig = NSRegularExpression("ifconfig [\\d\\.]+ [\\d\\.]+")
static let ifconfig6 = NSRegularExpression("ifconfig-ipv6 [\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")
static let gateway = NSRegularExpression("route-gateway [\\d\\.]+")
static let route = NSRegularExpression("route [\\d\\.]+( [\\d\\.]+){0,2}")
static let route6 = NSRegularExpression("route-ipv6 [\\da-fA-F:]+/\\d+( [\\da-fA-F:]+){0,2}")
static let dns = NSRegularExpression("dhcp-option DNS6? [\\d\\.a-fA-F:]+")
static let comp = NSRegularExpression("comp(ress|-lzo)[ \\w]*")
static let ping = NSRegularExpression("ping \\d+")
static let authToken = NSRegularExpression("auth-token [a-zA-Z0-9/=+]+")
static let peerId = NSRegularExpression("peer-id [0-9]+")
static let cipher = NSRegularExpression("cipher [^,\\s]+")
}
private let original: String
let ipv4: IPv4Settings?
let ipv6: IPv6Settings?
let dnsServers: [String]
let compressionFraming: SessionProxy.CompressionFraming?
let compressionAlgorithm: SessionProxy.CompressionAlgorithm?
let ping: Int?
let authToken: String?
let peerId: UInt32?
let cipher: SessionProxy.Cipher?
let options: OptionsBundle
init?(message: String) throws {
guard message.hasPrefix(PushReply.prefix) else {
@ -236,224 +64,15 @@ extension SessionProxy {
}
original = String(message[prefixIndex...])
var optTopologyArguments: [String]?
var optIfconfig4Arguments: [String]?
var optGateway4Arguments: [String]?
let address4: String
let addressMask4: String
let defaultGateway4: String
var routes4: [IPv4Settings.Route] = []
var optIfconfig6Arguments: [String]?
var dnsServers: [String] = []
var compressionFraming: SessionProxy.CompressionFraming?
var compressionAlgorithm: SessionProxy.CompressionAlgorithm?
var ping: Int?
var authToken: String?
var peerId: UInt32?
var cipher: SessionProxy.Cipher?
// MARK: Routing (IPv4)
Regex.topology.enumerateArguments(in: message) {
optTopologyArguments = $0
}
guard let topologyArguments = optTopologyArguments, topologyArguments.count == 1 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: topologyArguments[0]) else {
fatalError("Bad topology regexp, accepted unrecognized value: \(topologyArguments[0])")
}
Regex.ifconfig.enumerateArguments(in: message) {
optIfconfig4Arguments = $0
}
guard let ifconfig4Arguments = optIfconfig4Arguments, ifconfig4Arguments.count == 2 else {
throw SessionError.malformedPushReply
}
Regex.gateway.enumerateArguments(in: message) {
optGateway4Arguments = $0
}
//
// 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 gateway4Arguments = optGateway4Arguments, gateway4Arguments.count == 1 else {
throw SessionError.malformedPushReply
}
address4 = ifconfig4Arguments[0]
addressMask4 = ifconfig4Arguments[1]
defaultGateway4 = gateway4Arguments[0]
default:
address4 = ifconfig4Arguments[0]
addressMask4 = "255.255.255.255"
defaultGateway4 = ifconfig4Arguments[1]
}
Regex.route.enumerateArguments(in: message) {
let routeEntryArguments = $0
let address = routeEntryArguments[0]
let mask: String?
let gateway: String?
if routeEntryArguments.count > 1 {
mask = routeEntryArguments[1]
} else {
mask = nil
}
if routeEntryArguments.count > 2 {
gateway = routeEntryArguments[2]
} else {
gateway = defaultGateway4
}
routes4.append(IPv4Settings.Route(address, mask, gateway))
}
ipv4 = IPv4Settings(
address: address4,
addressMask: addressMask4,
defaultGateway: defaultGateway4,
routes: routes4
)
// MARK: Routing (IPv6)
Regex.ifconfig6.enumerateArguments(in: message) {
optIfconfig6Arguments = $0
}
if let ifconfig6Arguments = optIfconfig6Arguments, ifconfig6Arguments.count == 2 {
let address6Components = ifconfig6Arguments[0].components(separatedBy: "/")
guard address6Components.count == 2 else {
throw SessionError.malformedPushReply
}
guard let addressPrefix6 = UInt8(address6Components[1]) else {
throw SessionError.malformedPushReply
}
let address6 = address6Components[0]
let defaultGateway6 = ifconfig6Arguments[1]
var routes6: [IPv6Settings.Route] = []
Regex.route6.enumerateArguments(in: message) {
let routeEntryArguments = $0
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
guard destinationComponents.count == 2 else {
// throw SessionError.malformedPushReply
return
}
guard let prefix = UInt8(destinationComponents[1]) else {
// throw SessionError.malformedPushReply
return
}
let destination = destinationComponents[0]
let gateway: String?
if routeEntryArguments.count > 1 {
gateway = routeEntryArguments[1]
} else {
gateway = defaultGateway6
}
routes6.append(IPv6Settings.Route(destination, prefix, gateway))
}
ipv6 = IPv6Settings(
address: address6,
addressPrefixLength: addressPrefix6,
defaultGateway: defaultGateway6,
routes: routes6
)
} else {
ipv6 = nil
}
// MARK: DNS
Regex.dns.enumerateArguments(in: message) {
dnsServers.append($0[1])
}
// MARK: Compression
Regex.comp.enumerateComponents(in: message) {
switch $0[0] {
case "comp-lzo":
compressionFraming = .compLZO
if ($0.count == 2) && ($0[1] == "no") {
compressionAlgorithm = .disabled
} else {
compressionAlgorithm = .LZO
}
case "compress":
compressionFraming = .compress
if $0.count == 1 {
compressionAlgorithm = .disabled
} else if ($0.count == 2) && ($0[1] == "lzo") {
compressionAlgorithm = .LZO
} else {
compressionAlgorithm = .other
}
default:
break
}
}
// MARK: Keep-alive
Regex.ping.enumerateArguments(in: message) {
ping = Int($0[0])
}
// MARK: Authentication
Regex.authToken.enumerateArguments(in: message) {
authToken = $0[0]
}
Regex.peerId.enumerateArguments(in: message) {
peerId = UInt32($0[0])
}
// MARK: NCP
Regex.cipher.enumerateArguments(in: message) {
cipher = SessionProxy.Cipher(rawValue: $0[0].uppercased())
}
self.dnsServers = dnsServers
self.compressionFraming = compressionFraming
self.compressionAlgorithm = compressionAlgorithm
self.ping = ping
self.authToken = authToken
self.peerId = peerId
self.cipher = cipher
let lines = original.components(separatedBy: ",")
options = try OptionsBundle(from: lines)
}
// MARK: CustomStringConvertible
var description: String {
let stripped = NSMutableString(string: original)
Regex.authToken.replaceMatches(
OptionsBundle.Regex.authToken.replaceMatches(
in: stripped,
options: [],
range: NSMakeRange(0, stripped.length),

View File

@ -86,7 +86,7 @@ public class SessionProxy {
private var keepAliveInterval: TimeInterval? {
let interval: TimeInterval?
if let negInterval = pushReply?.ping, negInterval > 0 {
if let negInterval = pushReply?.options.keepAliveSeconds, negInterval > 0 {
interval = TimeInterval(negInterval)
} else if let cfgInterval = configuration.keepAliveInterval, cfgInterval > 0.0 {
interval = cfgInterval
@ -276,7 +276,7 @@ public class SessionProxy {
- Seealso: `canRebindLink()`.
*/
public func rebindLink(_ link: LinkInterface) {
guard let _ = pushReply?.peerId else {
guard let _ = pushReply?.options.peerId else {
log.warning("Session doesn't support link rebinding!")
return
}
@ -666,7 +666,7 @@ public class SessionProxy {
negotiationKey.controlState = .preAuth
do {
authenticator = try Authenticator(credentials?.username, pushReply?.authToken ?? credentials?.password)
authenticator = try Authenticator(credentials?.username, pushReply?.options.authToken ?? credentials?.password)
try authenticator?.putAuth(into: negotiationKey.tls)
} catch let e {
deferStop(.shutdown, e)
@ -919,7 +919,7 @@ public class SessionProxy {
reply = optionalReply
log.debug("Received PUSH_REPLY: \"\(reply.maskedDescription)\"")
if let framing = reply.compressionFraming, let compression = reply.compressionAlgorithm {
if let framing = reply.options.compressionFraming, let compression = reply.options.compressionAlgorithm {
switch compression {
case .disabled:
break
@ -1036,18 +1036,18 @@ public class SessionProxy {
log.debug("Set up encryption")
}
let pushedFraming = pushReply.compressionFraming
let pushedFraming = pushReply.options.compressionFraming
if let negFraming = pushedFraming {
log.info("\tNegotiated compression framing: \(negFraming)")
}
let pushedCompression = pushReply.compressionAlgorithm
let pushedCompression = pushReply.options.compressionAlgorithm
if let negCompression = pushedCompression {
log.info("\tNegotiated compression algorithm: \(negCompression)")
}
if let negPing = pushReply.ping {
if let negPing = pushReply.options.keepAliveSeconds {
log.info("\tNegotiated keep-alive: \(negPing) seconds")
}
let pushedCipher = pushReply.cipher
let pushedCipher = pushReply.options.cipher
if let negCipher = pushedCipher {
log.info("\tNegotiated cipher: \(negCipher.rawValue)")
}
@ -1069,7 +1069,7 @@ public class SessionProxy {
negotiationKey.dataPath = DataPath(
encrypter: bridge.encrypter(),
decrypter: bridge.decrypter(),
peerId: pushReply.peerId ?? PacketPeerIdDisabled,
peerId: pushReply.options.peerId ?? PacketPeerIdDisabled,
compressionFraming: (pushedFraming ?? configuration.compressionFraming).native,
compressionAlgorithm: (pushedCompression ?? configuration.compressionAlgorithm ?? .disabled).native,
maxPackets: link?.packetBufferSize ?? 200,

View File

@ -28,11 +28,11 @@ import XCTest
private extension SessionReply {
func debug() {
print("Compression framing: \(compressionFraming?.description ?? "none")")
print("Compression algorithm: \(compressionAlgorithm?.description ?? "none")")
print("IPv4: \(ipv4?.description ?? "none")")
print("IPv6: \(ipv6?.description ?? "none")")
print("DNS: \(dnsServers)")
print("Compression framing: \(options.compressionFraming?.description ?? "none")")
print("Compression algorithm: \(options.compressionAlgorithm?.description ?? "none")")
print("IPv4: \(options.ipv4?.description ?? "none")")
print("IPv6: \(options.ipv6?.description ?? "none")")
print("DNS: \(options.dnsServers)")
}
}
@ -51,10 +51,10 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.ipv4?.address, "10.5.10.6")
XCTAssertEqual(reply.ipv4?.addressMask, "255.255.255.255")
XCTAssertEqual(reply.ipv4?.defaultGateway, "10.5.10.5")
XCTAssertEqual(reply.dnsServers, ["209.222.18.222", "209.222.18.218"])
XCTAssertEqual(reply.options.ipv4?.address, "10.5.10.6")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.255")
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.5.10.5")
XCTAssertEqual(reply.options.dnsServers, ["209.222.18.222", "209.222.18.218"])
}
func testSubnet() {
@ -62,10 +62,10 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.ipv4?.defaultGateway, "10.8.0.1")
XCTAssertEqual(reply.dnsServers, ["8.8.8.8", "4.4.4.4"])
XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1")
XCTAssertEqual(reply.options.dnsServers, ["8.8.8.8", "4.4.4.4"])
}
func testRoute() {
@ -73,7 +73,7 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
let route = reply.ipv4!.routes.first!
let route = reply.options.ipv4!.routes.first!
XCTAssertEqual(route.destination, "192.168.0.0")
XCTAssertEqual(route.mask, "255.255.255.0")
@ -85,13 +85,13 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.ipv4?.defaultGateway, "10.8.0.1")
XCTAssertEqual(reply.ipv6?.address, "fe80::601:30ff:feb7:ec01")
XCTAssertEqual(reply.ipv6?.addressPrefixLength, 64)
XCTAssertEqual(reply.ipv6?.defaultGateway, "fe80::601:30ff:feb7:dc02")
XCTAssertEqual(reply.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"])
XCTAssertEqual(reply.options.ipv4?.address, "10.8.0.2")
XCTAssertEqual(reply.options.ipv4?.addressMask, "255.255.255.0")
XCTAssertEqual(reply.options.ipv4?.defaultGateway, "10.8.0.1")
XCTAssertEqual(reply.options.ipv6?.address, "fe80::601:30ff:feb7:ec01")
XCTAssertEqual(reply.options.ipv6?.addressPrefixLength, 64)
XCTAssertEqual(reply.options.ipv6?.defaultGateway, "fe80::601:30ff:feb7:dc02")
XCTAssertEqual(reply.options.dnsServers, ["2001:4860:4860::8888", "2001:4860:4860::8844"])
}
func testCompressionFraming() {
@ -99,7 +99,7 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertEqual(reply.options.compressionFraming, .compLZO)
}
func testCompression() {
@ -108,28 +108,28 @@ class PushTests: XCTestCase {
reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo no"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertEqual(reply.compressionAlgorithm, .disabled)
XCTAssertEqual(reply.options.compressionFraming, .compLZO)
XCTAssertEqual(reply.options.compressionAlgorithm, .disabled)
reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertEqual(reply.compressionAlgorithm, .LZO)
XCTAssertEqual(reply.options.compressionFraming, .compLZO)
XCTAssertEqual(reply.options.compressionAlgorithm, .LZO)
reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo yes"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertEqual(reply.compressionAlgorithm, .LZO)
XCTAssertEqual(reply.options.compressionFraming, .compLZO)
XCTAssertEqual(reply.options.compressionAlgorithm, .LZO)
reply = try! SessionProxy.PushReply(message: msg.appending(",compress"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compress)
XCTAssertEqual(reply.compressionAlgorithm, .disabled)
XCTAssertEqual(reply.options.compressionFraming, .compress)
XCTAssertEqual(reply.options.compressionAlgorithm, .disabled)
reply = try! SessionProxy.PushReply(message: msg.appending(",compress lz4"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compress)
XCTAssertEqual(reply.compressionAlgorithm, .other)
XCTAssertEqual(reply.options.compressionFraming, .compress)
XCTAssertEqual(reply.options.compressionAlgorithm, .other)
}
func testNCP() {
@ -137,7 +137,7 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.cipher, .aes256gcm)
XCTAssertEqual(reply.options.cipher, .aes256gcm)
}
func testNCPTrailing() {
@ -145,7 +145,7 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.cipher, .aes256gcm)
XCTAssertEqual(reply.options.cipher, .aes256gcm)
}
func testPing() {
@ -153,6 +153,6 @@ class PushTests: XCTestCase {
let reply = try! SessionProxy.PushReply(message: msg)!
reply.debug()
XCTAssertEqual(reply.ping, 10)
XCTAssertEqual(reply.options.keepAliveSeconds, 10)
}
}