Source code

Revision control

Copy as Markdown

Other Tools

/* vim:set ts=4 sw=2 sts=2 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/. */
// HttpLog.h should generally be included first
#include "HttpLog.h"
#include "ConnectionHandle.h"
#include "DnsAndConnectSocket.h"
#include "nsHttpConnection.h"
#include "nsIClassOfService.h"
#include "nsIDNSRecord.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIHttpActivityObserver.h"
#include "nsSocketTransportService2.h"
#include "nsDNSService2.h"
#include "nsQueryObject.h"
#include "nsURLHelper.h"
#include "mozilla/Components.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/SyncRunnable.h"
#include "nsHttpHandler.h"
#include "ConnectionEntry.h"
#include "HttpConnectionUDP.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
// Log on level :5, instead of default :4.
#undef LOG
#define LOG(args) LOG5(args)
#undef LOG_ENABLED
#define LOG_ENABLED() LOG5_ENABLED()
namespace mozilla {
namespace net {
//////////////////////// DnsAndConnectSocket
NS_IMPL_ADDREF(DnsAndConnectSocket)
NS_IMPL_RELEASE(DnsAndConnectSocket)
NS_INTERFACE_MAP_BEGIN(DnsAndConnectSocket)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsINamed)
NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
NS_INTERFACE_MAP_ENTRY_CONCRETE(DnsAndConnectSocket)
NS_INTERFACE_MAP_END
static void NotifyActivity(nsHttpConnectionInfo* aConnInfo, uint32_t aSubtype) {
HttpConnectionActivity activity(
aConnInfo->HashKey(), aConnInfo->GetOrigin(), aConnInfo->OriginPort(),
aConnInfo->EndToEndSSL(), !aConnInfo->GetEchConfig().IsEmpty(),
aConnInfo->IsHttp3());
gHttpHandler->ObserveHttpActivityWithArgs(
activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION, aSubtype, PR_Now(), 0, ""_ns);
}
DnsAndConnectSocket::DnsAndConnectSocket(nsHttpConnectionInfo* ci,
nsAHttpTransaction* trans,
uint32_t caps, bool speculative,
bool isFromPredictor, bool urgentStart)
: mTransaction(trans),
mCaps(caps),
mSpeculative(speculative),
mUrgentStart(urgentStart),
mIsFromPredictor(isFromPredictor),
mConnInfo(ci) {
MOZ_ASSERT(ci && trans, "constructor with null arguments");
LOG(("Creating DnsAndConnectSocket [this=%p trans=%p ent=%s key=%s]\n", this,
trans, mConnInfo->Origin(), mConnInfo->HashKey().get()));
mIsHttp3 = mConnInfo->IsHttp3();
if (speculative) {
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN>
totalSpeculativeConn;
++totalSpeculativeConn;
if (isFromPredictor) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED>
totalPreconnectsCreated;
++totalPreconnectsCreated;
}
}
MOZ_ASSERT(mConnInfo);
NotifyActivity(mConnInfo,
mSpeculative
? NS_HTTP_ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED
: NS_HTTP_ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED);
}
void DnsAndConnectSocket::CheckIsDone() {
MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mSocketTransport);
MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mDNSRequest);
MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mSocketTransport);
MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mDNSRequest);
}
DnsAndConnectSocket::~DnsAndConnectSocket() {
LOG(("Destroying DnsAndConnectSocket [this=%p]\n", this));
MOZ_ASSERT(mState == DnsAndSocketState::DONE);
CheckIsDone();
// Check in case something goes wrong that we decrease
// the nsHttpConnectionMgr active connection number.
mPrimaryTransport.MaybeSetConnectingDone();
mBackupTransport.MaybeSetConnectingDone();
if (mSpeculative) {
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN>
unusedSpeculativeConn;
++unusedSpeculativeConn;
if (mIsFromPredictor) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED>
totalPreconnectsUnused;
++totalPreconnectsUnused;
}
}
}
nsresult DnsAndConnectSocket::Init(ConnectionEntry* ent) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(mState == DnsAndSocketState::INIT);
if (mConnInfo->GetRoutedHost().IsEmpty()) {
mPrimaryTransport.mHost = mConnInfo->GetOrigin();
mBackupTransport.mHost = mConnInfo->GetOrigin();
} else {
mPrimaryTransport.mHost = mConnInfo->GetRoutedHost();
mBackupTransport.mHost = mConnInfo->GetRoutedHost();
}
CheckProxyConfig();
if (!mSkipDnsResolution) {
nsresult rv = SetupDnsFlags(ent);
NS_ENSURE_SUCCESS(rv, rv);
}
return SetupEvent(SetupEvents::INIT_EVENT);
}
void DnsAndConnectSocket::CheckProxyConfig() {
if (nsCOMPtr<nsProxyInfo> proxyInfo = mConnInfo->ProxyInfo()) {
nsAutoCString proxyType(proxyInfo->Type());
bool proxyTransparent = false;
if (proxyType.EqualsLiteral("socks") || proxyType.EqualsLiteral("socks4")) {
proxyTransparent = true;
if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
mProxyTransparentResolvesHost = true;
}
}
if (mProxyTransparentResolvesHost) {
// Name resolution is done on the server side. Just pretend
// client resolution is complete, this will get picked up later.
// since we don't need to do DNS now, we bypass the resolving
// step by initializing mNetAddr to an empty address, but we
// must keep the port. The SOCKS IO layer will use the hostname
// we send it when it's created, rather than the empty address
// we send with the connect call.
mPrimaryTransport.mSkipDnsResolution = true;
mBackupTransport.mSkipDnsResolution = true;
mSkipDnsResolution = true;
}
if (!proxyTransparent && !proxyInfo->Host().IsEmpty()) {
mProxyNotTransparent = true;
mPrimaryTransport.mHost = proxyInfo->Host();
mBackupTransport.mHost = proxyInfo->Host();
}
}
}
nsresult DnsAndConnectSocket::SetupDnsFlags(ConnectionEntry* ent) {
LOG(("DnsAndConnectSocket::SetupDnsFlags [this=%p] ", this));
nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
bool disableIpv6ForBackup = false;
if (mCaps & NS_HTTP_REFRESH_DNS) {
dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
}
if (mCaps & NS_HTTP_DISABLE_IPV4) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
} else if (mCaps & NS_HTTP_DISABLE_IPV6) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
} else if (ent->PreferenceKnown()) {
if (ent->mPreferIPv6) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
} else if (ent->mPreferIPv4) {
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
}
mPrimaryTransport.mRetryWithDifferentIPFamily = true;
mBackupTransport.mRetryWithDifferentIPFamily = true;
} else if (gHttpHandler->FastFallbackToIPv4()) {
// For backup connections, we disable IPv6. That's because some users have
// broken IPv6 connectivity (leading to very long timeouts), and disabling
// IPv6 on the backup connection gives them a much better user experience
// with dual-stack hosts, though they still pay the 250ms delay for each new
// connection. This strategy is also known as "happy eyeballs".
disableIpv6ForBackup = true;
}
if (ent->mConnInfo->HasIPHintAddress()) {
nsresult rv;
nsCOMPtr<nsIDNSService> dns =
do_GetService("@mozilla.org/network/dns-service;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
// The spec says: "If A and AAAA records for TargetName are locally
// available, the client SHOULD ignore these hints.", so we check if the DNS
// record is in cache before setting USE_IP_HINT_ADDRESS.
nsCOMPtr<nsIDNSRecord> record;
rv = dns->ResolveNative(
mPrimaryTransport.mHost, nsIDNSService::RESOLVE_OFFLINE,
mConnInfo->GetOriginAttributes(), getter_AddRefs(record));
if (NS_FAILED(rv) || !record) {
LOG(("Setting Socket to use IP hint address"));
dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
}
}
dnsFlags |=
nsIDNSService::GetFlagsFromTRRMode(NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps));
// When we get here, we are not resolving using any configured proxy likely
// because of individual proxy setting on the request or because the host is
// excluded from proxying. Hence, force resolution despite global proxy-DNS
// configuration.
dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
"Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
mPrimaryTransport.mDnsFlags = dnsFlags;
mBackupTransport.mDnsFlags = dnsFlags;
if (disableIpv6ForBackup) {
mBackupTransport.mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
}
LOG(("DnsAndConnectSocket::SetupDnsFlags flags=%u flagsBackup=%u [this=%p]",
mPrimaryTransport.mDnsFlags, mBackupTransport.mDnsFlags, this));
NS_ASSERTION(
!(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
!(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
"Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
return NS_OK;
}
nsresult DnsAndConnectSocket::SetupEvent(SetupEvents event) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("DnsAndConnectSocket::SetupEvent state=%d event=%d this=%p", mState,
event, this));
nsresult rv = NS_OK;
switch (event) {
case SetupEvents::INIT_EVENT:
MOZ_ASSERT(mState == DnsAndSocketState::INIT);
rv = mPrimaryTransport.Init(this);
if (NS_FAILED(rv)) {
mState = DnsAndSocketState::DONE;
} else if (mPrimaryTransport.FirstResolving()) {
mState = DnsAndSocketState::RESOLVING;
} else if (!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) {
mState = DnsAndSocketState::CONNECTING;
SetupBackupTimer();
} else {
MOZ_ASSERT(false);
mState = DnsAndSocketState::DONE;
Abandon();
rv = NS_ERROR_UNEXPECTED;
}
break;
case SetupEvents::RESOLVED_PRIMARY_EVENT:
// This event may be posted multiple times if a DNS lookup is
// retriggered, e.g with different parameter.
if (!mIsHttp3 && (mState == DnsAndSocketState::RESOLVING)) {
mState = DnsAndSocketState::CONNECTING;
SetupBackupTimer();
}
break;
case SetupEvents::PRIMARY_DONE_EVENT:
MOZ_ASSERT((mState == DnsAndSocketState::RESOLVING) ||
(mState == DnsAndSocketState::CONNECTING) ||
(mState == DnsAndSocketState::ONE_CONNECTED));
CancelBackupTimer();
mBackupTransport.CancelDnsResolution();
if (mBackupTransport.ConnectingOrRetry()) {
mState = DnsAndSocketState::ONE_CONNECTED;
} else {
mState = DnsAndSocketState::DONE;
}
break;
case SetupEvents::BACKUP_DONE_EVENT:
MOZ_ASSERT((mState == DnsAndSocketState::CONNECTING) ||
(mState == DnsAndSocketState::ONE_CONNECTED));
if (mPrimaryTransport.ConnectingOrRetry()) {
mState = DnsAndSocketState::ONE_CONNECTED;
} else {
mState = DnsAndSocketState::DONE;
}
break;
case SetupEvents::BACKUP_TIMER_FIRED_EVENT:
MOZ_ASSERT(mState == DnsAndSocketState::CONNECTING);
mBackupTransport.Init(this);
}
LOG(("DnsAndConnectSocket::SetupEvent state=%d", mState));
if (mState == DnsAndSocketState::DONE) {
CheckIsDone();
RefPtr<DnsAndConnectSocket> self(this);
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
if (ent) {
ent->RemoveDnsAndConnectSocket(this, false);
}
return rv;
}
return NS_OK;
}
void DnsAndConnectSocket::SetupBackupTimer() {
uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
MOZ_ASSERT(!mSynTimer, "timer already initd");
// When using Fast Open the correct transport will be setup for sure (it is
// guaranteed), but it can be that it will happened a bit later.
if (timeout && (!mSpeculative || mConnInfo->GetFallbackConnection()) &&
!mIsHttp3) {
// Setup the timer that will establish a backup socket
// if we do not get a writable event on the main one.
// We do this because a lost SYN takes a very long time
// to repair at the TCP level.
//
// Failure to setup the timer is something we can live with,
// so don't return an error in that case.
NS_NewTimerWithCallback(getter_AddRefs(mSynTimer), this, timeout,
nsITimer::TYPE_ONE_SHOT);
LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p]", this));
} else if (timeout) {
LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p], did not arm\n",
this));
}
}
void DnsAndConnectSocket::CancelBackupTimer() {
// If the syntimer is still armed, we can cancel it because no backup
// socket should be formed at this point
if (!mSynTimer) {
return;
}
LOG(("DnsAndConnectSocket::CancelBackupTimer()"));
mSynTimer->Cancel();
// Keeping the reference to the timer to remember we have already
// performed the backup connection.
}
void DnsAndConnectSocket::Abandon() {
LOG(("DnsAndConnectSocket::Abandon [this=%p ent=%s] %p %p %p %p", this,
mConnInfo->Origin(), mPrimaryTransport.mSocketTransport.get(),
mBackupTransport.mSocketTransport.get(),
mPrimaryTransport.mStreamOut.get(), mBackupTransport.mStreamOut.get()));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// Tell socket (and backup socket) to forget the half open socket.
mPrimaryTransport.Abandon();
mBackupTransport.Abandon();
// Stop the timer - we don't want any new backups.
CancelBackupTimer();
mState = DnsAndSocketState::DONE;
}
double DnsAndConnectSocket::Duration(TimeStamp epoch) {
if (mPrimaryTransport.mSynStarted.IsNull()) {
return 0;
}
return (epoch - mPrimaryTransport.mSynStarted).ToMilliseconds();
}
NS_IMETHODIMP // method for nsITimerCallback
DnsAndConnectSocket::Notify(nsITimer* timer) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(timer == mSynTimer, "wrong timer");
MOZ_ASSERT(!mBackupTransport.mDNSRequest);
MOZ_ASSERT(!mBackupTransport.mSocketTransport);
MOZ_ASSERT(mSynTimer);
DebugOnly<nsresult> rv = SetupEvent(BACKUP_TIMER_FIRED_EVENT);
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Keeping the reference to the timer to remember we have already
// performed the backup connection.
return NS_OK;
}
NS_IMETHODIMP // method for nsINamed
DnsAndConnectSocket::GetName(nsACString& aName) {
aName.AssignLiteral("DnsAndConnectSocket");
return NS_OK;
}
NS_IMETHODIMP
DnsAndConnectSocket::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
nsresult status) {
LOG((
"DnsAndConnectSocket::OnLookupComplete: this=%p mState=%d status %" PRIx32
".",
this, mState, static_cast<uint32_t>(status)));
if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface((rec))) {
nsIRequest::TRRMode effectivemode = nsIRequest::TRR_DEFAULT_MODE;
addrRecord->GetEffectiveTRRMode(&effectivemode);
nsITRRSkipReason::value skipReason = nsITRRSkipReason::TRR_UNSET;
addrRecord->GetTrrSkipReason(&skipReason);
if (mTransaction) {
mTransaction->SetTRRInfo(effectivemode, skipReason);
}
}
MOZ_DIAGNOSTIC_ASSERT(request);
RefPtr<DnsAndConnectSocket> deleteProtector(this);
if (!request || (!IsPrimary(request) && !IsBackup(request))) {
return NS_OK;
}
if (IsPrimary(request) && NS_SUCCEEDED(status)) {
mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RESOLVED_HOST, 0);
}
// When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
// proxy host is not found, so we fixup the error code.
// For SOCKS proxies (mProxyTransparent == true), the socket
// transport resolves the real host here, so there's no fixup
// (see bug 226943).
if (mProxyNotTransparent && (status == NS_ERROR_UNKNOWN_HOST)) {
status = NS_ERROR_UNKNOWN_PROXY_HOST;
}
nsresult rv;
// remember if it was primary because TransportSetups will delete the ref to
// the DNS request and check cannot be performed later.
bool isPrimary = IsPrimary(request);
if (isPrimary) {
rv = mPrimaryTransport.OnLookupComplete(this, rec, status);
if ((!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) ||
(mIsHttp3 && mPrimaryTransport.Resolved())) {
SetupEvent(SetupEvents::RESOLVED_PRIMARY_EVENT);
}
} else {
rv = mBackupTransport.OnLookupComplete(this, rec, status);
}
if (NS_FAILED(rv) || mIsHttp3) {
// If we are retrying DNS, we should not setup the connection.
if (mIsHttp3 && mPrimaryTransport.mState ==
TransportSetup::TransportSetupState::RETRY_RESOLVING) {
LOG(("Retry DNS for Http3"));
return NS_OK;
}
// Before calling SetupConn we need to hold reference to this, i.e. a
// delete protector, because the corresponding ConnectionEntry may be
// abandoned and that will abandon this DnsAndConnectSocket.
SetupConn(isPrimary, rv);
// During a connection dispatch that will happen in SetupConn,
// a ConnectionEntry may be abandon and that will abandon this
// DnsAndConnectSocket. In that case mState will already be
// DnsAndSocketState::DONE and we do not need to set it again.
if (mState != DnsAndSocketState::DONE) {
if (isPrimary) {
SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
} else {
SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
}
}
}
return NS_OK;
}
// method for nsIAsyncOutputStreamCallback
NS_IMETHODIMP
DnsAndConnectSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_DIAGNOSTIC_ASSERT(mPrimaryTransport.mSocketTransport ||
mBackupTransport.mSocketTransport);
MOZ_DIAGNOSTIC_ASSERT(IsPrimary(out) || IsBackup(out), "stream mismatch");
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
MOZ_DIAGNOSTIC_ASSERT(ent);
Unused << ent;
RefPtr<DnsAndConnectSocket> deleteProtector(this);
LOG(("DnsAndConnectSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this,
mConnInfo->Origin(), IsPrimary(out) ? "primary" : "backup"));
// Remember if it was primary or backup reuest.
bool isPrimary = IsPrimary(out);
nsresult rv = NS_OK;
if (isPrimary) {
rv = mPrimaryTransport.CheckConnectedResult(this);
if (!mPrimaryTransport.DoneConnecting()) {
return NS_OK;
}
MOZ_ASSERT((NS_SUCCEEDED(rv) &&
(mPrimaryTransport.mState ==
TransportSetup::TransportSetupState::CONNECTING_DONE) &&
mPrimaryTransport.mSocketTransport) ||
(NS_FAILED(rv) &&
(mPrimaryTransport.mState ==
TransportSetup::TransportSetupState::DONE) &&
!mPrimaryTransport.mSocketTransport));
} else if (IsBackup(out)) {
rv = mBackupTransport.CheckConnectedResult(this);
if (!mBackupTransport.DoneConnecting()) {
return NS_OK;
}
MOZ_ASSERT((NS_SUCCEEDED(rv) &&
(mBackupTransport.mState ==
TransportSetup::TransportSetupState::CONNECTING_DONE) &&
mBackupTransport.mSocketTransport) ||
(NS_FAILED(rv) &&
(mBackupTransport.mState ==
TransportSetup::TransportSetupState::DONE) &&
!mBackupTransport.mSocketTransport));
} else {
MOZ_ASSERT(false, "unexpected stream");
return NS_ERROR_UNEXPECTED;
}
// Before calling SetupConn we need to hold a reference to this, i.e. a
// delete protector, because the corresponding ConnectionEntry may be
// abandoned and that will abandon this DnsAndConnectSocket.
rv = SetupConn(isPrimary, rv);
if (mState != DnsAndSocketState::DONE) {
// During a connection dispatch that will happen in SetupConn,
// a ConnectionEntry may be abandon and that will abandon this
// DnsAndConnectSocket. In that case mState will already be
// DnsAndSocketState::DONE and we do not need to set it again.
if (isPrimary) {
SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
} else {
SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
}
}
return rv;
}
nsresult DnsAndConnectSocket::SetupConn(bool isPrimary, nsresult status) {
// assign the new socket to the http connection
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
MOZ_DIAGNOSTIC_ASSERT(ent);
if (!ent) {
Abandon();
return NS_OK;
}
RefPtr<HttpConnectionBase> conn;
nsresult rv = NS_OK;
if (isPrimary) {
rv = mPrimaryTransport.SetupConn(mTransaction, ent, status, mCaps,
getter_AddRefs(conn));
} else {
rv = mBackupTransport.SetupConn(mTransaction, ent, status, mCaps,
getter_AddRefs(conn));
}
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
if (NS_FAILED(rv)) {
LOG(
("DnsAndConnectSocket::SetupConn "
"conn->init (%p) failed %" PRIx32 "\n",
conn.get(), static_cast<uint32_t>(rv)));
if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) {
if (mIsHttp3 && !mConnInfo->GetWebTransport()) {
trans->DisableHttp3(true);
gHttpHandler->ExcludeHttp3(mConnInfo);
}
// The transaction's connection info is changed after DisableHttp3(), so
// this is the only point we can remove this transaction from its conn
// entry.
ent->RemoveTransFromPendingQ(trans);
}
mTransaction->Close(rv);
return rv;
}
// This half-open socket has created a connection. This flag excludes it
// from counter of actual connections used for checking limits.
mHasConnected = true;
// if this is still in the pending list, remove it and dispatch it
RefPtr<PendingTransactionInfo> pendingTransInfo =
gHttpHandler->ConnMgr()->FindTransactionHelper(true, ent, mTransaction);
if (pendingTransInfo) {
MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
ent->InsertIntoActiveConns(conn);
if (mIsHttp3) {
// Each connection must have a ConnectionHandle wrapper.
// In case of Http < 2 the a ConnectionHandle is created for each
// transaction in DispatchAbstractTransaction.
// In case of Http2/ and Http3, ConnectionHandle is created only once.
// Http2 connection always starts as http1 connection and the first
// transaction use DispatchAbstractTransaction to be dispatched and
// a ConnectionHandle is created. All consecutive transactions for
// Http2 use a short-cut in DispatchTransaction and call
// HttpConnectionBase::Activate (DispatchAbstractTransaction is never
// called).
// In case of Http3 the short-cut HttpConnectionBase::Activate is always
// used also for the first transaction, therefore we need to create
// ConnectionHandle here.
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
pendingTransInfo->Transaction()->SetConnection(handle);
}
rv = gHttpHandler->ConnMgr()->DispatchTransaction(
ent, pendingTransInfo->Transaction(), conn);
} else {
// this transaction was dispatched off the pending q before all the
// sockets established themselves.
// After about 1 second allow for the possibility of restarting a
// transaction due to server close. Keep at sub 1 second as that is the
// minimum granularity we can expect a server to be timing out with.
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
if (connTCP) {
connTCP->SetIsReusedAfter(950);
}
// if we are using ssl and no other transactions are waiting right now,
// then form a null transaction to drive the SSL handshake to
// completion. Afterwards the connection will be 100% ready for the next
// transaction to use it. Make an exception for SSL tunneled HTTP proxy as
// the NullHttpTransaction does not know how to drive Connect
// Http3 cannot be dispatched using OnMsgReclaimConnection (see below),
// therefore we need to use a Nulltransaction.
if (!connTCP ||
(ent->mConnInfo->FirstHopSSL() && !ent->UrgentStartQueueLength() &&
!ent->PendingQueueLength() && !ent->mConnInfo->UsingConnect())) {
LOG(
("DnsAndConnectSocket::SetupConn null transaction will "
"be used to finish SSL handshake on conn %p\n",
conn.get()));
RefPtr<nsAHttpTransaction> trans;
if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
// null transactions cannot be put in the entry queue, so that
// explains why it is not present.
mDispatchedMTransaction = true;
trans = mTransaction;
} else {
trans = new NullHttpTransaction(mConnInfo, callbacks, mCaps);
}
ent->InsertIntoActiveConns(conn);
rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(ent, trans,
mCaps, conn, 0);
} else {
// otherwise just put this in the persistent connection pool
LOG(
("DnsAndConnectSocket::SetupConn no transaction match "
"returning conn %p to pool\n",
conn.get()));
gHttpHandler->ConnMgr()->OnMsgReclaimConnection(conn);
// We expect that there is at least one tranasction in the pending
// queue that can take this connection, but it can happened that
// all transactions are blocked or they have took other idle
// connections. In that case the connection has been added to the
// idle queue.
// If the connection is in the idle queue but it is using ssl, make
// a nulltransaction for it to finish ssl handshake!
if (ent->mConnInfo->FirstHopSSL() && !ent->mConnInfo->UsingConnect()) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
// If RemoveIdleConnection succeeds that means that conn is in the
// idle queue.
if (connTCP && NS_SUCCEEDED(ent->RemoveIdleConnection(connTCP))) {
RefPtr<nsAHttpTransaction> trans;
if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
mDispatchedMTransaction = true;
trans = mTransaction;
} else {
trans = new NullHttpTransaction(ent->mConnInfo, callbacks, mCaps);
}
ent->InsertIntoActiveConns(conn);
rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
ent, trans, mCaps, conn, 0);
}
}
}
}
// If this halfOpenConn was speculative, but at the end the conn got a
// non-null transaction than this halfOpen is not speculative anymore!
if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
Claim();
}
return rv;
}
// method for nsITransportEventSink
NS_IMETHODIMP
DnsAndConnectSocket::OnTransportStatus(nsITransport* trans, nsresult status,
int64_t progress, int64_t progressMax) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_ASSERT(IsPrimary(trans) || IsBackup(trans));
if (mTransaction) {
if (IsPrimary(trans) ||
(IsBackup(trans) && (status == NS_NET_STATUS_CONNECTED_TO) &&
mPrimaryTransport.mSocketTransport)) {
// Send this status event only if the transaction is still pending,
// i.e. it has not found a free already connected socket.
// Sockets in halfOpen state can only get following events:
// NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
// mBackupTransport is only started after
// NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore
// NS_NET_STATUS_CONNECTING_TO event for mBackupTransport and
// send NS_NET_STATUS_CONNECTED_TO.
// mBackupTransport must be connected before mSocketTransport(e.g.
// mPrimaryTransport.mSocketTransport != nullpttr).
mTransaction->OnTransportStatus(trans, status, progress);
}
}
if (status == NS_NET_STATUS_CONNECTED_TO) {
if (IsPrimary(trans)) {
mPrimaryTransport.mConnectedOK = true;
} else {
mBackupTransport.mConnectedOK = true;
}
}
// The rest of this method only applies to the primary transport
if (!IsPrimary(trans)) {
return NS_OK;
}
// if we are doing spdy coalescing and haven't recorded the ip address
// for this entry before then make the hash key if our dns lookup
// just completed. We can't do coalescing if using a proxy because the
// ip addresses are not available to the client.
nsCOMPtr<nsIDNSAddrRecord> dnsRecord(
do_GetInterface(mPrimaryTransport.mSocketTransport));
if (status == NS_NET_STATUS_CONNECTING_TO &&
StaticPrefs::network_http_http2_enabled() &&
StaticPrefs::network_http_http2_coalesce_hostnames()) {
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
MOZ_DIAGNOSTIC_ASSERT(ent);
if (ent) {
if (ent->MaybeProcessCoalescingKeys(dnsRecord)) {
gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
}
}
}
return NS_OK;
}
bool DnsAndConnectSocket::IsPrimary(nsITransport* trans) {
return trans == mPrimaryTransport.mSocketTransport;
}
bool DnsAndConnectSocket::IsPrimary(nsIAsyncOutputStream* out) {
return out == mPrimaryTransport.mStreamOut;
}
bool DnsAndConnectSocket::IsPrimary(nsICancelable* dnsRequest) {
return dnsRequest == mPrimaryTransport.mDNSRequest;
}
bool DnsAndConnectSocket::IsBackup(nsITransport* trans) {
return trans == mBackupTransport.mSocketTransport;
}
bool DnsAndConnectSocket::IsBackup(nsIAsyncOutputStream* out) {
return out == mBackupTransport.mStreamOut;
}
bool DnsAndConnectSocket::IsBackup(nsICancelable* dnsRequest) {
return dnsRequest == mBackupTransport.mDNSRequest;
}
// method for nsIInterfaceRequestor
NS_IMETHODIMP
DnsAndConnectSocket::GetInterface(const nsIID& iid, void** result) {
if (mTransaction) {
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
if (callbacks) {
return callbacks->GetInterface(iid, result);
}
}
return NS_ERROR_NO_INTERFACE;
}
bool DnsAndConnectSocket::AcceptsTransaction(nsHttpTransaction* trans) {
// When marked as urgent start, only accept urgent start marked transactions.
// Otherwise, accept any kind of transaction.
return !mUrgentStart || (trans->Caps() & nsIClassOfService::UrgentStart);
}
bool DnsAndConnectSocket::Claim() {
if (mSpeculative) {
mSpeculative = false;
mAllow1918 = true;
auto resetFlag = [](TransportSetup& transport) {
uint32_t flags;
if (transport.mSocketTransport &&
NS_SUCCEEDED(
transport.mSocketTransport->GetConnectionFlags(&flags))) {
flags &= ~nsISocketTransport::DISABLE_RFC1918;
flags &= ~nsISocketTransport::IS_SPECULATIVE_CONNECTION;
transport.mSocketTransport->SetConnectionFlags(flags);
}
};
resetFlag(mPrimaryTransport);
resetFlag(mBackupTransport);
Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN>
usedSpeculativeConn;
++usedSpeculativeConn;
if (mIsFromPredictor) {
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_USED>
totalPreconnectsUsed;
++totalPreconnectsUsed;
}
// Http3 has its own syn-retransmission, therefore it does not need a
// backup connection.
if (mPrimaryTransport.ConnectingOrRetry() &&
!mBackupTransport.mSocketTransport && !mSynTimer && !mIsHttp3) {
SetupBackupTimer();
}
}
if (mFreeToUse) {
mFreeToUse = false;
if (mPrimaryTransport.mSocketTransport) {
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
if (NS_SUCCEEDED(mPrimaryTransport.mSocketTransport->GetTlsSocketControl(
getter_AddRefs(tlsSocketControl))) &&
tlsSocketControl) {
Unused << tlsSocketControl->Claim();
}
}
return true;
}
return false;
}
void DnsAndConnectSocket::Unclaim() {
MOZ_ASSERT(!mSpeculative && !mFreeToUse);
// We will keep the backup-timer running. Most probably this halfOpen will
// be used by a transaction from which this transaction took the halfOpen.
// (this is happening because of the transaction priority.)
mFreeToUse = true;
}
void DnsAndConnectSocket::CloseTransports(nsresult error) {
if (mPrimaryTransport.mSocketTransport) {
mPrimaryTransport.mSocketTransport->Close(error);
}
if (mBackupTransport.mSocketTransport) {
mBackupTransport.mSocketTransport->Close(error);
}
}
DnsAndConnectSocket::TransportSetup::TransportSetup(bool isBackup)
: mState(TransportSetup::TransportSetupState::INIT), mIsBackup(isBackup) {}
nsresult DnsAndConnectSocket::TransportSetup::Init(
DnsAndConnectSocket* dnsAndSock) {
nsresult rv;
mSynStarted = TimeStamp::Now();
if (mSkipDnsResolution) {
mState = TransportSetup::TransportSetupState::CONNECTING;
rv = SetupStreams(dnsAndSock);
} else {
mState = TransportSetup::TransportSetupState::RESOLVING;
rv = ResolveHost(dnsAndSock);
}
if (NS_FAILED(rv)) {
CloseAll();
mState = TransportSetup::TransportSetupState::DONE;
}
return rv;
}
void DnsAndConnectSocket::TransportSetup::CancelDnsResolution() {
if (mDNSRequest) {
mDNSRequest->Cancel(NS_ERROR_ABORT);
mDNSRequest = nullptr;
}
if (mState == TransportSetup::TransportSetupState::RESOLVING) {
mState = TransportSetup::TransportSetupState::INIT;
}
}
void DnsAndConnectSocket::TransportSetup::Abandon() {
CloseAll();
mState = TransportSetup::TransportSetupState::DONE;
}
void DnsAndConnectSocket::TransportSetup::SetConnecting() {
MOZ_ASSERT(!mWaitingForConnect);
mWaitingForConnect = true;
gHttpHandler->ConnMgr()->StartedConnect();
}
void DnsAndConnectSocket::TransportSetup::MaybeSetConnectingDone() {
if (mWaitingForConnect) {
mWaitingForConnect = false;
gHttpHandler->ConnMgr()->RecvdConnect();
}
}
void DnsAndConnectSocket::TransportSetup::CloseAll() {
MaybeSetConnectingDone();
// Tell socket (and backup socket) to forget the half open socket.
if (mSocketTransport) {
mSocketTransport->SetEventSink(nullptr, nullptr);
mSocketTransport->SetSecurityCallbacks(nullptr);
mSocketTransport = nullptr;
}
// Tell output stream (and backup) to forget the half open socket.
if (mStreamOut) {
mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
mStreamOut = nullptr;
}
// Lose references to input stream (and backup).
if (mStreamIn) {
mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
mStreamIn = nullptr;
}
if (mDNSRequest) {
mDNSRequest->Cancel(NS_ERROR_ABORT);
mDNSRequest = nullptr;
}
mConnectedOK = false;
}
nsresult DnsAndConnectSocket::TransportSetup::CheckConnectedResult(
DnsAndConnectSocket* dnsAndSock) {
mState = TransportSetup::TransportSetupState::CONNECTING_DONE;
MaybeSetConnectingDone();
if (mSkipDnsResolution) {
return NS_OK;
}
bool retryDns = false;
mSocketTransport->GetRetryDnsIfPossible(&retryDns);
if (!retryDns) {
return NS_OK;
}
bool retry = false;
if (mRetryWithDifferentIPFamily) {
mRetryWithDifferentIPFamily = false;
mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
nsIDNSService::RESOLVE_DISABLE_IPV4);
mResetFamilyPreference = true;
retry = true;
} else if (!(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_TRR)) {
bool trrEnabled;
mDNSRecord->IsTRR(&trrEnabled);
if (trrEnabled) {
nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
mDNSRecord->GetEffectiveTRRMode(&trrMode);
// If current trr mode is trr only, we should not retry.
if (trrMode != nsIRequest::TRR_ONLY_MODE) {
// Drop state to closed. This will trigger a new round of
// DNS resolving. Bypass the cache this time since the
// cached data came from TRR and failed already!
LOG((" failed to connect with TRR enabled, try w/o\n"));
mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR |
nsIDNSService::RESOLVE_BYPASS_CACHE |
nsIDNSService::RESOLVE_REFRESH_CACHE;
retry = true;
}
}
}
if (retry) {
CloseAll();
mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
nsresult rv = ResolveHost(dnsAndSock);
if (NS_FAILED(rv)) {
CloseAll();
mState = TransportSetup::TransportSetupState::DONE;
}
return rv;
}
return NS_OK;
}
nsresult DnsAndConnectSocket::TransportSetup::SetupConn(
nsAHttpTransaction* transaction, ConnectionEntry* ent, nsresult status,
uint32_t cap, HttpConnectionBase** connection) {
RefPtr<HttpConnectionBase> conn;
if (!ent->mConnInfo->IsHttp3()) {
conn = new nsHttpConnection();
} else {
conn = new HttpConnectionUDP();
}
NotifyActivity(ent->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_CONNECTION_CREATED);
LOG(
("DnsAndConnectSocket::SocketTransport::SetupConn "
"Created new nshttpconnection %p %s\n",
conn.get(), ent->mConnInfo->IsHttp3() ? "using http3" : ""));
NullHttpTransaction* nullTrans = transaction->QueryNullTransaction();
if (nullTrans) {
conn->BootstrapTimings(nullTrans->Timings());
}
// Some capabilities are needed before a transaction actually gets
// scheduled (e.g. how to negotiate false start)
conn->SetTransactionCaps(transaction->Caps());
nsCOMPtr<nsIInterfaceRequestor> callbacks;
transaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
nsresult rv = NS_OK;
if (!ent->mConnInfo->IsHttp3()) {
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
rv =
connTCP->Init(ent->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
mSocketTransport, mStreamIn, mStreamOut, mConnectedOK,
status, callbacks,
PR_MillisecondsToInterval(static_cast<uint32_t>(
(TimeStamp::Now() - mSynStarted).ToMilliseconds())),
cap & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE);
} else {
RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn);
rv = connUDP->Init(ent->mConnInfo, mDNSRecord, status, callbacks, cap);
if (NS_SUCCEEDED(rv)) {
if (nsHttpHandler::IsHttp3Enabled() &&
StaticPrefs::network_http_http2_coalesce_hostnames()) {
if (ent->MaybeProcessCoalescingKeys(mDNSRecord, true)) {
gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
}
}
}
}
bool resetPreference = false;
if (mResetFamilyPreference ||
(mSocketTransport &&
NS_SUCCEEDED(
mSocketTransport->GetResetIPFamilyPreference(&resetPreference)) &&
resetPreference)) {
ent->ResetIPFamilyPreference();
}
NetAddr peeraddr;
if (mSocketTransport &&
NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
ent->RecordIPFamilyPreference(peeraddr.raw.family);
}
conn.forget(connection);
mSocketTransport = nullptr;
mStreamOut = nullptr;
mStreamIn = nullptr;
mState = TransportSetup::TransportSetupState::DONE;
return rv;
}
nsresult DnsAndConnectSocket::TransportSetup::SetupStreams(
DnsAndConnectSocket* dnsAndSock) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
nsresult rv;
nsTArray<nsCString> socketTypes;
const nsHttpConnectionInfo* ci = dnsAndSock->mConnInfo;
if (dnsAndSock->mIsHttp3) {
socketTypes.AppendElement("quic"_ns);
} else {
if (ci->FirstHopSSL()) {
socketTypes.AppendElement("ssl"_ns);
} else {
const nsCString& defaultType = gHttpHandler->DefaultSocketType();
if (!defaultType.IsVoid()) {
socketTypes.AppendElement(defaultType);
}
}
}
nsCOMPtr<nsISocketTransport> socketTransport;
nsCOMPtr<nsISocketTransportService> sts;
sts = components::SocketTransport::Service();
if (!sts) {
return NS_ERROR_NOT_AVAILABLE;
}
LOG(
("DnsAndConnectSocket::SetupStreams [this=%p ent=%s] "
"setup routed transport to origin %s:%d via %s:%d\n",
this, ci->HashKey().get(), ci->Origin(), ci->OriginPort(),
ci->RoutedHost(), ci->RoutedPort()));
nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
if (routedSTS) {
rv = routedSTS->CreateRoutedTransport(
socketTypes, ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(),
ci->RoutedPort(), ci->ProxyInfo(), mDNSRecord,
getter_AddRefs(socketTransport));
} else {
if (!ci->GetRoutedHost().IsEmpty()) {
// There is a route requested, but the legacy nsISocketTransportService
// can't handle it.
// Origin should be reachable on origin host name, so this should
// not be a problem - but log it.
LOG(
("DnsAndConnectSocket this=%p using legacy nsISocketTransportService "
"means explicit route %s:%d will be ignored.\n",
this, ci->RoutedHost(), ci->RoutedPort()));
}
rv = sts->CreateTransport(socketTypes, ci->GetOrigin(), ci->OriginPort(),
ci->ProxyInfo(), mDNSRecord,
getter_AddRefs(socketTransport));
}
NS_ENSURE_SUCCESS(rv, rv);
uint32_t tmpFlags = 0;
if (dnsAndSock->mCaps & NS_HTTP_REFRESH_DNS) {
tmpFlags = nsISocketTransport::BYPASS_CACHE;
}
tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
NS_HTTP_TRR_MODE_FROM_FLAGS(dnsAndSock->mCaps));
if (dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS) {
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
}
// When we are making a speculative connection we do not propagate all flags
// in mCaps, so we need to query nsHttpConnectionInfo directly as well.
if ((dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) ||
ci->GetAnonymousAllowClientCert()) {
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
}
if (ci->GetPrivate()) {
tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
}
Unused << socketTransport->SetIsPrivate(ci->GetPrivate());
if (dnsAndSock->mCaps & NS_HTTP_DISALLOW_ECH) {
tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
}
if (dnsAndSock->mCaps & NS_HTTP_IS_RETRY) {
tmpFlags |= nsISocketTransport::IS_RETRY;
}
if (((dnsAndSock->mCaps & NS_HTTP_BE_CONSERVATIVE) ||
ci->GetBeConservative()) &&
gHttpHandler->ConnMgr()->BeConservativeIfProxied(ci->ProxyInfo())) {
LOG(("Setting Socket to BE_CONSERVATIVE"));
tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
}
if (ci->HasIPHintAddress()) {
nsCOMPtr<nsIDNSService> dns =
do_GetService("@mozilla.org/network/dns-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
// The spec says: "If A and AAAA records for TargetName are locally
// available, the client SHOULD ignore these hints.", so we check if the DNS
// record is in cache before setting USE_IP_HINT_ADDRESS.
nsCOMPtr<nsIDNSRecord> record;
rv = dns->ResolveNative(mHost, nsIDNSService::RESOLVE_OFFLINE,
dnsAndSock->mConnInfo->GetOriginAttributes(),
getter_AddRefs(record));
if (NS_FAILED(rv) || !record) {
LOG(("Setting Socket to use IP hint address"));
tmpFlags |= nsISocketTransport::USE_IP_HINT_ADDRESS;
}
}
if (mRetryWithDifferentIPFamily) {
// From the same reason, let the backup socket fail faster to try the other
// family.
uint16_t fallbackTimeout =
mIsBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
if (fallbackTimeout) {
socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
fallbackTimeout);
}
}
if (!dnsAndSock->Allow1918()) {
tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
}
if (dnsAndSock->mSpeculative) {
tmpFlags |= nsISocketTransport::IS_SPECULATIVE_CONNECTION;
}
socketTransport->SetConnectionFlags(tmpFlags);
socketTransport->SetTlsFlags(ci->GetTlsFlags());
const OriginAttributes& originAttributes =
dnsAndSock->mConnInfo->GetOriginAttributes();
if (originAttributes != OriginAttributes()) {
socketTransport->SetOriginAttributes(originAttributes);
}
socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
rv = socketTransport->SetEventSink(dnsAndSock, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
rv = socketTransport->SetSecurityCallbacks(dnsAndSock);
NS_ENSURE_SUCCESS(rv, rv);
if (gHttpHandler->EchConfigEnabled() && !ci->GetEchConfig().IsEmpty()) {
MOZ_ASSERT(!ci->IsHttp3());
LOG(("Setting ECH"));
rv = socketTransport->SetEchConfig(ci->GetEchConfig());
NS_ENSURE_SUCCESS(rv, rv);
NotifyActivity(dnsAndSock->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET);
}
RefPtr<ConnectionEntry> ent =
gHttpHandler->ConnMgr()->FindConnectionEntry(ci);
MOZ_DIAGNOSTIC_ASSERT(ent);
if (ent) {
Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
ent->mUsedForConnection);
ent->mUsedForConnection = true;
}
nsCOMPtr<nsIOutputStream> sout;
rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
getter_AddRefs(sout));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> sin;
rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
getter_AddRefs(sin));
NS_ENSURE_SUCCESS(rv, rv);
mSocketTransport = socketTransport.forget();
mStreamIn = do_QueryInterface(sin);
mStreamOut = do_QueryInterface(sout);
rv = mStreamOut->AsyncWait(dnsAndSock, 0, 0, nullptr);
if (NS_SUCCEEDED(rv)) {
SetConnecting();
}
return rv;
}
nsresult DnsAndConnectSocket::TransportSetup::ResolveHost(
DnsAndConnectSocket* dnsAndSock) {
MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
LOG(("DnsAndConnectSocket::TransportSetup::ResolveHost [this=%p %s%s]", this,
PromiseFlatCString(mHost).get(),
(mDnsFlags & nsIDNSService::RESOLVE_BYPASS_CACHE) ? " bypass cache"
: ""));
nsCOMPtr<nsIDNSService> dns = GetOrInitDNSService();
if (!dns) {
return NS_ERROR_FAILURE;
}
if (!mIsBackup) {
dnsAndSock->mTransaction->OnTransportStatus(
nullptr, NS_NET_STATUS_RESOLVING_HOST, 0);
}
nsresult rv = NS_OK;
do {
rv = dns->AsyncResolveNative(
mHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
mDnsFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, nullptr,
dnsAndSock, gSocketTransportService,
dnsAndSock->mConnInfo->GetOriginAttributes(),
getter_AddRefs(mDNSRequest));
} while (NS_FAILED(rv) && ShouldRetryDNS());
if (NS_FAILED(rv)) {
mDNSRequest = nullptr;
}
return rv;
}
bool DnsAndConnectSocket::TransportSetup::ShouldRetryDNS() {
if (mDnsFlags & nsIDNSService::RESOLVE_IP_HINT) {
mDnsFlags &= ~nsIDNSService::RESOLVE_IP_HINT;
return true;
}
if (mRetryWithDifferentIPFamily) {
mRetryWithDifferentIPFamily = false;
mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
nsIDNSService::RESOLVE_DISABLE_IPV4);
mResetFamilyPreference = true;
return true;
}
return false;
}
nsresult DnsAndConnectSocket::TransportSetup::OnLookupComplete(
DnsAndConnectSocket* dnsAndSock, nsIDNSRecord* rec, nsresult status) {
mDNSRequest = nullptr;
if (NS_SUCCEEDED(status)) {
mDNSRecord = do_QueryInterface(rec);
MOZ_ASSERT(mDNSRecord);
if (dnsAndSock->mConnInfo->IsHttp3()) {
mState = TransportSetup::TransportSetupState::RESOLVED;
return status;
}
nsresult rv = SetupStreams(dnsAndSock);
if (NS_SUCCEEDED(rv)) {
mState = TransportSetup::TransportSetupState::CONNECTING;
} else {
CloseAll();
mState = TransportSetup::TransportSetupState::DONE;
}
return rv;
}
// DNS lookup status failed
if (ShouldRetryDNS()) {
mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
nsresult rv = ResolveHost(dnsAndSock);
if (NS_FAILED(rv)) {
CloseAll();
mState = TransportSetup::TransportSetupState::DONE;
}
return rv;
}
mState = TransportSetup::TransportSetupState::DONE;
return status;
}
} // namespace net
} // namespace mozilla