1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
/// A DNS resolver built on top of the libc `getaddrinfo` function.
///
/// This is the lowest-common-denominator resolver available to NIO. It's not really a very good
/// solution because the `getaddrinfo` call blocks during the DNS resolution, meaning that this resolver
/// will block a thread for as long as it takes to perform the getaddrinfo call. To prevent it from blocking `EventLoop`
/// threads, it will offload the blocking `getaddrinfo` calls to a `DispatchQueue`.
/// One advantage from leveraging `getaddrinfo` is the automatic conformance to RFC 6724, which removes some of the work
/// needed to implement it.
///
/// This resolver is a single-use object: it can only be used to perform a single host resolution.
import Dispatch
#if os(Linux) || os(FreeBSD) || os(Android)
import CNIOLinux
#endif
#if os(Windows)
import let WinSDK.AF_INET
import let WinSDK.AF_INET6
import func WinSDK.FreeAddrInfoW
import func WinSDK.GetAddrInfoW
import func WinSDK.gai_strerrorA
import struct WinSDK.ADDRESS_FAMILY
import struct WinSDK.ADDRINFOW
import struct WinSDK.SOCKADDR_IN
import struct WinSDK.SOCKADDR_IN6
#endif
// A thread-specific variable where we store the offload queue if we're on an `SelectableEventLoop`.
let offloadQueueTSV = ThreadSpecificVariable<DispatchQueue>()
internal class GetaddrinfoResolver: Resolver {
private let v4Future: EventLoopPromise<[SocketAddress]>
private let v6Future: EventLoopPromise<[SocketAddress]>
private let aiSocktype: NIOBSDSocket.SocketType
private let aiProtocol: NIOBSDSocket.OptionLevel
/// Create a new resolver.
///
/// - parameters:
/// - loop: The `EventLoop` whose thread this resolver will block.
/// - aiSocktype: The sock type to use as hint when calling getaddrinfo.
/// - aiProtocol: the protocol to use as hint when calling getaddrinfo.
init(loop: EventLoop, aiSocktype: NIOBSDSocket.SocketType,
aiProtocol: NIOBSDSocket.OptionLevel) {
self.v4Future = loop.makePromise()
self.v6Future = loop.makePromise()
self.aiSocktype = aiSocktype
self.aiProtocol = aiProtocol
}
/// Initiate a DNS A query for a given host.
///
/// Due to the nature of `getaddrinfo`, we only actually call the function once, in the AAAA query.
/// That means this just returns the future for the A results, which in practice will always have been
/// satisfied by the time this function is called.
///
/// - parameters:
/// - host: The hostname to do an A lookup on.
/// - port: The port we'll be connecting to.
/// - returns: An `EventLoopFuture` that fires with the result of the lookup.
func initiateAQuery(host: String, port: Int) -> EventLoopFuture<[SocketAddress]> {
return v4Future.futureResult
}
/// Initiate a DNS AAAA query for a given host.
///
/// Due to the nature of `getaddrinfo`, we only actually call the function once, in this function.
/// That means this function call actually blocks: sorry!
///
/// - parameters:
/// - host: The hostname to do an AAAA lookup on.
/// - port: The port we'll be connecting to.
/// - returns: An `EventLoopFuture` that fires with the result of the lookup.
func initiateAAAAQuery(host: String, port: Int) -> EventLoopFuture<[SocketAddress]> {
self.offloadQueue().async {
self.resolveBlocking(host: host, port: port)
}
return v6Future.futureResult
}
private func offloadQueue() -> DispatchQueue {
if let offloadQueue = offloadQueueTSV.currentValue {
return offloadQueue
} else {
if MultiThreadedEventLoopGroup.currentEventLoop != nil {
// Okay, we're on an SelectableEL thread. Let's stuff our queue into the thread local.
let offloadQueue = DispatchQueue(label: "io.swiftnio.GetaddrinfoResolver.offloadQueue")
offloadQueueTSV.currentValue = offloadQueue
return offloadQueue
} else {
return DispatchQueue.global()
}
}
}
/// Cancel all outstanding DNS queries.
///
/// This method is called whenever queries that have not completed no longer have their
/// results needed. The resolver should, if possible, abort any outstanding queries and
/// clean up their state.
///
/// In the getaddrinfo case this is a no-op, as the resolver blocks.
func cancelQueries() { }
/// Perform the DNS queries and record the result.
///
/// - parameters:
/// - host: The hostname to do the DNS queries on.
/// - port: The port we'll be connecting to.
private func resolveBlocking(host: String, port: Int) {
#if os(Windows)
host.withCString(encodedAs: UTF16.self) { wszHost in
String(port).withCString(encodedAs: UTF16.self) { wszPort in
var pResult: UnsafeMutablePointer<ADDRINFOW>?
var aiHints: ADDRINFOW = ADDRINFOW()
aiHints.ai_socktype = self.aiSocktype.rawValue
aiHints.ai_protocol = self.aiProtocol.rawValue
let iResult = GetAddrInfoW(wszHost, wszPort, &aiHints, &pResult)
guard iResult == 0 else {
self.fail(SocketAddressError.unknown(host: host, port: port))
return
}
if let pResult = pResult {
self.parseAndPublishResults(pResult, host: host)
FreeAddrInfoW(pResult)
} else {
self.fail(SocketAddressError.unsupported)
}
}
}
#else
var info: UnsafeMutablePointer<addrinfo>?
var hint = addrinfo()
hint.ai_socktype = self.aiSocktype.rawValue
hint.ai_protocol = self.aiProtocol.rawValue
guard getaddrinfo(host, String(port), &hint, &info) == 0 else {
self.fail(SocketAddressError.unknown(host: host, port: port))
return
}
if let info = info {
self.parseAndPublishResults(info, host: host)
freeaddrinfo(info)
} else {
/* this is odd, getaddrinfo returned NULL */
self.fail(SocketAddressError.unsupported)
}
#endif
}
/// Parses the DNS results from the `addrinfo` linked list.
///
/// - parameters:
/// - info: The pointer to the first of the `addrinfo` structures in the list.
/// - host: The hostname we resolved.
#if os(Windows)
internal typealias CAddrInfo = ADDRINFOW
#else
internal typealias CAddrInfo = addrinfo
#endif
private func parseAndPublishResults(_ info: UnsafeMutablePointer<CAddrInfo>, host: String) {
var v4Results: [SocketAddress] = []
var v6Results: [SocketAddress] = []
var info: UnsafeMutablePointer<CAddrInfo> = info
while true {
let addressBytes = UnsafeRawPointer(info.pointee.ai_addr)
switch NIOBSDSocket.AddressFamily(rawValue: info.pointee.ai_family) {
case .inet:
// Force-unwrap must be safe, or libc did the wrong thing.
v4Results.append(.init(addressBytes!.load(as: sockaddr_in.self), host: host))
case .inet6:
// Force-unwrap must be safe, or libc did the wrong thing.
v6Results.append(.init(addressBytes!.load(as: sockaddr_in6.self), host: host))
default:
self.fail(SocketAddressError.unsupported)
return
}
guard let nextInfo = info.pointee.ai_next else {
break
}
info = nextInfo
}
v6Future.succeed(v6Results)
v4Future.succeed(v4Results)
}
/// Record an error and fail the lookup process.
///
/// - parameters:
/// - error: The error encountered during lookup.
private func fail(_ error: Error) {
self.v6Future.fail(error)
self.v4Future.fail(error)
}
}
|