diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/http/DnsAndConnectSocket.cpp | 1391 |
1 files changed, 1391 insertions, 0 deletions
diff --git a/netwerk/protocol/http/DnsAndConnectSocket.cpp b/netwerk/protocol/http/DnsAndConnectSocket.cpp new file mode 100644 index 0000000000..8895ac97e1 --- /dev/null +++ b/netwerk/protocol/http/DnsAndConnectSocket.cpp @@ -0,0 +1,1391 @@ +/* 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 |