--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2083,16 +2083,20 @@ pref("network.dns.disableIPv6", false);
pref("network.dnsCacheEntries", 400);
// In the absence of OS TTLs, the DNS cache TTL value
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);
// This preference can be used to turn off DNS prefetch.
pref("network.dns.disablePrefetch", false);
// This preference controls whether .onion hostnames are
@@ -5399,16 +5403,40 @@ pref("memory_info_dumper.watch_fifo.enab
// If minInterval is 0, the check will only happen
// when the service has a strong suspicion we are in a captive portal
pref("network.captive-portal-service.minInterval", 60000); // 60 seconds
pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes
// Every 10 checks, the delay is increased by a factor of 5
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);
pref("captivedetect.pollingTime", 3000);
pref("captivedetect.maxRetryCount", 5);
#ifdef RELEASE_OR_BETA
pref("dom.forms.inputmode", false);
--- a/netwerk/base/nsIChannel.idl
+++ b/netwerk/base/nsIChannel.idl
@@ -196,17 +196,17 @@ interface nsIChannel : nsIRequest
/**
* Performs content security check and calls asyncOpen().
*/
void asyncOpen2(in nsIStreamListener aListener);
/**************************************************************************
* 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).
*/
/**
* Set (e.g., by the docshell) to indicate whether or not the channel
* corresponds to a document URI.
* While setting this flag is sufficient to mark a channel as a document
* load, _checking_ whether the channel is a document load requires the use
--- a/netwerk/base/nsISocketTransport.idl
+++ b/netwerk/base/nsISocketTransport.idl
@@ -233,16 +233,23 @@ interface nsISocketTransport : nsITransp
/**
* If set, do not use newer protocol features that might have interop problems
* on the Internet. Intended only for use with critical infra like the updater.
* default is false.
*/
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
* relating to the TLS implementation.
*/
attribute unsigned long tlsFlags;
/**
* Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -1105,16 +1105,18 @@ nsSocketTransport::ResolveHost()
uint32_t dnsFlags = 0;
if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE)
dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6)
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),
"Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
SendStatus(NS_NET_STATUS_RESOLVING_HOST);
if (!SocketHost().Equals(mOriginHost)) {
@@ -1797,16 +1799,27 @@ nsSocketTransport::RecoverFromError()
// XXX Could be optimized to only switch the flags to save
// duplicate connection attempts.
SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only "
"hosts, trying lookup/connect again with both "
"ipv4/ipv6\n"));
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;
+ }
}
}
}
// prepare to try again.
if (tryAgain) {
uint32_t msg;
--- a/netwerk/dns/DNS.cpp
+++ b/netwerk/dns/DNS.cpp
@@ -1,8 +1,10 @@
+/* -*- 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 "mozilla/net/DNS.h"
#include "mozilla/Assertions.h"
#include "mozilla/mozalloc.h"
@@ -291,16 +293,17 @@ NetAddrElement::NetAddrElement(const Net
NetAddrElement::~NetAddrElement() = default;
AddrInfo::AddrInfo(const char *host, const PRAddrInfo *prAddrInfo,
bool disableIPv4, bool filterNameCollision, const char *cname)
: 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
Init(host, cname);
PRNetAddr tmpAddr;
void *iter = nullptr;
do {
@@ -314,20 +317,50 @@ AddrInfo::AddrInfo(const char *host, con
}
} while (iter);
}
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;
while ((addrElement = mAddresses.popLast())) {
delete addrElement;
}
free(mHostName);
free(mCanonicalName);
--- a/netwerk/dns/DNS.h
+++ b/netwerk/dns/DNS.h
@@ -130,35 +130,41 @@ public:
NetAddr mAddress;
};
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;
char *mHostName;
char *mCanonicalName;
uint32_t ttl;
static const uint32_t NO_TTL_DATA = (uint32_t) -1;
LinkedList<NetAddrElement> mAddresses;
-
+ unsigned int IsTRR() { return mFromTRR; }
private:
+ unsigned int mFromTRR;
void Init(const char *host, const char *cname);
};
// Copies the contents of a PRNetAddr to a NetAddr.
// Does not do a ptr safety check!
void PRNetAddrToNetAddr(const PRNetAddr *prAddr, NetAddr *addr);
// Copies the contents of a NetAddr to a PRNetAddr.
--- a/netwerk/dns/DNSRequestChild.cpp
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -77,16 +77,23 @@ ChildDNSRecord::GetCanonicalName(nsACStr
return NS_ERROR_NOT_AVAILABLE;
}
result = mCanonicalName;
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)
{
if (mCurrent >= mLength) {
return NS_ERROR_NOT_AVAILABLE;
}
memcpy(addr, &mAddresses[mCurrent++], sizeof(NetAddr));
--- a/netwerk/dns/GetAddrInfo.cpp
+++ b/netwerk/dns/GetAddrInfo.cpp
@@ -15,17 +15,17 @@
#include "MainThreadUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/net/DNS.h"
#include <algorithm>
#include "prerror.h"
#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
// we make sure that UNICODE is unset.
#undef UNICODE
#include <ws2tcpip.h>
#undef GetAddrInfo
#include <windns.h>
@@ -35,17 +35,17 @@ namespace mozilla {
namespace net {
static LazyLogModule gGetAddrInfoLog("GetAddrInfo");
#define LOG(msg, ...) \
MOZ_LOG(gGetAddrInfoLog, LogLevel::Debug, ("[DNS]: " msg, ##__VA_ARGS__))
#define LOG_WARNING(msg, ...) \
MOZ_LOG(gGetAddrInfoLog, LogLevel::Warning, ("[DNS]: " msg, ##__VA_ARGS__))
-#if DNSQUERY_AVAILABLE
+#ifdef DNSQUERY_AVAILABLE
////////////////////////////
// WINDOWS IMPLEMENTATION //
////////////////////////////
// Ensure consistency of PR_* and AF_* constants to allow for legacy usage of
// PR_* constants with this API.
static_assert(PR_AF_INET == AF_INET && PR_AF_INET6 == AF_INET6
&& PR_AF_UNSPEC == AF_UNSPEC, "PR_AF_* must match AF_*");
@@ -297,54 +297,60 @@ static MOZ_ALWAYS_INLINE nsresult
//////////////////////////////////////
// COMMON/PLATFORM INDEPENDENT CODE //
//////////////////////////////////////
nsresult
GetAddrInfoInit() {
LOG("Initializing GetAddrInfo.\n");
-#if DNSQUERY_AVAILABLE
+#ifdef DNSQUERY_AVAILABLE
return _GetAddrInfoInit_Windows();
#else
return NS_OK;
#endif
}
nsresult
GetAddrInfoShutdown() {
LOG("Shutting down GetAddrInfo.\n");
-#if DNSQUERY_AVAILABLE
+#ifdef DNSQUERY_AVAILABLE
return _GetAddrInfoShutdown_Windows();
#else
return NS_OK;
#endif
}
nsresult
GetAddrInfo(const char* aHost, uint16_t aAddressFamily, uint16_t aFlags,
const char* aNetworkInterface, AddrInfo** aAddrInfo, bool aGetTtl)
{
if (NS_WARN_IF(!aHost) || NS_WARN_IF(!aAddrInfo)) {
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.
const char *name = nullptr;
if (*aAddrInfo != nullptr && (*aAddrInfo)->mCanonicalName) {
name = (*aAddrInfo)->mCanonicalName;
} else {
name = aHost;
--- a/netwerk/dns/GetAddrInfo.h
+++ b/netwerk/dns/GetAddrInfo.h
@@ -7,20 +7,18 @@
#ifndef netwerk_dns_GetAddrInfo_h
#define netwerk_dns_GetAddrInfo_h
#include "nsError.h"
#include "nscore.h"
#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 {
namespace net {
class AddrInfo;
/**
@@ -30,18 +28,18 @@ class AddrInfo;
* @param aHost[in] Character string defining the host name of interest
* @param aAddressFamily[in] May be AF_INET, AF_INET6, or AF_UNSPEC.
* @param aFlags[in] May be either PR_AI_ADDRCONFIG or
* PR_AI_ADDRCONFIG | PR_AI_NOCANONNAME. Include PR_AI_NOCANONNAME to
* suppress the determination of the canonical name corresponding to
* hostname (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,
const char* aNetworkInterface, AddrInfo** aAddrInfo, bool aGetTtl);
/**
* Initialize the GetAddrInfo module.
*
new file mode 100644
--- /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<unsigned char>(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<uint8_t>(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<nsIIOService> ios(do_GetIOService(&rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useGet = mTRRService->UseGET();
+ nsAutoCString body;
+ nsCOMPtr<nsIURI> 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<const unsigned char *>(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<nsIHttpChannel> 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<nsIHttpChannelInternal> 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<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
+ if (!uploadChannel) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<nsIInputStream> 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<nsIHttpPushListener> copy(this);
+ *result = copy.forget().take();
+ return NS_OK;
+}
+
+nsresult
+TRR::DohDecodeQuery(const nsCString &query, nsCString &host, enum TrrType &type)
+{
+ FallibleTArray<uint8_t> 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<nsIURI> 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<nsHostRecord> 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> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint16_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<AddrInfo> ai(new AddrInfo(mHost.get(), mType));
+ DOHaddr *item;
+ uint32_t ttl = AddrInfo::NO_TTL_DATA;
+ while ((item = static_cast<DOHaddr*>(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> 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<nsIChannel> channel;
+ channel.swap(mChannel);
+
+ // if status was "fine", parse the response and pass on the answer
+ if (!mFailed && NS_SUCCEEDED(aStatusCode)) {
+ nsCOMPtr<nsIHttpChannel> 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<DOHaddr> 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<TRR> 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
+}
+}
new file mode 100644
--- /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<DOHaddr> {
+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<DOHaddr> 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<nsHostRecord> mRec;
+ RefPtr<AHostResolver> 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<nsIChannel> mChannel;
+ enum TrrType mType;
+ TimeStamp mStartTime;
+ unsigned char mResponse[kMaxSize];
+ unsigned int mBodySize;
+ bool mFailed;
+ bool mPB;
+ DOHresp mDNS;
+ nsCOMPtr<nsITimer> mTimeout;
+ nsCString mCname;
+ uint32_t mCnameLoop; // loop detection counter
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // include guard
new file mode 100644
--- /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<nsIObserverService> 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<nsIPrefBranch> 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<nsIIOService> 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<nsIURI> url;
+ nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply<nsIStandardURLMutator>(&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<TRRService> 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> 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<AddrInfo> 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
new file mode 100644
--- /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<uint32_t, Relaxed> mMode;
+ Atomic<uint32_t, Relaxed> mTRRBlacklistExpireTime;
+ Atomic<uint32_t, Relaxed> mTRRTimeout;
+
+ Mutex mLock; // protects mPrivate* string
+ nsCString mPrivateURI; // main thread only
+ nsCString mPrivateCred; // main thread only
+ nsCString mConfirmationNS;
+ nsCString mBootstrapAddr;
+
+ Atomic<bool, Relaxed> mWaitForCaptive; // wait for the captive portal to say OK before using TRR
+ Atomic<bool, Relaxed> mRfc1918; // okay with local IP addresses in DOH responses?
+ Atomic<bool, Relaxed> mCaptiveIsPassed; // set when captive portal check is passed
+ Atomic<bool, Relaxed> mUseGET; // do DOH using GET requests (instead of POST)
+
+ // TRR Blacklist storage
+ RefPtr<DataStorage> mTRRBLStorage;
+ Atomic<bool, Relaxed> mClearTRRBLStorage;
+
+ enum ConfirmationState {
+ CONFIRM_INIT = 0,
+ CONFIRM_TRYING = 1,
+ CONFIRM_OK = 2,
+ CONFIRM_FAILED = 3
+ };
+ Atomic<ConfirmationState, Relaxed> mConfirmationState;
+ RefPtr<TRR> mConfirmer;
+};
+
+extern TRRService *gTRRService;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // TRRService_h_
--- a/netwerk/dns/moz.build
+++ b/netwerk/dns/moz.build
@@ -24,16 +24,17 @@ XPIDL_MODULE = 'necko_dns'
EXPORTS.mozilla.net += [
'ChildDNSService.h',
'DNS.h',
'DNSListenerProxy.h',
'DNSRequestChild.h',
'DNSRequestParent.h',
'PDNSParams.h',
+ 'TRRService.h',
]
SOURCES += [
'nsEffectiveTLDService.cpp', # Excluded from UNIFIED_SOURCES due to special build flags.
'nsHostResolver.cpp', # Redefines LOG
]
UNIFIED_SOURCES += [
@@ -41,16 +42,18 @@ UNIFIED_SOURCES += [
'DNS.cpp',
'DNSListenerProxy.cpp',
'DNSRequestChild.cpp',
'DNSRequestParent.cpp',
'GetAddrInfo.cpp',
'nsDNSService2.cpp',
'nsIDNService.cpp',
'punycode.c',
+ 'TRR.cpp',
+ 'TRRService.cpp',
]
IPDL_SOURCES = [
'PDNSRequest.ipdl',
'PDNSRequestParams.ipdlh',
]
include('/ipc/chromium/chromium-config.mozbuild')
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -27,16 +27,17 @@
#include "prio.h"
#include "plstr.h"
#include "nsIOService.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsNetAddr.h"
#include "nsProxyRelease.h"
#include "nsIObserverService.h"
#include "nsINetworkLinkService.h"
+#include "TRRService.h"
#include "mozilla/Attributes.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/net/NeckoCommon.h"
#include "mozilla/net/ChildDNSService.h"
#include "mozilla/net/DNSListenerProxy.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
@@ -100,16 +101,28 @@ nsDNSRecord::GetCanonicalName(nsACString
// if the record is for an IP address literal, then the canonical
// host name is the IP address literal.
result = mHostRecord->host;
}
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)
{
if (mDone) {
return NS_ERROR_NOT_AVAILABLE;
}
mHostRecord->addr_info_lock.Lock();
if (mHostRecord->addr_info) {
@@ -481,16 +494,17 @@ private:
nsDNSService::nsDNSService()
: mLock("nsDNSServer.mLock")
, mDisableIPv6(false)
, mDisablePrefetch(false)
, mFirstTime(true)
, mNotifyResolution(false)
, mOfflineLocalhost(false)
, mForceResolveOn(false)
+ , mTrrService(nullptr)
{
}
nsDNSService::~nsDNSService() = default;
NS_IMPL_ISUPPORTS(nsDNSService, nsIDNSService, nsPIDNSService, nsIObserver,
nsIMemoryReporter)
@@ -634,16 +648,21 @@ nsDNSService::Init()
mLocalDomains.PutEntry(tokenizer.nextToken());
}
}
mNotifyResolution = notifyResolution;
}
RegisterWeakMemoryReporter(this);
+ mTrrService = new TRRService();
+ if (NS_FAILED(mTrrService->Init())) {
+ mTrrService = nullptr;
+ }
+
return rv;
}
NS_IMETHODIMP
nsDNSService::Shutdown()
{
UnregisterWeakMemoryReporter(this);
@@ -706,16 +725,21 @@ nsDNSService::PreprocessHostname(bool
return NS_ERROR_UNKNOWN_HOST;
}
if (aLocalDomain) {
aACE.AssignLiteral("localhost");
return NS_OK;
}
+ if (mTrrService &&
+ mTrrService->MaybeBootstrap(aInput, aACE)) {
+ return NS_OK;
+ }
+
if (mForceResolveOn) {
MutexAutoLock lock(mLock);
if (!aInput.LowerCaseEqualsASCII("localhost") &&
!aInput.LowerCaseEqualsASCII("127.0.0.1")) {
aACE.Assign(mForceResolve);
return NS_OK;
}
}
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -13,16 +13,17 @@
#include "nsIObserver.h"
#include "nsHostResolver.h"
#include "nsAutoPtr.h"
#include "nsString.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "mozilla/Mutex.h"
#include "mozilla/Attributes.h"
+#include "TRRService.h"
class nsAuthSSPI;
class nsDNSService final : public nsPIDNSService
, public nsIObserver
, public nsIMemoryReporter
{
public:
@@ -42,17 +43,16 @@ public:
protected:
friend class nsAuthSSPI;
nsresult DeprecatedSyncResolve(const nsACString &aHostname,
uint32_t flags,
const mozilla::OriginAttributes &aOriginAttributes,
nsIDNSRecord **result);
-
private:
~nsDNSService();
static already_AddRefed<nsDNSService> GetSingleton();
uint16_t GetAFForLookup(const nsACString &host, uint32_t flags);
nsresult PreprocessHostname(bool aLocalDomain,
@@ -79,11 +79,12 @@ private:
bool mDisableIPv6;
bool mDisablePrefetch;
bool mBlockDotOnion;
bool mFirstTime;
bool mNotifyResolution;
bool mOfflineLocalhost;
bool mForceResolveOn;
nsTHashtable<nsCStringHashKey> mLocalDomains;
+ RefPtr<mozilla::net::TRRService> mTrrService;
};
#endif //nsDNSService2_h__
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -25,17 +25,20 @@
#include "prtime.h"
#include "mozilla/Logging.h"
#include "PLDHashTable.h"
#include "plstr.h"
#include "nsURLHelper.h"
#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"
#include "mozilla/DebugOnly.h"
#include "mozilla/Preferences.h"
using namespace mozilla;
using namespace mozilla::net;
@@ -65,19 +68,23 @@ static const unsigned int NEGATIVE_RECOR
#define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
#define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
static_assert(HighThreadThreshold <= MAX_RESOLVER_THREADS,
"High Thread Threshold should be less equal Maximum allowed thread");
//----------------------------------------------------------------------------
-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 " : "", \
(interface && interface[0] != '\0') ? interface : ""
//----------------------------------------------------------------------------
static inline void
@@ -191,30 +198,48 @@ nsHostKey::SizeOfExcludingThis(mozilla::
nsHostRecord::nsHostRecord(const nsHostKey& key)
: nsHostKey(key)
, addr_info_lock("nsHostRecord.addr_info_lock")
, addr_info_gencnt(0)
, 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)
{
mValidStart = now;
mGraceStart = now + TimeDuration::FromSeconds(valid);
mValidEnd = now + TimeDuration::FromSeconds(valid + grace);
}
void
@@ -223,16 +248,89 @@ nsHostRecord::CopyExpirationTimesAndFlag
// This is used to copy information from a cache entry to a record. All
// information necessary for HasUsableRecord needs to be copied.
mValidStart = aFromHostRecord->mValidStart;
mValidEnd = aFromHostRecord->mValidEnd;
mGraceStart = aFromHostRecord->mGraceStart;
mDoomed = aFromHostRecord->mDoomed;
}
+void
+nsHostRecord::ResolveComplete()
+{
+ if (mNativeUsed) {
+ if (mNativeSuccess) {
+ uint32_t millis = static_cast<uint32_t>(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<uint32_t>(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();
Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mBlacklistedCount);
delete addr_info;
}
@@ -264,18 +362,19 @@ nsHostRecord::Blacklisted(NetAddr *aQuer
return false;
}
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;
if (negative)
mDoomed = true;
char buf[kIPv6CStrBufSize];
if (NetAddrToString(aAddress, buf, sizeof(buf))) {
@@ -370,53 +469,59 @@ nsHostRecord::GetPriority(uint16_t aFlag
return nsHostRecord::DNS_PRIORITY_LOW;
}
// Returns true if the entry can be removed, or false if it should be left.
// Sets mResolveAgain true for entries being resolved right now.
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
// ensure it is called again.
mResolveAgain = true;
}
// if Onqueue is true, the host entry is already added to the cache
// 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<bool, mozilla::Relaxed> 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<nsHostResolver*> self = static_cast<nsHostResolver*>(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,
uint32_t defaultGracePeriod)
: mMaxCacheEntries(maxCacheEntries)
, mDefaultCacheLifetime(defaultCacheEntryLifetime)
, mDefaultGracePeriod(defaultGracePeriod)
, mLock("nsHostResolver.mLock")
@@ -438,62 +543,67 @@ nsHostResolver::nsHostResolver(uint32_t
mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
}
nsHostResolver::~nsHostResolver() = default;
nsresult
nsHostResolver::Init()
{
+ MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(GetAddrInfoInit())) {
return NS_ERROR_FAILURE;
}
LOG(("nsHostResolver::Init this=%p", this));
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
// callback won't be called.
{
DebugOnly<nsresult> rv = Preferences::RegisterCallbackAndCall(
&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,
// so we force it to reload those settings whenever we startup a subsequent
// nsHostResolver instance. We assume that there is no reason to do this
// for the first nsHostResolver instance since that is usually created
// during application startup.
static int initCount = 0;
if (initCount++ > 0) {
LOG(("Calling 'res_ninit'.\n"));
res_ninit(&_res);
}
#endif
+
return NS_OK;
}
void
nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
{
// loop through pending queue, erroring out pending lookups.
if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
PRCList *node = aPendingQ->next;
while (node != aPendingQ) {
RefPtr<nsHostRecord> rec = dont_AddRef(static_cast<nsHostRecord *>(node));
+ rec->Cancel();
node = node->next;
- CompleteLookup(rec, NS_ERROR_ABORT, nullptr);
+ CompleteLookup(rec, NS_ERROR_ABORT, nullptr, rec->pb);
}
}
}
//
// FlushCache() is what we call when the network has changed. We must not
// trust names that were resolved before this change. They may resolve
// differently now.
@@ -510,16 +620,17 @@ nsHostResolver::FlushCache()
mEvictionQSize = 0;
// Clear the evictionQ and remove all its corresponding entries from
// the cache first
if (!PR_CLIST_IS_EMPTY(&mEvictionQ)) {
PRCList *node = mEvictionQ.next;
while (node != &mEvictionQ) {
nsHostRecord *rec = static_cast<nsHostRecord *>(node);
+ rec->Cancel();
node = node->next;
PR_REMOVE_AND_INIT_LINK(rec);
mRecordDB.Remove(*static_cast<nsHostKey *>(rec));
NS_RELEASE(rec);
}
}
// Refresh the cache entries that are resolving RIGHT now, remove the rest.
@@ -533,24 +644,22 @@ nsHostResolver::FlushCache()
}
}
void
nsHostResolver::Shutdown()
{
LOG(("Shutting down host resolver.\n"));
-#if TTL_AVAILABLE
{
DebugOnly<nsresult> 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);
PR_INIT_CLIST(&pendingQMed);
PR_INIT_CLIST(&pendingQLow);
PR_INIT_CLIST(&evictionQ);
{
@@ -575,21 +684,25 @@ nsHostResolver::Shutdown()
ClearPendingQueue(&pendingQHigh);
ClearPendingQueue(&pendingQMed);
ClearPendingQueue(&pendingQLow);
if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
PRCList *node = evictionQ.next;
while (node != &evictionQ) {
nsHostRecord *rec = static_cast<nsHostRecord *>(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.
// Use this approach instead of PR_JoinThread() because that does
// not allow a timeout which may be necessary for a semi-responsive
// shutdown if the thread is blocked on a very slow DNS resolution.
// mThreadCount is read outside of mLock, but the worst case
// scenario for that race is one extra 25ms sleep.
@@ -612,16 +725,43 @@ nsHostResolver::MoveQueue(nsHostRecord *
{
NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
PR_REMOVE_LINK(aRec);
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<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
+ if (!entry) {
+ entry = new nsHostRecord(key);
+ }
+
+ RefPtr<nsHostRecord> 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,
uint16_t flags,
uint16_t af,
const char *netInterface,
nsResolveHostCallback *aCallback)
{
NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
@@ -638,35 +778,37 @@ nsHostResolver::ResolveHost(const char
RefPtr<nsResolveHostCallback> callback(aCallback);
// if result is set inside the lock, then we need to issue the
// callback before returning.
RefPtr<nsHostRecord> result;
nsresult status = NS_OK, rv = NS_OK;
{
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
// the output buffer in the case of IPv6 input. See bug 223145.
memset(&tempAddr, 0, sizeof(PRNetAddr));
// check to see if there is already an entry for this |host|
// in the hash table. if so, then check to see if we can't
// just reuse the lookup result. otherwise, if there are
// any pending callbacks, then add to pending callbacks queue,
// and return. otherwise, add ourselves as first pending
// callback, and proceed to do the lookup.
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<nsHostRecord>& entry = mRecordDB.GetOrInsert(key);
if (!entry) {
entry = new nsHostRecord(key);
}
RefPtr<nsHostRecord> rec = entry;
MOZ_ASSERT(rec, "Record should not be null");
@@ -685,64 +827,60 @@ nsHostResolver::ResolveHost(const char
if (rec->negative) {
LOG((" Negative cache entry for host [%s%s%s].\n",
LOG_HOST(host, netInterface)));
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
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! ;-)
rec->addr = MakeUnique<NetAddr>();
PRNetAddrToNetAddr(&tempAddr, rec->addr.get());
// put reference to host record on stack...
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",
LOG_HOST(host, netInterface)));
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
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<nsHostRecord> unspecRec = mRecordDB.Get(unspecKey);
TimeStamp now = TimeStamp::NowLoRes();
if (unspecRec && unspecRec->HasUsableResult(now, flags)) {
MOZ_ASSERT(unspecRec->addr_info || unspecRec->negative,
"Entry should be resolved or negative.");
@@ -787,22 +925,21 @@ nsHostResolver::ResolveHost(const char
if (rec->HasUsableResult(now, flags)) {
result = rec;
if (rec->negative) {
status = NS_ERROR_UNKNOWN_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)));
result = rec;
rec->negative = true;
status = NS_ERROR_UNKNOWN_HOST;
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
METHOD_NEGATIVE_HIT);
@@ -813,30 +950,28 @@ nsHostResolver::ResolveHost(const char
// AF_UNSPEC request, then start a new lookup.
if (!result) {
LOG((" No usable address in cache for host [%s%s%s].",
LOG_HOST(host, netInterface)));
// 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()));
rec->mCallbacks.insertBack(callback);
if (rec->onQueue) {
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
METHOD_NETWORK_SHARED);
@@ -885,17 +1020,19 @@ nsHostResolver::DetachCallback(const cha
RefPtr<nsResolveHostCallback> callback(aCallback);
{
MutexAutoLock lock(mLock);
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<nsHostRecord> entry = mRecordDB.Get(key);
if (entry) {
// walk list looking for |callback|... we cannot assume
// that it will be there!
for (nsResolveHostCallback* c: entry->mCallbacks) {
if (c == callback) {
@@ -942,29 +1079,131 @@ nsHostResolver::ConditionallyCreateThrea
}
else {
LOG((" Unable to find a thread for looking up host [%s%s%s].\n",
LOG_HOST(rec->host.get(), rec->netInterface.get())));
}
return NS_OK;
}
+#define TRROutstanding() ((rec->mTrrA || rec->mTrrAAAA))
+
nsresult
-nsHostResolver::IssueLookup(nsHostRecord *rec)
+nsHostResolver::TrrLookup_unlocked(nsHostRecord *rec, TRR *pushedTRR)
+{
+ 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)
{
- nsresult rv = NS_OK;
- NS_ASSERTION(!rec->resolving, "record is already being resolved");
+ 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;
+ 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<nsHostRecord *>(PR_LIST_HEAD(q));
+ while (i != rec) {
+ MOZ_ASSERT(i->next != q);
+ i = static_cast<nsHostRecord *>(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--;
}
switch (nsHostRecord::GetPriority(rec->flags)) {
case nsHostRecord::DNS_PRIORITY_HIGH:
PR_APPEND_LINK(rec, &mHighQ);
break;
@@ -973,38 +1212,91 @@ nsHostResolver::IssueLookup(nsHostRecord
break;
case nsHostRecord::DNS_PRIORITY_LOW:
PR_APPEND_LINK(rec, &mLowQ);
break;
}
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<uint32_t>(mThreadCount),
static_cast<uint32_t>(mActiveAnyThreadCount),
static_cast<uint32_t>(mNumIdleThreads),
static_cast<uint32_t>(mPendingCount)));
return rv;
}
+ResolverMode
+nsHostResolver::Mode()
+{
+ if (gTRRService) {
+ return static_cast<ResolverMode>(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
// track positive grace period induced renewals
Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
METHOD_RENEWAL);
}
}
@@ -1029,22 +1321,17 @@ nsHostResolver::GetHostToLookup(nsHostRe
MutexAutoLock lock(mLock);
timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
epoch = PR_IntervalNow();
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);
SET_GET_TTL(*result, false);
return true;
}
if (mActiveAnyThreadCount < HighThreadThreshold) {
@@ -1094,45 +1381,59 @@ nsHostResolver::GetHostToLookup(nsHostRe
// tell thread to exit...
return false;
}
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);
LOG(("Caching host [%s%s%s] negative record for %u seconds.\n",
LOG_HOST(rec->host.get(), rec->netInterface.get()),
NEGATIVE_RECORD_LIFETIME));
return;
}
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)
{
if (!rrset1 || !rrset2) {
return true;
}
LOG(("different_rrset %s\n", rrset1->mHostName));
@@ -1176,124 +1477,260 @@ different_rrset(AddrInfo *rrset1, AddrIn
return false;
}
//
// CompleteLookup() checks if the resolving should be redone and if so it
// 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<RefPtr<nsResolveHostCallback>> cbs;
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(rec);
+ MOZ_ASSERT(rec->pb == pb);
+
+ // newRRSet needs to be taken into the hostrecord (which will then own it)
+ // or deleted on early return.
+ nsAutoPtr<AddrInfo> newRRSet(aNewRRSet);
+
+ 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();
- {
- MutexAutoLock lock(mLock);
+ 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);
+ }
- 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;
+ if (NS_SUCCEEDED(status)) {
+ if (rec->mTRRSuccess == 0) { // first one
+ rec->mTrrDuration = TimeStamp::Now() - rec->mTrrStart;
+ }
+ rec->mTRRSuccess++;
}
- // grab list of callbacks to notify
- cbs = mozilla::Move(rec->mCallbacks);
+ 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);
- // 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;
+ } 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
}
- old_addr_info = newRRSet;
+ rec->mFirstTRR = nullptr;
}
- }
- delete old_addr_info;
+
+ if (!rec->mTRRSuccess) {
+ // no TRR success
+ newRRSet = nullptr;
+ }
- rec->negative = !rec->addr_info;
- PrepareRecordExpiration(rec);
- rec->resolving = false;
-
+ 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<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
- PR_REMOVE_AND_INIT_LINK(head);
- mRecordDB.Remove(*static_cast<nsHostKey *>(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<uint32_t>(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<nsresult> 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<AddrInfo> 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<RefPtr<nsResolveHostCallback>> 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<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
+ PR_REMOVE_AND_INIT_LINK(head);
+ mRecordDB.Remove(*static_cast<nsHostKey *>(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<uint32_t>(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<nsresult> rv = NameLookup(rec);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Could not issue second async lookup for TTL.");
+ }
+#endif
return LOOKUP_OK;
}
void
nsHostResolver::CancelAsyncRequest(const char *host,
const OriginAttributes &aOriginAttributes,
uint16_t flags,
uint16_t af,
const char *netInterface,
nsIDNSListener *aListener,
nsresult status)
{
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<nsHostRecord> rec = mRecordDB.Get(key);
if (rec) {
nsHostRecord* recPtr = nullptr;
for (RefPtr<nsResolveHostCallback> c : rec->mCallbacks) {
if (c->EqualsAsyncListener(aListener)) {
c->remove();
@@ -1303,17 +1740,17 @@ nsHostResolver::CancelAsyncRequest(const
}
}
// If there are no more callbacks, remove the hash table entry
if (recPtr && recPtr->mCallbacks.isEmpty()) {
mRecordDB.Remove(*static_cast<nsHostKey *>(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);
}
}
}
}
size_t
nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
@@ -1349,27 +1786,32 @@ nsHostResolver::ThreadFunc(void *arg)
#if defined(RES_RETRY_ON_FAILURE)
nsResState rs;
#endif
RefPtr<nsHostResolver> resolver = dont_AddRef((nsHostResolver *)arg);
RefPtr<nsHostRecord> rec;
AddrInfo *ai = nullptr;
- while (rec || resolver->GetHostToLookup(getter_AddRefs(rec))) {
+ do {
+ if (!rec) {
+ RefPtr<nsHostRecord> 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,
getTtl);
#if defined(RES_RETRY_ON_FAILURE)
if (NS_FAILED(status) && rs.Reset()) {
status = GetAddrInfo(rec->host.get(), rec->af,
rec->flags, rec->netInterface.get(), &ai,
@@ -1402,37 +1844,38 @@ nsHostResolver::ThreadFunc(void *arg)
}
}
}
LOG(("DNS lookup thread - lookup completed for host [%s%s%s]: %s.\n",
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"));
}
nsresult
nsHostResolver::Create(uint32_t maxCacheEntries,
uint32_t defaultCacheEntryLifetime,
uint32_t defaultGracePeriod,
nsHostResolver **result)
{
auto *res = new nsHostResolver(maxCacheEntries, defaultCacheEntryLifetime,
- defaultGracePeriod);
+ defaultGracePeriod);
NS_ADDREF(res);
nsresult rv = res->Init();
if (NS_FAILED(rv))
NS_RELEASE(res);
*result = res;
return rv;
@@ -1480,8 +1923,11 @@ nsHostResolver::GetDNSCacheEntries(nsTAr
addr = &addrElement->mAddress;
}
}
}
args->AppendElement(info);
}
}
+
+#undef LOG
+#undef LOG_ENABLED
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -10,51 +10,64 @@
#include "prclist.h"
#include "prnetdb.h"
#include "PLDHashTable.h"
#include "mozilla/CondVar.h"
#include "mozilla/Mutex.h"
#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<bool, mozilla::Relaxed> gNativeIsLocalhost;
#define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY 3
#define MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY 5
#define MAX_NON_PRIORITY_REQUESTS 150
#define MAX_RESOLVER_THREADS (MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY + \
MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY)
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) {
}
bool operator==(const nsHostKey& other) const;
size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
PLDHashNumber Hash() const;
};
@@ -109,16 +122,22 @@ public:
// When the record is no longer valid (it's time of expiration)
mozilla::TimeStamp mValidEnd;
// When the record enters its grace period. This must be before mValidEnd.
// If a record is in its grace period (and not expired), it will be used
// 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,
unsigned int grace);
void CopyExpirationTimesAndFlagsFrom(const nsHostRecord *aFromHostRecord);
// Checks if the record is usable (not expired and has a value)
bool HasUsableResult(const mozilla::TimeStamp& now, uint16_t queryFlags = 0) const;
@@ -134,34 +153,49 @@ public:
DNS_PRIORITY_LOW,
DNS_PRIORITY_MEDIUM,
DNS_PRIORITY_HIGH,
};
static DnsPriority GetPriority(uint16_t aFlags);
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;
explicit nsHostRecord(const nsHostKey& key);
mozilla::LinkedList<RefPtr<nsResolveHostCallback>> 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. */
+ 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<mozilla::net::AddrInfo> 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;
- 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 */
+ enum {
+ INIT, STARTED, OK, FAILED
+ } mTrrAUsed, mTrrAAAAUsed;
-#if TTL_AVAILABLE
- bool mGetTtl;
-#endif
+ RefPtr<mozilla::net::TRR> mTrrA;
+ RefPtr<mozilla::net::TRR> mTrrAAAA;
// The number of times ReportUnusable() has been called in the record's
// lifetime.
uint32_t mBlacklistedCount;
// when the results from this resolve is returned, it is not to be
// trusted, but instead a new resolve must be made!
bool mResolveAgain;
@@ -219,29 +253,53 @@ public:
*/
virtual bool EqualsAsyncListener(nsIDNSListener *aListener) = 0;
virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const = 0;
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.
*/
static nsresult Create(uint32_t maxCacheEntries, // zero disables cache
uint32_t defaultCacheEntryLifetime, // seconds
uint32_t defaultGracePeriod, // seconds
nsHostResolver **resolver);
@@ -305,42 +363,51 @@ public:
RES_BYPASS_CACHE = nsIDNSService::RESOLVE_BYPASS_CACHE,
RES_CANON_NAME = nsIDNSService::RESOLVE_CANONICAL_NAME,
RES_PRIORITY_MEDIUM = nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
RES_PRIORITY_LOW = nsIDNSService::RESOLVE_PRIORITY_LOW,
RES_SPECULATE = nsIDNSService::RESOLVE_SPECULATE,
//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;
/**
* Flush the DNS cache.
*/
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);
/**
* Starts a new lookup in the background for entries that are in the grace
* period with a failed connect or all cached entries are negative.
*/
--- a/netwerk/dns/nsIDNSRecord.idl
+++ b/netwerk/dns/nsIDNSRecord.idl
@@ -92,9 +92,14 @@ interface nsIDNSRecord : nsISupports
* This function indicates that the last address obtained via getNextAddr*()
* was not usuable and should be skipped in future uses of this
* record if other addresses are available.
*
* @param aPort is the port number associated with the failure, if any.
* It may be zero if not applicable.
*/
void reportUnusable(in uint16_t aPort);
+
+ /**
+ * Record retreived with TRR.
+ */
+ bool IsTRR();
};
--- a/netwerk/dns/nsIDNSService.idl
+++ b/netwerk/dns/nsIDNSService.idl
@@ -228,9 +228,15 @@ interface nsIDNSService : nsISupports
* If set, only IPv6 addresses will be returned from resolve/asyncResolve.
*/
const unsigned long RESOLVE_DISABLE_IPV4 = (1 << 7);
/**
* 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);
+
};
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -149,17 +149,17 @@ public:
const static uint32_t kMaxStreamID = 0x7800000;
// This is a sentinel for a deleted stream. It is not a valid
// 31 bit stream ID.
const static uint32_t kDeadStreamID = 0xffffdead;
// 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
const static uint32_t kDefaultRwin = 65535;
// We limit frames to 2^14 bytes of length in order to preserve responsiveness
// This is the smallest allowed value for SETTINGS_MAX_FRAME_SIZE
const static uint32_t kMaxFrameData = 0x4000;
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -178,16 +178,17 @@ HttpBaseChannel::HttpBaseChannel()
, mLoadedFromApplicationCache(false)
, mChannelIsForDownload(false)
, mTracingEnabled(true)
, mTimingEnabled(false)
, mReportTiming(true)
, mAllowSpdy(true)
, mAllowAltSvc(true)
, mBeConservative(false)
+ , mTRR(false)
, mResponseTimeoutEnabled(true)
, mAllRedirectsSameOrigin(true)
, mAllRedirectsPassTimingAllowCheck(true)
, mResponseCouldBeSynthesized(false)
, mBlockAuthPrompt(false)
, mAllowStaleCacheContent(false)
, mAddedAsNonTailRequest(false)
, mTlsFlags(0)
@@ -2684,16 +2685,32 @@ HttpBaseChannel::GetBeConservative(bool
NS_IMETHODIMP
HttpBaseChannel::SetBeConservative(bool aBeConservative)
{
mBeConservative = 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)
{
NS_ENSURE_ARG_POINTER(aTlsFlags);
*aTlsFlags = mTlsFlags;
return NS_OK;
}
@@ -3581,16 +3598,18 @@ HttpBaseChannel::SetupReplacementChannel
rv = httpInternal->SetThirdPartyFlags(mThirdPartyFlags);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = httpInternal->SetAllowSpdy(mAllowSpdy);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = httpInternal->SetAllowAltSvc(mAllowAltSvc);
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));
RefPtr<nsHttpChannel> realChannel;
CallQueryInterface(newChannel, realChannel.StartAssignment());
if (realChannel) {
rv = realChannel->SetTopWindowURI(mTopWindowURI);
MOZ_ASSERT(NS_SUCCEEDED(rv));
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -231,16 +231,18 @@ public:
NS_IMETHOD GetRemoteAddress(nsACString& addr) override;
NS_IMETHOD GetRemotePort(int32_t* port) override;
NS_IMETHOD GetAllowSpdy(bool *aAllowSpdy) override;
NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) override;
NS_IMETHOD GetAllowAltSvc(bool *aAllowAltSvc) override;
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;
virtual MOZ_MUST_USE nsresult AddSecurityMessage(const nsAString &aMessageTag, const nsAString &aMessageCategory);
NS_IMETHOD TakeAllSecurityMessages(nsCOMArray<nsISecurityConsoleMessage> &aMessages) override;
NS_IMETHOD GetResponseTimeoutEnabled(bool *aEnable) override;
NS_IMETHOD SetResponseTimeoutEnabled(bool aEnable) override;
NS_IMETHOD GetInitialRwin(uint32_t* aRwin) override;
@@ -569,16 +571,17 @@ protected:
uint32_t mChannelIsForDownload : 1;
uint32_t mTracingEnabled : 1;
// True if timing collection is enabled
uint32_t mTimingEnabled : 1;
uint32_t mReportTiming : 1;
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;
// Is 1 if no redirects have occured or if all redirects
// pass the Resource Timing timing-allow-check
uint32_t mAllRedirectsPassTimingAllowCheck : 1;
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -53,17 +53,17 @@ namespace net {
typedef uint8_t nsHttpVersion;
//-----------------------------------------------------------------------------
// http connection capabilities
//-----------------------------------------------------------------------------
#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.
#define NS_HTTP_STICKY_CONNECTION (1<<2)
// a transaction with this caps flag will, upon opening a new connection,
// bypass the local DNS cache
#define NS_HTTP_REFRESH_DNS (1<<3)
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1005,16 +1005,19 @@ nsHttpChannel::SetupTransaction()
mAltDataLength = 0;
}
mCacheInputStream.CloseAndRelease();
}
}
mUsedNetwork = 1;
+ if (mTRR) {
+ mCaps |= NS_HTTP_LARGE_KEEPALIVE;
+ }
if (!mAllowSpdy) {
mCaps |= NS_HTTP_DISALLOW_SPDY;
}
if (mBeConservative) {
mCaps |= NS_HTTP_BE_CONSERVATIVE;
}
// Use the URI path if not proxying (transparent proxying such as proxy
@@ -3751,16 +3754,19 @@ nsHttpChannel::OpenCacheEntryInternal(bo
// Only for backward compatibility with the old cache back end.
// When removed, remove the flags and related code snippets.
if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
if (mPostID) {
extension.Append(nsPrintfCString("%d", mPostID));
}
+ if (mTRR) {
+ extension.Append("TRR");
+ }
mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
mCacheQueueSizeWhenOpen = CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
if (sRCWNEnabled && maybeRCWN && !mApplicationCacheForWrite) {
bool hasAltData = false;
uint32_t sizeInKb = 0;
rv = cacheStorage->GetCacheIndexEntryAttrs(openURI, extension,
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -76,16 +76,17 @@ nsHttpConnection::nsHttpConnection()
, mNPNComplete(false)
, mSetupSSLCalled(false)
, mUsingSpdyVersion(0)
, mPriority(nsISupportsPriority::PRIORITY_NORMAL)
, mReportedSpdy(false)
, mEverUsedSpdy(false)
, mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
, mTransactionCaps(0)
+ , mDefaultTimeoutFactor(1)
, mResponseTimeoutEnabled(false)
, mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
, mForceSendPending(false)
, m0RTTChecked(false)
, mWaitingFor0RTTResponse(false)
, mContentBytesWritten0RTT(0)
, mEarlyDataNegotiated(false)
, mDid0RTTSpdy(false)
@@ -342,17 +343,17 @@ nsHttpConnection::StartSpdy(uint8_t spdy
// Disable TCP Keepalives - use SPDY ping instead.
rv = DisableTCPKeepalives();
if (NS_FAILED(rv)) {
LOG(("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
"rv[0x%" PRIx32 "]", this, static_cast<uint32_t>(rv)));
}
- mIdleTimeout = gHttpHandler->SpdyTimeout();
+ mIdleTimeout = gHttpHandler->SpdyTimeout() * mDefaultTimeoutFactor;
if (!mTLSFilter) {
mTransaction = mSpdySession;
} else {
rv = mTLSFilter->SetProxiedTransaction(mSpdySession);
if (NS_FAILED(rv)) {
LOG(("nsHttpConnection::StartSpdy [%p] SetProxiedTransaction failed"
" rv[0x%x]", this, static_cast<uint32_t>(rv)));
@@ -638,16 +639,20 @@ nsHttpConnection::Activate(nsAHttpTransa
if (hTrans) {
hTrans->BootstrapTimings(mBootstrappedTimings);
SetUrgentStartPreferred(hTrans->ClassOfService() & nsIClassOfService::UrgentStart);
}
}
mBootstrappedTimings = TimingStruct();
}
+ if (caps & NS_HTTP_LARGE_KEEPALIVE) {
+ mDefaultTimeoutFactor = 10; // don't ever lower
+ }
+
mTransactionCaps = caps;
mPriority = pri;
if (mTransaction && mUsingSpdyVersion) {
return AddTransaction(trans, pri);
}
NS_ENSURE_ARG_POINTER(trans);
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
@@ -1038,24 +1043,30 @@ nsHttpConnection::IdleTime()
mSpdySession->IdleTime() : (PR_IntervalNow() - mLastReadTime);
}
// returns the number of seconds left before the allowable idle period
// expires, or 0 if the period has already expied.
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;
}
bool
nsHttpConnection::IsAlive()
{
if (!mSocketTransport || !mConnectedTransport)
return false;
@@ -1176,30 +1187,27 @@ nsHttpConnection::OnHeadersAvailable(nsA
nsAutoCString keepAlive;
Unused << responseHead->GetHeader(nsHttp::Keep_Alive, keepAlive);
if (!mUsingSpdyVersion) {
const char *cp = PL_strcasestr(keepAlive.get(), "timeout=");
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) {
int maxUses = atoi(cp + 4);
if (maxUses > 0) {
foundKeepAliveMax = true;
mRemainingConnectionUses = static_cast<uint32_t>(maxUses);
}
}
}
- else {
- mIdleTimeout = gHttpHandler->SpdyTimeout();
- }
LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n",
this, PR_IntervalToSeconds(mIdleTimeout)));
}
if (!foundKeepAliveMax && mRemainingConnectionUses && !mUsingSpdyVersion)
--mRemainingConnectionUses;
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -375,16 +375,20 @@ private:
bool mEverUsedSpdy;
// mLastHttpResponseVersion stores the last response's http version seen.
uint8_t mLastHttpResponseVersion;
// 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).
uint32_t mTCPKeepaliveConfig;
nsCOMPtr<nsITimer> mTCPKeepaliveTransitionTimer;
private:
// For ForceSend()
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -252,17 +252,17 @@ nsHttpHandler::nsHttpHandler()
, mCoalesceSpdy(true)
, mSpdyPersistentSettings(false)
, mAllowPush(true)
, mEnableAltSvc(false)
, mEnableAltSvcOE(false)
, 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))
, mSpdyPingTimeout(PR_SecondsToInterval(8))
, mConnectTimeout(90000)
, mTLSHandshakeTimeout(30000)
, mParallelSpeculativeConnectLimit(6)
, mSpeculativeConnectEnabled(true)
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -212,16 +212,22 @@ interface nsIHttpChannelInternal : nsISu
/**
* If true, do not use newer protocol features that might have interop problems
* on the Internet. Intended only for use with critical infra like the updater.
* default is false.
*/
[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
* relating to the TLS implementation.
*/
[must_use] attribute unsigned long tlsFlags;
[must_use] readonly attribute PRTime lastModifiedTime;
new file mode 100644
--- /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++]();
+ }
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -406,8 +406,11 @@ skip-if = os == "android"
[test_bug1312782_http1.js]
[test_bug1355539_http1.js]
[test_bug1378385_http1.js]
[test_tls_flags_separate_connections.js]
[test_tls_flags.js]
[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"
--- a/security/manager/ssl/DataStorageList.h
+++ b/security/manager/ssl/DataStorageList.h
@@ -11,8 +11,9 @@
// Please note that it is crucial for performance reasons for the number of
// these classes to remain low. If you need to add to this list, you may
// need to update the algorithm in DataStorage::SetCachedStorageEntries()
// to something faster.
DATA_STORAGE(AlternateServices)
DATA_STORAGE(SecurityPreloadState)
DATA_STORAGE(SiteSecurityServiceState)
+DATA_STORAGE(TRRBlacklist)
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -189,16 +189,19 @@ function insertHardIllegalHpack(headers)
}
var h11required_conn = null;
var h11required_header = "yes";
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
Compressor.prototype.compress = originalCompressHeaders;
var u = url.parse(req.url);
var content = getHttpContent(u.pathname);
var push, push1, push1a, push2, push3;
@@ -521,16 +524,173 @@ function handleRequest(req, res) {
}
}
// for use with test_altsvc.js
else if (u.pathname === "/altsvc-test") {
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');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200, "OK");
res.end('{"http://' + req.headers['host'] + '": { "tls-ports": [' + serverPort + '] }}');
return;
}
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3607,70 +3607,128 @@
"DNT_USAGE": {
"record_in_processes": ["main", "content"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 3,
"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,
"releaseChannelCollection": "opt-out",
"alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],
"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,
"n_buckets": 50,
"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,
"n_buckets": 50,
"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,
"n_buckets": 50,
"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,
"releaseChannelCollection": "opt-out",
"alert_emails": ["necko@mozilla.com", "pmcmanus@mozilla.com"],
"n_buckets": 50,
"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"],
"kind": "linear",
"high": 21,
"n_buckets": 20,
"description": "The number of unusable addresses reported for each record"
},