From fa4f761593d2599348361abd8879b2ad1b4960d7 Mon Sep 17 00:00:00 2001 From: josullivan Date: Fri, 15 Aug 2014 17:25:06 -0700 Subject: [PATCH] Bug 820391: Use DnsQuery on Windows. r=sworkman From 21e22e494541d5e4c085a6ba84e1bc5b4c92330e Mon Sep 17 00:00:00 2001 --- netwerk/dns/DNS.cpp | 65 +++++++- netwerk/dns/DNS.h | 6 + netwerk/dns/GetAddrInfo.cpp | 362 +++++++++++++++++++++++++++++++++++++++++ netwerk/dns/GetAddrInfo.h | 65 ++++++++ netwerk/dns/moz.build | 1 + netwerk/dns/nsHostResolver.cpp | 320 ++++++++++++++++++++++++++---------- netwerk/dns/nsHostResolver.h | 43 ++++- 7 files changed, 766 insertions(+), 96 deletions(-) create mode 100644 netwerk/dns/GetAddrInfo.cpp create mode 100644 netwerk/dns/GetAddrInfo.h --- netwerk/dns/DNS.cpp | 65 +++++- netwerk/dns/DNS.h | 6 + netwerk/dns/GetAddrInfo.cpp | 362 +++++++++++++++++++++++++++++++++ netwerk/dns/GetAddrInfo.h | 65 ++++++ netwerk/dns/moz.build | 1 + netwerk/dns/nsHostResolver.cpp | 322 +++++++++++++++++++++-------- netwerk/dns/nsHostResolver.h | 43 +++- 7 files changed, 767 insertions(+), 97 deletions(-) create mode 100644 netwerk/dns/GetAddrInfo.cpp create mode 100644 netwerk/dns/GetAddrInfo.h diff --git a/netwerk/dns/DNS.cpp b/netwerk/dns/DNS.cpp index a2aaa534de28..c3b6efdd750b 100644 --- a/netwerk/dns/DNS.cpp +++ b/netwerk/dns/DNS.cpp @@ -255,6 +255,11 @@ NetAddrElement::NetAddrElement(const NetAddrElement& netAddr) mAddress = netAddr.mAddress; } +NetAddrElement::NetAddrElement(const NetAddr& aNetAddr) +{ + mAddress = aNetAddr; +} + NetAddrElement::~NetAddrElement() { } @@ -291,6 +296,21 @@ AddrInfo::~AddrInfo() moz_free(mCanonicalName); } +void +AddrInfo::SetCanonicalName(const char* cname) +{ + if (mCanonicalName) { + moz_free(mCanonicalName); + mCanonicalName = nullptr; + } + + if (cname) { + size_t cnameLen = strlen(cname); + mCanonicalName = static_cast(moz_xmalloc(cnameLen + 1)); + memcpy(mCanonicalName, cname, cnameLen + 1); + } +} + void AddrInfo::Init(const char *host, const char *cname) { @@ -299,14 +319,9 @@ AddrInfo::Init(const char *host, const char *cname) size_t hostlen = strlen(host); mHostName = static_cast(moz_xmalloc(hostlen + 1)); memcpy(mHostName, host, hostlen + 1); - if (cname) { - size_t cnameLen = strlen(cname); - mCanonicalName = static_cast(moz_xmalloc(cnameLen + 1)); - memcpy(mCanonicalName, cname, cnameLen + 1); - } - else { - mCanonicalName = nullptr; - } + + mCanonicalName = nullptr; + SetCanonicalName(cname); } void @@ -327,5 +342,39 @@ AddrInfo::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const return n; } +void +AddrInfo::MergeAndConsume(AddrInfo* aOther, uint16_t aAddressFamily) { + // Remove all of the addresses of the resolved type + NetAddrElement* iter = mAddresses.getFirst(); + while (iter) { + NetAddrElement* next = iter->getNext(); + + if (iter->mAddress.raw.family == aAddressFamily) { + iter->removeFrom(mAddresses); + delete iter; + } + + iter = next; + } + + // Add in the new results + if (aOther) { + iter = aOther->mAddresses.getFirst(); + while (iter) { + NetAddrElement* next = iter->getNext(); + + // Move it from one linked list to the aOther + iter->removeFrom(aOther->mAddresses); + mAddresses.insertBack(iter); + + iter = next; + } + + if (aOther->mCanonicalName && strlen(aOther->mCanonicalName)) { + SetCanonicalName(aOther->mCanonicalName); + } + } +} + } // namespace dns } // namespace mozilla diff --git a/netwerk/dns/DNS.h b/netwerk/dns/DNS.h index e3beaf5646b6..6f464fae8254 100644 --- a/netwerk/dns/DNS.h +++ b/netwerk/dns/DNS.h @@ -120,6 +120,7 @@ class NetAddrElement : public LinkedListElement { public: explicit NetAddrElement(const PRNetAddr *prNetAddr); NetAddrElement(const NetAddrElement& netAddr); + NetAddrElement(const NetAddr& aNetAddr); ~NetAddrElement(); NetAddr mAddress; @@ -137,6 +138,7 @@ public: ~AddrInfo(); void AddAddress(NetAddrElement *address); + void SetCanonicalName(const char* cname); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; @@ -144,6 +146,10 @@ public: char *mCanonicalName; LinkedList mAddresses; + // Removes every address of addressFamily, then takes every address in other + // as well as copying its canonicalName if it has one. other may be null. + void MergeAndConsume(AddrInfo* aOther, uint16_t aAddressFamily); + private: void Init(const char *host, const char *cname); }; diff --git a/netwerk/dns/GetAddrInfo.cpp b/netwerk/dns/GetAddrInfo.cpp new file mode 100644 index 000000000000..f846426f66d5 --- /dev/null +++ b/netwerk/dns/GetAddrInfo.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZ_LOGGING) +#define FORCE_PR_LOG +#endif + +#include "GetAddrInfo.h" +#include "mozilla/net/DNS.h" +#include "prnetdb.h" +#include "nsHostResolver.h" +#include "nsError.h" +#include "mozilla/Mutex.h" +#include "nsAutoPtr.h" +#include "mozilla/StaticPtr.h" +#include "MainThreadUtils.h" +#include "mozilla/DebugOnly.h" + +#include "prlog.h" +#if defined(PR_LOGGING) +static PRLogModuleInfo *gGetAddrInfoLog = PR_NewLogModule("GetAddrInfo"); +#define LOG(msg, ...) \ + PR_LOG(gGetAddrInfoLog, PR_LOG_DEBUG, ("[DNS]: " msg, ##__VA_ARGS__)) +#define LOG_WARNING(msg, ...) \ + PR_LOG(gGetAddrInfoLog, PR_LOG_WARNING, ("[DNS]: " msg, ##__VA_ARGS__)) +#else +#define LOG(args) +#define LOG_WARNING(args) +#endif + +#if DNS_API == DNS_API_WINDOWS_DNS_QUERY +// There is a bug in Windns.h where the type of parameter ppQueryResultsSet for +// DnsQuery_A is dependent on UNICODE being set. It should *always* be +// PDNS_RECORDA, but if UNICODE is set it is PDNS_RECORDW. To get around this +// we make sure that UNICODE is unset. +#undef UNICODE +#include "Windns.h" +#endif + +namespace mozilla { +namespace net { + +#if DNS_API == DNS_API_WINDOWS_DNS_QUERY +//////////////////////////// +// WINDOWS IMPLEMENTATION // +//////////////////////////// + +// Ensure consistency of PR_* and AF_* constants to allow for legacy usage of +// PR_* constants with this API. +static_assert(PR_AF_INET == AF_INET && PR_AF_INET6 == AF_INET6 + && PR_AF_UNSPEC == AF_UNSPEC, "PR_AF_* must match AF_*"); + +// We intentionally leak this mutex. This is because we can run into a +// situation where the worker threads are still running until the process +// is actually fully shut down, and at any time one of those worker +// threads can access gDnsapiInfoLock. +static OffTheBooksMutex* gDnsapiInfoLock = nullptr; + +struct DnsapiInfo +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DnsapiInfo); + + HMODULE mLibrary; + decltype(&DnsQuery_A) mDnsQueryFunc; + decltype(&DnsFree) mDnsFreeFunc; + +private: + // This will either be called during shutdown of the GetAddrInfo module, or + // when a worker thread is done doing a lookup (ie: within + // _GetAddrInfo_Windows). Note that the lock must be held when this is + // called. + ~DnsapiInfo() + { + if (gDnsapiInfoLock) { + gDnsapiInfoLock->AssertCurrentThreadOwns(); + } else { + MOZ_ASSERT_UNREACHABLE("No mutex available during GetAddrInfo shutdown."); + } + + LOG("Freeing Dnsapi.dll"); + MOZ_ASSERT(mLibrary); + DebugOnly rv = FreeLibrary(mLibrary); + NS_WARN_IF_FALSE(rv, "Failed to free Dnsapi.dll."); + } +}; + +static StaticRefPtr gDnsapiInfo; + +static MOZ_ALWAYS_INLINE nsresult +_GetAddrInfoInit_Windows() +{ + // This is necessary to ensure strict thread safety because if two threads + // run this function at the same time they can potentially create two + // mutexes. + MOZ_ASSERT(NS_IsMainThread(), + "Do not initialize GetAddrInfo off main thread!"); + + if (!gDnsapiInfoLock) { + gDnsapiInfoLock = new OffTheBooksMutex("GetAddrInfo.cpp::gDnsapiInfoLock"); + } + OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); + + if (gDnsapiInfo) { + MOZ_ASSERT_UNREACHABLE("GetAddrInfo is being initialized multiple times!"); + return NS_ERROR_ALREADY_INITIALIZED; + } + + HMODULE library = LoadLibraryA("Dnsapi.dll"); + if (NS_WARN_IF(!library)) { + return NS_ERROR_FAILURE; + } + + FARPROC dnsQueryFunc = GetProcAddress(library, "DnsQuery_A"); + FARPROC dnsFreeFunc = GetProcAddress(library, "DnsFree"); + if (NS_WARN_IF(!dnsQueryFunc) || NS_WARN_IF(!dnsFreeFunc)) { + DebugOnly rv = FreeLibrary(library); + NS_WARN_IF_FALSE(rv, "Failed to free Dnsapi.dll."); + return NS_ERROR_FAILURE; + } + + DnsapiInfo* info = new DnsapiInfo; + info->mLibrary = library; + info->mDnsQueryFunc = (decltype(info->mDnsQueryFunc)) dnsQueryFunc; + info->mDnsFreeFunc = (decltype(info->mDnsFreeFunc)) dnsFreeFunc; + gDnsapiInfo = info; + + return NS_OK; +} + +static MOZ_ALWAYS_INLINE nsresult +_GetAddrInfoShutdown_Windows() +{ + OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); + + if (NS_WARN_IF(!gDnsapiInfo) || NS_WARN_IF(!gDnsapiInfoLock)) { + MOZ_ASSERT_UNREACHABLE("GetAddrInfo not initialized!"); + return NS_ERROR_NOT_INITIALIZED; + } + + gDnsapiInfo = nullptr; + + return NS_OK; +} + +static MOZ_ALWAYS_INLINE nsresult +_GetAddrInfo_Windows(const char* aHost, uint16_t aAddressFamily, + uint16_t aFlags, AddrInfo** aAddrInfo) +{ + MOZ_ASSERT(aHost); + MOZ_ASSERT(aAddrInfo); + + nsRefPtr dnsapi = nullptr; + { + OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); + dnsapi = gDnsapiInfo; + } + + if (!dnsapi) { + LOG_WARNING("GetAddrInfo has been shutdown or has not been initialized."); + return NS_ERROR_NOT_INITIALIZED; + } + + WORD dns_type; + if (aAddressFamily == PR_AF_INET) { + dns_type = DNS_TYPE_A; + } else if (aAddressFamily == PR_AF_INET6) { + dns_type = DNS_TYPE_AAAA; + } else { + LOG_WARNING("Unsupported address family %X.\n", aAddressFamily); + MOZ_ASSERT_UNREACHABLE("Unsupported address family."); + return NS_ERROR_INVALID_ARG; + } + + PDNS_RECORDA dnsData = nullptr; + DNS_STATUS status = dnsapi->mDnsQueryFunc(aHost, dns_type, + DNS_QUERY_STANDARD, nullptr, &dnsData, nullptr); + if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR + || !dnsData) { + LOG("No DNS records found for %s.\n", aHost); + return NS_ERROR_UNKNOWN_HOST; + } else if (status != NOERROR) { + LOG_WARNING("DnsQuery_A failed with status %X.\n", status); + return NS_ERROR_FAILURE; + } + +#ifdef PR_LOGGING + int numARecords = 0; + int numAaaaRecords = 0; + int numCnameRecords = 0; + int numUnknownRecords = 0; + #define INCREMENT_IF_LOGGING(counter) ++counter +#else + #define INCREMENT_IF_LOGGING(counter) +#endif + + // Everything we need is now in dnsData. We just need to pull it out and put + // it into an AddrInfo object. + nsAutoPtr ai(new AddrInfo(aHost, nullptr)); + PDNS_RECORDA curRecord = dnsData; + while (curRecord) { + if (curRecord->wType == DNS_TYPE_A) { + INCREMENT_IF_LOGGING(numARecords); + + NetAddr addr; + addr.inet.family = AF_INET; + addr.inet.ip = curRecord->Data.A.IpAddress; + + // Initialize port to 0 to be filled in later at socket connection time + addr.inet.port = 0; + + ai->AddAddress(new NetAddrElement(addr)); + } else if (curRecord->wType == DNS_TYPE_AAAA) { + INCREMENT_IF_LOGGING(numAaaaRecords); + + NetAddr addr; + addr.inet6.family = AF_INET6; + memcpy(&addr.inet6.ip, &curRecord->Data.AAAA.Ip6Address, sizeof(addr.inet6.ip.u8)); + + // Initialize port to 0 to be filled in later at socket connection time + addr.inet6.port = 0; + + ai->AddAddress(new NetAddrElement(addr)); + } else if (curRecord->wType == DNS_TYPE_CNAME) { + INCREMENT_IF_LOGGING(numCnameRecords); + + char* cname = curRecord->Data.CNAME.pNameHost; + LOG("Got 'CNAME' %s for %s.\n", cname, aHost); + + ai->SetCanonicalName(cname); + } else { + INCREMENT_IF_LOGGING(numUnknownRecords); + + LOG("Ignoring record for %s of type %X.\n", aHost, curRecord->wType); + } + + curRecord = curRecord->pNext; + } + +#ifdef PR_LOGGING + LOG("Got %d 'A' records, %d 'AAAA' records, %d 'CNAME' records, and %d " + "unknown records for host %s.\n", numARecords, numAaaaRecords, + numCnameRecords, numUnknownRecords, aHost); +#endif + + dnsapi->mDnsFreeFunc(dnsData, DNS_FREE_TYPE::DnsFreeRecordList); + + { + // dnsapi's destructor is not thread-safe, so we release explicitly here + OffTheBooksMutexAutoLock lock(*gDnsapiInfoLock); + dnsapi = nullptr; + } + + if (ai->mAddresses.isEmpty()) { + return NS_ERROR_UNKNOWN_HOST; + } + + *aAddrInfo = ai.forget(); + return NS_OK; +} +#elif DNS_API == DNS_API_PORTABLE +//////////////////////////////////// +// PORTABLE RUNTIME IMPLEMENTATION// +//////////////////////////////////// + +static MOZ_ALWAYS_INLINE nsresult +_GetAddrInfo_Portable(const char* aHost, uint16_t aAddressFamily, + uint16_t aFlags, AddrInfo** aAddrInfo) +{ + MOZ_ASSERT(aHost); + MOZ_ASSERT(aAddrInfo); + + // We accept the same aFlags that nsHostResolver::ResolveHost accepts, but we + // need to translate the aFlags into a form that PR_GetAddrInfoByName + // accepts. + int prFlags = PR_AI_ADDRCONFIG; + if (!(aFlags & nsHostResolver::RES_CANON_NAME)) { + prFlags |= PR_AI_NOCANONNAME; + } + + // We need to remove IPv4 records manually because PR_GetAddrInfoByName + // doesn't support PR_AF_INET6. + bool disableIPv4 = aAddressFamily == PR_AF_INET6; + if (disableIPv4) { + aAddressFamily = PR_AF_UNSPEC; + } + + PRAddrInfo* prai = PR_GetAddrInfoByName(aHost, aAddressFamily, prFlags); + + if (!prai) { + return NS_ERROR_UNKNOWN_HOST; + } + + const char* canonName = nullptr; + if (aFlags & nsHostResolver::RES_CANON_NAME) { + canonName = PR_GetCanonNameFromAddrInfo(prai); + } + + nsAutoPtr ai(new AddrInfo(aHost, prai, disableIPv4, canonName)); + PR_FreeAddrInfo(prai); + if (ai->mAddresses.isEmpty()) { + return NS_ERROR_UNKNOWN_HOST; + } + + *aAddrInfo = ai.forget(); + + return NS_OK; +} +#endif + +////////////////////////////////////// +// COMMON/PLATFORM INDEPENDENT CODE // +////////////////////////////////////// +nsresult +GetAddrInfoInit() { + LOG("Initializing GetAddrInfo.\n"); + +#if DNS_API == DNS_API_WINDOWS_DNS_QUERY + return _GetAddrInfoInit_Windows(); +#else + return NS_OK; +#endif +} + +nsresult +GetAddrInfoShutdown() { + LOG("Shutting down GetAddrInfo.\n"); + +#if DNS_API == DNS_API_WINDOWS_DNS_QUERY + return _GetAddrInfoShutdown_Windows(); +#else + return NS_OK; +#endif +} + +nsresult +GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags, + AddrInfo** aAddrInfo) +{ + if (NS_WARN_IF(!aHost) || NS_WARN_IF(!aAddrInfo)) { + return NS_ERROR_NULL_POINTER; + } + + *aAddrInfo = nullptr; + +#if DNS_API == DNS_API_WINDOWS_DNS_QUERY + return _GetAddrInfo_Windows(aHost, aAddressFamily, aFlags, aAddrInfo); +#elif DNS_API == DNS_API_PORTABLE + return _GetAddrInfo_Portable(aHost, aAddressFamily, aFlags, aAddrInfo); +#else + // This is merely to prevent programmer error in the future (setting the + // platform ID to something invalid on accident). _GetAddrInfo_Portable is + // defaulted to in the header file. + #error Unknown or unspecified DNS_API. +#endif +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/dns/GetAddrInfo.h b/netwerk/dns/GetAddrInfo.h new file mode 100644 index 000000000000..c9f65a6dc63f --- /dev/null +++ b/netwerk/dns/GetAddrInfo.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef netwerk_dns_GetAddrInfo_h +#define netwerk_dns_GetAddrInfo_h + +#include "nsError.h" + +#define DNS_API_PORTABLE (0) // Portable: PR_GetAddrInfoByName() +#define DNS_API_WINDOWS_DNS_QUERY (1) // Windows: DnsQuery() + +#ifdef XP_WIN +#define DNS_API DNS_API_WINDOWS_DNS_QUERY +#else +#define DNS_API DNS_API_PORTABLE +#endif + +namespace mozilla { +namespace net { + +class AddrInfo; + +/** + * Look up a host by name. Mostly equivalent to getaddrinfo(host, NULL, ...) of + * RFC 3493. + * + * @param hostname[in] Character string defining the host name of interest + * @param aAf[in] May be AF_INET, AF_INET6, or AF_UNSPEC. Note that AF_UNSPEC + * is not supported if DNS_API is DNS_API_WINDOWS_DNS_QUERY. + * @param aFlags[in] May be either PR_AI_ADDRCONFIG or + * PR_AI_ADDRCONFIG | PR_AI_NOCANONNAME. Include PR_AI_NOCANONNAME to + * suppress the determination of the canonical name corresponding to + * hostname. + * @param aAddrInfo[out] Will point to the results of the host lookup, or be + * null if the lookup failed. + */ +nsresult +GetAddrInfo(const char* aHost, uint16_t aAf, uint16_t aFlags, AddrInfo** aAddrInfo); + +/** + * Initialize the GetAddrInfo module. + * + * GetAddrInfoShutdown() should be called for every time this function is + * called. + */ +nsresult +GetAddrInfoInit(); + +/** + * Shutdown the GetAddrInfo module. + * + * This function should be called for every time GetAddrInfoInit() is called. + * An assertion may throw (but is not guarenteed) if this function is called + * too many times. + */ +nsresult +GetAddrInfoShutdown(); + +} // namespace net +} // namespace mozilla + +#endif // netwerk_dns_GetAddrInfo_h diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build index 0b0717a5a8c8..a106daebdefc 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -25,6 +25,7 @@ EXPORTS.mozilla.net += [ ] SOURCES += [ + 'GetAddrInfo.cpp', # Excluded from UNIFIED_SOURCES due to NSPR forced logging. 'nsEffectiveTLDService.cpp', # Excluded from UNIFIED_SOURCES due to special build flags. 'nsHostResolver.cpp', # Excluded from UNIFIED_SOURCES due to NSPR forced logging. ] diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp index 1808fd1e3352..c26882addcaa 100644 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -10,7 +10,7 @@ #if defined(HAVE_RES_NINIT) #include #include -#include +#include #include #include #define RES_RETRY_ON_FAILURE @@ -19,6 +19,7 @@ #include #include "nsHostResolver.h" #include "nsError.h" +#include "GetAddrInfo.h" #include "nsISupportsBase.h" #include "nsISupportsUtils.h" #include "nsAutoPtr.h" @@ -35,6 +36,7 @@ #include "mozilla/TimeStamp.h" #include "mozilla/Telemetry.h" #include "mozilla/VisualEventTracer.h" +#include "mozilla/DebugOnly.h" using namespace mozilla; using namespace mozilla::net; @@ -166,6 +168,11 @@ nsHostRecord::nsHostRecord(const nsHostKey *key) , onQueue(false) , usingAnyThread(false) , mDoomed(false) +#if DO_MERGE_FOR_AF_UNSPEC + , mInnerAF(nsHostRecord::UNSPECAF_NULL) + , mCloneOf(nullptr) + , mNumPending(0) +#endif { host = ((char *) this) + sizeof(nsHostRecord); memcpy((char *) host, key->host, strlen(key->host) + 1); @@ -195,6 +202,26 @@ nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result) return NS_OK; } +#if DO_MERGE_FOR_AF_UNSPEC +nsresult +nsHostRecord::CloneForAFUnspec(nsHostRecord** aClonedRecord, uint16_t aInnerAF) +{ + nsHostRecord* cloned = nullptr; + nsresult rv = Create(static_cast(this), &cloned); + if (NS_FAILED(rv)) { + return rv; + } + + cloned->mInnerAF = aInnerAF; + cloned->mCloneOf = this; + NS_ADDREF_THIS(); + + *aClonedRecord = cloned; + + return NS_OK; +} +#endif + nsHostRecord::~nsHostRecord() { delete addr_info; @@ -444,6 +471,10 @@ nsHostResolver::~nsHostResolver() nsresult nsHostResolver::Init() { + if (NS_FAILED(GetAddrInfoInit())) { + return NS_ERROR_FAILURE; + } + PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0); mShutdown = false; @@ -534,6 +565,9 @@ nsHostResolver::Shutdown() while (mThreadCount && PR_IntervalNow() < stopTime) PR_Sleep(delay); #endif + + mozilla::DebugOnly rv = GetAddrInfoShutdown(); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to shutdown GetAddrInfo"); } void @@ -843,7 +877,42 @@ nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec) } nsresult -nsHostResolver::IssueLookup(nsHostRecord *rec) +nsHostResolver::IssueLookup(nsHostRecord* rec) +{ +#if DO_MERGE_FOR_AF_UNSPEC + // Issue two lookups to fulfill AF_UNSPEC requests: one each for AF_INET + // and AF_INET6. + if (rec->af == PR_AF_UNSPEC) { + // Enqueue a lookup for the IPv4 addresses first + rec->mInnerAF = AF_INET; + nsresult rv = IssueLookupInternal(rec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rec->mNumPending++; + + // Make a clone and issue a lookup for the IPv6 addresses + nsHostRecord* rec_clone; + rv = rec->CloneForAFUnspec(&rec_clone, AF_INET6); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = IssueLookupInternal(rec_clone); + NS_RELEASE(rec_clone); + if (NS_SUCCEEDED(rv)) { + rec->mNumPending++; + } + + return rv; + } +#endif + + return IssueLookupInternal(rec); +} + +nsresult +nsHostResolver::IssueLookupInternal(nsHostRecord* rec) { MOZ_EVENT_TRACER_WAIT(rec, "net::dns::resolve"); @@ -853,9 +922,9 @@ nsHostResolver::IssueLookup(nsHostRecord *rec) // Add rec to one of the pending queues, possibly removing it from mEvictionQ. // If rec is on mEvictionQ, then we can just move the owning // reference over to the new active queue. - if (rec->next == rec) + if (rec->next == rec) { NS_ADDREF(rec); - else { + } else { PR_REMOVE_LINK(rec); mEvictionQSize--; } @@ -978,7 +1047,7 @@ nsHostResolver::GetHostToLookup(nsHostRecord **result) } void -nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, AddrInfo *result) +nsHostResolver::OnLookupComplete(nsHostRecord* rec, nsresult status, AddrInfo* result) { // get the list of pending callbacks for this lookup, and notify // them that the lookup is complete. @@ -987,63 +1056,156 @@ nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, AddrInfo *r { MutexAutoLock lock(mLock); - // grab list of callbacks to notify - MoveCList(rec->callbacks, cbs); +#if DO_MERGE_FOR_AF_UNSPEC + // If this was an unspec query, then this function will be called twice + // and we need to make sure to merge the second result with the first. + if (rec->af == PR_AF_UNSPEC) { + MOZ_ASSERT(rec->mInnerAF != nsHostRecord::UNSPECAF_NULL); - // update record fields. We might have a rec->addr_info already if a - // previous lookup result expired and we're reresolving it.. - AddrInfo *old_addr_info; - { - MutexAutoLock lock(rec->addr_info_lock); - old_addr_info = rec->addr_info; - rec->addr_info = result; - rec->addr_info_gencnt++; - } - delete old_addr_info; + LOG(("OnLookupComplete: %s for UNSPEC request %s host %s.", + PR_AF_INET == rec->mInnerAF ? "INET" : "INET6", + rec->mCloneOf ? "clone" : "original", + rec->host)); - rec->expiration = TimeStamp::NowLoRes(); - if (result) { - rec->expiration += mMaxCacheLifetime; - rec->negative = false; - } - else { - rec->expiration += TimeDuration::FromSeconds(60); /* one minute for negative cache */ - rec->negative = true; - } - rec->resolving = false; - - if (rec->usingAnyThread) { - mActiveAnyThreadCount--; - rec->usingAnyThread = false; - } + nsHostRecord* originalRecord = rec->mCloneOf ? rec->mCloneOf : rec; - if (!mShutdown) { - // add to mEvictionQ - PR_APPEND_LINK(rec, &mEvictionQ); - NS_ADDREF(rec); - if (mEvictionQSize < mMaxCacheEntries) - mEvictionQSize++; - else { - // remove first element on mEvictionQ - nsHostRecord *head = - static_cast(PR_LIST_HEAD(&mEvictionQ)); - PR_REMOVE_AND_INIT_LINK(head); - PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE); + { + MutexAutoLock lock(originalRecord->addr_info_lock); - if (!head->negative) { - // record the age of the entry upon eviction. - TimeDuration age = TimeStamp::NowLoRes() - - (head->expiration - mMaxCacheLifetime); - Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, - static_cast(age.ToSeconds() / 60)); + // If we have addresses for this host already... + if (originalRecord->addr_info) { + LOG(("Merging AF_UNSPEC results into existing addr_info " + "for %s.\n", rec->host)); + + originalRecord->addr_info->MergeAndConsume(result, + rec->mInnerAF); + originalRecord->addr_info_gencnt++; + } else { + LOG(("Original has no addr_info, using new AF_UNSPEC " + "result for %s.\n", rec->host)); + + originalRecord->addr_info = result; + originalRecord->addr_info_gencnt++; + } + } + + // Release the cloned record if its lookup is complete. + if (rec != originalRecord) { + MOZ_ASSERT(rec->mCloneOf); + LOG(("Deleting cloned AF_UNSPEC record for %s.\n", rec->host)); + + if (rec->usingAnyThread) { + mActiveAnyThreadCount--; + rec->usingAnyThread = false; } - // release reference to rec owned by mEvictionQ - NS_RELEASE(head); + // The original record will be released at the end of this + // function, so we don't have to (and shouldn't) release it + // here. + rec->mCloneOf = nullptr; + + // We need to release the clone however because it won't be + // released otherwise. + NS_RELEASE(rec); + + // We're totally done with the clone now and can concern + // ourselves solely with the original record. + rec = originalRecord; + originalRecord = nullptr; + } + + MOZ_ASSERT(rec->mNumPending >= 0); + rec->mNumPending--; + } else { +#else // if !DO_MERGE_FOR_AF_UNSPEC + { +#endif + LOG(("Got result for %s.\n", rec->host)); + + // update record fields. We might have a rec->addr_info already if + // a previous lookup result expired and we're reresolving it.. + AddrInfo *old_addr_info; + { + MutexAutoLock lock(rec->addr_info_lock); + old_addr_info = rec->addr_info; + rec->addr_info = result; + rec->addr_info_gencnt++; + } + delete old_addr_info; + } + +#if DO_MERGE_FOR_AF_UNSPEC + // If we're merging for AF_UNSPEC, grab the callback list only if all + // the inner lookups are complete. + if (rec->mNumPending <= 0) { + MOZ_ASSERT(rec->mNumPending == 0); +#else + // Otherwise, go ahead and grab the list of callbacks to notify. + { +#endif + MoveCList(rec->callbacks, cbs); + + rec->expiration = TimeStamp::NowLoRes(); + if (result) { + rec->expiration += mMaxCacheLifetime; + rec->negative = false; + } + else { + // One minute for negative cache + rec->expiration += TimeDuration::FromSeconds(60); + rec->negative = true; + } + rec->resolving = false; + + if (rec->usingAnyThread) { + mActiveAnyThreadCount--; + rec->usingAnyThread = false; + } + + if (!mShutdown) { + // add to mEvictionQ + PR_APPEND_LINK(rec, &mEvictionQ); + NS_ADDREF(rec); + if (mEvictionQSize < mMaxCacheEntries) + mEvictionQSize++; + else { + // remove first element on mEvictionQ + nsHostRecord *head = + static_cast(PR_LIST_HEAD(&mEvictionQ)); + PR_REMOVE_AND_INIT_LINK(head); + PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE); + + if (!head->negative) { + // record the age of the entry upon eviction. + TimeDuration age = TimeStamp::NowLoRes() - + (head->expiration - mMaxCacheLifetime); + Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, + static_cast(age.ToSeconds() / 60)); + } + + // release reference to rec owned by mEvictionQ + NS_RELEASE(head); + } } } } +#if DO_MERGE_FOR_AF_UNSPEC + // We don't want to trigger the callbacks if the inner lookups haven't + // completed yet. + if (rec->mNumPending > 0) { + NS_RELEASE(rec); + return; + } + + MOZ_ASSERT(rec->mNumPending == 0); + + if (rec->af == PR_AF_UNSPEC && rec->addr_info) { + MutexAutoLock lock(rec->addr_info_lock); + status = rec->addr_info->mAddresses.isEmpty() ? status : NS_OK; + } +#endif + MOZ_EVENT_TRACER_DONE(rec, "net::dns::resolve"); if (!PR_CLIST_IS_EMPTY(&cbs)) { @@ -1142,62 +1304,48 @@ nsHostResolver::ThreadFunc(void *arg) #endif nsHostResolver *resolver = (nsHostResolver *)arg; nsHostRecord *rec; - PRAddrInfo *prai = nullptr; while (resolver->GetHostToLookup(&rec)) { - LOG(("DNS lookup thread - Calling getaddrinfo for host [%s].\n", + LOG(("DNS lookup thread - Getting address info for host [%s].\n", rec->host)); - int flags = PR_AI_ADDRCONFIG; - if (!(rec->flags & RES_CANON_NAME)) - flags |= PR_AI_NOCANONNAME; - TimeStamp startTime = TimeStamp::Now(); MOZ_EVENT_TRACER_EXEC(rec, "net::dns::resolve"); - // We need to remove IPv4 records manually - // because PR_GetAddrInfoByName doesn't support PR_AF_INET6. - bool disableIPv4 = rec->af == PR_AF_INET6; - uint16_t af = disableIPv4 ? PR_AF_UNSPEC : rec->af; - prai = PR_GetAddrInfoByName(rec->host, af, flags); + uint16_t af; +#if DO_MERGE_FOR_AF_UNSPEC + // In the case of an unspec request, we need to make sure to use the + // "real" address family when we call GetAddrInfo. + af = rec->af == PR_AF_UNSPEC ? rec->mInnerAF : rec->af; +#else + af = rec->af; +#endif + + AddrInfo* ai = nullptr; + nsresult rv = GetAddrInfo(rec->host, af, rec->flags, &ai); #if defined(RES_RETRY_ON_FAILURE) - if (!prai && rs.Reset()) - prai = PR_GetAddrInfoByName(rec->host, af, flags); + if (NS_FAILED(rv) && rs.Reset()) { + rv = GetAddrInfo(rec->host, af, rec->flags, &ai); + } #endif TimeDuration elapsed = TimeStamp::Now() - startTime; uint32_t millis = static_cast(elapsed.ToMilliseconds()); - // convert error code to nsresult - nsresult status; - AddrInfo *ai = nullptr; - if (prai) { - const char *cname = nullptr; - if (rec->flags & RES_CANON_NAME) - cname = PR_GetCanonNameFromAddrInfo(prai); - ai = new AddrInfo(rec->host, prai, disableIPv4, cname); - PR_FreeAddrInfo(prai); - if (ai->mAddresses.isEmpty()) { - delete ai; - ai = nullptr; - } - } - if (ai) { - status = NS_OK; - + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(ai); Telemetry::Accumulate(!rec->addr_info_gencnt ? Telemetry::DNS_LOOKUP_TIME : Telemetry::DNS_RENEWAL_TIME, millis); - } - else { - status = NS_ERROR_UNKNOWN_HOST; + } else { Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis); } // OnLookupComplete may release "rec", log before we lose it. LOG(("DNS lookup thread - lookup completed for host [%s]: %s.\n", - rec->host, ai ? "success" : "failure: unknown host")); - resolver->OnLookupComplete(rec, status, ai); + rec->host, + NS_SUCCEEDED(rv) ? "success" : "failure: unknown host")); + resolver->OnLookupComplete(rec, rv, ai); } NS_RELEASE(resolver); LOG(("DNS lookup thread - queue empty, thread finished.\n")); diff --git a/netwerk/dns/nsHostResolver.h b/netwerk/dns/nsHostResolver.h index 3c60152eab5b..04b0dc018c60 100644 --- a/netwerk/dns/nsHostResolver.h +++ b/netwerk/dns/nsHostResolver.h @@ -16,6 +16,7 @@ #include "nsIDNSListener.h" #include "nsString.h" #include "nsTArray.h" +#include "nsAutoPtr.h" #include "mozilla/net/DNS.h" #include "mozilla/net/DashboardTypes.h" #include "mozilla/TimeStamp.h" @@ -31,6 +32,17 @@ class nsResolveHostCallback; #define MAX_RESOLVER_THREADS (MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY + \ MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY) +#if XP_WIN +// If this is enabled, we will make two queries to our resolver for unspec +// queries. One for any AAAA records, and the other for A records. We will then +// merge the results ourselves. This is done for us by getaddrinfo, but because +// getaddrinfo doesn't give us the TTL we use other APIs when we can that may +// not do it for us (Window's DnsQuery function for example). +#define DO_MERGE_FOR_AF_UNSPEC 1 +#else +#define DO_MERGE_FOR_AF_UNSPEC 0 +#endif + struct nsHostKey { const char *host; @@ -69,7 +81,10 @@ public: * thread doesn't need to lock when reading |addr_info|. */ Mutex addr_info_lock; - int addr_info_gencnt; /* generation count of |addr_info| */ + /* generation count of |addr_info|. Must be incremented whenever addr_info + * is changed so any iterators going through the old linked list can be + * invalidated. */ + int addr_info_gencnt; mozilla::net::AddrInfo *addr_info; mozilla::net::NetAddr *addr; bool negative; /* True if this record is a cache of a failed lookup. @@ -101,6 +116,23 @@ private: bool usingAnyThread; /* true if off queue and contributing to mActiveAnyThreadCount */ bool mDoomed; /* explicitly expired */ +#if DO_MERGE_FOR_AF_UNSPEC + // If this->af is PR_AF_UNSPEC, this will contain the address family that + // should actually be passed to GetAddrInfo. + uint16_t mInnerAF; + static const uint16_t UNSPECAF_NULL = -1; + + // mCloneOf will point at the original host record if this record is a + // clone. Null otherwise. + nsHostRecord* mCloneOf; + + // This will be set to the number of unresolved host records out for the + // given host record key. 0 for non AF_UNSPEC records. + int mNumPending; + + nsresult CloneForAFUnspec(nsHostRecord** aNewRecord, uint16_t aUnspecAF); +#endif + // a list of addresses associated with this record that have been reported // as unusable. the list is kept as a set of strings to make it independent // of gencnt. @@ -244,13 +276,20 @@ private: ~nsHostResolver(); nsresult Init(); - nsresult IssueLookup(nsHostRecord *); bool GetHostToLookup(nsHostRecord **m); void OnLookupComplete(nsHostRecord *, nsresult, mozilla::net::AddrInfo *); void DeQueue(PRCList &aQ, nsHostRecord **aResult); void ClearPendingQueue(PRCList *aPendingQueue); nsresult ConditionallyCreateThread(nsHostRecord *rec); + // This will issue two lookups (using the internal version) if + // DO_MERGE_FOR_AF_UNSPEC is enabled and the passed in record is an + // AF_UNSPEC record. + nsresult IssueLookup(nsHostRecord *); + + // This actually issues a single lookup + nsresult IssueLookupInternal(nsHostRecord *); + /** * Starts a new lookup in the background for entries that are in the grace * period with a failed connect or all cached entries are negative.