2018-10-26 12:52:06 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2023-02-14 15:10:32 +00:00
|
|
|
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
2018-10-26 12:52:06 +00:00
|
|
|
|
|
|
|
import Network
|
|
|
|
import Foundation
|
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
enum DNSResolver {}
|
2018-10-26 12:52:06 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
extension DNSResolver {
|
2018-10-29 00:30:31 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
/// Concurrent queue used for DNS resolutions
|
|
|
|
private static let resolverQueue = DispatchQueue(label: "DNSResolverQueue", qos: .default, attributes: .concurrent)
|
2018-11-08 10:55:05 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
static func resolveSync(endpoints: [Endpoint?]) -> [Result<Endpoint, DNSResolutionError>?] {
|
2020-12-02 14:08:45 +00:00
|
|
|
let isAllEndpointsAlreadyResolved = endpoints.allSatisfy { maybeEndpoint -> Bool in
|
2020-11-04 15:59:33 +00:00
|
|
|
return maybeEndpoint?.hasHostAsIPAddress() ?? true
|
2020-12-02 14:08:45 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
|
|
|
|
if isAllEndpointsAlreadyResolved {
|
2020-12-02 14:08:45 +00:00
|
|
|
return endpoints.map { endpoint in
|
2020-11-04 15:59:33 +00:00
|
|
|
return endpoint.map { .success($0) }
|
|
|
|
}
|
2018-11-08 11:01:42 +00:00
|
|
|
}
|
|
|
|
|
2020-12-02 14:08:45 +00:00
|
|
|
return endpoints.concurrentMap(queue: resolverQueue) { endpoint -> Result<Endpoint, DNSResolutionError>? in
|
2020-11-04 15:59:33 +00:00
|
|
|
guard let endpoint = endpoint else { return nil }
|
|
|
|
|
2018-12-12 18:28:27 +00:00
|
|
|
if endpoint.hasHostAsIPAddress() {
|
2020-11-04 15:59:33 +00:00
|
|
|
return .success(endpoint)
|
2018-10-29 00:25:48 +00:00
|
|
|
} else {
|
2020-11-04 15:59:33 +00:00
|
|
|
return Result { try DNSResolver.resolveSync(endpoint: endpoint) }
|
2020-12-02 14:08:45 +00:00
|
|
|
.mapError { error -> DNSResolutionError in
|
|
|
|
// swiftlint:disable:next force_cast
|
|
|
|
return error as! DNSResolutionError
|
|
|
|
}
|
2018-10-26 23:11:05 +00:00
|
|
|
}
|
2018-10-28 10:04:07 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
}
|
2018-11-08 10:55:05 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
private static func resolveSync(endpoint: Endpoint) throws -> Endpoint {
|
|
|
|
guard case .name(let name, _) = endpoint.host else {
|
|
|
|
return endpoint
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
|
|
|
|
var hints = addrinfo()
|
|
|
|
hints.ai_flags = AI_ALL // We set this to ALL so that we get v4 addresses even on DNS64 networks
|
|
|
|
hints.ai_family = AF_UNSPEC
|
|
|
|
hints.ai_socktype = SOCK_DGRAM
|
|
|
|
hints.ai_protocol = IPPROTO_UDP
|
|
|
|
|
|
|
|
var resultPointer: UnsafeMutablePointer<addrinfo>?
|
|
|
|
defer {
|
|
|
|
resultPointer.flatMap { freeaddrinfo($0) }
|
2018-11-08 10:55:05 +00:00
|
|
|
}
|
2018-12-21 22:34:56 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
let errorCode = getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer)
|
|
|
|
if errorCode != 0 {
|
|
|
|
throw DNSResolutionError(errorCode: errorCode, address: name)
|
|
|
|
}
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
var ipv4Address: IPv4Address?
|
|
|
|
var ipv6Address: IPv6Address?
|
|
|
|
|
|
|
|
var next: UnsafeMutablePointer<addrinfo>? = resultPointer
|
|
|
|
let iterator = AnyIterator { () -> addrinfo? in
|
|
|
|
let result = next?.pointee
|
|
|
|
next = result?.ai_next
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
for addrInfo in iterator {
|
|
|
|
if let maybeIpv4Address = IPv4Address(addrInfo: addrInfo) {
|
|
|
|
ipv4Address = maybeIpv4Address
|
|
|
|
break // If we found an IPv4 address, we can stop
|
|
|
|
} else if let maybeIpv6Address = IPv6Address(addrInfo: addrInfo) {
|
|
|
|
ipv6Address = maybeIpv6Address
|
|
|
|
continue // If we already have an IPv6 address, we can skip this one
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We prefer an IPv4 address over an IPv6 address
|
|
|
|
if let ipv4Address = ipv4Address {
|
|
|
|
return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port)
|
|
|
|
} else if let ipv6Address = ipv6Address {
|
|
|
|
return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port)
|
|
|
|
} else {
|
|
|
|
// Must never happen
|
|
|
|
fatalError()
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
|
|
|
}
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
extension Endpoint {
|
2020-11-04 15:59:33 +00:00
|
|
|
func withReresolvedIP() throws -> Endpoint {
|
2023-12-15 23:40:37 +00:00
|
|
|
#if os(iOS) || os(tvOS)
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
let hostname: String
|
|
|
|
switch host {
|
|
|
|
case .name(let name, _):
|
|
|
|
hostname = name
|
|
|
|
case .ipv4(let address):
|
|
|
|
hostname = "\(address)"
|
|
|
|
case .ipv6(let address):
|
|
|
|
hostname = "\(address)"
|
2019-04-08 09:48:26 +00:00
|
|
|
@unknown default:
|
|
|
|
fatalError()
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
}
|
2018-12-12 21:33:14 +00:00
|
|
|
|
2020-11-04 15:59:33 +00:00
|
|
|
var hints = addrinfo()
|
|
|
|
hints.ai_family = AF_UNSPEC
|
|
|
|
hints.ai_socktype = SOCK_DGRAM
|
|
|
|
hints.ai_protocol = IPPROTO_UDP
|
2020-12-01 10:18:31 +00:00
|
|
|
hints.ai_flags = 0 // We set this to zero so that we actually resolve this using DNS64
|
2020-11-04 15:59:33 +00:00
|
|
|
|
|
|
|
var result: UnsafeMutablePointer<addrinfo>?
|
|
|
|
defer {
|
|
|
|
result.flatMap { freeaddrinfo($0) }
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
|
|
|
|
let errorCode = getaddrinfo(hostname, "\(self.port)", &hints, &result)
|
|
|
|
if errorCode != 0 {
|
|
|
|
throw DNSResolutionError(errorCode: errorCode, address: hostname)
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
|
|
|
|
let addrInfo = result!.pointee
|
|
|
|
if let ipv4Address = IPv4Address(addrInfo: addrInfo) {
|
|
|
|
return Endpoint(host: .ipv4(ipv4Address), port: port)
|
|
|
|
} else if let ipv6Address = IPv6Address(addrInfo: addrInfo) {
|
|
|
|
return Endpoint(host: .ipv6(ipv6Address), port: port)
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
} else {
|
2020-11-04 15:59:33 +00:00
|
|
|
fatalError()
|
Rework DNS and routes in network extension
The DNS resolver prior had useless comments, awful nesting, converted
bytes into strings and back into bytes, and generally made no sense.
That's been rewritten now.
But more fundumentally, this commit made the DNS resolver actually
accomplish its objective, by passing AI_ALL to it. It turns out, though,
that the Go library isn't actually using GAI in the way we need for
parsing IP addresses, so we actually need to do another round, this time
with hints flag as zero, so that we get the DNS64 address.
Additionally, since we're now binding sockets to interfaces, we can
entirely remove the excludedRoutes logic.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2018-12-28 18:34:31 +00:00
|
|
|
}
|
2019-01-22 12:09:38 +00:00
|
|
|
#elseif os(macOS)
|
|
|
|
return self
|
|
|
|
#else
|
|
|
|
#error("Unimplemented")
|
|
|
|
#endif
|
2018-12-12 21:33:14 +00:00
|
|
|
}
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
2020-11-04 15:59:33 +00:00
|
|
|
|
|
|
|
/// An error type describing DNS resolution error
|
|
|
|
public struct DNSResolutionError: LocalizedError {
|
|
|
|
public let errorCode: Int32
|
|
|
|
public let address: String
|
|
|
|
|
|
|
|
init(errorCode: Int32, address: String) {
|
|
|
|
self.errorCode = errorCode
|
|
|
|
self.address = address
|
|
|
|
}
|
|
|
|
|
|
|
|
public var errorDescription: String? {
|
|
|
|
return String(cString: gai_strerror(errorCode))
|
|
|
|
}
|
|
|
|
}
|