File: GetaddrinfoResolver.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (199 lines) | stat: -rw-r--r-- 7,422 bytes parent folder | download
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)
    }
}