author | josullivan <josullivan@mozilla.com> |
Fri, 15 Aug 2014 17:25:06 -0700 | |
changeset 200201 | 4741ef815af991357d609870965c8111ee1b7073 |
parent 200200 | 293f3a5eabdb6bf213a762bca37b5b0a128e1dad |
child 200202 | a5de6212a18cb19d7a26ea363cd8bc9cd78bb88b |
push id | 27337 |
push user | emorley@mozilla.com |
push date | Tue, 19 Aug 2014 12:40:34 +0000 |
treeherder | mozilla-central@a38daccaa557 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | sworkman |
bugs | 820391, 100644 |
milestone | 34.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/netwerk/dns/DNS.cpp +++ b/netwerk/dns/DNS.cpp @@ -250,16 +250,21 @@ NetAddrElement::NetAddrElement(const PRN PRNetAddrToNetAddr(prNetAddr, &mAddress); } NetAddrElement::NetAddrElement(const NetAddrElement& netAddr) { mAddress = netAddr.mAddress; } +NetAddrElement::NetAddrElement(const NetAddr& aNetAddr) +{ + mAddress = aNetAddr; +} + NetAddrElement::~NetAddrElement() { } AddrInfo::AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, bool disableIPv4, const char *cname) { MOZ_ASSERT(prAddrInfo, "Cannot construct AddrInfo with a null prAddrInfo pointer!"); @@ -287,31 +292,41 @@ AddrInfo::~AddrInfo() while ((addrElement = mAddresses.popLast())) { delete addrElement; } moz_free(mHostName); 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<char*>(moz_xmalloc(cnameLen + 1)); + memcpy(mCanonicalName, cname, cnameLen + 1); + } +} + +void AddrInfo::Init(const char *host, const char *cname) { MOZ_ASSERT(host, "Cannot initialize AddrInfo with a null host pointer!"); size_t hostlen = strlen(host); mHostName = static_cast<char*>(moz_xmalloc(hostlen + 1)); memcpy(mHostName, host, hostlen + 1); - if (cname) { - size_t cnameLen = strlen(cname); - mCanonicalName = static_cast<char*>(moz_xmalloc(cnameLen + 1)); - memcpy(mCanonicalName, cname, cnameLen + 1); - } - else { - mCanonicalName = nullptr; - } + + mCanonicalName = nullptr; + SetCanonicalName(cname); } void AddrInfo::AddAddress(NetAddrElement *address) { MOZ_ASSERT(address, "Cannot add the address to an uninitialized list"); mAddresses.insertBack(address); @@ -322,10 +337,44 @@ AddrInfo::SizeOfIncludingThis(MallocSize { size_t n = mallocSizeOf(this); n += mallocSizeOf(mHostName); n += mallocSizeOf(mCanonicalName); n += mAddresses.sizeOfExcludingThis(mallocSizeOf); 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
--- a/netwerk/dns/DNS.h +++ b/netwerk/dns/DNS.h @@ -115,16 +115,17 @@ union NetAddr { // This class wraps a NetAddr union to provide C++ linked list // capabilities and other methods. It is created from a PRNetAddr, // which is converted to a mozilla::dns::NetAddr. class NetAddrElement : public LinkedListElement<NetAddrElement> { public: explicit NetAddrElement(const PRNetAddr *prNetAddr); NetAddrElement(const NetAddrElement& netAddr); + NetAddrElement(const NetAddr& aNetAddr); ~NetAddrElement(); NetAddr mAddress; }; class AddrInfo { public: // Creates an AddrInfo object. It calls the AddrInfo(const char*, const char*) @@ -132,23 +133,28 @@ public: AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, bool disableIPv4, const char *cname); // Creates a basic AddrInfo object (initialize only the host and the cname). AddrInfo(const char *host, const char *cname); ~AddrInfo(); void AddAddress(NetAddrElement *address); + void SetCanonicalName(const char* cname); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; char *mHostName; char *mCanonicalName; LinkedList<NetAddrElement> 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); }; // Copies the contents of a PRNetAddr to a NetAddr. // Does not do a ptr safety check! void PRNetAddrToNetAddr(const PRNetAddr *prAddr, NetAddr *addr);
new file mode 100644 --- /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<BOOL> rv = FreeLibrary(mLibrary); + NS_WARN_IF_FALSE(rv, "Failed to free Dnsapi.dll."); + } +}; + +static StaticRefPtr<DnsapiInfo> 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<BOOL> 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<DnsapiInfo> 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<AddrInfo> 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<AddrInfo> 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
new file mode 100644 --- /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
--- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -20,16 +20,17 @@ EXPORTS.mozilla.net += [ 'DNS.h', 'DNSListenerProxy.h', 'DNSRequestChild.h', 'DNSRequestParent.h', 'PDNSParams.h', ] 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. ] UNIFIED_SOURCES += [ 'ChildDNSService.cpp', 'DNS.cpp', 'DNSListenerProxy.cpp',
--- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -5,41 +5,43 @@ #if defined(MOZ_LOGGING) #define FORCE_PR_LOG #endif #if defined(HAVE_RES_NINIT) #include <sys/types.h> #include <netinet/in.h> -#include <arpa/inet.h> +#include <arpa/inet.h> #include <arpa/nameser.h> #include <resolv.h> #define RES_RETRY_ON_FAILURE #endif #include <stdlib.h> #include "nsHostResolver.h" #include "nsError.h" +#include "GetAddrInfo.h" #include "nsISupportsBase.h" #include "nsISupportsUtils.h" #include "nsAutoPtr.h" #include "prthread.h" #include "prerror.h" #include "prtime.h" #include "prlog.h" #include "pldhash.h" #include "plstr.h" #include "nsURLHelper.h" #include "nsThreadUtils.h" #include "mozilla/HashFunctions.h" #include "mozilla/TimeStamp.h" #include "mozilla/Telemetry.h" #include "mozilla/VisualEventTracer.h" +#include "mozilla/DebugOnly.h" using namespace mozilla; using namespace mozilla::net; //---------------------------------------------------------------------------- // Use a persistent thread pool in order to avoid spinning up new threads all the time. // In particular, thread creation results in a res_init() call from libc which is @@ -161,16 +163,21 @@ nsHostRecord::nsHostRecord(const nsHostK , addr_info_gencnt(0) , addr_info(nullptr) , addr(nullptr) , negative(false) , resolving(false) , 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); flags = key->flags; af = key->af; expiration = TimeStamp::NowLoRes(); @@ -190,16 +197,36 @@ nsHostRecord::Create(const nsHostKey *ke *result = new(place) nsHostRecord(key); NS_ADDREF(*result); MOZ_EVENT_TRACER_NAME_OBJECT(*result, key->host); 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<nsHostKey*>(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; delete addr; } bool nsHostRecord::Blacklisted(NetAddr *aQuery) @@ -439,16 +466,20 @@ nsHostResolver::nsHostResolver(uint32_t nsHostResolver::~nsHostResolver() { PL_DHashTableFinish(&mDB); } nsresult nsHostResolver::Init() { + if (NS_FAILED(GetAddrInfoInit())) { + return NS_ERROR_FAILURE; + } + PL_DHashTableInit(&mDB, &gHostDB_ops, nullptr, sizeof(nsHostDBEnt), 0); mShutdown = false; #if defined(HAVE_RES_NINIT) // We want to make sure the system is using the correct resolver settings, // so we force it to reload those settings whenever we startup a subsequent // nsHostResolver instance. We assume that there is no reason to do this @@ -529,16 +560,19 @@ nsHostResolver::Shutdown() // mThreadCount is read outside of mLock, but the worst case // scenario for that race is one extra 25ms sleep. PRIntervalTime delay = PR_MillisecondsToInterval(25); PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20); while (mThreadCount && PR_IntervalNow() < stopTime) PR_Sleep(delay); #endif + + mozilla::DebugOnly<nsresult> rv = GetAddrInfoShutdown(); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to shutdown GetAddrInfo"); } void nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ) { NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued"); PR_REMOVE_LINK(aRec); @@ -838,29 +872,64 @@ nsHostResolver::ConditionallyCreateThrea #if defined(PR_LOGGING) else LOG((" Unable to find a thread for looking up host [%s].\n", rec->host)); #endif return NS_OK; } 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"); nsresult rv = NS_OK; NS_ASSERTION(!rec->resolving, "record is already being resolved"); // 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--; } if (IsHighPriority(rec->flags)) PR_APPEND_LINK(rec, &mHighQ); else if (IsMediumPriority(rec->flags)) PR_APPEND_LINK(rec, &mMediumQ); @@ -973,82 +1042,175 @@ nsHostResolver::GetHostToLookup(nsHostRe } // tell thread to exit... mThreadCount--; return false; } 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. PRCList cbs; PR_INIT_CLIST(&cbs); { 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); + + LOG(("OnLookupComplete: %s for UNSPEC request %s host %s.", + PR_AF_INET == rec->mInnerAF ? "INET" : "INET6", + rec->mCloneOf ? "clone" : "original", + rec->host)); + + nsHostRecord* originalRecord = rec->mCloneOf ? rec->mCloneOf : rec; + + { + MutexAutoLock lock(originalRecord->addr_info_lock); - // 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 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)); - 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; + if (rec->usingAnyThread) { + mActiveAnyThreadCount--; + rec->usingAnyThread = false; + } + + // 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 (!mShutdown) { - // add to mEvictionQ - PR_APPEND_LINK(rec, &mEvictionQ); - NS_ADDREF(rec); - if (mEvictionQSize < mMaxCacheEntries) - mEvictionQSize++; +#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 { - // remove first element on mEvictionQ - nsHostRecord *head = - static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ)); - PR_REMOVE_AND_INIT_LINK(head); - PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE); + // One minute for negative cache + rec->expiration += TimeDuration::FromSeconds(60); + rec->negative = true; + } + rec->resolving = false; + + if (rec->usingAnyThread) { + mActiveAnyThreadCount--; + rec->usingAnyThread = false; + } - 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<uint32_t>(age.ToSeconds() / 60)); + 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<nsHostRecord *>(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<uint32_t>(age.ToSeconds() / 60)); + } + + // release reference to rec owned by mEvictionQ + NS_RELEASE(head); } - - // 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)) { PRCList *node = cbs.next; while (node != &cbs) { nsResolveHostCallback *callback = static_cast<nsResolveHostCallback *>(node); node = node->next; @@ -1137,72 +1299,58 @@ nsHostResolver::ThreadFunc(void *arg) static nsThreadPoolNaming naming; naming.SetThreadPoolName(NS_LITERAL_CSTRING("DNS Resolver")); #if defined(RES_RETRY_ON_FAILURE) nsResState rs; #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<uint32_t>(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")); } nsresult nsHostResolver::Create(uint32_t maxCacheEntries, uint32_t maxCacheLifetime,
--- a/netwerk/dns/nsHostResolver.h +++ b/netwerk/dns/nsHostResolver.h @@ -11,31 +11,43 @@ #include "prnetdb.h" #include "pldhash.h" #include "mozilla/CondVar.h" #include "mozilla/Mutex.h" #include "nsISupportsImpl.h" #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" class nsHostResolver; class nsHostRecord; class nsResolveHostCallback; #define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY 3 #define MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY 5 #define MAX_NON_PRIORITY_REQUESTS 150 #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; uint16_t flags; uint16_t af; }; /** @@ -64,17 +76,20 @@ public: * are mutable and accessed by the resolver worker thread and the * nsDNSService2 class. |addr| doesn't change after it has been * assigned a value. only the resolver worker thread modifies * nsHostRecord (and only in nsHostResolver::OnLookupComplete); * the other threads just read it. therefore the resolver worker * 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. Negative cache entries are valid just like any other (though never for more than 60 seconds), but a use of that negative entry forces an asynchronous refresh. */ mozilla::TimeStamp expiration; @@ -96,16 +111,33 @@ private: bool resolving; /* true if this record is being resolved, which means * that it is either on the pending queue or owned by * one of the worker threads. */ bool onQueue; /* true if pending and on the queue (not yet given to getaddrinfo())*/ 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. nsTArray<nsCString> mBlacklistedItems; explicit nsHostRecord(const nsHostKey *key); /* use Create() instead */ ~nsHostRecord(); }; @@ -239,23 +271,30 @@ public: size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; private: explicit nsHostResolver(uint32_t maxCacheEntries = 50, uint32_t maxCacheLifetime = 60, uint32_t lifetimeGracePeriod = 0); ~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. */ nsresult ConditionallyRefreshRecord(nsHostRecord *rec, const char *host); static void MoveQueue(nsHostRecord *aRec, PRCList &aDestQ);