2018-10-26 12:52:06 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2019-01-02 00:56:33 +00:00
|
|
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
2018-10-26 12:52:06 +00:00
|
|
|
|
|
|
|
import Network
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
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-12-21 13:53:16 +00:00
|
|
|
static func resolveSync(endpoints: [Endpoint?]) -> [Endpoint?]? {
|
2018-12-20 17:22:37 +00:00
|
|
|
let 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
|
|
|
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-12-21 13:53:16 +00:00
|
|
|
wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure.joined(separator: ", "))")
|
|
|
|
return nil
|
2018-11-08 10:55:05 +00:00
|
|
|
}
|
|
|
|
return resolvedEndpoints
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|
2018-12-21 22:34:56 +00:00
|
|
|
|
2018-10-26 12:52:06 +00:00
|
|
|
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))
|
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.
2018-12-28 18:34:31 +00:00
|
|
|
var hints = addrinfo(
|
|
|
|
ai_flags: AI_ALL, // We set this to ALL so that we get v4 addresses even on DNS64 networks
|
|
|
|
ai_family: AF_UNSPEC,
|
|
|
|
ai_socktype: SOCK_DGRAM,
|
|
|
|
ai_protocol: IPPROTO_UDP,
|
|
|
|
ai_addrlen: 0,
|
|
|
|
ai_canonname: nil,
|
|
|
|
ai_addr: nil,
|
|
|
|
ai_next: nil)
|
|
|
|
if getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer) != 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var next = resultPointer
|
|
|
|
var ipv4Address: IPv4Address?
|
|
|
|
var ipv6Address: IPv6Address?
|
|
|
|
while next != nil {
|
|
|
|
let result = next!.pointee
|
|
|
|
next = result.ai_next
|
|
|
|
if result.ai_family == AF_INET && result.ai_addrlen == MemoryLayout<sockaddr_in>.size {
|
|
|
|
var sa4 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in.self).pointee
|
|
|
|
ipv4Address = IPv4Address(Data(bytes: &sa4.sin_addr, count: MemoryLayout<in_addr>.size))
|
|
|
|
break // If we found an IPv4 address, we can stop
|
|
|
|
} else if result.ai_family == AF_INET6 && result.ai_addrlen == MemoryLayout<sockaddr_in6>.size {
|
|
|
|
var sa6 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in6.self).pointee
|
|
|
|
ipv6Address = IPv6Address(Data(bytes: &sa6.sin6_addr, count: MemoryLayout<in6_addr>.size))
|
|
|
|
continue // If we already have an IPv6 address, we can skip this one
|
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.
2018-12-28 18:34:31 +00:00
|
|
|
}
|
|
|
|
freeaddrinfo(resultPointer)
|
|
|
|
|
|
|
|
// 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)
|
2018-10-26 12:52:06 +00:00
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return endpoint
|
|
|
|
}
|
|
|
|
}
|
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.
2018-12-28 18:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
extension Endpoint {
|
|
|
|
func withReresolvedIP() -> Endpoint {
|
|
|
|
var ret = self
|
|
|
|
let hostname: String
|
|
|
|
switch host {
|
|
|
|
case .name(let name, _):
|
|
|
|
hostname = name
|
|
|
|
case .ipv4(let address):
|
|
|
|
hostname = "\(address)"
|
|
|
|
case .ipv6(let address):
|
|
|
|
hostname = "\(address)"
|
|
|
|
}
|
2018-12-12 21:33:14 +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.
2018-12-28 18:34:31 +00:00
|
|
|
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
2018-12-12 21:33:14 +00:00
|
|
|
var hints = addrinfo(
|
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.
2018-12-28 18:34:31 +00:00
|
|
|
ai_flags: 0, // We set this to zero so that we actually resolve this using DNS64
|
2018-12-12 21:33:14 +00:00
|
|
|
ai_family: AF_UNSPEC,
|
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.
2018-12-28 18:34:31 +00:00
|
|
|
ai_socktype: SOCK_DGRAM,
|
|
|
|
ai_protocol: IPPROTO_UDP,
|
2018-12-12 21:33:14 +00:00
|
|
|
ai_addrlen: 0,
|
|
|
|
ai_canonname: nil,
|
|
|
|
ai_addr: nil,
|
|
|
|
ai_next: nil)
|
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.
2018-12-28 18:34:31 +00:00
|
|
|
if getaddrinfo(hostname, "\(port)", &hints, &resultPointer) != 0 || resultPointer == nil {
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
let result = resultPointer!.pointee
|
|
|
|
if result.ai_family == AF_INET && result.ai_addrlen == MemoryLayout<sockaddr_in>.size {
|
|
|
|
var sa4 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in.self).pointee
|
|
|
|
let addr = IPv4Address(Data(bytes: &sa4.sin_addr, count: MemoryLayout<in_addr>.size))
|
|
|
|
ret = Endpoint(host: .ipv4(addr!), port: port)
|
|
|
|
} else if result.ai_family == AF_INET6 && result.ai_addrlen == MemoryLayout<sockaddr_in6>.size {
|
|
|
|
var sa6 = UnsafeRawPointer(result.ai_addr)!.assumingMemoryBound(to: sockaddr_in6.self).pointee
|
|
|
|
let addr = IPv6Address(Data(bytes: &sa6.sin6_addr, count: MemoryLayout<in6_addr>.size))
|
|
|
|
ret = Endpoint(host: .ipv6(addr!), port: port)
|
|
|
|
}
|
|
|
|
freeaddrinfo(resultPointer)
|
|
|
|
if ret.host != host {
|
|
|
|
wg_log(.debug, message: "DNS64: mapped \(host) to \(ret.host)")
|
|
|
|
} else {
|
|
|
|
wg_log(.debug, message: "DNS64: mapped \(host) to itself.")
|
|
|
|
}
|
|
|
|
return ret
|
2018-12-12 21:33:14 +00:00
|
|
|
}
|
2018-10-26 12:52:06 +00:00
|
|
|
}
|