diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/http/ConnectionEntry.cpp | 1102 |
1 files changed, 1102 insertions, 0 deletions
diff --git a/netwerk/protocol/http/ConnectionEntry.cpp b/netwerk/protocol/http/ConnectionEntry.cpp new file mode 100644 index 0000000000..473f0ab9fc --- /dev/null +++ b/netwerk/protocol/http/ConnectionEntry.cpp @@ -0,0 +1,1102 @@ +/* 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" + +// Log on level :5, instead of default :4. +#undef LOG +#define LOG(args) LOG5(args) +#undef LOG_ENABLED +#define LOG_ENABLED() LOG5_ENABLED() + +#include "ConnectionEntry.h" +#include "nsQueryObject.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/StaticPrefs_network.h" +#include "nsHttpHandler.h" + +namespace mozilla { +namespace net { + +// ConnectionEntry +ConnectionEntry::~ConnectionEntry() { + LOG(("ConnectionEntry::~ConnectionEntry this=%p", this)); + + MOZ_ASSERT(!mIdleConns.Length()); + MOZ_ASSERT(!mActiveConns.Length()); + MOZ_DIAGNOSTIC_ASSERT(!mDnsAndConnectSockets.Length()); + MOZ_ASSERT(!PendingQueueLength()); + MOZ_ASSERT(!UrgentStartQueueLength()); + MOZ_ASSERT(!mDoNotDestroy); +} + +ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci) + : mConnInfo(ci), + mUsingSpdy(false), + mCanUseSpdy(true), + mPreferIPv4(false), + mPreferIPv6(false), + mUsedForConnection(false), + mDoNotDestroy(false) { + LOG(("ConnectionEntry::ConnectionEntry this=%p key=%s", this, + ci->HashKey().get())); +} + +bool ConnectionEntry::AvailableForDispatchNow() { + if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) { + return true; + } + + return gHttpHandler->ConnMgr()->GetH2orH3ActiveConn(this, false, false) != + nullptr; +} + +uint32_t ConnectionEntry::UnconnectedDnsAndConnectSockets() const { + uint32_t unconnectedDnsAndConnectSockets = 0; + for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); ++i) { + if (!mDnsAndConnectSockets[i]->HasConnected()) { + ++unconnectedDnsAndConnectSockets; + } + } + return unconnectedDnsAndConnectSockets; +} + +void ConnectionEntry::InsertIntoDnsAndConnectSockets( + DnsAndConnectSocket* sock) { + mDnsAndConnectSockets.AppendElement(sock); + gHttpHandler->ConnMgr()->IncreaseNumDnsAndConnectSockets(); +} + +void ConnectionEntry::RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock, + bool abandon) { + if (abandon) { + dnsAndSock->Abandon(); + } + if (mDnsAndConnectSockets.RemoveElement(dnsAndSock)) { + gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets(); + } + + if (!UnconnectedDnsAndConnectSockets()) { + // perhaps this reverted RestrictConnections() + // use the PostEvent version of processpendingq to avoid + // altering the pending q vector from an arbitrary stack + nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); + if (NS_FAILED(rv)) { + LOG( + ("ConnectionEntry::RemoveDnsAndConnectSocket\n" + " failed to process pending queue\n")); + } + } +} + +void ConnectionEntry::CloseAllDnsAndConnectSockets() { + for (const auto& dnsAndSock : mDnsAndConnectSockets) { + dnsAndSock->Abandon(); + gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets(); + } + mDnsAndConnectSockets.Clear(); + nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); + if (NS_FAILED(rv)) { + LOG( + ("ConnectionEntry::CloseAllDnsAndConnectSockets\n" + " failed to process pending queue\n")); + } +} + +void ConnectionEntry::DisallowHttp2() { + mCanUseSpdy = false; + + // If we have any spdy connections, we want to go ahead and close them when + // they're done so we can free up some connections. + for (uint32_t i = 0; i < mActiveConns.Length(); ++i) { + if (mActiveConns[i]->UsingSpdy()) { + mActiveConns[i]->DontReuse(); + } + } + for (uint32_t i = 0; i < mIdleConns.Length(); ++i) { + if (mIdleConns[i]->UsingSpdy()) { + mIdleConns[i]->DontReuse(); + } + } + + // Can't coalesce if we're not using spdy + mCoalescingKeys.Clear(); +} + +void ConnectionEntry::DontReuseHttp3Conn() { + MOZ_ASSERT(mConnInfo->IsHttp3()); + + // If we have any spdy connections, we want to go ahead and close them when + // they're done so we can free up some connections. + for (uint32_t i = 0; i < mActiveConns.Length(); ++i) { + mActiveConns[i]->DontReuse(); + } + + // Can't coalesce if we're not using http3 + mCoalescingKeys.Clear(); +} + +void ConnectionEntry::RecordIPFamilyPreference(uint16_t family) { + LOG(("ConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family)); + + if (family == PR_AF_INET && !mPreferIPv6) { + mPreferIPv4 = true; + } + + if (family == PR_AF_INET6 && !mPreferIPv4) { + mPreferIPv6 = true; + } + + LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4, + (bool)mPreferIPv6)); +} + +void ConnectionEntry::ResetIPFamilyPreference() { + LOG(("ConnectionEntry::ResetIPFamilyPreference %p", this)); + + mPreferIPv4 = false; + mPreferIPv6 = false; +} + +bool net::ConnectionEntry::PreferenceKnown() const { + return (bool)mPreferIPv4 || (bool)mPreferIPv6; +} + +size_t ConnectionEntry::PendingQueueLength() const { + return mPendingQ.PendingQueueLength(); +} + +size_t ConnectionEntry::PendingQueueLengthForWindow(uint64_t windowId) const { + return mPendingQ.PendingQueueLengthForWindow(windowId); +} + +void ConnectionEntry::AppendPendingUrgentStartQ( + nsTArray<RefPtr<PendingTransactionInfo>>& result) { + mPendingQ.AppendPendingUrgentStartQ(result); +} + +void ConnectionEntry::AppendPendingQForFocusedWindow( + uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result, + uint32_t maxCount) { + mPendingQ.AppendPendingQForFocusedWindow(windowId, result, maxCount); + LOG( + ("ConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], " + "pendingQ count=%zu for focused window (id=%" PRIu64 ")\n", + mConnInfo->HashKey().get(), result.Length(), windowId)); +} + +void ConnectionEntry::AppendPendingQForNonFocusedWindows( + uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result, + uint32_t maxCount) { + mPendingQ.AppendPendingQForNonFocusedWindows(windowId, result, maxCount); + LOG( + ("ConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], " + "pendingQ count=%zu for non focused window\n", + mConnInfo->HashKey().get(), result.Length())); +} + +void ConnectionEntry::RemoveEmptyPendingQ() { mPendingQ.RemoveEmptyPendingQ(); } + +void ConnectionEntry::InsertTransactionSorted( + nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ, + PendingTransactionInfo* pendingTransInfo, + bool aInsertAsFirstForTheSamePriority /*= false*/) { + mPendingQ.InsertTransactionSorted(pendingQ, pendingTransInfo, + aInsertAsFirstForTheSamePriority); +} + +void ConnectionEntry::ReschedTransaction(nsHttpTransaction* aTrans) { + mPendingQ.ReschedTransaction(aTrans); +} + +void ConnectionEntry::InsertTransaction( + PendingTransactionInfo* pendingTransInfo, + bool aInsertAsFirstForTheSamePriority /* = false */) { + mPendingQ.InsertTransaction(pendingTransInfo, + aInsertAsFirstForTheSamePriority); + pendingTransInfo->Transaction()->OnPendingQueueInserted(mConnInfo->HashKey()); +} + +nsTArray<RefPtr<PendingTransactionInfo>>* +ConnectionEntry::GetTransactionPendingQHelper(nsAHttpTransaction* trans) { + return mPendingQ.GetTransactionPendingQHelper(trans); +} + +bool ConnectionEntry::RestrictConnections() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (AvailableForDispatchNow()) { + // this might be a h2/spdy connection in this connection entry that + // is able to be immediately muxxed, or it might be one that + // was found in the same state through a coalescing hash + LOG( + ("ConnectionEntry::RestrictConnections %p %s restricted due to " + "active >=h2\n", + this, mConnInfo->HashKey().get())); + return true; + } + + // If this host is trying to negotiate a SPDY session right now, + // don't create any new ssl connections until the result of the + // negotiation is known. + + bool doRestrict = mConnInfo->FirstHopSSL() && + StaticPrefs::network_http_http2_enabled() && mUsingSpdy && + (mDnsAndConnectSockets.Length() || mActiveConns.Length()); + + // If there are no restrictions, we are done + if (!doRestrict) { + return false; + } + + // If the restriction is based on a tcp handshake in progress + // let that connect and then see if it was SPDY or not + if (UnconnectedDnsAndConnectSockets()) { + return true; + } + + // There is a concern that a host is using a mix of HTTP/1 and SPDY. + // In that case we don't want to restrict connections just because + // there is a single active HTTP/1 session in use. + if (mUsingSpdy && mActiveConns.Length()) { + bool confirmedRestrict = false; + for (uint32_t index = 0; index < mActiveConns.Length(); ++index) { + HttpConnectionBase* conn = mActiveConns[index]; + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + if ((connTCP && !connTCP->ReportedNPN()) || conn->CanDirectlyActivate()) { + confirmedRestrict = true; + break; + } + } + doRestrict = confirmedRestrict; + if (!confirmedRestrict) { + LOG( + ("nsHttpConnectionMgr spdy connection restriction to " + "%s bypassed.\n", + mConnInfo->Origin())); + } + } + return doRestrict; +} + +uint32_t ConnectionEntry::TotalActiveConnections() const { + // Add in the in-progress tcp connections, we will assume they are + // keepalive enabled. + // Exclude DnsAndConnectSocket's that has already created a usable connection. + // This prevents the limit being stuck on ipv6 connections that + // eventually time out after typical 21 seconds of no ACK+SYN reply. + return mActiveConns.Length() + UnconnectedDnsAndConnectSockets(); +} + +size_t ConnectionEntry::UrgentStartQueueLength() { + return mPendingQ.UrgentStartQueueLength(); +} + +void ConnectionEntry::PrintPendingQ() { mPendingQ.PrintPendingQ(); } + +void ConnectionEntry::Compact() { + mIdleConns.Compact(); + mActiveConns.Compact(); + mPendingQ.Compact(); +} + +void ConnectionEntry::RemoveFromIdleConnectionsIndex(size_t inx) { + mIdleConns.RemoveElementAt(inx); + gHttpHandler->ConnMgr()->DecrementNumIdleConns(); +} + +bool ConnectionEntry::RemoveFromIdleConnections(nsHttpConnection* conn) { + if (!mIdleConns.RemoveElement(conn)) { + return false; + } + + gHttpHandler->ConnMgr()->DecrementNumIdleConns(); + return true; +} + +void ConnectionEntry::CancelAllTransactions(nsresult reason) { + mPendingQ.CancelAllTransactions(reason); +} + +nsresult ConnectionEntry::CloseIdleConnection(nsHttpConnection* conn) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + RefPtr<nsHttpConnection> deleteProtector(conn); + if (!RemoveFromIdleConnections(conn)) { + return NS_ERROR_UNEXPECTED; + } + + // The connection is closed immediately no need to call EndIdleMonitoring. + conn->Close(NS_ERROR_ABORT); + return NS_OK; +} + +void ConnectionEntry::CloseIdleConnections() { + while (mIdleConns.Length()) { + RefPtr<nsHttpConnection> conn(mIdleConns[0]); + RemoveFromIdleConnectionsIndex(0); + // The connection is closed immediately no need to call EndIdleMonitoring. + conn->Close(NS_ERROR_ABORT); + } +} + +void ConnectionEntry::CloseIdleConnections(uint32_t maxToClose) { + uint32_t closed = 0; + while (mIdleConns.Length() && (closed < maxToClose)) { + RefPtr<nsHttpConnection> conn(mIdleConns[0]); + RemoveFromIdleConnectionsIndex(0); + // The connection is closed immediately no need to call EndIdleMonitoring. + conn->Close(NS_ERROR_ABORT); + closed++; + } +} + +void ConnectionEntry::CloseH2WebsocketConnections() { + while (mH2WebsocketConns.Length()) { + RefPtr<HttpConnectionBase> conn(mH2WebsocketConns[0]); + mH2WebsocketConns.RemoveElementAt(0); + + // safe to close connection since we are on the socket thread + // closing via transaction to break connection/transaction bond + conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true); + } +} + +nsresult ConnectionEntry::RemoveIdleConnection(nsHttpConnection* conn) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (!RemoveFromIdleConnections(conn)) { + return NS_ERROR_UNEXPECTED; + } + + conn->EndIdleMonitoring(); + return NS_OK; +} + +bool ConnectionEntry::IsInIdleConnections(HttpConnectionBase* conn) { + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + return connTCP && mIdleConns.Contains(connTCP); +} + +already_AddRefed<nsHttpConnection> ConnectionEntry::GetIdleConnection( + bool respectUrgency, bool urgentTrans, bool* onlyUrgent) { + RefPtr<nsHttpConnection> conn; + size_t index = 0; + while (!conn && (mIdleConns.Length() > index)) { + conn = mIdleConns[index]; + + if (!conn->CanReuse()) { + RemoveFromIdleConnectionsIndex(index); + LOG((" dropping stale connection: [conn=%p]\n", conn.get())); + conn->Close(NS_ERROR_ABORT); + conn = nullptr; + continue; + } + + // non-urgent transactions can only be dispatched on non-urgent + // started or used connections. + if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) { + LOG((" skipping urgent: [conn=%p]", conn.get())); + conn = nullptr; + ++index; + continue; + } + + *onlyUrgent = false; + + RemoveFromIdleConnectionsIndex(index); + conn->EndIdleMonitoring(); + LOG((" reusing connection: [conn=%p]\n", conn.get())); + } + + return conn.forget(); +} + +nsresult ConnectionEntry::RemoveActiveConnection(HttpConnectionBase* conn) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (!mActiveConns.RemoveElement(conn)) { + return NS_ERROR_UNEXPECTED; + } + gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn); + + return NS_OK; +} + +nsresult ConnectionEntry::RemovePendingConnection(HttpConnectionBase* conn) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (!mPendingConns.RemoveElement(conn)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +void ConnectionEntry::ClosePersistentConnections() { + LOG(("ConnectionEntry::ClosePersistentConnections [ci=%s]\n", + mConnInfo->HashKey().get())); + CloseIdleConnections(); + + int32_t activeCount = mActiveConns.Length(); + for (int32_t i = 0; i < activeCount; i++) { + mActiveConns[i]->DontReuse(); + } + + mCoalescingKeys.Clear(); +} + +uint32_t ConnectionEntry::PruneDeadConnections() { + uint32_t timeToNextExpire = UINT32_MAX; + + for (int32_t len = mIdleConns.Length(); len > 0; --len) { + int32_t idx = len - 1; + RefPtr<nsHttpConnection> conn(mIdleConns[idx]); + if (!conn->CanReuse()) { + RemoveFromIdleConnectionsIndex(idx); + // The connection is closed immediately no need to call + // EndIdleMonitoring. + conn->Close(NS_ERROR_ABORT); + } else { + timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive()); + } + } + + if (mUsingSpdy) { + for (uint32_t i = 0; i < mActiveConns.Length(); ++i) { + RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]); + // Http3 has its own timers, it is not using this one. + if (connTCP && connTCP->UsingSpdy()) { + if (!connTCP->CanReuse()) { + // Marking it don't-reuse will create an active + // tear down if the spdy session is idle. + connTCP->DontReuse(); + } else { + timeToNextExpire = std::min(timeToNextExpire, connTCP->TimeToLive()); + } + } + } + } + + return timeToNextExpire; +} + +void ConnectionEntry::VerifyTraffic() { + if (!mConnInfo->IsHttp3()) { + for (uint32_t index = 0; index < mPendingConns.Length(); ++index) { + RefPtr<nsHttpConnection> conn = do_QueryObject(mPendingConns[index]); + if (conn) { + conn->CheckForTraffic(true); + } + } + + uint32_t numConns = mActiveConns.Length(); + if (numConns) { + // Walk the list backwards to allow us to remove entries easily. + for (int index = numConns - 1; index >= 0; index--) { + RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]); + if (conn) { + conn->CheckForTraffic(true); + if (conn->EverUsedSpdy() && + StaticPrefs:: + network_http_http2_move_to_pending_list_after_network_change()) { + mActiveConns.RemoveElementAt(index); + gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn); + mPendingConns.AppendElement(conn); + LOG(("Move active connection to pending list [conn=%p]\n", + conn.get())); + } + } + } + } + + // Iterate the idle connections and unmark them for traffic checks. + for (uint32_t index = 0; index < mIdleConns.Length(); ++index) { + RefPtr<nsHttpConnection> conn = do_QueryObject(mIdleConns[index]); + if (conn) { + conn->CheckForTraffic(false); + } + } + } +} + +void ConnectionEntry::InsertIntoIdleConnections_internal( + nsHttpConnection* conn) { + uint32_t idx; + for (idx = 0; idx < mIdleConns.Length(); idx++) { + nsHttpConnection* idleConn = mIdleConns[idx]; + if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) { + break; + } + } + + mIdleConns.InsertElementAt(idx, conn); +} + +void ConnectionEntry::InsertIntoIdleConnections(nsHttpConnection* conn) { + InsertIntoIdleConnections_internal(conn); + gHttpHandler->ConnMgr()->NewIdleConnectionAdded(conn->TimeToLive()); + conn->BeginIdleMonitoring(); +} + +bool ConnectionEntry::IsInActiveConns(HttpConnectionBase* conn) { + return mActiveConns.Contains(conn); +} + +void ConnectionEntry::InsertIntoActiveConns(HttpConnectionBase* conn) { + mActiveConns.AppendElement(conn); + gHttpHandler->ConnMgr()->IncrementActiveConnCount(); +} + +bool ConnectionEntry::IsInH2WebsocketConns(HttpConnectionBase* conn) { + return mH2WebsocketConns.Contains(conn); +} + +void ConnectionEntry::InsertIntoH2WebsocketConns(HttpConnectionBase* conn) { + // no incrementing of connection count since it is just a "fake" connection + mH2WebsocketConns.AppendElement(conn); +} + +void ConnectionEntry::RemoveH2WebsocketConns(HttpConnectionBase* conn) { + mH2WebsocketConns.RemoveElement(conn); +} + +void ConnectionEntry::MakeAllDontReuseExcept(HttpConnectionBase* conn) { + for (uint32_t index = 0; index < mActiveConns.Length(); ++index) { + HttpConnectionBase* otherConn = mActiveConns[index]; + if (otherConn != conn) { + LOG( + ("ConnectionEntry::MakeAllDontReuseExcept shutting down old " + "connection (%p) " + "because new " + "spdy connection (%p) takes precedence\n", + otherConn, conn)); + otherConn->SetCloseReason( + ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING); + otherConn->DontReuse(); + } + } + + // Cancel any other pending connections - their associated transactions + // are in the pending queue and will be dispatched onto this new connection + CloseAllDnsAndConnectSockets(); +} + +bool ConnectionEntry::FindConnToClaim( + PendingTransactionInfo* pendingTransInfo) { + nsHttpTransaction* trans = pendingTransInfo->Transaction(); + + for (const auto& dnsAndSock : mDnsAndConnectSockets) { + if (dnsAndSock->AcceptsTransaction(trans) && dnsAndSock->Claim()) { + pendingTransInfo->RememberDnsAndConnectSocket(dnsAndSock); + // We've found a speculative connection or a connection that + // is free to be used in the DnsAndConnectSockets list. + // A free to be used connection is a connection that was + // open for a concrete transaction, but that trunsaction + // ended up using another connection. + LOG( + ("ConnectionEntry::FindConnToClaim [ci = %s]\n" + "Found a speculative or a free-to-use DnsAndConnectSocket\n", + mConnInfo->HashKey().get())); + + // return OK because we have essentially opened a new connection + // by converting a speculative DnsAndConnectSockets to general use + return true; + } + } + + // consider null transactions that are being used to drive the ssl handshake + // if the transaction creating this connection can re-use persistent + // connections + if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) { + uint32_t activeLength = mActiveConns.Length(); + for (uint32_t i = 0; i < activeLength; i++) { + if (pendingTransInfo->TryClaimingActiveConn(mActiveConns[i])) { + LOG( + ("ConnectionEntry::FindConnectingSocket [ci = %s] " + "Claiming a null transaction for later use\n", + mConnInfo->HashKey().get())); + return true; + } + } + } + return false; +} + +bool ConnectionEntry::MakeFirstActiveSpdyConnDontReuse() { + if (!mUsingSpdy) { + return false; + } + + for (uint32_t index = 0; index < mActiveConns.Length(); ++index) { + HttpConnectionBase* conn = mActiveConns[index]; + if (conn->UsingSpdy() && conn->CanReuse()) { + conn->DontReuse(); + return true; + } + } + return false; +} + +// Return an active h2 or h3 connection +// that can be directly activated or null. +HttpConnectionBase* ConnectionEntry::GetH2orH3ActiveConn() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + HttpConnectionBase* experienced = nullptr; + HttpConnectionBase* noExperience = nullptr; + uint32_t activeLen = mActiveConns.Length(); + + // activeLen should generally be 1.. this is a setup race being resolved + // take a conn who can activate and is experienced + for (uint32_t index = 0; index < activeLen; ++index) { + HttpConnectionBase* tmp = mActiveConns[index]; + if (tmp->CanDirectlyActivate()) { + if (tmp->IsExperienced()) { + experienced = tmp; + break; + } + noExperience = tmp; // keep looking for a better option + } + } + + // if that worked, cleanup anything else and exit + if (experienced) { + for (uint32_t index = 0; index < activeLen; ++index) { + HttpConnectionBase* tmp = mActiveConns[index]; + // in the case where there is a functional h2 session, drop the others + if (tmp != experienced) { + tmp->DontReuse(); + } + } + + LOG( + ("GetH2orH3ActiveConn() request for ent %p %s " + "found an active experienced connection %p in native connection " + "entry\n", + this, mConnInfo->HashKey().get(), experienced)); + return experienced; + } + + if (noExperience) { + LOG( + ("GetH2orH3ActiveConn() request for ent %p %s " + "found an active but inexperienced connection %p in native connection " + "entry\n", + this, mConnInfo->HashKey().get(), noExperience)); + return noExperience; + } + + return nullptr; +} + +void ConnectionEntry::CloseActiveConnections() { + while (mActiveConns.Length()) { + RefPtr<HttpConnectionBase> conn(mActiveConns[0]); + mActiveConns.RemoveElementAt(0); + gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn); + + // Since HttpConnectionBase::Close doesn't break the bond with + // the connection's transaction, we must explicitely tell it + // to close its transaction and not just self. + conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true); + } +} + +void ConnectionEntry::CloseAllActiveConnsWithNullTransactcion( + nsresult aCloseCode) { + for (uint32_t index = 0; index < mActiveConns.Length(); ++index) { + RefPtr<HttpConnectionBase> activeConn = mActiveConns[index]; + nsAHttpTransaction* liveTransaction = activeConn->Transaction(); + if (liveTransaction && liveTransaction->IsNullTransaction()) { + LOG( + ("ConnectionEntry::CloseAllActiveConnsWithNullTransactcion " + "also canceling Null Transaction %p on conn %p\n", + liveTransaction, activeConn.get())); + activeConn->CloseTransaction(liveTransaction, aCloseCode); + } + } +} + +void ConnectionEntry::ClosePendingConnections() { + while (mPendingConns.Length()) { + RefPtr<HttpConnectionBase> conn(mPendingConns[0]); + mPendingConns.RemoveElementAt(0); + + // Since HttpConnectionBase::Close doesn't break the bond with + // the connection's transaction, we must explicitely tell it + // to close its transaction and not just self. + conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true); + } +} + +void ConnectionEntry::PruneNoTraffic() { + LOG((" pruning no traffic [ci=%s]\n", mConnInfo->HashKey().get())); + if (mConnInfo->IsHttp3()) { + return; + } + + uint32_t numConns = mActiveConns.Length(); + if (numConns) { + // Walk the list backwards to allow us to remove entries easily. + for (int index = numConns - 1; index >= 0; index--) { + RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]); + if (conn && conn->NoTraffic()) { + mActiveConns.RemoveElementAt(index); + gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn); + conn->Close(NS_ERROR_ABORT); + LOG( + (" closed active connection due to no traffic " + "[conn=%p]\n", + conn.get())); + } + } + } +} + +uint32_t ConnectionEntry::TimeoutTick() { + uint32_t timeoutTickNext = 3600; // 1hr + + if (mConnInfo->IsHttp3()) { + return timeoutTickNext; + } + + LOG( + ("ConnectionEntry::TimeoutTick() this=%p host=%s " + "idle=%zu active=%zu" + " dnsAndSock-len=%zu pending=%zu" + " urgentStart pending=%zu\n", + this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(), + mDnsAndConnectSockets.Length(), PendingQueueLength(), + UrgentStartQueueLength())); + + // First call the tick handler for each active connection. + PRIntervalTime tickTime = PR_IntervalNow(); + for (uint32_t index = 0; index < mActiveConns.Length(); ++index) { + RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]); + if (conn) { + uint32_t connNextTimeout = conn->ReadTimeoutTick(tickTime); + timeoutTickNext = std::min(timeoutTickNext, connNextTimeout); + } + } + + // Now check for any stalled DnsAndConnectSockets. + if (mDnsAndConnectSockets.Length()) { + TimeStamp currentTime = TimeStamp::Now(); + double maxConnectTime_ms = gHttpHandler->ConnectTimeout(); + + for (const auto& dnsAndSock : Reversed(mDnsAndConnectSockets)) { + double delta = dnsAndSock->Duration(currentTime); + // If the socket has timed out, close it so the waiting + // transaction will get the proper signal. + if (delta > maxConnectTime_ms) { + LOG(("Force timeout of DnsAndConnectSocket to %s after %.2fms.\n", + mConnInfo->HashKey().get(), delta)); + dnsAndSock->CloseTransports(NS_ERROR_NET_TIMEOUT); + } + + // If this DnsAndConnectSocket hangs around for 5 seconds after we've + // closed() it then just abandon the socket. + if (delta > maxConnectTime_ms + 5000) { + LOG(("Abandon DnsAndConnectSocket to %s after %.2fms.\n", + mConnInfo->HashKey().get(), delta)); + RemoveDnsAndConnectSocket(dnsAndSock, true); + } + } + } + if (mDnsAndConnectSockets.Length()) { + timeoutTickNext = 1; + } + + return timeoutTickNext; +} + +void ConnectionEntry::MoveConnection(HttpConnectionBase* proxyConn, + ConnectionEntry* otherEnt) { + // To avoid changing mNumActiveConns/mNumIdleConns counter use internal + // functions. + RefPtr<HttpConnectionBase> deleteProtector(proxyConn); + if (mActiveConns.RemoveElement(proxyConn)) { + otherEnt->mActiveConns.AppendElement(proxyConn); + return; + } + + RefPtr<nsHttpConnection> proxyConnTCP = do_QueryObject(proxyConn); + if (proxyConnTCP) { + if (mIdleConns.RemoveElement(proxyConnTCP)) { + otherEnt->InsertIntoIdleConnections_internal(proxyConnTCP); + return; + } + } +} + +HttpRetParams ConnectionEntry::GetConnectionData() { + HttpRetParams data; + data.host = mConnInfo->Origin(); + data.port = mConnInfo->OriginPort(); + for (uint32_t i = 0; i < mActiveConns.Length(); i++) { + HttpConnInfo info; + RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]); + if (connTCP) { + info.ttl = connTCP->TimeToLive(); + } else { + info.ttl = 0; + } + info.rtt = mActiveConns[i]->Rtt(); + info.SetHTTPProtocolVersion(mActiveConns[i]->Version()); + data.active.AppendElement(info); + } + for (uint32_t i = 0; i < mIdleConns.Length(); i++) { + HttpConnInfo info; + info.ttl = mIdleConns[i]->TimeToLive(); + info.rtt = mIdleConns[i]->Rtt(); + info.SetHTTPProtocolVersion(mIdleConns[i]->Version()); + data.idle.AppendElement(info); + } + for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); i++) { + DnsAndConnectSockets dnsAndSock{}; + dnsAndSock.speculative = mDnsAndConnectSockets[i]->IsSpeculative(); + data.dnsAndSocks.AppendElement(dnsAndSock); + } + if (mConnInfo->IsHttp3()) { + data.httpVersion = "HTTP/3"_ns; + } else if (mUsingSpdy) { + data.httpVersion = "HTTP/2"_ns; + } else { + data.httpVersion = "HTTP <= 1.1"_ns; + } + data.ssl = mConnInfo->EndToEndSSL(); + return data; +} + +void ConnectionEntry::LogConnections() { + if (!mConnInfo->IsHttp3()) { + LOG(("active urgent conns [")); + for (HttpConnectionBase* conn : mActiveConns) { + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + MOZ_ASSERT(connTCP); + if (connTCP->IsUrgentStartPreferred()) { + LOG((" %p", conn)); + } + } + LOG(("] active regular conns [")); + for (HttpConnectionBase* conn : mActiveConns) { + RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn); + MOZ_ASSERT(connTCP); + if (!connTCP->IsUrgentStartPreferred()) { + LOG((" %p", conn)); + } + } + + LOG(("] idle urgent conns [")); + for (nsHttpConnection* conn : mIdleConns) { + if (conn->IsUrgentStartPreferred()) { + LOG((" %p", conn)); + } + } + LOG(("] idle regular conns [")); + for (nsHttpConnection* conn : mIdleConns) { + if (!conn->IsUrgentStartPreferred()) { + LOG((" %p", conn)); + } + } + } else { + LOG(("active conns [")); + for (HttpConnectionBase* conn : mActiveConns) { + LOG((" %p", conn)); + } + MOZ_ASSERT(mIdleConns.Length() == 0); + } + LOG(("]")); +} + +bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) { + // We will abandon all DnsAndConnectSockets belonging to the given + // transaction. + nsTArray<RefPtr<PendingTransactionInfo>>* infoArray = + GetTransactionPendingQHelper(aTrans); + + RefPtr<PendingTransactionInfo> pendingTransInfo; + int32_t transIndex = + infoArray ? infoArray->IndexOf(aTrans, 0, PendingComparator()) : -1; + if (transIndex >= 0) { + pendingTransInfo = (*infoArray)[transIndex]; + infoArray->RemoveElementAt(transIndex); + } + + if (!pendingTransInfo) { + return false; + } + + // Abandon all DnsAndConnectSockets belonging to the given transaction. + nsWeakPtr tmp = pendingTransInfo->ForgetDnsAndConnectSocketAndActiveConn(); + RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(tmp); + if (dnsAndSock) { + RemoveDnsAndConnectSocket(dnsAndSock, true); + } + return true; +} + +void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) { + if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) { + return; + } + + const nsCString& echConfig = aConnInfo->GetEchConfig(); + if (mConnInfo->GetEchConfig().Equals(echConfig)) { + return; + } + + LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n", + mConnInfo->HashKey().get())); + + mConnInfo->SetEchConfig(echConfig); + + // If echConfig is changed, we should close all DnsAndConnectSockets and idle + // connections. This is to make sure the new echConfig will be used for the + // next connection. + CloseAllDnsAndConnectSockets(); + CloseIdleConnections(); +} + +bool ConnectionEntry::MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord, + bool aIsHttp3) { + if (!mConnInfo || !mConnInfo->EndToEndSSL() || (!aIsHttp3 && !AllowHttp2()) || + mConnInfo->UsingProxy() || !mCoalescingKeys.IsEmpty() || !dnsRecord) { + return false; + } + + nsTArray<NetAddr> addressSet; + nsresult rv = dnsRecord->GetAddresses(addressSet); + + if (NS_FAILED(rv) || addressSet.IsEmpty()) { + return false; + } + + 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( + ("ConnectionEntry::MaybeProcessCoalescingKeys skip creating " + "Coalescing Key for host [%s]", + mConnInfo->Origin())); + continue; + } + nsCString* newKey = mCoalescingKeys.AppendElement(nsCString()); + newKey->SetLength(kIPv6CStrBufSize + 26); + addressSet[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize); + newKey->SetLength(strlen(newKey->BeginReading())); + if (mConnInfo->GetAnonymous()) { + newKey->AppendLiteral("~A:"); + } else { + newKey->AppendLiteral("~.:"); + } + if (mConnInfo->GetFallbackConnection()) { + newKey->AppendLiteral("~F:"); + } else { + newKey->AppendLiteral("~.:"); + } + newKey->AppendInt(mConnInfo->OriginPort()); + newKey->AppendLiteral("/["); + nsAutoCString suffix; + mConnInfo->GetOriginAttributes().CreateSuffix(suffix); + newKey->Append(suffix); + newKey->AppendLiteral("]viaDNS"); + LOG( + ("ConnectionEntry::MaybeProcessCoalescingKeys " + "Established New Coalescing Key # %d for host " + "%s [%s]", + i, mConnInfo->Origin(), newKey->get())); + } + return true; +} + +nsresult ConnectionEntry::CreateDnsAndConnectSocket( + nsAHttpTransaction* trans, uint32_t caps, bool speculative, + bool isFromPredictor, bool urgentStart, bool allow1918, + PendingTransactionInfo* pendingTransInfo) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT((speculative && !pendingTransInfo) || + (!speculative && pendingTransInfo)); + + RefPtr<DnsAndConnectSocket> sock = new DnsAndConnectSocket( + mConnInfo, trans, caps, speculative, isFromPredictor, urgentStart); + + if (speculative) { + sock->SetAllow1918(allow1918); + } + + nsresult rv = sock->Init(this); + if (NS_FAILED(rv)) { + sock->Abandon(); + return rv; + } + + InsertIntoDnsAndConnectSockets(sock); + + if (pendingTransInfo && sock->Claim()) { + pendingTransInfo->RememberDnsAndConnectSocket(sock); + } + + return NS_OK; +} + +bool ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3(nsresult aError) { + LOG( + ("ConnectionEntry::AllowToRetryDifferentIPFamilyForHttp3 %p " + "error=%" PRIx32, + this, static_cast<uint32_t>(aError))); + if (!IsHttp3()) { + MOZ_ASSERT(false, "Should not be called for non Http/3 connection"); + return false; + } + + if (!StaticPrefs::network_http_http3_retry_different_ip_family()) { + return false; + } + + // Only allow to retry with these two errors. + if (aError != NS_ERROR_CONNECTION_REFUSED && + aError != NS_ERROR_PROXY_CONNECTION_REFUSED) { + return false; + } + + // Already retried once. + if (mRetriedDifferentIPFamilyForHttp3) { + return false; + } + + return true; +} + +void ConnectionEntry::SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily) { + LOG(("ConnectionEntry::SetRetryDifferentIPFamilyForHttp3 %p, af=%u", this, + aIPFamily)); + + mPreferIPv4 = false; + mPreferIPv6 = false; + + if (aIPFamily == AF_INET) { + mPreferIPv6 = true; + } + + if (aIPFamily == AF_INET6) { + mPreferIPv4 = true; + } + + mRetriedDifferentIPFamilyForHttp3 = true; + + LOG((" %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4, + (bool)mPreferIPv6)); + MOZ_DIAGNOSTIC_ASSERT(mPreferIPv4 ^ mPreferIPv6); +} + +} // namespace net +} // namespace mozilla |