Line data Source code
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(HAVE_RES_NINIT)
7 : #include <sys/types.h>
8 : #include <netinet/in.h>
9 : #include <arpa/inet.h>
10 : #include <arpa/nameser.h>
11 : #include <resolv.h>
12 : #define RES_RETRY_ON_FAILURE
13 : #endif
14 :
15 : #include <stdlib.h>
16 : #include <ctime>
17 : #include "nsHostResolver.h"
18 : #include "nsError.h"
19 : #include "nsISupportsBase.h"
20 : #include "nsISupportsUtils.h"
21 : #include "nsAutoPtr.h"
22 : #include "nsPrintfCString.h"
23 : #include "prthread.h"
24 : #include "prerror.h"
25 : #include "prtime.h"
26 : #include "mozilla/Logging.h"
27 : #include "PLDHashTable.h"
28 : #include "plstr.h"
29 : #include "nsURLHelper.h"
30 : #include "nsThreadUtils.h"
31 : #include "GetAddrInfo.h"
32 : #include "GeckoProfiler.h"
33 :
34 : #include "mozilla/HashFunctions.h"
35 : #include "mozilla/TimeStamp.h"
36 : #include "mozilla/Telemetry.h"
37 : #include "mozilla/DebugOnly.h"
38 : #include "mozilla/Preferences.h"
39 :
40 : using namespace mozilla;
41 : using namespace mozilla::net;
42 :
43 : // None of our implementations expose a TTL for negative responses, so we use a
44 : // constant always.
45 : static const unsigned int NEGATIVE_RECORD_LIFETIME = 60;
46 :
47 : //----------------------------------------------------------------------------
48 :
49 : // Use a persistent thread pool in order to avoid spinning up new threads all the time.
50 : // In particular, thread creation results in a res_init() call from libc which is
51 : // quite expensive.
52 : //
53 : // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
54 : // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
55 : // currently in the pool a new thread is created for high priority requests. If
56 : // the new request is at a lower priority a new thread will only be created if
57 : // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
58 : // created or an idle thread located for the request it is queued.
59 : //
60 : // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
61 : // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
62 : // timeout period.
63 :
64 : #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
65 : #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
66 : #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
67 :
68 : static_assert(HighThreadThreshold <= MAX_RESOLVER_THREADS,
69 : "High Thread Threshold should be less equal Maximum allowed thread");
70 :
71 : //----------------------------------------------------------------------------
72 :
73 : static LazyLogModule gHostResolverLog("nsHostResolver");
74 : #define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
75 : #define LOG_ENABLED() MOZ_LOG_TEST(gHostResolverLog, mozilla::LogLevel::Debug)
76 :
77 : #define LOG_HOST(host, interface) host, \
78 : (interface && interface[0] != '\0') ? " on interface " : "", \
79 : (interface && interface[0] != '\0') ? interface : ""
80 :
81 : //----------------------------------------------------------------------------
82 :
83 : static inline void
84 5 : MoveCList(PRCList &from, PRCList &to)
85 : {
86 5 : if (!PR_CLIST_IS_EMPTY(&from)) {
87 1 : to.next = from.next;
88 1 : to.prev = from.prev;
89 1 : to.next->prev = &to;
90 1 : to.prev->next = &to;
91 1 : PR_INIT_CLIST(&from);
92 : }
93 5 : }
94 :
95 : //----------------------------------------------------------------------------
96 :
97 : #if defined(RES_RETRY_ON_FAILURE)
98 :
99 : // this class represents the resolver state for a given thread. if we
100 : // encounter a lookup failure, then we can invoke the Reset method on an
101 : // instance of this class to reset the resolver (in case /etc/resolv.conf
102 : // for example changed). this is mainly an issue on GNU systems since glibc
103 : // only reads in /etc/resolv.conf once per thread. it may be an issue on
104 : // other systems as well.
105 :
106 : class nsResState
107 : {
108 : public:
109 1 : nsResState()
110 : // initialize mLastReset to the time when this object
111 : // is created. this means that a reset will not occur
112 : // if a thread is too young. the alternative would be
113 : // to initialize this to the beginning of time, so that
114 : // the first failure would cause a reset, but since the
115 : // thread would have just started up, it likely would
116 : // already have current /etc/resolv.conf info.
117 1 : : mLastReset(PR_IntervalNow())
118 : {
119 1 : }
120 :
121 0 : bool Reset()
122 : {
123 : // reset no more than once per second
124 0 : if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
125 0 : return false;
126 :
127 0 : LOG(("Calling 'res_ninit'.\n"));
128 :
129 0 : mLastReset = PR_IntervalNow();
130 0 : return (res_ninit(&_res) == 0);
131 : }
132 :
133 : private:
134 : PRIntervalTime mLastReset;
135 : };
136 :
137 : #endif // RES_RETRY_ON_FAILURE
138 :
139 : //----------------------------------------------------------------------------
140 :
141 : static inline bool
142 1 : IsHighPriority(uint16_t flags)
143 : {
144 1 : return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM));
145 : }
146 :
147 : static inline bool
148 0 : IsMediumPriority(uint16_t flags)
149 : {
150 0 : return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
151 : }
152 :
153 : static inline bool
154 0 : IsLowPriority(uint16_t flags)
155 : {
156 0 : return flags & nsHostResolver::RES_PRIORITY_LOW;
157 : }
158 :
159 : //----------------------------------------------------------------------------
160 : // this macro filters out any flags that are not used when constructing the
161 : // host key. the significant flags are those that would affect the resulting
162 : // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
163 : #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
164 :
165 3 : nsHostRecord::nsHostRecord(const nsHostKey *key)
166 : : addr_info_lock("nsHostRecord.addr_info_lock")
167 : , addr_info_gencnt(0)
168 : , addr_info(nullptr)
169 : , addr(nullptr)
170 : , negative(false)
171 : , resolving(false)
172 : , onQueue(false)
173 : , usingAnyThread(false)
174 : , mDoomed(false)
175 : #if TTL_AVAILABLE
176 : , mGetTtl(false)
177 : #endif
178 : , mBlacklistedCount(0)
179 3 : , mResolveAgain(false)
180 : {
181 3 : host = ((char *) this) + sizeof(nsHostRecord);
182 3 : memcpy((char *) host, key->host, strlen(key->host) + 1);
183 3 : flags = key->flags;
184 3 : af = key->af;
185 3 : netInterface = host + strlen(key->host) + 1;
186 3 : memcpy((char *) netInterface, key->netInterface,
187 6 : strlen(key->netInterface) + 1);
188 3 : originSuffix = netInterface + strlen(key->netInterface) + 1;
189 3 : memcpy((char *) originSuffix, key->originSuffix,
190 6 : strlen(key->originSuffix) + 1);
191 3 : PR_INIT_CLIST(this);
192 3 : PR_INIT_CLIST(&callbacks);
193 3 : }
194 :
195 : nsresult
196 3 : nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
197 : {
198 3 : size_t hostLen = strlen(key->host) + 1;
199 3 : size_t netInterfaceLen = strlen(key->netInterface) + 1;
200 3 : size_t originSuffixLen = strlen(key->originSuffix) + 1;
201 3 : size_t size = hostLen + netInterfaceLen + originSuffixLen + sizeof(nsHostRecord);
202 :
203 : // Use placement new to create the object with room for the hostname,
204 : // network interface name and originSuffix allocated after it.
205 3 : void *place = ::operator new(size);
206 3 : *result = new(place) nsHostRecord(key);
207 3 : NS_ADDREF(*result);
208 :
209 3 : return NS_OK;
210 : }
211 :
212 : void
213 1 : nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace)
214 : {
215 1 : mValidStart = now;
216 1 : mGraceStart = now + TimeDuration::FromSeconds(valid);
217 1 : mValidEnd = now + TimeDuration::FromSeconds(valid + grace);
218 1 : }
219 :
220 : void
221 1 : nsHostRecord::CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecord)
222 : {
223 : // This is used to copy information from a cache entry to a record. All
224 : // information necessary for HasUsableRecord needs to be copied.
225 1 : mValidStart = aFromHostRecord->mValidStart;
226 1 : mValidEnd = aFromHostRecord->mValidEnd;
227 1 : mGraceStart = aFromHostRecord->mGraceStart;
228 1 : mDoomed = aFromHostRecord->mDoomed;
229 1 : }
230 :
231 0 : nsHostRecord::~nsHostRecord()
232 : {
233 0 : Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mBlacklistedCount);
234 0 : delete addr_info;
235 0 : delete addr;
236 0 : }
237 :
238 : bool
239 3 : nsHostRecord::Blacklisted(NetAddr *aQuery)
240 : {
241 : // must call locked
242 3 : LOG(("Checking blacklist for host [%s%s%s], host record [%p].\n",
243 : LOG_HOST(host, netInterface), this));
244 :
245 : // skip the string conversion for the common case of no blacklist
246 3 : if (!mBlacklistedItems.Length()) {
247 3 : return false;
248 : }
249 :
250 : char buf[kIPv6CStrBufSize];
251 0 : if (!NetAddrToString(aQuery, buf, sizeof(buf))) {
252 0 : return false;
253 : }
254 0 : nsDependentCString strQuery(buf);
255 :
256 0 : for (uint32_t i = 0; i < mBlacklistedItems.Length(); i++) {
257 0 : if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) {
258 0 : LOG(("Address [%s] is blacklisted for host [%s%s%s].\n", buf,
259 : LOG_HOST(host, netInterface)));
260 0 : return true;
261 : }
262 : }
263 :
264 0 : return false;
265 : }
266 :
267 : void
268 0 : nsHostRecord::ReportUnusable(NetAddr *aAddress)
269 : {
270 : // must call locked
271 0 : LOG(("Adding address to blacklist for host [%s%s%s], host record [%p].\n",
272 : LOG_HOST(host, netInterface), this));
273 :
274 0 : ++mBlacklistedCount;
275 :
276 0 : if (negative)
277 0 : mDoomed = true;
278 :
279 : char buf[kIPv6CStrBufSize];
280 0 : if (NetAddrToString(aAddress, buf, sizeof(buf))) {
281 0 : LOG(("Successfully adding address [%s] to blacklist for host "
282 : "[%s%s%s].\n", buf, LOG_HOST(host, netInterface)));
283 0 : mBlacklistedItems.AppendElement(nsCString(buf));
284 : }
285 0 : }
286 :
287 : void
288 0 : nsHostRecord::ResetBlacklist()
289 : {
290 : // must call locked
291 0 : LOG(("Resetting blacklist for host [%s%s%s], host record [%p].\n",
292 : LOG_HOST(host, netInterface), this));
293 0 : mBlacklistedItems.Clear();
294 0 : }
295 :
296 : nsHostRecord::ExpirationStatus
297 5 : nsHostRecord::CheckExpiration(const mozilla::TimeStamp& now) const {
298 13 : if (!mGraceStart.IsNull() && now >= mGraceStart
299 5 : && !mValidEnd.IsNull() && now < mValidEnd) {
300 0 : return nsHostRecord::EXP_GRACE;
301 : }
302 5 : if (!mValidEnd.IsNull() && now < mValidEnd) {
303 3 : return nsHostRecord::EXP_VALID;
304 : }
305 :
306 2 : return nsHostRecord::EXP_EXPIRED;
307 : }
308 :
309 :
310 : bool
311 4 : nsHostRecord::HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags) const
312 : {
313 4 : if (mDoomed) {
314 0 : return false;
315 : }
316 :
317 : // don't use cached negative results for high priority queries.
318 4 : if (negative && IsHighPriority(queryFlags)) {
319 0 : return false;
320 : }
321 :
322 4 : if (CheckExpiration(now) == EXP_EXPIRED) {
323 2 : return false;
324 : }
325 :
326 2 : return addr_info || addr || negative;
327 : }
328 :
329 : static size_t
330 0 : SizeOfResolveHostCallbackListExcludingHead(const PRCList *head,
331 : MallocSizeOf mallocSizeOf)
332 : {
333 0 : size_t n = 0;
334 0 : PRCList *curr = head->next;
335 0 : while (curr != head) {
336 : nsResolveHostCallback *callback =
337 0 : static_cast<nsResolveHostCallback*>(curr);
338 0 : n += callback->SizeOfIncludingThis(mallocSizeOf);
339 0 : curr = curr->next;
340 : }
341 0 : return n;
342 : }
343 :
344 : size_t
345 0 : nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
346 : {
347 0 : size_t n = mallocSizeOf(this);
348 :
349 : // The |host| field (inherited from nsHostKey) actually points to extra
350 : // memory that is allocated beyond the end of the nsHostRecord (see
351 : // nsHostRecord::Create()). So it will be included in the
352 : // |mallocSizeOf(this)| call above.
353 :
354 0 : n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf);
355 0 : n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
356 0 : n += mallocSizeOf(addr);
357 :
358 0 : n += mBlacklistedItems.ShallowSizeOfExcludingThis(mallocSizeOf);
359 0 : for (size_t i = 0; i < mBlacklistedItems.Length(); i++) {
360 0 : n += mBlacklistedItems[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
361 : }
362 0 : return n;
363 : }
364 :
365 : nsHostRecord::DnsPriority
366 1 : nsHostRecord::GetPriority(uint16_t aFlags)
367 : {
368 1 : if (IsHighPriority(aFlags)){
369 1 : return nsHostRecord::DNS_PRIORITY_HIGH;
370 : }
371 0 : if (IsMediumPriority(aFlags)) {
372 0 : return nsHostRecord::DNS_PRIORITY_MEDIUM;
373 : }
374 :
375 0 : return nsHostRecord::DNS_PRIORITY_LOW;
376 : }
377 :
378 : // Returns true if the entry can be removed, or false if it should be left.
379 : // Sets mResolveAgain true for entries being resolved right now.
380 : bool
381 0 : nsHostRecord::RemoveOrRefresh()
382 : {
383 0 : if (resolving) {
384 0 : if (!onQueue) {
385 : // The request has been passed to the OS resolver. The resultant DNS
386 : // record should be considered stale and not trusted; set a flag to
387 : // ensure it is called again.
388 0 : mResolveAgain = true;
389 : }
390 : // if Onqueue is true, the host entry is already added to the cache
391 : // but is still pending to get resolved: just leave it in hash.
392 0 : return false;
393 : }
394 : // Already resolved; not in a pending state; remove from cache.
395 0 : return true;
396 : }
397 :
398 : //----------------------------------------------------------------------------
399 :
400 : struct nsHostDBEnt : PLDHashEntryHdr
401 : {
402 : nsHostRecord *rec;
403 : };
404 :
405 : static PLDHashNumber
406 4 : HostDB_HashKey(const void *key)
407 : {
408 4 : const nsHostKey *hk = static_cast<const nsHostKey *>(key);
409 8 : return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af,
410 12 : HashString(hk->netInterface), HashString(hk->originSuffix));
411 : }
412 :
413 : static bool
414 1 : HostDB_MatchEntry(const PLDHashEntryHdr *entry,
415 : const void *key)
416 : {
417 1 : const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
418 1 : const nsHostKey *hk = static_cast<const nsHostKey *>(key);
419 :
420 1 : return !strcmp(he->rec->host ? he->rec->host : "",
421 2 : hk->host ? hk->host : "") &&
422 2 : RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
423 2 : he->rec->af == hk->af &&
424 3 : !strcmp(he->rec->netInterface, hk->netInterface) &&
425 2 : !strcmp(he->rec->originSuffix, hk->originSuffix);
426 : }
427 :
428 : static void
429 0 : HostDB_MoveEntry(PLDHashTable *table,
430 : const PLDHashEntryHdr *from,
431 : PLDHashEntryHdr *to)
432 : {
433 0 : static_cast<nsHostDBEnt *>(to)->rec =
434 0 : static_cast<const nsHostDBEnt *>(from)->rec;
435 0 : }
436 :
437 : static void
438 0 : HostDB_ClearEntry(PLDHashTable *table,
439 : PLDHashEntryHdr *entry)
440 : {
441 0 : nsHostDBEnt *he = static_cast<nsHostDBEnt*>(entry);
442 0 : MOZ_ASSERT(he, "nsHostDBEnt is null!");
443 :
444 0 : nsHostRecord *hr = he->rec;
445 0 : MOZ_ASSERT(hr, "nsHostDBEnt has null host record!");
446 :
447 0 : LOG(("Clearing cache db entry for host [%s%s%s].\n",
448 : LOG_HOST(hr->host, hr->netInterface)));
449 : #if defined(DEBUG)
450 : {
451 0 : MutexAutoLock lock(hr->addr_info_lock);
452 0 : if (!hr->addr_info) {
453 0 : LOG(("No address info for host [%s%s%s].\n",
454 : LOG_HOST(hr->host, hr->netInterface)));
455 : } else {
456 0 : if (!hr->mValidEnd.IsNull()) {
457 0 : TimeDuration diff = hr->mValidEnd - TimeStamp::NowLoRes();
458 0 : LOG(("Record for host [%s%s%s] expires in %f seconds.\n",
459 : LOG_HOST(hr->host, hr->netInterface),
460 : diff.ToSeconds()));
461 : } else {
462 0 : LOG(("Record for host [%s%s%s] not yet valid.\n",
463 : LOG_HOST(hr->host, hr->netInterface)));
464 : }
465 :
466 0 : NetAddrElement *addrElement = nullptr;
467 : char buf[kIPv6CStrBufSize];
468 0 : do {
469 0 : if (!addrElement) {
470 0 : addrElement = hr->addr_info->mAddresses.getFirst();
471 : } else {
472 0 : addrElement = addrElement->getNext();
473 : }
474 :
475 0 : if (addrElement) {
476 0 : NetAddrToString(&addrElement->mAddress, buf, sizeof(buf));
477 0 : LOG((" [%s]\n", buf));
478 : }
479 : }
480 0 : while (addrElement);
481 : }
482 : }
483 : #endif
484 0 : NS_RELEASE(he->rec);
485 0 : }
486 :
487 : static void
488 3 : HostDB_InitEntry(PLDHashEntryHdr *entry,
489 : const void *key)
490 : {
491 3 : nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
492 3 : nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
493 3 : }
494 :
495 : static const PLDHashTableOps gHostDB_ops =
496 : {
497 : HostDB_HashKey,
498 : HostDB_MatchEntry,
499 : HostDB_MoveEntry,
500 : HostDB_ClearEntry,
501 : HostDB_InitEntry,
502 : };
503 :
504 : //----------------------------------------------------------------------------
505 :
506 : #if TTL_AVAILABLE
507 : static const char kPrefGetTtl[] = "network.dns.get-ttl";
508 : static bool sGetTtlEnabled = false;
509 :
510 : static void DnsPrefChanged(const char* aPref, void* aClosure)
511 : {
512 : MOZ_ASSERT(NS_IsMainThread(),
513 : "Should be getting pref changed notification on main thread!");
514 :
515 : if (strcmp(aPref, kPrefGetTtl) != 0) {
516 : LOG(("DnsPrefChanged ignoring pref \"%s\"", aPref));
517 : return;
518 : }
519 :
520 : auto self = static_cast<nsHostResolver*>(aClosure);
521 : MOZ_ASSERT(self);
522 :
523 : sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl);
524 : }
525 : #endif
526 :
527 2 : nsHostResolver::nsHostResolver(uint32_t maxCacheEntries,
528 : uint32_t defaultCacheEntryLifetime,
529 2 : uint32_t defaultGracePeriod)
530 : : mMaxCacheEntries(maxCacheEntries)
531 : , mDefaultCacheLifetime(defaultCacheEntryLifetime)
532 : , mDefaultGracePeriod(defaultGracePeriod)
533 : , mLock("nsHostResolver.mLock")
534 : , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV")
535 : , mDB(&gHostDB_ops, sizeof(nsHostDBEnt), 0)
536 : , mEvictionQSize(0)
537 : , mShutdown(true)
538 : , mNumIdleThreads(0)
539 : , mThreadCount(0)
540 : , mActiveAnyThreadCount(0)
541 2 : , mPendingCount(0)
542 : {
543 2 : mCreationTime = PR_Now();
544 2 : PR_INIT_CLIST(&mHighQ);
545 2 : PR_INIT_CLIST(&mMediumQ);
546 2 : PR_INIT_CLIST(&mLowQ);
547 2 : PR_INIT_CLIST(&mEvictionQ);
548 :
549 2 : mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
550 2 : mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
551 2 : }
552 :
553 : nsHostResolver::~nsHostResolver() = default;
554 :
555 : nsresult
556 2 : nsHostResolver::Init()
557 : {
558 2 : if (NS_FAILED(GetAddrInfoInit())) {
559 0 : return NS_ERROR_FAILURE;
560 : }
561 :
562 2 : mShutdown = false;
563 :
564 : #if TTL_AVAILABLE
565 : // The preferences probably haven't been loaded from the disk yet, so we
566 : // need to register a callback that will set up the experiment once they
567 : // are. We also need to explicitly set a value for the props otherwise the
568 : // callback won't be called.
569 : {
570 : DebugOnly<nsresult> rv = Preferences::RegisterCallbackAndCall(
571 : &DnsPrefChanged, kPrefGetTtl, this);
572 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
573 : "Could not register DNS TTL pref callback.");
574 : }
575 : #endif
576 :
577 : #if defined(HAVE_RES_NINIT)
578 : // We want to make sure the system is using the correct resolver settings,
579 : // so we force it to reload those settings whenever we startup a subsequent
580 : // nsHostResolver instance. We assume that there is no reason to do this
581 : // for the first nsHostResolver instance since that is usually created
582 : // during application startup.
583 : static int initCount = 0;
584 2 : if (initCount++ > 0) {
585 1 : LOG(("Calling 'res_ninit'.\n"));
586 1 : res_ninit(&_res);
587 : }
588 : #endif
589 2 : return NS_OK;
590 : }
591 :
592 : void
593 3 : nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
594 : {
595 : // loop through pending queue, erroring out pending lookups.
596 3 : if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
597 0 : PRCList *node = aPendingQ->next;
598 0 : while (node != aPendingQ) {
599 0 : nsHostRecord *rec = static_cast<nsHostRecord *>(node);
600 0 : node = node->next;
601 0 : OnLookupComplete(rec, NS_ERROR_ABORT, nullptr);
602 : }
603 : }
604 3 : }
605 :
606 : //
607 : // FlushCache() is what we call when the network has changed. We must not
608 : // trust names that were resolved before this change. They may resolve
609 : // differently now.
610 : //
611 : // This function removes all existing resolved host entries from the hash.
612 : // Names that are in the pending queues can be left there. Entries in the
613 : // cache that have 'Resolve' set true but not 'onQueue' are being resolved
614 : // right now, so we need to mark them to get re-resolved on completion!
615 :
616 : void
617 0 : nsHostResolver::FlushCache()
618 : {
619 0 : MutexAutoLock lock(mLock);
620 0 : mEvictionQSize = 0;
621 :
622 : // Clear the evictionQ and remove all its corresponding entries from
623 : // the cache first
624 0 : if (!PR_CLIST_IS_EMPTY(&mEvictionQ)) {
625 0 : PRCList *node = mEvictionQ.next;
626 0 : while (node != &mEvictionQ) {
627 0 : nsHostRecord *rec = static_cast<nsHostRecord *>(node);
628 0 : node = node->next;
629 0 : PR_REMOVE_AND_INIT_LINK(rec);
630 0 : mDB.Remove((nsHostKey *) rec);
631 0 : NS_RELEASE(rec);
632 : }
633 : }
634 :
635 : // Refresh the cache entries that are resolving RIGHT now, remove the rest.
636 0 : for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
637 0 : auto entry = static_cast<nsHostDBEnt *>(iter.Get());
638 : // Try to remove the record, or mark it for refresh.
639 0 : if (entry->rec->RemoveOrRefresh()) {
640 0 : PR_REMOVE_LINK(entry->rec);
641 0 : iter.Remove();
642 : }
643 : }
644 0 : }
645 :
646 : void
647 1 : nsHostResolver::Shutdown()
648 : {
649 1 : LOG(("Shutting down host resolver.\n"));
650 :
651 : #if TTL_AVAILABLE
652 : {
653 : DebugOnly<nsresult> rv = Preferences::UnregisterCallback(
654 : &DnsPrefChanged, kPrefGetTtl, this);
655 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
656 : "Could not unregister DNS TTL pref callback.");
657 : }
658 : #endif
659 :
660 : PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
661 1 : PR_INIT_CLIST(&pendingQHigh);
662 1 : PR_INIT_CLIST(&pendingQMed);
663 1 : PR_INIT_CLIST(&pendingQLow);
664 1 : PR_INIT_CLIST(&evictionQ);
665 :
666 : {
667 2 : MutexAutoLock lock(mLock);
668 :
669 1 : mShutdown = true;
670 :
671 1 : MoveCList(mHighQ, pendingQHigh);
672 1 : MoveCList(mMediumQ, pendingQMed);
673 1 : MoveCList(mLowQ, pendingQLow);
674 1 : MoveCList(mEvictionQ, evictionQ);
675 1 : mEvictionQSize = 0;
676 1 : mPendingCount = 0;
677 :
678 1 : if (mNumIdleThreads)
679 0 : mIdleThreadCV.NotifyAll();
680 :
681 : // empty host database
682 1 : mDB.Clear();
683 : }
684 :
685 1 : ClearPendingQueue(&pendingQHigh);
686 1 : ClearPendingQueue(&pendingQMed);
687 1 : ClearPendingQueue(&pendingQLow);
688 :
689 1 : if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
690 0 : PRCList *node = evictionQ.next;
691 0 : while (node != &evictionQ) {
692 0 : nsHostRecord *rec = static_cast<nsHostRecord *>(node);
693 0 : node = node->next;
694 0 : NS_RELEASE(rec);
695 : }
696 : }
697 :
698 : #ifdef NS_BUILD_REFCNT_LOGGING
699 :
700 : // Logically join the outstanding worker threads with a timeout.
701 : // Use this approach instead of PR_JoinThread() because that does
702 : // not allow a timeout which may be necessary for a semi-responsive
703 : // shutdown if the thread is blocked on a very slow DNS resolution.
704 : // mThreadCount is read outside of mLock, but the worst case
705 : // scenario for that race is one extra 25ms sleep.
706 :
707 1 : PRIntervalTime delay = PR_MillisecondsToInterval(25);
708 1 : PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
709 1 : while (mThreadCount && PR_IntervalNow() < stopTime)
710 0 : PR_Sleep(delay);
711 : #endif
712 :
713 : {
714 2 : mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown();
715 1 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
716 : "Failed to shutdown GetAddrInfo");
717 : }
718 1 : }
719 :
720 : void
721 0 : nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
722 : {
723 0 : NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
724 :
725 0 : PR_REMOVE_LINK(aRec);
726 0 : PR_APPEND_LINK(aRec, &aDestQ);
727 0 : }
728 :
729 : nsresult
730 3 : nsHostResolver::ResolveHost(const char *host,
731 : const OriginAttributes &aOriginAttributes,
732 : uint16_t flags,
733 : uint16_t af,
734 : const char *netInterface,
735 : nsResolveHostCallback *callback)
736 : {
737 3 : NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
738 3 : NS_ENSURE_TRUE(netInterface, NS_ERROR_UNEXPECTED);
739 :
740 3 : LOG(("Resolving host [%s%s%s]%s.\n", LOG_HOST(host, netInterface),
741 : flags & RES_BYPASS_CACHE ? " - bypassing cache" : ""));
742 :
743 : // ensure that we are working with a valid hostname before proceeding. see
744 : // bug 304904 for details.
745 3 : if (!net_IsValidHostName(nsDependentCString(host)))
746 0 : return NS_ERROR_UNKNOWN_HOST;
747 :
748 : // if result is set inside the lock, then we need to issue the
749 : // callback before returning.
750 6 : RefPtr<nsHostRecord> result;
751 3 : nsresult status = NS_OK, rv = NS_OK;
752 : {
753 6 : MutexAutoLock lock(mLock);
754 :
755 3 : if (mShutdown)
756 0 : rv = NS_ERROR_NOT_INITIALIZED;
757 : else {
758 : // Used to try to parse to an IP address literal.
759 : PRNetAddr tempAddr;
760 : // Unfortunately, PR_StringToNetAddr does not properly initialize
761 : // the output buffer in the case of IPv6 input. See bug 223145.
762 3 : memset(&tempAddr, 0, sizeof(PRNetAddr));
763 :
764 : // check to see if there is already an entry for this |host|
765 : // in the hash table. if so, then check to see if we can't
766 : // just reuse the lookup result. otherwise, if there are
767 : // any pending callbacks, then add to pending callbacks queue,
768 : // and return. otherwise, add ourselves as first pending
769 : // callback, and proceed to do the lookup.
770 6 : nsAutoCString originSuffix;
771 3 : aOriginAttributes.CreateSuffix(originSuffix);
772 :
773 3 : nsHostKey key = { host, flags, af, netInterface, originSuffix.get() };
774 3 : auto he = static_cast<nsHostDBEnt*>(mDB.Add(&key, fallible));
775 :
776 : // if the record is null, the hash table OOM'd.
777 3 : if (!he) {
778 0 : LOG((" Out of memory: no cache entry for host [%s%s%s].\n",
779 : LOG_HOST(host, netInterface)));
780 0 : rv = NS_ERROR_OUT_OF_MEMORY;
781 : }
782 : // do we have a cached result that we can reuse?
783 11 : else if (!(flags & RES_BYPASS_CACHE) &&
784 9 : he->rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
785 0 : LOG((" Using cached record for host [%s%s%s].\n",
786 : LOG_HOST(host, netInterface)));
787 : // put reference to host record on stack...
788 0 : result = he->rec;
789 0 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
790 :
791 : // For entries that are in the grace period
792 : // or all cached negative entries, use the cache but start a new
793 : // lookup in the background
794 0 : ConditionallyRefreshRecord(he->rec, host);
795 :
796 0 : if (he->rec->negative) {
797 0 : LOG((" Negative cache entry for host [%s%s%s].\n",
798 : LOG_HOST(host, netInterface)));
799 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
800 0 : METHOD_NEGATIVE_HIT);
801 0 : status = NS_ERROR_UNKNOWN_HOST;
802 : }
803 : }
804 : // if the host name is an IP address literal and has been parsed,
805 : // go ahead and use it.
806 3 : else if (he->rec->addr) {
807 0 : LOG((" Using cached address for IP Literal [%s].\n", host));
808 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
809 0 : METHOD_LITERAL);
810 0 : result = he->rec;
811 : }
812 : // try parsing the host name as an IP address literal to short
813 : // circuit full host resolution. (this is necessary on some
814 : // platforms like Win9x. see bug 219376 for more details.)
815 3 : else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
816 1 : LOG((" Host is IP Literal [%s].\n", host));
817 : // ok, just copy the result into the host record, and be done
818 : // with it! ;-)
819 1 : he->rec->addr = new NetAddr();
820 1 : PRNetAddrToNetAddr(&tempAddr, he->rec->addr);
821 : // put reference to host record on stack...
822 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
823 1 : METHOD_LITERAL);
824 1 : result = he->rec;
825 : }
826 4 : else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
827 2 : !IsHighPriority(flags) &&
828 0 : !he->rec->resolving) {
829 0 : LOG((" Lookup queue full: dropping %s priority request for "
830 : "host [%s%s%s].\n",
831 : IsMediumPriority(flags) ? "medium" : "low",
832 : LOG_HOST(host, netInterface)));
833 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
834 0 : METHOD_OVERFLOW);
835 : // This is a lower priority request and we are swamped, so refuse it.
836 0 : rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
837 : }
838 2 : else if (flags & RES_OFFLINE) {
839 0 : LOG((" Offline request for host [%s%s%s]; ignoring.\n",
840 : LOG_HOST(host, netInterface)));
841 0 : rv = NS_ERROR_OFFLINE;
842 : }
843 :
844 : // If this is an IPV4 or IPV6 specific request, check if there is
845 : // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
846 2 : else if (!he->rec->resolving) {
847 2 : if (!(flags & RES_BYPASS_CACHE) &&
848 1 : ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
849 : // First, search for an entry with AF_UNSPEC
850 : const nsHostKey unspecKey = { host, flags, PR_AF_UNSPEC,
851 1 : netInterface, originSuffix.get() };
852 : auto unspecHe =
853 1 : static_cast<nsHostDBEnt*>(mDB.Search(&unspecKey));
854 1 : NS_ASSERTION(!unspecHe ||
855 : (unspecHe && unspecHe->rec),
856 : "Valid host entries should contain a record");
857 1 : TimeStamp now = TimeStamp::NowLoRes();
858 2 : if (unspecHe &&
859 1 : unspecHe->rec->HasUsableResult(now, flags)) {
860 :
861 1 : MOZ_ASSERT(unspecHe->rec->addr_info || unspecHe->rec->negative,
862 : "Entry should be resolved or negative.");
863 :
864 1 : LOG((" Trying AF_UNSPEC entry for host [%s%s%s] af: %s.\n",
865 : LOG_HOST(host, netInterface),
866 : (af == PR_AF_INET) ? "AF_INET" : "AF_INET6"));
867 :
868 1 : he->rec->addr_info = nullptr;
869 1 : if (unspecHe->rec->negative) {
870 0 : he->rec->negative = unspecHe->rec->negative;
871 0 : he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec);
872 1 : } else if (unspecHe->rec->addr_info) {
873 : // Search for any valid address in the AF_UNSPEC entry
874 : // in the cache (not blacklisted and from the right
875 : // family).
876 : NetAddrElement *addrIter =
877 1 : unspecHe->rec->addr_info->mAddresses.getFirst();
878 3 : while (addrIter) {
879 2 : if ((af == addrIter->mAddress.inet.family) &&
880 1 : !unspecHe->rec->Blacklisted(&addrIter->mAddress)) {
881 1 : if (!he->rec->addr_info) {
882 1 : he->rec->addr_info = new AddrInfo(
883 1 : unspecHe->rec->addr_info->mHostName,
884 2 : unspecHe->rec->addr_info->mCanonicalName);
885 1 : he->rec->CopyExpirationTimesAndFlagsFrom(unspecHe->rec);
886 : }
887 1 : he->rec->addr_info->AddAddress(
888 2 : new NetAddrElement(*addrIter));
889 : }
890 1 : addrIter = addrIter->getNext();
891 : }
892 : }
893 : // Now check if we have a new record.
894 1 : if (he->rec->HasUsableResult(now, flags)) {
895 1 : result = he->rec;
896 1 : if (he->rec->negative) {
897 0 : status = NS_ERROR_UNKNOWN_HOST;
898 : }
899 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
900 1 : METHOD_HIT);
901 1 : ConditionallyRefreshRecord(he->rec, host);
902 : }
903 : // For AF_INET6, a new lookup means another AF_UNSPEC
904 : // lookup. We have already iterated through the
905 : // AF_UNSPEC addresses, so we mark this record as
906 : // negative.
907 0 : else if (af == PR_AF_INET6) {
908 0 : LOG((" No AF_INET6 in AF_UNSPEC entry: "
909 : "host [%s%s%s] unknown host.",
910 : LOG_HOST(host, netInterface)));
911 0 : result = he->rec;
912 0 : he->rec->negative = true;
913 0 : status = NS_ERROR_UNKNOWN_HOST;
914 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
915 0 : METHOD_NEGATIVE_HIT);
916 : }
917 : }
918 : }
919 : // If no valid address was found in the cache or this is an
920 : // AF_UNSPEC request, then start a new lookup.
921 2 : if (!result) {
922 1 : LOG((" No usable address in cache for host [%s%s%s].",
923 : LOG_HOST(host, netInterface)));
924 :
925 : // Add callback to the list of pending callbacks.
926 1 : PR_APPEND_LINK(callback, &he->rec->callbacks);
927 1 : he->rec->flags = flags;
928 1 : rv = IssueLookup(he->rec);
929 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
930 1 : METHOD_NETWORK_FIRST);
931 1 : if (NS_FAILED(rv)) {
932 0 : PR_REMOVE_AND_INIT_LINK(callback);
933 : }
934 : else {
935 1 : LOG((" DNS lookup for host [%s%s%s] blocking "
936 : "pending 'getaddrinfo' query: callback [%p]",
937 : LOG_HOST(host, netInterface), callback));
938 : }
939 : }
940 : }
941 : else {
942 0 : LOG((" Host [%s%s%s] is being resolved. Appending callback "
943 : "[%p].", LOG_HOST(host, netInterface), callback));
944 :
945 0 : PR_APPEND_LINK(callback, &he->rec->callbacks);
946 0 : if (he->rec->onQueue) {
947 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
948 0 : METHOD_NETWORK_SHARED);
949 :
950 : // Consider the case where we are on a pending queue of
951 : // lower priority than the request is being made at.
952 : // In that case we should upgrade to the higher queue.
953 :
954 0 : if (IsHighPriority(flags) &&
955 0 : !IsHighPriority(he->rec->flags)) {
956 : // Move from (low|med) to high.
957 0 : MoveQueue(he->rec, mHighQ);
958 0 : he->rec->flags = flags;
959 0 : ConditionallyCreateThread(he->rec);
960 0 : } else if (IsMediumPriority(flags) &&
961 0 : IsLowPriority(he->rec->flags)) {
962 : // Move from low to med.
963 0 : MoveQueue(he->rec, mMediumQ);
964 0 : he->rec->flags = flags;
965 0 : mIdleThreadCV.Notify();
966 : }
967 : }
968 : }
969 : }
970 : }
971 3 : if (result) {
972 2 : callback->OnLookupComplete(this, result, status);
973 : }
974 :
975 3 : return rv;
976 : }
977 :
978 : void
979 0 : nsHostResolver::DetachCallback(const char *host,
980 : const OriginAttributes &aOriginAttributes,
981 : uint16_t flags,
982 : uint16_t af,
983 : const char *netInterface,
984 : nsResolveHostCallback *callback,
985 : nsresult status)
986 : {
987 0 : RefPtr<nsHostRecord> rec;
988 : {
989 0 : MutexAutoLock lock(mLock);
990 :
991 0 : nsAutoCString originSuffix;
992 0 : aOriginAttributes.CreateSuffix(originSuffix);
993 :
994 0 : nsHostKey key = { host, flags, af, netInterface, originSuffix.get() };
995 0 : auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key));
996 0 : if (he) {
997 : // walk list looking for |callback|... we cannot assume
998 : // that it will be there!
999 0 : PRCList *node = he->rec->callbacks.next;
1000 0 : while (node != &he->rec->callbacks) {
1001 0 : if (static_cast<nsResolveHostCallback *>(node) == callback) {
1002 0 : PR_REMOVE_LINK(callback);
1003 0 : rec = he->rec;
1004 0 : break;
1005 : }
1006 0 : node = node->next;
1007 : }
1008 : }
1009 : }
1010 :
1011 : // complete callback with the given status code; this would only be done if
1012 : // the record was in the process of being resolved.
1013 0 : if (rec)
1014 0 : callback->OnLookupComplete(this, rec, status);
1015 0 : }
1016 :
1017 : nsresult
1018 1 : nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
1019 : {
1020 1 : if (mNumIdleThreads) {
1021 : // wake up idle thread to process this lookup
1022 0 : mIdleThreadCV.Notify();
1023 : }
1024 2 : else if ((mThreadCount < HighThreadThreshold) ||
1025 0 : (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
1026 : // dispatch new worker thread
1027 1 : NS_ADDREF_THIS(); // owning reference passed to thread
1028 :
1029 1 : mThreadCount++;
1030 : PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
1031 : ThreadFunc,
1032 : this,
1033 : PR_PRIORITY_NORMAL,
1034 : PR_GLOBAL_THREAD,
1035 : PR_UNJOINABLE_THREAD,
1036 1 : 0);
1037 1 : if (!thr) {
1038 0 : mThreadCount--;
1039 0 : NS_RELEASE_THIS();
1040 0 : return NS_ERROR_OUT_OF_MEMORY;
1041 : }
1042 : }
1043 : else {
1044 0 : LOG((" Unable to find a thread for looking up host [%s%s%s].\n",
1045 : LOG_HOST(rec->host, rec->netInterface)));
1046 : }
1047 1 : return NS_OK;
1048 : }
1049 :
1050 : nsresult
1051 1 : nsHostResolver::IssueLookup(nsHostRecord *rec)
1052 : {
1053 1 : nsresult rv = NS_OK;
1054 1 : NS_ASSERTION(!rec->resolving, "record is already being resolved");
1055 :
1056 : // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
1057 : // If rec is on mEvictionQ, then we can just move the owning
1058 : // reference over to the new active queue.
1059 1 : if (rec->next == rec)
1060 1 : NS_ADDREF(rec);
1061 : else {
1062 0 : PR_REMOVE_LINK(rec);
1063 0 : mEvictionQSize--;
1064 : }
1065 :
1066 1 : switch (nsHostRecord::GetPriority(rec->flags)) {
1067 : case nsHostRecord::DNS_PRIORITY_HIGH:
1068 1 : PR_APPEND_LINK(rec, &mHighQ);
1069 1 : break;
1070 :
1071 : case nsHostRecord::DNS_PRIORITY_MEDIUM:
1072 0 : PR_APPEND_LINK(rec, &mMediumQ);
1073 0 : break;
1074 :
1075 : case nsHostRecord::DNS_PRIORITY_LOW:
1076 0 : PR_APPEND_LINK(rec, &mLowQ);
1077 0 : break;
1078 : }
1079 1 : mPendingCount++;
1080 :
1081 1 : rec->resolving = true;
1082 1 : rec->onQueue = true;
1083 :
1084 1 : rv = ConditionallyCreateThread(rec);
1085 :
1086 1 : LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n",
1087 : static_cast<uint32_t>(mThreadCount),
1088 : static_cast<uint32_t>(mActiveAnyThreadCount),
1089 : static_cast<uint32_t>(mNumIdleThreads),
1090 : static_cast<uint32_t>(mPendingCount)));
1091 :
1092 1 : return rv;
1093 : }
1094 :
1095 : nsresult
1096 1 : nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host)
1097 : {
1098 3 : if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID
1099 3 : || rec->negative) && !rec->resolving) {
1100 0 : LOG((" Using %s cache entry for host [%s] but starting async renewal.",
1101 : rec->negative ? "negative" :"positive", host));
1102 0 : IssueLookup(rec);
1103 :
1104 0 : if (!rec->negative) {
1105 : // negative entries are constantly being refreshed, only
1106 : // track positive grace period induced renewals
1107 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
1108 0 : METHOD_RENEWAL);
1109 : }
1110 : }
1111 1 : return NS_OK;
1112 : }
1113 :
1114 : void
1115 1 : nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
1116 : {
1117 1 : *aResult = static_cast<nsHostRecord *>(aQ.next);
1118 1 : PR_REMOVE_AND_INIT_LINK(*aResult);
1119 1 : mPendingCount--;
1120 1 : (*aResult)->onQueue = false;
1121 1 : }
1122 :
1123 : bool
1124 2 : nsHostResolver::GetHostToLookup(nsHostRecord **result)
1125 : {
1126 2 : bool timedOut = false;
1127 : PRIntervalTime epoch, now, timeout;
1128 :
1129 3 : MutexAutoLock lock(mLock);
1130 :
1131 2 : timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
1132 2 : epoch = PR_IntervalNow();
1133 :
1134 2 : while (!mShutdown) {
1135 : // remove next record from Q; hand over owning reference. Check high, then med, then low
1136 :
1137 : #if TTL_AVAILABLE
1138 : #define SET_GET_TTL(var, val) \
1139 : (var)->mGetTtl = sGetTtlEnabled && (val)
1140 : #else
1141 : #define SET_GET_TTL(var, val)
1142 : #endif
1143 :
1144 2 : if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
1145 1 : DeQueue (mHighQ, result);
1146 : SET_GET_TTL(*result, false);
1147 1 : return true;
1148 : }
1149 :
1150 1 : if (mActiveAnyThreadCount < HighThreadThreshold) {
1151 1 : if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
1152 0 : DeQueue (mMediumQ, result);
1153 0 : mActiveAnyThreadCount++;
1154 0 : (*result)->usingAnyThread = true;
1155 : SET_GET_TTL(*result, true);
1156 0 : return true;
1157 : }
1158 :
1159 1 : if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
1160 0 : DeQueue (mLowQ, result);
1161 0 : mActiveAnyThreadCount++;
1162 0 : (*result)->usingAnyThread = true;
1163 : SET_GET_TTL(*result, true);
1164 0 : return true;
1165 : }
1166 : }
1167 :
1168 : // Determining timeout is racy, so allow one cycle through checking the queues
1169 : // before exiting.
1170 1 : if (timedOut)
1171 0 : break;
1172 :
1173 : // wait for one or more of the following to occur:
1174 : // (1) the pending queue has a host record to process
1175 : // (2) the shutdown flag has been set
1176 : // (3) the thread has been idle for too long
1177 :
1178 1 : mNumIdleThreads++;
1179 1 : mIdleThreadCV.Wait(timeout);
1180 0 : mNumIdleThreads--;
1181 :
1182 0 : now = PR_IntervalNow();
1183 :
1184 0 : if ((PRIntervalTime)(now - epoch) >= timeout)
1185 0 : timedOut = true;
1186 : else {
1187 : // It is possible that PR_WaitCondVar() was interrupted and returned early,
1188 : // in which case we will loop back and re-enter it. In that case we want to
1189 : // do so with the new timeout reduced to reflect time already spent waiting.
1190 0 : timeout -= (PRIntervalTime)(now - epoch);
1191 0 : epoch = now;
1192 : }
1193 : }
1194 :
1195 : // tell thread to exit...
1196 0 : return false;
1197 : }
1198 :
1199 : void
1200 1 : nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const
1201 : {
1202 1 : MOZ_ASSERT(((bool)rec->addr_info) != rec->negative);
1203 1 : if (!rec->addr_info) {
1204 0 : rec->SetExpiration(TimeStamp::NowLoRes(),
1205 0 : NEGATIVE_RECORD_LIFETIME, 0);
1206 0 : LOG(("Caching host [%s%s%s] negative record for %u seconds.\n",
1207 : LOG_HOST(rec->host, rec->netInterface),
1208 : NEGATIVE_RECORD_LIFETIME));
1209 0 : return;
1210 : }
1211 :
1212 1 : unsigned int lifetime = mDefaultCacheLifetime;
1213 1 : unsigned int grace = mDefaultGracePeriod;
1214 : #if TTL_AVAILABLE
1215 : unsigned int ttl = mDefaultCacheLifetime;
1216 : if (sGetTtlEnabled) {
1217 : MutexAutoLock lock(rec->addr_info_lock);
1218 : if (rec->addr_info && rec->addr_info->ttl != AddrInfo::NO_TTL_DATA) {
1219 : ttl = rec->addr_info->ttl;
1220 : }
1221 : lifetime = ttl;
1222 : grace = 0;
1223 : }
1224 : #endif
1225 :
1226 1 : rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace);
1227 1 : LOG(("Caching host [%s%s%s] record for %u seconds (grace %d).",
1228 : LOG_HOST(rec->host, rec->netInterface), lifetime, grace));
1229 : }
1230 :
1231 : static bool
1232 1 : different_rrset(AddrInfo *rrset1, AddrInfo *rrset2)
1233 : {
1234 1 : if (!rrset1 || !rrset2) {
1235 1 : return true;
1236 : }
1237 :
1238 0 : LOG(("different_rrset %s\n", rrset1->mHostName));
1239 0 : nsTArray<NetAddr> orderedSet1;
1240 0 : nsTArray<NetAddr> orderedSet2;
1241 :
1242 0 : for (NetAddrElement *element = rrset1->mAddresses.getFirst();
1243 0 : element; element = element->getNext()) {
1244 0 : if (LOG_ENABLED()) {
1245 : char buf[128];
1246 0 : NetAddrToString(&element->mAddress, buf, 128);
1247 0 : LOG(("different_rrset add to set 1 %s\n", buf));
1248 : }
1249 0 : orderedSet1.InsertElementAt(orderedSet1.Length(), element->mAddress);
1250 : }
1251 :
1252 0 : for (NetAddrElement *element = rrset2->mAddresses.getFirst();
1253 0 : element; element = element->getNext()) {
1254 0 : if (LOG_ENABLED()) {
1255 : char buf[128];
1256 0 : NetAddrToString(&element->mAddress, buf, 128);
1257 0 : LOG(("different_rrset add to set 2 %s\n", buf));
1258 : }
1259 0 : orderedSet2.InsertElementAt(orderedSet2.Length(), element->mAddress);
1260 : }
1261 :
1262 0 : if (orderedSet1.Length() != orderedSet2.Length()) {
1263 0 : LOG(("different_rrset true due to length change\n"));
1264 0 : return true;
1265 : }
1266 0 : orderedSet1.Sort();
1267 0 : orderedSet2.Sort();
1268 :
1269 0 : for (uint32_t i = 0; i < orderedSet1.Length(); ++i) {
1270 0 : if (!(orderedSet1[i] == orderedSet2[i])) {
1271 0 : LOG(("different_rrset true due to content change\n"));
1272 0 : return true;
1273 : }
1274 : }
1275 0 : LOG(("different_rrset false\n"));
1276 0 : return false;
1277 : }
1278 :
1279 : //
1280 : // OnLookupComplete() checks if the resolving should be redone and if so it
1281 : // returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
1282 : // takes ownership of AddrInfo parameter
1283 : nsHostResolver::LookupStatus
1284 1 : nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* newRRSet)
1285 : {
1286 : // get the list of pending callbacks for this lookup, and notify
1287 : // them that the lookup is complete.
1288 : PRCList cbs;
1289 1 : PR_INIT_CLIST(&cbs);
1290 : {
1291 2 : MutexAutoLock lock(mLock);
1292 :
1293 1 : if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) {
1294 0 : LOG(("nsHostResolver record %p resolve again due to flushcache\n", rec));
1295 0 : rec->mResolveAgain = false;
1296 0 : delete newRRSet;
1297 0 : return LOOKUP_RESOLVEAGAIN;
1298 : }
1299 :
1300 : // grab list of callbacks to notify
1301 1 : MoveCList(rec->callbacks, cbs);
1302 :
1303 : // update record fields. We might have a rec->addr_info already if a
1304 : // previous lookup result expired and we're reresolving it..
1305 : AddrInfo *old_addr_info;
1306 : {
1307 2 : MutexAutoLock lock(rec->addr_info_lock);
1308 1 : if (different_rrset(rec->addr_info, newRRSet)) {
1309 1 : LOG(("nsHostResolver record %p new gencnt\n", rec));
1310 1 : old_addr_info = rec->addr_info;
1311 1 : rec->addr_info = newRRSet;
1312 1 : rec->addr_info_gencnt++;
1313 : } else {
1314 0 : if (rec->addr_info && newRRSet) {
1315 0 : rec->addr_info->ttl = newRRSet->ttl;
1316 : }
1317 0 : old_addr_info = newRRSet;
1318 : }
1319 : }
1320 1 : delete old_addr_info;
1321 :
1322 1 : rec->negative = !rec->addr_info;
1323 1 : PrepareRecordExpiration(rec);
1324 1 : rec->resolving = false;
1325 :
1326 1 : if (rec->usingAnyThread) {
1327 0 : mActiveAnyThreadCount--;
1328 0 : rec->usingAnyThread = false;
1329 : }
1330 :
1331 1 : if (!mShutdown) {
1332 : // add to mEvictionQ
1333 1 : PR_APPEND_LINK(rec, &mEvictionQ);
1334 1 : NS_ADDREF(rec);
1335 1 : if (mEvictionQSize < mMaxCacheEntries)
1336 1 : mEvictionQSize++;
1337 : else {
1338 : // remove first element on mEvictionQ
1339 : nsHostRecord *head =
1340 0 : static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
1341 0 : PR_REMOVE_AND_INIT_LINK(head);
1342 0 : mDB.Remove((nsHostKey *) head);
1343 :
1344 0 : if (!head->negative) {
1345 : // record the age of the entry upon eviction.
1346 0 : TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
1347 0 : Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE,
1348 0 : static_cast<uint32_t>(age.ToSeconds() / 60));
1349 : }
1350 :
1351 : // release reference to rec owned by mEvictionQ
1352 0 : NS_RELEASE(head);
1353 : }
1354 : #if TTL_AVAILABLE
1355 : if (!rec->mGetTtl && !rec->resolving && sGetTtlEnabled) {
1356 : LOG(("Issuing second async lookup for TTL for host [%s%s%s].",
1357 : LOG_HOST(rec->host, rec->netInterface)));
1358 : rec->flags =
1359 : (rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW;
1360 : DebugOnly<nsresult> rv = IssueLookup(rec);
1361 : NS_WARNING_ASSERTION(
1362 : NS_SUCCEEDED(rv),
1363 : "Could not issue second async lookup for TTL.");
1364 : }
1365 : #endif
1366 : }
1367 : }
1368 :
1369 1 : if (!PR_CLIST_IS_EMPTY(&cbs)) {
1370 1 : PRCList *node = cbs.next;
1371 3 : while (node != &cbs) {
1372 : nsResolveHostCallback *callback =
1373 1 : static_cast<nsResolveHostCallback *>(node);
1374 1 : node = node->next;
1375 1 : callback->OnLookupComplete(this, rec, status);
1376 : // NOTE: callback must not be dereferenced after this point!!
1377 : }
1378 : }
1379 :
1380 1 : NS_RELEASE(rec);
1381 :
1382 1 : return LOOKUP_OK;
1383 : }
1384 :
1385 : void
1386 0 : nsHostResolver::CancelAsyncRequest(const char *host,
1387 : const OriginAttributes &aOriginAttributes,
1388 : uint16_t flags,
1389 : uint16_t af,
1390 : const char *netInterface,
1391 : nsIDNSListener *aListener,
1392 : nsresult status)
1393 :
1394 : {
1395 0 : MutexAutoLock lock(mLock);
1396 :
1397 0 : nsAutoCString originSuffix;
1398 0 : aOriginAttributes.CreateSuffix(originSuffix);
1399 :
1400 : // Lookup the host record associated with host, flags & address family
1401 0 : nsHostKey key = { host, flags, af, netInterface, originSuffix.get() };
1402 0 : auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key));
1403 0 : if (he) {
1404 0 : nsHostRecord* recPtr = nullptr;
1405 0 : PRCList *node = he->rec->callbacks.next;
1406 : // Remove the first nsDNSAsyncRequest callback which matches the
1407 : // supplied listener object
1408 0 : while (node != &he->rec->callbacks) {
1409 : nsResolveHostCallback *callback
1410 0 : = static_cast<nsResolveHostCallback *>(node);
1411 0 : if (callback && (callback->EqualsAsyncListener(aListener))) {
1412 : // Remove from the list of callbacks
1413 0 : PR_REMOVE_LINK(callback);
1414 0 : recPtr = he->rec;
1415 0 : callback->OnLookupComplete(this, recPtr, status);
1416 0 : break;
1417 : }
1418 0 : node = node->next;
1419 : }
1420 :
1421 : // If there are no more callbacks, remove the hash table entry
1422 0 : if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
1423 0 : mDB.Remove((nsHostKey *)recPtr);
1424 : // If record is on a Queue, remove it and then deref it
1425 0 : if (recPtr->next != recPtr) {
1426 0 : PR_REMOVE_LINK(recPtr);
1427 0 : NS_RELEASE(recPtr);
1428 : }
1429 : }
1430 : }
1431 0 : }
1432 :
1433 : size_t
1434 0 : nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
1435 : {
1436 0 : MutexAutoLock lock(mLock);
1437 :
1438 0 : size_t n = mallocSizeOf(this);
1439 :
1440 0 : n += mDB.ShallowSizeOfExcludingThis(mallocSizeOf);
1441 0 : for (auto iter = mDB.ConstIter(); !iter.Done(); iter.Next()) {
1442 0 : auto entry = static_cast<nsHostDBEnt*>(iter.Get());
1443 0 : n += entry->rec->SizeOfIncludingThis(mallocSizeOf);
1444 : }
1445 :
1446 : // The following fields aren't measured.
1447 : // - mHighQ, mMediumQ, mLowQ, mEvictionQ, because they just point to
1448 : // nsHostRecords that also pointed to by entries |mDB|, and measured when
1449 : // |mDB| is measured.
1450 :
1451 0 : return n;
1452 : }
1453 :
1454 : void
1455 1 : nsHostResolver::ThreadFunc(void *arg)
1456 : {
1457 : char stackTop;
1458 :
1459 1 : LOG(("DNS lookup thread - starting execution.\n"));
1460 :
1461 1 : static nsThreadPoolNaming naming;
1462 1 : nsCString name = naming.GetNextThreadName("DNS Resolver");
1463 :
1464 1 : NS_SetCurrentThreadName(name.BeginReading());
1465 1 : profiler_register_thread(name.BeginReading(), &stackTop);
1466 :
1467 : #if defined(RES_RETRY_ON_FAILURE)
1468 1 : nsResState rs;
1469 : #endif
1470 1 : nsHostResolver *resolver = (nsHostResolver *)arg;
1471 1 : nsHostRecord *rec = nullptr;
1472 1 : AddrInfo *ai = nullptr;
1473 :
1474 3 : while (rec || resolver->GetHostToLookup(&rec)) {
1475 1 : LOG(("DNS lookup thread - Calling getaddrinfo for host [%s%s%s].\n",
1476 : LOG_HOST(rec->host, rec->netInterface)));
1477 :
1478 1 : TimeStamp startTime = TimeStamp::Now();
1479 : #if TTL_AVAILABLE
1480 : bool getTtl = rec->mGetTtl;
1481 : #else
1482 1 : bool getTtl = false;
1483 : #endif
1484 :
1485 1 : nsresult status = GetAddrInfo(rec->host, rec->af, rec->flags, rec->netInterface,
1486 1 : &ai, getTtl);
1487 : #if defined(RES_RETRY_ON_FAILURE)
1488 1 : if (NS_FAILED(status) && rs.Reset()) {
1489 0 : status = GetAddrInfo(rec->host, rec->af, rec->flags, rec->netInterface, &ai,
1490 0 : getTtl);
1491 : }
1492 : #endif
1493 :
1494 : { // obtain lock to check shutdown and manage inter-module telemetry
1495 2 : MutexAutoLock lock(resolver->mLock);
1496 :
1497 1 : if (!resolver->mShutdown) {
1498 1 : TimeDuration elapsed = TimeStamp::Now() - startTime;
1499 1 : uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds());
1500 :
1501 1 : if (NS_SUCCEEDED(status)) {
1502 : Telemetry::HistogramID histogramID;
1503 1 : if (!rec->addr_info_gencnt) {
1504 : // Time for initial lookup.
1505 1 : histogramID = Telemetry::DNS_LOOKUP_TIME;
1506 0 : } else if (!getTtl) {
1507 : // Time for renewal; categorized by expiration strategy.
1508 0 : histogramID = Telemetry::DNS_RENEWAL_TIME;
1509 : } else {
1510 : // Time to get TTL; categorized by expiration strategy.
1511 0 : histogramID = Telemetry::DNS_RENEWAL_TIME_FOR_TTL;
1512 : }
1513 1 : Telemetry::Accumulate(histogramID, millis);
1514 : } else {
1515 0 : Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis);
1516 : }
1517 : }
1518 : }
1519 :
1520 : // OnLookupComplete may release "rec", long before we lose it.
1521 1 : LOG(("DNS lookup thread - lookup completed for host [%s%s%s]: %s.\n",
1522 : LOG_HOST(rec->host, rec->netInterface),
1523 : ai ? "success" : "failure: unknown host"));
1524 :
1525 1 : if (LOOKUP_RESOLVEAGAIN == resolver->OnLookupComplete(rec, status, ai)) {
1526 : // leave 'rec' assigned and loop to make a renewed host resolve
1527 0 : LOG(("DNS lookup thread - Re-resolving host [%s%s%s].\n",
1528 : LOG_HOST(rec->host, rec->netInterface)));
1529 : } else {
1530 1 : rec = nullptr;
1531 : }
1532 : }
1533 0 : resolver->mThreadCount--;
1534 0 : NS_RELEASE(resolver);
1535 0 : LOG(("DNS lookup thread - queue empty, thread finished.\n"));
1536 :
1537 0 : profiler_unregister_thread();
1538 0 : }
1539 :
1540 : nsresult
1541 2 : nsHostResolver::Create(uint32_t maxCacheEntries,
1542 : uint32_t defaultCacheEntryLifetime,
1543 : uint32_t defaultGracePeriod,
1544 : nsHostResolver **result)
1545 : {
1546 : auto *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime,
1547 2 : defaultGracePeriod);
1548 2 : NS_ADDREF(res);
1549 :
1550 2 : nsresult rv = res->Init();
1551 2 : if (NS_FAILED(rv))
1552 0 : NS_RELEASE(res);
1553 :
1554 2 : *result = res;
1555 2 : return rv;
1556 : }
1557 :
1558 : void
1559 0 : nsHostResolver::GetDNSCacheEntries(nsTArray<DNSCacheEntries> *args)
1560 : {
1561 0 : for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
1562 : // We don't pay attention to address literals, only resolved domains.
1563 : // Also require a host.
1564 0 : auto entry = static_cast<nsHostDBEnt*>(iter.Get());
1565 0 : nsHostRecord* rec = entry->rec;
1566 0 : MOZ_ASSERT(rec, "rec should never be null here!");
1567 0 : if (!rec || !rec->addr_info || !rec->host) {
1568 0 : continue;
1569 : }
1570 :
1571 0 : DNSCacheEntries info;
1572 0 : info.hostname = rec->host;
1573 0 : info.family = rec->af;
1574 0 : info.netInterface = rec->netInterface;
1575 0 : info.expiration =
1576 0 : (int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds();
1577 0 : if (info.expiration <= 0) {
1578 : // We only need valid DNS cache entries
1579 0 : continue;
1580 : }
1581 :
1582 : {
1583 0 : MutexAutoLock lock(rec->addr_info_lock);
1584 :
1585 0 : NetAddr *addr = nullptr;
1586 0 : NetAddrElement *addrElement = rec->addr_info->mAddresses.getFirst();
1587 0 : if (addrElement) {
1588 0 : addr = &addrElement->mAddress;
1589 : }
1590 0 : while (addr) {
1591 : char buf[kIPv6CStrBufSize];
1592 0 : if (NetAddrToString(addr, buf, sizeof(buf))) {
1593 0 : info.hostaddr.AppendElement(buf);
1594 : }
1595 0 : addr = nullptr;
1596 0 : addrElement = addrElement->getNext();
1597 0 : if (addrElement) {
1598 0 : addr = &addrElement->mAddress;
1599 : }
1600 : }
1601 : }
1602 :
1603 0 : args->AppendElement(info);
1604 : }
1605 0 : }
|