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