1 | /* |
2 | * IXDNSLookup.cpp |
3 | * Author: Benjamin Sergeant |
4 | * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. |
5 | */ |
6 | |
7 | // |
8 | // On Windows Universal Platform (uwp), gai_strerror defaults behavior is to returns wchar_t |
9 | // which is different from all other platforms. We want the non unicode version. |
10 | // See https://github.com/microsoft/vcpkg/pull/11030 |
11 | // We could do this in IXNetSystem.cpp but so far we are only using gai_strerror in here. |
12 | // |
13 | #ifdef _UNICODE |
14 | #undef _UNICODE |
15 | #endif |
16 | #ifdef UNICODE |
17 | #undef UNICODE |
18 | #endif |
19 | |
20 | #include "IXDNSLookup.h" |
21 | |
22 | #include "IXNetSystem.h" |
23 | #include <chrono> |
24 | #include <string.h> |
25 | #include <thread> |
26 | |
27 | // mingw build quirks |
28 | #if defined(_WIN32) && defined(__GNUC__) |
29 | #define AI_NUMERICSERV NI_NUMERICSERV |
30 | #define AI_ADDRCONFIG LUP_ADDRCONFIG |
31 | #endif |
32 | |
33 | namespace ix |
34 | { |
35 | const int64_t DNSLookup::kDefaultWait = 1; // ms |
36 | |
37 | DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) |
38 | : _hostname(hostname) |
39 | , _port(port) |
40 | , _wait(wait) |
41 | , _res(nullptr) |
42 | , _done(false) |
43 | { |
44 | ; |
45 | } |
46 | |
47 | struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname, |
48 | int port, |
49 | std::string& errMsg) |
50 | { |
51 | struct addrinfo hints; |
52 | memset(&hints, 0, sizeof(hints)); |
53 | hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; |
54 | hints.ai_family = AF_UNSPEC; |
55 | hints.ai_socktype = SOCK_STREAM; |
56 | |
57 | std::string sport = std::to_string(port); |
58 | |
59 | struct addrinfo* res; |
60 | int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res); |
61 | if (getaddrinfo_result) |
62 | { |
63 | errMsg = gai_strerror(getaddrinfo_result); |
64 | res = nullptr; |
65 | } |
66 | return res; |
67 | } |
68 | |
69 | struct addrinfo* DNSLookup::resolve(std::string& errMsg, |
70 | const CancellationRequest& isCancellationRequested, |
71 | bool cancellable) |
72 | { |
73 | return cancellable ? resolveCancellable(errMsg, isCancellationRequested) |
74 | : resolveUnCancellable(errMsg, isCancellationRequested); |
75 | } |
76 | |
77 | void DNSLookup::release(struct addrinfo* addr) |
78 | { |
79 | freeaddrinfo(addr); |
80 | } |
81 | |
82 | struct addrinfo* DNSLookup::resolveUnCancellable( |
83 | std::string& errMsg, const CancellationRequest& isCancellationRequested) |
84 | { |
85 | errMsg = "no error" ; |
86 | |
87 | // Maybe a cancellation request got in before the background thread terminated ? |
88 | if (isCancellationRequested()) |
89 | { |
90 | errMsg = "cancellation requested" ; |
91 | return nullptr; |
92 | } |
93 | |
94 | return getAddrInfo(_hostname, _port, errMsg); |
95 | } |
96 | |
97 | struct addrinfo* DNSLookup::resolveCancellable( |
98 | std::string& errMsg, const CancellationRequest& isCancellationRequested) |
99 | { |
100 | errMsg = "no error" ; |
101 | |
102 | // Can only be called once, otherwise we would have to manage a pool |
103 | // of background thread which is overkill for our usage. |
104 | if (_done) |
105 | { |
106 | return nullptr; // programming error, create a second DNSLookup instance |
107 | // if you need a second lookup. |
108 | } |
109 | |
110 | // |
111 | // Good resource on thread forced termination |
112 | // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread |
113 | // |
114 | auto ptr = shared_from_this(); |
115 | std::weak_ptr<DNSLookup> self(ptr); |
116 | |
117 | int port = _port; |
118 | std::string hostname(_hostname); |
119 | |
120 | // We make the background thread doing the work a shared pointer |
121 | // instead of a member variable, because it can keep running when |
122 | // this object goes out of scope, in case of cancellation |
123 | auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port); |
124 | t->detach(); |
125 | |
126 | while (!_done) |
127 | { |
128 | // Wait for 1 milliseconds, to see if the bg thread has terminated. |
129 | // We do not use a condition variable to wait, as destroying this one |
130 | // if the bg thread is alive can cause undefined behavior. |
131 | std::this_thread::sleep_for(std::chrono::milliseconds(_wait)); |
132 | |
133 | // Were we cancelled ? |
134 | if (isCancellationRequested()) |
135 | { |
136 | errMsg = "cancellation requested" ; |
137 | return nullptr; |
138 | } |
139 | } |
140 | |
141 | // Maybe a cancellation request got in before the bg terminated ? |
142 | if (isCancellationRequested()) |
143 | { |
144 | errMsg = "cancellation requested" ; |
145 | return nullptr; |
146 | } |
147 | |
148 | errMsg = getErrMsg(); |
149 | return getRes(); |
150 | } |
151 | |
152 | void DNSLookup::run(std::weak_ptr<DNSLookup> self, |
153 | std::string hostname, |
154 | int port) // thread runner |
155 | { |
156 | // We don't want to read or write into members variables of an object that could be |
157 | // gone, so we use temporary variables (res) or we pass in by copy everything that |
158 | // getAddrInfo needs to work. |
159 | std::string errMsg; |
160 | struct addrinfo* res = getAddrInfo(hostname, port, errMsg); |
161 | |
162 | if (auto lock = self.lock()) |
163 | { |
164 | // Copy result into the member variables |
165 | setRes(res); |
166 | setErrMsg(errMsg); |
167 | |
168 | _done = true; |
169 | } |
170 | } |
171 | |
172 | void DNSLookup::setErrMsg(const std::string& errMsg) |
173 | { |
174 | std::lock_guard<std::mutex> lock(_errMsgMutex); |
175 | _errMsg = errMsg; |
176 | } |
177 | |
178 | const std::string& DNSLookup::getErrMsg() |
179 | { |
180 | std::lock_guard<std::mutex> lock(_errMsgMutex); |
181 | return _errMsg; |
182 | } |
183 | |
184 | void DNSLookup::setRes(struct addrinfo* addr) |
185 | { |
186 | std::lock_guard<std::mutex> lock(_resMutex); |
187 | _res = addr; |
188 | } |
189 | |
190 | struct addrinfo* DNSLookup::getRes() |
191 | { |
192 | std::lock_guard<std::mutex> lock(_resMutex); |
193 | return _res; |
194 | } |
195 | } // namespace ix |
196 | |