Bug 1600965 - Add support for blocking on the DNS resolution for HTTP channels; r=dragana

Differential Revision: https://phabricator.services.mozilla.com/D56799

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Ehsan Akhgari 2020-01-15 14:53:12 +00:00
Родитель c75b5dbb22
Коммит 338a38518d
4 изменённых файлов: 180 добавлений и 41 удалений

Просмотреть файл

@ -7051,6 +7051,12 @@
value: 2000
mirror: always
# Whether the SOCKS proxy should be in charge of DNS resolution.
- name: network.proxy.socks_remote_dns
type: bool
value: false
mirror: always
# Some requests during a page load are marked as "tail", mainly trackers, but not only.
# This pref controls whether such requests are put to the tail, behind other requests
# emerging during page loading process.

Просмотреть файл

@ -1967,7 +1967,6 @@ pref("network.proxy.ssl_port", 0);
pref("network.proxy.socks", "");
pref("network.proxy.socks_port", 0);
pref("network.proxy.socks_version", 5);
pref("network.proxy.socks_remote_dns", false);
pref("network.proxy.proxy_over_tls", true);
pref("network.proxy.no_proxies_on", "");
// Set true to allow resolving proxy for localhost

Просмотреть файл

@ -832,8 +832,45 @@ nsresult nsHttpChannel::ContinueConnect() {
}
nsresult nsHttpChannel::DoConnect(HttpTransactionShell* aTransWithStickyConn) {
LOG(("nsHttpChannel::DoConnect [this=%p, aTransWithStickyConn=%p]\n", this,
aTransWithStickyConn));
LOG(("nsHttpChannel::DoConnect [this=%p]\n", this));
if (!mDNSBlockingPromise.IsEmpty()) {
LOG((" waiting for DNS prefetch"));
// Transaction is passed only from auth retry for which we will definitely
// not block on DNS to alter the origin server name for IP; it has already
// been done.
MOZ_ASSERT(!aTransWithStickyConn);
MOZ_ASSERT(mDNSBlockingThenable);
nsCOMPtr<nsISerialEventTarget> target(do_GetMainThread());
RefPtr<nsHttpChannel> self(this);
mDNSBlockingThenable->Then(
target, __func__,
[self](const nsCOMPtr<nsIDNSRecord>& aRec) {
nsresult rv = self->DoConnectActual(nullptr);
if (NS_FAILED(rv)) {
self->CloseCacheEntry(false);
Unused << self->AsyncAbort(rv);
}
},
[self](nsresult err) {
self->CloseCacheEntry(false);
Unused << self->AsyncAbort(err);
});
// The connection will continue when the promise is resolved in
// OnLookupComplete.
return NS_OK;
}
return DoConnectActual(aTransWithStickyConn);
}
nsresult nsHttpChannel::DoConnectActual(
HttpTransactionShell* aTransWithStickyConn) {
LOG(("nsHttpChannel::DoConnectActual [this=%p, aTransWithStickyConn=%p]\n",
this, aTransWithStickyConn));
nsresult rv = SetupTransaction();
if (NS_FAILED(rv)) {
@ -6606,6 +6643,26 @@ nsHttpChannel::GetOrCreateChannelClassifier() {
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.
if (!mProxyInfo) {
return DNS_PREFETCH_ORIGIN;
}
nsAutoCString type;
mProxyInfo->GetType(type);
if (!StaticPrefs::network_proxy_socks_remote_dns()) {
if (type.EqualsLiteral("socks")) {
return DNS_PREFETCH_ORIGIN;
}
}
return 0;
}
// BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
// functions that called BeginConnect if needed. Only AsyncOpenFinal,
// MaybeResolveProxyAndBeginConnect and OnProxyAvailable ever call
@ -6815,7 +6872,16 @@ nsresult nsHttpChannel::BeginConnect() {
ReEvaluateReferrerAfterTrackingStatusIsKnown();
}
MaybeStartDNSPrefetch();
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;
}
rv = ContinueBeginConnectWithResult();
if (NS_FAILED(rv)) {
@ -6834,30 +6900,55 @@ nsresult nsHttpChannel::BeginConnect() {
return NS_OK;
}
void nsHttpChannel::MaybeStartDNSPrefetch() {
if (!mConnectionInfo->UsingHttpProxy() &&
!(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
// 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.
LOG(("nsHttpChannel::MaybeStartDNSPrefetch [this=%p] prefetching%s\n", this,
mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
nsresult 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)) {
return NS_OK;
}
auto dnsStrategy = GetProxyDNSStrategy();
LOG(
("nsHttpChannel::MaybeStartDNSPrefetch [this=%p, strategy=%u] "
"prefetching%s\n",
this, dnsStrategy,
mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
if (dnsStrategy & DNS_PREFETCH_ORIGIN) {
OriginAttributes originAttributes;
NS_GetOriginAttributes(this, originAttributes);
mDNSPrefetch = new nsDNSPrefetch(
mURI, originAttributes, nsIRequest::GetTRRMode(), this, mTimingEnabled);
mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
nsresult rv = mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
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<uint32_t>(rv)));
return rv;
}
// Resolved in OnLookupComplete.
mDNSBlockingThenable = mDNSBlockingPromise.Ensure(__func__);
}
}
return NS_OK;
}
NS_IMETHODIMP
@ -7586,8 +7677,8 @@ nsresult nsHttpChannel::ProcessCrossOriginResourcePolicyHeader() {
RefPtr<mozilla::dom::BrowsingContext> ctx;
mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
// Note that we treat invalid value as "cross-origin", which spec indicates.
// We might want to make that stricter.
// Note that we treat invalid value as "cross-origin", which spec
// indicates. We might want to make that stricter.
if (content.IsEmpty() && ctx &&
ctx->GetEmbedderPolicy() == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
content = NS_LITERAL_CSTRING("same-origin");
@ -7666,8 +7757,9 @@ nsHttpChannel::OnStartRequest(nsIRequest* request) {
static_cast<int32_t>(mFirstResponseSource), request == mCachePump,
request == mTransactionPump));
if (mFirstResponseSource == RESPONSE_PENDING) {
// When the cache wins mFirstResponseSource is set to RESPONSE_FROM_CACHE
// earlier in ReadFromCache, so this must be a response from the network.
// When the cache wins mFirstResponseSource is set to
// RESPONSE_FROM_CACHE earlier in ReadFromCache, so this must be a
// response from the network.
MOZ_ASSERT(request == mTransactionPump);
LOG((" First response from network\n"));
{
@ -7731,8 +7823,8 @@ nsHttpChannel::OnStartRequest(nsIRequest* request) {
// don't enter this block if we're reading from the cache...
if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
// mTransactionPump doesn't hit OnInputStreamReady and call this until
// all of the response headers have been acquired, so we can take ownership
// of them from the transaction.
// all of the response headers have been acquired, so we can take
// ownership of them from the transaction.
mResponseHead = mTransaction->TakeResponseHead();
// the response head may be null if the transaction was cancelled. in
// which case we just need to call OnStartRequest/OnStopRequest.
@ -7916,7 +8008,8 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
// allow content to be cached if it was loaded successfully (bug #482935)
bool contentComplete = NS_SUCCEEDED(status);
// honor the cancelation status even if the underlying transaction completed.
// honor the cancelation status even if the underlying transaction
// completed.
if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
if (mCachedContentIsPartial) {
@ -8197,7 +8290,8 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
chanDisposition =
static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
} else if (mLoadInfo && mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
// HTTP content the browser would upgrade to HTTPS if upgrading was enabled
// HTTP content the browser would upgrade to HTTPS if upgrading was
// enabled
upgradeKey = NS_LITERAL_CSTRING("disabledUpgrade");
} else {
// HTTP content that wouldn't upgrade
@ -8258,8 +8352,9 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
// perform any final cache operations before we close the cache entry.
if (mCacheEntry && mRequestTimeInitialized) {
bool writeAccess;
// New implementation just returns value of the !mCacheEntryIsReadOnly flag
// passed in. Old implementation checks on nsICache::ACCESS_WRITE flag.
// New implementation just returns value of the !mCacheEntryIsReadOnly
// flag passed in. Old implementation checks on nsICache::ACCESS_WRITE
// flag.
mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
if (writeAccess) {
nsresult rv = FinalizeCacheEntry();
@ -8601,10 +8696,10 @@ nsHttpChannel::OnTransportStatus(nsITransport* trans, nsresult status,
} else {
nsCOMPtr<nsIParentChannel> parentChannel;
NS_QueryNotificationCallbacks(this, parentChannel);
// If the event sink is |HttpChannelParent|, we have to send status events
// to it even if LOAD_BACKGROUND is set. |HttpChannelParent| needs to be
// aware of whether the status is |NS_NET_STATUS_RECEIVING_FROM| or
// |NS_NET_STATUS_READING|.
// If the event sink is |HttpChannelParent|, we have to send status
// events to it even if LOAD_BACKGROUND is set. |HttpChannelParent|
// needs to be aware of whether the status is
// |NS_NET_STATUS_RECEIVING_FROM| or |NS_NET_STATUS_READING|.
// LOAD_BACKGROUND is checked again in |HttpChannelChild|, so the final
// consumer won't get this event.
if (SameCOMIdentity(parentChannel, mProgressSink)) {
@ -9254,6 +9349,15 @@ nsHttpChannel::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
}
}
if (!mDNSBlockingPromise.IsEmpty()) {
if (NS_SUCCEEDED(status)) {
nsCOMPtr<nsIDNSRecord> record(rec);
mDNSBlockingPromise.Resolve(record, __func__);
} else {
mDNSBlockingPromise.Reject(status, __func__);
}
}
return NS_OK;
}
@ -9641,12 +9745,13 @@ nsHttpChannel::ResumeInternal() {
// notification is already pending in the queue right now, because
// AsyncRead doesn't (regardless if called after Suspend) respect
// the suspend coutner and the right order would not be preserved.
// Hence, we do another dispatch round to actually Resume after the
// notification from the original pump.
// Hence, we do another dispatch round to actually Resume after
// the notification from the original pump.
if (transactionPump != self->mTransactionPump &&
self->mTransactionPump) {
LOG(
("nsHttpChannel::CallOnResume async-resuming new transaction "
("nsHttpChannel::CallOnResume async-resuming new "
"transaction "
"pump %p, this=%p",
self->mTransactionPump.get(), self.get()));

Просмотреть файл

@ -35,6 +35,7 @@
class nsDNSPrefetch;
class nsICancelable;
class nsIDNSRecord;
class nsIHttpChannelAuthProvider;
class nsInputStreamPump;
class nsITransportSecurityInfo;
@ -46,6 +47,8 @@ class nsChannelClassifier;
class Http2PushedStream;
class HttpChannelSecurityWarningReporter;
using DNSPromise = MozPromise<nsCOMPtr<nsIDNSRecord>, nsresult, false>;
//-----------------------------------------------------------------------------
// nsHttpChannel
//-----------------------------------------------------------------------------
@ -305,7 +308,23 @@ class nsHttpChannel final : public HttpBaseChannel,
// Connections will only be established in this function.
// (including DNS prefetch and speculative connection.)
nsresult MaybeResolveProxyAndBeginConnect();
void MaybeStartDNSPrefetch();
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;
// 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();
// We might synchronously or asynchronously call BeginConnect,
// which includes DNS prefetch and speculative connection, according to
@ -434,6 +453,8 @@ class nsHttpChannel final : public HttpBaseChannel,
aContinueOnStopRequestFunc);
MOZ_MUST_USE nsresult
DoConnect(HttpTransactionShell* aTransWithStickyConn = nullptr);
MOZ_MUST_USE nsresult
DoConnectActual(HttpTransactionShell* aTransWithStickyConn);
MOZ_MUST_USE nsresult ContinueOnStopRequestAfterAuthRetry(
nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
HttpTransactionShell* aTransWithStickyConn);
@ -822,6 +843,14 @@ class nsHttpChannel final : public HttpBaseChannel,
TimeStamp mNavigationStartTimeStamp;
// Promise that blocks connection creation when we want to resolve the origin
// host name to be able to give the configured proxy only the resolved IP
// to not leak names.
MozPromiseHolder<DNSPromise> mDNSBlockingPromise;
// When we hit DoConnect before the resolution is done, Then() will be set
// here to resume DoConnect.
RefPtr<DNSPromise> mDNSBlockingThenable;
// We update the value of mProxyConnectResponseCode when OnStartRequest is
// called and reset the value when we switch to another failover proxy.
int32_t mProxyConnectResponseCode;