diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /netwerk/protocol/http/HalfOpenSocket.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/protocol/http/HalfOpenSocket.cpp')
-rw-r--r-- | netwerk/protocol/http/HalfOpenSocket.cpp | 1307 |
1 files changed, 1307 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HalfOpenSocket.cpp b/netwerk/protocol/http/HalfOpenSocket.cpp new file mode 100644 index 0000000000..815f2f41c4 --- /dev/null +++ b/netwerk/protocol/http/HalfOpenSocket.cpp @@ -0,0 +1,1307 @@ +/* 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 "HalfOpenSocket.h" +#include "TCPFastOpenLayer.h" +#include "nsHttpConnection.h" +#include "nsIDNSRecord.h" +#include "nsIDNSService.h" +#include "nsQueryObject.h" + +// 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 { + +//////////////////////// HalfOpenSocket +NS_IMPL_ADDREF(HalfOpenSocket) +NS_IMPL_RELEASE(HalfOpenSocket) + +NS_INTERFACE_MAP_BEGIN(HalfOpenSocket) + 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_CONCRETE(HalfOpenSocket) +NS_INTERFACE_MAP_END + +HalfOpenSocket::HalfOpenSocket(ConnectionEntry* ent, nsAHttpTransaction* trans, + uint32_t caps, bool speculative, + bool isFromPredictor, bool urgentStart) + : mTransaction(trans), + mDispatchedMTransaction(false), + mCaps(caps), + mSpeculative(speculative), + mUrgentStart(urgentStart), + mIsFromPredictor(isFromPredictor), + mAllow1918(true), + mHasConnected(false), + mPrimaryConnectedOK(false), + mBackupConnectedOK(false), + mBackupConnStatsSet(false), + mFreeToUse(true), + mPrimaryStreamStatus(NS_OK), + mFastOpenInProgress(false), + mEnt(ent) { + MOZ_ASSERT(ent && trans, "constructor with null arguments"); + LOG(("Creating HalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n", this, + trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get())); + + mIsHttp3 = mEnt->mConnInfo->IsHttp3(); + if (speculative) { + Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> + totalSpeculativeConn; + ++totalSpeculativeConn; + + if (isFromPredictor) { + Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED> + totalPreconnectsCreated; + ++totalPreconnectsCreated; + } + } + + if (mEnt->mConnInfo->FirstHopSSL()) { + mFastOpenStatus = TFO_UNKNOWN; + } else { + mFastOpenStatus = TFO_HTTP; + } + MOZ_ASSERT(mEnt); +} + +HalfOpenSocket::~HalfOpenSocket() { + MOZ_ASSERT(!mStreamOut); + MOZ_ASSERT(!mBackupStreamOut); + LOG(("Destroying HalfOpenSocket [this=%p]\n", this)); + + if (mEnt) { + bool inqueue = mEnt->RemoveHalfOpen(this); + LOG(("Destroying HalfOpenSocket was in the HalfOpenList=%d [this=%p]\n", + inqueue, this)); + } +} + +nsresult HalfOpenSocket::SetupStreams(nsISocketTransport** transport, + nsIAsyncInputStream** instream, + nsIAsyncOutputStream** outstream, + bool isBackup) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MOZ_ASSERT(mEnt); + nsresult rv; + nsTArray<nsCString> socketTypes; + const nsHttpConnectionInfo* ci = mEnt->mConnInfo; + if (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 = services::GetSocketTransportService(); + if (!sts) { + return NS_ERROR_NOT_AVAILABLE; + } + + LOG( + ("HalfOpenSocket::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(), 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( + ("HalfOpenSocket 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(), getter_AddRefs(socketTransport)); + } + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t tmpFlags = 0; + if (mCaps & NS_HTTP_REFRESH_DNS) { + tmpFlags = nsISocketTransport::BYPASS_CACHE; + } + + tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode( + NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps)); + + if (mCaps & NS_HTTP_LOAD_ANONYMOUS) { + tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT; + } + + if (ci->GetPrivate() || ci->GetIsolated()) { + tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE; + } + + Unused << socketTransport->SetIsPrivate(ci->GetPrivate()); + + if (ci->GetLessThanTls13()) { + tmpFlags |= nsISocketTransport::DONT_TRY_ECH; + } + + if (((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); + 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(ci->GetRoutedHost(), nsIDNSService::RESOLVE_OFFLINE, + mEnt->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 (mCaps & NS_HTTP_DISABLE_IPV4) { + tmpFlags |= nsISocketTransport::DISABLE_IPV4; + } else if (mCaps & NS_HTTP_DISABLE_IPV6) { + tmpFlags |= nsISocketTransport::DISABLE_IPV6; + } else if (mEnt->PreferenceKnown()) { + if (mEnt->mPreferIPv6) { + tmpFlags |= nsISocketTransport::DISABLE_IPV4; + } else if (mEnt->mPreferIPv4) { + tmpFlags |= nsISocketTransport::DISABLE_IPV6; + } + + // In case the host is no longer accessible via the preferred IP family, + // try the opposite one and potentially restate the preference. + tmpFlags |= nsISocketTransport::RETRY_WITH_DIFFERENT_IP_FAMILY; + + // From the same reason, let the backup socket fail faster to try the other + // family. + uint16_t fallbackTimeout = + isBackup ? gHttpHandler->GetFallbackSynTimeout() : 0; + if (fallbackTimeout) { + socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, + fallbackTimeout); + } + } else if (isBackup && 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". + tmpFlags |= nsISocketTransport::DISABLE_IPV6; + } + + if (!Allow1918()) { + tmpFlags |= nsISocketTransport::DISABLE_RFC1918; + } + + if ((mFastOpenStatus != TFO_HTTP) && !isBackup) { + if (mEnt->mUseFastOpen) { + socketTransport->SetFastOpenCallback(this); + } else { + mFastOpenStatus = TFO_DISABLED; + } + } + + MOZ_ASSERT(!(tmpFlags & nsISocketTransport::DISABLE_IPV4) || + !(tmpFlags & nsISocketTransport::DISABLE_IPV6), + "Both types should not be disabled at the same time."); + socketTransport->SetConnectionFlags(tmpFlags); + socketTransport->SetTlsFlags(ci->GetTlsFlags()); + + const OriginAttributes& originAttributes = + mEnt->mConnInfo->GetOriginAttributes(); + if (originAttributes != OriginAttributes()) { + socketTransport->SetOriginAttributes(originAttributes); + } + + socketTransport->SetQoSBits(gHttpHandler->GetQoSBits()); + + rv = socketTransport->SetEventSink(this, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = socketTransport->SetSecurityCallbacks(this); + NS_ENSURE_SUCCESS(rv, rv); + + if (gHttpHandler->EchConfigEnabled()) { + rv = socketTransport->SetEchConfig(ci->GetEchConfig()); + NS_ENSURE_SUCCESS(rv, rv); + } + + Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1, + mEnt->mUsedForConnection); + mEnt->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); + + socketTransport.forget(transport); + CallQueryInterface(sin, instream); + CallQueryInterface(sout, outstream); + + rv = (*outstream)->AsyncWait(this, 0, 0, nullptr); + if (NS_SUCCEEDED(rv)) { + gHttpHandler->ConnMgr()->StartedConnect(); + } + + return rv; +} + +nsresult HalfOpenSocket::SetupPrimaryStreams() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsresult rv; + + mPrimarySynStarted = TimeStamp::Now(); + rv = SetupStreams(getter_AddRefs(mSocketTransport), getter_AddRefs(mStreamIn), + getter_AddRefs(mStreamOut), false); + + LOG(("HalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%" PRIx32 "]", + this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv))); + if (NS_FAILED(rv)) { + if (mStreamOut) { + mStreamOut->AsyncWait(nullptr, 0, 0, nullptr); + } + if (mSocketTransport) { + mSocketTransport->SetFastOpenCallback(nullptr); + } + mStreamOut = nullptr; + mStreamIn = nullptr; + mSocketTransport = nullptr; + } + return rv; +} + +nsresult HalfOpenSocket::SetupBackupStreams() { + MOZ_ASSERT(mTransaction); + + mBackupSynStarted = TimeStamp::Now(); + nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport), + getter_AddRefs(mBackupStreamIn), + getter_AddRefs(mBackupStreamOut), true); + + LOG(("HalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%" PRIx32 "]", + this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv))); + if (NS_FAILED(rv)) { + if (mBackupStreamOut) { + mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr); + } + mBackupStreamOut = nullptr; + mBackupStreamIn = nullptr; + mBackupTransport = nullptr; + } + return rv; +} + +void HalfOpenSocket::SetupBackupTimer() { + MOZ_ASSERT(mEnt); + uint16_t timeout = gHttpHandler->GetIdleSynTimeout(); + MOZ_ASSERT(!mSynTimer, "timer already initd"); + if (!timeout && mFastOpenInProgress) { + timeout = 250; + } + // 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 (mFastOpenInProgress || (timeout && !mSpeculative)) { + // 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(("HalfOpenSocket::SetupBackupTimer() [this=%p]", this)); + } else if (timeout) { + LOG(("HalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", this)); + } +} + +void HalfOpenSocket::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(("HalfOpenSocket::CancelBackupTimer()")); + mSynTimer->Cancel(); + + // Keeping the reference to the timer to remember we have already + // performed the backup connection. +} + +void HalfOpenSocket::Abandon() { + LOG(("HalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p", this, + mEnt->mConnInfo->Origin(), mSocketTransport.get(), + mBackupTransport.get(), mStreamOut.get(), mBackupStreamOut.get())); + + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + RefPtr<HalfOpenSocket> deleteProtector(this); + + // Tell socket (and backup socket) to forget the half open socket. + if (mSocketTransport) { + mSocketTransport->SetEventSink(nullptr, nullptr); + mSocketTransport->SetSecurityCallbacks(nullptr); + mSocketTransport->SetFastOpenCallback(nullptr); + mSocketTransport = nullptr; + } + if (mBackupTransport) { + mBackupTransport->SetEventSink(nullptr, nullptr); + mBackupTransport->SetSecurityCallbacks(nullptr); + mBackupTransport = nullptr; + } + + // Tell output stream (and backup) to forget the half open socket. + if (mStreamOut) { + if (!mFastOpenInProgress) { + // If mFastOpenInProgress is true HalfOpen are not in mHalfOpen + // list and are not counted so we do not need to decrease counter. + gHttpHandler->ConnMgr()->RecvdConnect(); + } + mStreamOut->AsyncWait(nullptr, 0, 0, nullptr); + mStreamOut = nullptr; + } + if (mBackupStreamOut) { + gHttpHandler->ConnMgr()->RecvdConnect(); + mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr); + mBackupStreamOut = nullptr; + } + + // Lose references to input stream (and backup). + if (mStreamIn) { + mStreamIn->AsyncWait(nullptr, 0, 0, nullptr); + mStreamIn = nullptr; + } + if (mBackupStreamIn) { + mBackupStreamIn->AsyncWait(nullptr, 0, 0, nullptr); + mBackupStreamIn = nullptr; + } + + // Stop the timer - we don't want any new backups. + CancelBackupTimer(); + + // Remove the half open from the connection entry. + if (mEnt) { + mEnt->mDoNotDestroy = false; + mEnt->RemoveHalfOpen(this); + } + mEnt = nullptr; +} + +double HalfOpenSocket::Duration(TimeStamp epoch) { + if (mPrimarySynStarted.IsNull()) { + return 0; + } + + return (epoch - mPrimarySynStarted).ToMilliseconds(); +} + +NS_IMETHODIMP // method for nsITimerCallback +HalfOpenSocket::Notify(nsITimer* timer) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(timer == mSynTimer, "wrong timer"); + + MOZ_ASSERT(!mBackupTransport); + MOZ_ASSERT(mSynTimer); + MOZ_ASSERT(mEnt); + + DebugOnly<nsresult> rv = SetupBackupStreams(); + 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 +HalfOpenSocket::GetName(nsACString& aName) { + aName.AssignLiteral("HalfOpenSocket"); + return NS_OK; +} + +// method for nsIAsyncOutputStreamCallback +NS_IMETHODIMP +HalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mStreamOut || mBackupStreamOut); + MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut, "stream mismatch"); + MOZ_ASSERT(mEnt); + + LOG(("HalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this, + mEnt->mConnInfo->Origin(), out == mStreamOut ? "primary" : "backup")); + + mEnt->mDoNotDestroy = true; + gHttpHandler->ConnMgr()->RecvdConnect(); + + CancelBackupTimer(); + + if (mFastOpenInProgress) { + LOG( + ("HalfOpenSocket::OnOutputStreamReady backup stream is ready, " + "close the fast open socket %p [this=%p ent=%s]\n", + mSocketTransport.get(), this, mEnt->mConnInfo->Origin())); + // If fast open is used, right after a socket for the primary stream is + // created a HttpConnectionBase is created for that socket. The connection + // listens for OnOutputStreamReady not HalfOpenSocket. So this stream + // cannot be mStreamOut. + MOZ_ASSERT((out == mBackupStreamOut) && mConnectionNegotiatingFastOpen); + // Here the backup, non-TFO connection has connected successfully, + // before the TFO connection. + // + // The primary, TFO connection will be cancelled and the transaction + // will be rewind. CloseConnectionFastOpenTakesTooLongOrError will + // return the rewind transaction. The transaction will be put back to + // the pending queue and as well connected to this halfOpenSocket. + // SetupConn should set up a new HttpConnectionBase with the backup + // socketTransport and the rewind transaction. + mSocketTransport->SetFastOpenCallback(nullptr); + mConnectionNegotiatingFastOpen->SetFastOpen(false); + mEnt->RemoveHalfOpenFastOpenBackups(this); + RefPtr<nsAHttpTransaction> trans = + mConnectionNegotiatingFastOpen + ->CloseConnectionFastOpenTakesTooLongOrError(true); + mSocketTransport = nullptr; + mStreamOut = nullptr; + mStreamIn = nullptr; + + if (trans && trans->QueryHttpTransaction()) { + RefPtr<PendingTransactionInfo> pendingTransInfo = + new PendingTransactionInfo(trans->QueryHttpTransaction()); + pendingTransInfo->AddHalfOpen(this); + mEnt->InsertTransaction(pendingTransInfo, true); + } + if (mEnt->mUseFastOpen) { + gHttpHandler->IncrementFastOpenConsecutiveFailureCounter(); + mEnt->mUseFastOpen = false; + } + + mFastOpenInProgress = false; + mConnectionNegotiatingFastOpen = nullptr; + if (mFastOpenStatus == TFO_NOT_TRIED) { + mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_NOT_TRIED; + } else if (mFastOpenStatus == TFO_TRIED) { + mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_TRIED; + } else if (mFastOpenStatus == TFO_DATA_SENT) { + mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_SENT; + } else { + // This is TFO_DATA_COOKIE_NOT_ACCEPTED (I think this cannot + // happened, because the primary connection will be already + // connected or in recovery and mFastOpenInProgress==false). + mFastOpenStatus = + TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED; + } + } + + if (((mFastOpenStatus == TFO_DISABLED) || (mFastOpenStatus == TFO_HTTP)) && + !mBackupConnStatsSet) { + // Collect telemetry for backup connection being faster than primary + // connection. We want to collect this telemetry only for cases where + // TFO is not used. + mBackupConnStatsSet = true; + Telemetry::Accumulate(Telemetry::NETWORK_HTTP_BACKUP_CONN_WON_1, + (out == mBackupStreamOut)); + } + + if (mFastOpenStatus == TFO_UNKNOWN) { + MOZ_ASSERT(out == mStreamOut); + if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVING_HOST) { + mFastOpenStatus = TFO_UNKNOWN_RESOLVING; + } else if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVED_HOST) { + mFastOpenStatus = TFO_UNKNOWN_RESOLVED; + } else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) { + mFastOpenStatus = TFO_UNKNOWN_CONNECTING; + } else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTED_TO) { + mFastOpenStatus = TFO_UNKNOWN_CONNECTED; + } + } + nsresult rv = SetupConn(out, false); + if (mEnt) { + mEnt->mDoNotDestroy = false; + } + return rv; +} + +bool HalfOpenSocket::FastOpenEnabled() { + LOG(("HalfOpenSocket::FastOpenEnabled [this=%p]\n", this)); + + MOZ_ASSERT(mEnt); + + if (!mEnt) { + return false; + } + + MOZ_ASSERT(mEnt->mConnInfo->FirstHopSSL()); + + // If mEnt is present this HalfOpen must be in the mHalfOpens, + // but we want to be sure!!! + if (!mEnt->IsInHalfOpens(this)) { + return false; + } + + if (!gHttpHandler->UseFastOpen()) { + // fast open was turned off. + LOG(("HalfOpenSocket::FastEnabled - fast open was turned off.\n")); + mEnt->mUseFastOpen = false; + mFastOpenStatus = TFO_DISABLED; + return false; + } + // We can use FastOpen if we have a transaction or if it is ssl + // connection. For ssl we will use a null transaction to drive the SSL + // handshake to completion if there is not a pending transaction. 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. + if (mEnt->mConnInfo->UsingConnect()) { + LOG(("HalfOpenSocket::FastOpenEnabled - It is using Connect.")); + mFastOpenStatus = TFO_DISABLED_CONNECT; + return false; + } + return true; +} + +nsresult HalfOpenSocket::StartFastOpen() { + MOZ_ASSERT(mStreamOut); + MOZ_ASSERT(!mBackupTransport); + MOZ_ASSERT(mEnt); + MOZ_ASSERT(mFastOpenStatus == TFO_UNKNOWN); + + LOG(("HalfOpenSocket::StartFastOpen [this=%p]\n", this)); + + RefPtr<HalfOpenSocket> deleteProtector(this); + + mFastOpenInProgress = true; + mEnt->mDoNotDestroy = true; + // Remove this HalfOpen from mEnt->mHalfOpens. + // The new connection will take care of closing this HalfOpen from now on! + if (!mEnt->RemoveHalfOpen(this)) { + MOZ_ASSERT(false, "HalfOpen is not in mHalfOpens!"); + mSocketTransport->SetFastOpenCallback(nullptr); + CancelBackupTimer(); + mFastOpenInProgress = false; + Abandon(); + mFastOpenStatus = TFO_INIT_FAILED; + return NS_ERROR_ABORT; + } + + gHttpHandler->ConnMgr()->DecreaseNumHalfOpenConns(); + + // Count this socketTransport as connected. + gHttpHandler->ConnMgr()->RecvdConnect(); + + // Remove HalfOpen from callbacks, the new connection will take them. + mSocketTransport->SetEventSink(nullptr, nullptr); + mSocketTransport->SetSecurityCallbacks(nullptr); + mStreamOut->AsyncWait(nullptr, 0, 0, nullptr); + + nsresult rv = SetupConn(mStreamOut, true); + if (!mConnectionNegotiatingFastOpen) { + LOG( + ("HalfOpenSocket::StartFastOpen SetupConn failed " + "[this=%p rv=%x]\n", + this, static_cast<uint32_t>(rv))); + if (NS_SUCCEEDED(rv)) { + rv = NS_ERROR_ABORT; + } + // If SetupConn failed this will CloseTransaction and socketTransport + // with an error, therefore we can close this HalfOpen. socketTransport + // will remove reference to this HalfOpen as well. + mSocketTransport->SetFastOpenCallback(nullptr); + CancelBackupTimer(); + mFastOpenInProgress = false; + + // The connection is responsible to take care of the halfOpen so we + // need to clean it up. + Abandon(); + mFastOpenStatus = TFO_INIT_FAILED; + } else { + LOG(("HalfOpenSocket::StartFastOpen [this=%p conn=%p]\n", this, + mConnectionNegotiatingFastOpen.get())); + + mEnt->InsertIntoHalfOpenFastOpenBackups(this); + // SetupBackupTimer should setup timer which will hold a ref to this + // halfOpen. It will failed only if it cannot create timer. Anyway just + // to be sure I will add this deleteProtector!!! + if (!mSynTimer) { + // For Fast Open we will setup backup timer also for + // NullTransaction. + // So maybe it is not set and we need to set it here. + SetupBackupTimer(); + } + } + if (mEnt) { + mEnt->mDoNotDestroy = false; + } + return rv; +} + +void HalfOpenSocket::SetFastOpenConnected(nsresult aError, bool aWillRetry) { + MOZ_ASSERT(mFastOpenInProgress); + MOZ_ASSERT(mEnt); + + LOG(("HalfOpenSocket::SetFastOpenConnected [this=%p conn=%p error=%x]\n", + this, mConnectionNegotiatingFastOpen.get(), + static_cast<uint32_t>(aError))); + + // mConnectionNegotiatingFastOpen is set after a StartFastOpen creates + // and activates a HttpConnectionBase successfully (SetupConn calls + // DispatchTransaction and DispatchAbstractTransaction which calls + // conn->Activate). + // HttpConnectionBase::Activate can fail which will close socketTransport + // and socketTransport will call this function. The FastOpen clean up + // in case HttpConnectionBase::Activate fails will be done in StartFastOpen. + // Also OnMsgReclaimConnection can decided that we do not need this + // transaction and cancel it as well. + // In all other cases mConnectionNegotiatingFastOpen must not be nullptr. + if (!mConnectionNegotiatingFastOpen) { + return; + } + + MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) || + (mFastOpenStatus == TFO_DATA_SENT) || + (mFastOpenStatus == TFO_TRIED) || + (mFastOpenStatus == TFO_DATA_COOKIE_NOT_ACCEPTED) || + (mFastOpenStatus == TFO_DISABLED)); + + RefPtr<HalfOpenSocket> deleteProtector(this); + + mEnt->mDoNotDestroy = true; + + // Delete 2 points of entry to FastOpen function so that we do not reenter. + mEnt->RemoveHalfOpenFastOpenBackups(this); + mSocketTransport->SetFastOpenCallback(nullptr); + + mConnectionNegotiatingFastOpen->SetFastOpen(false); + + // Check if we want to restart connection! + if (aWillRetry && ((aError == NS_ERROR_CONNECTION_REFUSED) || +#if defined(_WIN64) && defined(WIN95) + // On Windows PR_ContinueConnect can return + // NS_ERROR_FAILURE. This will be fixed in bug 1386719 and + // this is just a temporary work around. + (aError == NS_ERROR_FAILURE) || +#endif + (aError == NS_ERROR_PROXY_CONNECTION_REFUSED) || + (aError == NS_ERROR_NET_TIMEOUT))) { + if (mEnt->mUseFastOpen) { + gHttpHandler->IncrementFastOpenConsecutiveFailureCounter(); + mEnt->mUseFastOpen = false; + } + // This is called from nsSocketTransport::RecoverFromError. The + // socket will try connect and we need to rewind nsHttpTransaction. + + RefPtr<nsAHttpTransaction> trans = + mConnectionNegotiatingFastOpen + ->CloseConnectionFastOpenTakesTooLongOrError(false); + if (trans && trans->QueryHttpTransaction()) { + RefPtr<PendingTransactionInfo> pendingTransInfo = + new PendingTransactionInfo(trans->QueryHttpTransaction()); + pendingTransInfo->AddHalfOpen(this); + mEnt->InsertTransaction(pendingTransInfo, true); + } + // We are doing a restart without fast open, so the easiest way is to + // return mSocketTransport to the halfOpenSock and destroy connection. + // This makes http2 implemenntation easier. + // mConnectionNegotiatingFastOpen is going away and halfOpen is taking + // this mSocketTransport so add halfOpen to mEnt and update + // mNumActiveConns. + mEnt->InsertIntoHalfOpens(this); + gHttpHandler->ConnMgr()->StartedConnect(); + + // Restore callbacks. + mStreamOut->AsyncWait(this, 0, 0, nullptr); + mSocketTransport->SetEventSink(this, nullptr); + mSocketTransport->SetSecurityCallbacks(this); + mStreamIn->AsyncWait(nullptr, 0, 0, nullptr); + + if ((aError == NS_ERROR_CONNECTION_REFUSED) || + (aError == NS_ERROR_PROXY_CONNECTION_REFUSED)) { + mFastOpenStatus = TFO_FAILED_CONNECTION_REFUSED; + } else { + mFastOpenStatus = TFO_FAILED_NET_TIMEOUT; + } + + } else { + // On success or other error we proceed with connection, we just need + // to close backup timer and halfOpenSock. + CancelBackupTimer(); + if (NS_SUCCEEDED(aError)) { + NetAddr peeraddr; + if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) { + mEnt->RecordIPFamilyPreference(peeraddr.raw.family); + } + gHttpHandler->ResetFastOpenConsecutiveFailureCounter(); + } + mSocketTransport = nullptr; + mStreamOut = nullptr; + mStreamIn = nullptr; + + // If backup transport has already started put this HalfOpen back to + // mEnt list. + if (mBackupTransport) { + mFastOpenStatus = TFO_BACKUP_CONN; + mEnt->InsertIntoHalfOpens(this); + } + } + + mFastOpenInProgress = false; + mConnectionNegotiatingFastOpen = nullptr; + if (mEnt) { + mEnt->mDoNotDestroy = false; + } else { + MOZ_ASSERT(!mBackupTransport); + MOZ_ASSERT(!mBackupStreamOut); + } +} + +void HalfOpenSocket::SetFastOpenStatus(uint8_t tfoStatus) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(mFastOpenInProgress); + + mFastOpenStatus = tfoStatus; + mConnectionNegotiatingFastOpen->SetFastOpenStatus(tfoStatus); + if (mConnectionNegotiatingFastOpen->Transaction()) { + // The transaction could already be canceled in the meantime, hence + // nullified. + mConnectionNegotiatingFastOpen->Transaction()->SetFastOpenStatus(tfoStatus); + } +} + +void HalfOpenSocket::CancelFastOpenConnection() { + MOZ_ASSERT(mFastOpenInProgress); + + LOG(("HalfOpenSocket::CancelFastOpenConnection [this=%p conn=%p]\n", this, + mConnectionNegotiatingFastOpen.get())); + + RefPtr<HalfOpenSocket> deleteProtector(this); + mEnt->RemoveHalfOpenFastOpenBackups(this); + mSocketTransport->SetFastOpenCallback(nullptr); + mConnectionNegotiatingFastOpen->SetFastOpen(false); + RefPtr<nsAHttpTransaction> trans = + mConnectionNegotiatingFastOpen + ->CloseConnectionFastOpenTakesTooLongOrError(true); + mSocketTransport = nullptr; + mStreamOut = nullptr; + mStreamIn = nullptr; + + if (trans && trans->QueryHttpTransaction()) { + RefPtr<PendingTransactionInfo> pendingTransInfo = + new PendingTransactionInfo(trans->QueryHttpTransaction()); + + mEnt->InsertTransaction(pendingTransInfo, true); + } + + mFastOpenInProgress = false; + mConnectionNegotiatingFastOpen = nullptr; + Abandon(); + + MOZ_ASSERT(!mBackupTransport); + MOZ_ASSERT(!mBackupStreamOut); +} + +void HalfOpenSocket::FastOpenNotSupported() { + MOZ_ASSERT(mFastOpenInProgress); + gHttpHandler->SetFastOpenNotSupported(); +} + +nsresult HalfOpenSocket::SetupConn(nsIAsyncOutputStream* out, bool aFastOpen) { + MOZ_ASSERT(!aFastOpen || (out == mStreamOut)); + // We cannot ask for a connection for TFO and Http3 ata the same time. + MOZ_ASSERT(!(mIsHttp3 && aFastOpen)); + // assign the new socket to the http connection + RefPtr<HttpConnectionBase> conn; + if (!mIsHttp3) { + conn = new nsHttpConnection(); + } else { + conn = new HttpConnectionUDP(); + } + + LOG( + ("HalfOpenSocket::SetupConn " + "Created new nshttpconnection %p %s\n", + conn.get(), mIsHttp3 ? "using http3" : "")); + + NullHttpTransaction* nullTrans = mTransaction->QueryNullTransaction(); + if (nullTrans) { + conn->BootstrapTimings(nullTrans->Timings()); + } + + // Some capabilities are needed before a transaciton actually gets + // scheduled (e.g. how to negotiate false start) + conn->SetTransactionCaps(mTransaction->Caps()); + + NetAddr peeraddr; + nsCOMPtr<nsIInterfaceRequestor> callbacks; + mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks)); + nsresult rv; + if (out == mStreamOut) { + TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted; + rv = conn->Init( + mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, + mSocketTransport, mStreamIn, mStreamOut, + mPrimaryConnectedOK || aFastOpen, callbacks, + PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds()))); + + bool resetPreference = false; + mSocketTransport->GetResetIPFamilyPreference(&resetPreference); + if (resetPreference) { + mEnt->ResetIPFamilyPreference(); + } + + if (!aFastOpen && NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) { + mEnt->RecordIPFamilyPreference(peeraddr.raw.family); + } + + // The nsHttpConnection object now owns these streams and sockets + if (!aFastOpen) { + mStreamOut = nullptr; + mStreamIn = nullptr; + mSocketTransport = nullptr; + } else { + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + MOZ_ASSERT(connTCP); + if (connTCP) { + connTCP->SetFastOpen(true); + } + } + } else if (out == mBackupStreamOut) { + TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted; + rv = conn->Init( + mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, + mBackupTransport, mBackupStreamIn, mBackupStreamOut, mBackupConnectedOK, + callbacks, + PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds()))); + + bool resetPreference = false; + mBackupTransport->GetResetIPFamilyPreference(&resetPreference); + if (resetPreference) { + mEnt->ResetIPFamilyPreference(); + } + + if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr))) { + mEnt->RecordIPFamilyPreference(peeraddr.raw.family); + } + + // The nsHttpConnection object now owns these streams and sockets + mBackupStreamOut = nullptr; + mBackupStreamIn = nullptr; + mBackupTransport = nullptr; + } else { + MOZ_ASSERT(false, "unexpected stream"); + rv = NS_ERROR_UNEXPECTED; + } + + if (NS_FAILED(rv)) { + LOG( + ("HalfOpenSocket::SetupConn " + "conn->init (%p) failed %" PRIx32 "\n", + conn.get(), static_cast<uint32_t>(rv))); + + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + if (connTCP) { + // Set TFO status. + if ((mFastOpenStatus == TFO_HTTP) || (mFastOpenStatus == TFO_DISABLED) || + (mFastOpenStatus == TFO_DISABLED_CONNECT)) { + connTCP->SetFastOpenStatus(mFastOpenStatus); + } else { + connTCP->SetFastOpenStatus(TFO_INIT_FAILED); + } + } + + if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) { + if (mIsHttp3) { + trans->DisableHttp3(); + gHttpHandler->ExcludeHttp3(mEnt->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. + mEnt->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. + if (!aFastOpen) { + mHasConnected = true; + } + + // if this is still in the pending list, remove it and dispatch it + RefPtr<PendingTransactionInfo> pendingTransInfo = + gHttpHandler->ConnMgr()->FindTransactionHelper(true, mEnt, mTransaction); + if (pendingTransInfo) { + MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction"); + + mEnt->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( + mEnt, 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 || + (mEnt->mConnInfo->FirstHopSSL() && !mEnt->UrgentStartQueueLength() && + !mEnt->PendingQueueLength() && !mEnt->mConnInfo->UsingConnect())) { + LOG( + ("HalfOpenSocket::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(mEnt->mConnInfo, callbacks, mCaps); + } + + mEnt->InsertIntoActiveConns(conn); + rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(mEnt, trans, + mCaps, conn, 0); + } else { + // otherwise just put this in the persistent connection pool + LOG( + ("HalfOpenSocket::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! + + // !!! It can be that mEnt is null after OnMsgReclaimConnection.!!! + if (mEnt && mEnt->mConnInfo->FirstHopSSL() && + !mEnt->mConnInfo->UsingConnect()) { + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + // If RemoveIdleConnection succeeds that means that conn is in the + // idle queue. + if (connTCP && NS_SUCCEEDED(mEnt->RemoveIdleConnection(connTCP))) { + RefPtr<nsAHttpTransaction> trans; + if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) { + mDispatchedMTransaction = true; + trans = mTransaction; + } else { + trans = new NullHttpTransaction(mEnt->mConnInfo, callbacks, mCaps); + } + mEnt->InsertIntoActiveConns(conn); + rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction( + mEnt, trans, mCaps, conn, 0); + } + } + } + } + + // If this connection has a transaction get reference to its + // ConnectionHandler. + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + if (connTCP) { + if (aFastOpen) { + MOZ_ASSERT(mEnt); + MOZ_ASSERT(!mEnt->IsInIdleConnections(connTCP)); + if (NS_SUCCEEDED(rv) && mEnt->IsInActiveConns(conn)) { + mConnectionNegotiatingFastOpen = connTCP; + } else { + connTCP->SetFastOpen(false); + connTCP->SetFastOpenStatus(TFO_INIT_FAILED); + } + } else { + connTCP->SetFastOpenStatus(mFastOpenStatus); + if ((mFastOpenStatus != TFO_HTTP) && (mFastOpenStatus != TFO_DISABLED) && + (mFastOpenStatus != TFO_DISABLED_CONNECT)) { + mFastOpenStatus = TFO_BACKUP_CONN; // Set this to TFO_BACKUP_CONN + // so that if a backup + // connection is established we + // do not report values twice. + } + } + } + + // 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 +HalfOpenSocket::OnTransportStatus(nsITransport* trans, nsresult status, + int64_t progress, int64_t progressMax) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + MOZ_ASSERT((trans == mSocketTransport) || (trans == mBackupTransport)); + MOZ_ASSERT(mEnt); + if (mTransaction) { + if ((trans == mSocketTransport) || + ((trans == mBackupTransport) && + (status == NS_NET_STATUS_CONNECTED_TO) && 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_RESOLVING_HOST, NS_NET_STATUS_RESOLVED_HOST, + // 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 all + // mBackupTransport events until NS_NET_STATUS_CONNECTED_TO. + // mBackupTransport must be connected before mSocketTransport. + mTransaction->OnTransportStatus(trans, status, progress); + } + } + + MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport); + if (status == NS_NET_STATUS_CONNECTED_TO) { + if (trans == mSocketTransport) { + mPrimaryConnectedOK = true; + } else { + mBackupConnectedOK = true; + } + } + + // The rest of this method only applies to the primary transport + if (trans != mSocketTransport) { + return NS_OK; + } + + mPrimaryStreamStatus = status; + + // 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. + + if (status == NS_NET_STATUS_CONNECTING_TO && gHttpHandler->IsSpdyEnabled() && + gHttpHandler->CoalesceSpdy() && mEnt && mEnt->mConnInfo && + mEnt->mConnInfo->EndToEndSSL() && mEnt->AllowHttp2() && + !mEnt->mConnInfo->UsingProxy() && mEnt->mCoalescingKeys.IsEmpty()) { + nsCOMPtr<nsIDNSAddrRecord> dnsRecord(do_GetInterface(mSocketTransport)); + nsTArray<NetAddr> addressSet; + nsresult rv = NS_ERROR_NOT_AVAILABLE; + if (dnsRecord) { + rv = dnsRecord->GetAddresses(addressSet); + } + + if (NS_SUCCEEDED(rv) && !addressSet.IsEmpty()) { + for (uint32_t i = 0; i < addressSet.Length(); ++i) { + if ((addressSet[i].raw.family == AF_INET && + addressSet[i].inet.ip == 0) || + (addressSet[i].raw.family == AF_INET6 && + addressSet[i].inet6.ip.u64[0] == 0 && + addressSet[i].inet6.ip.u64[1] == 0)) { + // Bug 1680249 - Don't create the coalescing key if the ip address is + // `0.0.0.0` or `::`. + LOG(("HalfOpenSocket: skip creating Coalescing Key for host [%s]", + mEnt->mConnInfo->Origin())); + continue; + } + nsCString* newKey = mEnt->mCoalescingKeys.AppendElement(nsCString()); + newKey->SetLength(kIPv6CStrBufSize + 26); + addressSet[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize); + newKey->SetLength(strlen(newKey->BeginReading())); + if (mEnt->mConnInfo->GetAnonymous()) { + newKey->AppendLiteral("~A:"); + } else { + newKey->AppendLiteral("~.:"); + } + newKey->AppendInt(mEnt->mConnInfo->OriginPort()); + newKey->AppendLiteral("/["); + nsAutoCString suffix; + mEnt->mConnInfo->GetOriginAttributes().CreateSuffix(suffix); + newKey->Append(suffix); + newKey->AppendLiteral("]viaDNS"); + LOG(( + "HalfOpenSocket::OnTransportStatus " + "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host " + "%s [%s]", + i, mEnt->mConnInfo->Origin(), newKey->get())); + } + gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt); + } + } + + switch (status) { + case NS_NET_STATUS_CONNECTING_TO: + // Passed DNS resolution, now trying to connect, start the backup timer + // only prevent creating another backup transport. + // We also check for mEnt presence to not instantiate the timer after + // this half open socket has already been abandoned. It may happen + // when we get this notification right between main-thread calls to + // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown + // where the first abandons all half open socket instances and only + // after that the second stops the socket thread. + // Http3 has its own syn-retransmission, therefore it does not need a + // backup connection. + if (mEnt && !mBackupTransport && !mSynTimer && !mIsHttp3) { + SetupBackupTimer(); + } + break; + + case NS_NET_STATUS_CONNECTED_TO: + // TCP connection's up, now transfer or SSL negotiantion starts, + // no need for backup socket + CancelBackupTimer(); + break; + + default: + break; + } + + return NS_OK; +} + +// method for nsIInterfaceRequestor +NS_IMETHODIMP +HalfOpenSocket::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 HalfOpenSocket::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 HalfOpenSocket::Claim() { + if (mSpeculative) { + mSpeculative = false; + uint32_t flags; + if (mSocketTransport && + NS_SUCCEEDED(mSocketTransport->GetConnectionFlags(&flags))) { + flags &= ~nsISocketTransport::DISABLE_RFC1918; + mSocketTransport->SetConnectionFlags(flags); + } + + 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 ((mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) && mEnt && + !mBackupTransport && !mSynTimer && !mIsHttp3) { + SetupBackupTimer(); + } + } + + if (mFreeToUse) { + mFreeToUse = false; + return true; + } + return false; +} + +void HalfOpenSocket::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; +} + +} // namespace net +} // namespace mozilla |