mirror of
https://github.com/passepartoutvpn/wireguard-apple.git
synced 2025-01-03 15:22:35 +00:00
0b828f9b96
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>
153 lines
6.3 KiB
Swift
153 lines
6.3 KiB
Swift
// SPDX-License-Identifier: MIT
|
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
|
|
|
import Network
|
|
import Foundation
|
|
|
|
class DNSResolver {
|
|
|
|
static func isAllEndpointsAlreadyResolved(endpoints: [Endpoint?]) -> Bool {
|
|
for endpoint in endpoints {
|
|
guard let endpoint = endpoint else { continue }
|
|
if !endpoint.hasHostAsIPAddress() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
static func resolveSync(endpoints: [Endpoint?]) -> [Endpoint?]? {
|
|
let dispatchGroup = DispatchGroup()
|
|
|
|
if isAllEndpointsAlreadyResolved(endpoints: endpoints) {
|
|
return endpoints
|
|
}
|
|
|
|
var resolvedEndpoints: [Endpoint?] = Array(repeating: nil, count: endpoints.count)
|
|
for (index, endpoint) in endpoints.enumerated() {
|
|
guard let endpoint = endpoint else { continue }
|
|
if endpoint.hasHostAsIPAddress() {
|
|
resolvedEndpoints[index] = endpoint
|
|
} else {
|
|
let workItem = DispatchWorkItem {
|
|
resolvedEndpoints[index] = DNSResolver.resolveSync(endpoint: endpoint)
|
|
}
|
|
DispatchQueue.global(qos: .userInitiated).async(group: dispatchGroup, execute: workItem)
|
|
}
|
|
}
|
|
|
|
dispatchGroup.wait() // TODO: Timeout?
|
|
|
|
var hostnamesWithDnsResolutionFailure = [String]()
|
|
assert(endpoints.count == resolvedEndpoints.count)
|
|
for tuple in zip(endpoints, resolvedEndpoints) {
|
|
let endpoint = tuple.0
|
|
let resolvedEndpoint = tuple.1
|
|
if let endpoint = endpoint {
|
|
if resolvedEndpoint == nil {
|
|
guard let hostname = endpoint.hostname() else { fatalError() }
|
|
hostnamesWithDnsResolutionFailure.append(hostname)
|
|
}
|
|
}
|
|
}
|
|
if !hostnamesWithDnsResolutionFailure.isEmpty {
|
|
wg_log(.error, message: "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure.joined(separator: ", "))")
|
|
return nil
|
|
}
|
|
return resolvedEndpoints
|
|
}
|
|
|
|
private static func resolveSync(endpoint: Endpoint) -> Endpoint? {
|
|
switch endpoint.host {
|
|
case .name(let name, _):
|
|
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
|
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
|
|
}
|
|
}
|
|
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)
|
|
} else {
|
|
return nil
|
|
}
|
|
default:
|
|
return endpoint
|
|
}
|
|
}
|
|
}
|
|
|
|
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)"
|
|
}
|
|
|
|
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
|
var hints = addrinfo(
|
|
ai_flags: 0, // We set this to zero so that we actually resolve this using DNS64
|
|
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(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
|
|
}
|
|
}
|