Http3Session.cpp (108918B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=4 sw=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ASpdySession.h" // because of SoftStreamError() 8 #include "Http3Session.h" 9 #include "Http3Stream.h" 10 #include "Http3StreamBase.h" 11 #include "Http3WebTransportSession.h" 12 #include "Http3ConnectUDPStream.h" 13 #include "Http3StreamTunnel.h" 14 #include "Http3WebTransportStream.h" 15 #include "HttpConnectionUDP.h" 16 #include "HttpLog.h" 17 #include "QuicSocketControl.h" 18 #include "SSLServerCertVerification.h" 19 #include "SSLTokensCache.h" 20 #include "ScopedNSSTypes.h" 21 #include "mozilla/RandomNum.h" 22 #include "mozilla/RefPtr.h" 23 #include "mozilla/ScopeExit.h" 24 #include "mozilla/glean/NetwerkMetrics.h" 25 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h" 26 #include "mozilla/net/DNS.h" 27 #include "nsHttpHandler.h" 28 #include "nsIHttpActivityObserver.h" 29 #include "nsIOService.h" 30 #include "nsITLSSocketControl.h" 31 #include "nsNetAddr.h" 32 #include "nsQueryObject.h" 33 #include "nsSocketTransportService2.h" 34 #include "nsThreadUtils.h" 35 #include "sslerr.h" 36 #include "WebTransportCertificateVerifier.h" 37 38 namespace mozilla::net { 39 40 extern const nsCString& TRRProviderKey(); 41 42 const uint64_t HTTP3_APP_ERROR_NO_ERROR = 0x100; 43 // const uint64_t HTTP3_APP_ERROR_GENERAL_PROTOCOL_ERROR = 0x101; 44 // const uint64_t HTTP3_APP_ERROR_INTERNAL_ERROR = 0x102; 45 // const uint64_t HTTP3_APP_ERROR_STREAM_CREATION_ERROR = 0x103; 46 // const uint64_t HTTP3_APP_ERROR_CLOSED_CRITICAL_STREAM = 0x104; 47 // const uint64_t HTTP3_APP_ERROR_FRAME_UNEXPECTED = 0x105; 48 // const uint64_t HTTP3_APP_ERROR_FRAME_ERROR = 0x106; 49 // const uint64_t HTTP3_APP_ERROR_EXCESSIVE_LOAD = 0x107; 50 // const uint64_t HTTP3_APP_ERROR_ID_ERROR = 0x108; 51 // const uint64_t HTTP3_APP_ERROR_SETTINGS_ERROR = 0x109; 52 // const uint64_t HTTP3_APP_ERROR_MISSING_SETTINGS = 0x10a; 53 const uint64_t HTTP3_APP_ERROR_REQUEST_REJECTED = 0x10b; 54 const uint64_t HTTP3_APP_ERROR_REQUEST_CANCELLED = 0x10c; 55 // const uint64_t HTTP3_APP_ERROR_REQUEST_INCOMPLETE = 0x10d; 56 // const uint64_t HTTP3_APP_ERROR_EARLY_RESPONSE = 0x10e; 57 // const uint64_t HTTP3_APP_ERROR_CONNECT_ERROR = 0x10f; 58 const uint64_t HTTP3_APP_ERROR_VERSION_FALLBACK = 0x110; 59 60 // const uint32_t UDP_MAX_PACKET_SIZE = 4096; 61 const uint32_t MAX_PTO_COUNTS = 16; 62 63 const uint32_t TRANSPORT_ERROR_STATELESS_RESET = 20; 64 65 NS_IMPL_ADDREF_INHERITED(Http3Session, nsAHttpConnection) 66 NS_IMPL_RELEASE_INHERITED(Http3Session, nsAHttpConnection) 67 NS_INTERFACE_MAP_BEGIN(Http3Session) 68 NS_INTERFACE_MAP_ENTRY(nsAHttpConnection) 69 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 70 NS_INTERFACE_MAP_ENTRY_CONCRETE(Http3Session) 71 NS_INTERFACE_MAP_END 72 73 Http3Session::Http3Session() { 74 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 75 LOG(("Http3Session::Http3Session [this=%p]", this)); 76 77 mCurrentBrowserId = gHttpHandler->ConnMgr()->CurrentBrowserId(); 78 } 79 80 static nsresult RawBytesToNetAddr(uint16_t aFamily, const uint8_t* aRemoteAddr, 81 uint16_t remotePort, NetAddr* netAddr) { 82 if (aFamily == AF_INET) { 83 netAddr->inet.family = AF_INET; 84 netAddr->inet.port = htons(remotePort); 85 memcpy(&netAddr->inet.ip, aRemoteAddr, 4); 86 } else if (aFamily == AF_INET6) { 87 netAddr->inet6.family = AF_INET6; 88 netAddr->inet6.port = htons(remotePort); 89 memcpy(&netAddr->inet6.ip.u8, aRemoteAddr, 16); 90 } else { 91 return NS_ERROR_UNEXPECTED; 92 } 93 94 return NS_OK; 95 } 96 97 nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo, 98 nsINetAddr* aSelfAddr, nsINetAddr* aPeerAddr, 99 HttpConnectionUDP* udpConn, uint32_t aProviderFlags, 100 nsIInterfaceRequestor* callbacks, 101 nsIUDPSocket* socket, bool aIsTunnel) { 102 LOG3(("Http3Session::Init %p", this)); 103 104 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 105 MOZ_ASSERT(udpConn); 106 107 mConnInfo = aConnInfo->Clone(); 108 mNetAddr = aPeerAddr; 109 110 // When `isOuterConnection` is true, this Http3Session represents the *outer* 111 // connection between Firefox and the proxy (e.g., when using CONNECT-UDP). 112 // 113 // We track this flag for two main reasons: 114 // 1. To select the correct hostname during TLS negotiation on the outer 115 // connection. 116 // 2. To explicitly enable Path MTU Discovery (PMTUD) on the outer connection, 117 // since the outer path’s MTU must be at least as large as the inner one. 118 bool isOuterConnection = false; 119 if (!aIsTunnel) { 120 if (auto* proxyInfo = aConnInfo->ProxyInfo()) { 121 isOuterConnection = proxyInfo->IsHttp3Proxy(); 122 } 123 } 124 125 // Create security control and info object for quic. 126 mSocketControl = 127 new QuicSocketControl(isOuterConnection ? aConnInfo->ProxyInfo()->Host() 128 : aConnInfo->GetOrigin(), 129 isOuterConnection ? aConnInfo->ProxyInfo()->Port() 130 : aConnInfo->OriginPort(), 131 aProviderFlags, this); 132 const nsCString& alpn = isOuterConnection ? aConnInfo->GetProxyNPNToken() 133 : aConnInfo->GetNPNToken(); 134 135 NetAddr selfAddr; 136 MOZ_ALWAYS_SUCCEEDS(aSelfAddr->GetNetAddr(&selfAddr)); 137 NetAddr peerAddr; 138 MOZ_ALWAYS_SUCCEEDS(aPeerAddr->GetNetAddr(&peerAddr)); 139 140 LOG3( 141 ("Http3Session::Init origin=%s, alpn=%s, selfAddr=%s, peerAddr=%s," 142 " qpack table size=%u, max blocked streams=%u webtransport=%d " 143 "[this=%p]", 144 PromiseFlatCString(mSocketControl->GetHostName()).get(), 145 PromiseFlatCString(alpn).get(), selfAddr.ToString().get(), 146 peerAddr.ToString().get(), gHttpHandler->DefaultQpackTableSize(), 147 gHttpHandler->DefaultHttp3MaxBlockedStreams(), 148 mConnInfo->GetWebTransport(), this)); 149 150 if (mConnInfo->GetWebTransport()) { 151 ExtState(ExtendedConnectKind::WebTransport).mStatus = NEGOTIATING; 152 } 153 if (isOuterConnection) { 154 ExtState(ExtendedConnectKind::ConnectUDP).mStatus = NEGOTIATING; 155 } 156 157 mUseNSPRForIO = 158 StaticPrefs::network_http_http3_use_nspr_for_io() || aIsTunnel; 159 160 uint32_t idleTimeout = 161 mConnInfo->GetIsTrrServiceChannel() 162 ? StaticPrefs::network_trr_idle_timeout_for_http3_conn() 163 : StaticPrefs::network_http_http3_idle_timeout(); 164 165 nsresult rv; 166 if (mUseNSPRForIO) { 167 rv = NeqoHttp3Conn::InitUseNSPRForIO( 168 mSocketControl->GetHostName(), alpn, selfAddr, peerAddr, 169 gHttpHandler->DefaultQpackTableSize(), 170 gHttpHandler->DefaultHttp3MaxBlockedStreams(), 171 StaticPrefs::network_http_http3_max_data(), 172 StaticPrefs::network_http_http3_max_stream_data(), 173 StaticPrefs::network_http_http3_version_negotiation_enabled(), 174 mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(), 175 aProviderFlags, idleTimeout, getter_AddRefs(mHttp3Connection)); 176 } else { 177 rv = NeqoHttp3Conn::Init( 178 mSocketControl->GetHostName(), alpn, selfAddr, peerAddr, 179 gHttpHandler->DefaultQpackTableSize(), 180 gHttpHandler->DefaultHttp3MaxBlockedStreams(), 181 StaticPrefs::network_http_http3_max_data(), 182 StaticPrefs::network_http_http3_max_stream_data(), 183 StaticPrefs::network_http_http3_version_negotiation_enabled(), 184 mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(), 185 aProviderFlags, idleTimeout, socket->GetFileDescriptor(), 186 isOuterConnection, getter_AddRefs(mHttp3Connection)); 187 } 188 if (NS_FAILED(rv)) { 189 return rv; 190 } 191 192 nsAutoCString peerId; 193 mSocketControl->GetPeerId(peerId); 194 nsTArray<uint8_t> token; 195 SessionCacheInfo info; 196 udpConn->ChangeConnectionState(ConnectionState::TLS_HANDSHAKING); 197 198 auto hasServCertHashes = [&]() -> bool { 199 if (!mConnInfo->GetWebTransport()) { 200 return false; 201 } 202 const nsTArray<RefPtr<nsIWebTransportHash>>* servCertHashes = 203 gHttpHandler->ConnMgr()->GetServerCertHashes(mConnInfo); 204 return servCertHashes && !servCertHashes->IsEmpty(); 205 }; 206 207 // See https://github.com/mozilla/neqo/issues/2442. 208 // We need to set ECH first before set resumption token. 209 auto config = mConnInfo->GetEchConfig(); 210 if (config.IsEmpty()) { 211 if (StaticPrefs::security_tls_ech_grease_http3() && config.IsEmpty()) { 212 if ((RandomUint64().valueOr(0) % 100) >= 213 100 - StaticPrefs::security_tls_ech_grease_probability()) { 214 // Setting an empty config enables GREASE mode. 215 mSocketControl->SetEchConfig(config); 216 mEchExtensionStatus = EchExtensionStatus::kGREASE; 217 } 218 } 219 } else if (nsHttpHandler::EchConfigEnabled(true) && !config.IsEmpty()) { 220 mSocketControl->SetEchConfig(config); 221 mEchExtensionStatus = EchExtensionStatus::kReal; 222 HttpConnectionActivity activity( 223 mConnInfo->HashKey(), mConnInfo->GetOrigin(), mConnInfo->OriginPort(), 224 mConnInfo->EndToEndSSL(), !mConnInfo->GetEchConfig().IsEmpty(), 225 mConnInfo->IsHttp3()); 226 gHttpHandler->ObserveHttpActivityWithArgs( 227 activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION, 228 NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET, PR_Now(), 0, ""_ns); 229 } else { 230 mEchExtensionStatus = EchExtensionStatus::kNotPresent; 231 } 232 233 // In WebTransport, when servCertHashes is specified, it indicates that the 234 // connection to the WebTransport server should authenticate using the 235 // expected certificate hash. Therefore, 0RTT should be disabled in this 236 // context to ensure the certificate hash is checked. 237 if (StaticPrefs::network_http_http3_enable_0rtt() && !hasServCertHashes() && 238 NS_SUCCEEDED(SSLTokensCache::Get(peerId, token, info))) { 239 LOG(("Found a resumption token in the cache.")); 240 mHttp3Connection->SetResumptionToken(token); 241 mSocketControl->SetSessionCacheInfo(std::move(info)); 242 if (mHttp3Connection->IsZeroRtt()) { 243 LOG(("Can send ZeroRtt data")); 244 RefPtr<Http3Session> self(this); 245 mState = ZERORTT; 246 udpConn->ChangeConnectionState(ConnectionState::ZERORTT); 247 mZeroRttStarted = TimeStamp::Now(); 248 // Let the nsHttpConnectionMgr know that the connection can accept 249 // transactions. 250 // We need to dispatch the following function to this thread so that 251 // it is executed after the current function. At this point a 252 // Http3Session is still being initialized and ReportHttp3Connection 253 // will try to dispatch transaction on this session therefore it 254 // needs to be executed after the initializationg is done. 255 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread( 256 NS_NewRunnableFunction("Http3Session::ReportHttp3Connection", 257 [self]() { self->ReportHttp3Connection(); })); 258 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 259 "NS_DispatchToCurrentThread failed"); 260 } 261 } 262 263 #ifndef ANDROID 264 if (mState != ZERORTT) { 265 ZeroRttTelemetry(ZeroRttOutcome::NOT_USED); 266 } 267 #endif 268 269 // After this line, Http3Session and HttpConnectionUDP become a cycle. We put 270 // this line in the end of Http3Session::Init to make sure Http3Session can be 271 // released when Http3Session::Init early returned. 272 mUdpConn = udpConn; 273 return NS_OK; 274 } 275 276 void Http3Session::DoSetEchConfig(const nsACString& aEchConfig) { 277 LOG(("Http3Session::DoSetEchConfig %p of length %zu", this, 278 aEchConfig.Length())); 279 nsTArray<uint8_t> config; 280 config.AppendElements( 281 reinterpret_cast<const uint8_t*>(aEchConfig.BeginReading()), 282 aEchConfig.Length()); 283 mHttp3Connection->SetEchConfig(config); 284 } 285 286 nsresult Http3Session::SendPriorityUpdateFrame(uint64_t aStreamId, 287 uint8_t aPriorityUrgency, 288 bool aPriorityIncremental) { 289 return mHttp3Connection->PriorityUpdate(aStreamId, aPriorityUrgency, 290 aPriorityIncremental); 291 } 292 293 // Shutdown the http3session and close all transactions. 294 void Http3Session::Shutdown() { 295 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 296 297 if (mTimer) { 298 mTimer->Cancel(); 299 } 300 mTimer = nullptr; 301 mTimerCallback = nullptr; 302 303 bool isEchRetry = mError == mozilla::psm::GetXPCOMFromNSSError( 304 SSL_ERROR_ECH_RETRY_WITH_ECH); 305 bool isNSSError = psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(mError)); 306 bool allowToRetryWithDifferentIPFamily = 307 mBeforeConnectedError && 308 gHttpHandler->ConnMgr()->AllowToRetryDifferentIPFamilyForHttp3(mConnInfo, 309 mError); 310 LOG(("Http3Session::Shutdown %p allowToRetryWithDifferentIPFamily=%d", this, 311 allowToRetryWithDifferentIPFamily)); 312 if ((mBeforeConnectedError || 313 (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR)) && 314 !isNSSError && !isEchRetry && !mConnInfo->GetWebTransport() && 315 !allowToRetryWithDifferentIPFamily && !mDontExclude) { 316 gHttpHandler->ExcludeHttp3(mConnInfo); 317 if (mFirstHttpTransaction) { 318 mFirstHttpTransaction->DisableHttp3(false); 319 } 320 } 321 322 for (const auto& stream : mStreamTransactionHash.Values()) { 323 if (mBeforeConnectedError) { 324 // We have an error before we were connected, just restart transactions. 325 // The transaction restart code path will remove AltSvc mapping and the 326 // direct path will be used. 327 MOZ_ASSERT(NS_FAILED(mError)); 328 if (isEchRetry) { 329 // We have to propagate this error to nsHttpTransaction, so the 330 // transaction will be restarted with a new echConfig. 331 stream->Close(mError); 332 } else if (isNSSError) { 333 stream->Close(mError); 334 } else { 335 if (allowToRetryWithDifferentIPFamily && mNetAddr) { 336 NetAddr addr; 337 mNetAddr->GetNetAddr(&addr); 338 gHttpHandler->ConnMgr()->SetRetryDifferentIPFamilyForHttp3( 339 mConnInfo, addr.raw.family); 340 // We want the transaction to be restarted with the same connection 341 // info. 342 stream->Transaction()->DoNotRemoveAltSvc(); 343 // We already set the preference in SetRetryDifferentIPFamilyForHttp3, 344 // so we want to keep it for the next retry. 345 stream->Transaction()->DoNotResetIPFamilyPreference(); 346 stream->Close(NS_ERROR_NET_RESET); 347 // Since Http3Session::Shutdown can be called multiple times, we set 348 // mDontExclude for not putting this domain into the excluded list. 349 mDontExclude = true; 350 } else { 351 stream->Close(NS_ERROR_NET_RESET); 352 } 353 } 354 } else if (!stream->HasStreamId()) { 355 if (NS_SUCCEEDED(mError)) { 356 // Connection has not been started yet. We can restart it. 357 stream->Transaction()->DoNotRemoveAltSvc(); 358 } 359 stream->Close(NS_ERROR_NET_RESET); 360 } else if (stream->GetHttp3Stream() && 361 stream->GetHttp3Stream()->RecvdData()) { 362 stream->Close(NS_ERROR_NET_PARTIAL_TRANSFER); 363 } else if (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR) { 364 stream->Close(NS_ERROR_NET_HTTP3_PROTOCOL_ERROR); 365 } else if (mError == NS_ERROR_NET_RESET) { 366 stream->Close(NS_ERROR_NET_RESET); 367 } else { 368 stream->Close(NS_ERROR_ABORT); 369 } 370 RemoveStreamFromQueues(stream); 371 if (stream->HasStreamId()) { 372 mStreamIdHash.Remove(stream->StreamId()); 373 } 374 } 375 mStreamTransactionHash.Clear(); 376 377 for (const auto& stream : mWebTransportSessions) { 378 stream->Close(NS_ERROR_ABORT); 379 RemoveStreamFromQueues(stream); 380 mStreamIdHash.Remove(stream->StreamId()); 381 } 382 mWebTransportSessions.Clear(); 383 384 for (const auto& stream : mTunnelStreams) { 385 stream->Close(NS_ERROR_ABORT); 386 RemoveStreamFromQueues(stream); 387 mStreamIdHash.Remove(stream->StreamId()); 388 } 389 mTunnelStreams.Clear(); 390 391 for (const auto& stream : mWebTransportStreams) { 392 stream->Close(NS_ERROR_ABORT); 393 RemoveStreamFromQueues(stream); 394 mStreamIdHash.Remove(stream->StreamId()); 395 } 396 397 RefPtr<Http3StreamBase> stream; 398 while ((stream = mQueuedStreams.PopFront())) { 399 LOG(("Close remaining stream in queue:%p", stream.get())); 400 stream->SetQueued(false); 401 stream->Close(NS_ERROR_ABORT); 402 } 403 mWebTransportStreams.Clear(); 404 } 405 406 Http3Session::~Http3Session() { 407 LOG3(("Http3Session::~Http3Session %p", this)); 408 #ifndef ANDROID 409 EchOutcomeTelemetry(); 410 #endif 411 glean::http3::request_per_conn.AccumulateSingleSample(mTransactionCount); 412 glean::http3::blocked_by_stream_limit_per_conn.AccumulateSingleSample( 413 mBlockedByStreamLimitCount); 414 glean::http3::trans_blocked_by_stream_limit_per_conn.AccumulateSingleSample( 415 mTransactionsBlockedByStreamLimitCount); 416 417 glean::http3::trans_sending_blocked_by_flow_control_per_conn 418 .AccumulateSingleSample(mTransactionsSenderBlockedByFlowControlCount); 419 420 if (mTrrStreams) { 421 mozilla::glean::networking::trr_request_count_per_conn 422 .Get(nsPrintfCString("%s_h3", mConnInfo->Origin())) 423 .Add(static_cast<int32_t>(mTrrStreams)); 424 } 425 426 Shutdown(); 427 } 428 429 // This function may return a socket error. 430 // It will not return an error if socket error is 431 // NS_BASE_STREAM_WOULD_BLOCK. 432 // A caller of this function will close the Http3 connection 433 // in case of an error. 434 // The only callers is Http3Session::RecvData. 435 nsresult Http3Session::ProcessInput(nsIUDPSocket* socket) { 436 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 437 MOZ_ASSERT(mUdpConn); 438 439 LOG(("Http3Session::ProcessInput writer=%p [this=%p state=%d]", 440 mUdpConn.get(), this, mState)); 441 442 if (!socket || socket->IsSocketClosed()) { 443 MOZ_DIAGNOSTIC_ASSERT(false, "UDP socket should still be open"); 444 return NS_ERROR_UNEXPECTED; 445 } 446 447 if (mUseNSPRForIO) { 448 while (true) { 449 nsTArray<uint8_t> data; 450 NetAddr addr{}; 451 // RecvWithAddr actually does not return an error. 452 nsresult rv = socket->RecvWithAddr(&addr, data); 453 MOZ_ALWAYS_SUCCEEDS(rv); 454 if (NS_FAILED(rv) || data.IsEmpty()) { 455 break; 456 } 457 rv = mHttp3Connection->ProcessInputUseNSPRForIO(addr, data); 458 MOZ_ALWAYS_SUCCEEDS(rv); 459 if (NS_FAILED(rv)) { 460 break; 461 } 462 463 LOG(("Http3Session::ProcessInput received=%zu", data.Length())); 464 mTotalBytesRead += static_cast<int64_t>(data.Length()); 465 } 466 467 return NS_OK; 468 } 469 470 // Not using NSPR. 471 472 auto rv = mHttp3Connection->ProcessInput(); 473 // Note: WOULD_BLOCK is handled in neqo_glue. 474 if (NS_FAILED(rv.result)) { 475 mSocketError = rv.result; 476 // If there was an error return from here. We do not need to set a timer, 477 // because we will close the connection. 478 return rv.result; 479 } 480 mTotalBytesRead += rv.bytes_read; 481 socket->AddInputBytes(rv.bytes_read); 482 483 return NS_OK; 484 } 485 486 nsresult Http3Session::ProcessTransactionRead(uint64_t stream_id) { 487 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(stream_id); 488 if (!stream) { 489 LOG( 490 ("Http3Session::ProcessTransactionRead - stream not found " 491 "stream_id=0x%" PRIx64 " [this=%p].", 492 stream_id, this)); 493 return NS_OK; 494 } 495 496 return ProcessTransactionRead(stream); 497 } 498 499 nsresult Http3Session::ProcessTransactionRead(Http3StreamBase* stream) { 500 nsresult rv = stream->WriteSegments(); 501 502 if (ASpdySession::SoftStreamError(rv) || stream->Done()) { 503 LOG3( 504 ("Http3Session::ProcessSingleTransactionRead session=%p stream=%p " 505 "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n", 506 this, stream, stream->StreamId(), static_cast<uint32_t>(rv), 507 stream->Done())); 508 CloseStream(stream, 509 (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED : NS_OK); 510 return NS_OK; 511 } 512 513 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) { 514 return rv; 515 } 516 return NS_OK; 517 } 518 519 nsresult Http3Session::ProcessEvents() { 520 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 521 522 LOG(("Http3Session::ProcessEvents [this=%p]", this)); 523 524 // We need an array to pick up header data or a resumption token. 525 nsTArray<uint8_t> data; 526 Http3Event event{}; 527 event.tag = Http3Event::Tag::NoEvent; 528 529 nsresult rv = mHttp3Connection->GetEvent(&event, data); 530 if (NS_FAILED(rv)) { 531 LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this, 532 static_cast<uint32_t>(rv))); 533 return rv; 534 } 535 536 while (event.tag != Http3Event::Tag::NoEvent) { 537 switch (event.tag) { 538 case Http3Event::Tag::HeaderReady: { 539 MOZ_ASSERT(mState == CONNECTED); 540 LOG(("Http3Session::ProcessEvents - HeaderReady")); 541 uint64_t id = event.header_ready.stream_id; 542 543 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id); 544 if (!stream) { 545 LOG( 546 ("Http3Session::ProcessEvents - HeaderReady - stream not found " 547 "stream_id=0x%" PRIx64 " [this=%p].", 548 id, this)); 549 break; 550 } 551 552 MOZ_RELEASE_ASSERT(stream->GetHttp3Stream(), 553 "This must be a Http3Stream"); 554 555 stream->SetResponseHeaders(data, event.header_ready.fin, 556 event.header_ready.interim); 557 558 rv = ProcessTransactionRead(stream); 559 560 if (NS_FAILED(rv)) { 561 LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this, 562 static_cast<uint32_t>(rv))); 563 return rv; 564 } 565 566 mUdpConn->NotifyDataRead(); 567 break; 568 } 569 case Http3Event::Tag::DataReadable: { 570 MOZ_ASSERT(mState == CONNECTED); 571 LOG(("Http3Session::ProcessEvents - DataReadable")); 572 uint64_t id = event.data_readable.stream_id; 573 574 nsresult rv = ProcessTransactionRead(id); 575 576 if (NS_FAILED(rv)) { 577 LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this, 578 static_cast<uint32_t>(rv))); 579 return rv; 580 } 581 break; 582 } 583 case Http3Event::Tag::DataWritable: { 584 MOZ_ASSERT(CanSendData()); 585 LOG(("Http3Session::ProcessEvents - DataWritable")); 586 587 RefPtr<Http3StreamBase> stream = 588 mStreamIdHash.Get(event.data_writable.stream_id); 589 590 if (stream) { 591 StreamReadyToWrite(stream); 592 stream->SetBlockedByFlowControl(false); 593 } 594 } break; 595 case Http3Event::Tag::Reset: 596 LOG(("Http3Session::ProcessEvents %p - Reset", this)); 597 ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error, 598 RESET); 599 break; 600 case Http3Event::Tag::StopSending: 601 LOG( 602 ("Http3Session::ProcessEvents %p - StopSeniding with error " 603 "0x%" PRIx64, 604 this, event.stop_sending.error)); 605 if (event.stop_sending.error == HTTP3_APP_ERROR_NO_ERROR) { 606 RefPtr<Http3StreamBase> stream = 607 mStreamIdHash.Get(event.data_writable.stream_id); 608 if (stream) { 609 RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream(); 610 MOZ_RELEASE_ASSERT(httpStream, "This must be a Http3Stream"); 611 httpStream->StopSending(); 612 } 613 } else { 614 ResetOrStopSendingRecvd(event.reset.stream_id, event.reset.error, 615 STOP_SENDING); 616 } 617 break; 618 case Http3Event::Tag::PushPromise: 619 LOG(("Http3Session::ProcessEvents - PushPromise")); 620 break; 621 case Http3Event::Tag::PushHeaderReady: 622 LOG(("Http3Session::ProcessEvents - PushHeaderReady")); 623 break; 624 case Http3Event::Tag::PushDataReadable: 625 LOG(("Http3Session::ProcessEvents - PushDataReadable")); 626 break; 627 case Http3Event::Tag::PushCanceled: 628 LOG(("Http3Session::ProcessEvents - PushCanceled")); 629 break; 630 case Http3Event::Tag::RequestsCreatable: 631 LOG(("Http3Session::ProcessEvents - StreamCreatable")); 632 ProcessPending(); 633 break; 634 case Http3Event::Tag::AuthenticationNeeded: 635 LOG(("Http3Session::ProcessEvents - AuthenticationNeeded %d", 636 mAuthenticationStarted)); 637 if (!mAuthenticationStarted) { 638 mAuthenticationStarted = true; 639 LOG(("Http3Session::ProcessEvents - AuthenticationNeeded called")); 640 CallCertVerification(Nothing()); 641 } 642 break; 643 case Http3Event::Tag::ZeroRttRejected: 644 LOG(("Http3Session::ProcessEvents - ZeroRttRejected")); 645 if (mState == ZERORTT) { 646 mState = INITIALIZING; 647 mTransactionCount = 0; 648 Finish0Rtt(true); 649 #ifndef ANDROID 650 ZeroRttTelemetry(ZeroRttOutcome::USED_REJECTED); 651 #endif 652 } 653 break; 654 case Http3Event::Tag::ResumptionToken: { 655 LOG(("Http3Session::ProcessEvents - ResumptionToken")); 656 if (StaticPrefs::network_http_http3_enable_0rtt() && !data.IsEmpty()) { 657 LOG(("Got a resumption token")); 658 nsAutoCString peerId; 659 mSocketControl->GetPeerId(peerId); 660 if (NS_FAILED(SSLTokensCache::Put( 661 peerId, data.Elements(), data.Length(), mSocketControl, 662 PR_Now() + event.resumption_token.expire_in))) { 663 LOG(("Adding resumption token failed")); 664 } 665 } 666 } break; 667 case Http3Event::Tag::ConnectionConnected: { 668 LOG(("Http3Session::ProcessEvents - ConnectionConnected")); 669 bool was0RTT = mState == ZERORTT; 670 mState = CONNECTED; 671 SetSecInfo(); 672 mSocketControl->HandshakeCompleted(); 673 if (was0RTT) { 674 Finish0Rtt(false); 675 #ifndef ANDROID 676 ZeroRttTelemetry(ZeroRttOutcome::USED_SUCCEEDED); 677 #endif 678 } 679 680 OnTransportStatus(nullptr, NS_NET_STATUS_CONNECTED_TO, 0); 681 mUdpConn->OnConnected(); 682 ReportHttp3Connection(); 683 // Maybe call ResumeSend: 684 // In case ZeroRtt has been used and it has been rejected, 2 events will 685 // be received: ZeroRttRejected and ConnectionConnected. ZeroRttRejected 686 // that will put all transaction into mReadyForWrite queue and it will 687 // call MaybeResumeSend, but that will not have an effect because the 688 // connection is ont in CONNECTED state. When ConnectionConnected event 689 // is received call MaybeResumeSend to trigger reads for the 690 // zero-rtt-rejected transactions. 691 MaybeResumeSend(); 692 } break; 693 case Http3Event::Tag::GoawayReceived: 694 LOG(("Http3Session::ProcessEvents - GoawayReceived")); 695 mUdpConn->SetCloseReason(ConnectionCloseReason::GO_AWAY); 696 mGoawayReceived = true; 697 break; 698 case Http3Event::Tag::ConnectionClosing: 699 LOG(("Http3Session::ProcessEvents - ConnectionClosing")); 700 if (NS_SUCCEEDED(mError) && !IsClosing()) { 701 mError = NS_ERROR_NET_HTTP3_PROTOCOL_ERROR; 702 CloseConnectionTelemetry(event.connection_closing.error, true); 703 704 auto isStatelessResetOrNoError = [](CloseError& aError) -> bool { 705 if (aError.tag == CloseError::Tag::TransportInternalErrorOther && 706 aError.transport_internal_error_other._0 == 707 TRANSPORT_ERROR_STATELESS_RESET) { 708 return true; 709 } 710 if (aError.tag == CloseError::Tag::TransportError && 711 aError.transport_error._0 == 0) { 712 return true; 713 } 714 if (aError.tag == CloseError::Tag::PeerError && 715 aError.peer_error._0 == 0) { 716 return true; 717 } 718 if (aError.tag == CloseError::Tag::AppError && 719 aError.app_error._0 == HTTP3_APP_ERROR_NO_ERROR) { 720 return true; 721 } 722 if (aError.tag == CloseError::Tag::PeerAppError && 723 aError.peer_app_error._0 == HTTP3_APP_ERROR_NO_ERROR) { 724 return true; 725 } 726 return false; 727 }; 728 if (isStatelessResetOrNoError(event.connection_closing.error)) { 729 mError = NS_ERROR_NET_RESET; 730 } 731 if (event.connection_closing.error.tag == CloseError::Tag::EchRetry) { 732 mSocketControl->SetRetryEchConfig(Substring( 733 reinterpret_cast<const char*>(data.Elements()), data.Length())); 734 mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH); 735 } 736 } 737 return mError; 738 break; 739 case Http3Event::Tag::ConnectionClosed: 740 LOG(("Http3Session::ProcessEvents - ConnectionClosed")); 741 if (NS_SUCCEEDED(mError)) { 742 mError = NS_ERROR_NET_TIMEOUT; 743 mUdpConn->SetCloseReason(ConnectionCloseReason::IDLE_TIMEOUT); 744 CloseConnectionTelemetry(event.connection_closed.error, false); 745 } 746 mIsClosedByNeqo = true; 747 if (event.connection_closed.error.tag == CloseError::Tag::EchRetry) { 748 mSocketControl->SetRetryEchConfig(Substring( 749 reinterpret_cast<const char*>(data.Elements()), data.Length())); 750 mError = psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH); 751 } 752 LOG(("Http3Session::ProcessEvents - ConnectionClosed error=%" PRIx32, 753 static_cast<uint32_t>(mError))); 754 // We need to return here and let HttpConnectionUDP close the session. 755 return mError; 756 break; 757 case Http3Event::Tag::EchFallbackAuthenticationNeeded: { 758 nsCString echPublicName(reinterpret_cast<const char*>(data.Elements()), 759 data.Length()); 760 LOG( 761 ("Http3Session::ProcessEvents - EchFallbackAuthenticationNeeded " 762 "echPublicName=%s", 763 echPublicName.get())); 764 if (!mAuthenticationStarted) { 765 mAuthenticationStarted = true; 766 CallCertVerification(Some(echPublicName)); 767 } 768 } break; 769 case Http3Event::Tag::WebTransport: { 770 switch (event.web_transport._0.tag) { 771 case WebTransportEventExternal::Tag::Negotiated: 772 LOG(("Http3Session::ProcessEvents - WebTransport %d", 773 event.web_transport._0.negotiated._0)); 774 FinishNegotiation(ExtendedConnectKind::WebTransport, 775 event.web_transport._0.negotiated._0); 776 break; 777 case WebTransportEventExternal::Tag::Session: { 778 MOZ_ASSERT(mState == CONNECTED); 779 780 uint64_t id = event.web_transport._0.session._0; 781 LOG( 782 ("Http3Session::ProcessEvents - WebTransport Session " 783 " sessionId=0x%" PRIx64, 784 id)); 785 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id); 786 if (!stream) { 787 LOG( 788 ("Http3Session::ProcessEvents - WebTransport Session - " 789 "stream not found " 790 "stream_id=0x%" PRIx64 " [this=%p].", 791 id, this)); 792 break; 793 } 794 795 MOZ_RELEASE_ASSERT(stream->GetHttp3WebTransportSession(), 796 "It must be a WebTransport session"); 797 stream->SetResponseHeaders(data, false, false); 798 799 rv = stream->WriteSegments(); 800 801 if (ASpdySession::SoftStreamError(rv) || stream->Done()) { 802 LOG3( 803 ("Http3Session::ProcessSingleTransactionRead session=%p " 804 "stream=%p " 805 "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n", 806 this, stream.get(), stream->StreamId(), 807 static_cast<uint32_t>(rv), stream->Done())); 808 // We need to keep the transaction, so we can use it to remove the 809 // stream from mStreamTransactionHash. 810 nsAHttpTransaction* trans = stream->Transaction(); 811 if (mStreamTransactionHash.Contains(trans)) { 812 CloseStream(stream, (rv == NS_BINDING_RETARGETED) 813 ? NS_BINDING_RETARGETED 814 : NS_OK); 815 mStreamTransactionHash.Remove(trans); 816 } else { 817 stream->GetHttp3WebTransportSession()->TransactionIsDone( 818 (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED 819 : NS_OK); 820 } 821 break; 822 } 823 824 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) { 825 LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this, 826 static_cast<uint32_t>(rv))); 827 return rv; 828 } 829 } break; 830 case WebTransportEventExternal::Tag::SessionClosed: { 831 uint64_t id = event.web_transport._0.session_closed.stream_id; 832 LOG( 833 ("Http3Session::ProcessEvents - WebTransport SessionClosed " 834 " sessionId=0x%" PRIx64, 835 id)); 836 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id); 837 if (!stream) { 838 LOG( 839 ("Http3Session::ProcessEvents - WebTransport SessionClosed - " 840 "stream not found " 841 "stream_id=0x%" PRIx64 " [this=%p].", 842 id, this)); 843 break; 844 } 845 846 RefPtr<Http3WebTransportSession> wt = 847 stream->GetHttp3WebTransportSession(); 848 MOZ_RELEASE_ASSERT(wt, "It must be a WebTransport session"); 849 850 bool cleanly = false; 851 852 // TODO we do not handle the case when a WebTransport session stream 853 // is closed before headers are sent. 854 SessionCloseReasonExternal& reasonExternal = 855 event.web_transport._0.session_closed.reason; 856 uint32_t status = 0; 857 nsCString reason = ""_ns; 858 if (reasonExternal.tag == SessionCloseReasonExternal::Tag::Error) { 859 status = reasonExternal.error._0; 860 } else if (reasonExternal.tag == 861 SessionCloseReasonExternal::Tag::Status) { 862 status = reasonExternal.status._0; 863 cleanly = true; 864 } else { 865 status = reasonExternal.clean._0; 866 reason.Assign(reinterpret_cast<const char*>(data.Elements()), 867 data.Length()); 868 cleanly = true; 869 } 870 LOG(("reason.tag=%u err=%u data=%s\n", 871 static_cast<uint32_t>(reasonExternal.tag), status, 872 reason.get())); 873 wt->OnSessionClosed(cleanly, status, reason); 874 875 } break; 876 case WebTransportEventExternal::Tag::NewStream: { 877 LOG( 878 ("Http3Session::ProcessEvents - WebTransport NewStream " 879 "streamId=0x%" PRIx64 " sessionId=0x%" PRIx64, 880 event.web_transport._0.new_stream.stream_id, 881 event.web_transport._0.new_stream.session_id)); 882 uint64_t sessionId = event.web_transport._0.new_stream.session_id; 883 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId); 884 if (!stream) { 885 LOG( 886 ("Http3Session::ProcessEvents - WebTransport NewStream - " 887 "session not found " 888 "sessionId=0x%" PRIx64 " [this=%p].", 889 sessionId, this)); 890 break; 891 } 892 893 RefPtr<Http3WebTransportSession> wt = 894 stream->GetHttp3WebTransportSession(); 895 if (!wt) { 896 break; 897 } 898 899 RefPtr<Http3WebTransportStream> wtStream = 900 wt->OnIncomingWebTransportStream( 901 event.web_transport._0.new_stream.stream_type, 902 event.web_transport._0.new_stream.stream_id); 903 if (!wtStream) { 904 break; 905 } 906 907 // WebTransportStream is managed by Http3Session now. 908 mWebTransportStreams.AppendElement(wtStream); 909 mWebTransportStreamToSessionMap.InsertOrUpdate(wtStream->StreamId(), 910 wt->StreamId()); 911 mStreamIdHash.InsertOrUpdate(wtStream->StreamId(), 912 std::move(wtStream)); 913 } break; 914 case WebTransportEventExternal::Tag::Datagram: 915 LOG( 916 ("Http3Session::ProcessEvents - " 917 "WebTransportEventExternal::Tag::Datagram [this=%p]", 918 this)); 919 uint64_t sessionId = event.web_transport._0.datagram.session_id; 920 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(sessionId); 921 if (!stream) { 922 LOG( 923 ("Http3Session::ProcessEvents - WebTransport Datagram - " 924 "session not found " 925 "sessionId=0x%" PRIx64 " [this=%p].", 926 sessionId, this)); 927 break; 928 } 929 930 RefPtr<Http3WebTransportSession> wt = 931 stream->GetHttp3WebTransportSession(); 932 if (!wt) { 933 break; 934 } 935 936 wt->OnDatagramReceived(std::move(data)); 937 break; 938 } 939 } break; 940 case Http3Event::Tag::ConnectUdp: { 941 switch (event.connect_udp._0.tag) { 942 case ConnectUdpEventExternal::Tag::Negotiated: 943 LOG(("Http3Session::ProcessEvents - ConnectUdp Negotiated %d", 944 event.connect_udp._0.negotiated._0)); 945 FinishNegotiation(ExtendedConnectKind::ConnectUDP, 946 event.connect_udp._0.negotiated._0); 947 break; 948 case ConnectUdpEventExternal::Tag::Session: { 949 MOZ_ASSERT(mState == CONNECTED); 950 951 uint64_t id = event.connect_udp._0.session._0; 952 LOG( 953 ("Http3Session::ProcessEvents - ConnectUdp " 954 " streamId=0x%" PRIx64, 955 id)); 956 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id); 957 if (!stream) { 958 LOG( 959 ("Http3Session::ProcessEvents - ConnectUdp Session - " 960 "stream not found " 961 "stream_id=0x%" PRIx64 " [this=%p].", 962 id, this)); 963 break; 964 } 965 966 MOZ_RELEASE_ASSERT(stream->GetHttp3ConnectUDPStream(), 967 "It must be a ConnectUdp session"); 968 stream->SetResponseHeaders(data, false, false); 969 970 rv = stream->WriteSegments(); 971 972 LOG(("rv=%x", static_cast<uint32_t>(rv))); 973 974 if (ASpdySession::SoftStreamError(rv) || stream->Done()) { 975 LOG3( 976 ("Http3Session::ProcessSingleTransactionRead session=%p " 977 "stream=%p " 978 "0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n", 979 this, stream.get(), stream->StreamId(), 980 static_cast<uint32_t>(rv), stream->Done())); 981 // We need to keep the transaction, so we can use it to remove the 982 // stream from mStreamTransactionHash. 983 nsAHttpTransaction* trans = stream->Transaction(); 984 if (mStreamTransactionHash.Contains(trans)) { 985 CloseStream(stream, (rv == NS_BINDING_RETARGETED) 986 ? NS_BINDING_RETARGETED 987 : NS_OK); 988 mStreamTransactionHash.Remove(trans); 989 } else { 990 stream->GetHttp3ConnectUDPStream()->TransactionIsDone( 991 (rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED 992 : NS_OK); 993 } 994 break; 995 } 996 997 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) { 998 LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this, 999 static_cast<uint32_t>(rv))); 1000 return rv; 1001 } 1002 } break; 1003 case ConnectUdpEventExternal::Tag::SessionClosed: { 1004 uint64_t id = event.connect_udp._0.session_closed.stream_id; 1005 LOG( 1006 ("Http3Session::ProcessEvents - connect_udp SessionClosed " 1007 " sessionId=0x%" PRIx64, 1008 id)); 1009 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id); 1010 if (!stream) { 1011 LOG( 1012 ("Http3Session::ProcessEvents - connect_udp SessionClosed - " 1013 "stream not found " 1014 "stream_id=0x%" PRIx64 " [this=%p].", 1015 id, this)); 1016 break; 1017 } 1018 1019 RefPtr<Http3ConnectUDPStream> connectUDPStream = 1020 stream->GetHttp3ConnectUDPStream(); 1021 MOZ_RELEASE_ASSERT(connectUDPStream, 1022 "It must be a ConnectUDP stream"); 1023 1024 // TODO we do not handle the case when a ConnectUDP session stream 1025 // is closed before headers are sent. 1026 SessionCloseReasonExternal& reasonExternal = 1027 event.connect_udp._0.session_closed.reason; 1028 uint32_t status = 0; 1029 nsCString reason = ""_ns; 1030 if (reasonExternal.tag == SessionCloseReasonExternal::Tag::Error) { 1031 status = reasonExternal.error._0; 1032 } else if (reasonExternal.tag == 1033 SessionCloseReasonExternal::Tag::Status) { 1034 status = reasonExternal.status._0; 1035 } else { 1036 status = reasonExternal.clean._0; 1037 reason.Assign(reinterpret_cast<const char*>(data.Elements()), 1038 data.Length()); 1039 } 1040 LOG(("reason.tag=%u err=%u data=%s\n", 1041 static_cast<uint32_t>(reasonExternal.tag), status, 1042 reason.get())); 1043 CloseStream(connectUDPStream, 1044 status == 0 ? NS_OK : NS_ERROR_FAILURE); 1045 } break; 1046 case ConnectUdpEventExternal::Tag::Datagram: 1047 LOG(("Http3Session::ProcessEvents - ConnectUdp Datagram")); 1048 uint64_t streamId = event.connect_udp._0.datagram.session_id; 1049 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(streamId); 1050 if (!stream) { 1051 LOG( 1052 ("Http3Session::ProcessEvents - ConnectUdp Datagram - " 1053 "stream not found " 1054 "streamId=0x%" PRIx64 " [this=%p].", 1055 streamId, this)); 1056 break; 1057 } 1058 1059 RefPtr<Http3ConnectUDPStream> tunnelStream = 1060 stream->GetHttp3ConnectUDPStream(); 1061 if (!tunnelStream) { 1062 break; 1063 } 1064 tunnelStream->OnDatagramReceived(std::move(data)); 1065 break; 1066 } 1067 } break; 1068 default: 1069 break; 1070 } 1071 // Delete previous content of data 1072 data.TruncateLength(0); 1073 rv = mHttp3Connection->GetEvent(&event, data); 1074 if (NS_FAILED(rv)) { 1075 LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this, 1076 static_cast<uint32_t>(rv))); 1077 return rv; 1078 } 1079 } 1080 1081 return NS_OK; 1082 } // namespace net 1083 1084 // This function may return a socket error. 1085 // It will not return an error if socket error is 1086 // NS_BASE_STREAM_WOULD_BLOCK. 1087 // A Caller of this function will close the Http3 connection 1088 // if this function returns an error. 1089 // Callers are: 1090 // 1) HttpConnectionUDP::OnQuicTimeoutExpired 1091 // 2) HttpConnectionUDP::SendData -> 1092 // Http3Session::SendData 1093 nsresult Http3Session::ProcessOutput(nsIUDPSocket* socket) { 1094 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1095 MOZ_ASSERT(mUdpConn); 1096 1097 LOG(("Http3Session::ProcessOutput reader=%p, [this=%p]", mUdpConn.get(), 1098 this)); 1099 1100 if (!socket || socket->IsSocketClosed()) { 1101 MOZ_DIAGNOSTIC_ASSERT(false, "UDP socket should still be open"); 1102 return NS_ERROR_UNEXPECTED; 1103 } 1104 1105 if (mUseNSPRForIO) { 1106 mSocket = socket; 1107 nsresult rv = mHttp3Connection->ProcessOutputAndSendUseNSPRForIO( 1108 this, 1109 [](void* aContext, uint16_t aFamily, const uint8_t* aAddr, 1110 uint16_t aPort, const uint8_t* aData, uint32_t aLength) { 1111 Http3Session* self = (Http3Session*)aContext; 1112 1113 uint32_t written = 0; 1114 NetAddr addr; 1115 if (NS_FAILED(RawBytesToNetAddr(aFamily, aAddr, aPort, &addr))) { 1116 return NS_OK; 1117 } 1118 1119 LOG3( 1120 ("Http3Session::ProcessOutput sending packet with %u bytes to %s " 1121 "port=%d [this=%p].", 1122 aLength, addr.ToString().get(), aPort, self)); 1123 1124 nsresult rv = 1125 self->mSocket->SendWithAddress(&addr, aData, aLength, &written); 1126 1127 LOG(("Http3Session::ProcessOutput sending packet rv=%d osError=%d", 1128 static_cast<int32_t>(rv), NS_FAILED(rv) ? PR_GetOSError() : 0)); 1129 if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) { 1130 self->mSocketError = rv; 1131 // If there was an error that is not NS_BASE_STREAM_WOULD_BLOCK 1132 // return from here. We do not need to set a timer, because we 1133 // will close the connection. 1134 return rv; 1135 } 1136 self->mTotalBytesWritten += aLength; 1137 self->mLastWriteTime = PR_IntervalNow(); 1138 return NS_OK; 1139 }, 1140 [](void* aContext, uint64_t timeout) { 1141 Http3Session* self = (Http3Session*)aContext; 1142 self->SetupTimer(timeout); 1143 }); 1144 mSocket = nullptr; 1145 return rv; 1146 } 1147 1148 // Not using NSPR. 1149 1150 auto rv = mHttp3Connection->ProcessOutputAndSend( 1151 this, [](void* aContext, uint64_t timeout) { 1152 Http3Session* self = (Http3Session*)aContext; 1153 self->SetupTimer(timeout); 1154 }); 1155 if (rv.result == NS_BASE_STREAM_WOULD_BLOCK) { 1156 // The OS buffer was full. Tell the UDP socket to poll for 1157 // write-availability. 1158 socket->EnableWritePoll(); 1159 } else if (NS_FAILED(rv.result)) { 1160 mSocketError = rv.result; 1161 // If there was an error return from here. We do not need to set a timer, 1162 // because we will close the connection. 1163 return rv.result; 1164 } 1165 if (rv.bytes_written != 0) { 1166 mTotalBytesWritten += rv.bytes_written; 1167 mLastWriteTime = PR_IntervalNow(); 1168 socket->AddOutputBytes(rv.bytes_written); 1169 } 1170 1171 return NS_OK; 1172 } 1173 1174 // This is only called when timer expires. 1175 // It is called by HttpConnectionUDP::OnQuicTimeout. 1176 // If tihs function returns an error OnQuicTimeout will handle the error 1177 // properly and close the connection. 1178 nsresult Http3Session::ProcessOutputAndEvents(nsIUDPSocket* socket) { 1179 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1180 1181 MOZ_ASSERT(mTimerShouldTrigger); 1182 1183 if (Telemetry::CanRecordPrereleaseData()) { 1184 auto now = TimeStamp::Now(); 1185 if (mTimerShouldTrigger > now) { 1186 // See bug 1935459 1187 glean::http3::timer_delayed.AccumulateRawDuration(0); 1188 } else { 1189 glean::http3::timer_delayed.AccumulateRawDuration(now - 1190 mTimerShouldTrigger); 1191 } 1192 } 1193 1194 mTimerShouldTrigger = TimeStamp(); 1195 1196 nsresult rv = SendData(socket); 1197 if (NS_FAILED(rv)) { 1198 return rv; 1199 } 1200 return NS_OK; 1201 } 1202 1203 NS_IMPL_ISUPPORTS(Http3Session::OnQuicTimeout, nsITimerCallback, nsINamed) 1204 1205 Http3Session::OnQuicTimeout::OnQuicTimeout(HttpConnectionUDP* aConnection) 1206 : mConnection(aConnection) { 1207 MOZ_ASSERT(mConnection); 1208 } 1209 1210 NS_IMETHODIMP 1211 Http3Session::OnQuicTimeout::Notify(nsITimer* timer) { 1212 mConnection->OnQuicTimeoutExpired(); 1213 return NS_OK; 1214 } 1215 1216 NS_IMETHODIMP 1217 Http3Session::OnQuicTimeout::GetName(nsACString& aName) { 1218 aName.AssignLiteral("net::HttpConnectionUDP::OnQuicTimeout"); 1219 return NS_OK; 1220 } 1221 1222 void Http3Session::SetupTimer(uint64_t aTimeout) { 1223 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1224 // UINT64_MAX indicated a no-op from neqo, which only happens when a 1225 // connection is in or going to be Closed state. 1226 if (aTimeout == UINT64_MAX) { 1227 return; 1228 } 1229 1230 LOG3( 1231 ("Http3Session::SetupTimer to %" PRIu64 "ms [this=%p].", aTimeout, this)); 1232 1233 // Remember the time when the timer should trigger. 1234 mTimerShouldTrigger = 1235 TimeStamp::Now() + TimeDuration::FromMilliseconds(aTimeout); 1236 1237 if (!mTimerCallback) { 1238 // We can keep the same callback object for all our lifetime. 1239 mTimerCallback = MakeRefPtr<OnQuicTimeout>(mUdpConn); 1240 } 1241 1242 if (!mTimer) { 1243 // This can only fail on OOM and we'd crash. 1244 mTimer = NS_NewTimer(); 1245 } 1246 1247 DebugOnly<nsresult> rv = mTimer->InitWithCallback(mTimerCallback, aTimeout, 1248 nsITimer::TYPE_ONE_SHOT); 1249 // There is no meaningful error handling we can do here. But an error here 1250 // should only be possible if the timer thread did already shut down. 1251 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1252 } 1253 1254 bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction, 1255 int32_t aPriority, 1256 nsIInterfaceRequestor* aCallbacks) { 1257 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1258 1259 nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction(); 1260 1261 bool firstStream = false; 1262 if (!mConnection) { 1263 // Get the connection from the first transaction. 1264 mConnection = aHttpTransaction->Connection(); 1265 firstStream = true; 1266 } 1267 1268 // Make sure we report the connectStart 1269 auto reportConnectStart = MakeScopeExit([&] { 1270 if (firstStream) { 1271 OnTransportStatus(nullptr, NS_NET_STATUS_CONNECTING_TO, 0); 1272 } 1273 }); 1274 1275 if (IsClosing()) { 1276 LOG3( 1277 ("Http3Session::AddStream %p atrans=%p trans=%p session unusable - " 1278 "resched.\n", 1279 this, aHttpTransaction, trans)); 1280 aHttpTransaction->SetConnection(nullptr); 1281 nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority()); 1282 if (NS_FAILED(rv)) { 1283 LOG3( 1284 ("Http3Session::AddStream %p atrans=%p trans=%p failed to initiate " 1285 "transaction (0x%" PRIx32 ").\n", 1286 this, aHttpTransaction, trans, static_cast<uint32_t>(rv))); 1287 } 1288 return true; 1289 } 1290 1291 aHttpTransaction->SetConnection(this); 1292 aHttpTransaction->OnActivated(); 1293 // reset the read timers to wash away any idle time 1294 mLastWriteTime = PR_IntervalNow(); 1295 1296 ClassOfService cos; 1297 if (trans) { 1298 cos = trans->GetClassOfService(); 1299 } 1300 1301 Http3StreamBase* stream = nullptr; 1302 1303 if (trans && mConnInfo->IsHttp3ProxyConnection() && !mIsInTunnel) { 1304 LOG3(("Http3Session::AddStream new connect-udp stream %p atrans=%p.\n", 1305 this, aHttpTransaction)); 1306 stream = new Http3ConnectUDPStream(aHttpTransaction, this, 1307 NS_GetCurrentThread()); 1308 } else if (trans && trans->IsForWebTransport()) { 1309 LOG3(("Http3Session::AddStream new WeTransport session %p atrans=%p.\n", 1310 this, aHttpTransaction)); 1311 stream = new Http3WebTransportSession(aHttpTransaction, this); 1312 mHasWebTransportSession = true; 1313 } else { 1314 LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction)); 1315 stream = new Http3Stream(aHttpTransaction, this, cos, mCurrentBrowserId); 1316 } 1317 1318 mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream}); 1319 1320 if (mState == ZERORTT) { 1321 if (!stream->Do0RTT()) { 1322 LOG(("Http3Session %p will not get early data from Http3Stream %p", this, 1323 stream)); 1324 if (!mCannotDo0RTTStreams.Contains(stream)) { 1325 mCannotDo0RTTStreams.AppendElement(stream); 1326 } 1327 if (stream->GetHttp3WebTransportSession()) { 1328 DeferIfNegotiating(ExtendedConnectKind::WebTransport, stream); 1329 } else if (stream->GetHttp3ConnectUDPStream()) { 1330 DeferIfNegotiating(ExtendedConnectKind::ConnectUDP, stream); 1331 } 1332 return true; 1333 } 1334 m0RTTStreams.AppendElement(stream); 1335 } 1336 1337 if (stream->GetHttp3WebTransportSession()) { 1338 if (DeferIfNegotiating(ExtendedConnectKind::WebTransport, stream)) { 1339 return true; 1340 } 1341 } else if (stream->GetHttp3ConnectUDPStream()) { 1342 if (DeferIfNegotiating(ExtendedConnectKind::ConnectUDP, stream)) { 1343 return true; 1344 } 1345 } 1346 1347 if (!mFirstHttpTransaction && !IsConnected()) { 1348 mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction(); 1349 LOG3(("Http3Session::AddStream first session=%p trans=%p ", this, 1350 mFirstHttpTransaction.get())); 1351 } 1352 StreamReadyToWrite(stream); 1353 1354 return true; 1355 } 1356 1357 bool Http3Session::DeferIfNegotiating(ExtendedConnectKind aKind, 1358 Http3StreamBase* aStream) { 1359 auto& st = ExtState(aKind); 1360 if (st.mStatus == NEGOTIATING) { 1361 if (!st.mWaiters.Contains(aStream)) { 1362 LOG(("waiting for negotiation")); 1363 st.mWaiters.AppendElement(aStream); 1364 } 1365 return true; 1366 } 1367 return false; 1368 } 1369 1370 void Http3Session::FinishNegotiation(ExtendedConnectKind aKind, bool aSuccess) { 1371 auto& st = ExtState(aKind); 1372 if (st.mWaiters.IsEmpty()) { 1373 st.mStatus = aSuccess ? SUCCEEDED : FAILED; 1374 return; 1375 } 1376 1377 MOZ_ASSERT(st.mStatus == NEGOTIATING); 1378 st.mStatus = aSuccess ? SUCCEEDED : FAILED; 1379 1380 for (size_t i = 0; i < st.mWaiters.Length(); ++i) { 1381 if (st.mWaiters[i]) { 1382 mReadyForWrite.Push(st.mWaiters[i]); 1383 } 1384 } 1385 st.mWaiters.Clear(); 1386 MaybeResumeSend(); 1387 } 1388 1389 bool Http3Session::CanReuse() { 1390 // TODO: we assume "pooling" is disabled here, so we don't allow this session 1391 // to be reused. "pooling" will be implemented in bug 1815735. 1392 return CanSendData() && !(mGoawayReceived || mShouldClose) && 1393 !mHasWebTransportSession; 1394 } 1395 1396 void Http3Session::QueueStream(Http3StreamBase* stream) { 1397 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1398 MOZ_ASSERT(!stream->Queued()); 1399 1400 LOG3(("Http3Session::QueueStream %p stream %p queued.", this, stream)); 1401 1402 stream->SetQueued(true); 1403 mQueuedStreams.Push(stream); 1404 } 1405 1406 void Http3Session::ProcessPending() { 1407 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1408 1409 RefPtr<Http3StreamBase> stream; 1410 while ((stream = mQueuedStreams.PopFront())) { 1411 LOG3(("Http3Session::ProcessPending %p stream %p woken from queue.", this, 1412 stream.get())); 1413 MOZ_ASSERT(stream->Queued()); 1414 stream->SetQueued(false); 1415 mReadyForWrite.Push(stream); 1416 } 1417 MaybeResumeSend(); 1418 } 1419 1420 static void RemoveStreamFromQueue(Http3StreamBase* aStream, 1421 nsRefPtrDeque<Http3StreamBase>& queue) { 1422 size_t size = queue.GetSize(); 1423 for (size_t count = 0; count < size; ++count) { 1424 RefPtr<Http3StreamBase> stream = queue.PopFront(); 1425 if (stream != aStream) { 1426 queue.Push(stream); 1427 } 1428 } 1429 } 1430 1431 void Http3Session::RemoveStreamFromQueues(Http3StreamBase* aStream) { 1432 RemoveStreamFromQueue(aStream, mReadyForWrite); 1433 RemoveStreamFromQueue(aStream, mQueuedStreams); 1434 mSlowConsumersReadyForRead.RemoveElement(aStream); 1435 } 1436 1437 // This is called by Http3Stream::OnReadSegment. 1438 // ProcessOutput will be called in Http3Session::ReadSegment that 1439 // calls Http3Stream::OnReadSegment. 1440 nsresult Http3Session::TryActivating( 1441 const nsACString& aMethod, const nsACString& aScheme, 1442 const nsACString& aAuthorityHeader, const nsACString& aPathQuery, 1443 const nsACString& aHeaders, uint64_t* aStreamId, Http3StreamBase* aStream) { 1444 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1445 MOZ_ASSERT(*aStreamId == UINT64_MAX); 1446 1447 LOG(("Http3Session::TryActivating [stream=%p, this=%p state=%d]", aStream, 1448 this, mState)); 1449 1450 if (IsClosing()) { 1451 if (NS_FAILED(mError)) { 1452 return mError; 1453 } 1454 return NS_ERROR_FAILURE; 1455 } 1456 1457 if (aStream->Queued()) { 1458 LOG3(("Http3Session::TryActivating %p stream=%p already queued.\n", this, 1459 aStream)); 1460 return NS_BASE_STREAM_WOULD_BLOCK; 1461 } 1462 1463 if (mState == ZERORTT) { 1464 if (!aStream->Do0RTT()) { 1465 // Stream can't do 0RTT - queue it for activation when the session 1466 // reaches CONNECTED state via Finish0Rtt. 1467 if (!mCannotDo0RTTStreams.Contains(aStream)) { 1468 LOG(("Http3Session %p queuing stream %p for post-0RTT activation", this, 1469 aStream)); 1470 mCannotDo0RTTStreams.AppendElement(aStream); 1471 } 1472 return NS_BASE_STREAM_WOULD_BLOCK; 1473 } 1474 } 1475 1476 nsresult rv = NS_OK; 1477 // The order of these checks is important: Http3StreamTunnel inherits from 1478 // Http3Stream, so a tunnel will also match conditions for a regular stream. 1479 // Ensure we handle Http3StreamTunnel cases before generic Http3Stream logic. 1480 if (RefPtr<Http3StreamTunnel> streamTunnel = 1481 aStream->GetHttp3StreamTunnel()) { 1482 rv = mHttp3Connection->Connect(aAuthorityHeader, aHeaders, aStreamId, 3, 1483 false); 1484 } else if (RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream()) { 1485 rv = mHttp3Connection->Fetch( 1486 aMethod, aScheme, aAuthorityHeader, aPathQuery, aHeaders, aStreamId, 1487 httpStream->PriorityUrgency(), httpStream->PriorityIncremental()); 1488 } else if (RefPtr<Http3ConnectUDPStream> udpStream = 1489 aStream->GetHttp3ConnectUDPStream()) { 1490 if (DeferIfNegotiating(ExtendedConnectKind::ConnectUDP, aStream)) { 1491 return NS_BASE_STREAM_WOULD_BLOCK; 1492 } 1493 rv = mHttp3Connection->CreateConnectUdp(aAuthorityHeader, aPathQuery, 1494 aHeaders, aStreamId); 1495 } else { 1496 MOZ_RELEASE_ASSERT(aStream->GetHttp3WebTransportSession(), 1497 "It must be a WebTransport session"); 1498 // Don't call CreateWebTransport if we are still waiting for the negotiation 1499 // result. 1500 if (DeferIfNegotiating(ExtendedConnectKind::WebTransport, aStream)) { 1501 return NS_BASE_STREAM_WOULD_BLOCK; 1502 } 1503 rv = mHttp3Connection->CreateWebTransport(aAuthorityHeader, aPathQuery, 1504 aHeaders, aStreamId); 1505 } 1506 1507 if (NS_FAILED(rv)) { 1508 LOG(("Http3Session::TryActivating returns error=0x%" PRIx32 "[stream=%p, " 1509 "this=%p]", 1510 static_cast<uint32_t>(rv), aStream, this)); 1511 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1512 LOG3( 1513 ("Http3Session::TryActivating %p stream=%p no room for more " 1514 "concurrent streams\n", 1515 this, aStream)); 1516 mTransactionsBlockedByStreamLimitCount++; 1517 if (mQueuedStreams.GetSize() == 0) { 1518 mBlockedByStreamLimitCount++; 1519 } 1520 QueueStream(aStream); 1521 return rv; 1522 } 1523 1524 // Previously we always returned NS_OK here, which caused the 1525 // transaction to wait until the quic connection timed out 1526 // after which it was retried without quic. 1527 if (StaticPrefs::network_http_http3_fallback_to_h2_on_error()) { 1528 return NS_ERROR_HTTP2_FALLBACK_TO_HTTP1; 1529 } 1530 1531 return rv; 1532 } 1533 1534 LOG(("Http3Session::TryActivating streamId=0x%" PRIx64 1535 " for stream=%p [this=%p].", 1536 *aStreamId, aStream, this)); 1537 1538 MOZ_ASSERT(*aStreamId != UINT64_MAX); 1539 1540 if (mTransactionCount > 0 && mStreamIdHash.IsEmpty()) { 1541 MOZ_ASSERT(mConnectionIdleStart); 1542 MOZ_ASSERT(mFirstStreamIdReuseIdleConnection.isNothing()); 1543 1544 mConnectionIdleEnd = TimeStamp::Now(); 1545 mFirstStreamIdReuseIdleConnection = Some(*aStreamId); 1546 } 1547 mStreamIdHash.InsertOrUpdate(*aStreamId, RefPtr{aStream}); 1548 mTransactionCount++; 1549 1550 return NS_OK; 1551 } 1552 1553 // This is called by Http3WebTransportStream::OnReadSegment. 1554 // TODO: this function is almost the same as TryActivating(). 1555 // We should try to reduce the duplicate code. 1556 nsresult Http3Session::TryActivatingWebTransportStream( 1557 uint64_t* aStreamId, Http3StreamBase* aStream) { 1558 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1559 MOZ_ASSERT(*aStreamId == UINT64_MAX); 1560 1561 LOG( 1562 ("Http3Session::TryActivatingWebTransportStream [stream=%p, this=%p " 1563 "state=%d]", 1564 aStream, this, mState)); 1565 1566 if (IsClosing()) { 1567 if (NS_FAILED(mError)) { 1568 return mError; 1569 } 1570 return NS_ERROR_FAILURE; 1571 } 1572 1573 if (aStream->Queued()) { 1574 LOG3( 1575 ("Http3Session::TryActivatingWebTransportStream %p stream=%p already " 1576 "queued.\n", 1577 this, aStream)); 1578 return NS_BASE_STREAM_WOULD_BLOCK; 1579 } 1580 1581 nsresult rv = NS_OK; 1582 RefPtr<Http3WebTransportStream> wtStream = 1583 aStream->GetHttp3WebTransportStream(); 1584 MOZ_RELEASE_ASSERT(wtStream, "It must be a WebTransport stream"); 1585 rv = mHttp3Connection->CreateWebTransportStream( 1586 wtStream->SessionId(), wtStream->StreamType(), aStreamId); 1587 1588 if (NS_FAILED(rv)) { 1589 LOG(( 1590 "Http3Session::TryActivatingWebTransportStream returns error=0x%" PRIx32 1591 "[stream=%p, " 1592 "this=%p]", 1593 static_cast<uint32_t>(rv), aStream, this)); 1594 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1595 LOG3( 1596 ("Http3Session::TryActivatingWebTransportStream %p stream=%p no room " 1597 "for more " 1598 "concurrent streams\n", 1599 this, aStream)); 1600 QueueStream(aStream); 1601 return rv; 1602 } 1603 1604 return rv; 1605 } 1606 1607 LOG(("Http3Session::TryActivatingWebTransportStream streamId=0x%" PRIx64 1608 " for stream=%p [this=%p].", 1609 *aStreamId, aStream, this)); 1610 1611 MOZ_ASSERT(*aStreamId != UINT64_MAX); 1612 1613 RefPtr<Http3StreamBase> session = mStreamIdHash.Get(wtStream->SessionId()); 1614 MOZ_ASSERT(session); 1615 Http3WebTransportSession* wtSession = session->GetHttp3WebTransportSession(); 1616 MOZ_ASSERT(wtSession); 1617 1618 wtSession->RemoveWebTransportStream(wtStream); 1619 1620 // WebTransportStream is managed by Http3Session now. 1621 mWebTransportStreams.AppendElement(wtStream); 1622 mWebTransportStreamToSessionMap.InsertOrUpdate(*aStreamId, 1623 session->StreamId()); 1624 mStreamIdHash.InsertOrUpdate(*aStreamId, std::move(wtStream)); 1625 return NS_OK; 1626 } 1627 1628 // This is only called by Http3Stream::OnReadSegment. 1629 // ProcessOutput will be called in Http3Session::ReadSegment that 1630 // calls Http3Stream::OnReadSegment. 1631 void Http3Session::CloseSendingSide(uint64_t aStreamId) { 1632 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1633 mHttp3Connection->CloseStream(aStreamId); 1634 } 1635 1636 // This is only called by Http3Stream::OnReadSegment. 1637 // ProcessOutput will be called in Http3Session::ReadSegment that 1638 // calls Http3Stream::OnReadSegment. 1639 nsresult Http3Session::SendRequestBody(uint64_t aStreamId, const char* buf, 1640 uint32_t count, uint32_t* countRead) { 1641 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1642 nsresult rv = mHttp3Connection->SendRequestBody( 1643 aStreamId, (const uint8_t*)buf, count, countRead); 1644 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1645 mTransactionsSenderBlockedByFlowControlCount++; 1646 } else if (NS_FAILED(rv)) { 1647 // Ignore this error. This may happen if some events are not handled yet. 1648 // TODO we may try to add an assertion here. 1649 // We will pretend that sender is blocked, that will cause the caller to 1650 // stop trying. 1651 *countRead = 0; 1652 rv = NS_BASE_STREAM_WOULD_BLOCK; 1653 } 1654 1655 MOZ_ASSERT((*countRead != 0) || NS_FAILED(rv)); 1656 return rv; 1657 } 1658 1659 void Http3Session::ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError, 1660 ResetType aType) { 1661 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1662 1663 uint64_t sessionId = 0; 1664 if (mWebTransportStreamToSessionMap.Get(aStreamId, &sessionId)) { 1665 uint8_t wtError = Http3ErrorToWebTransportError(aError); 1666 nsresult rv = GetNSResultFromWebTransportError(wtError); 1667 1668 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId); 1669 if (stream) { 1670 if (aType == RESET) { 1671 stream->SetRecvdReset(); 1672 } 1673 RefPtr<Http3WebTransportStream> wtStream = 1674 stream->GetHttp3WebTransportStream(); 1675 if (wtStream) { 1676 CloseWebTransportStream(wtStream, rv); 1677 } 1678 } 1679 1680 RefPtr<Http3StreamBase> session = mStreamIdHash.Get(sessionId); 1681 if (session) { 1682 Http3WebTransportSession* wtSession = 1683 session->GetHttp3WebTransportSession(); 1684 MOZ_ASSERT(wtSession); 1685 if (wtSession) { 1686 if (aType == RESET) { 1687 wtSession->OnStreamReset(aStreamId, rv); 1688 } else { 1689 wtSession->OnStreamStopSending(aStreamId, rv); 1690 } 1691 } 1692 } 1693 return; 1694 } 1695 1696 RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(aStreamId); 1697 if (!stream) { 1698 return; 1699 } 1700 1701 RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream(); 1702 if (!httpStream) { 1703 return; 1704 } 1705 1706 // We only handle some of Http3 error as epecial, the rest are just equivalent 1707 // to cancel. 1708 if (aError == HTTP3_APP_ERROR_VERSION_FALLBACK) { 1709 // We will restart the request and the alt-svc will be removed 1710 // automatically. 1711 // Also disable http3 we want http1.1. 1712 httpStream->Transaction()->DisableHttp3(false); 1713 httpStream->Transaction()->DisableSpdy(); 1714 CloseStream(stream, NS_ERROR_NET_RESET); 1715 } else if (aError == HTTP3_APP_ERROR_REQUEST_REJECTED) { 1716 // This request was rejected because server is probably busy or going away. 1717 // We can restart the request using alt-svc. Without calling 1718 // DoNotRemoveAltSvc the alt-svc route will be removed. 1719 httpStream->Transaction()->DoNotRemoveAltSvc(); 1720 CloseStream(stream, NS_ERROR_NET_RESET); 1721 } else { 1722 if (httpStream->RecvdData()) { 1723 CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER); 1724 } else { 1725 CloseStream(stream, NS_ERROR_NET_INTERRUPT); 1726 } 1727 } 1728 } 1729 1730 void Http3Session::SetConnection(nsAHttpConnection* aConn) { 1731 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1732 mConnection = aConn; 1733 } 1734 1735 void Http3Session::GetSecurityCallbacks(nsIInterfaceRequestor** aOut) { 1736 *aOut = nullptr; 1737 } 1738 1739 // TODO 1740 void Http3Session::OnTransportStatus(nsITransport* aTransport, nsresult aStatus, 1741 int64_t aProgress) { 1742 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1743 1744 switch (aStatus) { 1745 // These should appear only once, deliver to the first 1746 // transaction on the session. 1747 case NS_NET_STATUS_RESOLVING_HOST: 1748 case NS_NET_STATUS_RESOLVED_HOST: 1749 case NS_NET_STATUS_CONNECTING_TO: 1750 case NS_NET_STATUS_CONNECTED_TO: { 1751 if (!mFirstHttpTransaction) { 1752 // if we still do not have a HttpTransaction store timings info in 1753 // a HttpConnection. 1754 // If some error occur it can happen that we do not have a connection. 1755 if (mConnection) { 1756 RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection(); 1757 conn->SetEvent(aStatus); 1758 } 1759 } else { 1760 mFirstHttpTransaction->OnTransportStatus(aTransport, aStatus, 1761 aProgress); 1762 } 1763 1764 if (aStatus == NS_NET_STATUS_CONNECTED_TO) { 1765 mFirstHttpTransaction = nullptr; 1766 } 1767 break; 1768 } 1769 1770 default: 1771 // The other transport events are ignored here because there is no good 1772 // way to map them to the right transaction in HTTP3. Instead, the events 1773 // are generated again from the HTTP3 code and passed directly to the 1774 // correct transaction. 1775 1776 // NS_NET_STATUS_SENDING_TO: 1777 // This is generated by the socket transport when (part) of 1778 // a transaction is written out 1779 // 1780 // There is no good way to map it to the right transaction in HTTP3, 1781 // so it is ignored here and generated separately when the request 1782 // is sent from Http3Stream. 1783 1784 // NS_NET_STATUS_WAITING_FOR: 1785 // Created by nsHttpConnection when the request has been totally sent. 1786 // There is no good way to map it to the right transaction in HTTP3, 1787 // so it is ignored here and generated separately when the same 1788 // condition is complete in Http3Stream when there is no more 1789 // request body left to be transmitted. 1790 1791 // NS_NET_STATUS_RECEIVING_FROM 1792 // Generated in Http3Stream whenever the stream reads data. 1793 1794 break; 1795 } 1796 } 1797 1798 bool Http3Session::IsDone() { return mState == CLOSED; } 1799 1800 nsresult Http3Session::Status() { 1801 MOZ_ASSERT(false, "Http3Session::Status()"); 1802 return NS_ERROR_UNEXPECTED; 1803 } 1804 1805 uint32_t Http3Session::Caps() { 1806 MOZ_ASSERT(false, "Http3Session::Caps()"); 1807 return 0; 1808 } 1809 1810 nsresult Http3Session::ReadSegments(nsAHttpSegmentReader* reader, 1811 uint32_t count, uint32_t* countRead) { 1812 MOZ_ASSERT(false, "Http3Session::ReadSegments()"); 1813 return NS_ERROR_UNEXPECTED; 1814 } 1815 1816 nsresult Http3Session::SendData(nsIUDPSocket* socket) { 1817 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1818 1819 LOG(("Http3Session::SendData [this=%p]", this)); 1820 1821 // 1) go through all streams/transactions that are ready to write and 1822 // write their data into quic streams (no network write yet). 1823 // 2) call ProcessOutput that will loop until all available packets are 1824 // written to a socket or the socket returns an error code. 1825 // 3) if we still have streams ready to write call ResumeSend()(we may 1826 // still have such streams because on an stream error we return earlier 1827 // to let the error be handled). 1828 // 4) 1829 1830 nsresult rv = NS_OK; 1831 RefPtr<Http3StreamBase> stream; 1832 1833 nsTArray<RefPtr<Http3StreamBase>> blockedStreams; 1834 1835 // Step 1) 1836 while (CanSendData() && (stream = mReadyForWrite.PopFront())) { 1837 LOG(("Http3Session::SendData call ReadSegments from stream=%p [this=%p]", 1838 stream.get(), this)); 1839 stream->SetInTxQueue(false); 1840 if (stream->BlockedByFlowControl()) { 1841 LOG(("stream %p blocked by flow control", stream.get())); 1842 blockedStreams.AppendElement(stream); 1843 continue; 1844 } 1845 rv = stream->ReadSegments(); 1846 1847 // on stream error we return earlier to let the error be handled. 1848 if (NS_FAILED(rv)) { 1849 LOG3(("Http3Session::SendData %p returns error code 0x%" PRIx32, this, 1850 static_cast<uint32_t>(rv))); 1851 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK); 1852 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { // Just in case! 1853 rv = NS_OK; 1854 } else if (ASpdySession::SoftStreamError(rv)) { 1855 CloseStream(stream, rv); 1856 LOG3(("Http3Session::SendData %p soft error override\n", this)); 1857 rv = NS_OK; 1858 } else { 1859 break; 1860 } 1861 } 1862 } 1863 1864 if (NS_SUCCEEDED(rv)) { 1865 // Step 2: 1866 // Call actual network write. 1867 rv = ProcessOutput(socket); 1868 } 1869 1870 // Step 3: 1871 MaybeResumeSend(); 1872 1873 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1874 rv = NS_OK; 1875 } 1876 1877 if (NS_FAILED(rv)) { 1878 return rv; 1879 } 1880 1881 // Put the blocked streams back to the queue, since they are ready to write. 1882 for (const auto& stream : blockedStreams) { 1883 mReadyForWrite.Push(stream); 1884 stream->SetInTxQueue(true); 1885 } 1886 1887 rv = ProcessEvents(); 1888 1889 // Let the connection know we sent some app data successfully. 1890 if (stream && NS_SUCCEEDED(rv)) { 1891 mUdpConn->NotifyDataWrite(); 1892 } 1893 1894 return rv; 1895 } 1896 1897 void Http3Session::StreamReadyToWrite(Http3StreamBase* aStream) { 1898 MOZ_ASSERT(aStream); 1899 // Http3Session::StreamReadyToWrite can be called multiple times when we get 1900 // duplicate DataWrite events from neqo at the same time. In this case, we 1901 // only want to insert the stream in `mReadyForWrite` once. 1902 if (aStream->IsInTxQueue()) { 1903 return; 1904 } 1905 1906 mReadyForWrite.Push(aStream); 1907 aStream->SetInTxQueue(true); 1908 if (CanSendData() && mConnection) { 1909 (void)mConnection->ResumeSend(); 1910 } 1911 } 1912 1913 void Http3Session::MaybeResumeSend() { 1914 if ((mReadyForWrite.GetSize() > 0) && CanSendData() && mConnection) { 1915 (void)mConnection->ResumeSend(); 1916 } 1917 } 1918 1919 nsresult Http3Session::ProcessSlowConsumers() { 1920 if (mSlowConsumersReadyForRead.IsEmpty()) { 1921 return NS_OK; 1922 } 1923 1924 RefPtr<Http3StreamBase> slowConsumer = 1925 mSlowConsumersReadyForRead.ElementAt(0); 1926 mSlowConsumersReadyForRead.RemoveElementAt(0); 1927 1928 nsresult rv = ProcessTransactionRead(slowConsumer); 1929 1930 return rv; 1931 } 1932 1933 nsresult Http3Session::WriteSegments(nsAHttpSegmentWriter* writer, 1934 uint32_t count, uint32_t* countWritten) { 1935 MOZ_ASSERT(false, "Http3Session::WriteSegments()"); 1936 return NS_ERROR_UNEXPECTED; 1937 } 1938 1939 nsresult Http3Session::RecvData(nsIUDPSocket* socket) { 1940 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1941 1942 // Process slow consumers. 1943 nsresult rv = ProcessSlowConsumers(); 1944 if (NS_FAILED(rv)) { 1945 LOG3(("Http3Session %p ProcessSlowConsumers returns 0x%" PRIx32 "\n", this, 1946 static_cast<uint32_t>(rv))); 1947 return rv; 1948 } 1949 1950 rv = ProcessInput(socket); 1951 if (NS_FAILED(rv)) { 1952 return rv; 1953 } 1954 1955 rv = ProcessEvents(); 1956 if (NS_FAILED(rv)) { 1957 return rv; 1958 } 1959 1960 rv = SendData(socket); 1961 if (NS_FAILED(rv)) { 1962 return rv; 1963 } 1964 1965 return NS_OK; 1966 } 1967 1968 const uint32_t HTTP3_TELEMETRY_APP_NECKO = 42; 1969 1970 void Http3Session::Close(nsresult aReason) { 1971 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1972 1973 LOG(("Http3Session::Close [this=%p]", this)); 1974 1975 if (NS_FAILED(mError)) { 1976 CloseInternal(false); 1977 } else { 1978 mError = aReason; 1979 // If necko closes connection, this will map to the "closing" key and the 1980 // value HTTP3_TELEMETRY_APP_NECKO. 1981 glean::http3::connection_close_code.Get("app_closing"_ns) 1982 .AccumulateSingleSample(HTTP3_TELEMETRY_APP_NECKO); 1983 CloseInternal(true); 1984 } 1985 1986 if (mCleanShutdown || mIsClosedByNeqo || NS_FAILED(mSocketError)) { 1987 // It is network-tear-down, a socker error or neqo is state CLOSED 1988 // (it does not need to send any more packets or wait for new packets). 1989 // We need to remove all references, so that 1990 // Http3Session will be destroyed. 1991 if (mTimer) { 1992 mTimer->Cancel(); 1993 } 1994 mTimer = nullptr; 1995 mTimerCallback = nullptr; 1996 mConnection = nullptr; 1997 mUdpConn = nullptr; 1998 mState = CLOSED; 1999 } 2000 if (mConnection) { 2001 // resume sending to send CLOSE_CONNECTION frame. 2002 (void)mConnection->ResumeSend(); 2003 } 2004 } 2005 2006 void Http3Session::CloseInternal(bool aCallNeqoClose) { 2007 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2008 2009 if (IsClosing()) { 2010 return; 2011 } 2012 2013 LOG(("Http3Session::Closing [this=%p]", this)); 2014 2015 if (mState != CONNECTED) { 2016 mBeforeConnectedError = true; 2017 } 2018 2019 #ifndef ANDROID 2020 if (mState == ZERORTT) { 2021 ZeroRttTelemetry(aCallNeqoClose ? ZeroRttOutcome::USED_CONN_CLOSED_BY_NECKO 2022 : ZeroRttOutcome::USED_CONN_ERROR); 2023 } 2024 #endif 2025 2026 mState = CLOSING; 2027 Shutdown(); 2028 2029 if (aCallNeqoClose) { 2030 mHttp3Connection->Close(HTTP3_APP_ERROR_NO_ERROR); 2031 } 2032 2033 mStreamIdHash.Clear(); 2034 mStreamTransactionHash.Clear(); 2035 } 2036 2037 nsHttpConnectionInfo* Http3Session::ConnectionInfo() { 2038 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2039 RefPtr<nsHttpConnectionInfo> ci; 2040 GetConnectionInfo(getter_AddRefs(ci)); 2041 return ci.get(); 2042 } 2043 2044 void Http3Session::SetProxyConnectFailed() { 2045 MOZ_ASSERT(false, "Http3Session::SetProxyConnectFailed()"); 2046 } 2047 2048 nsHttpRequestHead* Http3Session::RequestHead() { 2049 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2050 MOZ_ASSERT(false, 2051 "Http3Session::RequestHead() " 2052 "should not be called after http/3 is setup"); 2053 return nullptr; 2054 } 2055 2056 uint32_t Http3Session::Http1xTransactionCount() { return 0; } 2057 2058 nsresult Http3Session::TakeSubTransactions( 2059 nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) { 2060 return NS_OK; 2061 } 2062 2063 //----------------------------------------------------------------------------- 2064 // Pass through methods of nsAHttpConnection 2065 //----------------------------------------------------------------------------- 2066 2067 nsAHttpConnection* Http3Session::Connection() { 2068 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2069 return mConnection; 2070 } 2071 2072 nsresult Http3Session::OnHeadersAvailable(nsAHttpTransaction* transaction, 2073 nsHttpRequestHead* requestHead, 2074 nsHttpResponseHead* responseHead, 2075 bool* reset) { 2076 MOZ_ASSERT(mConnection); 2077 if (mConnection) { 2078 return mConnection->OnHeadersAvailable(transaction, requestHead, 2079 responseHead, reset); 2080 } 2081 return NS_OK; 2082 } 2083 2084 bool Http3Session::IsReused() { 2085 if (mConnection) { 2086 return mConnection->IsReused(); 2087 } 2088 return true; 2089 } 2090 2091 nsresult Http3Session::PushBack(const char* buf, uint32_t len) { 2092 return NS_ERROR_UNEXPECTED; 2093 } 2094 2095 already_AddRefed<HttpConnectionBase> Http3Session::TakeHttpConnection() { 2096 LOG(("Http3Session::TakeHttpConnection %p", this)); 2097 return nullptr; 2098 } 2099 2100 already_AddRefed<HttpConnectionBase> Http3Session::HttpConnection() { 2101 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2102 if (mConnection) { 2103 return mConnection->HttpConnection(); 2104 } 2105 return nullptr; 2106 } 2107 2108 void Http3Session::CloseTransaction(nsAHttpTransaction* aTransaction, 2109 nsresult aResult) { 2110 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2111 LOG3(("Http3Session::CloseTransaction %p %p 0x%" PRIx32, this, aTransaction, 2112 static_cast<uint32_t>(aResult))); 2113 2114 // Generally this arrives as a cancel event from the connection manager. 2115 2116 // need to find the stream and call CloseStream() on it. 2117 RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction); 2118 if (!stream) { 2119 LOG3(("Http3Session::CloseTransaction %p %p 0x%" PRIx32 " - not found.", 2120 this, aTransaction, static_cast<uint32_t>(aResult))); 2121 return; 2122 } 2123 LOG3( 2124 ("Http3Session::CloseTransaction probably a cancel. this=%p, " 2125 "trans=%p, result=0x%" PRIx32 ", streamId=0x%" PRIx64 " stream=%p", 2126 this, aTransaction, static_cast<uint32_t>(aResult), stream->StreamId(), 2127 stream.get())); 2128 CloseStream(stream, aResult); 2129 if (mConnection) { 2130 (void)mConnection->ResumeSend(); 2131 } 2132 } 2133 2134 void Http3Session::CloseStream(Http3StreamBase* aStream, nsresult aResult) { 2135 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2136 RefPtr<Http3WebTransportStream> wtStream = 2137 aStream->GetHttp3WebTransportStream(); 2138 if (wtStream) { 2139 CloseWebTransportStream(wtStream, aResult); 2140 return; 2141 } 2142 2143 RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream(); 2144 if (httpStream && !httpStream->RecvdFin() && !httpStream->RecvdReset() && 2145 httpStream->HasStreamId()) { 2146 mHttp3Connection->CancelFetch(httpStream->StreamId(), 2147 HTTP3_APP_ERROR_REQUEST_CANCELLED); 2148 } 2149 2150 if ((NS_SUCCEEDED(aResult) || NS_BASE_STREAM_CLOSED == aResult) && 2151 mConnInfo->GetIsTrrServiceChannel()) { 2152 // save time of last successful response 2153 mLastTRRResponseTime = TimeStamp::Now(); 2154 mTrrStreams++; 2155 } 2156 2157 aStream->Close(aResult); 2158 CloseStreamInternal(aStream, aResult); 2159 } 2160 2161 void Http3Session::CloseStreamInternal(Http3StreamBase* aStream, 2162 nsresult aResult) { 2163 LOG3(("Http3Session::CloseStreamInternal %p %p 0x%" PRIx32, this, aStream, 2164 static_cast<uint32_t>(aResult))); 2165 if (aStream->HasStreamId()) { 2166 // We know the transaction reusing an idle connection has succeeded or 2167 // failed. 2168 if (mFirstStreamIdReuseIdleConnection.isSome() && 2169 aStream->StreamId() == *mFirstStreamIdReuseIdleConnection) { 2170 MOZ_ASSERT(mConnectionIdleStart); 2171 MOZ_ASSERT(mConnectionIdleEnd); 2172 2173 #ifndef ANDROID 2174 if (mConnectionIdleStart) { 2175 mozilla::glean::netwerk::http3_time_to_reuse_idle_connection 2176 .Get(NS_SUCCEEDED(aResult) ? "succeeded"_ns : "failed"_ns) 2177 .AccumulateRawDuration(mConnectionIdleEnd - mConnectionIdleStart); 2178 } 2179 #endif 2180 2181 mConnectionIdleStart = TimeStamp(); 2182 mConnectionIdleEnd = TimeStamp(); 2183 mFirstStreamIdReuseIdleConnection.reset(); 2184 } 2185 2186 mStreamIdHash.Remove(aStream->StreamId()); 2187 2188 // Start to idle when we remove the last stream. 2189 if (mStreamIdHash.IsEmpty()) { 2190 mConnectionIdleStart = TimeStamp::Now(); 2191 } 2192 } 2193 RemoveStreamFromQueues(aStream); 2194 if (nsAHttpTransaction* transaction = aStream->Transaction()) { 2195 mStreamTransactionHash.Remove(transaction); 2196 } 2197 mWebTransportSessions.RemoveElement(aStream); 2198 mWebTransportStreams.RemoveElement(aStream); 2199 mTunnelStreams.RemoveElement(aStream); 2200 // Close(NS_OK) implies that the NeqoHttp3Conn will be closed, so we can only 2201 // do this when there is no Http3Steeam, WebTransportSession and 2202 // WebTransportStream. 2203 if ((mShouldClose || mGoawayReceived) && HasNoActiveStreams()) { 2204 MOZ_ASSERT(!IsClosing()); 2205 Close(NS_OK); 2206 } 2207 } 2208 2209 void Http3Session::CloseWebTransportStream(Http3WebTransportStream* aStream, 2210 nsresult aResult) { 2211 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2212 LOG3(("Http3Session::CloseWebTransportStream %p %p 0x%" PRIx32, this, aStream, 2213 static_cast<uint32_t>(aResult))); 2214 if (aStream && !aStream->RecvdFin() && !aStream->RecvdReset() && 2215 (aStream->HasStreamId())) { 2216 mHttp3Connection->ResetStream(aStream->StreamId(), 2217 HTTP3_APP_ERROR_REQUEST_CANCELLED); 2218 } 2219 2220 aStream->Close(aResult); 2221 CloseStreamInternal(aStream, aResult); 2222 } 2223 2224 void Http3Session::ResetWebTransportStream(Http3WebTransportStream* aStream, 2225 uint64_t aErrorCode) { 2226 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2227 LOG3(("Http3Session::ResetWebTransportStream %p %p 0x%" PRIx64, this, aStream, 2228 aErrorCode)); 2229 mHttp3Connection->ResetStream(aStream->StreamId(), aErrorCode); 2230 } 2231 2232 void Http3Session::StreamStopSending(Http3WebTransportStream* aStream, 2233 uint8_t aErrorCode) { 2234 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2235 LOG(("Http3Session::StreamStopSending %p %p 0x%" PRIx32, this, aStream, 2236 static_cast<uint32_t>(aErrorCode))); 2237 mHttp3Connection->StreamStopSending(aStream->StreamId(), aErrorCode); 2238 } 2239 2240 nsresult Http3Session::TakeTransport(nsISocketTransport**, 2241 nsIAsyncInputStream**, 2242 nsIAsyncOutputStream**) { 2243 MOZ_ASSERT(false, "TakeTransport of Http3Session"); 2244 return NS_ERROR_UNEXPECTED; 2245 } 2246 2247 WebTransportSessionBase* Http3Session::GetWebTransportSession( 2248 nsAHttpTransaction* aTransaction) { 2249 RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction); 2250 2251 if (!stream || !stream->GetHttp3WebTransportSession()) { 2252 MOZ_ASSERT(false, "There must be a stream"); 2253 return nullptr; 2254 } 2255 RemoveStreamFromQueues(stream); 2256 mStreamTransactionHash.Remove(aTransaction); 2257 mWebTransportSessions.AppendElement(stream); 2258 return stream->GetHttp3WebTransportSession(); 2259 } 2260 2261 bool Http3Session::IsPersistent() { return true; } 2262 2263 void Http3Session::DontReuse() { 2264 LOG3(("Http3Session::DontReuse %p\n", this)); 2265 if (!OnSocketThread()) { 2266 LOG3(("Http3Session %p not on socket thread\n", this)); 2267 nsCOMPtr<nsIRunnable> event = NewRunnableMethod( 2268 "Http3Session::DontReuse", this, &Http3Session::DontReuse); 2269 gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); 2270 return; 2271 } 2272 2273 if (mGoawayReceived || IsClosing()) { 2274 return; 2275 } 2276 2277 mShouldClose = true; 2278 if (HasNoActiveStreams()) { 2279 // This is a temporary workaround and should be fixed properly in Happy 2280 // Eyeballs project. We should not exclude this domain if 2281 // Http3Session::DontReuse is called from 2282 // ConnectionEntry::MakeAllDontReuseExcept. 2283 if (mUdpConn && 2284 mUdpConn->CloseReason() == 2285 ConnectionCloseReason::CLOSE_EXISTING_CONN_FOR_COALESCING) { 2286 mDontExclude = true; 2287 } 2288 Close(NS_OK); 2289 } 2290 } 2291 2292 void Http3Session::CloseWebTransportConn() { 2293 LOG3(("Http3Session::CloseWebTransportConn %p\n", this)); 2294 // We need to dispatch, since Http3Session could be released in 2295 // HttpConnectionUDP::CloseTransaction. 2296 gSocketTransportService->Dispatch( 2297 NS_NewRunnableFunction("Http3Session::CloseWebTransportConn", 2298 [self = RefPtr{this}]() { 2299 if (self->mUdpConn) { 2300 self->mUdpConn->CloseTransaction( 2301 self, NS_ERROR_ABORT); 2302 } 2303 }), 2304 NS_DISPATCH_NORMAL); 2305 } 2306 2307 void Http3Session::CurrentBrowserIdChanged(uint64_t id) { 2308 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2309 2310 mCurrentBrowserId = id; 2311 2312 for (const auto& stream : mStreamTransactionHash.Values()) { 2313 RefPtr<Http3Stream> httpStream = stream->GetHttp3Stream(); 2314 if (httpStream) { 2315 httpStream->CurrentBrowserIdChanged(id); 2316 } 2317 } 2318 } 2319 2320 // This is called by Http3Stream::OnWriteSegment. 2321 nsresult Http3Session::ReadResponseData(uint64_t aStreamId, char* aBuf, 2322 uint32_t aCount, 2323 uint32_t* aCountWritten, bool* aFin) { 2324 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2325 2326 nsresult rv = mHttp3Connection->ReadResponseData(aStreamId, (uint8_t*)aBuf, 2327 aCount, aCountWritten, aFin); 2328 2329 // This should not happen, i.e. stream must be present in neqo and in necko at 2330 // the same time. 2331 MOZ_ASSERT(rv != NS_ERROR_INVALID_ARG); 2332 if (NS_FAILED(rv)) { 2333 LOG3(("Http3Session::ReadResponseData return an error %" PRIx32 2334 " [this=%p]", 2335 static_cast<uint32_t>(rv), this)); 2336 // This error will be handled by neqo and the whole connection will be 2337 // closed. We will return NS_BASE_STREAM_WOULD_BLOCK here. 2338 *aCountWritten = 0; 2339 *aFin = false; 2340 rv = NS_BASE_STREAM_WOULD_BLOCK; 2341 } 2342 2343 MOZ_ASSERT((*aCountWritten != 0) || aFin || NS_FAILED(rv)); 2344 return rv; 2345 } 2346 2347 void Http3Session::TransactionHasDataToWrite(nsAHttpTransaction* caller) { 2348 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2349 LOG3(("Http3Session::TransactionHasDataToWrite %p trans=%p", this, caller)); 2350 2351 // a trapped signal from the http transaction to the connection that 2352 // it is no longer blocked on read. 2353 2354 RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(caller); 2355 if (!stream) { 2356 LOG3(("Http3Session::TransactionHasDataToWrite %p caller %p not found", 2357 this, caller)); 2358 return; 2359 } 2360 2361 LOG3(("Http3Session::TransactionHasDataToWrite %p ID is 0x%" PRIx64, this, 2362 stream->StreamId())); 2363 2364 StreamHasDataToWrite(stream); 2365 } 2366 2367 void Http3Session::StreamHasDataToWrite(Http3StreamBase* aStream) { 2368 if (!IsClosing()) { 2369 StreamReadyToWrite(aStream); 2370 } else { 2371 LOG3( 2372 ("Http3Session::TransactionHasDataToWrite %p closed so not setting " 2373 "Ready4Write\n", 2374 this)); 2375 } 2376 2377 // NSPR poll will not poll the network if there are non system PR_FileDesc's 2378 // that are ready - so we can get into a deadlock waiting for the system IO 2379 // to come back here if we don't force the send loop manually. 2380 (void)ForceSend(); 2381 } 2382 2383 void Http3Session::TransactionHasDataToRecv(nsAHttpTransaction* caller) { 2384 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2385 LOG3(("Http3Session::TransactionHasDataToRecv %p trans=%p", this, caller)); 2386 2387 // a signal from the http transaction to the connection that it will consume 2388 // more 2389 RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(caller); 2390 if (!stream) { 2391 LOG3(("Http3Session::TransactionHasDataToRecv %p caller %p not found", this, 2392 caller)); 2393 return; 2394 } 2395 2396 LOG3(("Http3Session::TransactionHasDataToRecv %p ID is 0x%" PRIx64 "\n", this, 2397 stream->StreamId())); 2398 ConnectSlowConsumer(stream); 2399 } 2400 2401 void Http3Session::ConnectSlowConsumer(Http3StreamBase* stream) { 2402 LOG3(("Http3Session::ConnectSlowConsumer %p 0x%" PRIx64 "\n", this, 2403 stream->StreamId())); 2404 mSlowConsumersReadyForRead.AppendElement(stream); 2405 (void)ForceRecv(); 2406 } 2407 2408 bool Http3Session::TestJoinConnection(const nsACString& hostname, 2409 int32_t port) { 2410 return RealJoinConnection(hostname, port, true); 2411 } 2412 2413 bool Http3Session::JoinConnection(const nsACString& hostname, int32_t port) { 2414 return RealJoinConnection(hostname, port, false); 2415 } 2416 2417 // TODO test 2418 bool Http3Session::RealJoinConnection(const nsACString& hostname, int32_t port, 2419 bool justKidding) { 2420 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2421 if (!mConnection || !CanSendData() || mShouldClose || mGoawayReceived) { 2422 return false; 2423 } 2424 2425 nsHttpConnectionInfo* ci = ConnectionInfo(); 2426 if (ci->UsingProxy()) { 2427 MOZ_ASSERT(false, 2428 "RealJoinConnection should not be called when using proxy"); 2429 return false; 2430 } 2431 2432 if (nsCString(hostname).EqualsIgnoreCase(ci->Origin()) && 2433 (port == ci->OriginPort())) { 2434 return true; 2435 } 2436 2437 nsAutoCString key(hostname); 2438 key.Append(':'); 2439 key.Append(justKidding ? 'k' : '.'); 2440 key.AppendInt(port); 2441 bool cachedResult; 2442 if (mJoinConnectionCache.Get(key, &cachedResult)) { 2443 LOG(("joinconnection [%p %s] %s result=%d cache\n", this, 2444 ConnectionInfo()->HashKey().get(), key.get(), cachedResult)); 2445 return cachedResult; 2446 } 2447 2448 nsresult rv; 2449 bool isJoined = false; 2450 2451 nsCOMPtr<nsITLSSocketControl> sslSocketControl; 2452 mConnection->GetTLSSocketControl(getter_AddRefs(sslSocketControl)); 2453 if (!sslSocketControl) { 2454 return false; 2455 } 2456 2457 bool joinedReturn = false; 2458 if (justKidding) { 2459 rv = sslSocketControl->TestJoinConnection(mConnInfo->GetNPNToken(), 2460 hostname, port, &isJoined); 2461 } else { 2462 rv = sslSocketControl->JoinConnection(mConnInfo->GetNPNToken(), hostname, 2463 port, &isJoined); 2464 } 2465 if (NS_SUCCEEDED(rv) && isJoined) { 2466 joinedReturn = true; 2467 } 2468 2469 LOG(("joinconnection [%p %s] %s result=%d lookup\n", this, 2470 ConnectionInfo()->HashKey().get(), key.get(), joinedReturn)); 2471 mJoinConnectionCache.InsertOrUpdate(key, joinedReturn); 2472 if (!justKidding) { 2473 // cache a kidding entry too as this one is good for both 2474 nsAutoCString key2(hostname); 2475 key2.Append(':'); 2476 key2.Append('k'); 2477 key2.AppendInt(port); 2478 if (!mJoinConnectionCache.Get(key2)) { 2479 mJoinConnectionCache.InsertOrUpdate(key2, joinedReturn); 2480 } 2481 } 2482 return joinedReturn; 2483 } 2484 2485 void Http3Session::CallCertVerification(Maybe<nsCString> aEchPublicName) { 2486 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2487 LOG(("Http3Session::CallCertVerification [this=%p]", this)); 2488 2489 NeqoCertificateInfo certInfo; 2490 if (NS_FAILED(mHttp3Connection->PeerCertificateInfo(&certInfo))) { 2491 LOG(("Http3Session::CallCertVerification [this=%p] - no cert", this)); 2492 mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE); 2493 mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE); 2494 return; 2495 } 2496 2497 if (mConnInfo->GetWebTransport()) { 2498 // if our connection is webtransport, we might do a verification 2499 // based on serverCertificatedHashes 2500 const nsTArray<RefPtr<nsIWebTransportHash>>* servCertHashes = 2501 gHttpHandler->ConnMgr()->GetServerCertHashes(mConnInfo); 2502 if (servCertHashes && !servCertHashes->IsEmpty() && 2503 certInfo.certs.Length() >= 1) { 2504 // ok, we verify based on serverCertificateHashes 2505 mozilla::pkix::Result rv = AuthCertificateWithServerCertificateHashes( 2506 certInfo.certs[0], *servCertHashes); 2507 if (rv != mozilla::pkix::Result::Success) { 2508 // ok we failed, report it back 2509 LOG( 2510 ("Http3Session::CallCertVerification [this=%p] " 2511 "AuthCertificateWithServerCertificateHashes failed", 2512 this)); 2513 mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE); 2514 mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE); 2515 return; 2516 } 2517 // ok, we succeded 2518 Authenticated(0, true); 2519 return; 2520 } 2521 } 2522 2523 Maybe<nsTArray<nsTArray<uint8_t>>> stapledOCSPResponse; 2524 if (certInfo.stapled_ocsp_responses_present) { 2525 stapledOCSPResponse.emplace(std::move(certInfo.stapled_ocsp_responses)); 2526 } 2527 2528 Maybe<nsTArray<uint8_t>> sctsFromTLSExtension; 2529 if (certInfo.signed_cert_timestamp_present) { 2530 sctsFromTLSExtension.emplace(std::move(certInfo.signed_cert_timestamp)); 2531 } 2532 2533 uint32_t providerFlags; 2534 // the return value is always NS_OK, just ignore it. 2535 (void)mSocketControl->GetProviderFlags(&providerFlags); 2536 2537 nsCString echConfig; 2538 nsresult nsrv = mSocketControl->GetEchConfig(echConfig); 2539 bool verifyToEchPublicName = NS_SUCCEEDED(nsrv) && !echConfig.IsEmpty() && 2540 aEchPublicName && !aEchPublicName->IsEmpty(); 2541 const nsACString& hostname = 2542 verifyToEchPublicName ? *aEchPublicName : mSocketControl->GetHostName(); 2543 2544 SECStatus rv = psm::AuthCertificateHookWithInfo( 2545 mSocketControl, hostname, static_cast<const void*>(this), 2546 std::move(certInfo.certs), stapledOCSPResponse, sctsFromTLSExtension, 2547 providerFlags); 2548 if ((rv != SECSuccess) && (rv != SECWouldBlock)) { 2549 LOG(("Http3Session::CallCertVerification [this=%p] AuthCertificate failed", 2550 this)); 2551 mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE); 2552 mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE); 2553 } 2554 } 2555 2556 void Http3Session::Authenticated(int32_t aError, 2557 bool aServCertHashesSucceeded) { 2558 LOG(("Http3Session::Authenticated error=0x%" PRIx32 " [this=%p].", aError, 2559 this)); 2560 if ((mState == INITIALIZING) || (mState == ZERORTT)) { 2561 if (psm::IsNSSErrorCode(aError)) { 2562 mError = psm::GetXPCOMFromNSSError(aError); 2563 LOG(("Http3Session::Authenticated psm-error=0x%" PRIx32 " [this=%p].", 2564 static_cast<uint32_t>(mError), this)); 2565 } else if (StaticPrefs:: 2566 network_http_http3_disable_when_third_party_roots_found()) { 2567 // In test, we use another perf value to override the value of 2568 // hasThirdPartyRoots. 2569 bool hasThirdPartyRoots = 2570 (xpc::IsInAutomation() || PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) 2571 ? StaticPrefs:: 2572 network_http_http3_has_third_party_roots_found_in_automation() 2573 : !mSocketControl->IsBuiltCertChainRootBuiltInRoot(); 2574 LOG( 2575 ("Http3Session::Authenticated [this=%p, hasThirdPartyRoots=%d, " 2576 "servCertHashesSucceeded=%d]", 2577 this, hasThirdPartyRoots, aServCertHashesSucceeded)); 2578 // If serverCertificateHashes is used a thirdPartyRoot is legal 2579 if (hasThirdPartyRoots && !aServCertHashesSucceeded) { 2580 if (mFirstHttpTransaction) { 2581 mFirstHttpTransaction->DisableHttp3(false); 2582 } 2583 mUdpConn->CloseTransaction(this, NS_ERROR_NET_RESET); 2584 return; 2585 } 2586 } 2587 mHttp3Connection->PeerAuthenticated(aError); 2588 2589 // Call OnQuicTimeoutExpired to properly process neqo events and outputs. 2590 // We call OnQuicTimeoutExpired instead of ProcessOutputAndEvents, because 2591 // HttpConnectionUDP must close this session in case of an error. 2592 NS_DispatchToCurrentThread( 2593 NewRunnableMethod("net::HttpConnectionUDP::OnQuicTimeoutExpired", 2594 mUdpConn, &HttpConnectionUDP::OnQuicTimeoutExpired)); 2595 mUdpConn->ChangeConnectionState(ConnectionState::TRANSFERING); 2596 } 2597 } 2598 2599 void Http3Session::SetSecInfo() { 2600 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2601 NeqoSecretInfo secInfo; 2602 if (NS_SUCCEEDED(mHttp3Connection->GetSecInfo(&secInfo))) { 2603 mSocketControl->SetSSLVersionUsed(secInfo.version); 2604 mSocketControl->SetResumed(secInfo.resumed); 2605 mSocketControl->SetNegotiatedNPN(secInfo.alpn); 2606 2607 mSocketControl->SetInfo(secInfo.cipher, secInfo.version, secInfo.group, 2608 secInfo.signature_scheme, secInfo.ech_accepted); 2609 mHandshakeSucceeded = true; 2610 } 2611 2612 if (!mSocketControl->HasServerCert()) { 2613 mSocketControl->RebuildCertificateInfoFromSSLTokenCache(); 2614 } 2615 } 2616 2617 // Transport error have values from 0x0 to 0x11. 2618 // (https://tools.ietf.org/html/draft-ietf-quic-transport-34#section-20.1) 2619 // We will map this error to 0-16. 2620 // 17 will capture error codes between and including 0x12 and 0x0ff. This 2621 // error codes are not define by the spec but who know peer may sent them. 2622 // CryptoAlerts have value 0x100 + alert code. The range of alert code is 2623 // 0x00-0xff. (https://tools.ietf.org/html/draft-ietf-quic-tls_34#section-4.8) 2624 // Since telemetry does not allow more than 100 bucket, we use three diffrent 2625 // keys to map all alert codes. 2626 const uint32_t HTTP3_TELEMETRY_TRANSPORT_INTERNAL_ERROR = 15; 2627 const uint32_t HTTP3_TELEMETRY_TRANSPORT_END = 16; 2628 const uint32_t HTTP3_TELEMETRY_TRANSPORT_UNKNOWN = 17; 2629 const uint32_t HTTP3_TELEMETRY_TRANSPORT_CRYPTO_UNKNOWN = 18; 2630 // All errors from CloseError::Tag::CryptoError will be map to 19 2631 const uint32_t HTTP3_TELEMETRY_CRYPTO_ERROR = 19; 2632 2633 uint64_t GetCryptoAlertCode(nsCString& key, uint64_t error) { 2634 if (error < 100) { 2635 key.Append("_a"_ns); 2636 return error; 2637 } 2638 if (error < 200) { 2639 error -= 100; 2640 key.Append("_b"_ns); 2641 return error; 2642 } 2643 if (error < 256) { 2644 error -= 200; 2645 key.Append("_c"_ns); 2646 return error; 2647 } 2648 return HTTP3_TELEMETRY_TRANSPORT_CRYPTO_UNKNOWN; 2649 } 2650 2651 uint64_t GetTransportErrorCodeForTelemetry(nsCString& key, uint64_t error) { 2652 if (error <= HTTP3_TELEMETRY_TRANSPORT_END) { 2653 return error; 2654 } 2655 if (error < 0x100) { 2656 return HTTP3_TELEMETRY_TRANSPORT_UNKNOWN; 2657 } 2658 2659 return GetCryptoAlertCode(key, error - 0x100); 2660 } 2661 2662 // Http3 error codes are 0x100-0x110. 2663 // (https://tools.ietf.org/html/draft-ietf-quic-http-33#section-8.1) 2664 // The mapping is described below. 2665 // 0x00-0x10 mapped to 0-16 2666 // 0x11-0xff mapped to 17 2667 // 0x100-0x110 mapped to 18-36 2668 // 0x111-0x1ff mapped to 37 2669 // 0x200-0x202 mapped to 38-40 2670 // Others mapped to 41 2671 const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_1 = 17; 2672 const uint32_t HTTP3_TELEMETRY_APP_START = 18; 2673 // Values between 0x111 and 0x1ff are no definded and will be map to 18. 2674 const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_2 = 37; 2675 // Error codes between 0x200 and 0x202 are related to qpack. 2676 // (https://tools.ietf.org/html/draft-ietf-quic-qpack-20#section-6) 2677 // They will be mapped to 19-21 2678 const uint32_t HTTP3_TELEMETRY_APP_QPACK_START = 38; 2679 // Values greater or equal to 0x203 are no definded and will be map to 41. 2680 const uint32_t HTTP3_TELEMETRY_APP_UNKNOWN_3 = 41; 2681 2682 uint64_t GetAppErrorCodeForTelemetry(uint64_t error) { 2683 if (error <= 0x10) { 2684 return error; 2685 } 2686 if (error <= 0xff) { 2687 return HTTP3_TELEMETRY_APP_UNKNOWN_1; 2688 } 2689 if (error <= 0x110) { 2690 return error - 0x100 + HTTP3_TELEMETRY_APP_START; 2691 } 2692 if (error < 0x200) { 2693 return HTTP3_TELEMETRY_APP_UNKNOWN_2; 2694 } 2695 if (error <= 0x202) { 2696 return error - 0x200 + HTTP3_TELEMETRY_APP_QPACK_START; 2697 } 2698 return HTTP3_TELEMETRY_APP_UNKNOWN_3; 2699 } 2700 2701 void Http3Session::CloseConnectionTelemetry(CloseError& aError, bool aClosing) { 2702 uint64_t value = 0; 2703 nsCString key = EmptyCString(); 2704 2705 switch (aError.tag) { 2706 case CloseError::Tag::TransportInternalError: 2707 key = "transport_internal"_ns; 2708 value = HTTP3_TELEMETRY_TRANSPORT_INTERNAL_ERROR; 2709 break; 2710 case CloseError::Tag::TransportInternalErrorOther: 2711 key = "transport_other"_ns; 2712 value = aError.transport_internal_error_other._0; 2713 break; 2714 case CloseError::Tag::TransportError: 2715 key = "transport"_ns; 2716 value = GetTransportErrorCodeForTelemetry(key, aError.transport_error._0); 2717 break; 2718 case CloseError::Tag::CryptoError: 2719 key = "transport"_ns; 2720 value = HTTP3_TELEMETRY_CRYPTO_ERROR; 2721 break; 2722 case CloseError::Tag::CryptoAlert: 2723 key = "transport_crypto_alert"_ns; 2724 value = GetCryptoAlertCode(key, aError.crypto_alert._0); 2725 break; 2726 case CloseError::Tag::PeerAppError: 2727 key = "peer_app"_ns; 2728 value = GetAppErrorCodeForTelemetry(aError.peer_app_error._0); 2729 break; 2730 case CloseError::Tag::PeerError: 2731 key = "peer_transport"_ns; 2732 value = GetTransportErrorCodeForTelemetry(key, aError.peer_error._0); 2733 break; 2734 case CloseError::Tag::AppError: 2735 key = "app"_ns; 2736 value = GetAppErrorCodeForTelemetry(aError.app_error._0); 2737 break; 2738 case CloseError::Tag::EchRetry: 2739 key = "transport_crypto_alert"_ns; 2740 value = 100; 2741 } 2742 2743 MOZ_DIAGNOSTIC_ASSERT(value <= 100); 2744 2745 key.Append(aClosing ? "_closing"_ns : "_closed"_ns); 2746 2747 glean::http3::connection_close_code.Get(key).AccumulateSingleSample(value); 2748 2749 Http3Stats stats{}; 2750 mHttp3Connection->GetStats(&stats); 2751 2752 if (stats.packets_tx > 0) { 2753 unsigned long loss = (stats.lost * 10000) / stats.packets_tx; 2754 glean::http3::loss_ratio.AccumulateSingleSample(loss); 2755 2756 glean::http3::late_ack.EnumGet(glean::http3::LateAckLabel::eAck) 2757 .AccumulateSingleSample(stats.late_ack); 2758 glean::http3::late_ack.EnumGet(glean::http3::LateAckLabel::ePto) 2759 .AccumulateSingleSample(stats.pto_ack); 2760 2761 unsigned long late_ack_ratio = (stats.late_ack * 10000) / stats.packets_tx; 2762 unsigned long pto_ack_ratio = (stats.pto_ack * 10000) / stats.packets_tx; 2763 glean::http3::late_ack_ratio.EnumGet(glean::http3::LateAckRatioLabel::eAck) 2764 .AccumulateSingleSample(late_ack_ratio); 2765 glean::http3::late_ack_ratio.EnumGet(glean::http3::LateAckRatioLabel::ePto) 2766 .AccumulateSingleSample(pto_ack_ratio); 2767 2768 for (uint32_t i = 0; i < MAX_PTO_COUNTS; i++) { 2769 nsAutoCString key; 2770 key.AppendInt(i); 2771 glean::http3::counts_pto.Get(key).AccumulateSingleSample( 2772 stats.pto_counts[i]); 2773 } 2774 2775 glean::http3::drop_dgrams.AccumulateSingleSample(stats.dropped_rx); 2776 glean::http3::saved_dgrams.AccumulateSingleSample(stats.saved_datagrams); 2777 } 2778 2779 glean::http3::received_sent_dgrams 2780 .EnumGet(glean::http3::ReceivedSentDgramsLabel::eReceived) 2781 .AccumulateSingleSample(stats.packets_rx); 2782 glean::http3::received_sent_dgrams 2783 .EnumGet(glean::http3::ReceivedSentDgramsLabel::eSent) 2784 .AccumulateSingleSample(stats.packets_tx); 2785 2786 if (aClosing) { 2787 RefPtr<nsHttpConnectionInfo> ci; 2788 GetConnectionInfo(getter_AddRefs(ci)); 2789 if (ci && ci->GetIsTrrServiceChannel() && !mLastTRRResponseTime.IsNull() && 2790 (mGoawayReceived || 2791 (aError.tag == CloseError::Tag::PeerAppError && 2792 aError.peer_app_error._0 == HTTP3_APP_ERROR_NO_ERROR))) { 2793 // Record telemetry keyed by TRR provider. 2794 glean::network::trr_idle_close_time_h3.Get(TRRProviderKey()) 2795 .AccumulateRawDuration(TimeStamp::Now() - mLastTRRResponseTime); 2796 mLastTRRResponseTime = TimeStamp(); 2797 } 2798 } 2799 } 2800 2801 void Http3Session::Finish0Rtt(bool aRestart) { 2802 for (size_t i = 0; i < m0RTTStreams.Length(); ++i) { 2803 if (m0RTTStreams[i]) { 2804 if (aRestart) { 2805 // When we need to restart transactions remove them from all lists. 2806 if (m0RTTStreams[i]->HasStreamId()) { 2807 mStreamIdHash.Remove(m0RTTStreams[i]->StreamId()); 2808 } 2809 RemoveStreamFromQueues(m0RTTStreams[i]); 2810 // The stream is ready to write again. 2811 mReadyForWrite.Push(m0RTTStreams[i]); 2812 } 2813 m0RTTStreams[i]->Finish0RTT(aRestart); 2814 } 2815 } 2816 2817 for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) { 2818 if (mCannotDo0RTTStreams[i]) { 2819 mReadyForWrite.Push(mCannotDo0RTTStreams[i]); 2820 } 2821 } 2822 m0RTTStreams.Clear(); 2823 mCannotDo0RTTStreams.Clear(); 2824 MaybeResumeSend(); 2825 } 2826 2827 void Http3Session::ReportHttp3Connection() { 2828 if (CanSendData() && !mHttp3ConnectionReported) { 2829 mHttp3ConnectionReported = true; 2830 gHttpHandler->ConnMgr()->ReportHttp3Connection(mUdpConn); 2831 MaybeResumeSend(); 2832 } 2833 } 2834 2835 #ifndef ANDROID 2836 void Http3Session::EchOutcomeTelemetry() { 2837 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2838 2839 glean::http3::EchOutcomeLabel label; 2840 switch (mEchExtensionStatus) { 2841 case EchExtensionStatus::kNotPresent: 2842 label = glean::http3::EchOutcomeLabel::eNone; 2843 break; 2844 case EchExtensionStatus::kGREASE: 2845 label = glean::http3::EchOutcomeLabel::eGrease; 2846 break; 2847 case EchExtensionStatus::kReal: 2848 label = glean::http3::EchOutcomeLabel::eReal; 2849 break; 2850 } 2851 2852 glean::http3::ech_outcome.EnumGet(label).AccumulateSingleSample( 2853 mHandshakeSucceeded ? 0 : 1); 2854 } 2855 2856 void Http3Session::ZeroRttTelemetry(ZeroRttOutcome aOutcome) { 2857 nsAutoCString key; 2858 2859 switch (aOutcome) { 2860 case USED_SUCCEEDED: 2861 key = "succeeded"_ns; 2862 break; 2863 case USED_REJECTED: 2864 key = "rejected"_ns; 2865 break; 2866 case USED_CONN_ERROR: 2867 key = "conn_error"_ns; 2868 break; 2869 case USED_CONN_CLOSED_BY_NECKO: 2870 key = "conn_closed_by_necko"_ns; 2871 break; 2872 default: 2873 break; 2874 } 2875 2876 if (key.IsEmpty()) { 2877 mozilla::glean::netwerk::http3_0rtt_state.Get("not_used"_ns).Add(1); 2878 } else { 2879 MOZ_ASSERT(mZeroRttStarted); 2880 mozilla::TimeStamp zeroRttEnded = mozilla::TimeStamp::Now(); 2881 mozilla::glean::netwerk::http3_0rtt_state_duration.Get(key) 2882 .AccumulateRawDuration(zeroRttEnded - mZeroRttStarted); 2883 2884 mozilla::glean::netwerk::http3_0rtt_state.Get(key).Add(1); 2885 } 2886 } 2887 #endif 2888 2889 nsresult Http3Session::GetTransactionTLSSocketControl( 2890 nsITLSSocketControl** tlsSocketControl) { 2891 NS_IF_ADDREF(*tlsSocketControl = mSocketControl); 2892 return NS_OK; 2893 } 2894 2895 PRIntervalTime Http3Session::LastWriteTime() { return mLastWriteTime; } 2896 2897 //========================================================================= 2898 // WebTransport 2899 //========================================================================= 2900 2901 nsresult Http3Session::CloseWebTransport(uint64_t aSessionId, uint32_t aError, 2902 const nsACString& aMessage) { 2903 return mHttp3Connection->CloseWebTransport(aSessionId, aError, aMessage); 2904 } 2905 2906 nsresult Http3Session::CreateWebTransportStream( 2907 uint64_t aSessionId, WebTransportStreamType aStreamType, 2908 uint64_t* aStreamId) { 2909 return mHttp3Connection->CreateWebTransportStream(aSessionId, aStreamType, 2910 aStreamId); 2911 } 2912 2913 void Http3Session::SendDatagram(Http3WebTransportSession* aSession, 2914 nsTArray<uint8_t>& aData, 2915 uint64_t aTrackingId) { 2916 nsresult rv = mHttp3Connection->WebTransportSendDatagram(aSession->StreamId(), 2917 aData, aTrackingId); 2918 LOG(("Http3Session::SendDatagram %p res=%" PRIx32, this, 2919 static_cast<uint32_t>(rv))); 2920 if (!aTrackingId) { 2921 return; 2922 } 2923 2924 switch (rv) { 2925 case NS_OK: 2926 aSession->OnOutgoingDatagramOutCome( 2927 aTrackingId, WebTransportSessionEventListener::DatagramOutcome::SENT); 2928 break; 2929 case NS_ERROR_NOT_AVAILABLE: 2930 aSession->OnOutgoingDatagramOutCome( 2931 aTrackingId, WebTransportSessionEventListener::DatagramOutcome:: 2932 DROPPED_TOO_MUCH_DATA); 2933 break; 2934 default: 2935 aSession->OnOutgoingDatagramOutCome( 2936 aTrackingId, 2937 WebTransportSessionEventListener::DatagramOutcome::UNKNOWN); 2938 break; 2939 } 2940 } 2941 2942 uint64_t Http3Session::MaxDatagramSize(uint64_t aSessionId) { 2943 uint64_t size = 0; 2944 (void)mHttp3Connection->WebTransportMaxDatagramSize(aSessionId, &size); 2945 return size; 2946 } 2947 2948 void Http3Session::SendHTTPDatagram(uint64_t aStreamId, 2949 nsTArray<uint8_t>& aData, 2950 uint64_t aTrackingId) { 2951 LOG(("Http3Session::SendHTTPDatagram %p length=%zu aTrackingId=%" PRIx64, 2952 this, aData.Length(), aTrackingId)); 2953 (void)mHttp3Connection->ConnectUdpSendDatagram(aStreamId, aData, aTrackingId); 2954 } 2955 2956 void Http3Session::SetSendOrder(Http3StreamBase* aStream, 2957 Maybe<int64_t> aSendOrder) { 2958 if (!IsClosing()) { 2959 nsresult rv = mHttp3Connection->WebTransportSetSendOrder( 2960 aStream->StreamId(), aSendOrder); 2961 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2962 (void)rv; 2963 } 2964 } 2965 2966 Http3Stats Http3Session::GetStats() { 2967 if (!mHttp3Connection) { 2968 return Http3Stats(); 2969 } 2970 2971 Http3Stats stats{}; 2972 mHttp3Connection->GetStats(&stats); 2973 return stats; 2974 } 2975 2976 already_AddRefed<HttpConnectionUDP> Http3Session::CreateTunnelStream( 2977 nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks) { 2978 LOG(("Http3Session::CreateTunnelStream %p aHttpTransaction=%p", this, 2979 aHttpTransaction)); 2980 RefPtr<Http3StreamBase> stream = 2981 new Http3ConnectUDPStream(aHttpTransaction, this, NS_GetCurrentThread()); 2982 mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream}); 2983 StreamHasDataToWrite(stream); 2984 2985 RefPtr<HttpConnectionUDP> conn = 2986 stream->GetHttp3ConnectUDPStream()->CreateUDPConnection(aCallbacks); 2987 return conn.forget(); 2988 } 2989 2990 void Http3Session::FinishTunnelSetup(nsAHttpTransaction* aTransaction) { 2991 LOG(("Http3Session::FinishTunnelSetup %p aHttpTransaction=%p", this, 2992 aTransaction)); 2993 RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction); 2994 if (!stream || !stream->GetHttp3ConnectUDPStream()) { 2995 MOZ_ASSERT(false, "There must be a stream"); 2996 return; 2997 } 2998 2999 RemoveStreamFromQueues(stream); 3000 mStreamTransactionHash.Remove(aTransaction); 3001 mTunnelStreams.AppendElement(stream); 3002 } 3003 3004 already_AddRefed<nsHttpConnection> Http3Session::CreateTunnelStream( 3005 nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks, 3006 PRIntervalTime aRtt, bool aIsExtendedCONNECT) { 3007 LOG(("Http3Session::CreateTunnelStream %p aHttpTransaction=%p", this, 3008 aHttpTransaction)); 3009 RefPtr<Http3StreamBase> stream = 3010 new Http3StreamTunnel(aHttpTransaction, this, mCurrentBrowserId); 3011 mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream}); 3012 StreamHasDataToWrite(stream); 3013 3014 RefPtr<nsHttpConnection> conn = 3015 stream->GetHttp3StreamTunnel()->CreateHttpConnection(aCallbacks, aRtt, 3016 aIsExtendedCONNECT); 3017 return conn.forget(); 3018 } 3019 3020 } // namespace mozilla::net