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:
parent
31db8ebb9d
commit
7659057888
|
@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- 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)
|
||||
|
||||
## 5.0.0 (2022-09-23)
|
||||
|
|
|
@ -24,9 +24,14 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import __TunnelKitUtils
|
||||
|
||||
/// Represents an endpoint.
|
||||
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 proto: EndpointProtocol
|
||||
|
@ -68,7 +73,7 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
|
|||
// MARK: RawRepresentable
|
||||
|
||||
public init?(rawValue: String) {
|
||||
let components = rawValue.components(separatedBy: ":")
|
||||
let components = Self.rx.groups(in: rawValue)
|
||||
guard components.count == 3 else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -313,16 +313,16 @@ extension OpenVPN {
|
|||
// MARK: Unsupported
|
||||
|
||||
// check blocks first
|
||||
Regex.connection.enumerateComponents(in: line) { (_) in
|
||||
Regex.connection.enumerateSpacedComponents(in: line) { (_) in
|
||||
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "<connection> blocks")
|
||||
}
|
||||
Regex.fragment.enumerateComponents(in: line) { (_) in
|
||||
Regex.fragment.enumerateSpacedComponents(in: line) { (_) in
|
||||
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "fragment")
|
||||
}
|
||||
Regex.connectionProxy.enumerateComponents(in: line) { (_) in
|
||||
Regex.connectionProxy.enumerateSpacedComponents(in: line) { (_) in
|
||||
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)\"")
|
||||
}
|
||||
if line.contains("mtu") || line.contains("mssfix") {
|
||||
|
@ -332,7 +332,7 @@ extension OpenVPN {
|
|||
// MARK: Continuation
|
||||
|
||||
var isContinuation = false
|
||||
Regex.continuation.enumerateArguments(in: line) {
|
||||
Regex.continuation.enumerateSpacedArguments(in: line) {
|
||||
isContinuation = ($0.first == "2")
|
||||
}
|
||||
guard !isContinuation else {
|
||||
|
@ -343,7 +343,7 @@ extension OpenVPN {
|
|||
|
||||
if unsupportedError == nil {
|
||||
if currentBlockName == nil {
|
||||
Regex.blockBegin.enumerateComponents(in: line) {
|
||||
Regex.blockBegin.enumerateSpacedComponents(in: line) {
|
||||
isHandled = true
|
||||
let tag = $0.first!
|
||||
let from = tag.index(after: tag.startIndex)
|
||||
|
@ -353,7 +353,7 @@ extension OpenVPN {
|
|||
currentBlock = []
|
||||
}
|
||||
}
|
||||
Regex.blockEnd.enumerateComponents(in: line) {
|
||||
Regex.blockEnd.enumerateSpacedComponents(in: line) {
|
||||
isHandled = true
|
||||
let tag = $0.first!
|
||||
let from = tag.index(tag.startIndex, offsetBy: 2)
|
||||
|
@ -399,14 +399,14 @@ extension OpenVPN {
|
|||
|
||||
// MARK: General
|
||||
|
||||
Regex.cipher.enumerateArguments(in: line) {
|
||||
Regex.cipher.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let rawValue = $0.first else {
|
||||
return
|
||||
}
|
||||
optCipher = Cipher(rawValue: rawValue.uppercased())
|
||||
}
|
||||
Regex.dataCiphers.enumerateArguments(in: line) {
|
||||
Regex.dataCiphers.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let rawValue = $0.first else {
|
||||
return
|
||||
|
@ -420,14 +420,14 @@ extension OpenVPN {
|
|||
optDataCiphers?.append(cipher)
|
||||
}
|
||||
}
|
||||
Regex.dataCiphersFallback.enumerateArguments(in: line) {
|
||||
Regex.dataCiphersFallback.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let rawValue = $0.first else {
|
||||
return
|
||||
}
|
||||
optDataCiphersFallback = Cipher(rawValue: rawValue.uppercased())
|
||||
}
|
||||
Regex.auth.enumerateArguments(in: line) {
|
||||
Regex.auth.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let rawValue = $0.first else {
|
||||
return
|
||||
|
@ -437,7 +437,7 @@ extension OpenVPN {
|
|||
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "auth \(rawValue)")
|
||||
}
|
||||
}
|
||||
Regex.compLZO.enumerateArguments(in: line) {
|
||||
Regex.compLZO.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
optCompressionFraming = .compLZO
|
||||
|
||||
|
@ -455,7 +455,7 @@ extension OpenVPN {
|
|||
optCompressionAlgorithm = (arg == "no") ? .disabled : .LZO
|
||||
}
|
||||
}
|
||||
Regex.compress.enumerateArguments(in: line) {
|
||||
Regex.compress.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
optCompressionFraming = .compress
|
||||
|
||||
|
@ -485,28 +485,28 @@ extension OpenVPN {
|
|||
}
|
||||
}
|
||||
}
|
||||
Regex.keyDirection.enumerateArguments(in: line) {
|
||||
Regex.keyDirection.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let arg = $0.first, let value = Int(arg) else {
|
||||
return
|
||||
}
|
||||
optKeyDirection = StaticKey.Direction(rawValue: value)
|
||||
}
|
||||
Regex.ping.enumerateArguments(in: line) {
|
||||
Regex.ping.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let arg = $0.first else {
|
||||
return
|
||||
}
|
||||
optKeepAliveSeconds = TimeInterval(arg)
|
||||
}
|
||||
Regex.pingRestart.enumerateArguments(in: line) {
|
||||
Regex.pingRestart.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let arg = $0.first else {
|
||||
return
|
||||
}
|
||||
optKeepAliveTimeoutSeconds = TimeInterval(arg)
|
||||
}
|
||||
Regex.keepAlive.enumerateArguments(in: line) {
|
||||
Regex.keepAlive.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let ping = $0.first, let pingRestart = $0.last else {
|
||||
return
|
||||
|
@ -514,14 +514,14 @@ extension OpenVPN {
|
|||
optKeepAliveSeconds = TimeInterval(ping)
|
||||
optKeepAliveTimeoutSeconds = TimeInterval(pingRestart)
|
||||
}
|
||||
Regex.renegSec.enumerateArguments(in: line) {
|
||||
Regex.renegSec.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let arg = $0.first else {
|
||||
return
|
||||
}
|
||||
optRenegotiateAfterSeconds = TimeInterval(arg)
|
||||
}
|
||||
Regex.xorMask.enumerateArguments(in: line) {
|
||||
Regex.xorMask.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
if $0.count != 2 {
|
||||
return
|
||||
|
@ -531,7 +531,7 @@ extension OpenVPN {
|
|||
|
||||
// MARK: Client
|
||||
|
||||
Regex.proto.enumerateArguments(in: line) {
|
||||
Regex.proto.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let str = $0.first else {
|
||||
return
|
||||
|
@ -541,14 +541,14 @@ extension OpenVPN {
|
|||
unsupportedError = ConfigurationError.unsupportedConfiguration(option: "proto \(str)")
|
||||
}
|
||||
}
|
||||
Regex.port.enumerateArguments(in: line) {
|
||||
Regex.port.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let str = $0.first else {
|
||||
return
|
||||
}
|
||||
optDefaultPort = UInt16(str)
|
||||
}
|
||||
Regex.remote.enumerateArguments(in: line) {
|
||||
Regex.remote.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let hostname = $0.first else {
|
||||
return
|
||||
|
@ -569,51 +569,51 @@ extension OpenVPN {
|
|||
// replace private data
|
||||
strippedLine = strippedComponents.joined(separator: " ")
|
||||
}
|
||||
Regex.eku.enumerateComponents(in: line) { (_) in
|
||||
Regex.eku.enumerateSpacedComponents(in: line) { (_) in
|
||||
isHandled = true
|
||||
optChecksEKU = true
|
||||
}
|
||||
Regex.remoteRandom.enumerateComponents(in: line) { (_) in
|
||||
Regex.remoteRandom.enumerateSpacedComponents(in: line) { (_) in
|
||||
isHandled = true
|
||||
optRandomizeEndpoint = true
|
||||
}
|
||||
Regex.remoteRandomHostname.enumerateComponents(in: line) { _ in
|
||||
Regex.remoteRandomHostname.enumerateSpacedComponents(in: line) { _ in
|
||||
isHandled = true
|
||||
optRandomizeHostnames = true
|
||||
}
|
||||
Regex.mtu.enumerateArguments(in: line) {
|
||||
Regex.mtu.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
guard let str = $0.first else {
|
||||
return
|
||||
}
|
||||
optMTU = Int(str)
|
||||
}
|
||||
Regex.authUserPass.enumerateComponents(in: line) { _ in
|
||||
Regex.authUserPass.enumerateSpacedComponents(in: line) { _ in
|
||||
isHandled = true
|
||||
authUserPass = true
|
||||
}
|
||||
|
||||
// MARK: Server
|
||||
|
||||
Regex.authToken.enumerateArguments(in: line) {
|
||||
Regex.authToken.enumerateSpacedArguments(in: line) {
|
||||
optAuthToken = $0[0]
|
||||
}
|
||||
Regex.peerId.enumerateArguments(in: line) {
|
||||
Regex.peerId.enumerateSpacedArguments(in: line) {
|
||||
optPeerId = UInt32($0[0])
|
||||
}
|
||||
|
||||
// MARK: Routing
|
||||
|
||||
Regex.topology.enumerateArguments(in: line) {
|
||||
Regex.topology.enumerateSpacedArguments(in: line) {
|
||||
optTopology = $0.first
|
||||
}
|
||||
Regex.ifconfig.enumerateArguments(in: line) {
|
||||
Regex.ifconfig.enumerateSpacedArguments(in: line) {
|
||||
optIfconfig4Arguments = $0
|
||||
}
|
||||
Regex.ifconfig6.enumerateArguments(in: line) {
|
||||
Regex.ifconfig6.enumerateSpacedArguments(in: line) {
|
||||
optIfconfig6Arguments = $0
|
||||
}
|
||||
Regex.route.enumerateArguments(in: line) {
|
||||
Regex.route.enumerateSpacedArguments(in: line) {
|
||||
let routeEntryArguments = $0
|
||||
|
||||
let address = routeEntryArguments[0]
|
||||
|
@ -624,7 +624,7 @@ extension OpenVPN {
|
|||
}
|
||||
optRoutes4.append((address, mask, gateway))
|
||||
}
|
||||
Regex.route6.enumerateArguments(in: line) {
|
||||
Regex.route6.enumerateSpacedArguments(in: line) {
|
||||
let routeEntryArguments = $0
|
||||
|
||||
let destinationComponents = routeEntryArguments[0].components(separatedBy: "/")
|
||||
|
@ -642,10 +642,10 @@ extension OpenVPN {
|
|||
}
|
||||
optRoutes6.append((destination, prefix, gateway))
|
||||
}
|
||||
Regex.gateway.enumerateArguments(in: line) {
|
||||
Regex.gateway.enumerateSpacedArguments(in: line) {
|
||||
optGateway4Arguments = $0
|
||||
}
|
||||
Regex.dns.enumerateArguments(in: line) {
|
||||
Regex.dns.enumerateSpacedArguments(in: line) {
|
||||
guard $0.count == 2 else {
|
||||
return
|
||||
}
|
||||
|
@ -654,13 +654,13 @@ extension OpenVPN {
|
|||
}
|
||||
optDNSServers?.append($0[1])
|
||||
}
|
||||
Regex.domain.enumerateArguments(in: line) {
|
||||
Regex.domain.enumerateSpacedArguments(in: line) {
|
||||
guard $0.count == 2 else {
|
||||
return
|
||||
}
|
||||
optDomain = $0[1]
|
||||
}
|
||||
Regex.domainSearch.enumerateArguments(in: line) {
|
||||
Regex.domainSearch.enumerateSpacedArguments(in: line) {
|
||||
guard $0.count == 2 else {
|
||||
return
|
||||
}
|
||||
|
@ -669,7 +669,7 @@ extension OpenVPN {
|
|||
}
|
||||
optSearchDomains?.append($0[1])
|
||||
}
|
||||
Regex.proxy.enumerateArguments(in: line) {
|
||||
Regex.proxy.enumerateSpacedArguments(in: line) {
|
||||
if $0.count == 2 {
|
||||
guard let url = URL(string: $0[1]) else {
|
||||
unsupportedError = ConfigurationError.malformed(option: "dhcp-option PROXY_AUTO_CONFIG_URL has malformed URL")
|
||||
|
@ -693,14 +693,14 @@ extension OpenVPN {
|
|||
break
|
||||
}
|
||||
}
|
||||
Regex.proxyBypass.enumerateArguments(in: line) {
|
||||
Regex.proxyBypass.enumerateSpacedArguments(in: line) {
|
||||
guard !$0.isEmpty else {
|
||||
return
|
||||
}
|
||||
optProxyBypass = $0
|
||||
optProxyBypass?.removeFirst()
|
||||
}
|
||||
Regex.redirectGateway.enumerateArguments(in: line) {
|
||||
Regex.redirectGateway.enumerateSpacedArguments(in: line) {
|
||||
|
||||
// redirect IPv4 by default
|
||||
optRedirectGateway = [.def1]
|
||||
|
@ -712,7 +712,7 @@ extension OpenVPN {
|
|||
optRedirectGateway?.insert(opt)
|
||||
}
|
||||
}
|
||||
Regex.routeNoPull.enumerateComponents(in: line) { _ in
|
||||
Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in
|
||||
optRouteNoPull = true
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,26 @@ extension NSRegularExpression {
|
|||
public convenience init(_ pattern: String) {
|
||||
try! self.init(pattern: pattern, options: [])
|
||||
}
|
||||
|
||||
public func groups(in string: String) -> [String] {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public func enumerateComponents(in string: String, using block: ([String]) -> Void) {
|
||||
enumerateMatches(in: string, options: [], range: NSMakeRange(0, string.count)) { (result, flags, stop) in
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
@ -41,8 +58,8 @@ extension NSRegularExpression {
|
|||
}
|
||||
}
|
||||
|
||||
public func enumerateArguments(in string: String, using block: ([String]) -> Void) {
|
||||
enumerateComponents(in: string) { (tokens) in
|
||||
public func enumerateSpacedArguments(in string: String, using block: ([String]) -> Void) {
|
||||
enumerateSpacedComponents(in: string) { (tokens) in
|
||||
var args = tokens
|
||||
args.removeFirst()
|
||||
block(args)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue