From e5d322669402c9af58a34dce2b4e29ca1125b4ef Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 1 Feb 2018 10:20:49 +0100 Subject: [PATCH] bug 1434852 - introducing TRR (DOH); r=mcmanus,valentin Provides an optional resolver mechanism for Firefox that allows running together with or instead of the native resolver. TRR offers resolving of host names using a dedicated DNS-over-HTTPS server (HTTPS is required, HTTP/2 is preferable). DNS-over-HTTPS (DOH) allows DNS resolves with enhanced privacy, secure transfers and improved performance. To keep the failure rate at a minimum, the TRR system manages a dynamic persistent blacklist for host names that can't be resolved with DOH but works with the native resolver. Blacklisted entries will not be retried over DOH for a couple of days. "localhost" and names in the ".local" TLD will not be resolved via DOH. TRR is preffed OFF by default and you need to set a URI for an available DOH server to be able to use it. Since the URI for DOH is set with a name itself, it may have to use the native resolver for bootstrapping. (Optionally, the user can set the IP address of the DOH server in a pref to avoid the required initial native resolve.) When TRR starts up, it will first verify that it works by checking a "confirmation" domain name. This confirmation domain is a pref by default set to "example.com". TRR will also by default await the captive-portal detection to raise its green flag before getting activated. All prefs for TRR are under the "network.trr" hierarchy. The DNS-over-HTTPS spec: https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-03 MozReview-Commit-ID: GuuU6vjTjlm --HG-- extra : rebase_source : 53fcca757334090ac05fec540ef29d109d5ceed3 --- modules/libpref/init/all.js | 28 + netwerk/base/nsIChannel.idl | 2 +- netwerk/base/nsISocketTransport.idl | 7 + netwerk/base/nsSocketTransport2.cpp | 13 + netwerk/dns/DNS.cpp | 33 + netwerk/dns/DNS.h | 14 +- netwerk/dns/DNSRequestChild.cpp | 7 + netwerk/dns/GetAddrInfo.cpp | 18 +- netwerk/dns/GetAddrInfo.h | 8 +- netwerk/dns/TRR.cpp | 1047 +++++++++++++++++ netwerk/dns/TRR.h | 170 +++ netwerk/dns/TRRService.cpp | 510 ++++++++ netwerk/dns/TRRService.h | 88 ++ netwerk/dns/moz.build | 3 + netwerk/dns/nsDNSService2.cpp | 24 + netwerk/dns/nsDNSService2.h | 3 +- netwerk/dns/nsHostResolver.cpp | 760 +++++++++--- netwerk/dns/nsHostResolver.h | 121 +- netwerk/dns/nsIDNSRecord.idl | 5 + netwerk/dns/nsIDNSService.idl | 6 + netwerk/protocol/http/Http2Session.h | 2 +- netwerk/protocol/http/HttpBaseChannel.cpp | 19 + netwerk/protocol/http/HttpBaseChannel.h | 3 + netwerk/protocol/http/nsHttp.h | 2 +- netwerk/protocol/http/nsHttpChannel.cpp | 6 + netwerk/protocol/http/nsHttpConnection.cpp | 22 +- netwerk/protocol/http/nsHttpConnection.h | 4 + netwerk/protocol/http/nsHttpHandler.cpp | 2 +- .../protocol/http/nsIHttpChannelInternal.idl | 6 + netwerk/test/unit/test_trr.js | 448 +++++++ netwerk/test/unit/xpcshell.ini | 3 + security/manager/ssl/DataStorageList.h | 1 + testing/xpcshell/moz-http2/moz-http2.js | 160 +++ toolkit/components/telemetry/Histograms.json | 74 +- 34 files changed, 3400 insertions(+), 219 deletions(-) create mode 100644 netwerk/dns/TRR.cpp create mode 100644 netwerk/dns/TRR.h create mode 100644 netwerk/dns/TRRService.cpp create mode 100644 netwerk/dns/TRRService.h create mode 100644 netwerk/test/unit/test_trr.js diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 6f482b28c15f..a8b4b20a9bc2 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2091,6 +2091,10 @@ pref("network.dnsCacheExpiration", 60); // Get TTL; not supported on all platforms; nop on the unsupported ones. pref("network.dns.get-ttl", true); +// For testing purposes! Makes the native resolver resolve IPv4 "localhost" +// instead of the actual given name. +pref("network.dns.native-is-localhost", false); + // The grace period allows the DNS cache to use expired entries, while kicking off // a revalidation in the background. pref("network.dnsCacheExpirationGracePeriod", 60); @@ -5404,6 +5408,30 @@ pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes pref("network.captive-portal-service.backoffFactor", "5.0"); pref("network.captive-portal-service.enabled", false); +// DNS Trusted Recursive Resolver +// 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow +pref("network.trr.mode", 0); +// DNS-over-HTTP service to use, must be HTTPS:// +pref("network.trr.uri", ""); +// credentials to pass to DOH end-point +pref("network.trr.credentials", ""); +// Wait for captive portal confirmation before enabling TRR +pref("network.trr.wait-for-portal", true); +// Allow RFC1918 address in responses? +pref("network.trr.allow-rfc1918", false); +// Use GET (rather than POST) +pref("network.trr.useGET", false); +// Before TRR is widely used the NS record for this host is fetched +// from the DOH end point to ensure proper configuration +pref("network.trr.confirmationNS", "example.com"); +// hardcode the resolution of the hostname in network.trr.uri instead of +// relying on the system resolver to do it for you +pref("network.trr.bootstrapAddress", ""); +// TRR blacklist entry expire time (in seconds). Default is 72 hours. +pref("network.trr.blacklist-duration", 259200); +// Single TRR request timeout, in milliseconds +pref("network.trr.request-timeout", 3000); + pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt"); pref("captivedetect.canonicalContent", "success\n"); pref("captivedetect.maxWaitingTime", 5000); diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl index 2be8e88c3736..c231d8668e9b 100644 --- a/netwerk/base/nsIChannel.idl +++ b/netwerk/base/nsIChannel.idl @@ -201,7 +201,7 @@ interface nsIChannel : nsIRequest /************************************************************************** * Channel specific load flags: * - * Bits 26-31 are reserved for future use by this interface or one of its + * Bits 16-31 are reserved for future use by this interface or one of its * derivatives (e.g., see nsICachingChannel). */ diff --git a/netwerk/base/nsISocketTransport.idl b/netwerk/base/nsISocketTransport.idl index bea19d94ca7c..ceefcc483f58 100644 --- a/netwerk/base/nsISocketTransport.idl +++ b/netwerk/base/nsISocketTransport.idl @@ -237,6 +237,13 @@ interface nsISocketTransport : nsITransport */ const unsigned long BE_CONSERVATIVE = (1 << 7); + /** + * If set, do not use TRR for resolving the host name. Intended only for + * retries or other scenarios when TRR is deemed likely to have returned a + * wrong adddress. + */ + const unsigned long DISABLE_TRR = (1 << 8); + /** * An opaque flags for non-standard behavior of the TLS system. * It is unlikely this will need to be set outside of telemetry studies diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp index 1b954a2511b6..d4ebfa52f7ac 100644 --- a/netwerk/base/nsSocketTransport2.cpp +++ b/netwerk/base/nsSocketTransport2.cpp @@ -1110,6 +1110,8 @@ nsSocketTransport::ResolveHost() dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; + if (mConnectionFlags & nsSocketTransport::DISABLE_TRR) + dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR; NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) || !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), @@ -1802,6 +1804,17 @@ nsSocketTransport::RecoverFromError() mState = STATE_CLOSED; mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); tryAgain = true; + } else if (!(mConnectionFlags & DISABLE_TRR)) { + bool trrEnabled; + mDNSRecord->IsTRR(&trrEnabled); + if (trrEnabled) { + // Drop state to closed. This will trigger a new round of + // DNS resolving. + SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n")); + mState = STATE_CLOSED; + mConnectionFlags |= DISABLE_TRR; + tryAgain = true; + } } } } diff --git a/netwerk/dns/DNS.cpp b/netwerk/dns/DNS.cpp index 12ee1ff73067..d08b9b69f2dd 100644 --- a/netwerk/dns/DNS.cpp +++ b/netwerk/dns/DNS.cpp @@ -1,3 +1,5 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ /* 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/. */ @@ -296,6 +298,7 @@ AddrInfo::AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, : mHostName(nullptr) , mCanonicalName(nullptr) , ttl(NO_TTL_DATA) + , mFromTRR(false) { MOZ_ASSERT(prAddrInfo, "Cannot construct AddrInfo with a null prAddrInfo pointer!"); const uint32_t nameCollisionAddr = htonl(0x7f003535); // 127.0.53.53 @@ -319,10 +322,40 @@ AddrInfo::AddrInfo(const char *host, const char *cname) : mHostName(nullptr) , mCanonicalName(nullptr) , ttl(NO_TTL_DATA) + , mFromTRR(false) { Init(host, cname); } +AddrInfo::AddrInfo(const char *host, unsigned int aTRR) + : mHostName(nullptr) + , mCanonicalName(nullptr) + , ttl(NO_TTL_DATA) + , mFromTRR(aTRR) +{ + Init(host, nullptr); +} + +// deep copy constructor +AddrInfo::AddrInfo(const AddrInfo *src) +{ + mHostName = nullptr; + if (src->mHostName) { + mHostName = strdup(src->mHostName); + } + mCanonicalName = nullptr; + if (src->mCanonicalName) { + mCanonicalName = strdup(src->mCanonicalName); + } + ttl = src->ttl; + mFromTRR = src->mFromTRR; + + for (auto element = src->mAddresses.getFirst(); element; + element = element->getNext()) { + AddAddress(new NetAddrElement(*element)); + } +} + AddrInfo::~AddrInfo() { NetAddrElement *addrElement; diff --git a/netwerk/dns/DNS.h b/netwerk/dns/DNS.h index 15b40e652c38..13d0ef2a196b 100644 --- a/netwerk/dns/DNS.h +++ b/netwerk/dns/DNS.h @@ -135,13 +135,18 @@ class AddrInfo { public: // Creates an AddrInfo object. It calls the AddrInfo(const char*, const char*) // to initialize the host and the cname. - AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, bool disableIPv4, - bool filterNameCollision, const char *cname); + explicit AddrInfo(const char *host, const PRAddrInfo *prAddrInfo, bool disableIPv4, + bool filterNameCollision, const char *cname); // Creates a basic AddrInfo object (initialize only the host and the cname). - AddrInfo(const char *host, const char *cname); + explicit AddrInfo(const char *host, const char *cname); + + // Creates a basic AddrInfo object (initialize only the host and TRR status). + explicit AddrInfo(const char *host, unsigned int TRRType); ~AddrInfo(); + explicit AddrInfo(const AddrInfo *src); // copy + void AddAddress(NetAddrElement *address); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; @@ -152,8 +157,9 @@ public: static const uint32_t NO_TTL_DATA = (uint32_t) -1; LinkedList mAddresses; - + unsigned int IsTRR() { return mFromTRR; } private: + unsigned int mFromTRR; void Init(const char *host, const char *cname); }; diff --git a/netwerk/dns/DNSRequestChild.cpp b/netwerk/dns/DNSRequestChild.cpp index e8c902138c17..4bb8eab60070 100644 --- a/netwerk/dns/DNSRequestChild.cpp +++ b/netwerk/dns/DNSRequestChild.cpp @@ -81,6 +81,13 @@ ChildDNSRecord::GetCanonicalName(nsACString &result) return NS_OK; } +NS_IMETHODIMP +ChildDNSRecord::IsTRR(bool *retval) +{ + *retval = false; + return NS_ERROR_NOT_AVAILABLE; +} + NS_IMETHODIMP ChildDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr) { diff --git a/netwerk/dns/GetAddrInfo.cpp b/netwerk/dns/GetAddrInfo.cpp index c25822e74736..32211b125320 100644 --- a/netwerk/dns/GetAddrInfo.cpp +++ b/netwerk/dns/GetAddrInfo.cpp @@ -20,7 +20,7 @@ #include "mozilla/Logging.h" -#if DNSQUERY_AVAILABLE +#ifdef DNSQUERY_AVAILABLE // 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 @@ -40,7 +40,7 @@ static LazyLogModule gGetAddrInfoLog("GetAddrInfo"); #define LOG_WARNING(msg, ...) \ MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__)) -#if DNSQUERY_AVAILABLE +#ifdef DNSQUERY_AVAILABLE //////////////////////////// // WINDOWS IMPLEMENTATION // //////////////////////////// @@ -302,7 +302,7 @@ nsresult GetAddrInfoInit() { LOG("Initializing GetAddrInfo.\n"); -#if DNSQUERY_AVAILABLE +#ifdef DNSQUERY_AVAILABLE return _GetAddrInfoInit_Windows(); #else return NS_OK; @@ -313,7 +313,7 @@ nsresult GetAddrInfoShutdown() { LOG("Shutting down GetAddrInfo.\n"); -#if DNSQUERY_AVAILABLE +#ifdef DNSQUERY_AVAILABLE return _GetAddrInfoShutdown_Windows(); #else return NS_OK; @@ -328,18 +328,24 @@ GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags, return NS_ERROR_NULL_POINTER; } -#if DNSQUERY_AVAILABLE +#ifdef DNSQUERY_AVAILABLE // The GetTTLData needs the canonical name to function properly if (aGetTtl) { aFlags |= nsHostResolver::RES_CANON_NAME; } #endif + if (gNativeIsLocalhost) { + // pretend we use the given host but use IPv4 localhost instead! + aHost = "localhost"; + aAddressFamily = PR_AF_INET; + } + *aAddrInfo = nullptr; nsresult rv = _GetAddrInfo_Portable(aHost, aAddressFamily, aFlags, aNetworkInterface, aAddrInfo); -#if DNSQUERY_AVAILABLE +#ifdef DNSQUERY_AVAILABLE if (aGetTtl && NS_SUCCEEDED(rv)) { // Figure out the canonical name, or if that fails, just use the host name // we have. diff --git a/netwerk/dns/GetAddrInfo.h b/netwerk/dns/GetAddrInfo.h index 57c008dd506c..59b0a43520a4 100644 --- a/netwerk/dns/GetAddrInfo.h +++ b/netwerk/dns/GetAddrInfo.h @@ -12,10 +12,8 @@ #if defined(XP_WIN) #define DNSQUERY_AVAILABLE 1 -#define TTL_AVAILABLE 1 #else -#define DNSQUERY_AVAILABLE 0 -#define TTL_AVAILABLE 0 +#undef DNSQUERY_AVAILABLE #endif namespace mozilla { @@ -35,8 +33,8 @@ class AddrInfo; * hostname (PR_AI_NOCANONNAME will be ignored if the TTL is retrieved). * @param aAddrInfo[out] Will point to the results of the host lookup, or be * null if the lookup failed. - * @param aGetTtl[in] If true, and TTL_AVAILABLE is truthy, the TTL will be - * retrieved if DNS provides the answers.. + * @param aGetTtl[in] If true, the TTL will be retrieved if DNS provides the + * answers.. */ nsresult GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags, diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp new file mode 100644 index 000000000000..df157695be5d --- /dev/null +++ b/netwerk/dns/TRR.cpp @@ -0,0 +1,1047 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* 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/. */ + +#include "DNS.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsHostResolver.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIIOService.h" +#include "nsIInputStream.h" +#include "nsISupportsBase.h" +#include "nsISupportsUtils.h" +#include "nsIUploadChannel2.h" +#include "nsNetUtil.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "nsURLHelper.h" +#include "TRR.h" +#include "TRRService.h" + +#include "mozilla/Base64.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Tokenizer.h" + +namespace mozilla { +namespace net { + +#undef LOG +extern mozilla::LazyLogModule gHostResolverLog; +#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug) + +NS_IMPL_ISUPPORTS(TRR, nsIHttpPushListener, nsIInterfaceRequestor, nsIStreamListener, nsIRunnable) + +const uint8_t kDNS_CLASS_IN = 1; + +NS_IMETHODIMP +TRR::Notify(nsITimer *aTimer) +{ + if (aTimer == mTimeout) { + LOG(("TRR request for %s timed out\n", mHost.get())); + mTimeout = nullptr; + Cancel(); + } else { + MOZ_CRASH("Unknown timer"); + } + + return NS_OK; +} + +// convert a given host request to a DOH 'body' +// +nsresult +TRR::DohEncode(nsCString &aBody) +{ + aBody.Truncate(); + // Header + aBody += '\0'; + aBody += '\0'; // 16 bit id + aBody += '\0'; // |QR| Opcode |AA|TC|RD| + aBody += '\0'; // |RA| Z | RCODE | + aBody += '\0'; + aBody += 1; // QDCOUNT (number of entries in the question section) + aBody += '\0'; + aBody += '\0'; // ANCOUNT + aBody += '\0'; + aBody += '\0'; // NSCOUNT + aBody += '\0'; + aBody += '\0'; // ARCOUNT + + // Question + + // The input host name should be converted to a sequence of labels, where + // each label consists of a length octet followed by that number of + // octets. The domain name terminates with the zero length octet for the + // null label of the root. + // Followed by 16 bit QTYPE and 16 bit QCLASS + + int32_t index = 0; + int32_t offset = 0; + do { + bool dotFound = false; + int32_t labelLength; + index = mHost.FindChar('.', offset); + if (kNotFound != index) { + dotFound = true; + labelLength = index - offset; + } else { + labelLength = mHost.Length() - offset; + } + if (labelLength > 63) { + // too long label! + return NS_ERROR_ILLEGAL_VALUE; + } + aBody += static_cast(labelLength); + nsDependentCSubstring label = Substring(mHost, offset, labelLength); + aBody.Append(label); + if(!dotFound) { + aBody += '\0'; // terminate with a final zero + break; + } + offset += labelLength + 1; // move over label and dot + } while(1); + + aBody += '\0'; // upper 8 bit TYPE + aBody += static_cast(mType); + aBody += '\0'; // upper 8 bit CLASS + aBody += kDNS_CLASS_IN; // IN - "the Internet" + + return NS_OK; +} + +NS_IMETHODIMP +TRR::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTRRService); + if (NS_FAILED(SendHTTPRequest())) { + FailData(); + // The dtor will now be run + } + return NS_OK; +} + +nsresult +TRR::SendHTTPRequest() +{ + // This is essentially the "run" method - created from nsHostResolver + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + + if ((mType != TRRTYPE_A) && (mType != TRRTYPE_AAAA) && (mType != TRRTYPE_NS)) { + // limit the calling interface because nsHostResolver has explicit slots for + // these types + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsCOMPtr ios(do_GetIOService(&rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool useGet = mTRRService->UseGET(); + nsAutoCString body; + nsCOMPtr dnsURI; + + LOG(("TRR::SendHTTPRequest resolve %s type %u\n", mHost.get(), mType)); + + if (useGet) { + nsAutoCString tmp; + rv = DohEncode(tmp); + NS_ENSURE_SUCCESS(rv, rv); + + /* For GET requests, the outgoing packet needs to be Base64url-encoded and + then appended to the end of the URI. */ + rv = Base64URLEncode(tmp.Length(), reinterpret_cast(tmp.get()), + Base64URLEncodePaddingPolicy::Omit, body); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString uri; + mTRRService->GetURI(uri); + uri.Append(NS_LITERAL_CSTRING("?ct&dns=")); + uri.Append(body); + rv = NS_NewURI(getter_AddRefs(dnsURI), uri); + } else { + rv = DohEncode(body); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString uri; + mTRRService->GetURI(uri); + rv = NS_NewURI(getter_AddRefs(dnsURI), uri); + } + if (NS_FAILED(rv)) { + LOG(("TRR:SendHTTPRequest: NewURI failed!\n")); + return rv; + } + + rv = NS_NewChannel(getter_AddRefs(mChannel), + dnsURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // PerformanceStorage + nullptr, // aLoadGroup + this, + nsIRequest::LOAD_ANONYMOUS, ios); + if (NS_FAILED(rv)) { + LOG(("TRR:SendHTTPRequest: NewChannel failed!\n")); + return rv; + } + + nsCOMPtr httpChannel = do_QueryInterface(mChannel); + if (!httpChannel) { + return NS_ERROR_UNEXPECTED; + } + + rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), + NS_LITERAL_CSTRING("application/dns-udpwireformat"), + false); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString cred; + mTRRService->GetCredentials(cred); + if (!cred.IsEmpty()){ + rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Authorization"), cred, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr internalChannel = do_QueryInterface(mChannel); + if (!internalChannel) { + return NS_ERROR_UNEXPECTED; + } + + // setting a small stream window means the h2 stack won't pipeline a window update + // with each HEADERS or reply to a DATA with a WINDOW UPDATE + rv = internalChannel->SetInitialRwin(127 * 1024); + NS_ENSURE_SUCCESS(rv, rv); + rv = internalChannel->SetTrr(true); + NS_ENSURE_SUCCESS(rv, rv); + + if (useGet) { + rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET")); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Cache-Control"), + NS_LITERAL_CSTRING("no-store"), false); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); + if (!uploadChannel) { + return NS_ERROR_UNEXPECTED; + } + nsCOMPtr uploadStream; + rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), body); + NS_ENSURE_SUCCESS(rv, rv); + + rv = uploadChannel->ExplicitSetUploadStream(uploadStream, + NS_LITERAL_CSTRING("application/dns-udpwireformat"), + body.Length(), + NS_LITERAL_CSTRING("POST"), false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // set the *default* response content type + if (NS_FAILED(httpChannel->SetContentType(NS_LITERAL_CSTRING("application/dns-udpwireformat")))) { + LOG(("TRR::SendHTTPRequest: couldn't set content-type!\n")); + } + if (NS_SUCCEEDED(httpChannel->AsyncOpen2(this))) { + NS_NewTimerWithCallback(getter_AddRefs(mTimeout), + this, mTRRService->GetRequestTimeout(), + nsITimer::TYPE_ONE_SHOT); + return NS_OK; + } + mChannel = nullptr; + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +TRR::GetInterface(const nsIID &iid, void **result) +{ + if (!iid.Equals(NS_GET_IID(nsIHttpPushListener))) { + return NS_ERROR_NO_INTERFACE; + } + + nsCOMPtr copy(this); + *result = copy.forget().take(); + return NS_OK; +} + +nsresult +TRR::DohDecodeQuery(const nsCString &query, nsCString &host, enum TrrType &type) +{ + FallibleTArray binary; + bool found_dns = false; + LOG(("TRR::DohDecodeQuery %s!\n", query.get())); + + // extract "dns=" from the query string + nsCCharSeparatedTokenizer tokenizer(query, '&'); + nsAutoCString data; + while (tokenizer.hasMoreTokens()) { + const nsACString& token = tokenizer.nextToken(); + nsDependentCSubstring dns = Substring(token, 0, 4); + nsAutoCString check(dns); + if (check.Equals("dns=")) { + nsDependentCSubstring q = Substring(token, 4, -1); + data = q; + found_dns = true; + break; + } + } + if (!found_dns) { + LOG(("TRR::DohDecodeQuery no dns= in pushed URI query string\n")); + return NS_ERROR_ILLEGAL_VALUE; + } + + nsresult rv = Base64URLDecode(data, + Base64URLDecodePaddingPolicy::Ignore, binary); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t avail = binary.Length(); + if (avail < 12) { + return NS_ERROR_FAILURE; + } + // check the query bit and the opcode + if ((binary[2] & 0xf8) != 0) { + return NS_ERROR_FAILURE; + } + uint32_t qdcount = (binary[4] << 8) + binary[5]; + if (!qdcount) { + return NS_ERROR_FAILURE; + } + + uint32_t index = 12; + uint32_t length = 0; + host.Truncate(); + do { + if (avail < (index + 1)) { + return NS_ERROR_UNEXPECTED; + } + + length = binary[index]; + if (length) { + if (host.Length()) { + host.Append("."); + } + if (avail < (index + 1 + length)) { + return NS_ERROR_UNEXPECTED; + } + host.Append((const char *)(&binary[0]) + index + 1, length); + } + index += 1 + length; // skip length byte + label + } while (length); + + LOG(("TRR::DohDecodeQuery host %s\n", host.get())); + + if (avail < (index + 2)) { + return NS_ERROR_UNEXPECTED; + } + uint16_t i16 = 0; + i16 += binary[index] << 8; + i16 += binary[index + 1]; + index += 4; // skip question's type, class + type = (enum TrrType)i16; + + LOG(("TRR::DohDecodeQuery type %d\n", (int)type)); + + return NS_OK; +} + +nsresult +TRR::ReceivePush(nsIHttpChannel *pushed, nsHostRecord *pushedRec) +{ + if (!mHostResolver) { + return NS_ERROR_UNEXPECTED; + } + + LOG(("TRR::ReceivePush: PUSH incoming!\n")); + + nsCOMPtr uri; + pushed->GetURI(getter_AddRefs(uri)); + nsAutoCString query; + if (uri) { + uri->GetQuery(query); + } + + PRNetAddr tempAddr; + if (NS_FAILED(DohDecodeQuery(query, mHost, mType)) || + (PR_StringToNetAddr(mHost.get(), &tempAddr) == PR_SUCCESS)) { // literal + LOG(("TRR::ReceivePush failed to decode %s\n", mHost.get())); + return NS_ERROR_UNEXPECTED; + } + + RefPtr hostRecord; + nsresult rv; + rv = mHostResolver->GetHostRecord(mHost.get(), + pushedRec->flags, pushedRec->af, + pushedRec->pb, pushedRec->netInterface, + pushedRec->originSuffix, + getter_AddRefs(hostRecord)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = mHostResolver->TrrLookup_unlocked(hostRecord, this); + if (NS_FAILED(rv)) { + return rv; + } + + rv = pushed->AsyncOpen2(this); + if (NS_FAILED(rv)) { + return rv; + } + + // OK! + mChannel = pushed; + mRec.swap(hostRecord); + + return NS_OK; +} + +NS_IMETHODIMP +TRR::OnPush(nsIHttpChannel *associated, nsIHttpChannel *pushed) +{ + LOG(("TRR::OnPush entry\n")); + MOZ_ASSERT(associated == mChannel); + if (!mRec) { + return NS_ERROR_FAILURE; + } + + RefPtr trr = new TRR(mHostResolver, mPB); + return trr->ReceivePush(pushed, mRec); +} + +NS_IMETHODIMP +TRR::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + LOG(("TRR::OnStartRequest %p %s %d\n", this, mHost.get(), mType)); + mStartTime = TimeStamp::Now(); + return NS_OK; +} + +static uint16_t get16bit(unsigned char *aData, int index) +{ + return ((aData[index] << 8) | aData[index + 1]); +} + +static uint32_t get32bit(unsigned char *aData, int index) +{ + return (aData[index] << 24) | (aData[index+1] << 16) | + (aData[index+2] << 8) | aData[index+3]; +} + +// +// DohDecode() collects the TTL and the IP addresses in the response +// +nsresult +TRR::DohDecode() +{ + // The response has a 12 byte header followed by the question (returned) + // and then the answer. The answer section itself contains the name, type + // and class again and THEN the record data. + + // www.example.com response: + // header: + // abcd 8180 0001 0001 0000 0000 + // the question: + // 0377 7777 0765 7861 6d70 6c65 0363 6f6d 0000 0100 01 + // the answer: + // 03 7777 7707 6578 616d 706c 6503 636f 6d00 0001 0001 + // 0000 0080 0004 5db8 d822 + + unsigned int index = 12; + uint8_t length; + nsAutoCString host; + + LOG(("doh decode %s %d bytes\n", mHost.get(), mBodySize)); + + mCname.Truncate(); + + if (mBodySize < 12 || mResponse[0] || mResponse[1]) { + LOG(("TRR bad incoming DOH, eject!\n")); + return NS_ERROR_ILLEGAL_VALUE; + } + uint8_t rcode = mResponse[3] & 0x0F; + if (rcode) { + LOG(("TRR Decode %s RCODE %d\n", mHost.get(), rcode)); + return NS_ERROR_FAILURE; + } + + uint16_t questionRecords = get16bit(mResponse, 4); // qdcount + // iterate over the single(?) host name in question + while (questionRecords) { + do { + if (mBodySize < (index + 1)) { + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if (length) { + if (host.Length()) { + host.Append("."); + } + if (mBodySize < (index + 1 + length)) { + return NS_ERROR_ILLEGAL_VALUE; + } + host.Append(((char *)mResponse) + index + 1, length); + } + index += 1 + length; // skip length byte + label + } while (length); + if (mBodySize < (index + 4)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 4; // skip question's type, class + questionRecords--; + } + + // Figure out the number of answer records from ANCOUNT + uint16_t answerRecords = get16bit(mResponse, 6); + + LOG(("TRR Decode: %d answer records (%u bytes body) %s index=%u\n", + answerRecords, mBodySize, host.get(), index)); + + while (answerRecords) { + if (mBodySize < (index + 1)) { + LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index)); + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if ((length & 0xc0) == 0xc0) { + // name pointer, advance over it + if (mBodySize < (index + 2)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 2; + } else if (length & 0xc0) { + // illegal length, bail out + LOG(("TRR: illegal label length byte (%x)\n", length)); + return NS_ERROR_ILLEGAL_VALUE; + } else { + // iterate over host name in answer + do { + if (mBodySize < (index + 1)) { + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if (mBodySize < (index + 1 + length)) { + LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index)); + return NS_ERROR_ILLEGAL_VALUE; + } + index += 1 + length; + } while (length); + } + // 16 bit TYPE + if (mBodySize < (index + 2)) { + LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index + 2)); + return NS_ERROR_ILLEGAL_VALUE; + } + uint16_t TYPE = get16bit(mResponse, index); + + if ((TYPE != TRRTYPE_CNAME) && + (TYPE != static_cast(mType))) { + // Not the same type as was asked for nor CNAME + LOG(("TRR: Dohdecode:%d asked for type %d got %d\n", __LINE__, + mType, TYPE)); + return NS_ERROR_UNEXPECTED; + } + index += 2; + + // 16 bit class + if (mBodySize < (index + 2)) { + LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index + 2)); + return NS_ERROR_ILLEGAL_VALUE; + } + uint16_t CLASS = get16bit(mResponse, index); + if (kDNS_CLASS_IN != CLASS) { + LOG(("TRR bad CLASS (%u) at index %d\n", CLASS, index)); + return NS_ERROR_UNEXPECTED; + } + index += 2; + + // 32 bit TTL (seconds) + if (mBodySize < (index + 4)) { + LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index)); + return NS_ERROR_ILLEGAL_VALUE; + } + uint32_t TTL = get32bit(mResponse, index); + index += 4; + + // 16 bit RDLENGTH + if (mBodySize < (index + 2)) { + LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index)); + return NS_ERROR_ILLEGAL_VALUE; + } + uint16_t RDLENGTH = get16bit(mResponse, index); + index += 2; + + if (mBodySize < (index + RDLENGTH)) { + LOG(("TRR: Dohdecode:%d fail RDLENGTH=%d at index %d\n", __LINE__, + RDLENGTH, index)); + return NS_ERROR_ILLEGAL_VALUE; + } + + // RDATA + // - A (TYPE 1): 4 bytes + // - AAAA (TYPE 28): 16 bytes + // - NS (TYPE 2): N bytes + + nsresult rv; + switch(TYPE) { + case TRRTYPE_A: + if (RDLENGTH != 4) { + LOG(("TRR bad length for A (%u)\n", RDLENGTH)); + return NS_ERROR_UNEXPECTED; + } + rv = mDNS.Add(TTL, mResponse, index, RDLENGTH, + mTRRService->AllowRFC1918()); + if (NS_FAILED(rv)) { + LOG(("TRR:DohDecode failed: local IP addresses or unknown IP family\n")); + return rv; + } + break; + case TRRTYPE_AAAA: + if (RDLENGTH != 16) { + LOG(("TRR bad length for AAAA (%u)\n", RDLENGTH)); + return NS_ERROR_UNEXPECTED; + } + rv = mDNS.Add(TTL, mResponse, index, RDLENGTH, + mTRRService->AllowRFC1918()); + if (NS_FAILED(rv)) { + LOG(("TRR got unique/local IPv6 address!\n")); + return rv; + } + break; + + case TRRTYPE_NS: + break; + case TRRTYPE_CNAME: + if (mCname.IsEmpty()) { + uint8_t clength = 0; + unsigned int cindex = index; + do { + if (cindex >= mBodySize) { + LOG(("TRR: bad cname packet\n")); + return NS_ERROR_ILLEGAL_VALUE; + } + clength = static_cast(mResponse[cindex]); + if ((clength & 0xc0) == 0xc0) { + // name pointer, get the new offset (14 bits) + if ((cindex +1) >= mBodySize) { + return NS_ERROR_ILLEGAL_VALUE; + } + // extract the new index position for the next label + uint16_t newpos = (clength & 0x3f) << 8 | mResponse[cindex+1]; + cindex = newpos; + continue; + } else if (clength & 0xc0) { + // any of those bits set individually is an error + LOG(("TRR: bad cname packet\n")); + return NS_ERROR_ILLEGAL_VALUE; + } else { + cindex++; + } + if (clength) { + if (!mCname.IsEmpty()) { + mCname.Append("."); + } + if ((cindex + clength) > mBodySize) { + return NS_ERROR_ILLEGAL_VALUE; + } + mCname.Append((const char *)(&mResponse[cindex]), clength); + cindex += clength; // skip label + } + } while (clength); + + LOG(("TRR::DohDecode CNAME host %s => %s\n", + host.get(), mCname.get())); + } + else { + LOG(("TRR::DohDecode CNAME - ignoring another entry\n")); + } + break; + default: + // skip unknown record types + LOG(("TRR unsupported TYPE (%u) RDLENGTH %u\n", TYPE, RDLENGTH)); + break; + } + + index += RDLENGTH; + LOG(("done with record type %u len %u index now %u of %u\n", + TYPE, RDLENGTH, index, mBodySize)); + answerRecords--; + } + + // NSCOUNT + uint16_t nsRecords = get16bit(mResponse, 8); + LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, mBodySize)); + while (nsRecords) { + if (mBodySize < (index + 1)) { + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if ((length & 0xc0) == 0xc0) { + // name pointer, advance over it + if (mBodySize < (index + 2)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 2; + } else if (length & 0xc0) { + // illegal length, bail out + LOG(("TRR: illegal label length byte (%x)\n", length)); + return NS_ERROR_ILLEGAL_VALUE; + } else { + // iterate over host name in answer + do { + if (mBodySize < (index + 1)) { + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if (mBodySize < (index + 1 + length)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 1 + length; + LOG(("TRR: move over %d bytes\n", 1 + length)); + } while (length); + } + + if (mBodySize < (index + 8)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 2; // type + index += 2; // class + index += 4; // ttl + + // 16 bit RDLENGTH + if (mBodySize < (index + 2)) { + return NS_ERROR_ILLEGAL_VALUE; + } + uint16_t RDLENGTH = get16bit(mResponse, index); + index += 2; + if (mBodySize < (index + RDLENGTH)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += RDLENGTH; + LOG(("done with nsRecord now %u of %u\n", index, mBodySize)); + nsRecords--; + } + + // additional resource records + uint16_t arRecords = get16bit(mResponse, 10); + LOG(("TRR Decode: %d additional resource records (%u bytes body)\n", + arRecords, mBodySize)); + while (arRecords) { + if (mBodySize < (index + 1)) { + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if ((length & 0xc0) == 0xc0) { + // name pointer, advance over it + if (mBodySize < (index + 2)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 2; + } else if (length & 0xc0) { + // illegal length, bail out + LOG(("TRR: illegal label length byte (%x)\n", length)); + return NS_ERROR_ILLEGAL_VALUE; + } else { + // iterate over host name in answer + do { + if (mBodySize < (index + 1)) { + return NS_ERROR_ILLEGAL_VALUE; + } + length = static_cast(mResponse[index]); + if (mBodySize < (index + 1 + length)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 1 + length; + LOG(("TRR: move over %d bytes\n", 1 + length)); + } while (length); + } + + if (mBodySize < (index + 8)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += 2; // type + index += 2; // class + index += 4; // ttl + + // 16 bit RDLENGTH + if (mBodySize < (index + 2)) { + return NS_ERROR_ILLEGAL_VALUE; + } + uint16_t RDLENGTH = get16bit(mResponse, index); + index += 2; + if (mBodySize < (index + RDLENGTH)) { + return NS_ERROR_ILLEGAL_VALUE; + } + index += RDLENGTH; + LOG(("done with additional rr now %u of %u\n", index, mBodySize)); + arRecords--; + } + + if (index != mBodySize) { + LOG(("DohDecode failed to parse entire response body, %u out of %u bytes\n", + index, mBodySize)); + // failed to parse 100%, do not continue + return NS_ERROR_ILLEGAL_VALUE; + } + + if ((mType != TRRTYPE_NS) && mCname.IsEmpty() && + !mDNS.mAddresses.getFirst()) { + // no entries were stored! + LOG(("TRR: No entries were stored!\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +TRR::ReturnData() +{ + // create and populate an AddrInfo instance to pass on + nsAutoPtr ai(new AddrInfo(mHost.get(), mType)); + DOHaddr *item; + uint32_t ttl = AddrInfo::NO_TTL_DATA; + while ((item = static_cast(mDNS.mAddresses.popFirst()))) { + PRNetAddr prAddr; + NetAddrToPRNetAddr(&item->mNet, &prAddr); + auto *addrElement = new NetAddrElement(&prAddr); + ai->AddAddress(addrElement); + if (item->mTtl < ttl) { + // While the DNS packet might return individual TTLs for each address, + // we can only return one value in the AddrInfo class so pick the + // lowest number. + ttl = item->mTtl; + } + } + ai->ttl = ttl; + if (!mHostResolver) { + return NS_ERROR_FAILURE; + } + (void)mHostResolver->CompleteLookup(mRec, NS_OK, ai.forget(), mPB); + if (mTimeout) { + mTimeout->Cancel(); + mTimeout = nullptr; + } + mHostResolver = nullptr; + mRec = nullptr; + return NS_OK; +} + +nsresult +TRR::FailData() +{ + if (!mHostResolver) { + return NS_ERROR_FAILURE; + } + // create and populate an TRR AddrInfo instance to pass on to signal that + // this comes from TRR + AddrInfo *ai = new AddrInfo(mHost.get(), mType); + + (void)mHostResolver->CompleteLookup(mRec, NS_ERROR_FAILURE, ai, mPB); + if (mTimeout) { + mTimeout->Cancel(); + mTimeout = nullptr; + } + mHostResolver = nullptr; + mRec = nullptr; + return NS_OK; +} + +nsresult +TRR::On200Response() +{ + // decode body and create an AddrInfo struct for the response + nsresult rv = DohDecode(); + + if (NS_SUCCEEDED(rv)) { + if (!mCname.IsEmpty()) { + if (!--mCnameLoop) { + LOG(("TRR::On200Response CNAME loop, eject!\n")); + } else { + LOG(("TRR::On200Response CNAME %s => %s (%u)\n", mHost.get(), mCname.get(), + mCnameLoop)); + RefPtr trr = new TRR(mHostResolver, mRec, mCname, + mType, mCnameLoop, mPB); + rv = NS_DispatchToMainThread(trr); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } + } else { + // pass back the response data + ReturnData(); + return NS_OK; + } + } else { + LOG(("TRR::On200Response DohDecode %x\n", (int)rv)); + } + return NS_ERROR_FAILURE; +} + + +NS_IMETHODIMP +TRR::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + // The dtor will be run after the function returns + LOG(("TRR:OnStopRequest %p %s %d failed=%d code=%X\n", + this, mHost.get(), mType, mFailed, (unsigned int)aStatusCode)); + nsCOMPtr channel; + channel.swap(mChannel); + + // if status was "fine", parse the response and pass on the answer + if (!mFailed && NS_SUCCEEDED(aStatusCode)) { + nsCOMPtr httpChannel = do_QueryInterface(aRequest); + if (!httpChannel) { + return NS_ERROR_UNEXPECTED; + } + nsresult rv = NS_OK; + nsAutoCString contentType; + httpChannel->GetContentType(contentType); + if (contentType.Length() && + !contentType.LowerCaseEqualsLiteral("application/dns-udpwireformat")) { + // try and parse missing content-types, but otherwise require udpwireformat + LOG(("TRR:OnStopRequest %p %s %d should fail due to content type %s\n", + this, mHost.get(), mType, contentType.get())); + FailData(); + return NS_OK; + } + + uint32_t httpStatus; + rv = httpChannel->GetResponseStatus(&httpStatus); + if (NS_SUCCEEDED(rv) && httpStatus == 200) { + rv = On200Response(); + if (NS_SUCCEEDED(rv)) { + return rv; + } + } else { + LOG(("TRR:OnStopRequest:%d %p rv %x httpStatus %d\n", __LINE__, + this, (int)rv, httpStatus)); + } + } + + LOG(("TRR:OnStopRequest %p status %x mFailed %d\n", + this, (int)aStatusCode, mFailed)); + FailData(); + return NS_OK; +} + +NS_IMETHODIMP +TRR::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, + const uint32_t aCount) +{ + LOG(("TRR:OnDataAvailable %p %s %d failed=%d aCount=%u\n", + this, mHost.get(), mType, mFailed, (unsigned int)aCount)); + // receive DNS response into the local buffer + if (mFailed) { + return NS_ERROR_FAILURE; + } + + if (aCount + mBodySize > kMaxSize) { + LOG(("TRR::OnDataAvailable:%d fail\n", __LINE__)); + mFailed = true; + return NS_ERROR_FAILURE; + } + + uint32_t count; + nsresult rv = aInputStream->Read((char *)mResponse + mBodySize, aCount, &count); + if (NS_FAILED(rv)) { + LOG(("TRR::OnDataAvailable:%d fail\n", __LINE__)); + mFailed = true; + return rv; + } + MOZ_ASSERT(count == aCount); + mBodySize += aCount; + return NS_OK; +} + +nsresult +DOHresp::Add(uint32_t TTL, unsigned char *dns, int index, uint16_t len, + bool aLocalAllowed) +{ + nsAutoPtr doh(new DOHaddr); + NetAddr *addr = &doh->mNet; + if (4 == len) { + // IPv4 + addr->inet.family = AF_INET; + addr->inet.port = 0; // unknown + addr->inet.ip = ntohl(get32bit(dns, index)); + } else if (16 == len) { + // IPv6 + addr->inet6.family = AF_INET6; + addr->inet6.port = 0; // unknown + addr->inet6.flowinfo = 0; // unknown + addr->inet6.scope_id = 0; // unknown + for(int i = 0; i < 16; i++, index++) { + addr->inet6.ip.u8[i] = dns[index]; + } + } else { + return NS_ERROR_UNEXPECTED; + } + + if (IsIPAddrLocal(addr) && !aLocalAllowed) { + return NS_ERROR_FAILURE; + } + doh->mTtl = TTL; + + if (LOG_ENABLED()) { + char buf[128]; + NetAddrToString(addr, buf, sizeof(buf)); + LOG(("DOHresp:Add %s\n", buf)); + } + mAddresses.insertBack(doh.forget()); + return NS_OK; +} + +class ProxyCancel : public Runnable +{ +public: + explicit ProxyCancel(TRR *aTRR) + : Runnable("proxyTrrCancel") + , mTRR(aTRR) + { } + + NS_IMETHOD Run() override + { + mTRR->Cancel(); + mTRR = nullptr; + return NS_OK; + } + +private: + RefPtr mTRR; +}; + +void +TRR::Cancel() +{ + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(new ProxyCancel(this)); + return; + } + if (mChannel) { + LOG(("TRR: %p canceling Channel %p %s %d\n", this, + mChannel.get(), mHost.get(), mType)); + if (mTimeout) { + mTimeout->Cancel(); + mTimeout = nullptr; + } + mChannel->Cancel(NS_ERROR_ABORT); + } +} + +#undef LOG + +// namespace +} +} diff --git a/netwerk/dns/TRR.h b/netwerk/dns/TRR.h new file mode 100644 index 000000000000..38a66b08f8a3 --- /dev/null +++ b/netwerk/dns/TRR.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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 mozilla_net_TRR_h +#define mozilla_net_TRR_h + +#include "nsIChannel.h" +#include "nsIHttpPushListener.h" +#include "nsIInterfaceRequestor.h" +#include "nsIStreamListener.h" + +namespace mozilla { namespace net { + +// the values map to RFC1035 type identifiers +enum TrrType { + TRRTYPE_A = 1, + TRRTYPE_NS = 2, + TRRTYPE_CNAME = 5, + TRRTYPE_AAAA = 28, +}; + +class DOHaddr : public LinkedListElement { +public: + NetAddr mNet; + uint32_t mTtl; +}; + +class TRRService; +extern TRRService *gTRRService; + +class DOHresp { +public: + ~DOHresp() { + DOHaddr *el; + while ((el = mAddresses.popLast())) { + delete el; + } + } + nsresult Add(uint32_t TTL, unsigned char *dns, int index, uint16_t len, + bool aLocalAllowed); + LinkedList mAddresses; +}; + +class TRR + : public Runnable + , public nsITimerCallback + , public nsIHttpPushListener + , public nsIInterfaceRequestor + , public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIHTTPPUSHLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSITIMERCALLBACK + + // Never accept larger DOH responses than this as that would indicate + // something is wrong. Typical ones are much smaller. + static const unsigned int kMaxSize = 3200; + + // Number of "steps" we follow CNAME chains + static const unsigned int kCnameChaseMax = 64; + + // when firing off a normal A or AAAA query + explicit TRR(AHostResolver *aResolver, + nsHostRecord *aRec, + enum TrrType aType) + : mozilla::Runnable("TRR") + , mRec(aRec) + , mHostResolver(aResolver) + , mTRRService(gTRRService) + , mType(aType) + , mBodySize(0) + , mFailed(false) + , mCnameLoop(kCnameChaseMax) + { + mHost = aRec->host; + mPB = aRec->pb; + } + + // when following CNAMEs + explicit TRR(AHostResolver *aResolver, + nsHostRecord *aRec, + nsCString &aHost, + enum TrrType & aType, + unsigned int aLoopCount, + bool aPB) + : mozilla::Runnable("TRR") + , mHost(aHost) + , mRec(aRec) + , mHostResolver(aResolver) + , mTRRService(gTRRService) + , mType(aType) + , mBodySize(0) + , mFailed(false) + , mPB(aPB) + , mCnameLoop(aLoopCount) + { + + } + + // used on push + explicit TRR(AHostResolver *aResolver, bool aPB) + : mozilla::Runnable("TRR") + , mHostResolver(aResolver) + , mTRRService(gTRRService) + , mBodySize(0) + , mFailed(false) + , mPB(aPB) + , mCnameLoop(kCnameChaseMax) + { } + + // to verify a domain + explicit TRR(AHostResolver *aResolver, + nsACString &aHost, + enum TrrType aType, + bool aPB) + : mozilla::Runnable("TRR") + , mHost(aHost) + , mHostResolver(aResolver) + , mTRRService(gTRRService) + , mType(aType) + , mBodySize(0) + , mFailed(false) + , mPB(aPB) + , mCnameLoop(kCnameChaseMax) + { } + + NS_IMETHOD Run() override; + void Cancel(); + enum TrrType Type() { return mType; } + nsCString mHost; + RefPtr mRec; + RefPtr mHostResolver; + TRRService *mTRRService; + +private: + ~TRR() { if (mTimeout) { mTimeout->Cancel(); } }; + nsresult SendHTTPRequest(); + nsresult DohEncode(nsCString &target); + nsresult DohDecode(); + nsresult ReturnData(); + nsresult FailData(); + nsresult DohDecodeQuery(const nsCString &query, + nsCString &host, enum TrrType &type); + nsresult ReceivePush(nsIHttpChannel *pushed, nsHostRecord *pushedRec); + nsresult On200Response(); + + nsCOMPtr mChannel; + enum TrrType mType; + TimeStamp mStartTime; + unsigned char mResponse[kMaxSize]; + unsigned int mBodySize; + bool mFailed; + bool mPB; + DOHresp mDNS; + nsCOMPtr mTimeout; + nsCString mCname; + uint32_t mCnameLoop; // loop detection counter +}; + +} // namespace net +} // namespace mozilla + +#endif // include guard diff --git a/netwerk/dns/TRRService.cpp b/netwerk/dns/TRRService.cpp new file mode 100644 index 000000000000..a73c0c917346 --- /dev/null +++ b/netwerk/dns/TRRService.cpp @@ -0,0 +1,510 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsICaptivePortalService.h" +#include "nsIObserverService.h" +#include "nsIURIMutator.h" +#include "nsNetUtil.h" +#include "nsStandardURL.h" +#include "TRR.h" +#include "TRRService.h" + +#include "mozilla/Preferences.h" + +static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login"; +static const char kClearPrivateData[] = "clear-private-data"; +static const char kPurge[] = "browser:purge-session-history"; + +#define TRR_PREF_PREFIX "network.trr." +#define TRR_PREF(x) TRR_PREF_PREFIX x + +namespace mozilla { +namespace net { + +#undef LOG +extern mozilla::LazyLogModule gHostResolverLog; +#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args) + +TRRService *gTRRService = nullptr; + +NS_IMPL_ISUPPORTS(TRRService, nsIObserver, nsISupportsWeakReference) + +TRRService::TRRService() + : mInitialized(false) + , mMode(0) + , mTRRBlacklistExpireTime(72 * 3600) + , mTRRTimeout(3000) + , mLock("trrservice") + , mConfirmationNS(NS_LITERAL_CSTRING("example.com")) + , mWaitForCaptive(true) + , mRfc1918(false) + , mCaptiveIsPassed(false) + , mUseGET(false) + , mClearTRRBLStorage(false) + , mConfirmationState(CONFIRM_INIT) +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); +} + +nsresult +TRRService::Init() +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + if (mInitialized) { + return NS_OK; + } + mInitialized = true; + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, NS_CAPTIVE_PORTAL_CONNECTIVITY, true); + observerService->AddObserver(this, kOpenCaptivePortalLoginEvent, true); + observerService->AddObserver(this, kClearPrivateData, true); + observerService->AddObserver(this, kPurge, true); + } + nsCOMPtr prefBranch; + GetPrefBranch(getter_AddRefs(prefBranch)); + if (prefBranch) { + prefBranch->AddObserver(TRR_PREF_PREFIX, this, true); + } + + ReadPrefs(NULL); + + gTRRService = this; + + LOG(("Initialized TRRService\n")); + return NS_OK; +} + +bool +TRRService::Enabled() +{ + if (mConfirmationState == CONFIRM_INIT && !mWaitForCaptive) { + mConfirmationState = CONFIRM_TRYING; + } + + if (mConfirmationState == CONFIRM_TRYING) { + MaybeConfirm(); + } + + return (mConfirmationState == CONFIRM_OK); +} + +void +TRRService::GetPrefBranch(nsIPrefBranch **result) +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + *result = nullptr; + CallGetService(NS_PREFSERVICE_CONTRACTID, result); +} + +nsresult +TRRService::ReadPrefs(const char *name) +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + if (!name || !strcmp(name, TRR_PREF("mode"))) { + // 0 - off, 1 - parallel, 2 - TRR first, 3 - TRR only, 4 - shadow + uint32_t tmp; + if (NS_SUCCEEDED(Preferences::GetUint(TRR_PREF("mode"), &tmp))) { + mMode = tmp; + } + } + if (!name || !strcmp(name, TRR_PREF("uri"))) { + // Base URI, appends "?ct&dns=..." + MutexAutoLock lock(mLock); + nsAutoCString old(mPrivateURI); + Preferences::GetCString(TRR_PREF("uri"), mPrivateURI); + nsAutoCString scheme; + if (!mPrivateURI.IsEmpty()) { + nsCOMPtr ios(do_GetIOService()); + if (ios) { + ios->ExtractScheme(mPrivateURI, scheme); + } + } + if (!mPrivateURI.IsEmpty() && !scheme.Equals("https")) { + LOG(("TRRService TRR URI %s is not https. Not used.\n", + mPrivateURI.get())); + mPrivateURI.Truncate(); + } + if (!mPrivateURI.IsEmpty()) { + LOG(("TRRService TRR URI %s\n", mPrivateURI.get())); + } + if (!old.IsEmpty() && !mPrivateURI.Equals(old)) { + mClearTRRBLStorage = true; + LOG(("TRRService clearing blacklist because of change is uri service\n")); + } + } + if (!name || !strcmp(name, TRR_PREF("credentials"))) { + MutexAutoLock lock(mLock); + Preferences::GetCString(TRR_PREF("credentials"), mPrivateCred); + } + if (!name || !strcmp(name, TRR_PREF("confirmationNS"))) { + MutexAutoLock lock(mLock); + nsAutoCString old(mConfirmationNS); + Preferences::GetCString(TRR_PREF("confirmationNS"), mConfirmationNS); + if (name && !old.IsEmpty() && !mConfirmationNS.Equals(old) && + (mConfirmationState > CONFIRM_TRYING)) { + LOG(("TRR::ReadPrefs: restart confirmationNS state\n")); + mConfirmationState = CONFIRM_TRYING; + } + } + if (!name || !strcmp(name, TRR_PREF("bootstrapAddress"))) { + MutexAutoLock lock(mLock); + Preferences::GetCString(TRR_PREF("bootstrapAddress"), mBootstrapAddr); + } + if (!name || !strcmp(name, TRR_PREF("wait-for-portal"))) { + // Wait for captive portal? + bool tmp; + if (NS_SUCCEEDED(Preferences::GetBool(TRR_PREF("wait-for-portal"), &tmp))) { + mWaitForCaptive = tmp; + } + } + if (!name || !strcmp(name, TRR_PREF("allow-rfc1918"))) { + bool tmp; + if (NS_SUCCEEDED(Preferences::GetBool(TRR_PREF("allow-rfc1918"), &tmp))) { + mRfc1918 = tmp; + } + } + if (!name || !strcmp(name, TRR_PREF("useGET"))) { + bool tmp; + if (NS_SUCCEEDED(Preferences::GetBool(TRR_PREF("useGET"), &tmp))) { + mUseGET = tmp; + } + } + if (!name || !strcmp(name, TRR_PREF("blacklist-duration"))) { + // prefs is given in number of seconds + uint32_t secs; + if (NS_SUCCEEDED(Preferences::GetUint(TRR_PREF("blacklist-duration"), &secs))) { + mTRRBlacklistExpireTime = secs; + } + } + if (!name || !strcmp(name, TRR_PREF("request-timeout"))) { + // number of milliseconds + uint32_t ms; + if (NS_SUCCEEDED(Preferences::GetUint(TRR_PREF("request-timeout"), &ms))) { + mTRRTimeout = ms; + } + } + + return NS_OK; +} + +nsresult +TRRService::GetURI(nsCString &result) +{ + MutexAutoLock lock(mLock); + result = mPrivateURI; + return NS_OK; +} + +nsresult +TRRService::GetCredentials(nsCString &result) +{ + MutexAutoLock lock(mLock); + result = mPrivateCred; + return NS_OK; +} + +nsresult +TRRService::Start() +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + if (!mInitialized) { + return NS_ERROR_NOT_INITIALIZED; + } + return NS_OK; +} + +TRRService::~TRRService() +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + LOG(("Exiting TRRService\n")); + gTRRService = nullptr; +} + +NS_IMETHODIMP +TRRService::Observe(nsISupports *aSubject, + const char * aTopic, + const char16_t * aData) +{ + MOZ_ASSERT(NS_IsMainThread(), "wrong thread"); + LOG(("TRR::Observe() topic=%s\n", aTopic)); + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + ReadPrefs(NS_ConvertUTF16toUTF8(aData).get()); + + if ((mConfirmationState == CONFIRM_INIT) && + !mBootstrapAddr.IsEmpty() && + (mMode == MODE_TRRONLY)) { + mConfirmationState = CONFIRM_TRYING; + MaybeConfirm(); + } + + } else if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) { + // We are in a captive portal + LOG(("TRRservice in captive portal\n")); + mCaptiveIsPassed = false; + } else if (!strcmp(aTopic, NS_CAPTIVE_PORTAL_CONNECTIVITY)) { + nsAutoCString data = NS_ConvertUTF16toUTF8(aData); + LOG(("TRRservice captive portal was %s\n", data.get())); + if (!mTRRBLStorage) { + mTRRBLStorage = DataStorage::Get(DataStorageClass::TRRBlacklist); + if (mTRRBLStorage) { + bool storageWillPersist = true; + if (NS_FAILED(mTRRBLStorage->Init(storageWillPersist))) { + mTRRBLStorage = nullptr; + } + if (mClearTRRBLStorage) { + if (mTRRBLStorage) { + mTRRBLStorage->Clear(); + } + mClearTRRBLStorage = false; + } + } + } + + mConfirmationState = CONFIRM_TRYING; + MaybeConfirm(); + mCaptiveIsPassed = true; + + } else if (!strcmp(aTopic, kClearPrivateData) || + !strcmp(aTopic, kPurge)) { + // flush the TRR blacklist, both in-memory and on-disk + if (mTRRBLStorage) { + mTRRBLStorage->Clear(); + } + } + return NS_OK; +} + +void +TRRService::MaybeConfirm() +{ + if ((mMode == MODE_NATIVEONLY) || mConfirmer || + mConfirmationState != CONFIRM_TRYING) { + return; + } + nsAutoCString host; + { + MutexAutoLock lock(mLock); + host = mConfirmationNS; + } + if (host.Equals("skip")) { + LOG(("TRRService starting confirmation test %s SKIPPED\n", + mPrivateURI.get())); + mConfirmationState = CONFIRM_OK; + } else { + LOG(("TRRService starting confirmation test %s %s\n", + mPrivateURI.get(), host.get())); + mConfirmer = new TRR(this, host, TRRTYPE_NS, false); + NS_DispatchToMainThread(mConfirmer); + } +} + +bool +TRRService::MaybeBootstrap(const nsACString &aPossible, nsACString &aResult) +{ + MutexAutoLock lock(mLock); + if ((mMode == MODE_NATIVEONLY) || mBootstrapAddr.IsEmpty()) { + return false; + } + + nsCOMPtr url; + nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .Apply(&nsIStandardURLMutator::Init, + nsIStandardURL::URLTYPE_STANDARD, 443, + mPrivateURI, nullptr, nullptr, + nullptr) + .Finalize(url); + if (NS_FAILED(rv)) { + LOG(("TRRService::MaybeBootstrap failed to create URI!\n")); + return false; + } + + nsAutoCString host; + url->GetHost(host); + if (!aPossible.Equals(host)) { + return false; + } + LOG(("TRRService::MaybeBootstrap: use %s instead of %s\n", + mBootstrapAddr.get(), host.get())); + aResult = mBootstrapAddr; + return true; +} + +// When running in TRR-only mode, the blacklist is not used and it will also +// try resolving the localhost / .local names. +bool +TRRService::IsTRRBlacklisted(const nsACString &aHost, bool privateBrowsing, + bool aParentsToo) // false if domain +{ + if (mClearTRRBLStorage) { + if (mTRRBLStorage) { + mTRRBLStorage->Clear(); + } + mClearTRRBLStorage = false; + } + + if (mMode == MODE_TRRONLY) { + return false; // might as well try + } + + // hardcode these so as to not worry about expiration + if (StringEndsWith(aHost, NS_LITERAL_CSTRING(".local")) || + aHost.Equals(NS_LITERAL_CSTRING("localhost"))) { + return true; + } + + if (!Enabled()) { + return true; + } + if (!mTRRBLStorage) { + return false; + } + + int32_t dot = aHost.FindChar('.'); + if ((dot == kNotFound) && aParentsToo) { + // Only if a full host name. Domains can be dotless to be able to + // blacklist entire TLDs + return true; + } else if(dot != kNotFound) { + // there was a dot, check the parent first + dot++; + nsDependentCSubstring domain = Substring(aHost, dot, aHost.Length() - dot); + nsAutoCString check(domain); + + // recursively check the domain part of this name + if (IsTRRBlacklisted(check, privateBrowsing, false)) { + // the domain name of this name is already TRR blacklisted + return true; + } + } + + MutexAutoLock lock(mLock); + // use a unified casing for the hashkey + nsAutoCString hashkey(aHost); + nsCString val(mTRRBLStorage->Get(hashkey, privateBrowsing ? + DataStorage_Private : DataStorage_Persistent)); + + if (!val.IsEmpty()) { + nsresult code; + int32_t until = val.ToInteger(&code) + mTRRBlacklistExpireTime; + int32_t expire = NowInSeconds(); + if (NS_SUCCEEDED(code) && (until > expire)) { + LOG(("Host [%s] is TRR blacklisted\n", nsCString(aHost).get())); + return true; + } else { + // the blacklisted entry has expired + mTRRBLStorage->Remove(hashkey, privateBrowsing ? + DataStorage_Private : DataStorage_Persistent); + } + } + return false; +} + +class ProxyBlacklist : public Runnable +{ +public: + ProxyBlacklist(TRRService *service, const nsACString &aHost, bool pb, bool aParentsToo) + : mozilla::Runnable("proxyBlackList") + , mService(service), mHost(aHost), mPB(pb), mParentsToo(aParentsToo) + { } + + NS_IMETHOD Run() override + { + mService->TRRBlacklist(mHost, mPB, mParentsToo); + mService = nullptr; + return NS_OK; + } + +private: + RefPtr mService; + nsCString mHost; + bool mPB; + bool mParentsToo; +}; + +void +TRRService::TRRBlacklist(const nsACString &aHost, bool privateBrowsing, bool aParentsToo) +{ + if (!mTRRBLStorage) { + return; + } + + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(new ProxyBlacklist(this, aHost, + privateBrowsing, aParentsToo)); + return; + } + + LOG(("TRR blacklist %s\n", nsCString(aHost).get())); + nsAutoCString hashkey(aHost); + nsAutoCString val; + val.AppendInt( NowInSeconds() ); // creation time + + // this overwrites any existing entry + { + MutexAutoLock lock(mLock); + mTRRBLStorage->Put(hashkey, val, privateBrowsing ? + DataStorage_Private : DataStorage_Persistent); + } + + if (aParentsToo) { + // when given a full host name, verify its domain as well + int32_t dot = aHost.FindChar('.'); + if (dot != kNotFound) { + // this has a domain to be checked + dot++; + nsDependentCSubstring domain = Substring(aHost, dot, aHost.Length() - dot); + nsAutoCString check(domain); + if (IsTRRBlacklisted(check, privateBrowsing, false)) { + // the domain part is already blacklisted, no need to add this entry + return; + } + // verify 'check' over TRR + LOG(("TRR: verify if '%s' resolves as NS\n", check.get())); + + // check if there's an NS entry for this name + RefPtr trr = new TRR(this, check, TRRTYPE_NS, privateBrowsing); + NS_DispatchToMainThread(trr); + } + } +} + +AHostResolver::LookupStatus +TRRService::CompleteLookup(nsHostRecord *rec, nsresult status, AddrInfo *aNewRRSet, bool pb) +{ + // this is an NS check for the TRR blacklist or confirmationNS check + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!rec); + + nsAutoPtr newRRSet(aNewRRSet); + MOZ_ASSERT(newRRSet && newRRSet->IsTRR() == TRRTYPE_NS); + + MOZ_ASSERT(!mConfirmer || (mConfirmationState == CONFIRM_TRYING)); + if (mConfirmationState == CONFIRM_TRYING) { + MOZ_ASSERT(mConfirmer); + mConfirmationState = NS_SUCCEEDED(status) ? CONFIRM_OK : CONFIRM_FAILED; + LOG(("TRRService finishing confirmation test %s %d %X\n", + mPrivateURI.get(), (int)mConfirmationState, (unsigned int)status)); + mConfirmer = nullptr; + return LOOKUP_OK; + } + + // when called without a host record, this is a domain name check response. + if (NS_SUCCEEDED(status)) { + LOG(("TRR verified %s to be fine!\n", newRRSet->mHostName)); + } else { + LOG(("TRR says %s doesn't resove as NS!\n", newRRSet->mHostName)); + TRRBlacklist(nsCString(newRRSet->mHostName), pb, false); + } + return LOOKUP_OK; +} + +#undef LOG + +} // namespace net +} // namespace mozilla diff --git a/netwerk/dns/TRRService.h b/netwerk/dns/TRRService.h new file mode 100644 index 000000000000..3a4a3e39cf9f --- /dev/null +++ b/netwerk/dns/TRRService.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 TRRService_h_ +#define TRRService_h_ + +#include "mozilla/Atomics.h" +#include "mozilla/DataStorage.h" +#include "nsHostResolver.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" + +class nsIPrefBranch; + +namespace mozilla { +namespace net { + +class TRRService + : public nsIObserver + , public nsSupportsWeakReference + , public AHostResolver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + TRRService(); + nsresult Init(); + nsresult Start(); + bool Enabled(); + + uint32_t Mode() { return mMode; } + bool AllowRFC1918() { return mRfc1918; } + bool UseGET() { return mUseGET; } + nsresult GetURI(nsCString &result); + nsresult GetCredentials(nsCString &result); + uint32_t GetRequestTimeout() { return mTRRTimeout; } + + LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *, bool pb) override; + void TRRBlacklist(const nsACString &host, bool privateBrowsing, bool aParentsToo); + bool IsTRRBlacklisted(const nsACString &host, bool privateBrowsing, bool fullhost); + + bool MaybeBootstrap(const nsACString &possible, nsACString &result); + +private: + virtual ~TRRService(); + nsresult ReadPrefs(const char *name); + void GetPrefBranch(nsIPrefBranch **result); + void MaybeConfirm(); + + bool mInitialized; + Atomic mMode; + Atomic mTRRBlacklistExpireTime; + Atomic mTRRTimeout; + + Mutex mLock; // protects mPrivate* string + nsCString mPrivateURI; // main thread only + nsCString mPrivateCred; // main thread only + nsCString mConfirmationNS; + nsCString mBootstrapAddr; + + Atomic mWaitForCaptive; // wait for the captive portal to say OK before using TRR + Atomic mRfc1918; // okay with local IP addresses in DOH responses? + Atomic mCaptiveIsPassed; // set when captive portal check is passed + Atomic mUseGET; // do DOH using GET requests (instead of POST) + + // TRR Blacklist storage + RefPtr mTRRBLStorage; + Atomic mClearTRRBLStorage; + + enum ConfirmationState { + CONFIRM_INIT = 0, + CONFIRM_TRYING = 1, + CONFIRM_OK = 2, + CONFIRM_FAILED = 3 + }; + Atomic mConfirmationState; + RefPtr mConfirmer; +}; + +extern TRRService *gTRRService; + +} // namespace net +} // namespace mozilla + +#endif // TRRService_h_ diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build index 1e0294519790..a69b3f9f65d9 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -29,6 +29,7 @@ EXPORTS.mozilla.net += [ 'DNSRequestChild.h', 'DNSRequestParent.h', 'PDNSParams.h', + 'TRRService.h', ] SOURCES += [ @@ -46,6 +47,8 @@ UNIFIED_SOURCES += [ 'nsDNSService2.cpp', 'nsIDNService.cpp', 'punycode.c', + 'TRR.cpp', + 'TRRService.cpp', ] IPDL_SOURCES = [ diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index 7cae3fea0c96..b4d1d9564b8f 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -32,6 +32,7 @@ #include "nsProxyRelease.h" #include "nsIObserverService.h" #include "nsINetworkLinkService.h" +#include "TRRService.h" #include "mozilla/Attributes.h" #include "mozilla/ClearOnShutdown.h" @@ -104,6 +105,18 @@ nsDNSRecord::GetCanonicalName(nsACString &result) return NS_OK; } +NS_IMETHODIMP +nsDNSRecord::IsTRR(bool *retval) +{ + MutexAutoLock lock(mHostRecord->addr_info_lock); + if (mHostRecord->addr_info) { + *retval = mHostRecord->addr_info->IsTRR(); + } + else { + *retval = false; + } + return NS_OK; +} NS_IMETHODIMP nsDNSRecord::GetNextAddr(uint16_t port, NetAddr *addr) { @@ -486,6 +499,7 @@ nsDNSService::nsDNSService() , mNotifyResolution(false) , mOfflineLocalhost(false) , mForceResolveOn(false) + , mTrrService(nullptr) { } @@ -639,6 +653,11 @@ nsDNSService::Init() RegisterWeakMemoryReporter(this); + mTrrService = new TRRService(); + if (NS_FAILED(mTrrService->Init())) { + mTrrService = nullptr; + } + return rv; } @@ -711,6 +730,11 @@ nsDNSService::PreprocessHostname(bool aLocalDomain, return NS_OK; } + if (mTrrService && + mTrrService->MaybeBootstrap(aInput, aACE)) { + return NS_OK; + } + if (mForceResolveOn) { MutexAutoLock lock(mLock); if (!aInput.LowerCaseEqualsASCII("localhost") && diff --git a/netwerk/dns/nsDNSService2.h b/netwerk/dns/nsDNSService2.h index 9970ca2233b8..9da561a30859 100644 --- a/netwerk/dns/nsDNSService2.h +++ b/netwerk/dns/nsDNSService2.h @@ -18,6 +18,7 @@ #include "nsHashKeys.h" #include "mozilla/Mutex.h" #include "mozilla/Attributes.h" +#include "TRRService.h" class nsAuthSSPI; @@ -47,7 +48,6 @@ protected: uint32_t flags, const mozilla::OriginAttributes &aOriginAttributes, nsIDNSRecord **result); - private: ~nsDNSService(); @@ -84,6 +84,7 @@ private: bool mOfflineLocalhost; bool mForceResolveOn; nsTHashtable mLocalDomains; + RefPtr mTrrService; }; #endif //nsDNSService2_h__ diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp index 774f1dabf229..30fac52c07f2 100644 --- a/netwerk/dns/nsHostResolver.cpp +++ b/netwerk/dns/nsHostResolver.cpp @@ -30,7 +30,10 @@ #include "nsThreadUtils.h" #include "GetAddrInfo.h" #include "GeckoProfiler.h" +#include "TRR.h" +#include "TRRService.h" +#include "mozilla/Atomics.h" #include "mozilla/HashFunctions.h" #include "mozilla/TimeStamp.h" #include "mozilla/Telemetry.h" @@ -70,9 +73,13 @@ static_assert(HighThreadThreshold <= MAX_RESOLVER_THREADS, //---------------------------------------------------------------------------- -static LazyLogModule gHostResolverLog("nsHostResolver"); -#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args) -#define LOG_ENABLED() MOZ_LOG_TEST(gHostResolverLog, mozilla::LogLevel::Debug) +namespace mozilla { +namespace net { +LazyLogModule gHostResolverLog("nsHostResolver"); +#define LOG(args) MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug) +} +} #define LOG_HOST(host, interface) host, \ (interface && interface[0] != '\0') ? " on interface " : "", \ @@ -196,19 +203,37 @@ nsHostRecord::nsHostRecord(const nsHostKey& key) , addr_info(nullptr) , addr(nullptr) , negative(false) - , resolving(false) + , mResolving(0) + , mNative(false) + , mTRRSuccess(0) + , mTRRUsed(false) + , mNativeUsed(false) + , mNativeSuccess(false) + , mFirstTRR(nullptr) , onQueue(false) , usingAnyThread(false) , mDoomed(false) -#if TTL_AVAILABLE + , mDidCallbacks(false) , mGetTtl(false) -#endif + , mTrrAUsed(INIT) + , mTrrAAAAUsed(INIT) , mBlacklistedCount(0) , mResolveAgain(false) { PR_INIT_CLIST(this); } +void +nsHostRecord::Cancel() +{ + if (mTrrA) { + mTrrA->Cancel(); + } + if (mTrrAAAA) { + mTrrAAAA->Cancel(); + } +} + void nsHostRecord::SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, unsigned int grace) { @@ -228,6 +253,79 @@ nsHostRecord::CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecor mDoomed = aFromHostRecord->mDoomed; } +void +nsHostRecord::ResolveComplete() +{ + if (mNativeUsed) { + if (mNativeSuccess) { + uint32_t millis = static_cast(mNativeDuration.ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DNS_NATIVE_LOOKUP_TIME, millis); + } + AccumulateCategorical(mNativeSuccess ? + Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::osOK : + Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::osFail); + } + + if (mTRRUsed) { + if (mTRRSuccess) { + uint32_t millis = static_cast(mTrrDuration.ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DNS_TRR_LOOKUP_TIME, millis); + } + AccumulateCategorical(mTRRSuccess ? + Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::trrOK : + Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::trrFail); + + if (mTrrAUsed == OK) { + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::trrAOK); + } else if (mTrrAUsed == FAILED) { + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::trrAFail); + } + + if (mTrrAAAAUsed == OK) { + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::trrAAAAOK); + } else if (mTrrAAAAUsed == FAILED) { + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_DISPOSITION::trrAAAAFail); + } + } + + if (mTRRUsed && mNativeUsed && mNativeSuccess && mTRRSuccess) { // race or shadow! + static const TimeDuration k50ms = TimeDuration::FromMilliseconds(50); + if (mTrrDuration <= mNativeDuration) { + AccumulateCategorical(((mNativeDuration - mTrrDuration) > k50ms) ? + Telemetry::LABELS_DNS_TRR_RACE::TRRFasterBy50 : + Telemetry::LABELS_DNS_TRR_RACE::TRRFaster); + LOG(("nsHostRecord::Complete %s Dns Race: TRR\n", host.get())); + } else { + AccumulateCategorical(((mTrrDuration - mNativeDuration) > k50ms) ? + Telemetry::LABELS_DNS_TRR_RACE::NativeFasterBy50 : + Telemetry::LABELS_DNS_TRR_RACE::NativeFaster); + LOG(("nsHostRecord::Complete %s Dns Race: NATIVE\n", host.get())); + } + } + + switch(mResolverMode) { + case MODE_NATIVEONLY: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::nativeOnly); + break; + case MODE_PARALLEL: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrRace); + break; + case MODE_TRRFIRST: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrFirst); + break; + case MODE_TRRONLY: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrOnly); + break; + case MODE_SHADOW: + AccumulateCategorical(Telemetry::LABELS_DNS_LOOKUP_ALGORITHM::trrShadow); + break; + } + + if (mTRRUsed && !mTRRSuccess && mNativeSuccess && gTRRService) { + gTRRService->TRRBlacklist(nsCString(host), pb, true); + } +} + nsHostRecord::~nsHostRecord() { mCallbacks.clear(); @@ -269,8 +367,9 @@ void nsHostRecord::ReportUnusable(NetAddr *aAddress) { // must call locked - LOG(("Adding address to blacklist for host [%s%s%s], host record [%p].\n", - LOG_HOST(host.get(), netInterface.get()), this)); + LOG(("Adding address to blacklist for host [%s%s%s], host record [%p]." + "used trr=%d\n", LOG_HOST(host.get(), netInterface.get()), + this, mTRRSuccess)); ++mBlacklistedCount; @@ -375,7 +474,12 @@ nsHostRecord::GetPriority(uint16_t aFlags) bool nsHostRecord::RemoveOrRefresh() { - if (resolving) { + // no need to flush TRRed names, they're not resolved "locally" + Cancel(); + if (addr_info && addr_info->IsTRR()) { + return false; + } + if (mNative) { if (!onQueue) { // The request has been passed to the OS resolver. The resultant DNS // record should be considered stale and not trusted; set a flag to @@ -386,32 +490,33 @@ nsHostRecord::RemoveOrRefresh() // but is still pending to get resolved: just leave it in hash. return false; } - // Already resolved; not in a pending state; remove from cache. + // Already resolved; not in a pending state; remove from cache return true; } //---------------------------------------------------------------------------- -#if TTL_AVAILABLE static const char kPrefGetTtl[] = "network.dns.get-ttl"; +static const char kPrefNativeIsLocalhost[] = "network.dns.native-is-localhost"; static bool sGetTtlEnabled = false; +mozilla::Atomic gNativeIsLocalhost; static void DnsPrefChanged(const char* aPref, void* aClosure) { MOZ_ASSERT(NS_IsMainThread(), "Should be getting pref changed notification on main thread!"); - if (strcmp(aPref, kPrefGetTtl) != 0) { - LOG(("DnsPrefChanged ignoring pref \"%s\"", aPref)); - return; - } - DebugOnly self = static_cast(aClosure); MOZ_ASSERT(self); - sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl); + if (!strcmp(aPref, kPrefGetTtl)) { + sGetTtlEnabled = Preferences::GetBool(kPrefGetTtl); + } else if (!strcmp(aPref, kPrefNativeIsLocalhost)) { + gNativeIsLocalhost = Preferences::GetBool(kPrefNativeIsLocalhost); + } } -#endif + +NS_IMPL_ISUPPORTS0(nsHostResolver) nsHostResolver::nsHostResolver(uint32_t maxCacheEntries, uint32_t defaultCacheEntryLifetime, @@ -443,6 +548,7 @@ nsHostResolver::~nsHostResolver() = default; nsresult nsHostResolver::Init() { + MOZ_ASSERT(NS_IsMainThread()); if (NS_FAILED(GetAddrInfoInit())) { return NS_ERROR_FAILURE; } @@ -451,7 +557,6 @@ nsHostResolver::Init() mShutdown = false; -#if TTL_AVAILABLE // The preferences probably haven't been loaded from the disk yet, so we // need to register a callback that will set up the experiment once they // are. We also need to explicitly set a value for the props otherwise the @@ -461,8 +566,11 @@ nsHostResolver::Init() &DnsPrefChanged, kPrefGetTtl, this); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not register DNS TTL pref callback."); + rv = Preferences::RegisterCallbackAndCall( + &DnsPrefChanged, kPrefNativeIsLocalhost, this); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Could not register DNS pref callback."); } -#endif #if defined(HAVE_RES_NINIT) // We want to make sure the system is using the correct resolver settings, @@ -476,6 +584,7 @@ nsHostResolver::Init() res_ninit(&_res); } #endif + return NS_OK; } @@ -487,8 +596,9 @@ nsHostResolver::ClearPendingQueue(PRCList *aPendingQ) PRCList *node = aPendingQ->next; while (node != aPendingQ) { RefPtr rec = dont_AddRef(static_cast(node)); + rec->Cancel(); node = node->next; - CompleteLookup(rec, NS_ERROR_ABORT, nullptr); + CompleteLookup(rec, NS_ERROR_ABORT, nullptr, rec->pb); } } } @@ -515,6 +625,7 @@ nsHostResolver::FlushCache() PRCList *node = mEvictionQ.next; while (node != &mEvictionQ) { nsHostRecord *rec = static_cast(node); + rec->Cancel(); node = node->next; PR_REMOVE_AND_INIT_LINK(rec); mRecordDB.Remove(*static_cast(rec)); @@ -538,14 +649,12 @@ nsHostResolver::Shutdown() { LOG(("Shutting down host resolver.\n")); -#if TTL_AVAILABLE { DebugOnly rv = Preferences::UnregisterCallback( &DnsPrefChanged, kPrefGetTtl, this); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not unregister DNS TTL pref callback."); } -#endif PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ; PR_INIT_CLIST(&pendingQHigh); @@ -580,11 +689,15 @@ nsHostResolver::Shutdown() PRCList *node = evictionQ.next; while (node != &evictionQ) { nsHostRecord *rec = static_cast(node); + rec->Cancel(); node = node->next; NS_RELEASE(rec); } } + for (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->Cancel(); + } #ifdef NS_BUILD_REFCNT_LOGGING // Logically join the outstanding worker threads with a timeout. @@ -616,6 +729,33 @@ nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ) PR_APPEND_LINK(aRec, &aDestQ); } +nsresult +nsHostResolver::GetHostRecord(const char *host, + uint16_t flags, uint16_t af, bool pb, + const nsCString &netInterface, + const nsCString &originSuffix, + nsHostRecord **result) +{ + MutexAutoLock lock(mLock); + nsHostKey key(nsCString(host), flags, af, pb, + netInterface, originSuffix); + + RefPtr& entry = mRecordDB.GetOrInsert(key); + if (!entry) { + entry = new nsHostRecord(key); + } + + RefPtr rec = entry; + if (rec->addr) { + return NS_ERROR_FAILURE; + } + if (rec->mResolving) { + return NS_ERROR_FAILURE; + } + *result = rec.forget().take(); + return NS_OK; +} + nsresult nsHostResolver::ResolveHost(const char *host, const OriginAttributes &aOriginAttributes, @@ -643,9 +783,9 @@ nsHostResolver::ResolveHost(const char *host, { MutexAutoLock lock(mLock); - if (mShutdown) + if (mShutdown) { rv = NS_ERROR_NOT_INITIALIZED; - else { + } else { // Used to try to parse to an IP address literal. PRNetAddr tempAddr; // Unfortunately, PR_StringToNetAddr does not properly initialize @@ -661,7 +801,9 @@ nsHostResolver::ResolveHost(const char *host, nsAutoCString originSuffix; aOriginAttributes.CreateSuffix(originSuffix); - nsHostKey key(nsCString(host), flags, af, nsCString(netInterface), + nsHostKey key(nsCString(host), flags, af, + (aOriginAttributes.mPrivateBrowsingId > 0), + nsCString(netInterface), originSuffix); RefPtr& entry = mRecordDB.GetOrInsert(key); if (!entry) { @@ -690,19 +832,17 @@ nsHostResolver::ResolveHost(const char *host, METHOD_NEGATIVE_HIT); status = NS_ERROR_UNKNOWN_HOST; } - } - // if the host name is an IP address literal and has been parsed, - // go ahead and use it. - else if (rec->addr) { + } else if (rec->addr) { + // if the host name is an IP address literal and has been parsed, + // go ahead and use it. LOG((" Using cached address for IP Literal [%s].\n", host)); Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL); result = rec; - } - // try parsing the host name as an IP address literal to short - // circuit full host resolution. (this is necessary on some - // platforms like Win9x. see bug 219376 for more details.) - else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) { + } else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) { + // try parsing the host name as an IP address literal to short + // circuit full host resolution. (this is necessary on some + // platforms like Win9x. see bug 219376 for more details.) LOG((" Host is IP Literal [%s].\n", host)); // ok, just copy the result into the host record, and be done // with it! ;-) @@ -712,10 +852,9 @@ nsHostResolver::ResolveHost(const char *host, Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL); result = rec; - } - else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS && - !IsHighPriority(flags) && - !rec->resolving) { + } else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS && + !IsHighPriority(flags) && + !rec->mResolving) { LOG((" Lookup queue full: dropping %s priority request for " "host [%s%s%s].\n", IsMediumPriority(flags) ? "medium" : "low", @@ -724,20 +863,19 @@ nsHostResolver::ResolveHost(const char *host, METHOD_OVERFLOW); // This is a lower priority request and we are swamped, so refuse it. rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL; - } - else if (flags & RES_OFFLINE) { + } else if (flags & RES_OFFLINE) { LOG((" Offline request for host [%s%s%s]; ignoring.\n", LOG_HOST(host, netInterface))); rv = NS_ERROR_OFFLINE; - } + } else if (!rec->mResolving) { + // If this is an IPV4 or IPV6 specific request, check if there is + // an AF_UNSPEC entry we can use. Otherwise, hit the resolver... - // If this is an IPV4 or IPV6 specific request, check if there is - // an AF_UNSPEC entry we can use. Otherwise, hit the resolver... - else if (!rec->resolving) { if (!(flags & RES_BYPASS_CACHE) && ((af == PR_AF_INET) || (af == PR_AF_INET6))) { // First, search for an entry with AF_UNSPEC const nsHostKey unspecKey(nsCString(host), flags, PR_AF_UNSPEC, + (aOriginAttributes.mPrivateBrowsingId > 0), nsCString(netInterface), originSuffix); RefPtr unspecRec = mRecordDB.Get(unspecKey); @@ -792,12 +930,11 @@ nsHostResolver::ResolveHost(const char *host, Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT); ConditionallyRefreshRecord(rec, host); - } - // For AF_INET6, a new lookup means another AF_UNSPEC - // lookup. We have already iterated through the - // AF_UNSPEC addresses, so we mark this record as - // negative. - else if (af == PR_AF_INET6) { + } else if (af == PR_AF_INET6) { + // For AF_INET6, a new lookup means another AF_UNSPEC + // lookup. We have already iterated through the + // AF_UNSPEC addresses, so we mark this record as + // negative. LOG((" No AF_INET6 in AF_UNSPEC entry: " "host [%s%s%s] unknown host.", LOG_HOST(host, netInterface))); @@ -818,20 +955,18 @@ nsHostResolver::ResolveHost(const char *host, // Add callback to the list of pending callbacks. rec->mCallbacks.insertBack(callback); rec->flags = flags; - rv = IssueLookup(rec); + rv = NameLookup(rec); Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_NETWORK_FIRST); if (NS_FAILED(rv) && callback->isInList()) { callback->remove(); - } - else { + } else { LOG((" DNS lookup for host [%s%s%s] blocking " "pending 'getaddrinfo' query: callback [%p]", LOG_HOST(host, netInterface), callback.get())); } } - } - else { + } else { LOG((" Host [%s%s%s] is being resolved. Appending callback " "[%p].", LOG_HOST(host, netInterface), callback.get())); @@ -890,7 +1025,9 @@ nsHostResolver::DetachCallback(const char *host, nsAutoCString originSuffix; aOriginAttributes.CreateSuffix(originSuffix); - nsHostKey key(nsCString(host), flags, af, nsCString(netInterface), + nsHostKey key(nsCString(host), flags, af, + (aOriginAttributes.mPrivateBrowsingId > 0), + nsCString(netInterface), originSuffix); RefPtr entry = mRecordDB.Get(key); if (entry) { @@ -947,19 +1084,121 @@ nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec) return NS_OK; } +#define TRROutstanding() ((rec->mTrrA || rec->mTrrAAAA)) + nsresult -nsHostResolver::IssueLookup(nsHostRecord *rec) +nsHostResolver::TrrLookup_unlocked(nsHostRecord *rec, TRR *pushedTRR) { - nsresult rv = NS_OK; - NS_ASSERTION(!rec->resolving, "record is already being resolved"); + MutexAutoLock lock(mLock); + return TrrLookup(rec, pushedTRR); +} + +// returns error if no TRR resolve is issued +// it is impt this is not called while a native lookup is going on +nsresult +nsHostResolver::TrrLookup(nsHostRecord *rec, TRR *pushedTRR) +{ + mLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(!TRROutstanding()); + MOZ_ASSERT(!rec->mResolving); + + if (!gTRRService || !gTRRService->Enabled()) { + LOG(("TrrLookup:: %s service not enabled\n", rec->host.get())); + return NS_ERROR_UNKNOWN_HOST; + } + + if (rec->next != rec) { + // we're already on the eviction queue. This is a renewal + MOZ_ASSERT(mEvictionQSize); + AssertOnQ(rec, &mEvictionQ); + PR_REMOVE_AND_INIT_LINK(rec); + mEvictionQSize--; + rec->Release(); + } + + rec->mTRRSuccess = 0; // bump for each successful TRR response + rec->mTrrAUsed = nsHostRecord::INIT; + rec->mTrrAAAAUsed = nsHostRecord::INIT; + + if (gTRRService && gTRRService->IsTRRBlacklisted(rec->host, rec->pb, true)) { + Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED, true); + MOZ_ASSERT(!rec->mTRRUsed); + // not really an error but no TRR is issued + return NS_ERROR_UNKNOWN_HOST; + } + Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED, false); + + rec->mTrrStart = TimeStamp::Now(); + rec->mTRRUsed = true; // this record gets TRR treatment + + // If asking for AF_UNSPEC, issue both A and AAAA. + // If asking for AF_INET6 or AF_INET, do only that single type + enum TrrType rectype = (rec->af == AF_INET6)? TRRTYPE_AAAA : TRRTYPE_A; + if (pushedTRR) { + rectype = pushedTRR->Type(); + } + bool sendAgain; + + bool madeQuery = false; + do { + sendAgain = false; + LOG(("TRR Resolve %s type %d\n", rec->host.get(), (int)rectype)); + RefPtr trr; + trr = pushedTRR ? pushedTRR : new TRR(this, rec, rectype); + if (pushedTRR || NS_SUCCEEDED(NS_DispatchToMainThread(trr))) { + rec->mResolving++; + if (rectype == TRRTYPE_A) { + MOZ_ASSERT(!rec->mTrrA); + rec->mTrrA = trr; + rec->mTrrAUsed = nsHostRecord::STARTED; + } else if (rectype == TRRTYPE_AAAA) { + MOZ_ASSERT(!rec->mTrrAAAA); + rec->mTrrAAAA = trr; + rec->mTrrAAAAUsed = nsHostRecord::STARTED; + } else { + LOG(("TrrLookup called with bad type set: %d\n", rectype)); + MOZ_ASSERT(0); + } + madeQuery = true; + if (!pushedTRR && (rec->af == AF_UNSPEC) && (rectype == TRRTYPE_A)) { + rectype = TRRTYPE_AAAA; + sendAgain = true; + } + } + } while (sendAgain); + + return madeQuery ? NS_OK : NS_ERROR_UNKNOWN_HOST; +} + +void +nsHostResolver::AssertOnQ(nsHostRecord *rec, PRCList *q) +{ +#ifdef DEBUG + MOZ_ASSERT(!PR_CLIST_IS_EMPTY(q)); + nsHostRecord *i = static_cast(PR_LIST_HEAD(q)); + while (i != rec) { + MOZ_ASSERT(i->next != q); + i = static_cast(i->next); + } +#endif +} + +nsresult +nsHostResolver::NativeLookup(nsHostRecord *rec) +{ + mLock.AssertCurrentThreadOwns(); + + rec->mNativeStart = TimeStamp::Now(); // 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) { // not on a pending queue NS_ADDREF(rec); - else { - PR_REMOVE_LINK(rec); + } else { + MOZ_ASSERT(mEvictionQSize); + AssertOnQ(rec, &mEvictionQ); + PR_REMOVE_AND_INIT_LINK(rec); // was on the eviction queue mEvictionQSize--; } @@ -978,10 +1217,12 @@ nsHostResolver::IssueLookup(nsHostRecord *rec) } mPendingCount++; - rec->resolving = true; + rec->mNative = true; + rec->mNativeUsed = true; rec->onQueue = true; + rec->mResolving++; - rv = ConditionallyCreateThread(rec); + nsresult rv = ConditionallyCreateThread(rec); LOG ((" DNS thread counters: total=%d any-live=%d idle=%d pending=%d\n", static_cast(mThreadCount), @@ -992,14 +1233,65 @@ nsHostResolver::IssueLookup(nsHostRecord *rec) return rv; } +ResolverMode +nsHostResolver::Mode() +{ + if (gTRRService) { + return static_cast(gTRRService->Mode()); + } + + return MODE_NATIVEONLY; +} + +// Kick-off a name resolve operation, using native resolver and/or TRR +nsresult +nsHostResolver::NameLookup(nsHostRecord *rec) +{ + nsresult rv = NS_ERROR_UNKNOWN_HOST; + if (rec->mResolving) { + LOG(("NameLookup %s while already resolving\n", rec->host.get())); + return NS_OK; + } + + ResolverMode mode = rec->mResolverMode = Mode(); + + rec->mNativeUsed = false; + rec->mTRRUsed = false; + rec->mNativeSuccess = false; + rec->mTRRSuccess = 0; + rec->mDidCallbacks = false; + rec->mTrrAUsed = nsHostRecord::INIT; + rec->mTrrAAAAUsed = nsHostRecord::INIT; + + if (rec->flags & RES_DISABLE_TRR) { + if (mode == MODE_TRRONLY) { + return rv; + } + mode = MODE_NATIVEONLY; + } + + if (mode != MODE_NATIVEONLY) { + rv = TrrLookup(rec); + } + + if ((mode == MODE_PARALLEL) || + (mode == MODE_NATIVEONLY) || + (mode == MODE_SHADOW) || + ((mode == MODE_TRRFIRST) && NS_FAILED(rv))) { + rv = NativeLookup(rec); + } + + return rv; +} + nsresult nsHostResolver::ConditionallyRefreshRecord(nsHostRecord *rec, const char *host) { if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID - || rec->negative) && !rec->resolving) { + || rec->negative) && !rec->mResolving) { LOG((" Using %s cache entry for host [%s] but starting async renewal.", rec->negative ? "negative" :"positive", host)); - IssueLookup(rec); + NameLookup(rec); if (!rec->negative) { // negative entries are constantly being refreshed, only @@ -1034,12 +1326,7 @@ nsHostResolver::GetHostToLookup(nsHostRecord **result) while (!mShutdown) { // remove next record from Q; hand over owning reference. Check high, then med, then low -#if TTL_AVAILABLE - #define SET_GET_TTL(var, val) \ - (var)->mGetTtl = sGetTtlEnabled && (val) -#else - #define SET_GET_TTL(var, val) -#endif +#define SET_GET_TTL(var, val) (var)->mGetTtl = sGetTtlEnabled && (val) if (!PR_CLIST_IS_EMPTY(&mHighQ)) { DeQueue (mHighQ, result); @@ -1099,7 +1386,9 @@ nsHostResolver::GetHostToLookup(nsHostRecord **result) void nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const { + // NOTE: rec->addr_info_lock is already held by parent MOZ_ASSERT(((bool)rec->addr_info) != rec->negative); + mLock.AssertCurrentThreadOwns(); if (!rec->addr_info) { rec->SetExpiration(TimeStamp::NowLoRes(), NEGATIVE_RECORD_LIFETIME, 0); @@ -1111,23 +1400,35 @@ nsHostResolver::PrepareRecordExpiration(nsHostRecord* rec) const unsigned int lifetime = mDefaultCacheLifetime; unsigned int grace = mDefaultGracePeriod; -#if TTL_AVAILABLE + unsigned int ttl = mDefaultCacheLifetime; if (sGetTtlEnabled) { - MutexAutoLock lock(rec->addr_info_lock); if (rec->addr_info && rec->addr_info->ttl != AddrInfo::NO_TTL_DATA) { ttl = rec->addr_info->ttl; } lifetime = ttl; grace = 0; } -#endif rec->SetExpiration(TimeStamp::NowLoRes(), lifetime, grace); LOG(("Caching host [%s%s%s] record for %u seconds (grace %d).", LOG_HOST(rec->host.get(), rec->netInterface.get()), lifetime, grace)); } +static nsresult +merge_rrset(AddrInfo *rrto, AddrInfo *rrfrom) +{ + if (!rrto || !rrfrom) { + return NS_ERROR_NULL_POINTER; + } + NetAddrElement *element; + while ((element = rrfrom->mAddresses.getFirst())) { + element->remove(); // unlist from old + rrto->AddAddress(element); // enlist on new + } + return NS_OK; +} + static bool different_rrset(AddrInfo *rrset1, AddrInfo *rrset2) { @@ -1181,94 +1482,227 @@ different_rrset(AddrInfo *rrset1, AddrInfo *rrset2) // returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT. // takes ownership of AddrInfo parameter nsHostResolver::LookupStatus -nsHostResolver::CompleteLookup(nsHostRecord* rec, nsresult status, AddrInfo* newRRSet) +nsHostResolver::CompleteLookup(nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb) { - // get the list of pending callbacks for this lookup, and notify - // them that the lookup is complete. - mozilla::LinkedList> cbs; + MutexAutoLock lock(mLock); + MOZ_ASSERT(rec); + MOZ_ASSERT(rec->pb == pb); - { - MutexAutoLock lock(mLock); + // newRRSet needs to be taken into the hostrecord (which will then own it) + // or deleted on early return. + nsAutoPtr newRRSet(aNewRRSet); - if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) { - LOG(("nsHostResolver record %p resolve again due to flushcache\n", rec)); - rec->mResolveAgain = false; - delete newRRSet; - return LOOKUP_RESOLVEAGAIN; + MOZ_ASSERT(rec->mResolving); + rec->mResolving--; + LOG(("nsHostResolver::CompleteLookup %s %p %X trr=%d stillResolving=%d\n", + rec->host.get(), aNewRRSet, (unsigned int)status, + aNewRRSet ? aNewRRSet->IsTRR() : 0, rec->mResolving)); + + bool trrResult = newRRSet && newRRSet->IsTRR(); + + if (trrResult) { + LOG(("TRR lookup Complete (%d) %s %s\n", + newRRSet->IsTRR(), newRRSet->mHostName, + NS_SUCCEEDED(status) ? "OK" : "FAILED")); + MOZ_ASSERT(TRROutstanding()); + if (newRRSet->IsTRR() == TRRTYPE_A) { + MOZ_ASSERT(rec->mTrrA); + rec->mTrrA = nullptr; + rec->mTrrAUsed = NS_SUCCEEDED(status) ? nsHostRecord::OK : nsHostRecord::FAILED; + } else if (newRRSet->IsTRR() == TRRTYPE_AAAA) { + MOZ_ASSERT(rec->mTrrAAAA); + rec->mTrrAAAA = nullptr; + rec->mTrrAAAAUsed = NS_SUCCEEDED(status) ? nsHostRecord::OK : nsHostRecord::FAILED; + } else { + MOZ_ASSERT(0); } - // grab list of callbacks to notify - cbs = mozilla::Move(rec->mCallbacks); - - // 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); - if (different_rrset(rec->addr_info, newRRSet)) { - LOG(("nsHostResolver record %p new gencnt\n", rec)); - old_addr_info = rec->addr_info; - rec->addr_info = newRRSet; - rec->addr_info_gencnt++; - } else { - if (rec->addr_info && newRRSet) { - rec->addr_info->ttl = newRRSet->ttl; - } - old_addr_info = newRRSet; + if (NS_SUCCEEDED(status)) { + if (rec->mTRRSuccess == 0) { // first one + rec->mTrrDuration = TimeStamp::Now() - rec->mTrrStart; } + rec->mTRRSuccess++; } - delete old_addr_info; - rec->negative = !rec->addr_info; - PrepareRecordExpiration(rec); - rec->resolving = false; + if (TRROutstanding()) { + if (NS_FAILED(status)) { + return LOOKUP_OK; // wait for outstanding + } + // There's another TRR complete pending. Wait for it and keep + // this RRset around until then. + MOZ_ASSERT(!rec->mFirstTRR && newRRSet); + rec->mFirstTRR = newRRSet; // autoPtr.swap() + MOZ_ASSERT(rec->mFirstTRR && !newRRSet); + + if (rec->mDidCallbacks || rec->mResolverMode == MODE_SHADOW) { + return LOOKUP_OK; + } + + // we can do some callbacks with this partial result which requires + // a deep copy + newRRSet = new AddrInfo(rec->mFirstTRR); + MOZ_ASSERT(rec->mFirstTRR && newRRSet); + + } else { + // no more outstanding TRRs + // If mFirstTRR is set, merge those addresses into current set! + if (rec->mFirstTRR) { + if (NS_SUCCEEDED(status)) { + merge_rrset(newRRSet, rec->mFirstTRR); + } + else { + newRRSet = rec->mFirstTRR; // transfers + } + rec->mFirstTRR = nullptr; + } + + if (!rec->mTRRSuccess) { + // no TRR success + newRRSet = nullptr; + } + + if (!rec->mTRRSuccess && rec->mResolverMode == MODE_TRRFIRST) { + MOZ_ASSERT(!rec->mResolving); + NativeLookup(rec); + MOZ_ASSERT(rec->mResolving); + return LOOKUP_OK; + } + // continue + } + } else { // native resolve completed 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); - mRecordDB.Remove(*static_cast(head)); - if (!head->negative) { - // record the age of the entry upon eviction. - TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart; - Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, - static_cast(age.ToSeconds() / 60)); - } - - // release reference to rec owned by mEvictionQ - NS_RELEASE(head); - } -#if TTL_AVAILABLE - if (!rec->mGetTtl && !rec->resolving && sGetTtlEnabled) { - LOG(("Issuing second async lookup for TTL for host [%s%s%s].", - LOG_HOST(rec->host.get(), rec->netInterface.get()))); - rec->flags = - (rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW; - DebugOnly rv = IssueLookup(rec); - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "Could not issue second async lookup for TTL."); - } -#endif + rec->mNative = false; + rec->mNativeSuccess = newRRSet ? true : false; + if (rec->mNativeSuccess) { + rec->mNativeDuration = TimeStamp::Now() - rec->mNativeStart; } } - for (nsResolveHostCallback* c = cbs.getFirst(); c; c = c->removeAndGetNext()) { - c->OnResolveHostComplete(this, rec, status); + if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) { + LOG(("nsHostResolver record %p resolve again due to flushcache\n", rec)); + rec->mResolveAgain = false; + return LOOKUP_RESOLVEAGAIN; } + // update record fields. We might have a rec->addr_info already if a + // previous lookup result expired and we're reresolving it or we get + // a late second TRR response. + // note that we don't update the addr_info if this is trr shadow results + if (!mShutdown && + !(trrResult && rec->mResolverMode == MODE_SHADOW)) { + MutexAutoLock lock(rec->addr_info_lock); + nsAutoPtr old_addr_info; + if (different_rrset(rec->addr_info, newRRSet)) { + LOG(("nsHostResolver record %p new gencnt\n", rec)); + old_addr_info = rec->addr_info; + rec->addr_info = newRRSet.forget(); + rec->addr_info_gencnt++; + } else { + if (rec->addr_info && newRRSet) { + rec->addr_info->ttl = newRRSet->ttl; + } + old_addr_info = newRRSet.forget(); + } + rec->negative = !rec->addr_info; + PrepareRecordExpiration(rec); + } + + bool doCallbacks = true; + + if (trrResult && (rec->mResolverMode == MODE_SHADOW) && !rec->mDidCallbacks) { + // don't report result based only on suppressed TRR info + doCallbacks = false; + LOG(("nsHostResolver Suppressing TRR %s because it is first shadow result\n", + rec->host.get())); + } else if(trrResult && rec->mDidCallbacks) { + // already callback'ed on the first TRR response + LOG(("nsHostResolver Suppressing callback for second TRR response for %s\n", + rec->host.get())); + doCallbacks = false; + } + + + if (LOG_ENABLED()) { + MutexAutoLock lock(rec->addr_info_lock); + NetAddrElement *element; + if (rec->addr_info) { + for (element = rec->addr_info->mAddresses.getFirst(); + element; element = element->getNext()) { + char buf[128]; + NetAddrToString(&element->mAddress, buf, sizeof(buf)); + LOG(("CompleteLookup: %s has %s\n", rec->host.get(), buf)); + } + } else { + LOG(("CompleteLookup: %s has NO address\n", rec->host.get())); + } + } + + if (doCallbacks) { + // get the list of pending callbacks for this lookup, and notify + // them that the lookup is complete. + mozilla::LinkedList> cbs = mozilla::Move(rec->mCallbacks); + + LOG(("nsHostResolver record %p calling back dns users\n", rec)); + + for (nsResolveHostCallback* c = cbs.getFirst(); c; c = c->removeAndGetNext()) { + c->OnResolveHostComplete(this, rec, status); + } + rec->mDidCallbacks = true; + } + + if (!rec->mResolving && !mShutdown) { + rec->ResolveComplete(); + + // add to mEvictionQ + MOZ_ASSERT(rec->next == rec && rec->prev == rec); // not on a queue + PR_APPEND_LINK(rec, &mEvictionQ); + rec->AddRef(); + if (mEvictionQSize < mMaxCacheEntries) { + mEvictionQSize++; + } else { + // remove first element on mEvictionQ + nsHostRecord *head = + static_cast(PR_LIST_HEAD(&mEvictionQ)); + PR_REMOVE_AND_INIT_LINK(head); + mRecordDB.Remove(*static_cast(head)); + + if (!head->negative) { + // record the age of the entry upon eviction. + TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart; + Telemetry::Accumulate(Telemetry::DNS_CLEANUP_AGE, + static_cast(age.ToSeconds() / 60)); + } + head->Release(); // release reference owned by mEvictionQ + } + } + +#ifdef DNSQUERY_AVAILABLE + // Unless the result is from TRR, resolve again to get TTL + bool fromTRR = false; + { + MutexAutoLock lock(rec->addr_info_lock); + if(rec->addr_info && rec->addr_info->IsTRR()) { + fromTRR = true; + } + } + if (!fromTRR && + !mShutdown && !rec->mGetTtl && !rec->mResolving && sGetTtlEnabled) { + LOG(("Issuing second async lookup for TTL for host [%s%s%s].", + LOG_HOST(rec->host.get(), rec->netInterface.get()))); + rec->flags = + (rec->flags & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW | + RES_DISABLE_TRR; + DebugOnly rv = NameLookup(rec); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Could not issue second async lookup for TTL."); + } +#endif return LOOKUP_OK; } @@ -1284,11 +1718,14 @@ nsHostResolver::CancelAsyncRequest(const char *host, { MutexAutoLock lock(mLock); - nsCString originSuffix; + nsAutoCString originSuffix; aOriginAttributes.CreateSuffix(originSuffix); // Lookup the host record associated with host, flags & address family - nsHostKey key(nsCString(host), flags, af, nsCString(netInterface), + + nsHostKey key(nsCString(host), flags, af, + (aOriginAttributes.mPrivateBrowsingId > 0), + nsCString(netInterface), originSuffix); RefPtr rec = mRecordDB.Get(key); if (rec) { @@ -1308,7 +1745,7 @@ nsHostResolver::CancelAsyncRequest(const char *host, mRecordDB.Remove(*static_cast(recPtr)); // If record is on a Queue, remove it and then deref it if (recPtr->next != recPtr) { - PR_REMOVE_LINK(recPtr); + PR_REMOVE_AND_INIT_LINK(recPtr); NS_RELEASE(recPtr); } } @@ -1354,17 +1791,22 @@ nsHostResolver::ThreadFunc(void *arg) RefPtr rec; AddrInfo *ai = nullptr; - while (rec || resolver->GetHostToLookup(getter_AddRefs(rec))) { + do { + if (!rec) { + RefPtr tmpRec; + if (!resolver->GetHostToLookup(getter_AddRefs(tmpRec))) { + break; // thread shutdown signal + } + // GetHostToLookup() returns an owning reference + MOZ_ASSERT(tmpRec); + rec.swap(tmpRec); + } + LOG(("DNS lookup thread - Calling getaddrinfo for host [%s%s%s].\n", LOG_HOST(rec->host.get(), rec->netInterface.get()))); TimeStamp startTime = TimeStamp::Now(); -#if TTL_AVAILABLE bool getTtl = rec->mGetTtl; -#else - bool getTtl = false; -#endif - nsresult status = GetAddrInfo(rec->host.get(), rec->af, rec->flags, rec->netInterface.get(), &ai, @@ -1407,14 +1849,15 @@ nsHostResolver::ThreadFunc(void *arg) LOG_HOST(rec->host.get(), rec->netInterface.get()), ai ? "success" : "failure: unknown host")); - if (LOOKUP_RESOLVEAGAIN == resolver->CompleteLookup(rec, status, ai)) { + if (LOOKUP_RESOLVEAGAIN == resolver->CompleteLookup(rec, status, ai, rec->pb)) { // leave 'rec' assigned and loop to make a renewed host resolve LOG(("DNS lookup thread - Re-resolving host [%s%s%s].\n", LOG_HOST(rec->host.get(), rec->netInterface.get()))); } else { rec = nullptr; } - } + } while(true); + resolver->mThreadCount--; resolver = nullptr; LOG(("DNS lookup thread - queue empty, thread finished.\n")); @@ -1427,7 +1870,7 @@ nsHostResolver::Create(uint32_t maxCacheEntries, nsHostResolver **result) { auto *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime, - defaultGracePeriod); + defaultGracePeriod); NS_ADDREF(res); nsresult rv = res->Init(); @@ -1485,3 +1928,6 @@ nsHostResolver::GetDNSCacheEntries(nsTArray *args) args->AppendElement(info); } } + +#undef LOG +#undef LOG_ENABLED diff --git a/netwerk/dns/nsHostResolver.h b/netwerk/dns/nsHostResolver.h index d6a0815f31cd..f52c63c90841 100644 --- a/netwerk/dns/nsHostResolver.h +++ b/netwerk/dns/nsHostResolver.h @@ -15,19 +15,30 @@ #include "nsISupportsImpl.h" #include "nsIDNSListener.h" #include "nsIDNSService.h" -#include "nsString.h" #include "nsTArray.h" #include "GetAddrInfo.h" #include "mozilla/net/DNS.h" #include "mozilla/net/DashboardTypes.h" +#include "mozilla/Atomics.h" #include "mozilla/LinkedList.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "nsRefPtrHashtable.h" class nsHostResolver; -class nsHostRecord; class nsResolveHostCallback; +namespace mozilla { namespace net { +class TRR; +enum ResolverMode { + MODE_NATIVEONLY, // TRR OFF + MODE_PARALLEL, // race and use the first response + MODE_TRRFIRST, // fallback to native on TRR failure + MODE_TRRONLY, // don't even fallback + MODE_SHADOW // race for stats, but always use native result +}; +} } + +extern mozilla::Atomic gNativeIsLocalhost; #define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY 3 #define MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY 5 @@ -41,15 +52,17 @@ struct nsHostKey const nsCString host; uint16_t flags; uint16_t af; + bool pb; const nsCString netInterface; const nsCString originSuffix; nsHostKey(const nsACString& host, uint16_t flags, - uint16_t af, const nsACString& netInterface, + uint16_t af, bool pb, const nsACString& netInterface, const nsACString& originSuffix) : host(host) , flags(flags) , af(af) + , pb(pb) , netInterface(netInterface) , originSuffix(originSuffix) { } @@ -114,6 +127,12 @@ public: // but a request to refresh it will be made. mozilla::TimeStamp mGraceStart; + // When the lookups of this record started and their durations + mozilla::TimeStamp mTrrStart; + mozilla::TimeStamp mNativeStart; + mozilla::TimeDuration mTrrDuration; + mozilla::TimeDuration mNativeDuration; + // Convenience function for setting the timestamps above (mValidStart, // mValidEnd, and mGraceStart). valid and grace are durations in seconds. void SetExpiration(const mozilla::TimeStamp& now, unsigned int valid, @@ -139,6 +158,11 @@ public: bool RemoveOrRefresh(); // Mark records currently being resolved as needed // to resolve again. + bool IsTRR() { return mTRRUsed; } + void ResolveComplete(); + void Cancel(); + + mozilla::net::ResolverMode mResolverMode; private: friend class nsHostResolver; @@ -146,17 +170,27 @@ private: explicit nsHostRecord(const nsHostKey& key); mozilla::LinkedList> mCallbacks; - 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 TTL_AVAILABLE + int mResolving; // counter of outstanding resolving calls + bool mNative; // true if this record is being resolved "natively", + // which means that it is either on the pending queue + // or owned by one of the worker threads. */ + int mTRRSuccess; // number of successful TRR responses + bool mTRRUsed; // TRR was used on this record + bool mNativeUsed; + int mNativeSuccess; // number of native lookup responses + nsAutoPtr mFirstTRR; // partial TRR storage + 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 + bool mDidCallbacks; bool mGetTtl; -#endif + + enum { + INIT, STARTED, OK, FAILED + } mTrrAUsed, mTrrAAAAUsed; + + RefPtr mTrrA; + RefPtr mTrrAAAA; // The number of times ReportUnusable() has been called in the record's // lifetime. @@ -224,19 +258,43 @@ protected: virtual ~nsResolveHostCallback() = default; }; +class AHostResolver +{ +public: + AHostResolver() {} + virtual ~AHostResolver() {} + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + enum LookupStatus { + LOOKUP_OK, + LOOKUP_RESOLVEAGAIN, + }; + + virtual LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *, bool pb) = 0; + virtual nsresult GetHostRecord(const char *host, + uint16_t flags, uint16_t af, bool pb, + const nsCString &netInterface, + const nsCString &originSuffix, + nsHostRecord **result) + { + return NS_ERROR_FAILURE; + } + virtual nsresult TrrLookup_unlocked(nsHostRecord *, mozilla::net::TRR *pushedTRR = nullptr) + { + return NS_ERROR_FAILURE; + } +}; + /** * nsHostResolver - an asynchronous host name resolver. */ -class nsHostResolver +class nsHostResolver : public nsISupports, public AHostResolver { typedef mozilla::CondVar CondVar; typedef mozilla::Mutex Mutex; public: - /** - * host resolver instances are reference counted. - */ - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHostResolver) + NS_DECL_THREADSAFE_ISUPPORTS /** * creates an addref'd instance of a nsHostResolver object. @@ -310,7 +368,8 @@ public: //RES_DISABLE_IPV6 = nsIDNSService::RESOLVE_DISABLE_IPV6, // Not used RES_OFFLINE = nsIDNSService::RESOLVE_OFFLINE, //RES_DISABLE_IPv4 = nsIDNSService::RESOLVE_DISABLE_IPV4, // Not Used - RES_ALLOW_NAME_COLLISION = nsIDNSService::RESOLVE_ALLOW_NAME_COLLISION + RES_ALLOW_NAME_COLLISION = nsIDNSService::RESOLVE_ALLOW_NAME_COLLISION, + RES_DISABLE_TRR = nsIDNSService::RESOLVE_DISABLE_TRR }; size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; @@ -320,22 +379,30 @@ public: */ void FlushCache(); + LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *, bool pb) override; + nsresult GetHostRecord(const char *host, + uint16_t flags, uint16_t af, bool pb, + const nsCString &netInterface, + const nsCString &originSuffix, + nsHostRecord **result) override; + nsresult TrrLookup_unlocked(nsHostRecord *, mozilla::net::TRR *pushedTRR = nullptr) override; + private: explicit nsHostResolver(uint32_t maxCacheEntries, uint32_t defaultCacheEntryLifetime, uint32_t defaultGracePeriod); - ~nsHostResolver(); + virtual ~nsHostResolver(); nsresult Init(); - nsresult IssueLookup(nsHostRecord *); + void AssertOnQ(nsHostRecord *, PRCList *); + mozilla::net::ResolverMode Mode(); + nsresult NativeLookup(nsHostRecord *); + nsresult TrrLookup(nsHostRecord *, mozilla::net::TRR *pushedTRR = nullptr); + + // Kick-off a name resolve operation, using native resolver and/or TRR + nsresult NameLookup(nsHostRecord *); bool GetHostToLookup(nsHostRecord **m); - enum LookupStatus { - LOOKUP_OK, - LOOKUP_RESOLVEAGAIN, - }; - - LookupStatus CompleteLookup(nsHostRecord *, nsresult, mozilla::net::AddrInfo *); void DeQueue(PRCList &aQ, nsHostRecord **aResult); void ClearPendingQueue(PRCList *aPendingQueue); nsresult ConditionallyCreateThread(nsHostRecord *rec); diff --git a/netwerk/dns/nsIDNSRecord.idl b/netwerk/dns/nsIDNSRecord.idl index b3158bd3be01..4fd187bf2889 100644 --- a/netwerk/dns/nsIDNSRecord.idl +++ b/netwerk/dns/nsIDNSRecord.idl @@ -97,4 +97,9 @@ interface nsIDNSRecord : nsISupports * It may be zero if not applicable. */ void reportUnusable(in uint16_t aPort); + + /** + * Record retreived with TRR. + */ + bool IsTRR(); }; diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl index 632ebbfced11..5bb9350f7114 100644 --- a/netwerk/dns/nsIDNSService.idl +++ b/netwerk/dns/nsIDNSService.idl @@ -233,4 +233,10 @@ interface nsIDNSService : nsISupports * If set, allow name collision results (127.0.53.53) which are normally filtered. */ const unsigned long RESOLVE_ALLOW_NAME_COLLISION = (1 << 8); + + /** + * If set, do not use TRR for resolving the host name. + */ + const unsigned long RESOLVE_DISABLE_TRR = (1 << 9); + }; diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h index 55320102285a..200bad4919ca 100644 --- a/netwerk/protocol/http/Http2Session.h +++ b/netwerk/protocol/http/Http2Session.h @@ -154,7 +154,7 @@ public: // below the emergency threshold of local window we ack every received // byte. Above that we coalesce bytes into the MinimumToAck size. - const static int32_t kEmergencyWindowThreshold = 256 * 1024; + const static int32_t kEmergencyWindowThreshold = 96 * 1024; const static uint32_t kMinimumToAck = 4 * 1024 * 1024; // The default rwin is 64KB - 1 unless updated by a settings frame diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index e4fb7aef8eed..6f7975a12125 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -183,6 +183,7 @@ HttpBaseChannel::HttpBaseChannel() , mAllowSpdy(true) , mAllowAltSvc(true) , mBeConservative(false) + , mTRR(false) , mResponseTimeoutEnabled(true) , mAllRedirectsSameOrigin(true) , mAllRedirectsPassTimingAllowCheck(true) @@ -2687,6 +2688,22 @@ HttpBaseChannel::SetBeConservative(bool aBeConservative) return NS_OK; } +NS_IMETHODIMP +HttpBaseChannel::GetTrr(bool *aTRR) +{ + NS_ENSURE_ARG_POINTER(aTRR); + + *aTRR = mTRR; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetTrr(bool aTRR) +{ + mTRR = aTRR; + return NS_OK; +} + NS_IMETHODIMP HttpBaseChannel::GetTlsFlags(uint32_t *aTlsFlags) { @@ -3612,6 +3629,8 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, MOZ_ASSERT(NS_SUCCEEDED(rv)); rv = httpInternal->SetBeConservative(mBeConservative); MOZ_ASSERT(NS_SUCCEEDED(rv)); + rv = httpInternal->SetTrr(mTRR); + MOZ_ASSERT(NS_SUCCEEDED(rv)); rv = httpInternal->SetTlsFlags(mTlsFlags); MOZ_ASSERT(NS_SUCCEEDED(rv)); diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 6766b2bb69cd..41e678ba57a2 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -236,6 +236,8 @@ public: NS_IMETHOD SetAllowAltSvc(bool aAllowAltSvc) override; NS_IMETHOD GetBeConservative(bool *aBeConservative) override; NS_IMETHOD SetBeConservative(bool aBeConservative) override; + NS_IMETHOD GetTrr(bool *aTRR) override; + NS_IMETHOD SetTrr(bool aTRR) override; NS_IMETHOD GetTlsFlags(uint32_t *aTlsFlags) override; NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override; NS_IMETHOD GetApiRedirectToURI(nsIURI * *aApiRedirectToURI) override; @@ -574,6 +576,7 @@ protected: uint32_t mAllowSpdy : 1; uint32_t mAllowAltSvc : 1; uint32_t mBeConservative : 1; + uint32_t mTRR : 1; uint32_t mResponseTimeoutEnabled : 1; // A flag that should be false only if a cross-domain redirect occurred uint32_t mAllRedirectsSameOrigin : 1; diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index 6f6f5c713851..732ceb8bd4ba 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -58,7 +58,7 @@ typedef uint8_t nsHttpVersion; //----------------------------------------------------------------------------- #define NS_HTTP_ALLOW_KEEPALIVE (1<<0) -// NS_HTTP_ALLOW_PIPELINING (1<<1) removed +#define NS_HTTP_LARGE_KEEPALIVE (1<<1) // a transaction with this caps flag will continue to own the connection, // preventing it from being reclaimed, even after the transaction completes. diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 813f4bbd8d61..6d3fc21be812 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -1010,6 +1010,9 @@ nsHttpChannel::SetupTransaction() mUsedNetwork = 1; + if (mTRR) { + mCaps |= NS_HTTP_LARGE_KEEPALIVE; + } if (!mAllowSpdy) { mCaps |= NS_HTTP_DISALLOW_SPDY; } @@ -3756,6 +3759,9 @@ nsHttpChannel::OpenCacheEntryInternal(bool isHttps, if (mPostID) { extension.Append(nsPrintfCString("%d", mPostID)); } + if (mTRR) { + extension.Append("TRR"); + } mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY; mCacheQueueSizeWhenOpen = CacheStorageService::CacheQueueSize(mCacheOpenWithPriority); diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 755207886f3b..ef49084416ff 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -81,6 +81,7 @@ nsHttpConnection::nsHttpConnection() , mEverUsedSpdy(false) , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1) , mTransactionCaps(0) + , mDefaultTimeoutFactor(1) , mResponseTimeoutEnabled(false) , mTCPKeepaliveConfig(kTCPKeepaliveDisabled) , mForceSendPending(false) @@ -347,7 +348,7 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion) "rv[0x%" PRIx32 "]", this, static_cast(rv))); } - mIdleTimeout = gHttpHandler->SpdyTimeout(); + mIdleTimeout = gHttpHandler->SpdyTimeout() * mDefaultTimeoutFactor; if (!mTLSFilter) { mTransaction = mSpdySession; @@ -643,6 +644,10 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri mBootstrappedTimings = TimingStruct(); } + if (caps & NS_HTTP_LARGE_KEEPALIVE) { + mDefaultTimeoutFactor = 10; // don't ever lower + } + mTransactionCaps = caps; mPriority = pri; if (mTransaction && mUsingSpdyVersion) { @@ -1043,14 +1048,20 @@ nsHttpConnection::IdleTime() uint32_t nsHttpConnection::TimeToLive() { - if (IdleTime() >= mIdleTimeout) + LOG(("nsHttpConnection::TTL: %p %s idle %d timeout %d\n", + this, mConnInfo->Origin(), IdleTime(), mIdleTimeout)); + + if (IdleTime() >= mIdleTimeout) { return 0; + } + uint32_t timeToLive = PR_IntervalToSeconds(mIdleTimeout - IdleTime()); // a positive amount of time can be rounded to 0. Because 0 is used // as the expiration signal, round all values from 0 to 1 up to 1. - if (!timeToLive) + if (!timeToLive) { timeToLive = 1; + } return timeToLive; } @@ -1181,7 +1192,7 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, if (cp) mIdleTimeout = PR_SecondsToInterval((uint32_t) atoi(cp + 8)); else - mIdleTimeout = gHttpHandler->IdleTimeout(); + mIdleTimeout = gHttpHandler->IdleTimeout() * mDefaultTimeoutFactor; cp = PL_strcasestr(keepAlive.get(), "max="); if (cp) { @@ -1192,9 +1203,6 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, } } } - else { - mIdleTimeout = gHttpHandler->SpdyTimeout(); - } LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n", this, PR_IntervalToSeconds(mIdleTimeout))); diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index a9430f760af8..7531585f2a06 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -380,6 +380,10 @@ private: // The capabailities associated with the most recent transaction uint32_t mTransactionCaps; + // If a large keepalive has been requested for any trans, + // scale the default by this factor + uint32_t mDefaultTimeoutFactor; + bool mResponseTimeoutEnabled; // Flag to indicate connection is in inital keepalive period (fast detect). diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index c0b157f48bbd..997816af948e 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -257,7 +257,7 @@ nsHttpHandler::nsHttpHandler() , mEnableOriginExtension(false) , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize) , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize) - , mSpdyPushAllowance(32768) + , mSpdyPushAllowance(131072) // match default pref , mSpdyPullAllowance(ASpdySession::kInitialRwin) , mDefaultSpdyConcurrent(ASpdySession::kDefaultMaxConcurrent) , mSpdyPingThreshold(PR_SecondsToInterval(58)) diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index 4a7aa7df2476..082df2b76444 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -216,6 +216,12 @@ interface nsIHttpChannelInternal : nsISupports */ [must_use] attribute boolean beConservative; + /** + * True if channel is used by the internal trusted recursive resolver + * This flag places data for the request in a cache segment specific to TRR + */ + [noscript, must_use] attribute boolean trr; + /** * An opaque flags for non-standard behavior of the TLS system. * It is unlikely this will need to be set outside of telemetry studies diff --git a/netwerk/test/unit/test_trr.js b/netwerk/test/unit/test_trr.js new file mode 100644 index 000000000000..05a1bddc8558 --- /dev/null +++ b/netwerk/test/unit/test_trr.js @@ -0,0 +1,448 @@ +Cu.import("resource://gre/modules/NetUtil.jsm"); + +var prefs; +var origin; +var h2Port; + +var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService); +var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); +var mainThread = threadManager.currentThread; + +const defaultOriginAttributes = {}; + +function run_test() { + dump ("start!\n"); + + var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + h2Port = env.get("MOZHTTP2_PORT"); + Assert.notEqual(h2Port, null); + Assert.notEqual(h2Port, ""); + + // Set to allow the cert presented by our H2 server + do_get_profile(); + prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + + prefs.setBoolPref("network.http.spdy.enabled", true); + prefs.setBoolPref("network.http.spdy.enabled.http2", true); + // the TRR server is on 127.0.0.1 + prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1"); + + // use the h2 server as DOH provider + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns"); + // make all native resolve calls "secretly" resolve localhost instead + prefs.setBoolPref("network.dns.native-is-localhost", true); + + // 0 - off, 1 - race, 2 TRR first, 3 TRR only, 4 shadow + prefs.setIntPref("network.trr.mode", 2); // TRR first + prefs.setBoolPref("network.trr.wait-for-portal", false); + // don't confirm that TRR is working, just go! + prefs.setCharPref("network.trr.confirmationNS", "skip"); + + // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der + // so add that cert to the trust list as a signing cert. // the foo.example.com domain name. + let certdb = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + addCertFromFile(certdb, "CA.cert.der", "CTu,u,u"); + do_test_pending(); + run_dns_tests(); +} + +function resetTRRPrefs() { + prefs.clearUserPref("network.trr.mode"); + prefs.clearUserPref("network.trr.uri"); + prefs.clearUserPref("network.trr.credentials"); + prefs.clearUserPref("network.trr.wait-for-portal"); + prefs.clearUserPref("network.trr.allow-rfc1918"); + prefs.clearUserPref("network.trr.useGET"); + prefs.clearUserPref("network.trr.confirmationNS"); + prefs.clearUserPref("network.trr.bootstrapAddress"); + prefs.clearUserPref("network.trr.blacklist-duration"); + prefs.clearUserPref("network.trr.request-timeout"); +} + +registerCleanupFunction(() => { + prefs.clearUserPref("network.http.spdy.enabled"); + prefs.clearUserPref("network.http.spdy.enabled.http2"); + prefs.clearUserPref("network.dns.localDomains"); + prefs.clearUserPref("network.dns.native-is-localhost"); + resetTRRPrefs(); +}); + +function readFile(file) { + let fstream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + fstream.init(file, -1, 0, 0); + let data = NetUtil.readInputStreamToString(fstream, fstream.available()); + fstream.close(); + return data; +} + +function addCertFromFile(certdb, filename, trustString) { + let certFile = do_get_file(filename, false); + let der = readFile(certFile); + certdb.addCert(der, trustString); +} + +function testsDone() +{ + do_test_finished(); + do_test_finished(); +} + +var test_loops; +var test_answer="127.0.0.1"; + +// check that we do lookup the name fine +var listenerFine = { + onLookupComplete: function(inRequest, inRecord, inStatus) { + if (inRequest == listen) { + Assert.ok(!inStatus); + var answer = inRecord.getNextAddrAsString(); + Assert.equal(answer, test_answer); + do_test_finished(); + run_dns_tests(); + } + }, + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIDNSListener) || + aIID.equals(Ci.nsISupports)) { + return this; + } + throw Cr.NS_ERROR_NO_INTERFACE; + } +}; + +// check that the name lookup fails +var listenerFails = { + onLookupComplete: function(inRequest, inRecord, inStatus) { + if (inRequest == listen) { + Assert.ok(!Components.isSuccessCode(inStatus)); + do_test_finished(); + run_dns_tests(); + } + }, + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIDNSListener) || + aIID.equals(Ci.nsISupports)) { + return this; + } + throw Cr.NS_ERROR_NO_INTERFACE; + } +}; + +// check that we do lookup the name fine +var listenerUntilFine = { + onLookupComplete: function(inRequest, inRecord, inStatus) { + if ((inRequest == listen) && (inRecord != null)) { + var answer = inRecord.getNextAddrAsString(); + if (answer == test_answer) { + Assert.equal(answer, test_answer); + dump("Got what we were waiting for!\n"); + } + } + else { + // not the one we want, try again + dump("Waiting for " + test_answer + " but got " + answer + "\n"); + --test_loops; + Assert.ok(test_loops != 0); + current_test--; + } + do_test_finished(); + run_dns_tests(); + }, + QueryInterface: function(aIID) { + if (aIID.equals(Ci.nsIDNSListener) || + aIID.equals(Ci.nsISupports)) { + return this; + } + throw Cr.NS_ERROR_NO_INTERFACE; + } +}; + +var listen; + +// verify basic A record +function test1() +{ + prefs.setIntPref("network.trr.mode", 2); // TRR-first + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns"); + test_answer="127.0.0.1"; + listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// verify basic A record - without bootstrapping +function test1b() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns"); + prefs.clearUserPref("network.trr.bootstrapAddress"); + prefs.setCharPref("network.dns.localDomains", "foo.example.com"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// verify that the name was put in cache - it works with bad DNS URI +function test2() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + //prefs.clearUserPref("network.trr.bootstrapAddress"); + //prefs.setCharPref("network.dns.localDomains", "foo.example.com"); + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// verify working credentials in DOH request +function test3() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-auth"); + prefs.setCharPref("network.trr.credentials", "user:password"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("auth.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// verify failing credentials in DOH request +function test4() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-auth"); + prefs.setCharPref("network.trr.credentials", "evil:person"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("wrong.example.com", 0, listenerFails, mainThread, defaultOriginAttributes); +} + +// verify DOH push, part A +function test5() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-push"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("first.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +function test5b() +{ + // At this point the second host name should've been pushed and we can resolve it using + // cache only. Set back the URI to a path that fails. + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404"); + dump("test5b - resolve push.example.now please\n"); + test_answer = "2018::2018"; + listen = dns.asyncResolve("push.example.com", 0, listenerUntilFine, mainThread, defaultOriginAttributes); +} + +// verify AAAA entry +function test6() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-aaaa"); + test_answer = "2020:2020::2020"; + listen = dns.asyncResolve("aaaa.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// verify RFC1918 address from the server is rejected +function test7() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-rfc1918"); + listen = dns.asyncResolve("rfc1918.example.com", 0, listenerFails, mainThread, defaultOriginAttributes); +} + +// verify RFC1918 address from the server is fine when told so +function test8() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-rfc1918"); + prefs.setBoolPref("network.trr.allow-rfc1918", true); + test_answer = "192.168.0.1"; + listen = dns.asyncResolve("rfc1918.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// use GET +function test9() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-get"); + prefs.clearUserPref("network.trr.allow-rfc1918"); + prefs.setBoolPref("network.trr.useGET", true); + test_answer = "1.2.3.4"; + listen = dns.asyncResolve("get.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// confirmationNS set without confirmed NS yet +// NOTE: this requires test9 to run before, as the http2 server resets state there +function test10() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.clearUserPref("network.trr.useGET"); + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-confirm"); + prefs.setCharPref("network.trr.confirmationNS", "confirm.example.com"); + test_loops = 100; // set for test10b + try { + listen = dns.asyncResolve("wrong.example.com", 0, listenerFails, + mainThread, defaultOriginAttributes); + } catch (e) { + // NS_ERROR_UNKNOWN_HOST exception is expected + do_test_finished(); + run_dns_tests(); + } +} + +// confirmationNS, retry until the confirmed NS works +function test10b() +{ + print("test confirmationNS, retry until the confirmed NS works"); + prefs.setIntPref("network.trr.mode", 3); // TRR-only + // same URI as in test10 + test_answer = "1::ffff" + // this test needs to resolve new names in every attempt since the DNS cache + // will keep the negative resolved info + try { + listen = dns.asyncResolve("10b-" + test_loops + ".example.com", 0, listenerUntilFine, + mainThread, defaultOriginAttributes); + } catch(e) { + // wait a while and try again + test_loops--; + do_timeout(200, test10b); + } +} +// use a slow server and short timeout! +function test11() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.confirmationNS", "skip"); + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-750ms"); + prefs.setIntPref("network.trr.request-timeout", 10); + listen = dns.asyncResolve("test11.example.com", 0, listenerFails, mainThread, defaultOriginAttributes); +} + +// gets an NS back from DOH +function test12() +{ + prefs.setIntPref("network.trr.mode", 2); // TRR-first + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-ns"); + prefs.clearUserPref("network.trr.request-timeout"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test12.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-first gets a 404 back from DOH +function test13() +{ + prefs.setIntPref("network.trr.mode", 2); // TRR-first + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test13.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-shadow gets a 404 back from DOH +function test14() +{ + prefs.setIntPref("network.trr.mode", 4); // TRR-shadow + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test14.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-shadow and timed out TRR +function test15() +{ + prefs.setIntPref("network.trr.mode", 4); // TRR-shadow + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-750ms"); + prefs.setIntPref("network.trr.request-timeout", 10); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test15.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-first and timed out TRR +function test16() +{ + prefs.setIntPref("network.trr.mode", 2); // TRR-first + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-750ms"); + prefs.setIntPref("network.trr.request-timeout", 10); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test16.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-only and chase CNAME +function test17() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname"); + prefs.clearUserPref("network.trr.request-timeout"); + test_answer = "99.88.77.66"; + listen = dns.asyncResolve("cname.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-only and a CNAME loop +function test18() +{ + prefs.setIntPref("network.trr.mode", 3); // TRR-only + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop"); + listen = dns.asyncResolve("test18.example.com", 0, listenerFails, mainThread, defaultOriginAttributes); +} + +// TRR-race and a CNAME loop +function test19() +{ + prefs.setIntPref("network.trr.mode", 1); // Race them! + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test19.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-first and a CNAME loop +function test20() +{ + prefs.setIntPref("network.trr.mode", 2); // TRR-first + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test20.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +// TRR-shadow and a CNAME loop +function test21() +{ + prefs.setIntPref("network.trr.mode", 4); // TRR-first + prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop"); + test_answer = "127.0.0.1"; + listen = dns.asyncResolve("test21.example.com", 0, listenerFine, mainThread, defaultOriginAttributes); +} + +var tests = [ test1, + test1b, + test2, + test3, + test4, + test5, + test5b, + test6, + test7, + test8, + test9, + test10, + test10b, + test11, + test12, + test13, + test14, + test15, + test16, + test17, + test18, + test19, + test20, + test21, + testsDone + ]; + +var current_test = 0; + +function run_dns_tests() +{ + if (current_test < tests.length) { + dump("starting test " + current_test + "\n"); + do_test_pending(); + tests[current_test++](); + } +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index d5a909f23722..b96be310f772 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -412,3 +412,6 @@ skip-if = os == "android" [test_uri_mutator.js] [test_bug1411316_http1.js] [test_header_Server_Timing.js] +[test_trr.js] +# http2-using tests require node available +skip-if = os == "android" diff --git a/security/manager/ssl/DataStorageList.h b/security/manager/ssl/DataStorageList.h index 307a9373a84f..235300a4d678 100644 --- a/security/manager/ssl/DataStorageList.h +++ b/security/manager/ssl/DataStorageList.h @@ -16,3 +16,4 @@ DATA_STORAGE(AlternateServices) DATA_STORAGE(SecurityPreloadState) DATA_STORAGE(SiteSecurityServiceState) +DATA_STORAGE(TRRBlacklist) diff --git a/testing/xpcshell/moz-http2/moz-http2.js b/testing/xpcshell/moz-http2/moz-http2.js index d5a06ed388c8..aa0df58f7ef6 100644 --- a/testing/xpcshell/moz-http2/moz-http2.js +++ b/testing/xpcshell/moz-http2/moz-http2.js @@ -194,6 +194,9 @@ var didRst = false; var rstConnection = null; var illegalheader_conn = null; +var ns_confirm = 0; +var cname_confirm = 0; + function handleRequest(req, res) { // We do this first to ensure nothing goes wonky in our tests that don't want // the headers to have something illegal in them @@ -526,6 +529,163 @@ function handleRequest(req, res) { res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Alt-Svc', 'h2=' + req.headers['x-altsvc']); } + // for use with test_trr.js + else if (u.pathname === "/dns-cname") { + // asking for cname.example.com + var content; + if(0 == cname_confirm) { + // ... this sends a CNAME back to pointing-elsewhere.example.com + content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C6503636F6D00", "hex"); + cname_confirm++; + } + else { + // ... this sends an A 99.88.77.66 entry back for pointing-elsewhere.example.com + content = new Buffer("00000100000100010000000012706F696E74696E672D656C73657768657265076578616D706C6503636F6D0000010001C00C0001000100000037000463584D42", "hex"); + } + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + + } + else if (u.pathname === "/dns-cname-loop") { + // asking for cname.example.com + var content; + // ... this always sends a CNAME back to pointing-elsewhere.example.com. Loop time! + content = new Buffer("00000100000100010000000005636E616D65076578616D706C6503636F6D0000050001C00C0005000100000037002012706F696E74696E672D656C73657768657265076578616D706C6503636F6D00", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + + } + + // for use with test_trr.js + else if (u.path === "/dns-get?ct&dns=AAAAAAABAAAAAAAAA2dldAdleGFtcGxlA2NvbQAAAQAB") { + // the query string asks for an A entry for get.example.com + // get.example.com has A entry 1.2.3.4 + var content= new Buffer("00000100000100010000000003676574076578616D706C6503636F6D0000010001C00C0001000100000037000401020304", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + ns_confirm = 0; // back to first reply for dns-confirm + cname_confirm = 0; // back to first reply for dns-cname + return; + } + // for use with test_trr.js + else if (u.pathname === "/dns") { + // bar.example.com has A entry 127.0.0.1 + var content= new Buffer("00000100000100010000000003626172076578616D706C6503636F6D0000010001C00C000100010000003700047F000001", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } + else if (u.pathname === "/dns-ns") { + // confirm.example.com has NS entry ns.example.com + var content= new Buffer("00000100000100010000000007636F6E6669726D076578616D706C6503636F6D0000020001C00C00020001000000370012026E73076578616D706C6503636F6D010A00", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } + else if (u.pathname === '/dns-750ms') { + // it's just meant to be this slow - the test doesn't care about the actual response + return; + } + // for use with test_trr.js + else if (u.pathname === "/dns-confirm") { + if (0 == ns_confirm) { + // confirm.example.com has NS entry ns.example.com + var content= new Buffer("00000100000100010000000007636F6E6669726D076578616D706C6503636F6D0000020001C00C00020001000000370012026E73076578616D706C6503636F6D010A00", "hex"); + ns_confirm++; + } else { + // next response: wrong.example.com has AAAA entry 1::FFFF + var content= new Buffer("0000010000010001000000000577726F6E67076578616D706C6503636F6D00001C0001C00C001C00010000003700100001000000000000000000000000FFFF", "hex"); + } + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } + // for use with test_trr.js + else if (u.pathname === "/dns-aaaa") { + // aaaa.example.com has AAAA entry 2020:2020::2020 + var content= new Buffer("0000010000010001000000000461616161076578616D706C6503636F6D00001C0001C00C001C000100000037001020202020000000000000000000002020", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } + else if (u.pathname === "/dns-rfc1918") { + // rfc1918.example.com has A entry 192.168.0.1 + var content= new Buffer("0000010000010001000000000772666331393138076578616D706C6503636F6D0000010001C00C00010001000000370004C0A80001", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } + // for use with test_trr.js + else if (u.pathname === "/dns-push") { + // first.example.com has A entry 127.0.0.1 + var content= new Buffer("000001000001000100000000056669727374076578616D706C6503636F6D0000010001C00C000100010000003700047F000001", "hex"); + // push.example.com has AAAA entry 2018::2018 + var pcontent= new Buffer("0000010000010001000000000470757368076578616D706C6503636F6D00001C0001C00C001C000100000037001020180000000000000000000000002018", "hex"); + push = res.push({ + hostname: 'foo.example.com:' + serverPort, + port: serverPort, + path: '/dns-pushed-response?ct&dns=AAAAAAABAAAAAAAABHB1c2gHZXhhbXBsZQNjb20AABwAAQ', + method: 'GET', + headers: { + 'accept' : 'application/dns-udpwireformat' + } + }); + push.writeHead(200, { + 'content-type': 'application/dns-udpwireformat', + 'pushed' : 'yes', + 'content-length' : pcontent.length, + 'X-Connection-Http2': 'yes' + }); + push.end(pcontent); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } + // for use with test_trr.js + else if (u.pathname === "/dns-auth") { + if (req.headers['authorization'] != "user:password") { + res.writeHead(401); + res.end("bad boy!"); + return; + } + // bar.example.com has A entry 127.0.0.1 + var content= new Buffer("00000100000100010000000003626172076578616D706C6503636F6D0000010001C00C000100010000003700047F000001", "hex"); + res.setHeader('Content-Type', 'application/dns-udpwireformat'); + res.setHeader('Content-Length', content.length); + res.writeHead(200); + res.write(content); + res.end(""); + return; + } else if (u.pathname === "/.well-known/http-opportunistic") { res.setHeader('Cache-Control', 'no-cache'); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index fb442b65bf96..0ef60b861f0b 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -3612,7 +3612,7 @@ "description": "I want to be tracked, I do NOT want to be tracked, DNT unset" }, "DNS_LOOKUP_METHOD2": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "kind": "enumerated", "n_values": 16, @@ -3621,7 +3621,7 @@ "description": "DNS Lookup Type (hit, renewal, negative-hit, literal, overflow, network-first, network-shared)" }, "DNS_CLEANUP_AGE": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "kind": "exponential", "high": 1440, @@ -3629,17 +3629,75 @@ "description": "DNS Cache Entry Age at Removal Time (minutes)" }, "DNS_LOOKUP_TIME": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "kind": "exponential", "high": 60000, "releaseChannelCollection": "opt-out", "alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"], "n_buckets": 50, - "description": "Time for a successful DNS OS resolution (msec)" + "description": "Time for a successful DNS resolution (msec)" + }, + "DNS_TRR_LOOKUP_TIME": { + "record_in_processes": ["main"], + "expires_in_version": "never", + "kind": "exponential", + "high": 60000, + "releaseChannelCollection": "opt-out", + "alert_emails": ["necko@mozilla.com", "dstenberg@mozilla.com"], + "bug_numbers": [1434852], + "n_buckets": 50, + "description": "Time for a completed TRR resolution (msec)" + }, + "DNS_NATIVE_LOOKUP_TIME": { + "record_in_processes": ["main"], + "expires_in_version": "never", + "kind": "exponential", + "high": 60000, + "releaseChannelCollection": "opt-out", + "alert_emails": ["necko@mozilla.com", "dstenberg@mozilla.com"], + "bug_numbers": [1434852], + "n_buckets": 50, + "description": "Time for a completed native name resolution (msec)" + }, + "DNS_TRR_RACE": { + "record_in_processes": ["main"], + "alert_emails": ["necko@mozilla.com", "dstenberg@mozilla.com"], + "expires_in_version": "never", + "kind": "categorical", + "labels": ["TRRFasterBy50", "TRRFaster", "NativeFaster", "NativeFasterBy50"], + "bug_numbers": [1434852], + "description": "DNS: TRR parallel resolve racing results" + }, + "DNS_TRR_BLACKLISTED": { + "record_in_processes": ["main"], + "expires_in_version": "never", + "kind": "boolean", + "description": "DNS check for TRR was blocked by blacklist", + "bug_numbers": [1434852], + "alert_emails": ["necko@mozilla.com", "dstenberg@mozilla.com"] + }, + "DNS_LOOKUP_ALGORITHM": { + "record_in_processes": ["main"], + "alert_emails": ["necko@mozilla.com", "dstenberg@mozilla.com"], + "expires_in_version": "never", + "kind": "categorical", + "labels": ["nativeOnly", "trrRace", "trrFirst", "trrOnly", "trrShadow"], + "bug_numbers": [1434852], + "description": "DNS: lookup algorithm" + }, + "DNS_LOOKUP_DISPOSITION": { + "record_in_processes": ["main"], + "alert_emails": ["necko@mozilla.com", "dstenberg@mozilla.com"], + "expires_in_version": "never", + "kind": "categorical", + "labels": ["trrOK", "trrFail", "trrAOK", "trrAFail", + "trrAAAAOK", "trrAAAAFail", "osOK", "osFail"], + "bug_numbers": [1434852], + "description": "DNS: lookup algorithm" }, "DNS_RENEWAL_TIME": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "kind": "exponential", "high": 60000, @@ -3647,7 +3705,7 @@ "description": "Time for a renewed DNS OS resolution (msec)" }, "DNS_RENEWAL_TIME_FOR_TTL": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "kind": "exponential", "high": 60000, @@ -3655,7 +3713,7 @@ "description": "Time for a DNS OS resolution (msec) used to get TTL" }, "DNS_FAILED_LOOKUP_TIME": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "kind": "exponential", "high": 60000, @@ -3665,7 +3723,7 @@ "description": "Time for an unsuccessful DNS OS resolution (msec)" }, "DNS_BLACKLIST_COUNT": { - "record_in_processes": ["main", "content"], + "record_in_processes": ["main"], "expires_in_version": "never", "releaseChannelCollection": "opt-out", "alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],