Wed, 31 Dec 2014 06:55:46 +0100
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 | } |