Support --remote-random-hostname (#286)
This commit is contained in:
parent
769a79c4c0
commit
17c272d733
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue