Merge pull request #65 from keeshux/catch-compression-mismatch
Catch compression mismatch
This commit is contained in:
commit
0750d860ba
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -595,6 +595,9 @@ extension TunnelKitProvider {
|
|||
case .badCredentials:
|
||||
return .authentication
|
||||
|
||||
case .serverCompression:
|
||||
return .serverCompression
|
||||
|
||||
case .failedLinkWrite:
|
||||
return .linkError
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
@ -176,32 +179,34 @@ extension SessionProxy {
|
|||
case subnet
|
||||
}
|
||||
|
||||
private static let prefix = "PUSH_REPLY,"
|
||||
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 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 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?
|
||||
|
@ -212,6 +217,8 @@ extension SessionProxy {
|
|||
|
||||
let compressionFraming: SessionProxy.CompressionFraming?
|
||||
|
||||
let usesCompression: Bool
|
||||
|
||||
let ping: Int?
|
||||
|
||||
let authToken: String?
|
||||
|
@ -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]?
|
||||
|
@ -239,6 +246,7 @@ extension SessionProxy {
|
|||
|
||||
var dnsServers: [String] = []
|
||||
var compressionFraming: SessionProxy.CompressionFraming?
|
||||
var usesCompression = false
|
||||
var ping: Int?
|
||||
var authToken: String?
|
||||
var peerId: UInt32?
|
||||
|
@ -246,7 +254,7 @@ extension SessionProxy {
|
|||
|
||||
// MARK: Routing (IPv4)
|
||||
|
||||
PushReply.topologyRegexp.enumerateArguments(in: message) {
|
||||
Regex.topology.enumerateArguments(in: message) {
|
||||
optTopologyArguments = $0
|
||||
}
|
||||
guard let topologyArguments = optTopologyArguments, topologyArguments.count == 1 else {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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 {
|
||||
|
@ -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: "/")
|
||||
|
@ -377,20 +385,22 @@ 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
|
||||
}
|
||||
|
@ -398,28 +408,29 @@ extension SessionProxy {
|
|||
|
||||
// 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
|
||||
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)")
|
||||
|
@ -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)!
|
||||
|
|
Loading…
Reference in New Issue