From 9dfb15d3718d08be2022fe9f1efac521cd0a4166 Mon Sep 17 00:00:00 2001 From: daijro Date: Mon, 16 Sep 2024 00:49:28 -0500 Subject: [PATCH] Add upstream DNS leak fix #10 --- patches/upstream-dns-leak-fix.patch | 875 ++++++++++++++++++++++++++++ 1 file changed, 875 insertions(+) create mode 100644 patches/upstream-dns-leak-fix.patch diff --git a/patches/upstream-dns-leak-fix.patch b/patches/upstream-dns-leak-fix.patch new file mode 100644 index 0000000..4efd0a1 --- /dev/null +++ b/patches/upstream-dns-leak-fix.patch @@ -0,0 +1,875 @@ + +# HG changeset patch +# User Kershaw Chang +# Date 1725998669 0 +# Node ID eb748cbf195dc2195a5a26910efb0f35a0967e03 +# Parent f2a1d0b442ab14b7d00c4229444d4845d14cbb68 +Bug 1910593 - Don't prefetch HTTPS RR if proxyDNS is enabled, r=necko-reviewers,valentin + +Differential Revision: https://phabricator.services.mozilla.com/D219528 + +diff --git a/dom/chrome-webidl/NetDashboard.webidl b/dom/chrome-webidl/NetDashboard.webidl +--- a/dom/chrome-webidl/NetDashboard.webidl ++++ b/dom/chrome-webidl/NetDashboard.webidl +@@ -63,16 +63,17 @@ dictionary WebSocketDict { + dictionary DnsCacheEntry { + DOMString hostname = ""; + sequence hostaddr; + DOMString family = ""; + double expiration = 0; + boolean trr = false; + DOMString originAttributesSuffix = ""; + DOMString flags = ""; ++ unsigned short type = 0; + }; + + [GenerateConversionToJS] + dictionary DNSCacheDict { + sequence entries; + }; + + [GenerateConversionToJS] +diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp +--- a/netwerk/base/Dashboard.cpp ++++ b/netwerk/base/Dashboard.cpp +@@ -907,20 +907,23 @@ nsresult Dashboard::GetDNSCacheEntries(D + nsString* addr = addrs.AppendElement(fallible); + if (!addr) { + JS_ReportOutOfMemory(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr); + } + +- if (dnsData->mData[i].family == PR_AF_INET6) { +- entry.mFamily.AssignLiteral(u"ipv6"); +- } else { +- entry.mFamily.AssignLiteral(u"ipv4"); ++ entry.mType = dnsData->mData[i].resolveType; ++ if (entry.mType == nsIDNSService::RESOLVE_TYPE_DEFAULT) { ++ if (dnsData->mData[i].family == PR_AF_INET6) { ++ entry.mFamily.AssignLiteral(u"ipv6"); ++ } else { ++ entry.mFamily.AssignLiteral(u"ipv4"); ++ } + } + + entry.mOriginAttributesSuffix = + NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix); + entry.mFlags = NS_ConvertUTF8toUTF16(dnsData->mData[i].flags); + } + + JS::Rooted val(cx); +diff --git a/netwerk/base/DashboardTypes.h b/netwerk/base/DashboardTypes.h +--- a/netwerk/base/DashboardTypes.h ++++ b/netwerk/base/DashboardTypes.h +@@ -30,22 +30,22 @@ inline bool operator==(const SocketInfo& + + struct DnsAndConnectSockets { + bool speculative; + }; + + struct DNSCacheEntries { + nsCString hostname; + nsTArray hostaddr; +- uint16_t family; +- int64_t expiration; +- nsCString netInterface; +- bool TRR; ++ uint16_t family{0}; ++ int64_t expiration{0}; ++ bool TRR{false}; + nsCString originAttributesSuffix; + nsCString flags; ++ uint16_t resolveType{0}; + }; + + struct HttpConnInfo { + uint32_t ttl; + uint32_t rtt; + nsString protocolVersion; + + void SetHTTPProtocolVersion(HttpVersion pv); +@@ -94,27 +94,31 @@ template <> + struct ParamTraits { + typedef mozilla::net::DNSCacheEntries paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.hostname); + WriteParam(aWriter, aParam.hostaddr); + WriteParam(aWriter, aParam.family); + WriteParam(aWriter, aParam.expiration); +- WriteParam(aWriter, aParam.netInterface); + WriteParam(aWriter, aParam.TRR); ++ WriteParam(aWriter, aParam.originAttributesSuffix); ++ WriteParam(aWriter, aParam.flags); ++ WriteParam(aWriter, aParam.resolveType); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->hostname) && + ReadParam(aReader, &aResult->hostaddr) && + ReadParam(aReader, &aResult->family) && + ReadParam(aReader, &aResult->expiration) && +- ReadParam(aReader, &aResult->netInterface) && +- ReadParam(aReader, &aResult->TRR); ++ ReadParam(aReader, &aResult->TRR) && ++ ReadParam(aReader, &aResult->originAttributesSuffix) && ++ ReadParam(aReader, &aResult->flags) && ++ ReadParam(aReader, &aResult->resolveType); + } + }; + + template <> + struct ParamTraits { + typedef mozilla::net::DnsAndConnectSockets paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { +diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp +--- a/netwerk/dns/nsHostResolver.cpp ++++ b/netwerk/dns/nsHostResolver.cpp +@@ -2030,50 +2030,44 @@ void nsHostResolver::GetDNSCacheEntries( + // Also require a host. + nsHostRecord* rec = recordEntry.GetWeak(); + MOZ_ASSERT(rec, "rec should never be null here!"); + + if (!rec) { + continue; + } + +- // For now we only show A/AAAA records. +- if (!rec->IsAddrRecord()) { ++ DNSCacheEntries info; ++ info.resolveType = rec->type; ++ info.hostname = rec->host; ++ info.family = rec->af; ++ if (rec->mValidEnd.IsNull()) { + continue; + } +- +- RefPtr addrRec = do_QueryObject(rec); +- MOZ_ASSERT(addrRec); +- if (!addrRec || !addrRec->addr_info) { +- continue; +- } +- +- DNSCacheEntries info; +- info.hostname = rec->host; +- info.family = rec->af; + info.expiration = + (int64_t)(rec->mValidEnd - TimeStamp::NowLoRes()).ToSeconds(); + if (info.expiration <= 0) { + // We only need valid DNS cache entries + continue; + } + +- { ++ info.originAttributesSuffix = recordEntry.GetKey().originSuffix; ++ info.flags = nsPrintfCString("%u|0x%x|%u|%d|%s", rec->type, rec->flags, ++ rec->af, rec->pb, rec->mTrrServer.get()); ++ ++ RefPtr addrRec = do_QueryObject(rec); ++ if (addrRec && addrRec->addr_info) { + MutexAutoLock lock(addrRec->addr_info_lock); + for (const auto& addr : addrRec->addr_info->Addresses()) { + char buf[kIPv6CStrBufSize]; + if (addr.ToStringBuffer(buf, sizeof(buf))) { + info.hostaddr.AppendElement(buf); + } + } + info.TRR = addrRec->addr_info->IsTRR(); + } + +- info.originAttributesSuffix = recordEntry.GetKey().originSuffix; +- info.flags = nsPrintfCString("%u|0x%x|%u|%d|%s", rec->type, rec->flags, +- rec->af, rec->pb, rec->mTrrServer.get()); +- + args->AppendElement(std::move(info)); + } + } + + #undef LOG + #undef LOG_ENABLED +diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp +--- a/netwerk/protocol/http/nsHttp.cpp ++++ b/netwerk/protocol/http/nsHttp.cpp +@@ -30,16 +30,18 @@ + #include + #include + #include "nsLiteralString.h" + #include + + namespace mozilla { + namespace net { + ++extern const char kProxyType_SOCKS[]; ++ + const uint32_t kHttp3VersionCount = 5; + const nsCString kHttp3Versions[] = {"h3-29"_ns, "h3-30"_ns, "h3-31"_ns, + "h3-32"_ns, "h3"_ns}; + + // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-4.3 + constexpr uint64_t kWebTransportErrorCodeStart = 0x52e4a40fa8db; + constexpr uint64_t kWebTransportErrorCodeEnd = 0x52e4a40fa9e2; + +@@ -1173,10 +1175,24 @@ nsLiteralCString HttpVersionToTelemetryL + case HttpVersion::v3_0: + return "http_3"_ns; + default: + break; + } + return "unknown"_ns; + } + ++ProxyDNSStrategy GetProxyDNSStrategyHelper(const char* aType, uint32_t aFlag) { ++ if (!aType) { ++ return ProxyDNSStrategy::ORIGIN; ++ } ++ ++ if (!(aFlag & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST)) { ++ if (aType == kProxyType_SOCKS) { ++ return ProxyDNSStrategy::ORIGIN; ++ } ++ } ++ ++ return ProxyDNSStrategy::PROXY; ++} ++ + } // namespace net + } // namespace mozilla +diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h +--- a/netwerk/protocol/http/nsHttp.h ++++ b/netwerk/protocol/http/nsHttp.h +@@ -524,12 +524,22 @@ uint64_t WebTransportErrorToHttp3Error(u + uint8_t Http3ErrorToWebTransportError(uint64_t aErrorCode); + + bool PossibleZeroRTTRetryError(nsresult aReason); + + void DisallowHTTPSRR(uint32_t& aCaps); + + nsLiteralCString HttpVersionToTelemetryLabel(HttpVersion version); + ++enum class ProxyDNSStrategy : uint8_t { ++ // To resolve the origin of the end server we are connecting ++ // to. ++ ORIGIN = 1 << 0, ++ // To resolve the host name of the proxy. ++ PROXY = 1 << 1 ++}; ++ ++ProxyDNSStrategy GetProxyDNSStrategyHelper(const char* aType, uint32_t aFlag); ++ + } // namespace net + } // namespace mozilla + + #endif // nsHttp_h__ +diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp +--- a/netwerk/protocol/http/nsHttpChannel.cpp ++++ b/netwerk/protocol/http/nsHttpChannel.cpp +@@ -773,16 +773,20 @@ nsresult nsHttpChannel::MaybeUseHTTPSRRF + return aStatus; + } + + if (mURI->SchemeIs("https") || aShouldUpgrade || !LoadUseHTTPSSVC()) { + return ContinueOnBeforeConnect(aShouldUpgrade, aStatus); + } + + auto shouldSkipUpgradeWithHTTPSRR = [&]() -> bool { ++ if (mCaps & NS_HTTP_DISALLOW_HTTPS_RR) { ++ return true; ++ } ++ + // Skip using HTTPS RR to upgrade when this is not a top-level load and the + // loading principal is http. + if ((mLoadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT) && + (mLoadInfo->GetLoadingPrincipal() && + mLoadInfo->GetLoadingPrincipal()->SchemeIs("http"))) { + return true; + } +@@ -795,16 +799,21 @@ nsresult nsHttpChannel::MaybeUseHTTPSRRF + return true; + } + + // Don't block the channel when TRR is not used. + if (!trrEnabled) { + return true; + } + ++ auto dnsStrategy = GetProxyDNSStrategy(); ++ if (dnsStrategy != ProxyDNSStrategy::ORIGIN) { ++ return true; ++ } ++ + nsAutoCString uriHost; + mURI->GetAsciiHost(uriHost); + + return gHttpHandler->IsHostExcludedForHTTPSRR(uriHost); + }; + + if (shouldSkipUpgradeWithHTTPSRR()) { + StoreUseHTTPSSVC(false); +@@ -819,21 +828,16 @@ nsresult nsHttpChannel::MaybeUseHTTPSRRF + LOG(( + "nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] mHTTPSSVCRecord is some", + this)); + StoreWaitHTTPSSVCRecord(false); + bool hasHTTPSRR = (mHTTPSSVCRecord.ref() != nullptr); + return ContinueOnBeforeConnect(hasHTTPSRR, aStatus, hasHTTPSRR); + } + +- auto dnsStrategy = GetProxyDNSStrategy(); +- if (!(dnsStrategy & DNS_PREFETCH_ORIGIN)) { +- return ContinueOnBeforeConnect(aShouldUpgrade, aStatus); +- } +- + LOG(("nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] wait for HTTPS RR", + this)); + + OriginAttributes originAttributes; + StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(this, originAttributes); + + RefPtr resolver = + new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode()); +@@ -1346,23 +1350,23 @@ void nsHttpChannel::SpeculativeConnect() + if (LoadAllowStaleCacheContent()) { + return; + } + + nsCOMPtr callbacks; + NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup, + getter_AddRefs(callbacks)); + if (!callbacks) return; +- +- Unused << gHttpHandler->SpeculativeConnect( ++ bool httpsRRAllowed = !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR); ++ Unused << gHttpHandler->MaybeSpeculativeConnectWithHTTPSRR( + mConnectionInfo, callbacks, + mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_TRR_MODE_MASK | + NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6 | + NS_HTTP_DISALLOW_HTTP3 | NS_HTTP_REFRESH_DNS), +- gHttpHandler->EchConfigEnabled()); ++ gHttpHandler->EchConfigEnabled() && httpsRRAllowed); + } + + void nsHttpChannel::DoNotifyListenerCleanup() { + // We don't need this info anymore + CleanRedirectCacheChainIfNecessary(); + } + + void nsHttpChannel::ReleaseListeners() { +@@ -6755,39 +6759,26 @@ nsHttpChannel::GetOrCreateChannelClassif + LOG(("nsHttpChannel [%p] created nsChannelClassifier [%p]\n", this, + mChannelClassifier.get())); + } + + RefPtr classifier = mChannelClassifier; + return classifier.forget(); + } + +-uint16_t nsHttpChannel::GetProxyDNSStrategy() { +- // This function currently only supports returning DNS_PREFETCH_ORIGIN. +- // Support for the rest of the DNS_* flags will be added later. +- ++ProxyDNSStrategy nsHttpChannel::GetProxyDNSStrategy() { + // When network_dns_force_use_https_rr is true, return DNS_PREFETCH_ORIGIN. + // This ensures that we always perform HTTPS RR query. +- if (!mProxyInfo || StaticPrefs::network_dns_force_use_https_rr()) { +- return DNS_PREFETCH_ORIGIN; +- } +- +- uint32_t flags = 0; +- nsAutoCString type; +- mProxyInfo->GetFlags(&flags); +- mProxyInfo->GetType(type); ++ nsCOMPtr proxyInfo(static_cast(mProxyInfo.get())); ++ if (!proxyInfo || StaticPrefs::network_dns_force_use_https_rr()) { ++ return ProxyDNSStrategy::ORIGIN; ++ } + + // If the proxy is not to perform name resolution itself. +- if (!(flags & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST)) { +- if (type.EqualsLiteral("socks")) { +- return DNS_PREFETCH_ORIGIN; +- } +- } +- +- return 0; ++ return GetProxyDNSStrategyHelper(proxyInfo->Type(), proxyInfo->Flags()); + } + + // BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by + // functions that called BeginConnect if needed. Only + // MaybeResolveProxyAndBeginConnect and OnProxyAvailable ever call + // BeginConnect. + nsresult nsHttpChannel::BeginConnect() { + LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this)); +@@ -6962,21 +6953,23 @@ nsresult nsHttpChannel::BeginConnect() { + } else { + LOG(("nsHttpChannel %p Using default connection info", this)); + + mConnectionInfo = connInfo; + Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false); + } + + bool trrEnabled = false; ++ auto dnsStrategy = GetProxyDNSStrategy(); + bool httpsRRAllowed = + !LoadBeConservative() && !(mCaps & NS_HTTP_BE_CONSERVATIVE) && + !(mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() && + mLoadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_DOCUMENT) && ++ dnsStrategy == ProxyDNSStrategy::ORIGIN && + !mConnectionInfo->UsingConnect() && canUseHTTPSRRonNetwork(&trrEnabled) && + StaticPrefs::network_dns_use_https_rr_as_altsvc(); + if (!httpsRRAllowed) { + DisallowHTTPSRR(mCaps); + } else if (trrEnabled) { + if (nsIRequest::GetTRRMode() != nsIRequest::TRR_DISABLED_MODE) { + mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR; + } +@@ -7077,26 +7070,17 @@ nsresult nsHttpChannel::BeginConnect() { + "[this=%p]\n", + this)); + return NS_OK; + } + + ReEvaluateReferrerAfterTrackingStatusIsKnown(); + } + +- rv = MaybeStartDNSPrefetch(); +- if (NS_FAILED(rv)) { +- auto dnsStrategy = GetProxyDNSStrategy(); +- if (dnsStrategy & DNS_BLOCK_ON_ORIGIN_RESOLVE) { +- // TODO: Should this be fatal? +- return rv; +- } +- // Otherwise this shouldn't be fatal. +- return NS_OK; +- } ++ MaybeStartDNSPrefetch(); + + rv = CallOrWaitForResume( + [](nsHttpChannel* self) { return self->PrepareToConnect(); }); + if (NS_FAILED(rv)) { + return rv; + } + + if (shouldBeClassified) { +@@ -7106,69 +7090,57 @@ nsresult nsHttpChannel::BeginConnect() { + LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]", + channelClassifier.get(), this)); + channelClassifier->Start(); + } + + return NS_OK; + } + +-nsresult nsHttpChannel::MaybeStartDNSPrefetch() { ++void nsHttpChannel::MaybeStartDNSPrefetch() { + // Start a DNS lookup very early in case the real open is queued the DNS can + // happen in parallel. Do not do so in the presence of an HTTP proxy as + // all lookups other than for the proxy itself are done by the proxy. + // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or + // LOAD_ONLY_FROM_CACHE flags are set. + // + // We keep the DNS prefetch object around so that we can retrieve + // timing information from it. There is no guarantee that we actually + // use the DNS prefetch data for the real connection, but as we keep + // this data around for 3 minutes by default, this should almost always + // be correct, and even when it isn't, the timing still represents _a_ + // valid DNS lookup timing for the site, even if it is not _the_ + // timing we used. + if ((mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE)) || + LoadAuthRedirectedChannel()) { +- return NS_OK; ++ return; + } + + auto dnsStrategy = GetProxyDNSStrategy(); + + LOG( + ("nsHttpChannel::MaybeStartDNSPrefetch [this=%p, strategy=%u] " + "prefetching%s\n", +- this, dnsStrategy, ++ this, static_cast(dnsStrategy), + mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "")); + +- if (dnsStrategy & DNS_PREFETCH_ORIGIN) { ++ if (dnsStrategy == ProxyDNSStrategy::ORIGIN) { + OriginAttributes originAttributes; + StoragePrincipalHelper::GetOriginAttributesForNetworkState( + this, originAttributes); + + mDNSPrefetch = + new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode(), + this, LoadTimingEnabled()); + nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS; + if (mCaps & NS_HTTP_REFRESH_DNS) { + dnsFlags |= nsIDNSService::RESOLVE_BYPASS_CACHE; + } +- nsresult rv = mDNSPrefetch->PrefetchHigh(dnsFlags); +- +- if (dnsStrategy & DNS_BLOCK_ON_ORIGIN_RESOLVE) { +- LOG((" blocking on prefetching origin")); +- +- if (NS_WARN_IF(NS_FAILED(rv))) { +- LOG((" lookup failed with 0x%08" PRIx32 ", aborting request", +- static_cast(rv))); +- return rv; +- } +- +- // Resolved in OnLookupComplete. +- mDNSBlockingThenable = mDNSBlockingPromise.Ensure(__func__); +- } ++ ++ Unused << mDNSPrefetch->PrefetchHigh(dnsFlags); + + if (StaticPrefs::network_dns_use_https_rr_as_altsvc() && !mHTTPSSVCRecord && + !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR) && canUseHTTPSRRonNetwork()) { + MOZ_ASSERT(!mHTTPSSVCRecord); + + OriginAttributes originAttributes; + StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(this, + originAttributes); +@@ -7176,18 +7148,16 @@ nsresult nsHttpChannel::MaybeStartDNSPre + RefPtr resolver = + new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode()); + Unused << resolver->FetchHTTPSSVC(mCaps & NS_HTTP_REFRESH_DNS, true, + [](nsIDNSHTTPSSVCRecord*) { + // Do nothing. This is a DNS prefetch. + }); + } + } +- +- return NS_OK; + } + + NS_IMETHODIMP + nsHttpChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) { + if (mCacheEntry && !LoadCacheEntryIsWriteOnly()) { + int64_t dataSize = 0; + mCacheEntry->GetDataSize(&dataSize); + *aEncodedBodySize = dataSize; +diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h +--- a/netwerk/protocol/http/nsHttpChannel.h ++++ b/netwerk/protocol/http/nsHttpChannel.h +@@ -304,33 +304,21 @@ class nsHttpChannel final : public HttpB + bool RequestIsConditional(); + void HandleContinueCancellingByURLClassifier(nsresult aErrorCode); + nsresult CancelInternal(nsresult status); + void ContinueCancellingByURLClassifier(nsresult aErrorCode); + + // Connections will only be established in this function. + // (including DNS prefetch and speculative connection.) + void MaybeResolveProxyAndBeginConnect(); +- nsresult MaybeStartDNSPrefetch(); +- +- // Tells the channel to resolve the origin of the end server we are connecting +- // to. +- static uint16_t const DNS_PREFETCH_ORIGIN = 1 << 0; +- // Tells the channel to resolve the host name of the proxy. +- static uint16_t const DNS_PREFETCH_PROXY = 1 << 1; +- // Will be set if the current channel uses an HTTP/HTTPS proxy. +- static uint16_t const DNS_PROXY_IS_HTTP = 1 << 2; +- // Tells the channel to wait for the result of the origin server resolution +- // before any connection attempts are made. +- static uint16_t const DNS_BLOCK_ON_ORIGIN_RESOLVE = 1 << 3; ++ void MaybeStartDNSPrefetch(); + + // Based on the proxy configuration determine the strategy for resolving the + // end server host name. +- // Returns a combination of the above flags. +- uint16_t GetProxyDNSStrategy(); ++ ProxyDNSStrategy GetProxyDNSStrategy(); + + // We might synchronously or asynchronously call BeginConnect, + // which includes DNS prefetch and speculative connection, according to + // whether an async tracker lookup is required. If the tracker lookup + // is required, this funciton will just return NS_OK and BeginConnect() + // will be called when callback. See Bug 1325054 for more information. + nsresult BeginConnect(); + [[nodiscard]] nsresult PrepareToConnect(); +diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h +--- a/netwerk/protocol/http/nsHttpConnectionInfo.h ++++ b/netwerk/protocol/http/nsHttpConnectionInfo.h +@@ -122,16 +122,23 @@ class nsHttpConnectionInfo final : publi + return mProxyInfo ? mProxyInfo->Type() : nullptr; + } + const char* ProxyUsername() const { + return mProxyInfo ? mProxyInfo->Username().get() : nullptr; + } + const char* ProxyPassword() const { + return mProxyInfo ? mProxyInfo->Password().get() : nullptr; + } ++ uint32_t ProxyFlag() const { ++ uint32_t flags = 0; ++ if (mProxyInfo) { ++ mProxyInfo->GetFlags(&flags); ++ } ++ return flags; ++ } + + const nsCString& ProxyAuthorizationHeader() const { + return mProxyInfo ? mProxyInfo->ProxyAuthorizationHeader() : EmptyCString(); + } + const nsCString& ConnectionIsolationKey() const { + return mProxyInfo ? mProxyInfo->ConnectionIsolationKey() : EmptyCString(); + } + +diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp +--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp ++++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp +@@ -3570,19 +3570,25 @@ void nsHttpConnectionMgr::DoSpeculativeC + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(aTrans); + MOZ_ASSERT(aEnt); + if (!gHttpHandler->Active()) { + // Do nothing if we are shutting down. + return; + } + +- if (aFetchHTTPSRR && NS_SUCCEEDED(aTrans->FetchHTTPSRR())) { +- // nsHttpConnectionMgr::DoSpeculativeConnection will be called again when +- // HTTPS RR is available. ++ ProxyDNSStrategy strategy = GetProxyDNSStrategyHelper( ++ aEnt->mConnInfo->ProxyType(), aEnt->mConnInfo->ProxyFlag()); ++ // Speculative connections can be triggered by non-Necko consumers, ++ // so add an extra check to ensure HTTPS RR isn't fetched when a proxy is ++ // used. ++ if (aFetchHTTPSRR && strategy == ProxyDNSStrategy::ORIGIN && ++ NS_SUCCEEDED(aTrans->FetchHTTPSRR())) { ++ // nsHttpConnectionMgr::DoSpeculativeConnection will be called again ++ // when HTTPS RR is available. + return; + } + + uint32_t parallelSpeculativeConnectLimit = + aTrans->ParallelSpeculativeConnectLimit() + ? *aTrans->ParallelSpeculativeConnectLimit() + : gHttpHandler->ParallelSpeculativeConnectLimit(); + bool ignoreIdle = aTrans->IgnoreIdle() ? *aTrans->IgnoreIdle() : false; +diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp +--- a/netwerk/protocol/http/nsHttpHandler.cpp ++++ b/netwerk/protocol/http/nsHttpHandler.cpp +@@ -2403,17 +2403,19 @@ nsresult nsHttpHandler::SpeculativeConne + if (!neckoParent) { + continue; + } + Unused << neckoParent->SendSpeculativeConnectRequest(); + } + } + } + +- return SpeculativeConnect(ci, aCallbacks); ++ // When ech is enabled, always do speculative connect with HTTPS RR. ++ return MaybeSpeculativeConnectWithHTTPSRR(ci, aCallbacks, 0, ++ EchConfigEnabled()); + } + + NS_IMETHODIMP + nsHttpHandler::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal, + nsIInterfaceRequestor* aCallbacks, + bool aAnonymous) { + return SpeculativeConnectInternal(aURI, aPrincipal, Nothing(), aCallbacks, + aAnonymous); +diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h +--- a/netwerk/protocol/http/nsHttpHandler.h ++++ b/netwerk/protocol/http/nsHttpHandler.h +@@ -291,24 +291,23 @@ class nsHttpHandler final : public nsIHt + [[nodiscard]] nsresult ProcessPendingQ() { + return mConnMgr->ProcessPendingQ(); + } + + [[nodiscard]] nsresult GetSocketThreadTarget(nsIEventTarget** target) { + return mConnMgr->GetSocketThreadTarget(target); + } + +- [[nodiscard]] nsresult SpeculativeConnect(nsHttpConnectionInfo* ci, +- nsIInterfaceRequestor* callbacks, +- uint32_t caps = 0, +- bool aFetchHTTPSRR = false) { ++ [[nodiscard]] nsresult MaybeSpeculativeConnectWithHTTPSRR( ++ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps, ++ bool aFetchHTTPSRR) { + TickleWifi(callbacks); + RefPtr clone = ci->Clone(); + return mConnMgr->SpeculativeConnect(clone, callbacks, caps, nullptr, +- aFetchHTTPSRR | EchConfigEnabled()); ++ aFetchHTTPSRR); + } + + [[nodiscard]] nsresult SpeculativeConnect(nsHttpConnectionInfo* ci, + nsIInterfaceRequestor* callbacks, + uint32_t caps, + SpeculativeTransaction* aTrans) { + RefPtr clone = ci->Clone(); + return mConnMgr->SpeculativeConnect(clone, callbacks, caps, aTrans); +diff --git a/netwerk/test/unit/test_proxyDNS_leak.js b/netwerk/test/unit/test_proxyDNS_leak.js +new file mode 100644 +--- /dev/null ++++ b/netwerk/test/unit/test_proxyDNS_leak.js +@@ -0,0 +1,111 @@ ++/* 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/. */ ++ ++// Test when socks proxy is registered, we don't try to resolve HTTPS record. ++// Steps: ++// 1. Use addHTTPSRecordOverride to add an override for service.com. ++// 2. Add a proxy filter to use socks proxy. ++// 3. Create a request to load service.com. ++// 4. See if the HTTPS record is in DNS cache entries. ++ ++"use strict"; ++ ++const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService( ++ Ci.nsIDashboard ++); ++const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); ++ ++add_task(async function setup() { ++ Services.prefs.setBoolPref("network.dns.native_https_query", true); ++ Services.prefs.setBoolPref("network.dns.native_https_query_win10", true); ++ const override = Cc["@mozilla.org/network/native-dns-override;1"].getService( ++ Ci.nsINativeDNSResolverOverride ++ ); ++ ++ let rawBuffer = [ ++ 0, 0, 128, 0, 0, 0, 0, 1, 0, 0, 0, 0, 7, 115, 101, 114, 118, 105, 99, 101, ++ 3, 99, 111, 109, 0, 0, 65, 0, 1, 0, 0, 0, 55, 0, 13, 0, 1, 0, 0, 1, 0, 6, 2, ++ 104, 50, 2, 104, 51, ++ ]; ++ override.addHTTPSRecordOverride("service.com", rawBuffer, rawBuffer.length); ++ override.addIPOverride("service.com", "127.0.0.1"); ++ registerCleanupFunction(() => { ++ Services.prefs.clearUserPref("network.dns.native_https_query"); ++ Services.prefs.clearUserPref("network.dns.native_https_query_win10"); ++ Services.prefs.clearUserPref("network.dns.localDomains"); ++ override.clearOverrides(); ++ }); ++}); ++ ++function makeChan(uri) { ++ let chan = NetUtil.newChannel({ ++ uri, ++ loadUsingSystemPrincipal: true, ++ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, ++ }).QueryInterface(Ci.nsIHttpChannel); ++ chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; ++ return chan; ++} ++ ++function channelOpenPromise(chan, flags) { ++ return new Promise(resolve => { ++ function finish(req, buffer) { ++ resolve([req, buffer]); ++ } ++ chan.asyncOpen(new ChannelListener(finish, null, flags)); ++ }); ++} ++ ++async function isRecordFound(hostname) { ++ return new Promise(resolve => { ++ gDashboard.requestDNSInfo(function (data) { ++ let found = false; ++ for (let i = 0; i < data.entries.length; i++) { ++ if ( ++ data.entries[i].hostname == hostname && ++ data.entries[i].type == Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC ++ ) { ++ found = true; ++ break; ++ } ++ } ++ resolve(found); ++ }); ++ }); ++} ++ ++async function do_test_with_proxy_filter(filter) { ++ pps.registerFilter(filter, 10); ++ ++ let chan = makeChan(`https://service.com/`); ++ await channelOpenPromise(chan, CL_EXPECT_LATE_FAILURE | CL_ALLOW_UNKNOWN_CL); ++ ++ let found = await isRecordFound("service.com"); ++ pps.unregisterFilter(filter); ++ ++ return found; ++} ++ ++add_task(async function test_proxyDNS_do_leak() { ++ let filter = new NodeProxyFilter("socks", "localhost", 443, 0); ++ ++ let res = await do_test_with_proxy_filter(filter); ++ ++ Assert.ok(res, "Should find a DNS entry"); ++}); ++ ++add_task(async function test_proxyDNS_dont_leak() { ++ Services.dns.clearCache(false); ++ ++ let filter = new NodeProxyFilter( ++ "socks", ++ "localhost", ++ 443, ++ Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST ++ ); ++ ++ let res = await do_test_with_proxy_filter(filter); ++ ++ Assert.ok(!res, "Should not find a DNS entry"); ++}); +diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml +--- a/netwerk/test/unit/xpcshell.toml ++++ b/netwerk/test/unit/xpcshell.toml +@@ -974,16 +974,22 @@ skip-if = [ + + ["test_proxy-slow-upload.js"] + + ["test_proxy_cancel.js"] + run-sequentially = "node server exceptions dont replay well" + + ["test_proxy_pac.js"] + ++["test_proxyDNS_leak.js"] ++skip-if = [ ++ "os == 'android'", ++ "socketprocess_networking", ++] ++ + ["test_proxyconnect.js"] + skip-if = [ + "tsan", + "socketprocess_networking", # Bug 1614708 + ] + + ["test_proxyconnect_https.js"] + skip-if = [ +diff --git a/toolkit/content/aboutNetworking.js b/toolkit/content/aboutNetworking.js +--- a/toolkit/content/aboutNetworking.js ++++ b/toolkit/content/aboutNetworking.js +@@ -111,16 +111,21 @@ function displayDns(data) { + prevURL.parentNode.replaceChild(trr_url_tbody, prevURL); + + let cont = document.getElementById("dns_content"); + let parent = cont.parentNode; + let new_cont = document.createElement("tbody"); + new_cont.setAttribute("id", "dns_content"); + + for (let i = 0; i < data.entries.length; i++) { ++ // TODO: Will be supported in bug 1889387. ++ if (data.entries[i].type != Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT) { ++ continue; ++ } ++ + let row = document.createElement("tr"); + row.appendChild(col(data.entries[i].hostname)); + row.appendChild(col(data.entries[i].family)); + row.appendChild(col(data.entries[i].trr)); + let column = document.createElement("td"); + + for (let j = 0; j < data.entries[i].hostaddr.length; j++) { + column.appendChild(document.createTextNode(data.entries[i].hostaddr[j])); +