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
- 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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

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))
}
}