diff --git a/TunnelKit/Sources/AppExtension/ConnectionStrategy.swift b/TunnelKit/Sources/AppExtension/ConnectionStrategy.swift index 6fa263e..3837830 100644 --- a/TunnelKit/Sources/AppExtension/ConnectionStrategy.swift +++ b/TunnelKit/Sources/AppExtension/ConnectionStrategy.swift @@ -58,7 +58,12 @@ class ConnectionStrategy { self.hostname = hostname prefersResolvedAddresses = configuration.prefersResolvedAddresses resolvedAddresses = configuration.resolvedAddresses - endpointProtocols = configuration.endpointProtocols + + if configuration.sessionConfiguration.randomizeEndpoint ?? false { + endpointProtocols = configuration.endpointProtocols.shuffled() + } else { + endpointProtocols = configuration.endpointProtocols + } } func createSocket( diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift index b2decda..70c052e 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift @@ -67,7 +67,8 @@ extension TunnelKitProvider { keepAliveInterval: nil, renegotiatesAfter: nil, usesPIAPatches: nil, - dnsServers: nil + dnsServers: nil, + randomizeEndpoint: false ), shouldDebug: false, debugLogFormat: nil, @@ -186,10 +187,11 @@ extension TunnelKitProvider { throw ProviderConfigurationError.parameter(name: "protocolConfiguration.providerConfiguration[\(S.tlsWrap)]") } } - sessionConfigurationBuilder.keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval - sessionConfigurationBuilder.renegotiatesAfter = providerConfiguration[S.renegotiatesAfter] as? TimeInterval - sessionConfigurationBuilder.usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? false + sessionConfigurationBuilder.keepAliveInterval = providerConfiguration[S.keepAlive] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.keepAliveInterval + sessionConfigurationBuilder.renegotiatesAfter = providerConfiguration[S.renegotiatesAfter] as? TimeInterval ?? ConfigurationBuilder.defaults.sessionConfiguration.renegotiatesAfter + sessionConfigurationBuilder.usesPIAPatches = providerConfiguration[S.usesPIAPatches] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.usesPIAPatches sessionConfigurationBuilder.dnsServers = providerConfiguration[S.dnsServers] as? [String] + sessionConfigurationBuilder.randomizeEndpoint = providerConfiguration[S.randomizeEndpoint] as? Bool ?? ConfigurationBuilder.defaults.sessionConfiguration.randomizeEndpoint sessionConfiguration = sessionConfigurationBuilder.build() shouldDebug = providerConfiguration[S.debug] as? Bool ?? ConfigurationBuilder.defaults.shouldDebug @@ -261,6 +263,8 @@ extension TunnelKitProvider { static let dnsServers = "DNSServers" + static let randomizeEndpoint = "RandomizeEndpoint" + // MARK: Debugging static let debug = "Debug" @@ -426,6 +430,9 @@ extension TunnelKitProvider { if let dnsServers = sessionConfiguration.dnsServers { dict[S.dnsServers] = dnsServers } + if let randomizeEndpoint = sessionConfiguration.randomizeEndpoint { + dict[S.randomizeEndpoint] = randomizeEndpoint + } if let debugLogFormat = debugLogFormat { dict[S.debugLogFormat] = debugLogFormat } @@ -508,6 +515,9 @@ extension TunnelKitProvider { if let dnsServers = sessionConfiguration.dnsServers { log.info("\tCustom DNS servers: \(dnsServers.maskedDescription)") } + if sessionConfiguration.randomizeEndpoint ?? false { + log.info("\tRandomize endpoint: true") + } log.info("\tDebug: \(shouldDebug)") log.info("\tMasks private data: \(masksPrivateData ?? true)") } diff --git a/TunnelKit/Sources/Core/ConfigurationParser.swift b/TunnelKit/Sources/Core/ConfigurationParser.swift index 0790ca1..da31a44 100644 --- a/TunnelKit/Sources/Core/ConfigurationParser.swift +++ b/TunnelKit/Sources/Core/ConfigurationParser.swift @@ -96,6 +96,8 @@ public class ConfigurationParser { static let dns = NSRegularExpression("^dhcp-option +DNS6? +[\\d\\.a-fA-F:]+") + static let remoteRandom = NSRegularExpression("^remote-random") + // unsupported // static let fragment = NSRegularExpression("^fragment +\\d+") @@ -153,6 +155,7 @@ public class ConfigurationParser { var tlsKeyLines: [Substring]? var tlsWrap: SessionProxy.TLSWrap? var dnsServers: [String]? + var randomizeEndpoint = false var currentBlockName: String? var currentBlock: [String] = [] @@ -359,6 +362,9 @@ public class ConfigurationParser { } dnsServers?.append($0[1]) } + Regex.remoteRandom.enumerateComponents(in: line) { (_) in + randomizeEndpoint = true + } Regex.fragment.enumerateComponents(in: line) { (_) in unsupportedError = ParsingError.unsupportedConfiguration(option: "fragment") } @@ -434,6 +440,7 @@ public class ConfigurationParser { sessionBuilder.keepAliveInterval = keepAliveSeconds sessionBuilder.renegotiatesAfter = renegotiateAfterSeconds sessionBuilder.dnsServers = dnsServers + sessionBuilder.randomizeEndpoint = randomizeEndpoint return ParsingResult( url: originalURL, diff --git a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift index d1f0225..83983de 100644 --- a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift +++ b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift @@ -174,6 +174,9 @@ extension SessionProxy { /// Optionally override the server DNS entries. public var dnsServers: [String]? + /// Optionally randomize endpoint order. + public var randomizeEndpoint: Bool? + /// :nodoc: public init(ca: CryptoContainer) { cipher = .aes128cbc @@ -189,6 +192,7 @@ extension SessionProxy { renegotiatesAfter = nil usesPIAPatches = false dnsServers = nil + randomizeEndpoint = false } /** @@ -210,7 +214,8 @@ extension SessionProxy { keepAliveInterval: keepAliveInterval, renegotiatesAfter: renegotiatesAfter, usesPIAPatches: usesPIAPatches, - dnsServers: dnsServers + dnsServers: dnsServers, + randomizeEndpoint: randomizeEndpoint ) } } @@ -257,6 +262,9 @@ extension SessionProxy { /// - Seealso: `SessionProxy.ConfigurationBuilder.dnsServers` public let dnsServers: [String]? + /// - Seealso: `SessionProxy.ConfigurationBuilder.randomizeEndpoint` + public let randomizeEndpoint: Bool? + /** Returns a `SessionProxy.ConfigurationBuilder` to use this configuration as a starting point for a new one. @@ -276,6 +284,7 @@ extension SessionProxy { builder.renegotiatesAfter = renegotiatesAfter builder.usesPIAPatches = usesPIAPatches builder.dnsServers = dnsServers + builder.randomizeEndpoint = randomizeEndpoint return builder } @@ -295,7 +304,8 @@ extension SessionProxy { (lhs.keepAliveInterval == rhs.keepAliveInterval) && (lhs.renegotiatesAfter == rhs.renegotiatesAfter) && (lhs.usesPIAPatches == rhs.usesPIAPatches) && - (lhs.dnsServers == rhs.dnsServers) + (lhs.dnsServers == rhs.dnsServers) && + (lhs.randomizeEndpoint == rhs.randomizeEndpoint) } } }