netwerk/dns/nsHostResolver.cpp

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

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

mercurial