Bug 820391: Use DnsQuery on Windows. r=sworkman
authorjosullivan <josullivan@mozilla.com>
Fri, 15 Aug 2014 17:25:06 -0700
changeset 200201 4741ef815af991357d609870965c8111ee1b7073
parent 200200 293f3a5eabdb6bf213a762bca37b5b0a128e1dad
child 200202 a5de6212a18cb19d7a26ea363cd8bc9cd78bb88b
push id27337
push useremorley@mozilla.com
push dateTue, 19 Aug 2014 12:40:34 +0000
treeherdermozilla-central@a38daccaa557 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssworkman
bugs820391, 100644
milestone34.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
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
netwerk/dns/DNS.h
netwerk/dns/GetAddrInfo.cpp
netwerk/dns/GetAddrInfo.h
netwerk/dns/moz.build
netwerk/dns/nsHostResolver.cpp
netwerk/dns/nsHostResolver.h
--- 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);