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
|
### Added
|
||||||
|
|
||||||
- OpenVPN: Support for `--route-nopull`. [#280](https://github.com/passepartoutvpn/tunnelkit/pull/280)
|
- 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
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,35 @@ public struct Endpoint: RawRepresentable, Codable, Equatable, CustomStringConver
|
||||||
self.proto = proto
|
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
|
// MARK: RawRepresentable
|
||||||
|
|
||||||
public init?(rawValue: String) {
|
public init?(rawValue: String) {
|
||||||
|
|
|
@ -57,12 +57,9 @@ class ConnectionStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(configuration: OpenVPN.Configuration) {
|
init(configuration: OpenVPN.Configuration) {
|
||||||
guard var remotes = configuration.remotes, !remotes.isEmpty else {
|
guard let remotes = configuration.processedRemotes, !remotes.isEmpty else {
|
||||||
fatalError("No remotes provided")
|
fatalError("No remotes provided")
|
||||||
}
|
}
|
||||||
if configuration.randomizeEndpoint ?? false {
|
|
||||||
remotes.shuffle()
|
|
||||||
}
|
|
||||||
self.remotes = remotes.map(ResolvedRemote.init)
|
self.remotes = remotes.map(ResolvedRemote.init)
|
||||||
currentRemoteIndex = 0
|
currentRemoteIndex = 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,6 +229,9 @@ extension OpenVPN {
|
||||||
/// Picks endpoint from `remotes` randomly.
|
/// Picks endpoint from `remotes` randomly.
|
||||||
public var randomizeEndpoint: Bool?
|
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.
|
/// Server is patched for the PIA VPN provider.
|
||||||
public var usesPIAPatches: Bool?
|
public var usesPIAPatches: Bool?
|
||||||
|
|
||||||
|
@ -345,6 +348,7 @@ extension OpenVPN {
|
||||||
checksSANHost: checksSANHost,
|
checksSANHost: checksSANHost,
|
||||||
sanHost: sanHost,
|
sanHost: sanHost,
|
||||||
randomizeEndpoint: randomizeEndpoint,
|
randomizeEndpoint: randomizeEndpoint,
|
||||||
|
randomizeHostnames: randomizeHostnames,
|
||||||
usesPIAPatches: usesPIAPatches,
|
usesPIAPatches: usesPIAPatches,
|
||||||
mtu: mtu,
|
mtu: mtu,
|
||||||
authUserPass: authUserPass,
|
authUserPass: authUserPass,
|
||||||
|
@ -381,6 +385,8 @@ extension OpenVPN {
|
||||||
static let compressionAlgorithm: CompressionAlgorithm = .disabled
|
static let compressionAlgorithm: CompressionAlgorithm = .disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static let randomHostnamePrefixLength = 6
|
||||||
|
|
||||||
/// - Seealso: `ConfigurationBuilder.cipher`
|
/// - Seealso: `ConfigurationBuilder.cipher`
|
||||||
public let cipher: Cipher?
|
public let cipher: Cipher?
|
||||||
|
|
||||||
|
@ -438,6 +444,9 @@ extension OpenVPN {
|
||||||
/// - Seealso: `ConfigurationBuilder.randomizeEndpoint`
|
/// - Seealso: `ConfigurationBuilder.randomizeEndpoint`
|
||||||
public let randomizeEndpoint: Bool?
|
public let randomizeEndpoint: Bool?
|
||||||
|
|
||||||
|
/// - Seealso: `ConfigurationBuilder.randomizeHostnames`
|
||||||
|
public var randomizeHostnames: Bool?
|
||||||
|
|
||||||
/// - Seealso: `ConfigurationBuilder.usesPIAPatches`
|
/// - Seealso: `ConfigurationBuilder.usesPIAPatches`
|
||||||
public let usesPIAPatches: Bool?
|
public let usesPIAPatches: Bool?
|
||||||
|
|
||||||
|
@ -524,6 +533,28 @@ extension OpenVPN {
|
||||||
let pulled = Array(Set(all).subtracting(notPulled))
|
let pulled = Array(Set(all).subtracting(notPulled))
|
||||||
return !pulled.isEmpty ? pulled : nil
|
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.checksSANHost = checksSANHost
|
||||||
builder.sanHost = sanHost
|
builder.sanHost = sanHost
|
||||||
builder.randomizeEndpoint = randomizeEndpoint
|
builder.randomizeEndpoint = randomizeEndpoint
|
||||||
|
builder.randomizeHostnames = randomizeHostnames
|
||||||
builder.usesPIAPatches = usesPIAPatches
|
builder.usesPIAPatches = usesPIAPatches
|
||||||
builder.mtu = mtu
|
builder.mtu = mtu
|
||||||
builder.authUserPass = authUserPass
|
builder.authUserPass = authUserPass
|
||||||
|
@ -638,6 +670,9 @@ extension OpenVPN.Configuration {
|
||||||
if randomizeEndpoint ?? false {
|
if randomizeEndpoint ?? false {
|
||||||
log.info("\tRandomize endpoint: true")
|
log.info("\tRandomize endpoint: true")
|
||||||
}
|
}
|
||||||
|
if randomizeHostnames ?? false {
|
||||||
|
log.info("\tRandomize hostnames: true")
|
||||||
|
}
|
||||||
if let routingPolicies = routingPolicies {
|
if let routingPolicies = routingPolicies {
|
||||||
log.info("\tGateway: \(routingPolicies.map { $0.rawValue })")
|
log.info("\tGateway: \(routingPolicies.map { $0.rawValue })")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -85,6 +85,8 @@ extension OpenVPN {
|
||||||
|
|
||||||
static let remoteRandom = NSRegularExpression("^remote-random")
|
static let remoteRandom = NSRegularExpression("^remote-random")
|
||||||
|
|
||||||
|
static let remoteRandomHostname = NSRegularExpression("^remote-random-hostname")
|
||||||
|
|
||||||
static let mtu = NSRegularExpression("^tun-mtu +\\d+")
|
static let mtu = NSRegularExpression("^tun-mtu +\\d+")
|
||||||
|
|
||||||
// MARK: Server
|
// MARK: Server
|
||||||
|
@ -123,7 +125,7 @@ extension OpenVPN {
|
||||||
|
|
||||||
// MARK: Unsupported
|
// MARK: Unsupported
|
||||||
|
|
||||||
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
||||||
static let fragment = NSRegularExpression("^fragment")
|
static let fragment = NSRegularExpression("^fragment")
|
||||||
|
|
||||||
static let connectionProxy = NSRegularExpression("^\\w+-proxy")
|
static let connectionProxy = NSRegularExpression("^\\w+-proxy")
|
||||||
|
@ -274,6 +276,7 @@ extension OpenVPN {
|
||||||
var authUserPass = false
|
var authUserPass = false
|
||||||
var optChecksEKU: Bool?
|
var optChecksEKU: Bool?
|
||||||
var optRandomizeEndpoint: Bool?
|
var optRandomizeEndpoint: Bool?
|
||||||
|
var optRandomizeHostnames: Bool?
|
||||||
var optMTU: Int?
|
var optMTU: Int?
|
||||||
//
|
//
|
||||||
var optAuthToken: String?
|
var optAuthToken: String?
|
||||||
|
@ -574,6 +577,10 @@ extension OpenVPN {
|
||||||
isHandled = true
|
isHandled = true
|
||||||
optRandomizeEndpoint = true
|
optRandomizeEndpoint = true
|
||||||
}
|
}
|
||||||
|
Regex.remoteRandomHostname.enumerateComponents(in: line) { _ in
|
||||||
|
isHandled = true
|
||||||
|
optRandomizeHostnames = true
|
||||||
|
}
|
||||||
Regex.mtu.enumerateArguments(in: line) {
|
Regex.mtu.enumerateArguments(in: line) {
|
||||||
isHandled = true
|
isHandled = true
|
||||||
guard let str = $0.first else {
|
guard let str = $0.first else {
|
||||||
|
@ -796,6 +803,7 @@ extension OpenVPN {
|
||||||
sessionBuilder.authUserPass = authUserPass
|
sessionBuilder.authUserPass = authUserPass
|
||||||
sessionBuilder.checksEKU = optChecksEKU
|
sessionBuilder.checksEKU = optChecksEKU
|
||||||
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
|
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
|
||||||
|
sessionBuilder.randomizeHostnames = optRandomizeHostnames
|
||||||
sessionBuilder.mtu = optMTU
|
sessionBuilder.mtu = optMTU
|
||||||
sessionBuilder.xorMask = optXorMask
|
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