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
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 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
//
//===----------------------------------------------------------------------===//
/// 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 an event loop thread for as long as it takes to perform the getaddrinfo call. However, it
/// does have the advantage of automatically conforming 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.
#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
internal class GetaddrinfoResolver: Resolver {
private let v4Future: EventLoopPromise<[SocketAddress]>
private let v6Future: EventLoopPromise<[SocketAddress]>
private let aiSocktype: NIOBSDSocket.SocketType
private let aiProtocol: CInt
/// 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: CInt) {
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]> {
resolve(host: host, port: port)
return v6Future.futureResult
}
/// 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 resolve(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
let iResult = GetAddrInfoW(wszHost, wszPort, &aiHints, &pResult)
guard iResult == 0 else {
self.fail(SocketAddressError.unknown(host: host, port: port))
return
}
if let pResult = pResult {
parseResults(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
guard getaddrinfo(host, String(port), &hint, &info) == 0 else {
self.fail(SocketAddressError.unknown(host: host, port: port))
return
}
if let info = info {
parseResults(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 parseResults(_ info: UnsafeMutablePointer<CAddrInfo>, host: String) {
var v4Results: [SocketAddress] = []
var v6Results: [SocketAddress] = []
var info: UnsafeMutablePointer<CAddrInfo> = info
while true {
switch NIOBSDSocket.AddressFamily(rawValue: info.pointee.ai_family) {
case .inet:
info.pointee.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { ptr in
v4Results.append(.init(ptr.pointee, host: host))
}
case .inet6:
info.pointee.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { ptr in
v6Results.append(.init(ptr.pointee, 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)
}
}
|