Merge pull request #163 from passepartoutvpn/enforce-ipv4-ipv6-resolution
Enforce IPv4/6 endpoints
This commit is contained in:
commit
a35636b1b1
|
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Index out of range during negotiation (Grivus). [#143](https://github.com/passepartoutvpn/tunnelkit/pull/143)
|
||||
- Handle server shutdown/restart (remote `--explicit-exit-notify`). [#131](https://github.com/passepartoutvpn/tunnelkit/issues/131)
|
||||
- Abrupt disconnection upon unknown packet key id (johankool). [#161](https://github.com/passepartoutvpn/tunnelkit/pull/161)
|
||||
- Handle explicit IPv4/IPv6 protocols (`4` or `6` suffix in `--proto`). [#153](https://github.com/passepartoutvpn/tunnelkit/issues/153)
|
||||
- Pointer warnings from Xcode 11.4 upgrade.
|
||||
|
||||
## 2.2.1 (2019-12-14)
|
||||
|
|
|
@ -36,6 +36,16 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// Result of `DNSResolver`.
|
||||
public struct DNSRecord {
|
||||
|
||||
/// Address string.
|
||||
public let address: String
|
||||
|
||||
/// `true` if IPv6.
|
||||
public let isIPv6: Bool
|
||||
}
|
||||
|
||||
/// Convenient methods for DNS resolution.
|
||||
public class DNSResolver {
|
||||
private static let queue = DispatchQueue(label: "DNSResolver")
|
||||
|
@ -48,17 +58,17 @@ public class DNSResolver {
|
|||
- Parameter queue: The queue to execute the `completionHandler` in.
|
||||
- Parameter completionHandler: The completion handler with the resolved addresses and an optional error.
|
||||
*/
|
||||
public static func resolve(_ hostname: String, timeout: Int, queue: DispatchQueue, completionHandler: @escaping ([String]?, Error?) -> Void) {
|
||||
var pendingHandler: (([String]?, Error?) -> Void)? = completionHandler
|
||||
public static func resolve(_ hostname: String, timeout: Int, queue: DispatchQueue, completionHandler: @escaping ([DNSRecord]?, Error?) -> Void) {
|
||||
var pendingHandler: (([DNSRecord]?, Error?) -> Void)? = completionHandler
|
||||
let host = CFHostCreateWithName(nil, hostname as CFString).takeRetainedValue()
|
||||
DNSResolver.queue.async {
|
||||
CFHostStartInfoResolution(host, .addresses, nil)
|
||||
guard let handler = pendingHandler else {
|
||||
return
|
||||
}
|
||||
DNSResolver.didResolve(host: host) { (addrs, error) in
|
||||
DNSResolver.didResolve(host: host) { (records, error) in
|
||||
queue.async {
|
||||
handler(addrs, error)
|
||||
handler(records, error)
|
||||
pendingHandler = nil
|
||||
}
|
||||
}
|
||||
|
@ -73,14 +83,14 @@ public class DNSResolver {
|
|||
}
|
||||
}
|
||||
|
||||
private static func didResolve(host: CFHost, completionHandler: @escaping ([String]?, Error?) -> Void) {
|
||||
private static func didResolve(host: CFHost, completionHandler: @escaping ([DNSRecord]?, Error?) -> Void) {
|
||||
var success: DarwinBoolean = false
|
||||
guard let rawAddresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as Array? else {
|
||||
completionHandler(nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var ipAddresses: [String] = []
|
||||
var records: [DNSRecord] = []
|
||||
for case let rawAddress as Data in rawAddresses {
|
||||
var ipAddress = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
||||
let result: Int32 = rawAddress.withUnsafeBytes {
|
||||
|
@ -98,9 +108,14 @@ public class DNSResolver {
|
|||
guard result == 0 else {
|
||||
continue
|
||||
}
|
||||
ipAddresses.append(String(cString: ipAddress))
|
||||
let address = String(cString: ipAddress)
|
||||
if rawAddress.count == 16 {
|
||||
records.append(DNSRecord(address: address, isIPv6: false))
|
||||
} else {
|
||||
records.append(DNSRecord(address: address, isIPv6: true))
|
||||
}
|
||||
}
|
||||
completionHandler(ipAddresses, nil)
|
||||
completionHandler(records, nil)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,4 +33,16 @@ public enum SocketType: String {
|
|||
|
||||
/// TCP socket type.
|
||||
case tcp = "TCP"
|
||||
|
||||
/// UDP socket type (IPv4).
|
||||
case udp4 = "UDP4"
|
||||
|
||||
/// TCP socket type (IPv4).
|
||||
case tcp4 = "TCP4"
|
||||
|
||||
/// UDP socket type (IPv6).
|
||||
case udp6 = "UDP6"
|
||||
|
||||
/// TCP socket type (IPv6).
|
||||
case tcp6 = "TCP6"
|
||||
}
|
||||
|
|
|
@ -41,25 +41,38 @@ import SwiftyBeaver
|
|||
private let log = SwiftyBeaver.self
|
||||
|
||||
class ConnectionStrategy {
|
||||
struct Endpoint: CustomStringConvertible {
|
||||
let record: DNSRecord
|
||||
|
||||
let proto: EndpointProtocol
|
||||
|
||||
var isValid: Bool {
|
||||
if record.isIPv6 {
|
||||
return proto.socketType != .udp4 && proto.socketType != .tcp4
|
||||
} else {
|
||||
return proto.socketType != .udp6 && proto.socketType != .tcp6
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
|
||||
var description: String {
|
||||
return "\(record.address.maskedDescription):\(proto)"
|
||||
}
|
||||
}
|
||||
|
||||
private let hostname: String?
|
||||
|
||||
private let prefersResolvedAddresses: Bool
|
||||
|
||||
private var resolvedAddresses: [String]?
|
||||
|
||||
private let endpointProtocols: [EndpointProtocol]
|
||||
|
||||
private var currentProtocolIndex = 0
|
||||
private var endpoints: [Endpoint]
|
||||
|
||||
private var currentEndpointIndex: Int
|
||||
|
||||
private let resolvedAddresses: [String]
|
||||
|
||||
init(configuration: OpenVPNTunnelProvider.Configuration) {
|
||||
hostname = configuration.sessionConfiguration.hostname
|
||||
prefersResolvedAddresses = (hostname == nil) || configuration.prefersResolvedAddresses
|
||||
resolvedAddresses = configuration.resolvedAddresses
|
||||
if prefersResolvedAddresses {
|
||||
guard !(resolvedAddresses?.isEmpty ?? true) else {
|
||||
fatalError("Either hostname or resolved addresses provided")
|
||||
}
|
||||
}
|
||||
guard var endpointProtocols = configuration.sessionConfiguration.endpointProtocols else {
|
||||
fatalError("No endpoints provided")
|
||||
}
|
||||
|
@ -67,101 +80,130 @@ class ConnectionStrategy {
|
|||
endpointProtocols.shuffle()
|
||||
}
|
||||
self.endpointProtocols = endpointProtocols
|
||||
|
||||
currentEndpointIndex = 0
|
||||
if let resolvedAddresses = configuration.resolvedAddresses {
|
||||
if configuration.prefersResolvedAddresses {
|
||||
endpoints = ConnectionStrategy.unrolledEndpoints(
|
||||
records: resolvedAddresses.map { DNSRecord(address: $0, isIPv6: false) },
|
||||
protos: endpointProtocols
|
||||
)
|
||||
} else {
|
||||
endpoints = []
|
||||
}
|
||||
self.resolvedAddresses = resolvedAddresses
|
||||
} else {
|
||||
guard hostname != nil else {
|
||||
fatalError("Either configuration.hostname or resolvedRecords required")
|
||||
}
|
||||
endpoints = []
|
||||
resolvedAddresses = []
|
||||
}
|
||||
}
|
||||
|
||||
private static func unrolledEndpoints(ipv4Addresses: [String], protos: [EndpointProtocol]) -> [Endpoint] {
|
||||
return unrolledEndpoints(records: ipv4Addresses.map { DNSRecord(address: $0, isIPv6: false) }, protos: protos)
|
||||
}
|
||||
|
||||
private static func unrolledEndpoints(records: [DNSRecord], protos: [EndpointProtocol]) -> [Endpoint] {
|
||||
guard !records.isEmpty else {
|
||||
return []
|
||||
}
|
||||
var endpoints: [Endpoint] = []
|
||||
for r in records {
|
||||
for p in protos {
|
||||
let endpoint = Endpoint(record: r, proto: p)
|
||||
guard endpoint.isValid else {
|
||||
continue
|
||||
}
|
||||
endpoints.append(endpoint)
|
||||
}
|
||||
}
|
||||
log.debug("Unrolled endpoints: \(endpoints.maskedDescription)")
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func hasEndpoint() -> Bool {
|
||||
return currentEndpointIndex < endpoints.count
|
||||
}
|
||||
|
||||
func currentEndpoint() -> Endpoint {
|
||||
guard hasEndpoint() else {
|
||||
fatalError("Endpoint index out of bounds (\(currentEndpointIndex) >= \(endpoints.count))")
|
||||
}
|
||||
return endpoints[currentEndpointIndex]
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func tryNextEndpoint() -> Bool {
|
||||
guard hasEndpoint() else {
|
||||
return false
|
||||
}
|
||||
currentEndpointIndex += 1
|
||||
guard currentEndpointIndex < endpoints.count else {
|
||||
log.debug("Exhausted endpoints")
|
||||
return false
|
||||
}
|
||||
log.debug("Try next endpoint: \(currentEndpoint().maskedDescription)")
|
||||
return true
|
||||
}
|
||||
|
||||
func createSocket(
|
||||
from provider: NEProvider,
|
||||
timeout: Int,
|
||||
preferredAddress: String? = nil,
|
||||
queue: DispatchQueue,
|
||||
completionHandler: @escaping (GenericSocket?, Error?) -> Void) {
|
||||
|
||||
// reuse preferred address
|
||||
if let preferredAddress = preferredAddress {
|
||||
log.debug("Pick preferred address: \(preferredAddress.maskedDescription)")
|
||||
let socket = provider.createSocket(to: preferredAddress, protocol: currentProtocol())
|
||||
|
||||
if hasEndpoint() {
|
||||
let endpoint = currentEndpoint()
|
||||
log.debug("Pick current endpoint: \(endpoint.maskedDescription)")
|
||||
let socket = provider.createSocket(to: endpoint)
|
||||
completionHandler(socket, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// use any resolved address
|
||||
if prefersResolvedAddresses, let resolvedAddress = anyResolvedAddress() {
|
||||
log.debug("Pick resolved address: \(resolvedAddress.maskedDescription)")
|
||||
let socket = provider.createSocket(to: resolvedAddress, protocol: currentProtocol())
|
||||
completionHandler(socket, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// fall back to DNS
|
||||
log.debug("No endpoints available, will resort to DNS resolution")
|
||||
|
||||
guard let hostname = hostname else {
|
||||
log.error("DNS resolution unavailable: no hostname provided!")
|
||||
completionHandler(nil, OpenVPNTunnelProvider.ProviderError.dnsFailure)
|
||||
return
|
||||
}
|
||||
log.debug("DNS resolve hostname: \(hostname.maskedDescription)")
|
||||
DNSResolver.resolve(hostname, timeout: timeout, queue: queue) { (addresses, error) in
|
||||
|
||||
// refresh resolved addresses
|
||||
if let resolved = addresses, !resolved.isEmpty {
|
||||
self.resolvedAddresses = resolved
|
||||
|
||||
log.debug("DNS resolved addresses: \(resolved.map { $0.maskedDescription })")
|
||||
DNSResolver.resolve(hostname, timeout: timeout, queue: queue) { (records, error) in
|
||||
self.currentEndpointIndex = 0
|
||||
if let records = records, !records.isEmpty {
|
||||
log.debug("DNS resolved addresses: \(records.map { $0.address }.maskedDescription)")
|
||||
self.endpoints = ConnectionStrategy.unrolledEndpoints(records: records, protos: self.endpointProtocols)
|
||||
} else {
|
||||
log.error("DNS resolution failed!")
|
||||
log.debug("Fall back to resolved addresses: \(self.resolvedAddresses.maskedDescription)")
|
||||
self.endpoints = ConnectionStrategy.unrolledEndpoints(ipv4Addresses: self.resolvedAddresses, protos: self.endpointProtocols)
|
||||
}
|
||||
|
||||
guard let targetAddress = self.resolvedAddress(from: addresses) else {
|
||||
log.error("No resolved or fallback address available")
|
||||
|
||||
guard self.hasEndpoint() else {
|
||||
log.error("No endpoints available")
|
||||
completionHandler(nil, OpenVPNTunnelProvider.ProviderError.dnsFailure)
|
||||
return
|
||||
}
|
||||
|
||||
let socket = provider.createSocket(to: targetAddress, protocol: self.currentProtocol())
|
||||
let targetEndpoint = self.currentEndpoint()
|
||||
log.debug("Pick current endpoint: \(targetEndpoint.maskedDescription)")
|
||||
let socket = provider.createSocket(to: targetEndpoint)
|
||||
completionHandler(socket, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func tryNextProtocol() -> Bool {
|
||||
let next = currentProtocolIndex + 1
|
||||
guard next < endpointProtocols.count else {
|
||||
log.debug("No more protocols available")
|
||||
return false
|
||||
}
|
||||
currentProtocolIndex = next
|
||||
log.debug("Fall back to next protocol: \(currentProtocol())")
|
||||
return true
|
||||
}
|
||||
|
||||
private func currentProtocol() -> EndpointProtocol {
|
||||
return endpointProtocols[currentProtocolIndex]
|
||||
}
|
||||
|
||||
private func resolvedAddress(from addresses: [String]?) -> String? {
|
||||
guard let resolved = addresses, !resolved.isEmpty else {
|
||||
return anyResolvedAddress()
|
||||
}
|
||||
return resolved[0]
|
||||
}
|
||||
|
||||
private func anyResolvedAddress() -> String? {
|
||||
guard let addresses = resolvedAddresses, !addresses.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
let n = Int(arc4random() % UInt32(addresses.count))
|
||||
return addresses[n]
|
||||
}
|
||||
}
|
||||
|
||||
private extension NEProvider {
|
||||
func createSocket(to address: String, protocol endpointProtocol: EndpointProtocol) -> GenericSocket {
|
||||
let endpoint = NWHostEndpoint(hostname: address, port: "\(endpointProtocol.port)")
|
||||
switch endpointProtocol.socketType {
|
||||
case .udp:
|
||||
let impl = createUDPSession(to: endpoint, from: nil)
|
||||
func createSocket(to endpoint: ConnectionStrategy.Endpoint) -> GenericSocket {
|
||||
let ep = NWHostEndpoint(hostname: endpoint.record.address, port: "\(endpoint.proto.port)")
|
||||
switch endpoint.proto.socketType {
|
||||
case .udp, .udp4, .udp6:
|
||||
let impl = createUDPSession(to: ep, from: nil)
|
||||
return NEUDPSocket(impl: impl)
|
||||
|
||||
case .tcp:
|
||||
let impl = createTCPConnection(to: endpoint, enableTLS: false, tlsParameters: nil, delegate: nil)
|
||||
case .tcp, .tcp4, .tcp6:
|
||||
let impl = createTCPConnection(to: ep, enableTLS: false, tlsParameters: nil, delegate: nil)
|
||||
return NETCPSocket(impl: impl)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ extension OpenVPNTunnelProvider {
|
|||
/// - Seealso: `fallbackServerAddresses`
|
||||
public var prefersResolvedAddresses: Bool
|
||||
|
||||
/// Resolved addresses in case DNS fails or `prefersResolvedAddresses` is `true`.
|
||||
/// Resolved addresses in case DNS fails or `prefersResolvedAddresses` is `true` (IPv4 only).
|
||||
public var resolvedAddresses: [String]?
|
||||
|
||||
/// The MTU of the link.
|
||||
|
|
|
@ -311,7 +311,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
|
|||
|
||||
// MARK: Connection (tunnel queue)
|
||||
|
||||
private func connectTunnel(upgradedSocket: GenericSocket? = nil, preferredAddress: String? = nil) {
|
||||
private func connectTunnel(upgradedSocket: GenericSocket? = nil) {
|
||||
log.info("Creating link session")
|
||||
|
||||
// reuse upgraded socket
|
||||
|
@ -321,7 +321,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
|
|||
return
|
||||
}
|
||||
|
||||
strategy.createSocket(from: self, timeout: dnsTimeout, preferredAddress: preferredAddress, queue: tunnelQueue) { (socket, error) in
|
||||
strategy.createSocket(from: self, timeout: dnsTimeout, queue: tunnelQueue) { (socket, error) in
|
||||
guard let socket = socket else {
|
||||
self.disposeTunnel(error: error)
|
||||
return
|
||||
|
@ -424,7 +424,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
|
|||
|
||||
// fallback: TCP connection timeout suggests falling back
|
||||
if let _ = socket as? NETCPSocket {
|
||||
guard tryNextProtocol() else {
|
||||
guard tryNextEndpoint() else {
|
||||
// disposeTunnel
|
||||
return
|
||||
}
|
||||
|
@ -471,7 +471,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
|
|||
|
||||
// fallback: UDP is connection-less, treat negotiation timeout as socket timeout
|
||||
if didTimeoutNegotiation {
|
||||
guard tryNextProtocol() else {
|
||||
guard tryNextEndpoint() else {
|
||||
// disposeTunnel
|
||||
return
|
||||
}
|
||||
|
@ -489,7 +489,7 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
|
|||
return
|
||||
}
|
||||
|
||||
self.connectTunnel(upgradedSocket: upgradedSocket, preferredAddress: socket.remoteAddress)
|
||||
self.connectTunnel(upgradedSocket: upgradedSocket)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -775,8 +775,8 @@ extension OpenVPNTunnelProvider: OpenVPNSessionDelegate {
|
|||
}
|
||||
|
||||
extension OpenVPNTunnelProvider {
|
||||
private func tryNextProtocol() -> Bool {
|
||||
guard strategy.tryNextProtocol() else {
|
||||
private func tryNextEndpoint() -> Bool {
|
||||
guard strategy.tryNextEndpoint() else {
|
||||
disposeTunnel(error: ProviderError.exhaustedProtocols)
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -62,11 +62,11 @@ extension OpenVPN {
|
|||
|
||||
// MARK: Client
|
||||
|
||||
static let proto = NSRegularExpression("^proto +(udp6?|tcp6?)")
|
||||
static let proto = NSRegularExpression("^proto +(udp[46]?|tcp[46]?)")
|
||||
|
||||
static let port = NSRegularExpression("^port +\\d+")
|
||||
|
||||
static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp6?|tcp6?))?")
|
||||
static let remote = NSRegularExpression("^remote +[^ ]+( +\\d+)?( +(udp[46]?|tcp[46]?))?")
|
||||
|
||||
static let eku = NSRegularExpression("^remote-cert-tls +server")
|
||||
|
||||
|
@ -817,10 +817,6 @@ private extension String {
|
|||
|
||||
private extension SocketType {
|
||||
init?(protoString: String) {
|
||||
var str = protoString
|
||||
if str.hasSuffix("6") {
|
||||
str.removeLast()
|
||||
}
|
||||
self.init(rawValue: str.uppercased())
|
||||
self.init(rawValue: protoString.uppercased())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ class AppExtensionTests: XCTestCase {
|
|||
|
||||
func testDNSResolver() {
|
||||
let exp = expectation(description: "DNS")
|
||||
DNSResolver.resolve("djsbjhcbjzhbxjnvsd.com", timeout: 1000, queue: DispatchQueue.main) { (addrs, error) in
|
||||
DNSResolver.resolve("www.google.com", timeout: 1000, queue: .main) { (addrs, error) in
|
||||
defer {
|
||||
exp.fulfill()
|
||||
}
|
||||
|
@ -126,4 +126,99 @@ class AppExtensionTests: XCTestCase {
|
|||
XCTAssertEqual(string, expString)
|
||||
}
|
||||
}
|
||||
|
||||
func testEndpointCycling() {
|
||||
CoreConfiguration.masksPrivateData = false
|
||||
|
||||
var builder1 = OpenVPN.ConfigurationBuilder()
|
||||
builder1.hostname = "italy.privateinternetaccess.com"
|
||||
builder1.endpointProtocols = [
|
||||
EndpointProtocol(.tcp6, 2222),
|
||||
EndpointProtocol(.udp, 1111),
|
||||
EndpointProtocol(.udp4, 3333)
|
||||
]
|
||||
var builder2 = OpenVPNTunnelProvider.ConfigurationBuilder(sessionConfiguration: builder1.build())
|
||||
builder2.prefersResolvedAddresses = true
|
||||
builder2.resolvedAddresses = [
|
||||
"82.102.21.218",
|
||||
"82.102.21.214",
|
||||
"82.102.21.213",
|
||||
]
|
||||
let strategy = ConnectionStrategy(configuration: builder2.build())
|
||||
|
||||
let expected = [
|
||||
"82.102.21.218:UDP:1111",
|
||||
"82.102.21.218:UDP4:3333",
|
||||
"82.102.21.214:UDP:1111",
|
||||
"82.102.21.214:UDP4:3333",
|
||||
"82.102.21.213:UDP:1111",
|
||||
"82.102.21.213:UDP4:3333",
|
||||
]
|
||||
var i = 0
|
||||
while strategy.hasEndpoint() {
|
||||
let endpoint = strategy.currentEndpoint()
|
||||
print("\(endpoint)")
|
||||
XCTAssertEqual(endpoint.description, expected[i])
|
||||
i += 1
|
||||
strategy.tryNextEndpoint()
|
||||
}
|
||||
}
|
||||
|
||||
// func testEndpointCycling4() {
|
||||
// CoreConfiguration.masksPrivateData = false
|
||||
//
|
||||
// var builder = OpenVPN.ConfigurationBuilder()
|
||||
// builder.hostname = "italy.privateinternetaccess.com"
|
||||
// builder.endpointProtocols = [
|
||||
// EndpointProtocol(.tcp4, 2222),
|
||||
// ]
|
||||
// let strategy = ConnectionStrategy(
|
||||
// configuration: builder.build(),
|
||||
// resolvedRecords: [
|
||||
// DNSRecord(address: "111:bbbb:ffff::eeee", isIPv6: true),
|
||||
// DNSRecord(address: "11.22.33.44", isIPv6: false),
|
||||
// ]
|
||||
// )
|
||||
//
|
||||
// let expected = [
|
||||
// "11.22.33.44:TCP4:2222"
|
||||
// ]
|
||||
// var i = 0
|
||||
// while strategy.hasEndpoint() {
|
||||
// let endpoint = strategy.currentEndpoint()
|
||||
// print("\(endpoint)")
|
||||
// XCTAssertEqual(endpoint.description, expected[i])
|
||||
// i += 1
|
||||
// strategy.tryNextEndpoint()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func testEndpointCycling6() {
|
||||
// CoreConfiguration.masksPrivateData = false
|
||||
//
|
||||
// var builder = OpenVPN.ConfigurationBuilder()
|
||||
// builder.hostname = "italy.privateinternetaccess.com"
|
||||
// builder.endpointProtocols = [
|
||||
// EndpointProtocol(.udp6, 2222),
|
||||
// ]
|
||||
// let strategy = ConnectionStrategy(
|
||||
// configuration: builder.build(),
|
||||
// resolvedRecords: [
|
||||
// DNSRecord(address: "111:bbbb:ffff::eeee", isIPv6: true),
|
||||
// DNSRecord(address: "11.22.33.44", isIPv6: false),
|
||||
// ]
|
||||
// )
|
||||
//
|
||||
// let expected = [
|
||||
// "111:bbbb:ffff::eeee:UDP6:2222"
|
||||
// ]
|
||||
// var i = 0
|
||||
// while strategy.hasEndpoint() {
|
||||
// let endpoint = strategy.currentEndpoint()
|
||||
// print("\(endpoint)")
|
||||
// XCTAssertEqual(endpoint.description, expected[i])
|
||||
// i += 1
|
||||
// strategy.tryNextEndpoint()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue