1 : /* vim:set ts=4 sw=4 sts=4 et cin: */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla.
16 : *
17 : * The Initial Developer of the Original Code is IBM Corporation.
18 : * Portions created by IBM Corporation are Copyright (C) 2003
19 : * IBM Corporation. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * IBM Corp.
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #if defined(MOZ_LOGGING)
39 : #define FORCE_PR_LOG
40 : #endif
41 :
42 : #if defined(HAVE_RES_NINIT)
43 : #include <sys/types.h>
44 : #include <netinet/in.h>
45 : #include <arpa/inet.h>
46 : #include <arpa/nameser.h>
47 : #include <resolv.h>
48 : #define RES_RETRY_ON_FAILURE
49 : #endif
50 :
51 : #include <stdlib.h>
52 : #include "nsHostResolver.h"
53 : #include "nsNetError.h"
54 : #include "nsISupportsBase.h"
55 : #include "nsISupportsUtils.h"
56 : #include "nsAutoPtr.h"
57 : #include "pratom.h"
58 : #include "prthread.h"
59 : #include "prerror.h"
60 : #include "prtime.h"
61 : #include "prlong.h"
62 : #include "prlog.h"
63 : #include "pldhash.h"
64 : #include "plstr.h"
65 : #include "nsURLHelper.h"
66 :
67 : #include "mozilla/HashFunctions.h"
68 : #include "mozilla/FunctionTimer.h"
69 : #include "mozilla/TimeStamp.h"
70 : #include "mozilla/Telemetry.h"
71 :
72 : using namespace mozilla;
73 :
74 : //----------------------------------------------------------------------------
75 :
76 : // Use a persistent thread pool in order to avoid spinning up new threads all the time.
77 : // In particular, thread creation results in a res_init() call from libc which is
78 : // quite expensive.
79 : //
80 : // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
81 : // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
82 : // currently in the pool a new thread is created for high priority requests. If
83 : // the new request is at a lower priority a new thread will only be created if
84 : // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
85 : // created or an idle thread located for the request it is queued.
86 : //
87 : // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
88 : // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
89 : // timeout period.
90 :
91 : #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
92 : #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
93 : #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
94 :
95 : PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS);
96 :
97 : //----------------------------------------------------------------------------
98 :
99 : #if defined(PR_LOGGING)
100 : static PRLogModuleInfo *gHostResolverLog = nsnull;
101 : #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args)
102 : #else
103 : #define LOG(args)
104 : #endif
105 :
106 : //----------------------------------------------------------------------------
107 :
108 : static inline void
109 7982 : MoveCList(PRCList &from, PRCList &to)
110 : {
111 7982 : if (!PR_CLIST_IS_EMPTY(&from)) {
112 2351 : to.next = from.next;
113 2351 : to.prev = from.prev;
114 2351 : to.next->prev = &to;
115 2351 : to.prev->next = &to;
116 2351 : PR_INIT_CLIST(&from);
117 : }
118 7982 : }
119 :
120 : static PRUint32
121 11309 : NowInMinutes()
122 : {
123 11309 : PRTime now = PR_Now(), minutes, factor;
124 11309 : LL_I2L(factor, 60 * PR_USEC_PER_SEC);
125 11309 : LL_DIV(minutes, now, factor);
126 : PRUint32 result;
127 11309 : LL_L2UI(result, minutes);
128 11309 : return result;
129 : }
130 :
131 : //----------------------------------------------------------------------------
132 :
133 : #if defined(RES_RETRY_ON_FAILURE)
134 :
135 : // this class represents the resolver state for a given thread. if we
136 : // encounter a lookup failure, then we can invoke the Reset method on an
137 : // instance of this class to reset the resolver (in case /etc/resolv.conf
138 : // for example changed). this is mainly an issue on GNU systems since glibc
139 : // only reads in /etc/resolv.conf once per thread. it may be an issue on
140 : // other systems as well.
141 :
142 : class nsResState
143 : {
144 : public:
145 273 : nsResState()
146 : // initialize mLastReset to the time when this object
147 : // is created. this means that a reset will not occur
148 : // if a thread is too young. the alternative would be
149 : // to initialize this to the beginning of time, so that
150 : // the first failure would cause a reset, but since the
151 : // thread would have just started up, it likely would
152 : // already have current /etc/resolv.conf info.
153 273 : : mLastReset(PR_IntervalNow())
154 : {
155 273 : }
156 :
157 129 : bool Reset()
158 : {
159 : // reset no more than once per second
160 129 : if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
161 124 : return false;
162 :
163 5 : LOG(("calling res_ninit\n"));
164 :
165 5 : mLastReset = PR_IntervalNow();
166 5 : return (res_ninit(&_res) == 0);
167 : }
168 :
169 : private:
170 : PRIntervalTime mLastReset;
171 : };
172 :
173 : #endif // RES_RETRY_ON_FAILURE
174 :
175 : //----------------------------------------------------------------------------
176 :
177 : // this macro filters out any flags that are not used when constructing the
178 : // host key. the significant flags are those that would affect the resulting
179 : // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
180 : #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
181 :
182 301 : nsHostRecord::nsHostRecord(const nsHostKey *key)
183 : : _refc(1)
184 : , addr_info_lock("nsHostRecord.addr_info_lock")
185 : , addr_info_gencnt(0)
186 : , addr_info(nsnull)
187 : , addr(nsnull)
188 : , negative(false)
189 : , resolving(false)
190 : , onQueue(false)
191 301 : , usingAnyThread(false)
192 : {
193 301 : host = ((char *) this) + sizeof(nsHostRecord);
194 301 : memcpy((char *) host, key->host, strlen(key->host) + 1);
195 301 : flags = key->flags;
196 301 : af = key->af;
197 :
198 301 : NS_LOG_ADDREF(this, 1, "nsHostRecord", sizeof(nsHostRecord));
199 301 : expiration = NowInMinutes();
200 :
201 301 : PR_INIT_CLIST(this);
202 301 : PR_INIT_CLIST(&callbacks);
203 301 : }
204 :
205 : nsresult
206 301 : nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
207 : {
208 301 : size_t hostLen = strlen(key->host) + 1;
209 301 : size_t size = hostLen + sizeof(nsHostRecord);
210 :
211 : // Use placement new to create the object with room for the hostname
212 : // allocated after it.
213 301 : void *place = ::operator new(size);
214 301 : *result = new(place) nsHostRecord(key);
215 301 : return NS_OK;
216 : }
217 :
218 602 : nsHostRecord::~nsHostRecord()
219 : {
220 301 : if (addr)
221 24 : free(addr);
222 301 : }
223 :
224 : bool
225 2919 : nsHostRecord::Blacklisted(PRNetAddr *aQuery)
226 : {
227 : // must call locked
228 2919 : LOG(("nsHostRecord::Blacklisted() %p %s\n", this, host));
229 :
230 : // skip the string conversion for the common case of no blacklist
231 2919 : if (!mBlacklistedItems.Length())
232 2869 : return false;
233 :
234 : char buf[64];
235 50 : if (PR_NetAddrToString(aQuery, buf, sizeof(buf)) != PR_SUCCESS)
236 0 : return false;
237 :
238 100 : nsDependentCString strQuery(buf);
239 50 : LOG(("nsHostRecord::Blacklisted() query %s\n", buf));
240 :
241 50 : for (PRUint32 i = 0; i < mBlacklistedItems.Length(); i++)
242 50 : if (mBlacklistedItems.ElementAt(i).Equals(strQuery)) {
243 50 : LOG(("nsHostRecord::Blacklisted() %s blacklist confirmed\n", buf));
244 50 : return true;
245 : }
246 :
247 0 : return false;
248 : }
249 :
250 : void
251 68 : nsHostRecord::ReportUnusable(PRNetAddr *aAddress)
252 : {
253 : // must call locked
254 68 : LOG(("nsHostRecord::ReportUnusable() %p %s\n", this, host));
255 :
256 : char buf[64];
257 68 : if (PR_NetAddrToString(aAddress, buf, sizeof(buf)) == PR_SUCCESS) {
258 68 : LOG(("nsHostrecord::ReportUnusable addr %s\n",buf));
259 68 : mBlacklistedItems.AppendElement(nsCString(buf));
260 : }
261 68 : }
262 :
263 : void
264 50 : nsHostRecord::ResetBlacklist()
265 : {
266 : // must call locked
267 50 : LOG(("nsHostRecord::ResetBlacklist() %p %s\n", this, host));
268 50 : mBlacklistedItems.Clear();
269 50 : }
270 :
271 : //----------------------------------------------------------------------------
272 :
273 : struct nsHostDBEnt : PLDHashEntryHdr
274 : {
275 : nsHostRecord *rec;
276 : };
277 :
278 : static PLDHashNumber
279 6493 : HostDB_HashKey(PLDHashTable *table, const void *key)
280 : {
281 6493 : const nsHostKey *hk = static_cast<const nsHostKey *>(key);
282 6493 : return AddToHash(HashString(hk->host), RES_KEY_FLAGS(hk->flags), hk->af);
283 : }
284 :
285 : static bool
286 6192 : HostDB_MatchEntry(PLDHashTable *table,
287 : const PLDHashEntryHdr *entry,
288 : const void *key)
289 : {
290 6192 : const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
291 6192 : const nsHostKey *hk = static_cast<const nsHostKey *>(key);
292 :
293 6192 : return !strcmp(he->rec->host, hk->host) &&
294 : RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
295 6192 : he->rec->af == hk->af;
296 : }
297 :
298 : static void
299 0 : HostDB_MoveEntry(PLDHashTable *table,
300 : const PLDHashEntryHdr *from,
301 : PLDHashEntryHdr *to)
302 : {
303 : static_cast<nsHostDBEnt *>(to)->rec =
304 0 : static_cast<const nsHostDBEnt *>(from)->rec;
305 0 : }
306 :
307 : static void
308 301 : HostDB_ClearEntry(PLDHashTable *table,
309 : PLDHashEntryHdr *entry)
310 : {
311 301 : LOG(("evicting record\n"));
312 301 : nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
313 : #if defined(DEBUG) && defined(PR_LOGGING)
314 301 : if (!he->rec->addr_info)
315 33 : LOG(("%s: => no addr_info\n", he->rec->host));
316 : else {
317 268 : PRInt32 now = (PRInt32) NowInMinutes();
318 268 : PRInt32 diff = (PRInt32) he->rec->expiration - now;
319 268 : LOG(("%s: exp=%d => %s\n",
320 : he->rec->host, diff,
321 : PR_GetCanonNameFromAddrInfo(he->rec->addr_info)));
322 268 : void *iter = nsnull;
323 : PRNetAddr addr;
324 : char buf[64];
325 268 : for (;;) {
326 536 : iter = PR_EnumerateAddrInfo(iter, he->rec->addr_info, 0, &addr);
327 536 : if (!iter)
328 268 : break;
329 268 : PR_NetAddrToString(&addr, buf, sizeof(buf));
330 268 : LOG((" %s\n", buf));
331 : }
332 : }
333 : #endif
334 301 : NS_RELEASE(he->rec);
335 301 : }
336 :
337 : static bool
338 301 : HostDB_InitEntry(PLDHashTable *table,
339 : PLDHashEntryHdr *entry,
340 : const void *key)
341 : {
342 301 : nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
343 301 : nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
344 301 : return true;
345 : }
346 :
347 : static PLDHashTableOps gHostDB_ops =
348 : {
349 : PL_DHashAllocTable,
350 : PL_DHashFreeTable,
351 : HostDB_HashKey,
352 : HostDB_MatchEntry,
353 : HostDB_MoveEntry,
354 : HostDB_ClearEntry,
355 : PL_DHashFinalizeStub,
356 : HostDB_InitEntry,
357 : };
358 :
359 : static PLDHashOperator
360 301 : HostDB_RemoveEntry(PLDHashTable *table,
361 : PLDHashEntryHdr *hdr,
362 : PRUint32 number,
363 : void *arg)
364 : {
365 301 : return PL_DHASH_REMOVE;
366 : }
367 :
368 : //----------------------------------------------------------------------------
369 :
370 1444 : nsHostResolver::nsHostResolver(PRUint32 maxCacheEntries,
371 : PRUint32 maxCacheLifetime,
372 : PRUint32 lifetimeGracePeriod)
373 : : mMaxCacheEntries(maxCacheEntries)
374 : , mMaxCacheLifetime(maxCacheLifetime)
375 : , mGracePeriod(lifetimeGracePeriod)
376 : , mLock("nsHostResolver.mLock")
377 : , mIdleThreadCV(mLock, "nsHostResolver.mIdleThreadCV")
378 : , mNumIdleThreads(0)
379 : , mThreadCount(0)
380 : , mActiveAnyThreadCount(0)
381 : , mEvictionQSize(0)
382 : , mPendingCount(0)
383 1444 : , mShutdown(true)
384 : {
385 1444 : mCreationTime = PR_Now();
386 1444 : PR_INIT_CLIST(&mHighQ);
387 1444 : PR_INIT_CLIST(&mMediumQ);
388 1444 : PR_INIT_CLIST(&mLowQ);
389 1444 : PR_INIT_CLIST(&mEvictionQ);
390 :
391 1444 : mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
392 1444 : mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
393 1444 : }
394 :
395 2888 : nsHostResolver::~nsHostResolver()
396 : {
397 1444 : PL_DHashTableFinish(&mDB);
398 1444 : }
399 :
400 : nsresult
401 1444 : nsHostResolver::Init()
402 : {
403 : NS_TIME_FUNCTION;
404 :
405 1444 : PL_DHashTableInit(&mDB, &gHostDB_ops, nsnull, sizeof(nsHostDBEnt), 0);
406 :
407 1444 : mShutdown = false;
408 :
409 : #if defined(HAVE_RES_NINIT)
410 : // We want to make sure the system is using the correct resolver settings,
411 : // so we force it to reload those settings whenever we startup a subsequent
412 : // nsHostResolver instance. We assume that there is no reason to do this
413 : // for the first nsHostResolver instance since that is usually created
414 : // during application startup.
415 : static int initCount = 0;
416 1444 : if (initCount++ > 0) {
417 25 : LOG(("calling res_ninit\n"));
418 25 : res_ninit(&_res);
419 : }
420 : #endif
421 1444 : return NS_OK;
422 : }
423 :
424 : void
425 4332 : nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
426 : {
427 : // loop through pending queue, erroring out pending lookups.
428 4332 : if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
429 0 : PRCList *node = aPendingQ->next;
430 0 : while (node != aPendingQ) {
431 0 : nsHostRecord *rec = static_cast<nsHostRecord *>(node);
432 0 : node = node->next;
433 0 : OnLookupComplete(rec, NS_ERROR_ABORT, nsnull);
434 : }
435 : }
436 4332 : }
437 :
438 : void
439 1444 : nsHostResolver::Shutdown()
440 : {
441 1444 : LOG(("nsHostResolver::Shutdown\n"));
442 :
443 : PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
444 1444 : PR_INIT_CLIST(&pendingQHigh);
445 1444 : PR_INIT_CLIST(&pendingQMed);
446 1444 : PR_INIT_CLIST(&pendingQLow);
447 1444 : PR_INIT_CLIST(&evictionQ);
448 :
449 : {
450 2888 : MutexAutoLock lock(mLock);
451 :
452 1444 : mShutdown = true;
453 :
454 1444 : MoveCList(mHighQ, pendingQHigh);
455 1444 : MoveCList(mMediumQ, pendingQMed);
456 1444 : MoveCList(mLowQ, pendingQLow);
457 1444 : MoveCList(mEvictionQ, evictionQ);
458 1444 : mEvictionQSize = 0;
459 1444 : mPendingCount = 0;
460 :
461 1444 : if (mNumIdleThreads)
462 267 : mIdleThreadCV.NotifyAll();
463 :
464 : // empty host database
465 1444 : PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nsnull);
466 : }
467 :
468 1444 : ClearPendingQueue(&pendingQHigh);
469 1444 : ClearPendingQueue(&pendingQMed);
470 1444 : ClearPendingQueue(&pendingQLow);
471 :
472 1444 : if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
473 264 : PRCList *node = evictionQ.next;
474 796 : while (node != &evictionQ) {
475 268 : nsHostRecord *rec = static_cast<nsHostRecord *>(node);
476 268 : node = node->next;
477 268 : NS_RELEASE(rec);
478 : }
479 : }
480 :
481 : #ifdef NS_BUILD_REFCNT_LOGGING
482 :
483 : // Logically join the outstanding worker threads with a timeout.
484 : // Use this approach instead of PR_JoinThread() because that does
485 : // not allow a timeout which may be necessary for a semi-responsive
486 : // shutdown if the thread is blocked on a very slow DNS resolution.
487 : // mThreadCount is read outside of mLock, but the worst case
488 : // scenario for that race is one extra 25ms sleep.
489 :
490 1444 : PRIntervalTime delay = PR_MillisecondsToInterval(25);
491 1444 : PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
492 3219 : while (mThreadCount && PR_IntervalNow() < stopTime)
493 331 : PR_Sleep(delay);
494 : #endif
495 1444 : }
496 :
497 : static inline bool
498 2252 : IsHighPriority(PRUint16 flags)
499 : {
500 2252 : return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM));
501 : }
502 :
503 : static inline bool
504 23 : IsMediumPriority(PRUint16 flags)
505 : {
506 23 : return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
507 : }
508 :
509 : static inline bool
510 0 : IsLowPriority(PRUint16 flags)
511 : {
512 0 : return flags & nsHostResolver::RES_PRIORITY_LOW;
513 : }
514 :
515 : void
516 0 : nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
517 : {
518 0 : NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
519 :
520 0 : PR_REMOVE_LINK(aRec);
521 0 : PR_APPEND_LINK(aRec, &aDestQ);
522 0 : }
523 :
524 : nsresult
525 6493 : nsHostResolver::ResolveHost(const char *host,
526 : PRUint16 flags,
527 : PRUint16 af,
528 : nsResolveHostCallback *callback)
529 : {
530 6493 : NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
531 :
532 6493 : LOG(("nsHostResolver::ResolveHost [host=%s]\n", host));
533 :
534 : // ensure that we are working with a valid hostname before proceeding. see
535 : // bug 304904 for details.
536 6493 : if (!net_IsValidHostName(nsDependentCString(host)))
537 0 : return NS_ERROR_UNKNOWN_HOST;
538 :
539 : // if result is set inside the lock, then we need to issue the
540 : // callback before returning.
541 12986 : nsRefPtr<nsHostRecord> result;
542 6493 : nsresult status = NS_OK, rv = NS_OK;
543 : {
544 12986 : MutexAutoLock lock(mLock);
545 :
546 6493 : if (mShutdown)
547 0 : rv = NS_ERROR_NOT_INITIALIZED;
548 : else {
549 : PRNetAddr tempAddr;
550 :
551 : // unfortunately, PR_StringToNetAddr does not properly initialize
552 : // the output buffer in the case of IPv6 input. see bug 223145.
553 6493 : memset(&tempAddr, 0, sizeof(PRNetAddr));
554 :
555 : // check to see if there is already an entry for this |host|
556 : // in the hash table. if so, then check to see if we can't
557 : // just reuse the lookup result. otherwise, if there are
558 : // any pending callbacks, then add to pending callbacks queue,
559 : // and return. otherwise, add ourselves as first pending
560 : // callback, and proceed to do the lookup.
561 :
562 6493 : nsHostKey key = { host, flags, af };
563 : nsHostDBEnt *he = static_cast<nsHostDBEnt *>
564 6493 : (PL_DHashTableOperate(&mDB, &key, PL_DHASH_ADD));
565 :
566 : // if the record is null, then HostDB_InitEntry failed.
567 6493 : if (!he || !he->rec)
568 0 : rv = NS_ERROR_OUT_OF_MEMORY;
569 : // do we have a cached result that we can reuse?
570 15330 : else if (!(flags & RES_BYPASS_CACHE) &&
571 4570 : he->rec->HasResult() &&
572 4267 : NowInMinutes() <= he->rec->expiration + mGracePeriod) {
573 :
574 4267 : LOG(("using cached record\n"));
575 : // put reference to host record on stack...
576 4267 : result = he->rec;
577 4267 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD, METHOD_HIT);
578 :
579 : // For entries that are in the grace period, or all cached
580 : // negative entries, use the cache but start a new lookup in
581 : // the background
582 4543 : if (((NowInMinutes() > he->rec->expiration) ||
583 276 : he->rec->negative) && !he->rec->resolving) {
584 119 : LOG(("Using %s cache entry but starting async renewal",
585 : he->rec->negative ? "negative" :"positive"));
586 119 : IssueLookup(he->rec);
587 :
588 119 : if (!he->rec->negative) {
589 : // negative entries are constantly being refreshed, only
590 : // track positive grace period induced renewals
591 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
592 0 : METHOD_RENEWAL);
593 : }
594 : }
595 :
596 4267 : if (he->rec->negative) {
597 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
598 276 : METHOD_NEGATIVE_HIT);
599 276 : status = NS_ERROR_UNKNOWN_HOST;
600 : }
601 : }
602 : // if the host name is an IP address literal and has been parsed,
603 : // go ahead and use it.
604 2226 : else if (he->rec->addr) {
605 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
606 81 : METHOD_LITERAL);
607 81 : result = he->rec;
608 : }
609 : // try parsing the host name as an IP address literal to short
610 : // circuit full host resolution. (this is necessary on some
611 : // platforms like Win9x. see bug 219376 for more details.)
612 2145 : else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
613 : // ok, just copy the result into the host record, and be done
614 : // with it! ;-)
615 24 : he->rec->addr = (PRNetAddr *) malloc(sizeof(PRNetAddr));
616 24 : if (!he->rec->addr)
617 0 : status = NS_ERROR_OUT_OF_MEMORY;
618 : else
619 24 : memcpy(he->rec->addr, &tempAddr, sizeof(PRNetAddr));
620 : // put reference to host record on stack...
621 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
622 24 : METHOD_LITERAL);
623 24 : result = he->rec;
624 : }
625 2121 : else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
626 0 : !IsHighPriority(flags) &&
627 0 : !he->rec->resolving) {
628 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
629 0 : METHOD_OVERFLOW);
630 : // This is a lower priority request and we are swamped, so refuse it.
631 0 : rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
632 : }
633 : // otherwise, hit the resolver...
634 : else {
635 : // Add callback to the list of pending callbacks.
636 2121 : PR_APPEND_LINK(callback, &he->rec->callbacks);
637 :
638 2121 : if (!he->rec->resolving) {
639 2087 : he->rec->flags = flags;
640 2087 : rv = IssueLookup(he->rec);
641 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
642 2087 : METHOD_NETWORK_FIRST);
643 2087 : if (NS_FAILED(rv))
644 0 : PR_REMOVE_AND_INIT_LINK(callback);
645 : else
646 2087 : LOG(("dns lookup blocking pending getaddrinfo query"));
647 : }
648 34 : else if (he->rec->onQueue) {
649 : Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD,
650 23 : METHOD_NETWORK_SHARED);
651 :
652 : // Consider the case where we are on a pending queue of
653 : // lower priority than the request is being made at.
654 : // In that case we should upgrade to the higher queue.
655 :
656 23 : if (IsHighPriority(flags) && !IsHighPriority(he->rec->flags)) {
657 : // Move from (low|med) to high.
658 0 : MoveQueue(he->rec, mHighQ);
659 0 : he->rec->flags = flags;
660 0 : ConditionallyCreateThread(he->rec);
661 23 : } else if (IsMediumPriority(flags) && IsLowPriority(he->rec->flags)) {
662 : // Move from low to med.
663 0 : MoveQueue(he->rec, mMediumQ);
664 0 : he->rec->flags = flags;
665 0 : mIdleThreadCV.Notify();
666 : }
667 : }
668 : }
669 : }
670 : }
671 6493 : if (result)
672 4372 : callback->OnLookupComplete(this, result, status);
673 6493 : return rv;
674 : }
675 :
676 : void
677 0 : nsHostResolver::DetachCallback(const char *host,
678 : PRUint16 flags,
679 : PRUint16 af,
680 : nsResolveHostCallback *callback,
681 : nsresult status)
682 : {
683 0 : nsRefPtr<nsHostRecord> rec;
684 : {
685 0 : MutexAutoLock lock(mLock);
686 :
687 0 : nsHostKey key = { host, flags, af };
688 : nsHostDBEnt *he = static_cast<nsHostDBEnt *>
689 0 : (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
690 0 : if (he && he->rec) {
691 : // walk list looking for |callback|... we cannot assume
692 : // that it will be there!
693 0 : PRCList *node = he->rec->callbacks.next;
694 0 : while (node != &he->rec->callbacks) {
695 0 : if (static_cast<nsResolveHostCallback *>(node) == callback) {
696 0 : PR_REMOVE_LINK(callback);
697 0 : rec = he->rec;
698 0 : break;
699 : }
700 0 : node = node->next;
701 : }
702 : }
703 : }
704 :
705 : // complete callback with the given status code; this would only be done if
706 : // the record was in the process of being resolved.
707 0 : if (rec)
708 0 : callback->OnLookupComplete(this, rec, status);
709 0 : }
710 :
711 : nsresult
712 2206 : nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
713 : {
714 2206 : if (mNumIdleThreads) {
715 : // wake up idle thread to process this lookup
716 1933 : mIdleThreadCV.Notify();
717 : }
718 273 : else if ((mThreadCount < HighThreadThreshold) ||
719 0 : (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
720 : // dispatch new worker thread
721 273 : NS_ADDREF_THIS(); // owning reference passed to thread
722 :
723 273 : mThreadCount++;
724 : PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
725 : ThreadFunc,
726 : this,
727 : PR_PRIORITY_NORMAL,
728 : PR_GLOBAL_THREAD,
729 : PR_UNJOINABLE_THREAD,
730 273 : 0);
731 273 : if (!thr) {
732 0 : mThreadCount--;
733 0 : NS_RELEASE_THIS();
734 0 : return NS_ERROR_OUT_OF_MEMORY;
735 : }
736 : }
737 : #if defined(PR_LOGGING)
738 : else
739 0 : LOG(("lookup waiting for thread - %s ...\n", rec->host));
740 : #endif
741 2206 : return NS_OK;
742 : }
743 :
744 : nsresult
745 2206 : nsHostResolver::IssueLookup(nsHostRecord *rec)
746 : {
747 2206 : nsresult rv = NS_OK;
748 2206 : NS_ASSERTION(!rec->resolving, "record is already being resolved");
749 :
750 : // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
751 : // If rec is on mEvictionQ, then we can just move the owning
752 : // reference over to the new active queue.
753 2206 : if (rec->next == rec)
754 397 : NS_ADDREF(rec);
755 : else {
756 1809 : PR_REMOVE_LINK(rec);
757 1809 : mEvictionQSize--;
758 : }
759 :
760 2206 : if (IsHighPriority(rec->flags))
761 2206 : PR_APPEND_LINK(rec, &mHighQ);
762 0 : else if (IsMediumPriority(rec->flags))
763 0 : PR_APPEND_LINK(rec, &mMediumQ);
764 : else
765 0 : PR_APPEND_LINK(rec, &mLowQ);
766 2206 : mPendingCount++;
767 :
768 2206 : rec->resolving = true;
769 2206 : rec->onQueue = true;
770 :
771 2206 : rv = ConditionallyCreateThread(rec);
772 :
773 2206 : LOG (("DNS Thread Counters: total=%d any-live=%d idle=%d pending=%d\n",
774 : mThreadCount,
775 : mActiveAnyThreadCount,
776 : mNumIdleThreads,
777 : mPendingCount));
778 :
779 2206 : return rv;
780 : }
781 :
782 : void
783 2206 : nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
784 : {
785 2206 : *aResult = static_cast<nsHostRecord *>(aQ.next);
786 2206 : PR_REMOVE_AND_INIT_LINK(*aResult);
787 2206 : mPendingCount--;
788 2206 : (*aResult)->onQueue = false;
789 2206 : }
790 :
791 : bool
792 2479 : nsHostResolver::GetHostToLookup(nsHostRecord **result)
793 : {
794 2479 : bool timedOut = false;
795 : PRIntervalTime epoch, now, timeout;
796 :
797 4958 : MutexAutoLock lock(mLock);
798 :
799 2479 : timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
800 2479 : epoch = PR_IntervalNow();
801 :
802 7163 : while (!mShutdown) {
803 : // remove next record from Q; hand over owning reference. Check high, then med, then low
804 :
805 4411 : if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
806 2206 : DeQueue (mHighQ, result);
807 2206 : return true;
808 : }
809 :
810 2205 : if (mActiveAnyThreadCount < HighThreadThreshold) {
811 2205 : if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
812 0 : DeQueue (mMediumQ, result);
813 0 : mActiveAnyThreadCount++;
814 0 : (*result)->usingAnyThread = true;
815 0 : return true;
816 : }
817 :
818 2205 : if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
819 0 : DeQueue (mLowQ, result);
820 0 : mActiveAnyThreadCount++;
821 0 : (*result)->usingAnyThread = true;
822 0 : return true;
823 : }
824 : }
825 :
826 : // Determining timeout is racy, so allow one cycle through checking the queues
827 : // before exiting.
828 2205 : if (timedOut)
829 0 : break;
830 :
831 : // wait for one or more of the following to occur:
832 : // (1) the pending queue has a host record to process
833 : // (2) the shutdown flag has been set
834 : // (3) the thread has been idle for too long
835 :
836 2205 : mNumIdleThreads++;
837 2205 : mIdleThreadCV.Wait(timeout);
838 2205 : mNumIdleThreads--;
839 :
840 2205 : now = PR_IntervalNow();
841 :
842 2205 : if ((PRIntervalTime)(now - epoch) >= timeout)
843 0 : timedOut = true;
844 : else {
845 : // It is possible that PR_WaitCondVar() was interrupted and returned early,
846 : // in which case we will loop back and re-enter it. In that case we want to
847 : // do so with the new timeout reduced to reflect time already spent waiting.
848 2205 : timeout -= (PRIntervalTime)(now - epoch);
849 2205 : epoch = now;
850 : }
851 : }
852 :
853 : // tell thread to exit...
854 273 : mThreadCount--;
855 273 : return false;
856 : }
857 :
858 : void
859 2206 : nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, PRAddrInfo *result)
860 : {
861 : // get the list of pending callbacks for this lookup, and notify
862 : // them that the lookup is complete.
863 : PRCList cbs;
864 2206 : PR_INIT_CLIST(&cbs);
865 : {
866 4412 : MutexAutoLock lock(mLock);
867 :
868 : // grab list of callbacks to notify
869 2206 : MoveCList(rec->callbacks, cbs);
870 :
871 : // update record fields. We might have a rec->addr_info already if a
872 : // previous lookup result expired and we're reresolving it..
873 : PRAddrInfo *old_addr_info;
874 : {
875 4412 : MutexAutoLock lock(rec->addr_info_lock);
876 2206 : old_addr_info = rec->addr_info;
877 2206 : rec->addr_info = result;
878 2206 : rec->addr_info_gencnt++;
879 : }
880 2206 : if (old_addr_info)
881 1809 : PR_FreeAddrInfo(old_addr_info);
882 2206 : rec->expiration = NowInMinutes();
883 2206 : if (result) {
884 2077 : rec->expiration += mMaxCacheLifetime;
885 2077 : rec->negative = false;
886 : }
887 : else {
888 129 : rec->expiration += 1; /* one minute for negative cache */
889 129 : rec->negative = true;
890 : }
891 2206 : rec->resolving = false;
892 :
893 2206 : if (rec->usingAnyThread) {
894 0 : mActiveAnyThreadCount--;
895 0 : rec->usingAnyThread = false;
896 : }
897 :
898 2206 : if (rec->addr_info && !mShutdown) {
899 : // add to mEvictionQ
900 2077 : PR_APPEND_LINK(rec, &mEvictionQ);
901 2077 : NS_ADDREF(rec);
902 2077 : if (mEvictionQSize < mMaxCacheEntries)
903 2077 : mEvictionQSize++;
904 : else {
905 : // remove first element on mEvictionQ
906 : nsHostRecord *head =
907 0 : static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
908 0 : PR_REMOVE_AND_INIT_LINK(head);
909 0 : PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE);
910 :
911 0 : if (!head->negative) {
912 : // record the age of the entry upon eviction.
913 : PRUint32 age =
914 0 : NowInMinutes() - (head->expiration - mMaxCacheLifetime);
915 0 : Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, age);
916 : }
917 :
918 : // release reference to rec owned by mEvictionQ
919 0 : NS_RELEASE(head);
920 : }
921 : }
922 : }
923 :
924 2206 : if (!PR_CLIST_IS_EMPTY(&cbs)) {
925 2087 : PRCList *node = cbs.next;
926 6295 : while (node != &cbs) {
927 : nsResolveHostCallback *callback =
928 2121 : static_cast<nsResolveHostCallback *>(node);
929 2121 : node = node->next;
930 2121 : callback->OnLookupComplete(this, rec, status);
931 : // NOTE: callback must not be dereferenced after this point!!
932 : }
933 : }
934 :
935 2206 : NS_RELEASE(rec);
936 2206 : }
937 :
938 : void
939 0 : nsHostResolver::CancelAsyncRequest(const char *host,
940 : PRUint16 flags,
941 : PRUint16 af,
942 : nsIDNSListener *aListener,
943 : nsresult status)
944 :
945 : {
946 0 : MutexAutoLock lock(mLock);
947 :
948 : // Lookup the host record associated with host, flags & address family
949 0 : nsHostKey key = { host, flags, af };
950 : nsHostDBEnt *he = static_cast<nsHostDBEnt *>
951 0 : (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
952 0 : if (he && he->rec) {
953 0 : nsHostRecord* recPtr = NULL;
954 0 : PRCList *node = he->rec->callbacks.next;
955 : // Remove the first nsDNSAsyncRequest callback which matches the
956 : // supplied listener object
957 0 : while (node != &he->rec->callbacks) {
958 : nsResolveHostCallback *callback
959 0 : = static_cast<nsResolveHostCallback *>(node);
960 0 : if (callback && (callback->EqualsAsyncListener(aListener))) {
961 : // Remove from the list of callbacks
962 0 : PR_REMOVE_LINK(callback);
963 0 : recPtr = he->rec;
964 0 : callback->OnLookupComplete(this, recPtr, status);
965 0 : break;
966 : }
967 0 : node = node->next;
968 : }
969 :
970 : // If there are no more callbacks, remove the hash table entry
971 0 : if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
972 0 : PL_DHashTableOperate(&mDB, (nsHostKey *)recPtr, PL_DHASH_REMOVE);
973 : // If record is on a Queue, remove it and then deref it
974 0 : if (recPtr->next != recPtr) {
975 0 : PR_REMOVE_LINK(recPtr);
976 0 : NS_RELEASE(recPtr);
977 : }
978 : }
979 : }
980 0 : }
981 :
982 : //----------------------------------------------------------------------------
983 :
984 : void
985 273 : nsHostResolver::ThreadFunc(void *arg)
986 : {
987 273 : LOG(("nsHostResolver::ThreadFunc entering\n"));
988 : #if defined(RES_RETRY_ON_FAILURE)
989 273 : nsResState rs;
990 : #endif
991 273 : nsHostResolver *resolver = (nsHostResolver *)arg;
992 : nsHostRecord *rec;
993 : PRAddrInfo *ai;
994 2752 : while (resolver->GetHostToLookup(&rec)) {
995 2206 : LOG(("resolving %s ...\n", rec->host));
996 :
997 2206 : PRIntn flags = PR_AI_ADDRCONFIG;
998 2206 : if (!(rec->flags & RES_CANON_NAME))
999 2206 : flags |= PR_AI_NOCANONNAME;
1000 :
1001 2206 : TimeStamp startTime = TimeStamp::Now();
1002 :
1003 2206 : ai = PR_GetAddrInfoByName(rec->host, rec->af, flags);
1004 : #if defined(RES_RETRY_ON_FAILURE)
1005 2206 : if (!ai && rs.Reset())
1006 5 : ai = PR_GetAddrInfoByName(rec->host, rec->af, flags);
1007 : #endif
1008 :
1009 2206 : TimeDuration elapsed = TimeStamp::Now() - startTime;
1010 2206 : PRUint32 millis = static_cast<PRUint32>(elapsed.ToMilliseconds());
1011 :
1012 : // convert error code to nsresult.
1013 : nsresult status;
1014 2206 : if (ai) {
1015 2077 : status = NS_OK;
1016 :
1017 2077 : Telemetry::Accumulate(!rec->addr_info_gencnt ?
1018 : Telemetry::DNS_LOOKUP_TIME :
1019 : Telemetry::DNS_RENEWAL_TIME,
1020 2077 : millis);
1021 : }
1022 : else {
1023 129 : status = NS_ERROR_UNKNOWN_HOST;
1024 129 : Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis);
1025 : }
1026 :
1027 2206 : resolver->OnLookupComplete(rec, status, ai);
1028 2206 : LOG(("lookup complete for %s ...\n", rec->host));
1029 : }
1030 273 : NS_RELEASE(resolver);
1031 273 : LOG(("nsHostResolver::ThreadFunc exiting\n"));
1032 273 : }
1033 :
1034 : //----------------------------------------------------------------------------
1035 :
1036 : nsresult
1037 1444 : nsHostResolver::Create(PRUint32 maxCacheEntries,
1038 : PRUint32 maxCacheLifetime,
1039 : PRUint32 lifetimeGracePeriod,
1040 : nsHostResolver **result)
1041 : {
1042 : #if defined(PR_LOGGING)
1043 1444 : if (!gHostResolverLog)
1044 1419 : gHostResolverLog = PR_NewLogModule("nsHostResolver");
1045 : #endif
1046 :
1047 : nsHostResolver *res = new nsHostResolver(maxCacheEntries,
1048 : maxCacheLifetime,
1049 1444 : lifetimeGracePeriod);
1050 1444 : if (!res)
1051 0 : return NS_ERROR_OUT_OF_MEMORY;
1052 1444 : NS_ADDREF(res);
1053 :
1054 1444 : nsresult rv = res->Init();
1055 1444 : if (NS_FAILED(rv))
1056 0 : NS_RELEASE(res);
1057 :
1058 1444 : *result = res;
1059 1444 : return rv;
1060 : }
|