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

Catch compression mismatch #65

Merged
merged 5 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Override DNS servers client side. [#56](https://github.com/keeshux/tunnelkit/pull/56)
- Shut down if server pushes a compression directive. [#65](https://github.com/keeshux/tunnelkit/pull/65)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ extension TunnelKitProvider {
/// Data encryption/decryption failed.
case encryptionData

/// Server uses compression and this is not supported.
case serverCompression

/// Tunnel timed out.
case timeout

Expand Down
3 changes: 3 additions & 0 deletions TunnelKit/Sources/AppExtension/TunnelKitProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@ extension TunnelKitProvider {
case .badCredentials:
return .authentication

case .serverCompression:
return .serverCompression

case .failedLinkWrite:
return .linkError

Expand Down
3 changes: 3 additions & 0 deletions TunnelKit/Sources/Core/SessionError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public enum SessionError: String, Error {

/// The session reached a stale state and can't be recovered.
case staleSession

/// Server uses compression.
case serverCompression
}

extension Error {
Expand Down
93 changes: 52 additions & 41 deletions TunnelKit/Sources/Core/SessionProxy+PushReply.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ public protocol SessionReply {
/// The optional compression framing.
var compressionFraming: SessionProxy.CompressionFraming? { get }

/// True if uses compression.
var usesCompression: Bool { get }

/// The optional keep-alive interval.
var ping: Int? { get }

Expand All @@ -176,32 +179,34 @@ extension SessionProxy {
case subnet
}

private static let prefix = "PUSH_REPLY,"

private static let topologyRegexp = NSRegularExpression("topology (net30|p2p|subnet)")

private static let ifconfigRegexp = NSRegularExpression("ifconfig [\\d\\.]+ [\\d\\.]+")

private static let ifconfig6Regexp = NSRegularExpression("ifconfig-ipv6 [\\da-fA-F:]+/\\d+ [\\da-fA-F:]+")

private static let gatewayRegexp = NSRegularExpression("route-gateway [\\d\\.]+")

private static let routeRegexp = NSRegularExpression("route [\\d\\.]+( [\\d\\.]+){0,2}")

private static let route6Regexp = NSRegularExpression("route-ipv6 [\\da-fA-F:]+/\\d+( [\\da-fA-F:]+){0,2}")

private static let dnsRegexp = NSRegularExpression("dhcp-option DNS6? [\\d\\.a-fA-F:]+")

private static let compRegexp = NSRegularExpression("comp(ress|-lzo)")
private struct Regex {
static let prefix = "PUSH_REPLY,"

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 static let pingRegexp = NSRegularExpression("ping \\d+")

private static let authTokenRegexp = NSRegularExpression("auth-token [a-zA-Z0-9/=+]+")

private static let peerIdRegexp = NSRegularExpression("peer-id [0-9]+")

private static let cipherRegexp = NSRegularExpression("cipher [^,\\s]+")

private let original: String

let ipv4: IPv4Settings?
Expand All @@ -212,6 +217,8 @@ extension SessionProxy {

let compressionFraming: SessionProxy.CompressionFraming?

let usesCompression: Bool

let ping: Int?

let authToken: String?
Expand All @@ -221,10 +228,10 @@ extension SessionProxy {
let cipher: SessionProxy.Cipher?

init?(message: String) throws {
guard message.hasPrefix(PushReply.prefix) else {
guard message.hasPrefix(Regex.prefix) else {
return nil
}
let prefixOffset = message.index(message.startIndex, offsetBy: PushReply.prefix.count)
let prefixOffset = message.index(message.startIndex, offsetBy: Regex.prefix.count)
original = String(message[prefixOffset..<message.endIndex])

var optTopologyArguments: [String]?
Expand All @@ -239,14 +246,15 @@ extension SessionProxy {

var dnsServers: [String] = []
var compressionFraming: SessionProxy.CompressionFraming?
var usesCompression = false
var ping: Int?
var authToken: String?
var peerId: UInt32?
var cipher: SessionProxy.Cipher?

// MARK: Routing (IPv4)

PushReply.topologyRegexp.enumerateArguments(in: message) {
Regex.topology.enumerateArguments(in: message) {
optTopologyArguments = $0
}
guard let topologyArguments = optTopologyArguments, topologyArguments.count == 1 else {
Expand All @@ -258,14 +266,14 @@ extension SessionProxy {
fatalError("Bad topology regexp, accepted unrecognized value: \(topologyArguments[0])")
}

PushReply.ifconfigRegexp.enumerateArguments(in: message) {
Regex.ifconfig.enumerateArguments(in: message) {
optIfconfig4Arguments = $0
}
guard let ifconfig4Arguments = optIfconfig4Arguments, ifconfig4Arguments.count == 2 else {
throw SessionError.malformedPushReply
}

PushReply.gatewayRegexp.enumerateArguments(in: message) {
Regex.gateway.enumerateArguments(in: message) {
optGateway4Arguments = $0
}

Expand Down Expand Up @@ -299,7 +307,7 @@ extension SessionProxy {
defaultGateway4 = ifconfig4Arguments[1]
}

PushReply.routeRegexp.enumerateArguments(in: message) {
Regex.route.enumerateArguments(in: message) {
let routeEntryArguments = $0

let address = routeEntryArguments[0]
Expand Down Expand Up @@ -327,7 +335,7 @@ extension SessionProxy {

// MARK: Routing (IPv6)

PushReply.ifconfig6Regexp.enumerateArguments(in: message) {
Regex.ifconfig6.enumerateArguments(in: message) {
optIfconfig6Arguments = $0
}
if let ifconfig6Arguments = optIfconfig6Arguments, ifconfig6Arguments.count == 2 {
Expand All @@ -342,7 +350,7 @@ extension SessionProxy {
let defaultGateway6 = ifconfig6Arguments[1]

var routes6: [IPv6Settings.Route] = []
PushReply.route6Regexp.enumerateArguments(in: message) {
Regex.route6.enumerateArguments(in: message) {
let routeEntryArguments = $0

let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
Expand Down Expand Up @@ -377,49 +385,52 @@ extension SessionProxy {

// MARK: DNS

PushReply.dnsRegexp.enumerateArguments(in: message) {
Regex.dns.enumerateArguments(in: message) {
dnsServers.append($0[1])
}

// MARK: Compression

PushReply.compRegexp.enumerateComponents(in: message) {
Regex.comp.enumerateComponents(in: message) {
switch $0[0] {
case "comp-lzo":
compressionFraming = .compLZO
usesCompression = !(($0.count == 2) && ($0[1] == "no"))

case "compress":
compressionFraming = .compress

usesCompression = ($0.count > 1)

default:
break
}
}

// MARK: Keep-alive

PushReply.pingRegexp.enumerateArguments(in: message) {
Regex.ping.enumerateArguments(in: message) {
ping = Int($0[0])
}

// MARK: Authentication

PushReply.authTokenRegexp.enumerateArguments(in: message) {
Regex.authToken.enumerateArguments(in: message) {
authToken = $0[0]
}

PushReply.peerIdRegexp.enumerateArguments(in: message) {
Regex.peerId.enumerateArguments(in: message) {
peerId = UInt32($0[0])
}

// MARK: NCP

PushReply.cipherRegexp.enumerateArguments(in: message) {
Regex.cipher.enumerateArguments(in: message) {
cipher = SessionProxy.Cipher(rawValue: $0[0].uppercased())
}

self.dnsServers = dnsServers
self.compressionFraming = compressionFraming
self.usesCompression = usesCompression
self.ping = ping
self.authToken = authToken
self.peerId = peerId
Expand All @@ -430,7 +441,7 @@ extension SessionProxy {

var description: String {
let stripped = NSMutableString(string: original)
PushReply.authTokenRegexp.replaceMatches(
Regex.authToken.replaceMatches(
in: stripped,
options: [],
range: NSMakeRange(0, stripped.length),
Expand Down
5 changes: 5 additions & 0 deletions TunnelKit/Sources/Core/SessionProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,11 @@ public class SessionProxy {
}
reply = optionalReply
log.debug("Received PUSH_REPLY: \"\(reply.maskedDescription)\"")

if let framing = reply.compressionFraming, reply.usesCompression {
log.error("Server has compression enabled and this is currently unsupported (\(framing))")
throw SessionError.serverCompression
}
} catch let e {
deferStop(.shutdown, e)
return
Expand Down
32 changes: 32 additions & 0 deletions TunnelKitTests/PushTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import XCTest

private extension SessionReply {
func debug() {
print("Compression framing: \(dnsServers)")
print("Compression: \(usesCompression)")
print("IPv4: \(ipv4?.description ?? "none")")
print("IPv6: \(ipv6?.description ?? "none")")
print("DNS: \(dnsServers)")
Expand Down Expand Up @@ -100,6 +102,36 @@ class PushTests: XCTestCase {
XCTAssertEqual(reply.compressionFraming, .compLZO)
}

func testCompression() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-CBC"
var reply: SessionReply

reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo no"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertFalse(reply.usesCompression)

reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertTrue(reply.usesCompression)

reply = try! SessionProxy.PushReply(message: msg.appending(",comp-lzo yes"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compLZO)
XCTAssertTrue(reply.usesCompression)

reply = try! SessionProxy.PushReply(message: msg.appending(",compress"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compress)
XCTAssertFalse(reply.usesCompression)

reply = try! SessionProxy.PushReply(message: msg.appending(",compress lz4"))!
reply.debug()
XCTAssertEqual(reply.compressionFraming, .compress)
XCTAssertTrue(reply.usesCompression)
}

func testNCP() {
let msg = "PUSH_REPLY,dhcp-option DNS 8.8.8.8,dhcp-option DNS 4.4.4.4,comp-lzo no,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM"
let reply = try! SessionProxy.PushReply(message: msg)!
Expand Down