Support --remote-random-hostname (#286)

This commit is contained in:
Davide De Rosa 2022-10-17 09:00:23 +02:00 committed by GitHub
parent 769a79c4c0
commit 17c272d733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 6 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- OpenVPN: Support for `--route-nopull`. [#280](https://github.com/passepartoutvpn/tunnelkit/pull/280)
- OpenVPN: Support for `--remote-random-hostname`. [#286](https://github.com/passepartoutvpn/tunnelkit/pull/286)
### Changed

View File

@ -31,10 +31,39 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
public let proto: EndpointProtocol
public init(_ address: String, _ proto: EndpointProtocol) {
public init(_ address: String, _ proto: EndpointProtocol) {
self.address = address
self.proto = proto
}
public var isIPv4: Bool {
var addr = in_addr()
let result = address.withCString {
inet_pton(AF_INET, $0, &addr)
}
return result > 0
}
public var isIPv6: Bool {
var addr = in_addr()
let result = address.withCString {
inet_pton(AF_INET6, $0, &addr)
}
return result > 0
}
public var isHostname: Bool {
!isIPv4 && !isIPv6
}
public func withRandomPrefixLength(_ length: Int) throws -> Endpoint {
guard isHostname else {
return self
}
let prefix = try SecureRandom.data(length: length)
let prefixedAddress = "\(prefix.toHex()).\(address)"
return Endpoint(prefixedAddress, proto)
}
// MARK: RawRepresentable

View File

@ -57,12 +57,9 @@ class ConnectionStrategy {
}
init(configuration: OpenVPN.Configuration) {
guard var remotes = configuration.remotes, !remotes.isEmpty else {
guard let remotes = configuration.processedRemotes, !remotes.isEmpty else {
fatalError("No remotes provided")
}
if configuration.randomizeEndpoint ?? false {
remotes.shuffle()
}
self.remotes = remotes.map(ResolvedRemote.init)
currentRemoteIndex = 0
}

View File

@ -229,6 +229,9 @@ extension OpenVPN {
/// Picks endpoint from `remotes` randomly.
public var randomizeEndpoint: Bool?
/// Prepend hostnames with a number of random bytes defined in `Configuration.randomHostnamePrefixLength`.
public var randomizeHostnames: Bool?
/// Server is patched for the PIA VPN provider.
public var usesPIAPatches: Bool?
@ -345,6 +348,7 @@ extension OpenVPN {
checksSANHost: checksSANHost,
sanHost: sanHost,
randomizeEndpoint: randomizeEndpoint,
randomizeHostnames: randomizeHostnames,
usesPIAPatches: usesPIAPatches,
mtu: mtu,
authUserPass: authUserPass,
@ -381,6 +385,8 @@ extension OpenVPN {
static let compressionAlgorithm: CompressionAlgorithm = .disabled
}
private static let randomHostnamePrefixLength = 6
/// - Seealso: `ConfigurationBuilder.cipher`
public let cipher: Cipher?
@ -438,6 +444,9 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.randomizeEndpoint`
public let randomizeEndpoint: Bool?
/// - Seealso: `ConfigurationBuilder.randomizeHostnames`
public var randomizeHostnames: Bool?
/// - Seealso: `ConfigurationBuilder.usesPIAPatches`
public let usesPIAPatches: Bool?
@ -524,6 +533,28 @@ extension OpenVPN {
let pulled = Array(Set(all).subtracting(notPulled))
return !pulled.isEmpty ? pulled : nil
}
// MARK: Computed
public var processedRemotes: [Endpoint]? {
guard var processedRemotes = remotes else {
return nil
}
if randomizeEndpoint ?? false {
processedRemotes.shuffle()
}
if let randomPrefixLength = (randomizeHostnames ?? false) ? Self.randomHostnamePrefixLength : nil {
processedRemotes = processedRemotes.compactMap {
do {
return try $0.withRandomPrefixLength(randomPrefixLength)
} catch {
log.warning("Could not prepend random prefix: \(error)")
return nil
}
}
}
return processedRemotes
}
}
}
@ -558,6 +589,7 @@ extension OpenVPN.Configuration {
builder.checksSANHost = checksSANHost
builder.sanHost = sanHost
builder.randomizeEndpoint = randomizeEndpoint
builder.randomizeHostnames = randomizeHostnames
builder.usesPIAPatches = usesPIAPatches
builder.mtu = mtu
builder.authUserPass = authUserPass
@ -638,6 +670,9 @@ extension OpenVPN.Configuration {
if randomizeEndpoint ?? false {
log.info("\tRandomize endpoint: true")
}
if randomizeHostnames ?? false {
log.info("\tRandomize hostnames: true")
}
if let routingPolicies = routingPolicies {
log.info("\tGateway: \(routingPolicies.map { $0.rawValue })")
} else {

View File

@ -85,6 +85,8 @@ extension OpenVPN {
static let remoteRandom = NSRegularExpression("^remote-random")
static let remoteRandomHostname = NSRegularExpression("^remote-random-hostname")
static let mtu = NSRegularExpression("^tun-mtu +\\d+")
// MARK: Server
@ -123,7 +125,7 @@ extension OpenVPN {
// MARK: Unsupported
// static let fragment = NSRegularExpression("^fragment +\\d+")
// static let fragment = NSRegularExpression("^fragment +\\d+")
static let fragment = NSRegularExpression("^fragment")
static let connectionProxy = NSRegularExpression("^\\w+-proxy")
@ -274,6 +276,7 @@ extension OpenVPN {
var authUserPass = false
var optChecksEKU: Bool?
var optRandomizeEndpoint: Bool?
var optRandomizeHostnames: Bool?
var optMTU: Int?
//
var optAuthToken: String?
@ -574,6 +577,10 @@ extension OpenVPN {
isHandled = true
optRandomizeEndpoint = true
}
Regex.remoteRandomHostname.enumerateComponents(in: line) { _ in
isHandled = true
optRandomizeHostnames = true
}
Regex.mtu.enumerateArguments(in: line) {
isHandled = true
guard let str = $0.first else {
@ -796,6 +803,7 @@ extension OpenVPN {
sessionBuilder.authUserPass = authUserPass
sessionBuilder.checksEKU = optChecksEKU
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
sessionBuilder.randomizeHostnames = optRandomizeHostnames
sessionBuilder.mtu = optMTU
sessionBuilder.xorMask = optXorMask

View File

@ -0,0 +1,70 @@
//
// ConfigurationTests.swift
// TunnelKitOpenVPNTests
//
// Created by Davide De Rosa on 10/17/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
import TunnelKitCore
import TunnelKitOpenVPNCore
class ConfigurationTests: XCTestCase {
override func setUp() {
super.setUp()
CoreConfiguration.masksPrivateData = false
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testRandomizeHostnames() {
var builder = OpenVPN.ConfigurationBuilder()
let hostname = "my.host.name"
let ipv4 = "1.2.3.4"
builder.remotes = [
.init(hostname, .init(.udp, 1111)),
.init(ipv4, .init(.udp4, 3333))
]
builder.randomizeHostnames = true
let cfg = builder.build()
cfg.processedRemotes?.forEach {
let comps = $0.address.components(separatedBy: ".")
guard let first = comps.first else {
XCTFail()
return
}
if $0.isHostname {
XCTAssert($0.address.hasSuffix(hostname))
XCTAssert(first.count == 12)
XCTAssert(first.allSatisfy {
"0123456789abcdef".contains($0)
})
} else {
XCTAssert($0.address == ipv4)
}
}
}
}