summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/HalfOpenSocket.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/http/HalfOpenSocket.cpp1307
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