# 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]));