1 /* vim:set ts=4 sw=2 sts=2 et cin: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 // HttpLog.h should generally be included first
7 #include "HttpLog.h"
8 
9 // Log on level :5, instead of default :4.
10 #undef LOG
11 #define LOG(args) LOG5(args)
12 #undef LOG_ENABLED
13 #define LOG_ENABLED() LOG5_ENABLED()
14 
15 #include "ConnectionEntry.h"
16 #include "nsQueryObject.h"
17 #include "mozilla/ChaosMode.h"
18 
19 namespace mozilla {
20 namespace net {
21 
22 // ConnectionEntry
~ConnectionEntry()23 ConnectionEntry::~ConnectionEntry() {
24   LOG(("ConnectionEntry::~ConnectionEntry this=%p", this));
25 
26   MOZ_ASSERT(!mIdleConns.Length());
27   MOZ_ASSERT(!mActiveConns.Length());
28   MOZ_DIAGNOSTIC_ASSERT(!mDnsAndConnectSockets.Length());
29   MOZ_ASSERT(!PendingQueueLength());
30   MOZ_ASSERT(!UrgentStartQueueLength());
31   MOZ_ASSERT(!mDoNotDestroy);
32 }
33 
ConnectionEntry(nsHttpConnectionInfo * ci)34 ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci)
35     : mConnInfo(ci),
36       mUsingSpdy(false),
37       mCanUseSpdy(true),
38       mPreferIPv4(false),
39       mPreferIPv6(false),
40       mUsedForConnection(false),
41       mDoNotDestroy(false) {
42   LOG(("ConnectionEntry::ConnectionEntry this=%p key=%s", this,
43        ci->HashKey().get()));
44 }
45 
AvailableForDispatchNow()46 bool ConnectionEntry::AvailableForDispatchNow() {
47   if (mIdleConns.Length() && mIdleConns[0]->CanReuse()) {
48     return true;
49   }
50 
51   return gHttpHandler->ConnMgr()->GetH2orH3ActiveConn(this, false, false) !=
52          nullptr;
53 }
54 
UnconnectedDnsAndConnectSockets() const55 uint32_t ConnectionEntry::UnconnectedDnsAndConnectSockets() const {
56   uint32_t unconnectedDnsAndConnectSockets = 0;
57   for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); ++i) {
58     if (!mDnsAndConnectSockets[i]->HasConnected()) {
59       ++unconnectedDnsAndConnectSockets;
60     }
61   }
62   return unconnectedDnsAndConnectSockets;
63 }
64 
InsertIntoDnsAndConnectSockets(DnsAndConnectSocket * sock)65 void ConnectionEntry::InsertIntoDnsAndConnectSockets(
66     DnsAndConnectSocket* sock) {
67   mDnsAndConnectSockets.AppendElement(sock);
68   gHttpHandler->ConnMgr()->IncreaseNumDnsAndConnectSockets();
69 }
70 
RemoveDnsAndConnectSocket(DnsAndConnectSocket * dnsAndSock,bool abandon)71 void ConnectionEntry::RemoveDnsAndConnectSocket(DnsAndConnectSocket* dnsAndSock,
72                                                 bool abandon) {
73   if (abandon) {
74     dnsAndSock->Abandon();
75   }
76   if (mDnsAndConnectSockets.RemoveElement(dnsAndSock)) {
77     gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
78   }
79 
80   if (!UnconnectedDnsAndConnectSockets()) {
81     // perhaps this reverted RestrictConnections()
82     // use the PostEvent version of processpendingq to avoid
83     // altering the pending q vector from an arbitrary stack
84     nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
85     if (NS_FAILED(rv)) {
86       LOG(
87           ("ConnectionEntry::RemoveDnsAndConnectSocket\n"
88            "    failed to process pending queue\n"));
89     }
90   }
91 }
92 
CloseAllDnsAndConnectSockets()93 void ConnectionEntry::CloseAllDnsAndConnectSockets() {
94   for (const auto& dnsAndSock : mDnsAndConnectSockets) {
95     dnsAndSock->Abandon();
96     gHttpHandler->ConnMgr()->DecreaseNumDnsAndConnectSockets();
97   }
98   mDnsAndConnectSockets.Clear();
99   nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
100   if (NS_FAILED(rv)) {
101     LOG(
102         ("ConnectionEntry::CloseAllDnsAndConnectSockets\n"
103          "    failed to process pending queue\n"));
104   }
105 }
106 
DisallowHttp2()107 void ConnectionEntry::DisallowHttp2() {
108   mCanUseSpdy = false;
109 
110   // If we have any spdy connections, we want to go ahead and close them when
111   // they're done so we can free up some connections.
112   for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
113     if (mActiveConns[i]->UsingSpdy()) {
114       mActiveConns[i]->DontReuse();
115     }
116   }
117   for (uint32_t i = 0; i < mIdleConns.Length(); ++i) {
118     if (mIdleConns[i]->UsingSpdy()) {
119       mIdleConns[i]->DontReuse();
120     }
121   }
122 
123   // Can't coalesce if we're not using spdy
124   mCoalescingKeys.Clear();
125 }
126 
DontReuseHttp3Conn()127 void ConnectionEntry::DontReuseHttp3Conn() {
128   MOZ_ASSERT(mConnInfo->IsHttp3());
129 
130   // If we have any spdy connections, we want to go ahead and close them when
131   // they're done so we can free up some connections.
132   for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
133     mActiveConns[i]->DontReuse();
134   }
135 
136   // Can't coalesce if we're not using http3
137   mCoalescingKeys.Clear();
138 }
139 
RecordIPFamilyPreference(uint16_t family)140 void ConnectionEntry::RecordIPFamilyPreference(uint16_t family) {
141   LOG(("ConnectionEntry::RecordIPFamilyPreference %p, af=%u", this, family));
142 
143   if (family == PR_AF_INET && !mPreferIPv6) {
144     mPreferIPv4 = true;
145   }
146 
147   if (family == PR_AF_INET6 && !mPreferIPv4) {
148     mPreferIPv6 = true;
149   }
150 
151   LOG(("  %p prefer ipv4=%d, ipv6=%d", this, (bool)mPreferIPv4,
152        (bool)mPreferIPv6));
153 }
154 
ResetIPFamilyPreference()155 void ConnectionEntry::ResetIPFamilyPreference() {
156   LOG(("ConnectionEntry::ResetIPFamilyPreference %p", this));
157 
158   mPreferIPv4 = false;
159   mPreferIPv6 = false;
160 }
161 
PreferenceKnown() const162 bool net::ConnectionEntry::PreferenceKnown() const {
163   return (bool)mPreferIPv4 || (bool)mPreferIPv6;
164 }
165 
PendingQueueLength() const166 size_t ConnectionEntry::PendingQueueLength() const {
167   return mPendingQ.PendingQueueLength();
168 }
169 
PendingQueueLengthForWindow(uint64_t windowId) const170 size_t ConnectionEntry::PendingQueueLengthForWindow(uint64_t windowId) const {
171   return mPendingQ.PendingQueueLengthForWindow(windowId);
172 }
173 
AppendPendingUrgentStartQ(nsTArray<RefPtr<PendingTransactionInfo>> & result)174 void ConnectionEntry::AppendPendingUrgentStartQ(
175     nsTArray<RefPtr<PendingTransactionInfo>>& result) {
176   mPendingQ.AppendPendingUrgentStartQ(result);
177 }
178 
AppendPendingQForFocusedWindow(uint64_t windowId,nsTArray<RefPtr<PendingTransactionInfo>> & result,uint32_t maxCount)179 void ConnectionEntry::AppendPendingQForFocusedWindow(
180     uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
181     uint32_t maxCount) {
182   mPendingQ.AppendPendingQForFocusedWindow(windowId, result, maxCount);
183   LOG(
184       ("ConnectionEntry::AppendPendingQForFocusedWindow [ci=%s], "
185        "pendingQ count=%zu for focused window (id=%" PRIu64 ")\n",
186        mConnInfo->HashKey().get(), result.Length(), windowId));
187 }
188 
AppendPendingQForNonFocusedWindows(uint64_t windowId,nsTArray<RefPtr<PendingTransactionInfo>> & result,uint32_t maxCount)189 void ConnectionEntry::AppendPendingQForNonFocusedWindows(
190     uint64_t windowId, nsTArray<RefPtr<PendingTransactionInfo>>& result,
191     uint32_t maxCount) {
192   mPendingQ.AppendPendingQForNonFocusedWindows(windowId, result, maxCount);
193   LOG(
194       ("ConnectionEntry::AppendPendingQForNonFocusedWindows [ci=%s], "
195        "pendingQ count=%zu for non focused window\n",
196        mConnInfo->HashKey().get(), result.Length()));
197 }
198 
RemoveEmptyPendingQ()199 void ConnectionEntry::RemoveEmptyPendingQ() { mPendingQ.RemoveEmptyPendingQ(); }
200 
InsertTransactionSorted(nsTArray<RefPtr<PendingTransactionInfo>> & pendingQ,PendingTransactionInfo * pendingTransInfo,bool aInsertAsFirstForTheSamePriority)201 void ConnectionEntry::InsertTransactionSorted(
202     nsTArray<RefPtr<PendingTransactionInfo>>& pendingQ,
203     PendingTransactionInfo* pendingTransInfo,
204     bool aInsertAsFirstForTheSamePriority /*= false*/) {
205   mPendingQ.InsertTransactionSorted(pendingQ, pendingTransInfo,
206                                     aInsertAsFirstForTheSamePriority);
207 }
208 
ReschedTransaction(nsHttpTransaction * aTrans)209 void ConnectionEntry::ReschedTransaction(nsHttpTransaction* aTrans) {
210   mPendingQ.ReschedTransaction(aTrans);
211 }
212 
InsertTransaction(PendingTransactionInfo * pendingTransInfo,bool aInsertAsFirstForTheSamePriority)213 void ConnectionEntry::InsertTransaction(
214     PendingTransactionInfo* pendingTransInfo,
215     bool aInsertAsFirstForTheSamePriority /* = false */) {
216   mPendingQ.InsertTransaction(pendingTransInfo,
217                               aInsertAsFirstForTheSamePriority);
218   pendingTransInfo->Transaction()->OnPendingQueueInserted();
219 }
220 
221 nsTArray<RefPtr<PendingTransactionInfo>>*
GetTransactionPendingQHelper(nsAHttpTransaction * trans)222 ConnectionEntry::GetTransactionPendingQHelper(nsAHttpTransaction* trans) {
223   return mPendingQ.GetTransactionPendingQHelper(trans);
224 }
225 
RestrictConnections()226 bool ConnectionEntry::RestrictConnections() {
227   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
228 
229   if (AvailableForDispatchNow()) {
230     // this might be a h2/spdy connection in this connection entry that
231     // is able to be immediately muxxed, or it might be one that
232     // was found in the same state through a coalescing hash
233     LOG(
234         ("ConnectionEntry::RestrictConnections %p %s restricted due to "
235          "active >=h2\n",
236          this, mConnInfo->HashKey().get()));
237     return true;
238   }
239 
240   // If this host is trying to negotiate a SPDY session right now,
241   // don't create any new ssl connections until the result of the
242   // negotiation is known.
243 
244   bool doRestrict = mConnInfo->FirstHopSSL() && gHttpHandler->IsSpdyEnabled() &&
245                     mUsingSpdy &&
246                     (mDnsAndConnectSockets.Length() || mActiveConns.Length());
247 
248   // If there are no restrictions, we are done
249   if (!doRestrict) {
250     return false;
251   }
252 
253   // If the restriction is based on a tcp handshake in progress
254   // let that connect and then see if it was SPDY or not
255   if (UnconnectedDnsAndConnectSockets()) {
256     return true;
257   }
258 
259   // There is a concern that a host is using a mix of HTTP/1 and SPDY.
260   // In that case we don't want to restrict connections just because
261   // there is a single active HTTP/1 session in use.
262   if (mUsingSpdy && mActiveConns.Length()) {
263     bool confirmedRestrict = false;
264     for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
265       HttpConnectionBase* conn = mActiveConns[index];
266       RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
267       if ((connTCP && !connTCP->ReportedNPN()) || conn->CanDirectlyActivate()) {
268         confirmedRestrict = true;
269         break;
270       }
271     }
272     doRestrict = confirmedRestrict;
273     if (!confirmedRestrict) {
274       LOG(
275           ("nsHttpConnectionMgr spdy connection restriction to "
276            "%s bypassed.\n",
277            mConnInfo->Origin()));
278     }
279   }
280   return doRestrict;
281 }
282 
TotalActiveConnections() const283 uint32_t ConnectionEntry::TotalActiveConnections() const {
284   // Add in the in-progress tcp connections, we will assume they are
285   // keepalive enabled.
286   // Exclude DnsAndConnectSocket's that has already created a usable connection.
287   // This prevents the limit being stuck on ipv6 connections that
288   // eventually time out after typical 21 seconds of no ACK+SYN reply.
289   return mActiveConns.Length() + UnconnectedDnsAndConnectSockets();
290 }
291 
UrgentStartQueueLength()292 size_t ConnectionEntry::UrgentStartQueueLength() {
293   return mPendingQ.UrgentStartQueueLength();
294 }
295 
PrintPendingQ()296 void ConnectionEntry::PrintPendingQ() { mPendingQ.PrintPendingQ(); }
297 
Compact()298 void ConnectionEntry::Compact() {
299   mIdleConns.Compact();
300   mActiveConns.Compact();
301   mPendingQ.Compact();
302 }
303 
RemoveFromIdleConnectionsIndex(size_t inx)304 void ConnectionEntry::RemoveFromIdleConnectionsIndex(size_t inx) {
305   mIdleConns.RemoveElementAt(inx);
306   gHttpHandler->ConnMgr()->DecrementNumIdleConns();
307 }
308 
RemoveFromIdleConnections(nsHttpConnection * conn)309 bool ConnectionEntry::RemoveFromIdleConnections(nsHttpConnection* conn) {
310   if (!mIdleConns.RemoveElement(conn)) {
311     return false;
312   }
313 
314   gHttpHandler->ConnMgr()->DecrementNumIdleConns();
315   return true;
316 }
317 
CancelAllTransactions(nsresult reason)318 void ConnectionEntry::CancelAllTransactions(nsresult reason) {
319   mPendingQ.CancelAllTransactions(reason);
320 }
321 
CloseIdleConnection(nsHttpConnection * conn)322 nsresult ConnectionEntry::CloseIdleConnection(nsHttpConnection* conn) {
323   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
324 
325   RefPtr<nsHttpConnection> deleteProtector(conn);
326   if (!RemoveFromIdleConnections(conn)) {
327     return NS_ERROR_UNEXPECTED;
328   }
329 
330   // The connection is closed immediately no need to call EndIdleMonitoring.
331   conn->Close(NS_ERROR_ABORT);
332   return NS_OK;
333 }
334 
CloseIdleConnections()335 void ConnectionEntry::CloseIdleConnections() {
336   while (mIdleConns.Length()) {
337     RefPtr<nsHttpConnection> conn(mIdleConns[0]);
338     RemoveFromIdleConnectionsIndex(0);
339     // The connection is closed immediately no need to call EndIdleMonitoring.
340     conn->Close(NS_ERROR_ABORT);
341   }
342 }
343 
CloseIdleConnections(uint32_t maxToClose)344 void ConnectionEntry::CloseIdleConnections(uint32_t maxToClose) {
345   uint32_t closed = 0;
346   while (mIdleConns.Length() && (closed < maxToClose)) {
347     RefPtr<nsHttpConnection> conn(mIdleConns[0]);
348     RemoveFromIdleConnectionsIndex(0);
349     // The connection is closed immediately no need to call EndIdleMonitoring.
350     conn->Close(NS_ERROR_ABORT);
351     closed++;
352   }
353 }
354 
RemoveIdleConnection(nsHttpConnection * conn)355 nsresult ConnectionEntry::RemoveIdleConnection(nsHttpConnection* conn) {
356   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
357 
358   if (!RemoveFromIdleConnections(conn)) {
359     return NS_ERROR_UNEXPECTED;
360   }
361 
362   conn->EndIdleMonitoring();
363   return NS_OK;
364 }
365 
IsInIdleConnections(HttpConnectionBase * conn)366 bool ConnectionEntry::IsInIdleConnections(HttpConnectionBase* conn) {
367   RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
368   return connTCP && mIdleConns.Contains(connTCP);
369 }
370 
GetIdleConnection(bool respectUrgency,bool urgentTrans,bool * onlyUrgent)371 already_AddRefed<nsHttpConnection> ConnectionEntry::GetIdleConnection(
372     bool respectUrgency, bool urgentTrans, bool* onlyUrgent) {
373   RefPtr<nsHttpConnection> conn;
374   size_t index = 0;
375   while (!conn && (mIdleConns.Length() > index)) {
376     conn = mIdleConns[index];
377 
378     if (!conn->CanReuse()) {
379       RemoveFromIdleConnectionsIndex(index);
380       LOG(("   dropping stale connection: [conn=%p]\n", conn.get()));
381       conn->Close(NS_ERROR_ABORT);
382       conn = nullptr;
383       continue;
384     }
385 
386     // non-urgent transactions can only be dispatched on non-urgent
387     // started or used connections.
388     if (respectUrgency && conn->IsUrgentStartPreferred() && !urgentTrans) {
389       LOG(("  skipping urgent: [conn=%p]", conn.get()));
390       conn = nullptr;
391       ++index;
392       continue;
393     }
394 
395     *onlyUrgent = false;
396 
397     RemoveFromIdleConnectionsIndex(index);
398     conn->EndIdleMonitoring();
399     LOG(("   reusing connection: [conn=%p]\n", conn.get()));
400   }
401 
402   return conn.forget();
403 }
404 
RemoveActiveConnection(HttpConnectionBase * conn)405 nsresult ConnectionEntry::RemoveActiveConnection(HttpConnectionBase* conn) {
406   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
407 
408   if (!mActiveConns.RemoveElement(conn)) {
409     return NS_ERROR_UNEXPECTED;
410   }
411   gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
412 
413   return NS_OK;
414 }
415 
ClosePersistentConnections()416 void ConnectionEntry::ClosePersistentConnections() {
417   LOG(("ConnectionEntry::ClosePersistentConnections [ci=%s]\n",
418        mConnInfo->HashKey().get()));
419   CloseIdleConnections();
420 
421   int32_t activeCount = mActiveConns.Length();
422   for (int32_t i = 0; i < activeCount; i++) {
423     mActiveConns[i]->DontReuse();
424   }
425 }
426 
PruneDeadConnections()427 uint32_t ConnectionEntry::PruneDeadConnections() {
428   uint32_t timeToNextExpire = UINT32_MAX;
429 
430   for (int32_t len = mIdleConns.Length(); len > 0; --len) {
431     int32_t idx = len - 1;
432     RefPtr<nsHttpConnection> conn(mIdleConns[idx]);
433     if (!conn->CanReuse()) {
434       RemoveFromIdleConnectionsIndex(idx);
435       // The connection is closed immediately no need to call
436       // EndIdleMonitoring.
437       conn->Close(NS_ERROR_ABORT);
438     } else {
439       timeToNextExpire = std::min(timeToNextExpire, conn->TimeToLive());
440     }
441   }
442 
443   if (mUsingSpdy) {
444     for (uint32_t i = 0; i < mActiveConns.Length(); ++i) {
445       RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
446       // Http3 has its own timers, it is not using this one.
447       if (connTCP && connTCP->UsingSpdy()) {
448         if (!connTCP->CanReuse()) {
449           // Marking it don't-reuse will create an active
450           // tear down if the spdy session is idle.
451           connTCP->DontReuse();
452         } else {
453           timeToNextExpire = std::min(timeToNextExpire, connTCP->TimeToLive());
454         }
455       }
456     }
457   }
458 
459   return timeToNextExpire;
460 }
461 
VerifyTraffic()462 void ConnectionEntry::VerifyTraffic() {
463   if (!mConnInfo->IsHttp3()) {
464     // Iterate over all active connections and check them.
465     for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
466       RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
467       if (conn) {
468         conn->CheckForTraffic(true);
469       }
470     }
471     // Iterate the idle connections and unmark them for traffic checks.
472     for (uint32_t index = 0; index < mIdleConns.Length(); ++index) {
473       RefPtr<nsHttpConnection> conn = do_QueryObject(mIdleConns[index]);
474       if (conn) {
475         conn->CheckForTraffic(false);
476       }
477     }
478   }
479 }
480 
InsertIntoIdleConnections_internal(nsHttpConnection * conn)481 void ConnectionEntry::InsertIntoIdleConnections_internal(
482     nsHttpConnection* conn) {
483   uint32_t idx;
484   for (idx = 0; idx < mIdleConns.Length(); idx++) {
485     nsHttpConnection* idleConn = mIdleConns[idx];
486     if (idleConn->MaxBytesRead() < conn->MaxBytesRead()) {
487       break;
488     }
489   }
490 
491   mIdleConns.InsertElementAt(idx, conn);
492 }
493 
InsertIntoIdleConnections(nsHttpConnection * conn)494 void ConnectionEntry::InsertIntoIdleConnections(nsHttpConnection* conn) {
495   InsertIntoIdleConnections_internal(conn);
496   gHttpHandler->ConnMgr()->NewIdleConnectionAdded(conn->TimeToLive());
497   conn->BeginIdleMonitoring();
498 }
499 
IsInActiveConns(HttpConnectionBase * conn)500 bool ConnectionEntry::IsInActiveConns(HttpConnectionBase* conn) {
501   return mActiveConns.Contains(conn);
502 }
503 
InsertIntoActiveConns(HttpConnectionBase * conn)504 void ConnectionEntry::InsertIntoActiveConns(HttpConnectionBase* conn) {
505   mActiveConns.AppendElement(conn);
506   gHttpHandler->ConnMgr()->IncrementActiveConnCount();
507 }
508 
MakeAllDontReuseExcept(HttpConnectionBase * conn)509 void ConnectionEntry::MakeAllDontReuseExcept(HttpConnectionBase* conn) {
510   for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
511     HttpConnectionBase* otherConn = mActiveConns[index];
512     if (otherConn != conn) {
513       LOG(
514           ("ConnectionEntry::MakeAllDontReuseExcept shutting down old "
515            "connection (%p) "
516            "because new "
517            "spdy connection (%p) takes precedence\n",
518            otherConn, conn));
519       otherConn->DontReuse();
520     }
521   }
522 
523   // Cancel any other pending connections - their associated transactions
524   // are in the pending queue and will be dispatched onto this new connection
525   CloseAllDnsAndConnectSockets();
526 }
527 
FindConnToClaim(PendingTransactionInfo * pendingTransInfo)528 bool ConnectionEntry::FindConnToClaim(
529     PendingTransactionInfo* pendingTransInfo) {
530   nsHttpTransaction* trans = pendingTransInfo->Transaction();
531 
532   for (const auto& dnsAndSock : mDnsAndConnectSockets) {
533     if (dnsAndSock->AcceptsTransaction(trans) && dnsAndSock->Claim()) {
534       pendingTransInfo->RememberDnsAndConnectSocket(dnsAndSock);
535       // We've found a speculative connection or a connection that
536       // is free to be used in the DnsAndConnectSockets list.
537       // A free to be used connection is a connection that was
538       // open for a concrete transaction, but that trunsaction
539       // ended up using another connection.
540       LOG(
541           ("ConnectionEntry::FindConnToClaim [ci = %s]\n"
542            "Found a speculative or a free-to-use DnsAndConnectSocket\n",
543            mConnInfo->HashKey().get()));
544 
545       // return OK because we have essentially opened a new connection
546       // by converting a speculative DnsAndConnectSockets to general use
547       return true;
548     }
549   }
550 
551   // consider null transactions that are being used to drive the ssl handshake
552   // if the transaction creating this connection can re-use persistent
553   // connections
554   if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
555     uint32_t activeLength = mActiveConns.Length();
556     for (uint32_t i = 0; i < activeLength; i++) {
557       if (pendingTransInfo->TryClaimingActiveConn(mActiveConns[i])) {
558         LOG(
559             ("ConnectionEntry::FindConnectingSocket [ci = %s] "
560              "Claiming a null transaction for later use\n",
561              mConnInfo->HashKey().get()));
562         return true;
563       }
564     }
565   }
566   return false;
567 }
568 
MakeFirstActiveSpdyConnDontReuse()569 bool ConnectionEntry::MakeFirstActiveSpdyConnDontReuse() {
570   if (!mUsingSpdy) {
571     return false;
572   }
573 
574   for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
575     HttpConnectionBase* conn = mActiveConns[index];
576     if (conn->UsingSpdy() && conn->CanReuse()) {
577       conn->DontReuse();
578       return true;
579     }
580   }
581   return false;
582 }
583 
584 // Return an active h2 or h3 connection
585 // that can be directly activated or null.
GetH2orH3ActiveConn()586 HttpConnectionBase* ConnectionEntry::GetH2orH3ActiveConn() {
587   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
588 
589   HttpConnectionBase* experienced = nullptr;
590   HttpConnectionBase* noExperience = nullptr;
591   uint32_t activeLen = mActiveConns.Length();
592 
593   // activeLen should generally be 1.. this is a setup race being resolved
594   // take a conn who can activate and is experienced
595   for (uint32_t index = 0; index < activeLen; ++index) {
596     HttpConnectionBase* tmp = mActiveConns[index];
597     if (tmp->CanDirectlyActivate()) {
598       if (tmp->IsExperienced()) {
599         experienced = tmp;
600         break;
601       }
602       noExperience = tmp;  // keep looking for a better option
603     }
604   }
605 
606   // if that worked, cleanup anything else and exit
607   if (experienced) {
608     for (uint32_t index = 0; index < activeLen; ++index) {
609       HttpConnectionBase* tmp = mActiveConns[index];
610       // in the case where there is a functional h2 session, drop the others
611       if (tmp != experienced) {
612         tmp->DontReuse();
613       }
614     }
615 
616     LOG(
617         ("GetH2orH3ActiveConn() request for ent %p %s "
618          "found an active experienced connection %p in native connection "
619          "entry\n",
620          this, mConnInfo->HashKey().get(), experienced));
621     return experienced;
622   }
623 
624   if (noExperience) {
625     LOG(
626         ("GetH2orH3ActiveConn() request for ent %p %s "
627          "found an active but inexperienced connection %p in native connection "
628          "entry\n",
629          this, mConnInfo->HashKey().get(), noExperience));
630     return noExperience;
631   }
632 
633   return nullptr;
634 }
635 
CloseActiveConnections()636 void ConnectionEntry::CloseActiveConnections() {
637   while (mActiveConns.Length()) {
638     RefPtr<HttpConnectionBase> conn(mActiveConns[0]);
639     mActiveConns.RemoveElementAt(0);
640     gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
641 
642     // Since HttpConnectionBase::Close doesn't break the bond with
643     // the connection's transaction, we must explicitely tell it
644     // to close its transaction and not just self.
645     conn->CloseTransaction(conn->Transaction(), NS_ERROR_ABORT, true);
646   }
647 }
648 
CloseAllActiveConnsWithNullTransactcion(nsresult aCloseCode)649 void ConnectionEntry::CloseAllActiveConnsWithNullTransactcion(
650     nsresult aCloseCode) {
651   for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
652     RefPtr<HttpConnectionBase> activeConn = mActiveConns[index];
653     nsAHttpTransaction* liveTransaction = activeConn->Transaction();
654     if (liveTransaction && liveTransaction->IsNullTransaction()) {
655       LOG(
656           ("ConnectionEntry::CloseAllActiveConnsWithNullTransactcion "
657            "also canceling Null Transaction %p on conn %p\n",
658            liveTransaction, activeConn.get()));
659       activeConn->CloseTransaction(liveTransaction, aCloseCode);
660     }
661   }
662 }
663 
PruneNoTraffic()664 void ConnectionEntry::PruneNoTraffic() {
665   LOG(("  pruning no traffic [ci=%s]\n", mConnInfo->HashKey().get()));
666   if (mConnInfo->IsHttp3()) {
667     return;
668   }
669 
670   uint32_t numConns = mActiveConns.Length();
671   if (numConns) {
672     // Walk the list backwards to allow us to remove entries easily.
673     for (int index = numConns - 1; index >= 0; index--) {
674       RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
675       if (conn && conn->NoTraffic()) {
676         mActiveConns.RemoveElementAt(index);
677         gHttpHandler->ConnMgr()->DecrementActiveConnCount(conn);
678         conn->Close(NS_ERROR_ABORT);
679         LOG(
680             ("  closed active connection due to no traffic "
681              "[conn=%p]\n",
682              conn.get()));
683       }
684     }
685   }
686 }
687 
TimeoutTick()688 uint32_t ConnectionEntry::TimeoutTick() {
689   uint32_t timeoutTickNext = 3600;  // 1hr
690 
691   if (mConnInfo->IsHttp3()) {
692     return timeoutTickNext;
693   }
694 
695   LOG(
696       ("ConnectionEntry::TimeoutTick() this=%p host=%s "
697        "idle=%zu active=%zu"
698        " dnsAndSock-len=%zu pending=%zu"
699        " urgentStart pending=%zu\n",
700        this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(),
701        mDnsAndConnectSockets.Length(), PendingQueueLength(),
702        UrgentStartQueueLength()));
703 
704   // First call the tick handler for each active connection.
705   PRIntervalTime tickTime = PR_IntervalNow();
706   for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
707     RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
708     if (conn) {
709       uint32_t connNextTimeout = conn->ReadTimeoutTick(tickTime);
710       timeoutTickNext = std::min(timeoutTickNext, connNextTimeout);
711     }
712   }
713 
714   // Now check for any stalled DnsAndConnectSockets.
715   if (mDnsAndConnectSockets.Length()) {
716     TimeStamp currentTime = TimeStamp::Now();
717     double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
718 
719     for (const auto& dnsAndSock : Reversed(mDnsAndConnectSockets)) {
720       double delta = dnsAndSock->Duration(currentTime);
721       // If the socket has timed out, close it so the waiting
722       // transaction will get the proper signal.
723       if (delta > maxConnectTime_ms) {
724         LOG(("Force timeout of DnsAndConnectSocket to %s after %.2fms.\n",
725              mConnInfo->HashKey().get(), delta));
726         dnsAndSock->CloseTransports(NS_ERROR_NET_TIMEOUT);
727       }
728 
729       // If this DnsAndConnectSocket hangs around for 5 seconds after we've
730       // closed() it then just abandon the socket.
731       if (delta > maxConnectTime_ms + 5000) {
732         LOG(("Abandon DnsAndConnectSocket to %s after %.2fms.\n",
733              mConnInfo->HashKey().get(), delta));
734         RemoveDnsAndConnectSocket(dnsAndSock, true);
735       }
736     }
737   }
738   if (mDnsAndConnectSockets.Length()) {
739     timeoutTickNext = 1;
740   }
741 
742   return timeoutTickNext;
743 }
744 
MoveConnection(HttpConnectionBase * proxyConn,ConnectionEntry * otherEnt)745 void ConnectionEntry::MoveConnection(HttpConnectionBase* proxyConn,
746                                      ConnectionEntry* otherEnt) {
747   // To avoid changing mNumActiveConns/mNumIdleConns counter use internal
748   // functions.
749   RefPtr<HttpConnectionBase> deleteProtector(proxyConn);
750   if (mActiveConns.RemoveElement(proxyConn)) {
751     otherEnt->mActiveConns.AppendElement(proxyConn);
752     return;
753   }
754 
755   RefPtr<nsHttpConnection> proxyConnTCP = do_QueryObject(proxyConn);
756   if (proxyConnTCP) {
757     if (mIdleConns.RemoveElement(proxyConnTCP)) {
758       otherEnt->InsertIntoIdleConnections_internal(proxyConnTCP);
759       return;
760     }
761   }
762 }
763 
GetConnectionData()764 HttpRetParams ConnectionEntry::GetConnectionData() {
765   HttpRetParams data;
766   data.host = mConnInfo->Origin();
767   data.port = mConnInfo->OriginPort();
768   for (uint32_t i = 0; i < mActiveConns.Length(); i++) {
769     HttpConnInfo info;
770     RefPtr<nsHttpConnection> connTCP = do_QueryObject(mActiveConns[i]);
771     if (connTCP) {
772       info.ttl = connTCP->TimeToLive();
773     } else {
774       info.ttl = 0;
775     }
776     info.rtt = mActiveConns[i]->Rtt();
777     info.SetHTTPProtocolVersion(mActiveConns[i]->Version());
778     data.active.AppendElement(info);
779   }
780   for (uint32_t i = 0; i < mIdleConns.Length(); i++) {
781     HttpConnInfo info;
782     info.ttl = mIdleConns[i]->TimeToLive();
783     info.rtt = mIdleConns[i]->Rtt();
784     info.SetHTTPProtocolVersion(mIdleConns[i]->Version());
785     data.idle.AppendElement(info);
786   }
787   for (uint32_t i = 0; i < mDnsAndConnectSockets.Length(); i++) {
788     DnsAndConnectSockets dnsAndSock{};
789     dnsAndSock.speculative = mDnsAndConnectSockets[i]->IsSpeculative();
790     data.dnsAndSocks.AppendElement(dnsAndSock);
791   }
792   if (mConnInfo->IsHttp3()) {
793     data.httpVersion = "HTTP/3"_ns;
794   } else if (mUsingSpdy) {
795     data.httpVersion = "HTTP/2"_ns;
796   } else {
797     data.httpVersion = "HTTP <= 1.1"_ns;
798   }
799   data.ssl = mConnInfo->EndToEndSSL();
800   return data;
801 }
802 
LogConnections()803 void ConnectionEntry::LogConnections() {
804   if (!mConnInfo->IsHttp3()) {
805     LOG(("active urgent conns ["));
806     for (HttpConnectionBase* conn : mActiveConns) {
807       RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
808       MOZ_ASSERT(connTCP);
809       if (connTCP->IsUrgentStartPreferred()) {
810         LOG(("  %p", conn));
811       }
812     }
813     LOG(("] active regular conns ["));
814     for (HttpConnectionBase* conn : mActiveConns) {
815       RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
816       MOZ_ASSERT(connTCP);
817       if (!connTCP->IsUrgentStartPreferred()) {
818         LOG(("  %p", conn));
819       }
820     }
821 
822     LOG(("] idle urgent conns ["));
823     for (nsHttpConnection* conn : mIdleConns) {
824       if (conn->IsUrgentStartPreferred()) {
825         LOG(("  %p", conn));
826       }
827     }
828     LOG(("] idle regular conns ["));
829     for (nsHttpConnection* conn : mIdleConns) {
830       if (!conn->IsUrgentStartPreferred()) {
831         LOG(("  %p", conn));
832       }
833     }
834   } else {
835     LOG(("active conns ["));
836     for (HttpConnectionBase* conn : mActiveConns) {
837       LOG(("  %p", conn));
838     }
839     MOZ_ASSERT(mIdleConns.Length() == 0);
840   }
841   LOG(("]"));
842 }
843 
RemoveTransFromPendingQ(nsHttpTransaction * aTrans)844 bool ConnectionEntry::RemoveTransFromPendingQ(nsHttpTransaction* aTrans) {
845   // We will abandon all DnsAndConnectSockets belonging to the given
846   // transaction.
847   nsTArray<RefPtr<PendingTransactionInfo>>* infoArray =
848       GetTransactionPendingQHelper(aTrans);
849 
850   RefPtr<PendingTransactionInfo> pendingTransInfo;
851   int32_t transIndex =
852       infoArray ? infoArray->IndexOf(aTrans, 0, PendingComparator()) : -1;
853   if (transIndex >= 0) {
854     pendingTransInfo = (*infoArray)[transIndex];
855     infoArray->RemoveElementAt(transIndex);
856   }
857 
858   if (!pendingTransInfo) {
859     return false;
860   }
861 
862   // Abandon all DnsAndConnectSockets belonging to the given transaction.
863   nsWeakPtr tmp = pendingTransInfo->ForgetDnsAndConnectSocketAndActiveConn();
864   RefPtr<DnsAndConnectSocket> dnsAndSock = do_QueryReferent(tmp);
865   if (dnsAndSock) {
866     RemoveDnsAndConnectSocket(dnsAndSock, true);
867   }
868   return true;
869 }
870 
MaybeUpdateEchConfig(nsHttpConnectionInfo * aConnInfo)871 void ConnectionEntry::MaybeUpdateEchConfig(nsHttpConnectionInfo* aConnInfo) {
872   if (!mConnInfo->HashKey().Equals(aConnInfo->HashKey())) {
873     return;
874   }
875 
876   const nsCString& echConfig = aConnInfo->GetEchConfig();
877   if (mConnInfo->GetEchConfig().Equals(echConfig)) {
878     return;
879   }
880 
881   LOG(("ConnectionEntry::MaybeUpdateEchConfig [ci=%s]\n",
882        mConnInfo->HashKey().get()));
883 
884   mConnInfo->SetEchConfig(echConfig);
885 
886   // If echConfig is changed, we should close all DnsAndConnectSockets and idle
887   // connections. This is to make sure the new echConfig will be used for the
888   // next connection.
889   CloseAllDnsAndConnectSockets();
890   CloseIdleConnections();
891 }
892 
MaybeProcessCoalescingKeys(nsIDNSAddrRecord * dnsRecord,bool aIsHttp3)893 bool ConnectionEntry::MaybeProcessCoalescingKeys(nsIDNSAddrRecord* dnsRecord,
894                                                  bool aIsHttp3) {
895   if (!mConnInfo || !mConnInfo->EndToEndSSL() || (!aIsHttp3 && !AllowHttp2()) ||
896       mConnInfo->UsingProxy() || !mCoalescingKeys.IsEmpty() || !dnsRecord) {
897     return false;
898   }
899 
900   nsTArray<NetAddr> addressSet;
901   nsresult rv = dnsRecord->GetAddresses(addressSet);
902 
903   if (NS_FAILED(rv) || addressSet.IsEmpty()) {
904     return false;
905   }
906 
907   for (uint32_t i = 0; i < addressSet.Length(); ++i) {
908     if ((addressSet[i].raw.family == AF_INET && addressSet[i].inet.ip == 0) ||
909         (addressSet[i].raw.family == AF_INET6 &&
910          addressSet[i].inet6.ip.u64[0] == 0 &&
911          addressSet[i].inet6.ip.u64[1] == 0)) {
912       // Bug 1680249 - Don't create the coalescing key if the ip address is
913       // `0.0.0.0` or `::`.
914       LOG(
915           ("ConnectionEntry::MaybeProcessCoalescingKeys skip creating "
916            "Coalescing Key for host [%s]",
917            mConnInfo->Origin()));
918       continue;
919     }
920     nsCString* newKey = mCoalescingKeys.AppendElement(nsCString());
921     newKey->SetLength(kIPv6CStrBufSize + 26);
922     addressSet[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize);
923     newKey->SetLength(strlen(newKey->BeginReading()));
924     if (mConnInfo->GetAnonymous()) {
925       newKey->AppendLiteral("~A:");
926     } else {
927       newKey->AppendLiteral("~.:");
928     }
929     if (mConnInfo->GetFallbackConnection()) {
930       newKey->AppendLiteral("~F:");
931     } else {
932       newKey->AppendLiteral("~.:");
933     }
934     newKey->AppendInt(mConnInfo->OriginPort());
935     newKey->AppendLiteral("/[");
936     nsAutoCString suffix;
937     mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
938     newKey->Append(suffix);
939     newKey->AppendLiteral("]viaDNS");
940     LOG(
941         ("ConnectionEntry::MaybeProcessCoalescingKeys "
942          "Established New Coalescing Key # %d for host "
943          "%s [%s]",
944          i, mConnInfo->Origin(), newKey->get()));
945   }
946   return true;
947 }
948 
CreateDnsAndConnectSocket(nsAHttpTransaction * trans,uint32_t caps,bool speculative,bool isFromPredictor,bool urgentStart,bool allow1918,PendingTransactionInfo * pendingTransInfo)949 nsresult ConnectionEntry::CreateDnsAndConnectSocket(
950     nsAHttpTransaction* trans, uint32_t caps, bool speculative,
951     bool isFromPredictor, bool urgentStart, bool allow1918,
952     PendingTransactionInfo* pendingTransInfo) {
953   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
954   MOZ_ASSERT((speculative && !pendingTransInfo) ||
955              (!speculative && pendingTransInfo));
956 
957   RefPtr<DnsAndConnectSocket> sock = new DnsAndConnectSocket(
958       mConnInfo, trans, caps, speculative, isFromPredictor, urgentStart);
959 
960   if (speculative) {
961     sock->SetAllow1918(allow1918);
962   }
963 
964   nsresult rv = sock->Init(this);
965   if (NS_FAILED(rv)) {
966     sock->Abandon();
967     return rv;
968   }
969 
970   InsertIntoDnsAndConnectSockets(sock);
971 
972   if (pendingTransInfo && sock->Claim()) {
973     pendingTransInfo->RememberDnsAndConnectSocket(sock);
974   }
975 
976   return NS_OK;
977 }
978 
979 }  // namespace net
980 }  // namespace mozilla
981