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
33namespace 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