|
1 /* vim:set ts=4 sw=4 sts=4 et cin: */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #if defined(MOZ_LOGGING) |
|
7 #define FORCE_PR_LOG |
|
8 #endif |
|
9 |
|
10 #if defined(HAVE_RES_NINIT) |
|
11 #include <sys/types.h> |
|
12 #include <netinet/in.h> |
|
13 #include <arpa/inet.h> |
|
14 #include <arpa/nameser.h> |
|
15 #include <resolv.h> |
|
16 #define RES_RETRY_ON_FAILURE |
|
17 #endif |
|
18 |
|
19 #include <stdlib.h> |
|
20 #include "nsHostResolver.h" |
|
21 #include "nsError.h" |
|
22 #include "nsISupportsBase.h" |
|
23 #include "nsISupportsUtils.h" |
|
24 #include "nsAutoPtr.h" |
|
25 #include "prthread.h" |
|
26 #include "prerror.h" |
|
27 #include "prtime.h" |
|
28 #include "prlog.h" |
|
29 #include "pldhash.h" |
|
30 #include "plstr.h" |
|
31 #include "nsURLHelper.h" |
|
32 #include "nsThreadUtils.h" |
|
33 |
|
34 #include "mozilla/HashFunctions.h" |
|
35 #include "mozilla/TimeStamp.h" |
|
36 #include "mozilla/Telemetry.h" |
|
37 #include "mozilla/VisualEventTracer.h" |
|
38 |
|
39 using namespace mozilla; |
|
40 using namespace mozilla::net; |
|
41 |
|
42 //---------------------------------------------------------------------------- |
|
43 |
|
44 // Use a persistent thread pool in order to avoid spinning up new threads all the time. |
|
45 // In particular, thread creation results in a res_init() call from libc which is |
|
46 // quite expensive. |
|
47 // |
|
48 // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests |
|
49 // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS |
|
50 // currently in the pool a new thread is created for high priority requests. If |
|
51 // the new request is at a lower priority a new thread will only be created if |
|
52 // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be |
|
53 // created or an idle thread located for the request it is queued. |
|
54 // |
|
55 // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after |
|
56 // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a |
|
57 // timeout period. |
|
58 |
|
59 #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY |
|
60 #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold |
|
61 #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS |
|
62 |
|
63 PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS); |
|
64 |
|
65 //---------------------------------------------------------------------------- |
|
66 |
|
67 #if defined(PR_LOGGING) |
|
68 static PRLogModuleInfo *gHostResolverLog = nullptr; |
|
69 #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args) |
|
70 #else |
|
71 #define LOG(args) |
|
72 #endif |
|
73 |
|
74 //---------------------------------------------------------------------------- |
|
75 |
|
76 static inline void |
|
77 MoveCList(PRCList &from, PRCList &to) |
|
78 { |
|
79 if (!PR_CLIST_IS_EMPTY(&from)) { |
|
80 to.next = from.next; |
|
81 to.prev = from.prev; |
|
82 to.next->prev = &to; |
|
83 to.prev->next = &to; |
|
84 PR_INIT_CLIST(&from); |
|
85 } |
|
86 } |
|
87 |
|
88 //---------------------------------------------------------------------------- |
|
89 |
|
90 #if defined(RES_RETRY_ON_FAILURE) |
|
91 |
|
92 // this class represents the resolver state for a given thread. if we |
|
93 // encounter a lookup failure, then we can invoke the Reset method on an |
|
94 // instance of this class to reset the resolver (in case /etc/resolv.conf |
|
95 // for example changed). this is mainly an issue on GNU systems since glibc |
|
96 // only reads in /etc/resolv.conf once per thread. it may be an issue on |
|
97 // other systems as well. |
|
98 |
|
99 class nsResState |
|
100 { |
|
101 public: |
|
102 nsResState() |
|
103 // initialize mLastReset to the time when this object |
|
104 // is created. this means that a reset will not occur |
|
105 // if a thread is too young. the alternative would be |
|
106 // to initialize this to the beginning of time, so that |
|
107 // the first failure would cause a reset, but since the |
|
108 // thread would have just started up, it likely would |
|
109 // already have current /etc/resolv.conf info. |
|
110 : mLastReset(PR_IntervalNow()) |
|
111 { |
|
112 } |
|
113 |
|
114 bool Reset() |
|
115 { |
|
116 // reset no more than once per second |
|
117 if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1) |
|
118 return false; |
|
119 |
|
120 LOG(("Calling 'res_ninit'.\n")); |
|
121 |
|
122 mLastReset = PR_IntervalNow(); |
|
123 return (res_ninit(&_res) == 0); |
|
124 } |
|
125 |
|
126 private: |
|
127 PRIntervalTime mLastReset; |
|
128 }; |
|
129 |
|
130 #endif // RES_RETRY_ON_FAILURE |
|
131 |
|
132 //---------------------------------------------------------------------------- |
|
133 |
|
134 static inline bool |
|
135 IsHighPriority(uint16_t flags) |
|
136 { |
|
137 return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM)); |
|
138 } |
|
139 |
|
140 static inline bool |
|
141 IsMediumPriority(uint16_t flags) |
|
142 { |
|
143 return flags & nsHostResolver::RES_PRIORITY_MEDIUM; |
|
144 } |
|
145 |
|
146 static inline bool |
|
147 IsLowPriority(uint16_t flags) |
|
148 { |
|
149 return flags & nsHostResolver::RES_PRIORITY_LOW; |
|
150 } |
|
151 |
|
152 //---------------------------------------------------------------------------- |
|
153 |
|
154 // this macro filters out any flags that are not used when constructing the |
|
155 // host key. the significant flags are those that would affect the resulting |
|
156 // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName). |
|
157 #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME) |
|
158 |
|
159 nsHostRecord::nsHostRecord(const nsHostKey *key) |
|
160 : addr_info_lock("nsHostRecord.addr_info_lock") |
|
161 , addr_info_gencnt(0) |
|
162 , addr_info(nullptr) |
|
163 , addr(nullptr) |
|
164 , negative(false) |
|
165 , resolving(false) |
|
166 , onQueue(false) |
|
167 , usingAnyThread(false) |
|
168 , mDoomed(false) |
|
169 { |
|
170 host = ((char *) this) + sizeof(nsHostRecord); |
|
171 memcpy((char *) host, key->host, strlen(key->host) + 1); |
|
172 flags = key->flags; |
|
173 af = key->af; |
|
174 |
|
175 expiration = TimeStamp::NowLoRes(); |
|
176 |
|
177 PR_INIT_CLIST(this); |
|
178 PR_INIT_CLIST(&callbacks); |
|
179 } |
|
180 |
|
181 nsresult |
|
182 nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result) |
|
183 { |
|
184 size_t hostLen = strlen(key->host) + 1; |
|
185 size_t size = hostLen + sizeof(nsHostRecord); |
|
186 |
|
187 // Use placement new to create the object with room for the hostname |
|
188 // allocated after it. |
|
189 void *place = ::operator new(size); |
|
190 *result = new(place) nsHostRecord(key); |
|
191 NS_ADDREF(*result); |
|
192 |
|
193 MOZ_EVENT_TRACER_NAME_OBJECT(*result, key->host); |
|
194 |
|
195 return NS_OK; |
|
196 } |
|
197 |
|
198 nsHostRecord::~nsHostRecord() |
|
199 { |
|
200 delete addr_info; |
|
201 delete addr; |
|
202 } |
|
203 |
|
204 bool |
|
205 nsHostRecord::Blacklisted(NetAddr *aQuery) |
|
206 { |
|
207 // must call locked |
|
208 LOG(("Checking blacklist for host [%s], host record [%p].\n", host, this)); |
|
209 |
|
210 // skip the string conversion for the common case of no blacklist |
|
211 if (!mBlacklistedItems.Length()) { |
|
212 return false; |
|
213 } |
|
214 |
|
215 char buf[kIPv6CStrBufSize]; |
|
216 if (!NetAddrToString(aQuery, buf, sizeof(buf))) { |
|
217 return false; |
|
218 } |
|
219 nsDependentCString strQuery(buf); |
|
220 |
|
221 for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) { |
|
222 if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) { |
|
223 LOG(("Address [%s] is blacklisted for host [%s].\n", buf, host)); |
|
224 return true; |
|
225 } |
|
226 } |
|
227 |
|
228 return false; |
|
229 } |
|
230 |
|
231 void |
|
232 nsHostRecord::ReportUnusable(NetAddr *aAddress) |
|
233 { |
|
234 // must call locked |
|
235 LOG(("Adding address to blacklist for host [%s], host record [%p].\n", host, this)); |
|
236 |
|
237 if (negative) |
|
238 mDoomed = true; |
|
239 |
|
240 char buf[kIPv6CStrBufSize]; |
|
241 if (NetAddrToString(aAddress, buf, sizeof(buf))) { |
|
242 LOG(("Successfully adding address [%s] to blacklist for host [%s].\n", buf, host)); |
|
243 mBlacklistedItems.AppendElement(nsCString(buf)); |
|
244 } |
|
245 } |
|
246 |
|
247 void |
|
248 nsHostRecord::ResetBlacklist() |
|
249 { |
|
250 // must call locked |
|
251 LOG(("Resetting blacklist for host [%s], host record [%p].\n", host, this)); |
|
252 mBlacklistedItems.Clear(); |
|
253 } |
|
254 |
|
255 bool |
|
256 nsHostRecord::HasUsableResult(uint16_t queryFlags) const |
|
257 { |
|
258 if (mDoomed) |
|
259 return false; |
|
260 |
|
261 // don't use cached negative results for high priority queries. |
|
262 if (negative && IsHighPriority(queryFlags)) |
|
263 return false; |
|
264 |
|
265 return addr_info || addr || negative; |
|
266 } |
|
267 |
|
268 static size_t |
|
269 SizeOfResolveHostCallbackListExcludingHead(const PRCList *head, |
|
270 MallocSizeOf mallocSizeOf) |
|
271 { |
|
272 size_t n = 0; |
|
273 PRCList *curr = head->next; |
|
274 while (curr != head) { |
|
275 nsResolveHostCallback *callback = |
|
276 static_cast<nsResolveHostCallback*>(curr); |
|
277 n += callback->SizeOfIncludingThis(mallocSizeOf); |
|
278 curr = curr->next; |
|
279 } |
|
280 return n; |
|
281 } |
|
282 |
|
283 size_t |
|
284 nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const |
|
285 { |
|
286 size_t n = mallocSizeOf(this); |
|
287 |
|
288 // The |host| field (inherited from nsHostKey) actually points to extra |
|
289 // memory that is allocated beyond the end of the nsHostRecord (see |
|
290 // nsHostRecord::Create()). So it will be included in the |
|
291 // |mallocSizeOf(this)| call above. |
|
292 |
|
293 n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf); |
|
294 n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0; |
|
295 n += mallocSizeOf(addr); |
|
296 |
|
297 n += mBlacklistedItems.SizeOfExcludingThis(mallocSizeOf); |
|
298 for (size_t i = 0; i < mBlacklistedItems.Length(); i++) { |
|
299 n += mBlacklistedItems[i].SizeOfExcludingThisMustBeUnshared(mallocSizeOf); |
|
300 } |
|
301 return n; |
|
302 } |
|
303 |
|
304 //---------------------------------------------------------------------------- |
|
305 |
|
306 struct nsHostDBEnt : PLDHashEntryHdr |
|
307 { |
|
308 nsHostRecord *rec; |
|
309 }; |
|
310 |
|
311 static PLDHashNumber |
|
312 HostDB_HashKey(PLDHashTable *table, const void *key) |
|
313 { |
|
314 const nsHostKey *hk = static_cast<const nsHostKey *>(key); |
|
315 return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af); |
|
316 } |
|
317 |
|
318 static bool |
|
319 HostDB_MatchEntry(PLDHashTable *table, |
|
320 const PLDHashEntryHdr *entry, |
|
321 const void *key) |
|
322 { |
|
323 const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry); |
|
324 const nsHostKey *hk = static_cast<const nsHostKey *>(key); |
|
325 |
|
326 return !strcmp(he->rec->host, hk->host) && |
|
327 RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) && |
|
328 he->rec->af == hk->af; |
|
329 } |
|
330 |
|
331 static void |
|
332 HostDB_MoveEntry(PLDHashTable *table, |
|
333 const PLDHashEntryHdr *from, |
|
334 PLDHashEntryHdr *to) |
|
335 { |
|
336 static_cast<nsHostDBEnt *>(to)->rec = |
|
337 static_cast<const nsHostDBEnt *>(from)->rec; |
|
338 } |
|
339 |
|
340 static void |
|
341 HostDB_ClearEntry(PLDHashTable *table, |
|
342 PLDHashEntryHdr *entry) |
|
343 { |
|
344 nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry); |
|
345 MOZ_ASSERT(he, "nsHostDBEnt is null!"); |
|
346 |
|
347 nsHostRecord *hr = he->rec; |
|
348 MOZ_ASSERT(hr, "nsHostDBEnt has null host record!"); |
|
349 |
|
350 LOG(("Clearing cache db entry for host [%s].\n", hr->host)); |
|
351 #if defined(DEBUG) && defined(PR_LOGGING) |
|
352 { |
|
353 MutexAutoLock lock(hr->addr_info_lock); |
|
354 if (!hr->addr_info) { |
|
355 LOG(("No address info for host [%s].\n", hr->host)); |
|
356 } else { |
|
357 TimeDuration diff = hr->expiration - TimeStamp::NowLoRes(); |
|
358 LOG(("Record for [%s] expires in %f seconds.\n", hr->host, diff.ToSeconds())); |
|
359 |
|
360 NetAddrElement *addrElement = nullptr; |
|
361 char buf[kIPv6CStrBufSize]; |
|
362 do { |
|
363 if (!addrElement) { |
|
364 addrElement = hr->addr_info->mAddresses.getFirst(); |
|
365 } else { |
|
366 addrElement = addrElement->getNext(); |
|
367 } |
|
368 |
|
369 if (addrElement) { |
|
370 NetAddrToString(&addrElement->mAddress, buf, sizeof(buf)); |
|
371 LOG((" [%s]\n", buf)); |
|
372 } |
|
373 } |
|
374 while (addrElement); |
|
375 } |
|
376 } |
|
377 #endif |
|
378 NS_RELEASE(he->rec); |
|
379 } |
|
380 |
|
381 static bool |
|
382 HostDB_InitEntry(PLDHashTable *table, |
|
383 PLDHashEntryHdr *entry, |
|
384 const void *key) |
|
385 { |
|
386 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry); |
|
387 nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec); |
|
388 return true; |
|
389 } |
|
390 |
|
391 static const PLDHashTableOps gHostDB_ops = |
|
392 { |
|
393 PL_DHashAllocTable, |
|
394 PL_DHashFreeTable, |
|
395 HostDB_HashKey, |
|
396 HostDB_MatchEntry, |
|
397 HostDB_MoveEntry, |
|
398 HostDB_ClearEntry, |
|
399 PL_DHashFinalizeStub, |
|
400 HostDB_InitEntry, |
|
401 }; |
|
402 |
|
403 static PLDHashOperator |
|
404 HostDB_RemoveEntry(PLDHashTable *table, |
|
405 PLDHashEntryHdr *hdr, |
|
406 uint32_t number, |
|
407 void *arg) |
|
408 { |
|
409 return PL_DHASH_REMOVE; |
|
410 } |
|
411 |
|
412 //---------------------------------------------------------------------------- |
|
413 |
|
414 nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, |
|
415 uint32_t maxCacheLifetime, |
|
416 uint32_t lifetimeGracePeriod) |
|
417 : mMaxCacheEntries(maxCacheEntries) |
|
418 , mMaxCacheLifetime(TimeDuration::FromSeconds(maxCacheLifetime)) |
|
419 , mGracePeriod(TimeDuration::FromSeconds(lifetimeGracePeriod)) |
|
420 , mLock("nsHostResolver.mLock") |
|
421 , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV") |
|
422 , mNumIdleThreads(0) |
|
423 , mThreadCount(0) |
|
424 , mActiveAnyThreadCount(0) |
|
425 , mEvictionQSize(0) |
|
426 , mPendingCount(0) |
|
427 , mShutdown(true) |
|
428 { |
|
429 mCreationTime = PR_Now(); |
|
430 PR_INIT_CLIST(&mHighQ); |
|
431 PR_INIT_CLIST(&mMediumQ); |
|
432 PR_INIT_CLIST(&mLowQ); |
|
433 PR_INIT_CLIST(&mEvictionQ); |
|
434 |
|
435 mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds); |
|
436 mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds); |
|
437 } |
|
438 |
|
439 nsHostResolver::~nsHostResolver() |
|
440 { |
|
441 PL_DHashTableFinish(&mDB); |
|
442 } |
|
443 |
|
444 nsresult |
|
445 nsHostResolver::Init() |
|
446 { |
|
447 PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0); |
|
448 |
|
449 mShutdown = false; |
|
450 |
|
451 #if defined(HAVE_RES_NINIT) |
|
452 // We want to make sure the system is using the correct resolver settings, |
|
453 // so we force it to reload those settings whenever we startup a subsequent |
|
454 // nsHostResolver instance. We assume that there is no reason to do this |
|
455 // for the first nsHostResolver instance since that is usually created |
|
456 // during application startup. |
|
457 static int initCount = 0; |
|
458 if (initCount++ > 0) { |
|
459 LOG(("Calling 'res_ninit'.\n")); |
|
460 res_ninit(&_res); |
|
461 } |
|
462 #endif |
|
463 return NS_OK; |
|
464 } |
|
465 |
|
466 void |
|
467 nsHostResolver::ClearPendingQueue(PRCList *aPendingQ) |
|
468 { |
|
469 // loop through pending queue, erroring out pending lookups. |
|
470 if (!PR_CLIST_IS_EMPTY(aPendingQ)) { |
|
471 PRCList *node = aPendingQ->next; |
|
472 while (node != aPendingQ) { |
|
473 nsHostRecord *rec = static_cast<nsHostRecord *>(node); |
|
474 node = node->next; |
|
475 OnLookupComplete(rec, NS_ERROR_ABORT, nullptr); |
|
476 } |
|
477 } |
|
478 } |
|
479 |
|
480 void |
|
481 nsHostResolver::Shutdown() |
|
482 { |
|
483 LOG(("Shutting down host resolver.\n")); |
|
484 |
|
485 PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ; |
|
486 PR_INIT_CLIST(&pendingQHigh); |
|
487 PR_INIT_CLIST(&pendingQMed); |
|
488 PR_INIT_CLIST(&pendingQLow); |
|
489 PR_INIT_CLIST(&evictionQ); |
|
490 |
|
491 { |
|
492 MutexAutoLock lock(mLock); |
|
493 |
|
494 mShutdown = true; |
|
495 |
|
496 MoveCList(mHighQ, pendingQHigh); |
|
497 MoveCList(mMediumQ, pendingQMed); |
|
498 MoveCList(mLowQ, pendingQLow); |
|
499 MoveCList(mEvictionQ, evictionQ); |
|
500 mEvictionQSize = 0; |
|
501 mPendingCount = 0; |
|
502 |
|
503 if (mNumIdleThreads) |
|
504 mIdleThreadCV.NotifyAll(); |
|
505 |
|
506 // empty host database |
|
507 PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nullptr); |
|
508 } |
|
509 |
|
510 ClearPendingQueue(&pendingQHigh); |
|
511 ClearPendingQueue(&pendingQMed); |
|
512 ClearPendingQueue(&pendingQLow); |
|
513 |
|
514 if (!PR_CLIST_IS_EMPTY(&evictionQ)) { |
|
515 PRCList *node = evictionQ.next; |
|
516 while (node != &evictionQ) { |
|
517 nsHostRecord *rec = static_cast<nsHostRecord *>(node); |
|
518 node = node->next; |
|
519 NS_RELEASE(rec); |
|
520 } |
|
521 } |
|
522 |
|
523 #ifdef NS_BUILD_REFCNT_LOGGING |
|
524 |
|
525 // Logically join the outstanding worker threads with a timeout. |
|
526 // Use this approach instead of PR_JoinThread() because that does |
|
527 // not allow a timeout which may be necessary for a semi-responsive |
|
528 // shutdown if the thread is blocked on a very slow DNS resolution. |
|
529 // mThreadCount is read outside of mLock, but the worst case |
|
530 // scenario for that race is one extra 25ms sleep. |
|
531 |
|
532 PRIntervalTime delay = PR_MillisecondsToInterval(25); |
|
533 PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20); |
|
534 while (mThreadCount && PR_IntervalNow() < stopTime) |
|
535 PR_Sleep(delay); |
|
536 #endif |
|
537 } |
|
538 |
|
539 void |
|
540 nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ) |
|
541 { |
|
542 NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued"); |
|
543 |
|
544 PR_REMOVE_LINK(aRec); |
|
545 PR_APPEND_LINK(aRec, &aDestQ); |
|
546 } |
|
547 |
|
548 nsresult |
|
549 nsHostResolver::ResolveHost(const char *host, |
|
550 uint16_t flags, |
|
551 uint16_t af, |
|
552 nsResolveHostCallback *callback) |
|
553 { |
|
554 NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED); |
|
555 |
|
556 LOG(("Resolving host [%s]%s.\n", |
|
557 host, flags & RES_BYPASS_CACHE ? " - bypassing cache" : "")); |
|
558 |
|
559 // ensure that we are working with a valid hostname before proceeding. see |
|
560 // bug 304904 for details. |
|
561 if (!net_IsValidHostName(nsDependentCString(host))) |
|
562 return NS_ERROR_UNKNOWN_HOST; |
|
563 |
|
564 // if result is set inside the lock, then we need to issue the |
|
565 // callback before returning. |
|
566 nsRefPtr<nsHostRecord> result; |
|
567 nsresult status = NS_OK, rv = NS_OK; |
|
568 { |
|
569 MutexAutoLock lock(mLock); |
|
570 |
|
571 if (mShutdown) |
|
572 rv = NS_ERROR_NOT_INITIALIZED; |
|
573 else { |
|
574 // Used to try to parse to an IP address literal. |
|
575 PRNetAddr tempAddr; |
|
576 // Unfortunately, PR_StringToNetAddr does not properly initialize |
|
577 // the output buffer in the case of IPv6 input. See bug 223145. |
|
578 memset(&tempAddr, 0, sizeof(PRNetAddr)); |
|
579 |
|
580 // check to see if there is already an entry for this |host| |
|
581 // in the hash table. if so, then check to see if we can't |
|
582 // just reuse the lookup result. otherwise, if there are |
|
583 // any pending callbacks, then add to pending callbacks queue, |
|
584 // and return. otherwise, add ourselves as first pending |
|
585 // callback, and proceed to do the lookup. |
|
586 |
|
587 nsHostKey key = { host, flags, af }; |
|
588 nsHostDBEnt *he = static_cast<nsHostDBEnt *> |
|
589 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_ADD)); |
|
590 |
|
591 // if the record is null, then HostDB_InitEntry failed. |
|
592 if (!he || !he->rec) { |
|
593 LOG((" Out of memory: no cache entry for [%s].\n", host)); |
|
594 rv = NS_ERROR_OUT_OF_MEMORY; |
|
595 } |
|
596 // do we have a cached result that we can reuse? |
|
597 else if (!(flags & RES_BYPASS_CACHE) && |
|
598 he->rec->HasUsableResult(flags) && |
|
599 TimeStamp::NowLoRes() <= (he->rec->expiration + mGracePeriod)) { |
|
600 LOG((" Using cached record for host [%s].\n", host)); |
|
601 // put reference to host record on stack... |
|
602 result = he->rec; |
|
603 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT); |
|
604 |
|
605 // For entries that are in the grace period |
|
606 // or all cached negative entries, use the cache but start a new |
|
607 // lookup in the background |
|
608 ConditionallyRefreshRecord(he->rec, host); |
|
609 |
|
610 if (he->rec->negative) { |
|
611 LOG((" Negative cache entry for[%s].\n", host)); |
|
612 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
613 METHOD_NEGATIVE_HIT); |
|
614 status = NS_ERROR_UNKNOWN_HOST; |
|
615 } |
|
616 } |
|
617 // if the host name is an IP address literal and has been parsed, |
|
618 // go ahead and use it. |
|
619 else if (he->rec->addr) { |
|
620 LOG((" Using cached address for IP Literal [%s].\n", host)); |
|
621 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
622 METHOD_LITERAL); |
|
623 result = he->rec; |
|
624 } |
|
625 // try parsing the host name as an IP address literal to short |
|
626 // circuit full host resolution. (this is necessary on some |
|
627 // platforms like Win9x. see bug 219376 for more details.) |
|
628 else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) { |
|
629 LOG((" Host is IP Literal [%s].\n", host)); |
|
630 // ok, just copy the result into the host record, and be done |
|
631 // with it! ;-) |
|
632 he->rec->addr = new NetAddr(); |
|
633 PRNetAddrToNetAddr(&tempAddr, he->rec->addr); |
|
634 // put reference to host record on stack... |
|
635 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
636 METHOD_LITERAL); |
|
637 result = he->rec; |
|
638 } |
|
639 else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS && |
|
640 !IsHighPriority(flags) && |
|
641 !he->rec->resolving) { |
|
642 LOG((" Lookup queue full: dropping %s priority request for " |
|
643 "[%s].\n", |
|
644 IsMediumPriority(flags) ? "medium" : "low", host)); |
|
645 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
646 METHOD_OVERFLOW); |
|
647 // This is a lower priority request and we are swamped, so refuse it. |
|
648 rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL; |
|
649 } |
|
650 else if (flags & RES_OFFLINE) { |
|
651 LOG((" Offline request for [%s]; ignoring.\n", host)); |
|
652 rv = NS_ERROR_OFFLINE; |
|
653 } |
|
654 |
|
655 // If this is an IPV4 or IPV6 specific request, check if there is |
|
656 // an AF_UNSPEC entry we can use. Otherwise, hit the resolver... |
|
657 else if (!he->rec->resolving) { |
|
658 if (!(flags & RES_BYPASS_CACHE) && |
|
659 ((af == PR_AF_INET) || (af == PR_AF_INET6))) { |
|
660 // First, search for an entry with AF_UNSPEC |
|
661 const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC }; |
|
662 nsHostDBEnt *unspecHe = static_cast<nsHostDBEnt *> |
|
663 (PL_DHashTableOperate(&mDB, &unspecKey, PL_DHASH_LOOKUP)); |
|
664 NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(unspecHe) || |
|
665 (PL_DHASH_ENTRY_IS_BUSY(unspecHe) && |
|
666 unspecHe->rec), |
|
667 "Valid host entries should contain a record"); |
|
668 if (PL_DHASH_ENTRY_IS_BUSY(unspecHe) && |
|
669 unspecHe->rec && |
|
670 unspecHe->rec->HasUsableResult(flags) && |
|
671 TimeStamp::NowLoRes() <= (he->rec->expiration + mGracePeriod)) { |
|
672 |
|
673 MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative, |
|
674 "Entry should be resolved or negative."); |
|
675 |
|
676 LOG((" Trying AF_UNSPEC entry for [%s] af: %s.\n", |
|
677 host, (af == PR_AF_INET) ? "AF_INET" : "AF_INET6")); |
|
678 |
|
679 he->rec->addr_info = nullptr; |
|
680 if (unspecHe->rec->negative) { |
|
681 he->rec->negative = unspecHe->rec->negative; |
|
682 } else if (unspecHe->rec->addr_info) { |
|
683 // Search for any valid address in the AF_UNSPEC entry |
|
684 // in the cache (not blacklisted and from the right |
|
685 // family). |
|
686 NetAddrElement *addrIter = |
|
687 unspecHe->rec->addr_info->mAddresses.getFirst(); |
|
688 while (addrIter) { |
|
689 if ((af == addrIter->mAddress.inet.family) && |
|
690 !unspecHe->rec->Blacklisted(&addrIter->mAddress)) { |
|
691 if (!he->rec->addr_info) { |
|
692 he->rec->addr_info = new AddrInfo( |
|
693 unspecHe->rec->addr_info->mHostName, |
|
694 unspecHe->rec->addr_info->mCanonicalName); |
|
695 } |
|
696 he->rec->addr_info->AddAddress( |
|
697 new NetAddrElement(*addrIter)); |
|
698 } |
|
699 addrIter = addrIter->getNext(); |
|
700 } |
|
701 } |
|
702 if (he->rec->HasUsableResult(flags)) { |
|
703 result = he->rec; |
|
704 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
705 METHOD_HIT); |
|
706 ConditionallyRefreshRecord(he->rec, host); |
|
707 } |
|
708 // For AF_INET6, a new lookup means another AF_UNSPEC |
|
709 // lookup. We have already iterated through the |
|
710 // AF_UNSPEC addresses, so we mark this record as |
|
711 // negative. |
|
712 else if (af == PR_AF_INET6) { |
|
713 LOG((" No AF_INET6 in AF_UNSPEC entry: " |
|
714 "[%s] unknown host", host)); |
|
715 result = he->rec; |
|
716 he->rec->negative = true; |
|
717 status = NS_ERROR_UNKNOWN_HOST; |
|
718 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
719 METHOD_NEGATIVE_HIT); |
|
720 } |
|
721 } |
|
722 } |
|
723 // If no valid address was found in the cache or this is an |
|
724 // AF_UNSPEC request, then start a new lookup. |
|
725 if (!result) { |
|
726 LOG((" No usable address in cache for [%s]", host)); |
|
727 // Add callback to the list of pending callbacks. |
|
728 PR_APPEND_LINK(callback, &he->rec->callbacks); |
|
729 he->rec->flags = flags; |
|
730 rv = IssueLookup(he->rec); |
|
731 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
732 METHOD_NETWORK_FIRST); |
|
733 if (NS_FAILED(rv)) { |
|
734 PR_REMOVE_AND_INIT_LINK(callback); |
|
735 } |
|
736 else { |
|
737 LOG((" DNS lookup for host [%s] blocking pending " |
|
738 "'getaddrinfo' query: callback [%p]", |
|
739 host, callback)); |
|
740 } |
|
741 } |
|
742 } |
|
743 else { |
|
744 LOG((" Host [%s] is being resolved. Appending callback [%p].", |
|
745 host, callback)); |
|
746 PR_APPEND_LINK(callback, &he->rec->callbacks); |
|
747 if (he->rec->onQueue) { |
|
748 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
749 METHOD_NETWORK_SHARED); |
|
750 |
|
751 // Consider the case where we are on a pending queue of |
|
752 // lower priority than the request is being made at. |
|
753 // In that case we should upgrade to the higher queue. |
|
754 |
|
755 if (IsHighPriority(flags) && |
|
756 !IsHighPriority(he->rec->flags)) { |
|
757 // Move from (low|med) to high. |
|
758 MoveQueue(he->rec, mHighQ); |
|
759 he->rec->flags = flags; |
|
760 ConditionallyCreateThread(he->rec); |
|
761 } else if (IsMediumPriority(flags) && |
|
762 IsLowPriority(he->rec->flags)) { |
|
763 // Move from low to med. |
|
764 MoveQueue(he->rec, mMediumQ); |
|
765 he->rec->flags = flags; |
|
766 mIdleThreadCV.Notify(); |
|
767 } |
|
768 } |
|
769 } |
|
770 } |
|
771 } |
|
772 if (result) |
|
773 callback->OnLookupComplete(this, result, status); |
|
774 return rv; |
|
775 } |
|
776 |
|
777 void |
|
778 nsHostResolver::DetachCallback(const char *host, |
|
779 uint16_t flags, |
|
780 uint16_t af, |
|
781 nsResolveHostCallback *callback, |
|
782 nsresult status) |
|
783 { |
|
784 nsRefPtr<nsHostRecord> rec; |
|
785 { |
|
786 MutexAutoLock lock(mLock); |
|
787 |
|
788 nsHostKey key = { host, flags, af }; |
|
789 nsHostDBEnt *he = static_cast<nsHostDBEnt *> |
|
790 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP)); |
|
791 if (he && he->rec) { |
|
792 // walk list looking for |callback|... we cannot assume |
|
793 // that it will be there! |
|
794 PRCList *node = he->rec->callbacks.next; |
|
795 while (node != &he->rec->callbacks) { |
|
796 if (static_cast<nsResolveHostCallback *>(node) == callback) { |
|
797 PR_REMOVE_LINK(callback); |
|
798 rec = he->rec; |
|
799 break; |
|
800 } |
|
801 node = node->next; |
|
802 } |
|
803 } |
|
804 } |
|
805 |
|
806 // complete callback with the given status code; this would only be done if |
|
807 // the record was in the process of being resolved. |
|
808 if (rec) |
|
809 callback->OnLookupComplete(this, rec, status); |
|
810 } |
|
811 |
|
812 nsresult |
|
813 nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec) |
|
814 { |
|
815 if (mNumIdleThreads) { |
|
816 // wake up idle thread to process this lookup |
|
817 mIdleThreadCV.Notify(); |
|
818 } |
|
819 else if ((mThreadCount < HighThreadThreshold) || |
|
820 (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) { |
|
821 // dispatch new worker thread |
|
822 NS_ADDREF_THIS(); // owning reference passed to thread |
|
823 |
|
824 mThreadCount++; |
|
825 PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD, |
|
826 ThreadFunc, |
|
827 this, |
|
828 PR_PRIORITY_NORMAL, |
|
829 PR_GLOBAL_THREAD, |
|
830 PR_UNJOINABLE_THREAD, |
|
831 0); |
|
832 if (!thr) { |
|
833 mThreadCount--; |
|
834 NS_RELEASE_THIS(); |
|
835 return NS_ERROR_OUT_OF_MEMORY; |
|
836 } |
|
837 } |
|
838 #if defined(PR_LOGGING) |
|
839 else |
|
840 LOG((" Unable to find a thread for looking up host [%s].\n", rec->host)); |
|
841 #endif |
|
842 return NS_OK; |
|
843 } |
|
844 |
|
845 nsresult |
|
846 nsHostResolver::IssueLookup(nsHostRecord *rec) |
|
847 { |
|
848 MOZ_EVENT_TRACER_WAIT(rec, "net::dns::resolve"); |
|
849 |
|
850 nsresult rv = NS_OK; |
|
851 NS_ASSERTION(!rec->resolving, "record is already being resolved"); |
|
852 |
|
853 // Add rec to one of the pending queues, possibly removing it from mEvictionQ. |
|
854 // If rec is on mEvictionQ, then we can just move the owning |
|
855 // reference over to the new active queue. |
|
856 if (rec->next == rec) |
|
857 NS_ADDREF(rec); |
|
858 else { |
|
859 PR_REMOVE_LINK(rec); |
|
860 mEvictionQSize--; |
|
861 } |
|
862 |
|
863 if (IsHighPriority(rec->flags)) |
|
864 PR_APPEND_LINK(rec, &mHighQ); |
|
865 else if (IsMediumPriority(rec->flags)) |
|
866 PR_APPEND_LINK(rec, &mMediumQ); |
|
867 else |
|
868 PR_APPEND_LINK(rec, &mLowQ); |
|
869 mPendingCount++; |
|
870 |
|
871 rec->resolving = true; |
|
872 rec->onQueue = true; |
|
873 |
|
874 rv = ConditionallyCreateThread(rec); |
|
875 |
|
876 LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n", |
|
877 mThreadCount, |
|
878 mActiveAnyThreadCount, |
|
879 mNumIdleThreads, |
|
880 mPendingCount)); |
|
881 |
|
882 return rv; |
|
883 } |
|
884 |
|
885 nsresult |
|
886 nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host) |
|
887 { |
|
888 if (((TimeStamp::NowLoRes() > rec->expiration) || rec->negative) && |
|
889 !rec->resolving) { |
|
890 LOG((" Using %s cache entry for host [%s] but starting async renewal.", |
|
891 rec->negative ? "negative" :"positive", host)); |
|
892 IssueLookup(rec); |
|
893 |
|
894 if (!rec->negative) { |
|
895 // negative entries are constantly being refreshed, only |
|
896 // track positive grace period induced renewals |
|
897 Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, |
|
898 METHOD_RENEWAL); |
|
899 } |
|
900 } |
|
901 return NS_OK; |
|
902 } |
|
903 |
|
904 void |
|
905 nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult) |
|
906 { |
|
907 *aResult = static_cast<nsHostRecord *>(aQ.next); |
|
908 PR_REMOVE_AND_INIT_LINK(*aResult); |
|
909 mPendingCount--; |
|
910 (*aResult)->onQueue = false; |
|
911 } |
|
912 |
|
913 bool |
|
914 nsHostResolver::GetHostToLookup(nsHostRecord **result) |
|
915 { |
|
916 bool timedOut = false; |
|
917 PRIntervalTime epoch, now, timeout; |
|
918 |
|
919 MutexAutoLock lock(mLock); |
|
920 |
|
921 timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout; |
|
922 epoch = PR_IntervalNow(); |
|
923 |
|
924 while (!mShutdown) { |
|
925 // remove next record from Q; hand over owning reference. Check high, then med, then low |
|
926 |
|
927 if (!PR_CLIST_IS_EMPTY(&mHighQ)) { |
|
928 DeQueue (mHighQ, result); |
|
929 return true; |
|
930 } |
|
931 |
|
932 if (mActiveAnyThreadCount < HighThreadThreshold) { |
|
933 if (!PR_CLIST_IS_EMPTY(&mMediumQ)) { |
|
934 DeQueue (mMediumQ, result); |
|
935 mActiveAnyThreadCount++; |
|
936 (*result)->usingAnyThread = true; |
|
937 return true; |
|
938 } |
|
939 |
|
940 if (!PR_CLIST_IS_EMPTY(&mLowQ)) { |
|
941 DeQueue (mLowQ, result); |
|
942 mActiveAnyThreadCount++; |
|
943 (*result)->usingAnyThread = true; |
|
944 return true; |
|
945 } |
|
946 } |
|
947 |
|
948 // Determining timeout is racy, so allow one cycle through checking the queues |
|
949 // before exiting. |
|
950 if (timedOut) |
|
951 break; |
|
952 |
|
953 // wait for one or more of the following to occur: |
|
954 // (1) the pending queue has a host record to process |
|
955 // (2) the shutdown flag has been set |
|
956 // (3) the thread has been idle for too long |
|
957 |
|
958 mNumIdleThreads++; |
|
959 mIdleThreadCV.Wait(timeout); |
|
960 mNumIdleThreads--; |
|
961 |
|
962 now = PR_IntervalNow(); |
|
963 |
|
964 if ((PRIntervalTime)(now - epoch) >= timeout) |
|
965 timedOut = true; |
|
966 else { |
|
967 // It is possible that PR_WaitCondVar() was interrupted and returned early, |
|
968 // in which case we will loop back and re-enter it. In that case we want to |
|
969 // do so with the new timeout reduced to reflect time already spent waiting. |
|
970 timeout -= (PRIntervalTime)(now - epoch); |
|
971 epoch = now; |
|
972 } |
|
973 } |
|
974 |
|
975 // tell thread to exit... |
|
976 mThreadCount--; |
|
977 return false; |
|
978 } |
|
979 |
|
980 void |
|
981 nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, AddrInfo *result) |
|
982 { |
|
983 // get the list of pending callbacks for this lookup, and notify |
|
984 // them that the lookup is complete. |
|
985 PRCList cbs; |
|
986 PR_INIT_CLIST(&cbs); |
|
987 { |
|
988 MutexAutoLock lock(mLock); |
|
989 |
|
990 // grab list of callbacks to notify |
|
991 MoveCList(rec->callbacks, cbs); |
|
992 |
|
993 // update record fields. We might have a rec->addr_info already if a |
|
994 // previous lookup result expired and we're reresolving it.. |
|
995 AddrInfo *old_addr_info; |
|
996 { |
|
997 MutexAutoLock lock(rec->addr_info_lock); |
|
998 old_addr_info = rec->addr_info; |
|
999 rec->addr_info = result; |
|
1000 rec->addr_info_gencnt++; |
|
1001 } |
|
1002 delete old_addr_info; |
|
1003 |
|
1004 rec->expiration = TimeStamp::NowLoRes(); |
|
1005 if (result) { |
|
1006 rec->expiration += mMaxCacheLifetime; |
|
1007 rec->negative = false; |
|
1008 } |
|
1009 else { |
|
1010 rec->expiration += TimeDuration::FromSeconds(60); /* one minute for negative cache */ |
|
1011 rec->negative = true; |
|
1012 } |
|
1013 rec->resolving = false; |
|
1014 |
|
1015 if (rec->usingAnyThread) { |
|
1016 mActiveAnyThreadCount--; |
|
1017 rec->usingAnyThread = false; |
|
1018 } |
|
1019 |
|
1020 if (!mShutdown) { |
|
1021 // add to mEvictionQ |
|
1022 PR_APPEND_LINK(rec, &mEvictionQ); |
|
1023 NS_ADDREF(rec); |
|
1024 if (mEvictionQSize < mMaxCacheEntries) |
|
1025 mEvictionQSize++; |
|
1026 else { |
|
1027 // remove first element on mEvictionQ |
|
1028 nsHostRecord *head = |
|
1029 static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ)); |
|
1030 PR_REMOVE_AND_INIT_LINK(head); |
|
1031 PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE); |
|
1032 |
|
1033 if (!head->negative) { |
|
1034 // record the age of the entry upon eviction. |
|
1035 TimeDuration age = TimeStamp::NowLoRes() - |
|
1036 (head->expiration - mMaxCacheLifetime); |
|
1037 Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, |
|
1038 static_cast<uint32_t>(age.ToSeconds() / 60)); |
|
1039 } |
|
1040 |
|
1041 // release reference to rec owned by mEvictionQ |
|
1042 NS_RELEASE(head); |
|
1043 } |
|
1044 } |
|
1045 } |
|
1046 |
|
1047 MOZ_EVENT_TRACER_DONE(rec, "net::dns::resolve"); |
|
1048 |
|
1049 if (!PR_CLIST_IS_EMPTY(&cbs)) { |
|
1050 PRCList *node = cbs.next; |
|
1051 while (node != &cbs) { |
|
1052 nsResolveHostCallback *callback = |
|
1053 static_cast<nsResolveHostCallback *>(node); |
|
1054 node = node->next; |
|
1055 callback->OnLookupComplete(this, rec, status); |
|
1056 // NOTE: callback must not be dereferenced after this point!! |
|
1057 } |
|
1058 } |
|
1059 |
|
1060 NS_RELEASE(rec); |
|
1061 } |
|
1062 |
|
1063 void |
|
1064 nsHostResolver::CancelAsyncRequest(const char *host, |
|
1065 uint16_t flags, |
|
1066 uint16_t af, |
|
1067 nsIDNSListener *aListener, |
|
1068 nsresult status) |
|
1069 |
|
1070 { |
|
1071 MutexAutoLock lock(mLock); |
|
1072 |
|
1073 // Lookup the host record associated with host, flags & address family |
|
1074 nsHostKey key = { host, flags, af }; |
|
1075 nsHostDBEnt *he = static_cast<nsHostDBEnt *> |
|
1076 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP)); |
|
1077 if (he && he->rec) { |
|
1078 nsHostRecord* recPtr = nullptr; |
|
1079 PRCList *node = he->rec->callbacks.next; |
|
1080 // Remove the first nsDNSAsyncRequest callback which matches the |
|
1081 // supplied listener object |
|
1082 while (node != &he->rec->callbacks) { |
|
1083 nsResolveHostCallback *callback |
|
1084 = static_cast<nsResolveHostCallback *>(node); |
|
1085 if (callback && (callback->EqualsAsyncListener(aListener))) { |
|
1086 // Remove from the list of callbacks |
|
1087 PR_REMOVE_LINK(callback); |
|
1088 recPtr = he->rec; |
|
1089 callback->OnLookupComplete(this, recPtr, status); |
|
1090 break; |
|
1091 } |
|
1092 node = node->next; |
|
1093 } |
|
1094 |
|
1095 // If there are no more callbacks, remove the hash table entry |
|
1096 if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) { |
|
1097 PL_DHashTableOperate(&mDB, (nsHostKey *)recPtr, PL_DHASH_REMOVE); |
|
1098 // If record is on a Queue, remove it and then deref it |
|
1099 if (recPtr->next != recPtr) { |
|
1100 PR_REMOVE_LINK(recPtr); |
|
1101 NS_RELEASE(recPtr); |
|
1102 } |
|
1103 } |
|
1104 } |
|
1105 } |
|
1106 |
|
1107 static size_t |
|
1108 SizeOfHostDBEntExcludingThis(PLDHashEntryHdr* hdr, MallocSizeOf mallocSizeOf, |
|
1109 void*) |
|
1110 { |
|
1111 nsHostDBEnt* ent = static_cast<nsHostDBEnt*>(hdr); |
|
1112 return ent->rec->SizeOfIncludingThis(mallocSizeOf); |
|
1113 } |
|
1114 |
|
1115 size_t |
|
1116 nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const |
|
1117 { |
|
1118 MutexAutoLock lock(mLock); |
|
1119 |
|
1120 size_t n = mallocSizeOf(this); |
|
1121 n += PL_DHashTableSizeOfExcludingThis(&mDB, SizeOfHostDBEntExcludingThis, |
|
1122 mallocSizeOf); |
|
1123 |
|
1124 // The following fields aren't measured. |
|
1125 // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to |
|
1126 // nsHostRecords that also pointed to by entries |mDB|, and measured when |
|
1127 // |mDB| is measured. |
|
1128 |
|
1129 return n; |
|
1130 } |
|
1131 |
|
1132 void |
|
1133 nsHostResolver::ThreadFunc(void *arg) |
|
1134 { |
|
1135 LOG(("DNS lookup thread - starting execution.\n")); |
|
1136 |
|
1137 static nsThreadPoolNaming naming; |
|
1138 naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver")); |
|
1139 |
|
1140 #if defined(RES_RETRY_ON_FAILURE) |
|
1141 nsResState rs; |
|
1142 #endif |
|
1143 nsHostResolver *resolver = (nsHostResolver *)arg; |
|
1144 nsHostRecord *rec; |
|
1145 PRAddrInfo *prai = nullptr; |
|
1146 while (resolver->GetHostToLookup(&rec)) { |
|
1147 LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n", |
|
1148 rec->host)); |
|
1149 |
|
1150 int flags = PR_AI_ADDRCONFIG; |
|
1151 if (!(rec->flags & RES_CANON_NAME)) |
|
1152 flags |= PR_AI_NOCANONNAME; |
|
1153 |
|
1154 TimeStamp startTime = TimeStamp::Now(); |
|
1155 MOZ_EVENT_TRACER_EXEC(rec, "net::dns::resolve"); |
|
1156 |
|
1157 // We need to remove IPv4 records manually |
|
1158 // because PR_GetAddrInfoByName doesn't support PR_AF_INET6. |
|
1159 bool disableIPv4 = rec->af == PR_AF_INET6; |
|
1160 uint16_t af = disableIPv4 ? PR_AF_UNSPEC : rec->af; |
|
1161 prai = PR_GetAddrInfoByName(rec->host, af, flags); |
|
1162 #if defined(RES_RETRY_ON_FAILURE) |
|
1163 if (!prai && rs.Reset()) |
|
1164 prai = PR_GetAddrInfoByName(rec->host, af, flags); |
|
1165 #endif |
|
1166 |
|
1167 TimeDuration elapsed = TimeStamp::Now() - startTime; |
|
1168 uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds()); |
|
1169 |
|
1170 // convert error code to nsresult |
|
1171 nsresult status; |
|
1172 AddrInfo *ai = nullptr; |
|
1173 if (prai) { |
|
1174 const char *cname = nullptr; |
|
1175 if (rec->flags & RES_CANON_NAME) |
|
1176 cname = PR_GetCanonNameFromAddrInfo(prai); |
|
1177 ai = new AddrInfo(rec->host, prai, disableIPv4, cname); |
|
1178 PR_FreeAddrInfo(prai); |
|
1179 if (ai->mAddresses.isEmpty()) { |
|
1180 delete ai; |
|
1181 ai = nullptr; |
|
1182 } |
|
1183 } |
|
1184 if (ai) { |
|
1185 status = NS_OK; |
|
1186 |
|
1187 Telemetry::Accumulate(!rec->addr_info_gencnt ? |
|
1188 Telemetry::DNS_LOOKUP_TIME : |
|
1189 Telemetry::DNS_RENEWAL_TIME, |
|
1190 millis); |
|
1191 } |
|
1192 else { |
|
1193 status = NS_ERROR_UNKNOWN_HOST; |
|
1194 Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis); |
|
1195 } |
|
1196 |
|
1197 // OnLookupComplete may release "rec", log before we lose it. |
|
1198 LOG(("DNS lookup thread - lookup completed for host [%s]: %s.\n", |
|
1199 rec->host, ai ? "success" : "failure: unknown host")); |
|
1200 resolver->OnLookupComplete(rec, status, ai); |
|
1201 } |
|
1202 NS_RELEASE(resolver); |
|
1203 LOG(("DNS lookup thread - queue empty, thread finished.\n")); |
|
1204 } |
|
1205 |
|
1206 nsresult |
|
1207 nsHostResolver::Create(uint32_t maxCacheEntries, |
|
1208 uint32_t maxCacheLifetime, |
|
1209 uint32_t lifetimeGracePeriod, |
|
1210 nsHostResolver **result) |
|
1211 { |
|
1212 #if defined(PR_LOGGING) |
|
1213 if (!gHostResolverLog) |
|
1214 gHostResolverLog = PR_NewLogModule("nsHostResolver"); |
|
1215 #endif |
|
1216 |
|
1217 nsHostResolver *res = new nsHostResolver(maxCacheEntries, |
|
1218 maxCacheLifetime, |
|
1219 lifetimeGracePeriod); |
|
1220 if (!res) |
|
1221 return NS_ERROR_OUT_OF_MEMORY; |
|
1222 NS_ADDREF(res); |
|
1223 |
|
1224 nsresult rv = res->Init(); |
|
1225 if (NS_FAILED(rv)) |
|
1226 NS_RELEASE(res); |
|
1227 |
|
1228 *result = res; |
|
1229 return rv; |
|
1230 } |
|
1231 |
|
1232 PLDHashOperator |
|
1233 CacheEntryEnumerator(PLDHashTable *table, PLDHashEntryHdr *entry, |
|
1234 uint32_t number, void *arg) |
|
1235 { |
|
1236 // We don't pay attention to address literals, only resolved domains. |
|
1237 // Also require a host. |
|
1238 nsHostRecord *rec = static_cast<nsHostDBEnt*>(entry)->rec; |
|
1239 MOZ_ASSERT(rec, "rec should never be null here!"); |
|
1240 if (!rec || !rec->addr_info || !rec->host) { |
|
1241 return PL_DHASH_NEXT; |
|
1242 } |
|
1243 |
|
1244 DNSCacheEntries info; |
|
1245 info.hostname = rec->host; |
|
1246 info.family = rec->af; |
|
1247 info.expiration = (int64_t)(rec->expiration - TimeStamp::NowLoRes()).ToSeconds(); |
|
1248 if (info.expiration <= 0) { |
|
1249 // We only need valid DNS cache entries |
|
1250 return PL_DHASH_NEXT; |
|
1251 } |
|
1252 |
|
1253 { |
|
1254 MutexAutoLock lock(rec->addr_info_lock); |
|
1255 |
|
1256 NetAddr *addr = nullptr; |
|
1257 NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst(); |
|
1258 if (addrElement) { |
|
1259 addr = &addrElement->mAddress; |
|
1260 } |
|
1261 while (addr) { |
|
1262 char buf[kIPv6CStrBufSize]; |
|
1263 if (NetAddrToString(addr, buf, sizeof(buf))) { |
|
1264 info.hostaddr.AppendElement(buf); |
|
1265 } |
|
1266 addr = nullptr; |
|
1267 addrElement = addrElement->getNext(); |
|
1268 if (addrElement) { |
|
1269 addr = &addrElement->mAddress; |
|
1270 } |
|
1271 } |
|
1272 } |
|
1273 |
|
1274 nsTArray<DNSCacheEntries> *args = static_cast<nsTArray<DNSCacheEntries> *>(arg); |
|
1275 args->AppendElement(info); |
|
1276 |
|
1277 return PL_DHASH_NEXT; |
|
1278 } |
|
1279 |
|
1280 void |
|
1281 nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args) |
|
1282 { |
|
1283 PL_DHashTableEnumerate(&mDB, CacheEntryEnumerator, args); |
|
1284 } |