Parse IPv6 enpdoints properly (#294)

* Fix incorrect parsing of IPv6 address in endpoint

* Use better names for space-based regex extensions
This commit is contained in:
Davide De Rosa 2022-10-25 11:29:36 +02:00 committed by GitHub
parent 31db8ebb9d
commit 7659057888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 47 deletions

View File

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- OpenVPN: Prioritize server configuration over client (standard behavior). - OpenVPN: Prioritize server configuration over client (standard behavior).
- IPv6 endpoints are parsed improperly. [#293](https://github.com/passepartoutvpn/tunnelkit/issues/293)
- Fix abandoned MockVPN. [#285](https://github.com/passepartoutvpn/tunnelkit/pull/285) - Fix abandoned MockVPN. [#285](https://github.com/passepartoutvpn/tunnelkit/pull/285)
## 5.0.0 (2022-09-23) ## 5.0.0 (2022-09-23)

View File

@ -24,9 +24,14 @@
// //
import Foundation import Foundation
import __TunnelKitUtils
/// Represents an endpoint. /// Represents an endpoint.
public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConvertible { public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConvertible {
// XXX: simplistic match
private static let rx = NSRegularExpression("^([0-9A-Fa-f\\.:]+):(UDP[46]?|TCP[46]?):(\\d+)$")
public let address: String public let address: String
public let proto: EndpointProtocol public let proto: EndpointProtocol
@ -68,7 +73,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
// MARK: RawRepresentable // MARK: RawRepresentable
public init?(rawValue: String) { public init?(rawValue: String) {
let components = rawValue.components(separatedBy: ":") let components = Self.rx.groups(in: rawValue)
guard components.count == 3 else { guard components.count == 3 else {
return nil return nil
} }

View File

@ -313,16 +313,16 @@ extension OpenVPN {
// MARK: Unsupported // MARK: Unsupported
// check blocks first // check blocks first
Regex.connection.enumerateComponents(in: line) { (_) in Regex.connection.enumerateSpacedComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "<connection> blocks") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "<connection> blocks")
} }
Regex.fragment.enumerateComponents(in: line) { (_) in Regex.fragment.enumerateSpacedComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment")
} }
Regex.connectionProxy.enumerateComponents(in: line) { (_) in Regex.connectionProxy.enumerateSpacedComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proxy: \"\(line)\"")
} }
Regex.externalFiles.enumerateComponents(in: line) { (_) in Regex.externalFiles.enumerateSpacedComponents(in: line) { (_) in
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "external file: \"\(line)\"") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "external file: \"\(line)\"")
} }
if line.contains("mtu") || line.contains("mssfix") { if line.contains("mtu") || line.contains("mssfix") {
@ -332,7 +332,7 @@ extension OpenVPN {
// MARK: Continuation // MARK: Continuation
var isContinuation = false var isContinuation = false
Regex.continuation.enumerateArguments(in: line) { Regex.continuation.enumerateSpacedArguments(in: line) {
isContinuation = ($0.first == "2") isContinuation = ($0.first == "2")
} }
guard !isContinuation else { guard !isContinuation else {
@ -343,7 +343,7 @@ extension OpenVPN {
if unsupportedError == nil { if unsupportedError == nil {
if currentBlockName == nil { if currentBlockName == nil {
Regex.blockBegin.enumerateComponents(in: line) { Regex.blockBegin.enumerateSpacedComponents(in: line) {
isHandled = true isHandled = true
let tag = $0.first! let tag = $0.first!
let from = tag.index(after: tag.startIndex) let from = tag.index(after: tag.startIndex)
@ -353,7 +353,7 @@ extension OpenVPN {
currentBlock = [] currentBlock = []
} }
} }
Regex.blockEnd.enumerateComponents(in: line) { Regex.blockEnd.enumerateSpacedComponents(in: line) {
isHandled = true isHandled = true
let tag = $0.first! let tag = $0.first!
let from = tag.index(tag.startIndex, offsetBy: 2) let from = tag.index(tag.startIndex, offsetBy: 2)
@ -399,14 +399,14 @@ extension OpenVPN {
// MARK: General // MARK: General
Regex.cipher.enumerateArguments(in: line) { Regex.cipher.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let rawValue = $0.first else { guard let rawValue = $0.first else {
return return
} }
optCipher = Cipher(rawValue: rawValue.uppercased()) optCipher = Cipher(rawValue: rawValue.uppercased())
} }
Regex.dataCiphers.enumerateArguments(in: line) { Regex.dataCiphers.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let rawValue = $0.first else { guard let rawValue = $0.first else {
return return
@ -420,14 +420,14 @@ extension OpenVPN {
optDataCiphers?.append(cipher) optDataCiphers?.append(cipher)
} }
} }
Regex.dataCiphersFallback.enumerateArguments(in: line) { Regex.dataCiphersFallback.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let rawValue = $0.first else { guard let rawValue = $0.first else {
return return
} }
optDataCiphersFallback = Cipher(rawValue: rawValue.uppercased()) optDataCiphersFallback = Cipher(rawValue: rawValue.uppercased())
} }
Regex.auth.enumerateArguments(in: line) { Regex.auth.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let rawValue = $0.first else { guard let rawValue = $0.first else {
return return
@ -437,7 +437,7 @@ extension OpenVPN {
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "auth \(rawValue)") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "auth \(rawValue)")
} }
} }
Regex.compLZO.enumerateArguments(in: line) { Regex.compLZO.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
optCompressionFraming = .compLZO optCompressionFraming = .compLZO
@ -455,7 +455,7 @@ extension OpenVPN {
optCompressionAlgorithm = (arg == "no") ? .disabled : .LZO optCompressionAlgorithm = (arg == "no") ? .disabled : .LZO
} }
} }
Regex.compress.enumerateArguments(in: line) { Regex.compress.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
optCompressionFraming = .compress optCompressionFraming = .compress
@ -485,28 +485,28 @@ extension OpenVPN {
} }
} }
} }
Regex.keyDirection.enumerateArguments(in: line) { Regex.keyDirection.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let arg = $0.first, let value = Int(arg) else { guard let arg = $0.first, let value = Int(arg) else {
return return
} }
optKeyDirection = StaticKey.Direction(rawValue: value) optKeyDirection = StaticKey.Direction(rawValue: value)
} }
Regex.ping.enumerateArguments(in: line) { Regex.ping.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let arg = $0.first else { guard let arg = $0.first else {
return return
} }
optKeepAliveSeconds = TimeInterval(arg) optKeepAliveSeconds = TimeInterval(arg)
} }
Regex.pingRestart.enumerateArguments(in: line) { Regex.pingRestart.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let arg = $0.first else { guard let arg = $0.first else {
return return
} }
optKeepAliveTimeoutSeconds = TimeInterval(arg) optKeepAliveTimeoutSeconds = TimeInterval(arg)
} }
Regex.keepAlive.enumerateArguments(in: line) { Regex.keepAlive.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let ping = $0.first, let pingRestart = $0.last else { guard let ping = $0.first, let pingRestart = $0.last else {
return return
@ -514,14 +514,14 @@ extension OpenVPN {
optKeepAliveSeconds = TimeInterval(ping) optKeepAliveSeconds = TimeInterval(ping)
optKeepAliveTimeoutSeconds = TimeInterval(pingRestart) optKeepAliveTimeoutSeconds = TimeInterval(pingRestart)
} }
Regex.renegSec.enumerateArguments(in: line) { Regex.renegSec.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let arg = $0.first else { guard let arg = $0.first else {
return return
} }
optRenegotiateAfterSeconds = TimeInterval(arg) optRenegotiateAfterSeconds = TimeInterval(arg)
} }
Regex.xorMask.enumerateArguments(in: line) { Regex.xorMask.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
if $0.count != 2 { if $0.count != 2 {
return return
@ -531,7 +531,7 @@ extension OpenVPN {
// MARK: Client // MARK: Client
Regex.proto.enumerateArguments(in: line) { Regex.proto.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let str = $0.first else { guard let str = $0.first else {
return return
@ -541,14 +541,14 @@ extension OpenVPN {
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proto \(str)") unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proto \(str)")
} }
} }
Regex.port.enumerateArguments(in: line) { Regex.port.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let str = $0.first else { guard let str = $0.first else {
return return
} }
optDefaultPort = UInt16(str) optDefaultPort = UInt16(str)
} }
Regex.remote.enumerateArguments(in: line) { Regex.remote.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let hostname = $0.first else { guard let hostname = $0.first else {
return return
@ -569,51 +569,51 @@ extension OpenVPN {
// replace private data // replace private data
strippedLine = strippedComponents.joined(separator: " ") strippedLine = strippedComponents.joined(separator: " ")
} }
Regex.eku.enumerateComponents(in: line) { (_) in Regex.eku.enumerateSpacedComponents(in: line) { (_) in
isHandled = true isHandled = true
optChecksEKU = true optChecksEKU = true
} }
Regex.remoteRandom.enumerateComponents(in: line) { (_) in Regex.remoteRandom.enumerateSpacedComponents(in: line) { (_) in
isHandled = true isHandled = true
optRandomizeEndpoint = true optRandomizeEndpoint = true
} }
Regex.remoteRandomHostname.enumerateComponents(in: line) { _ in Regex.remoteRandomHostname.enumerateSpacedComponents(in: line) { _ in
isHandled = true isHandled = true
optRandomizeHostnames = true optRandomizeHostnames = true
} }
Regex.mtu.enumerateArguments(in: line) { Regex.mtu.enumerateSpacedArguments(in: line) {
isHandled = true isHandled = true
guard let str = $0.first else { guard let str = $0.first else {
return return
} }
optMTU = Int(str) optMTU = Int(str)
} }
Regex.authUserPass.enumerateComponents(in: line) { _ in Regex.authUserPass.enumerateSpacedComponents(in: line) { _ in
isHandled = true isHandled = true
authUserPass = true authUserPass = true
} }
// MARK: Server // MARK: Server
Regex.authToken.enumerateArguments(in: line) { Regex.authToken.enumerateSpacedArguments(in: line) {
optAuthToken = $0[0] optAuthToken = $0[0]
} }
Regex.peerId.enumerateArguments(in: line) { Regex.peerId.enumerateSpacedArguments(in: line) {
optPeerId = UInt32($0[0]) optPeerId = UInt32($0[0])
} }
// MARK: Routing // MARK: Routing
Regex.topology.enumerateArguments(in: line) { Regex.topology.enumerateSpacedArguments(in: line) {
optTopology = $0.first optTopology = $0.first
} }
Regex.ifconfig.enumerateArguments(in: line) { Regex.ifconfig.enumerateSpacedArguments(in: line) {
optIfconfig4Arguments = $0 optIfconfig4Arguments = $0
} }
Regex.ifconfig6.enumerateArguments(in: line) { Regex.ifconfig6.enumerateSpacedArguments(in: line) {
optIfconfig6Arguments = $0 optIfconfig6Arguments = $0
} }
Regex.route.enumerateArguments(in: line) { Regex.route.enumerateSpacedArguments(in: line) {
let routeEntryArguments = $0 let routeEntryArguments = $0
let address = routeEntryArguments[0] let address = routeEntryArguments[0]
@ -624,7 +624,7 @@ extension OpenVPN {
} }
optRoutes4.append((address, mask, gateway)) optRoutes4.append((address, mask, gateway))
} }
Regex.route6.enumerateArguments(in: line) { Regex.route6.enumerateSpacedArguments(in: line) {
let routeEntryArguments = $0 let routeEntryArguments = $0
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/") let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
@ -642,10 +642,10 @@ extension OpenVPN {
} }
optRoutes6.append((destination, prefix, gateway)) optRoutes6.append((destination, prefix, gateway))
} }
Regex.gateway.enumerateArguments(in: line) { Regex.gateway.enumerateSpacedArguments(in: line) {
optGateway4Arguments = $0 optGateway4Arguments = $0
} }
Regex.dns.enumerateArguments(in: line) { Regex.dns.enumerateSpacedArguments(in: line) {
guard $0.count == 2 else { guard $0.count == 2 else {
return return
} }
@ -654,13 +654,13 @@ extension OpenVPN {
} }
optDNSServers?.append($0[1]) optDNSServers?.append($0[1])
} }
Regex.domain.enumerateArguments(in: line) { Regex.domain.enumerateSpacedArguments(in: line) {
guard $0.count == 2 else { guard $0.count == 2 else {
return return
} }
optDomain = $0[1] optDomain = $0[1]
} }
Regex.domainSearch.enumerateArguments(in: line) { Regex.domainSearch.enumerateSpacedArguments(in: line) {
guard $0.count == 2 else { guard $0.count == 2 else {
return return
} }
@ -669,7 +669,7 @@ extension OpenVPN {
} }
optSearchDomains?.append($0[1]) optSearchDomains?.append($0[1])
} }
Regex.proxy.enumerateArguments(in: line) { Regex.proxy.enumerateSpacedArguments(in: line) {
if $0.count == 2 { if $0.count == 2 {
guard let url = URL(string: $0[1]) else { guard let url = URL(string: $0[1]) else {
unsupportedError = ConfigurationError.malformed(option: "dhcp-option PROXY_AUTO_CONFIG_URL has malformed URL") unsupportedError = ConfigurationError.malformed(option: "dhcp-option PROXY_AUTO_CONFIG_URL has malformed URL")
@ -693,14 +693,14 @@ extension OpenVPN {
break break
} }
} }
Regex.proxyBypass.enumerateArguments(in: line) { Regex.proxyBypass.enumerateSpacedArguments(in: line) {
guard !$0.isEmpty else { guard !$0.isEmpty else {
return return
} }
optProxyBypass = $0 optProxyBypass = $0
optProxyBypass?.removeFirst() optProxyBypass?.removeFirst()
} }
Regex.redirectGateway.enumerateArguments(in: line) { Regex.redirectGateway.enumerateSpacedArguments(in: line) {
// redirect IPv4 by default // redirect IPv4 by default
optRedirectGateway = [.def1] optRedirectGateway = [.def1]
@ -712,7 +712,7 @@ extension OpenVPN {
optRedirectGateway?.insert(opt) optRedirectGateway?.insert(opt)
} }
} }
Regex.routeNoPull.enumerateComponents(in: line) { _ in Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in
optRouteNoPull = true optRouteNoPull = true
} }

View File

@ -30,8 +30,25 @@ extension NSRegularExpression {
try! self.init(pattern: pattern, options: []) try! self.init(pattern: pattern, options: [])
} }
public func enumerateComponents(in string: String, using block: ([String]) -> Void) { public func groups(in string: String) -> [String] {
enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { (result, flags, stop) in var results: [String] = []
enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { result, flags, stop in
guard let result = result else {
return
}
for i in 0..<numberOfCaptureGroups {
let subrange = result.range(at: i + 1)
let match = (string as NSString).substring(with: subrange)
results.append(match)
}
}
return results
}
}
extension NSRegularExpression {
public func enumerateSpacedComponents(in string: String, using block: ([String]) -> Void) {
enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { result, flags, stop in
guard let range = result?.range else { guard let range = result?.range else {
return return
} }
@ -41,8 +58,8 @@ extension NSRegularExpression {
} }
} }
public func enumerateArguments(in string: String, using block: ([String]) -> Void) { public func enumerateSpacedArguments(in string: String, using block: ([String]) -> Void) {
enumerateComponents(in: string) { (tokens) in enumerateSpacedComponents(in: string) { (tokens) in
var args = tokens var args = tokens
args.removeFirst() args.removeFirst()
block(args) block(args)

View File

@ -0,0 +1,64 @@
//
// ParsingTests.swift
// TunnelKitCoreTests
//
// Created by Davide De Rosa on 10/25/22.
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of TunnelKit.
//
// TunnelKit is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// TunnelKit is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
//
import XCTest
@testable import TunnelKitCore
class ParsingTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testEndpointV4() {
let ipAddress = "1.2.3.4"
let socketType = "TCP"
let port = 1194
guard let endpoint = Endpoint(rawValue: "\(ipAddress):\(socketType):\(port)") else {
XCTFail()
return
}
XCTAssertEqual(endpoint.address, ipAddress)
XCTAssertEqual(endpoint.proto.socketType.rawValue, socketType)
XCTAssertEqual(endpoint.proto.port, UInt16(port))
}
func testEndpointV6() {
let ipAddress = "2607:f0d0:1002:51::4"
let socketType = "TCP"
let port = 1194
guard let endpoint = Endpoint(rawValue: "\(ipAddress):\(socketType):\(port)") else {
XCTFail()
return
}
XCTAssertEqual(endpoint.address, ipAddress)
XCTAssertEqual(endpoint.proto.socketType.rawValue, socketType)
XCTAssertEqual(endpoint.proto.port, UInt16(port))
}
}