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