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();
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();
}
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;
dns = mozilla::components::DNS::Service(&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;