/src/mozilla-central/netwerk/dns/GetAddrInfo.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "GetAddrInfo.h" |
8 | | #include "mozilla/net/DNS.h" |
9 | | #include "prnetdb.h" |
10 | | #include "nsHostResolver.h" |
11 | | #include "nsError.h" |
12 | | #include "mozilla/Mutex.h" |
13 | | #include "nsAutoPtr.h" |
14 | | #include "mozilla/StaticPtr.h" |
15 | | #include "MainThreadUtils.h" |
16 | | #include "mozilla/DebugOnly.h" |
17 | | #include "mozilla/net/DNS.h" |
18 | | #include <algorithm> |
19 | | #include "prerror.h" |
20 | | |
21 | | #include "mozilla/Logging.h" |
22 | | |
23 | | #ifdef DNSQUERY_AVAILABLE |
24 | | // There is a bug in windns.h where the type of parameter ppQueryResultsSet for |
25 | | // DnsQuery_A is dependent on UNICODE being set. It should *always* be |
26 | | // PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this |
27 | | // we make sure that UNICODE is unset. |
28 | | #undef UNICODE |
29 | | #include <ws2tcpip.h> |
30 | | #undef GetAddrInfo |
31 | | #include <windns.h> |
32 | | #endif |
33 | | |
34 | | namespace mozilla { |
35 | | namespace net { |
36 | | |
37 | | static LazyLogModule gGetAddrInfoLog("GetAddrInfo"); |
38 | | #define LOG(msg, ...) \ |
39 | 3 | MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__)) |
40 | | #define LOG_WARNING(msg, ...) \ |
41 | | MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__)) |
42 | | |
43 | | #ifdef DNSQUERY_AVAILABLE |
44 | | //////////////////////////// |
45 | | // WINDOWS IMPLEMENTATION // |
46 | | //////////////////////////// |
47 | | |
48 | | // Ensure consistency of PR_* and AF_* constants to allow for legacy usage of |
49 | | // PR_* constants with this API. |
50 | | static_assert(PR_AF_INET == AF_INET && PR_AF_INET6 == AF_INET6 |
51 | | && PR_AF_UNSPEC == AF_UNSPEC, "PR_AF_* must match AF_*"); |
52 | | |
53 | | // We intentionally leak this mutex. This is because we can run into a |
54 | | // situation where the worker threads are still running until the process |
55 | | // is actually fully shut down, and at any time one of those worker |
56 | | // threads can access gDnsapiInfoLock. |
57 | | static OffTheBooksMutex* gDnsapiInfoLock = nullptr; |
58 | | |
59 | | struct DnsapiInfo |
60 | | { |
61 | | public: |
62 | | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DnsapiInfo); |
63 | | |
64 | | HMODULE mLibrary; |
65 | | decltype(&DnsQuery_A) mDnsQueryFunc; |
66 | | decltype(&DnsFree) mDnsFreeFunc; |
67 | | |
68 | | private: |
69 | | // This will either be called during shutdown of the GetAddrInfo module, or |
70 | | // when a worker thread is done doing a lookup (ie: within |
71 | | // _GetAddrInfo_Windows). Note that the lock must be held when this is |
72 | | // called. |
73 | | ~DnsapiInfo() |
74 | | { |
75 | | if (gDnsapiInfoLock) { |
76 | | gDnsapiInfoLock->AssertCurrentThreadOwns(); |
77 | | } else { |
78 | | MOZ_ASSERT_UNREACHABLE("No mutex available during GetAddrInfo " |
79 | | "shutdown."); |
80 | | return; |
81 | | } |
82 | | |
83 | | LOG("Freeing Dnsapi.dll"); |
84 | | MOZ_ASSERT(mLibrary); |
85 | | DebugOnly<BOOL> rv = FreeLibrary(mLibrary); |
86 | | NS_WARNING_ASSERTION(rv, "Failed to free Dnsapi.dll."); |
87 | | } |
88 | | }; |
89 | | |
90 | | static StaticRefPtr<DnsapiInfo> gDnsapiInfo; |
91 | | |
92 | | static MOZ_ALWAYS_INLINE nsresult |
93 | | _GetAddrInfoInit_Windows() |
94 | | { |
95 | | // This is necessary to ensure strict thread safety because if two threads |
96 | | // run this function at the same time they can potentially create two |
97 | | // mutexes. |
98 | | MOZ_ASSERT(NS_IsMainThread(), |
99 | | "Do not initialize GetAddrInfo off main thread!"); |
100 | | |
101 | | if (!gDnsapiInfoLock) { |
102 | | gDnsapiInfoLock = new OffTheBooksMutex("GetAddrInfo.cpp::gDnsapiInfoLock"); |
103 | | } |
104 | | OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); |
105 | | |
106 | | if (gDnsapiInfo) { |
107 | | MOZ_ASSERT_UNREACHABLE("GetAddrInfo is being initialized multiple times!"); |
108 | | return NS_ERROR_ALREADY_INITIALIZED; |
109 | | } |
110 | | |
111 | | HMODULE library = LoadLibraryA("Dnsapi.dll"); |
112 | | if (NS_WARN_IF(!library)) { |
113 | | return NS_ERROR_FAILURE; |
114 | | } |
115 | | |
116 | | FARPROC dnsQueryFunc = GetProcAddress(library, "DnsQuery_A"); |
117 | | FARPROC dnsFreeFunc = GetProcAddress(library, "DnsFree"); |
118 | | if (NS_WARN_IF(!dnsQueryFunc) || NS_WARN_IF(!dnsFreeFunc)) { |
119 | | DebugOnly<BOOL> rv = FreeLibrary(library); |
120 | | NS_WARNING_ASSERTION(rv, "Failed to free Dnsapi.dll."); |
121 | | return NS_ERROR_FAILURE; |
122 | | } |
123 | | |
124 | | DnsapiInfo* info = new DnsapiInfo; |
125 | | info->mLibrary = library; |
126 | | info->mDnsQueryFunc = (decltype(info->mDnsQueryFunc)) dnsQueryFunc; |
127 | | info->mDnsFreeFunc = (decltype(info->mDnsFreeFunc)) dnsFreeFunc; |
128 | | gDnsapiInfo = info; |
129 | | |
130 | | return NS_OK; |
131 | | } |
132 | | |
133 | | static MOZ_ALWAYS_INLINE nsresult |
134 | | _GetAddrInfoShutdown_Windows() |
135 | | { |
136 | | OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); |
137 | | |
138 | | if (NS_WARN_IF(!gDnsapiInfo) || NS_WARN_IF(!gDnsapiInfoLock)) { |
139 | | MOZ_ASSERT_UNREACHABLE("GetAddrInfo not initialized!"); |
140 | | return NS_ERROR_NOT_INITIALIZED; |
141 | | } |
142 | | |
143 | | gDnsapiInfo = nullptr; |
144 | | |
145 | | return NS_OK; |
146 | | } |
147 | | |
148 | | // If successful, returns in aResult a TTL value that is smaller or |
149 | | // equal with the one already there. Gets the TTL value by calling |
150 | | // to dnsapi->mDnsQueryFunc and iterating through the returned |
151 | | // records to find the one with the smallest TTL value. |
152 | | static MOZ_ALWAYS_INLINE nsresult |
153 | | _GetMinTTLForRequestType_Windows(DnsapiInfo * dnsapi, const char* aHost, |
154 | | uint16_t aRequestType, unsigned int* aResult) |
155 | | { |
156 | | MOZ_ASSERT(dnsapi); |
157 | | MOZ_ASSERT(aHost); |
158 | | MOZ_ASSERT(aResult); |
159 | | |
160 | | PDNS_RECORDA dnsData = nullptr; |
161 | | DNS_STATUS status = dnsapi->mDnsQueryFunc( |
162 | | aHost, |
163 | | aRequestType, |
164 | | (DNS_QUERY_STANDARD | DNS_QUERY_NO_NETBT | DNS_QUERY_NO_HOSTS_FILE |
165 | | | DNS_QUERY_NO_MULTICAST | DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE |
166 | | | DNS_QUERY_DONT_RESET_TTL_VALUES), |
167 | | nullptr, |
168 | | &dnsData, |
169 | | nullptr); |
170 | | if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR |
171 | | || !dnsData) { |
172 | | LOG("No DNS records found for %s. status=%X. aRequestType = %X\n", |
173 | | aHost, status, aRequestType); |
174 | | return NS_ERROR_FAILURE; |
175 | | } else if (status != NOERROR) { |
176 | | LOG_WARNING("DnsQuery_A failed with status %X.\n", status); |
177 | | return NS_ERROR_UNEXPECTED; |
178 | | } |
179 | | |
180 | | for (PDNS_RECORDA curRecord = dnsData; curRecord; curRecord = curRecord->pNext) { |
181 | | // Only records in the answer section are important |
182 | | if (curRecord->Flags.S.Section != DnsSectionAnswer) { |
183 | | continue; |
184 | | } |
185 | | |
186 | | if (curRecord->wType == aRequestType) { |
187 | | *aResult = std::min<unsigned int>(*aResult, curRecord->dwTtl); |
188 | | } else { |
189 | | LOG("Received unexpected record type %u in response for %s.\n", |
190 | | curRecord->wType, aHost); |
191 | | } |
192 | | } |
193 | | |
194 | | dnsapi->mDnsFreeFunc(dnsData, DNS_FREE_TYPE::DnsFreeRecordList); |
195 | | return NS_OK; |
196 | | } |
197 | | |
198 | | static MOZ_ALWAYS_INLINE nsresult |
199 | | _GetTTLData_Windows(const nsACString& aHost, uint32_t* aResult, uint16_t aAddressFamily) |
200 | | { |
201 | | MOZ_ASSERT(!aHost.IsEmpty()); |
202 | | MOZ_ASSERT(aResult); |
203 | | if (aAddressFamily != PR_AF_UNSPEC && |
204 | | aAddressFamily != PR_AF_INET && |
205 | | aAddressFamily != PR_AF_INET6) { |
206 | | return NS_ERROR_UNEXPECTED; |
207 | | } |
208 | | |
209 | | RefPtr<DnsapiInfo> dnsapi = nullptr; |
210 | | { |
211 | | OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); |
212 | | dnsapi = gDnsapiInfo; |
213 | | } |
214 | | |
215 | | if (!dnsapi) { |
216 | | LOG_WARNING("GetAddrInfo has been shutdown or has not been initialized."); |
217 | | return NS_ERROR_NOT_INITIALIZED; |
218 | | } |
219 | | |
220 | | // In order to avoid using ANY records which are not always implemented as a |
221 | | // "Gimme what you have" request in hostname resolvers, we should send A |
222 | | // and/or AAAA requests, based on the address family requested. |
223 | | unsigned int ttl = (unsigned int)-1; |
224 | | if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET) { |
225 | | _GetMinTTLForRequestType_Windows(dnsapi, aHost.BeginReading(), DNS_TYPE_A, &ttl); |
226 | | } |
227 | | if (aAddressFamily == PR_AF_UNSPEC || aAddressFamily == PR_AF_INET6) { |
228 | | _GetMinTTLForRequestType_Windows(dnsapi, aHost.BeginReading(), DNS_TYPE_AAAA, &ttl); |
229 | | } |
230 | | |
231 | | { |
232 | | // dnsapi's destructor is not thread-safe, so we release explicitly here |
233 | | OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); |
234 | | dnsapi = nullptr; |
235 | | } |
236 | | |
237 | | if (ttl == (unsigned int)-1) { |
238 | | LOG("No useable TTL found."); |
239 | | return NS_ERROR_FAILURE; |
240 | | } |
241 | | |
242 | | *aResult = ttl; |
243 | | return NS_OK; |
244 | | } |
245 | | #endif |
246 | | |
247 | | //////////////////////////////////// |
248 | | // PORTABLE RUNTIME IMPLEMENTATION// |
249 | | //////////////////////////////////// |
250 | | |
251 | | static MOZ_ALWAYS_INLINE nsresult |
252 | | _GetAddrInfo_Portable(const nsACString& aCanonHost, uint16_t aAddressFamily, |
253 | | uint16_t aFlags, AddrInfo** aAddrInfo) |
254 | 0 | { |
255 | 0 | MOZ_ASSERT(!aCanonHost.IsEmpty()); |
256 | 0 | MOZ_ASSERT(aAddrInfo); |
257 | 0 |
|
258 | 0 | // We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we |
259 | 0 | // need to translate the aFlags into a form that PR_GetAddrInfoByName |
260 | 0 | // accepts. |
261 | 0 | int prFlags = PR_AI_ADDRCONFIG; |
262 | 0 | if (!(aFlags & nsHostResolver::RES_CANON_NAME)) { |
263 | 0 | prFlags |= PR_AI_NOCANONNAME; |
264 | 0 | } |
265 | 0 |
|
266 | 0 | // We need to remove IPv4 records manually because PR_GetAddrInfoByName |
267 | 0 | // doesn't support PR_AF_INET6. |
268 | 0 | bool disableIPv4 = aAddressFamily == PR_AF_INET6; |
269 | 0 | if (disableIPv4) { |
270 | 0 | aAddressFamily = PR_AF_UNSPEC; |
271 | 0 | } |
272 | 0 |
|
273 | 0 | PRAddrInfo* prai = PR_GetAddrInfoByName(aCanonHost.BeginReading(), aAddressFamily, prFlags); |
274 | 0 |
|
275 | 0 | if (!prai) { |
276 | 0 | return NS_ERROR_UNKNOWN_HOST; |
277 | 0 | } |
278 | 0 | |
279 | 0 | nsAutoCString canonName; |
280 | 0 | if (aFlags & nsHostResolver::RES_CANON_NAME) { |
281 | 0 | canonName.Assign(PR_GetCanonNameFromAddrInfo(prai)); |
282 | 0 | } |
283 | 0 |
|
284 | 0 | bool filterNameCollision = !(aFlags & nsHostResolver::RES_ALLOW_NAME_COLLISION); |
285 | 0 | nsAutoPtr<AddrInfo> ai(new AddrInfo(aCanonHost, prai, disableIPv4, |
286 | 0 | filterNameCollision, canonName)); |
287 | 0 | PR_FreeAddrInfo(prai); |
288 | 0 | if (ai->mAddresses.isEmpty()) { |
289 | 0 | return NS_ERROR_UNKNOWN_HOST; |
290 | 0 | } |
291 | 0 | |
292 | 0 | *aAddrInfo = ai.forget(); |
293 | 0 |
|
294 | 0 | return NS_OK; |
295 | 0 | } |
296 | | |
297 | | ////////////////////////////////////// |
298 | | // COMMON/PLATFORM INDEPENDENT CODE // |
299 | | ////////////////////////////////////// |
300 | | nsresult |
301 | 3 | GetAddrInfoInit() { |
302 | 3 | LOG("Initializing GetAddrInfo.\n"); |
303 | 3 | |
304 | | #ifdef DNSQUERY_AVAILABLE |
305 | | return _GetAddrInfoInit_Windows(); |
306 | | #else |
307 | | return NS_OK; |
308 | 3 | #endif |
309 | 3 | } |
310 | | |
311 | | nsresult |
312 | 0 | GetAddrInfoShutdown() { |
313 | 0 | LOG("Shutting down GetAddrInfo.\n"); |
314 | 0 |
|
315 | | #ifdef DNSQUERY_AVAILABLE |
316 | | return _GetAddrInfoShutdown_Windows(); |
317 | | #else |
318 | | return NS_OK; |
319 | 0 | #endif |
320 | 0 | } |
321 | | |
322 | | nsresult |
323 | | GetAddrInfo(const nsACString& aHost, uint16_t aAddressFamily, uint16_t aFlags, |
324 | | AddrInfo** aAddrInfo, bool aGetTtl) |
325 | 0 | { |
326 | 0 | if (NS_WARN_IF(aHost.IsEmpty()) || NS_WARN_IF(!aAddrInfo)) { |
327 | 0 | return NS_ERROR_NULL_POINTER; |
328 | 0 | } |
329 | 0 | |
330 | | #ifdef DNSQUERY_AVAILABLE |
331 | | // The GetTTLData needs the canonical name to function properly |
332 | | if (aGetTtl) { |
333 | | aFlags |= nsHostResolver::RES_CANON_NAME; |
334 | | } |
335 | | #endif |
336 | | |
337 | 0 | nsAutoCString host(aHost); |
338 | 0 | if (gNativeIsLocalhost) { |
339 | 0 | // pretend we use the given host but use IPv4 localhost instead! |
340 | 0 | host = NS_LITERAL_CSTRING("localhost"); |
341 | 0 | aAddressFamily = PR_AF_INET; |
342 | 0 | } |
343 | 0 |
|
344 | 0 | *aAddrInfo = nullptr; |
345 | 0 | nsresult rv = _GetAddrInfo_Portable(host, aAddressFamily, aFlags, |
346 | 0 | aAddrInfo); |
347 | 0 |
|
348 | | #ifdef DNSQUERY_AVAILABLE |
349 | | if (aGetTtl && NS_SUCCEEDED(rv)) { |
350 | | // Figure out the canonical name, or if that fails, just use the host name |
351 | | // we have. |
352 | | nsAutoCString name; |
353 | | if (*aAddrInfo != nullptr && !(*aAddrInfo)->mCanonicalName.IsEmpty()) { |
354 | | name = (*aAddrInfo)->mCanonicalName; |
355 | | } else { |
356 | | name = host; |
357 | | } |
358 | | |
359 | | LOG("Getting TTL for %s (cname = %s).", host.get(), name.get()); |
360 | | uint32_t ttl = 0; |
361 | | nsresult ttlRv = _GetTTLData_Windows(name, &ttl, aAddressFamily); |
362 | | if (NS_SUCCEEDED(ttlRv)) { |
363 | | (*aAddrInfo)->ttl = ttl; |
364 | | LOG("Got TTL %u for %s (name = %s).", ttl, host.get(), name.get()); |
365 | | } else { |
366 | | LOG_WARNING("Could not get TTL for %s (cname = %s).", host.get(), name.get()); |
367 | | } |
368 | | } |
369 | | #endif |
370 | |
|
371 | 0 | return rv; |
372 | 0 | } |
373 | | |
374 | | } // namespace net |
375 | | } // namespace mozilla |