2018-10-26 12:52:06 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2018-10-30 02:57:35 +00:00
|
|
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
2018-10-26 12:52:06 +00:00
|
|
|
|
|
|
|
import Network
|
|
|
|
import Foundation
|
|
|
|
|
2018-11-08 10:55:05 +00:00
|
|
|
enum DNSResolverError: Error {
|
|
|
|
case dnsResolutionFailed(hostnames: [String])
|
|
|
|
}
|
|
|
|
|
2018-10-26 12:52:06 +00:00
|
|
|
class DNSResolver {
|
|
|
|
|
2018-11-08 11:10:38 +00:00
|
|
|
static func isAllEndpointsAlreadyResolved(endpoints: [Endpoint?]) -> Bool {
|
|
|
|
for endpoint in endpoints {
|
2018-10-29 00:30:31 +00:00
|
|
|
guard let endpoint = endpoint else { continue }
|
2018-12-12 18:28:27 +00:00
|
|
|
if !endpoint.hasHostAsIPAddress() {
|
2018-11-08 11:01:42 +00:00
|
|
|
return false
|
2018-10-29 00:30:31 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-08 11:01:42 +00:00
|
|
|
return true
|
2018-10-29 00:30:31 +00:00
|
|
|
}
|
|
|
|
|
2018-11-08 11:10:38 +00:00
|
|
|
static func resolveSync(endpoints: [Endpoint?]) throws -> [Endpoint?] {
|
|
|
|
let dispatchGroup: DispatchGroup = DispatchGroup()
|
2018-11-08 10:55:05 +00:00
|
|
|
|
2018-12-12 18:28:27 +00:00
|
|
|
if isAllEndpointsAlreadyResolved(endpoints: endpoints) {
|
2018-11-08 11:01:42 +00:00
|
|
|
return endpoints
|
|
|
|
}
|
|
|
|
|
2018-12-12 18:28:27 +00:00
|
|
|
var resolvedEndpoints: [Endpoint?] = Array(repeating: nil, count: endpoints.count)
|
2018-12-12 17:40:57 +00:00
|
|
|
for (index, endpoint) in endpoints.enumerated() {
|
2018-10-29 00:25:48 +00:00
|
|
|
guard let endpoint = endpoint else { continue }
|
2018-12-12 18:28:27 +00:00
|
|
|
if endpoint.hasHostAsIPAddress() {
|
2018-12-12 17:40:57 +00:00
|
|
|
resolvedEndpoints[index] = endpoint
|
2018-10-29 00:25:48 +00:00
|
|
|
} else {
|
|
|
|
let workItem = DispatchWorkItem {
|
2018-12-12 17:40:57 +00:00
|
|
|
resolvedEndpoints[index] = DNSResolver.resolveSync(endpoint: endpoint)
|
2018-10-29 00:25:48 +00:00
|
|
|
}
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async(group: dispatchGroup, execute: workItem)
|
2018-10-26 23:11:05 +00:00
|
|
|
}
|
2018-10-28 10:04:07 +00:00
|
|
|
}
|
2018-11-08 10:55:05 +00:00
|
|
|
|
|
|
|
dispatchGroup.wait() // TODO: Timeout?
|
|
|
|
|
2018-12-13 03:09:52 +00:00
|
|
|
var hostnamesWithDnsResolutionFailure = [String]()
|
2018-11-08 10:55:05 +00:00
|
|
|
assert(endpoints.count == resolvedEndpoints.count)
|
|
|
|
for tuple in zip(endpoints, resolvedEndpoints) {
|
|
|
|
let endpoint = tuple.0
|
|
|
|
let resolvedEndpoint = tuple.1
|
|
|
|
if let endpoint = endpoint {
|
2018-12-12 18:28:27 +00:00
|
|
|
if resolvedEndpoint == nil {
|
2018-11-08 10:55:05 +00:00
|
|
|
// DNS resolution failed
|
|
|
|
guard let hostname = endpoint.hostname() else { fatalError() }
|
|
|
|
hostnamesWithDnsResolutionFailure.append(hostname)
|
2018-10-29 00:25:48 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
2018-12-12 18:28:27 +00:00
|
|
|
if !hostnamesWithDnsResolutionFailure.isEmpty {
|
2018-11-08 10:55:05 +00:00
|
|
|
throw DNSResolverError.dnsResolutionFailed(hostnames: hostnamesWithDnsResolutionFailure)
|
|
|
|
}
|
|
|
|
return resolvedEndpoints
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
2018-10-28 10:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
extension DNSResolver {
|
2018-10-26 12:52:06 +00:00
|
|
|
// Based on DNS resolution code by Jason Donenfeld <jason@zx2c4.com>
|
|
|
|
// in parse_endpoint() in src/tools/config.c in the WireGuard codebase
|
|
|
|
private static func resolveSync(endpoint: Endpoint) -> Endpoint? {
|
2018-12-12 18:28:27 +00:00
|
|
|
switch endpoint.host {
|
2018-10-26 12:52:06 +00:00
|
|
|
case .name(let name, _):
|
2018-12-12 21:33:14 +00:00
|
|
|
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
|
|
|
|
2018-10-26 12:52:06 +00:00
|
|
|
// The endpoint is a hostname and needs DNS resolution
|
2018-12-12 21:33:14 +00:00
|
|
|
if addressInfo(for: name, port: endpoint.port, resultPointer: &resultPointer) == 0 {
|
2018-10-26 12:52:06 +00:00
|
|
|
// getaddrinfo succeeded
|
|
|
|
let ipv4Buffer = UnsafeMutablePointer<Int8>.allocate(capacity: Int(INET_ADDRSTRLEN))
|
|
|
|
let ipv6Buffer = UnsafeMutablePointer<Int8>.allocate(capacity: Int(INET6_ADDRSTRLEN))
|
2018-11-03 18:35:25 +00:00
|
|
|
var ipv4AddressString: String?
|
|
|
|
var ipv6AddressString: String?
|
2018-12-12 18:28:27 +00:00
|
|
|
while resultPointer != nil {
|
2018-10-26 12:52:06 +00:00
|
|
|
let result = resultPointer!.pointee
|
|
|
|
resultPointer = result.ai_next
|
2018-12-12 18:28:27 +00:00
|
|
|
if result.ai_family == AF_INET && result.ai_addrlen == MemoryLayout<sockaddr_in>.size {
|
2018-11-02 04:34:17 +00:00
|
|
|
var sa4 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in.self).pointee
|
2018-12-12 18:28:27 +00:00
|
|
|
if inet_ntop(result.ai_family, &sa4.sin_addr, ipv4Buffer, socklen_t(INET_ADDRSTRLEN)) != nil {
|
2018-10-26 12:52:06 +00:00
|
|
|
ipv4AddressString = String(cString: ipv4Buffer)
|
|
|
|
// If we found an IPv4 address, we can stop
|
|
|
|
break
|
|
|
|
}
|
2018-12-12 18:28:27 +00:00
|
|
|
} else if result.ai_family == AF_INET6 && result.ai_addrlen == MemoryLayout<sockaddr_in6>.size {
|
|
|
|
if ipv6AddressString != nil {
|
2018-10-26 12:52:06 +00:00
|
|
|
// If we already have an IPv6 address, we can skip this one
|
|
|
|
continue
|
|
|
|
}
|
2018-11-02 04:34:17 +00:00
|
|
|
var sa6 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in6.self).pointee
|
2018-12-12 18:28:27 +00:00
|
|
|
if inet_ntop(result.ai_family, &sa6.sin6_addr, ipv6Buffer, socklen_t(INET6_ADDRSTRLEN)) != nil {
|
2018-10-26 12:52:06 +00:00
|
|
|
ipv6AddressString = String(cString: ipv6Buffer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ipv4Buffer.deallocate()
|
|
|
|
ipv6Buffer.deallocate()
|
|
|
|
// We prefer an IPv4 address over an IPv6 address
|
|
|
|
if let ipv4AddressString = ipv4AddressString, let ipv4Address = IPv4Address(ipv4AddressString) {
|
2018-12-12 21:33:14 +00:00
|
|
|
return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port)
|
2018-10-26 12:52:06 +00:00
|
|
|
} else if let ipv6AddressString = ipv6AddressString, let ipv6Address = IPv6Address(ipv6AddressString) {
|
2018-12-12 21:33:14 +00:00
|
|
|
return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port)
|
2018-10-26 12:52:06 +00:00
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// getaddrinfo failed
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// The endpoint is already resolved
|
|
|
|
return endpoint
|
|
|
|
}
|
|
|
|
}
|
2018-12-12 21:33:14 +00:00
|
|
|
|
|
|
|
private static func addressInfo(for name: String, port: NWEndpoint.Port, resultPointer: inout UnsafeMutablePointer<addrinfo>?) -> Int32 {
|
|
|
|
var hints = addrinfo(
|
|
|
|
ai_flags: 0,
|
|
|
|
ai_family: AF_UNSPEC,
|
|
|
|
ai_socktype: SOCK_DGRAM, // WireGuard is UDP-only
|
|
|
|
ai_protocol: IPPROTO_UDP, // WireGuard is UDP-only
|
|
|
|
ai_addrlen: 0,
|
|
|
|
ai_canonname: nil,
|
|
|
|
ai_addr: nil,
|
|
|
|
ai_next: nil)
|
|
|
|
|
|
|
|
return getaddrinfo(
|
|
|
|
name.cString(using: .utf8), // Hostname
|
|
|
|
"\(port)".cString(using: .utf8), // Port
|
|
|
|
&hints,
|
|
|
|
&resultPointer)
|
|
|
|
}
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|