/* vim:set ts=4 sw=2 sts=2 et cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // HttpLog.h should generally be included first #include "HttpLog.h" #include "HappyEyeballsConnectionAttempt.h" #include "ConnectionEntry.h" #include "HttpConnectionUDP.h" #include "nsIDNSAdditionalInfo.h" #include "nsDNSService2.h" #include "nsHttpConnectionMgr.h" #include "nsHttpHandler.h" #include "nsQueryObject.h" #include "nsSocketTransportService2.h" // Log on level :5, instead of default :4. #undef LOG #define LOG(args) LOG5(args) #undef LOG_ENABLED #define LOG_ENABLED() LOG5_ENABLED() namespace mozilla::net { NS_IMPL_ADDREF_INHERITED(HappyEyeballsConnectionAttempt, ConnectionAttempt) NS_IMPL_RELEASE_INHERITED(HappyEyeballsConnectionAttempt, ConnectionAttempt) NS_INTERFACE_MAP_BEGIN(HappyEyeballsConnectionAttempt) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsITimerCallback) NS_INTERFACE_MAP_ENTRY(nsINamed) NS_INTERFACE_MAP_ENTRY(nsIDNSListener) NS_INTERFACE_MAP_END HappyEyeballsConnectionAttempt::HappyEyeballsConnectionAttempt( nsHttpConnectionInfo* ci, nsAHttpTransaction* trans, uint32_t caps, bool speculative, bool urgentStart) : ConnectionAttempt(ci, trans, caps, speculative, urgentStart) { LOG(("HappyEyeballsConnectionAttempt ctor %p", this)); if (mConnInfo->GetRoutedHost().IsEmpty()) { mHost = mConnInfo->GetOrigin(); } else { mHost = mConnInfo->GetRoutedHost(); } } HappyEyeballsConnectionAttempt::~HappyEyeballsConnectionAttempt() { LOG(("HappyEyeballsConnectionAttempt dtor %p", this)); } nsresult HappyEyeballsConnectionAttempt::CreateHappyEyeballs( ConnectionEntry* ent) { happy_eyeballs::IpPreference ipPref = happy_eyeballs::IpPreference::DualStackPreferV6; if (ent->PreferenceKnown() && ent->mPreferIPv4) { ipPref = happy_eyeballs::IpPreference::DualStackPreferV4; } if (mConnInfo->GetRoutedHost().IsEmpty()) { nsTArray emptyAltSvc; return HappyEyeballs::Init(getter_AddRefs(mHappyEyeballs), mHost, static_cast(mConnInfo->OriginPort()), &emptyAltSvc, ipPref); } if (mConnInfo->IsHttp3()) { LOG(("HappyEyeballsConnectionAttempt for HTTP/3")); nsTArray altSvcArray; happy_eyeballs::AltSvc altsvc{}; altsvc.http_version = happy_eyeballs::HttpVersion::H3; altSvcArray.AppendElement(altsvc); return HappyEyeballs::Init(getter_AddRefs(mHappyEyeballs), mHost, static_cast(mConnInfo->RoutedPort()), &altSvcArray, ipPref); } nsTArray emptyAltSvc; return HappyEyeballs::Init(getter_AddRefs(mHappyEyeballs), mHost, static_cast(mConnInfo->RoutedPort()), &emptyAltSvc, ipPref); } nsresult HappyEyeballsConnectionAttempt::Init(ConnectionEntry* ent) { mEntry = ent; nsresult rv = CreateHappyEyeballs(ent); if (NS_FAILED(rv)) { return rv; } return ProcessHappyEyeballsOutput(); } static Result ToNetAddr( const happy_eyeballs::IpAddr& aIpAddr, uint16_t aPort) { NetAddr addr; memset(&addr, 0, sizeof(NetAddr)); uint16_t port = htons(aPort); switch (aIpAddr.tag) { case happy_eyeballs::IpAddr::Tag::V4: addr.inet.family = AF_INET; addr.inet.port = port; memcpy(&addr.inet.ip, aIpAddr.v4._0, 4); break; case happy_eyeballs::IpAddr::Tag::V6: addr.inet6.family = AF_INET6; addr.inet6.port = port; memcpy(&addr.inet6.ip, aIpAddr.v6._0, 16); break; default: return Err(NS_ERROR_UNEXPECTED); } return addr; } nsresult HappyEyeballsConnectionAttempt::ProcessConnectionResult( const NetAddr& aAddr, nsresult aStatus, uint64_t aId) { LOG( ("HappyEyeballsConnectionAttempt::ProcessConnectionResult %p addr=[%s] " "id=%" PRIu64, this, aAddr.ToString().get(), aId)); nsresult rv = happy_eyeballs::process_connection_result(mHappyEyeballs, aId, aStatus); if (NS_FAILED(rv)) { LOG(("process_connection_result failed rv=%x", static_cast(rv))); } return ProcessHappyEyeballsOutput(); } nsresult HappyEyeballsConnectionAttempt::ProcessHappyEyeballsOutput() { LOG(("HappyEyeballsConnectionAttempt::ProcessHappyEyeballsOutput %p", this)); if (mDone) { return NS_OK; } nsresult rv = NS_OK; while (!mDone) { happy_eyeballs::Output event{}; nsTArray echConfig; rv = happy_eyeballs::process_output(mHappyEyeballs, &event, &echConfig); if (NS_FAILED(rv)) { LOG(("process_output failed rv=%x", static_cast(rv))); return rv; } switch (event.tag) { case happy_eyeballs::Output::Tag::SendDnsQuery: { LOG(("HappyEyeballsEvent::Tag::SendDnsQuery id=%" PRIu64, event.send_dns_query.id)); auto dnsFlags = SetupDnsFlags(event.send_dns_query.record_type); if (dnsFlags.isOk()) { rv = DNSLookup(event.send_dns_query.record_type, dnsFlags.unwrap(), event.send_dns_query.id); if (NS_FAILED(rv)) { Abandon(); return rv; } } break; } case happy_eyeballs::Output::Tag::Timer: { SetupTimer(event.timer.duration_ms); return NS_OK; } case happy_eyeballs::Output::Tag::AttemptConnection: { LOG(("HappyEyeballsEvent::Tag::AttemptConnection id=%" PRIu64 " protocol=%d port=%d ", event.attempt_connection.id, static_cast(event.attempt_connection.http_version), event.attempt_connection.port)); if (mFirstConnectionStart.IsNull()) { mFirstConnectionStart = TimeStamp::Now(); } auto res = ToNetAddr(event.attempt_connection.addr, event.attempt_connection.port); if (res.isErr()) { LOG(("Failed to convert to NetAddr")); // TODO: how to handle this error? MOZ_ASSERT(false, "Failed to convert to NetAddr"); return res.unwrapErr(); } LOG(("connect to:[%s] ech_config_len=%zu", res.unwrap().ToString().get(), echConfig.Length())); if (event.attempt_connection.http_version == happy_eyeballs::ConnectionAttemptHttpVersions::H3) { EstablishUDPConnection(res.unwrap(), event.attempt_connection.port, std::move(echConfig), event.attempt_connection.id); } else { EstablishTCPConnection(res.unwrap(), event.attempt_connection.port, std::move(echConfig), event.attempt_connection.id); } break; } case happy_eyeballs::Output::Tag::CancelConnection: { LOG(("CancelConnection id=%" PRIu64, event.cancel_connection.id)); CancelConnection(event.cancel_connection.id); break; } case happy_eyeballs::Output::Tag::Succeeded: LOG(("happy_eyeballs::Output::Tag::Succeeded")); OnSucceeded(); return NS_OK; case happy_eyeballs::Output::Tag::Failed: { LOG(("happy_eyeballs::Output::Tag::Failed")); RefPtr self(this); RefPtr entry(mEntry); if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) { if (entry) { entry->RemoveTransFromPendingQ(trans); } } mTransaction->Close(NS_ERROR_CONNECTION_REFUSED); Abandon(); if (entry) { entry->RemoveConnectionAttempt(this, false); } return NS_ERROR_CONNECTION_REFUSED; } case happy_eyeballs::Output::Tag::None: LOG(("happy_eyeballs::Output::Tag::None")); // No more events to process return NS_OK; } } return NS_OK; } Result HappyEyeballsConnectionAttempt::SetupDnsFlags( happy_eyeballs::DnsRecordType aType) { LOG(("HappyEyeballsConnectionAttempt::SetupDnsFlags [this=%p aType=%d] ", this, static_cast(aType))); nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS; if (mCaps & NS_HTTP_REFRESH_DNS) { dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE; } switch (aType) { case happy_eyeballs::DnsRecordType::Https: dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(mConnInfo->GetTRRMode()); return dnsFlags; case happy_eyeballs::DnsRecordType::Aaaa: if (mCaps & NS_HTTP_DISABLE_IPV6) { return Err(NS_ERROR_NOT_AVAILABLE); } dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; break; case happy_eyeballs::DnsRecordType::A: if (mCaps & NS_HTTP_DISABLE_IPV4) { return Err(NS_ERROR_NOT_AVAILABLE); } dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; break; } // Deal with IP hints later /*if (ent->mConnInfo->HasIPHintAddress()) { nsresult rv; nsCOMPtr dns; dns = mozilla::components::DNS::Service(&rv); if (NS_FAILED(rv)) { return rv; } // The spec says: "If A and AAAA records for TargetName are locally // available, the client SHOULD ignore these hints.", so we check if the DNS // record is in cache before setting USE_IP_HINT_ADDRESS. nsCOMPtr record; rv = dns->ResolveNative( mPrimaryTransport.mHost, nsIDNSService::RESOLVE_OFFLINE, mConnInfo->GetOriginAttributes(), getter_AddRefs(record)); if (NS_FAILED(rv) || !record) { LOG(("Setting Socket to use IP hint address")); dnsFlags |= nsIDNSService::RESOLVE_IP_HINT; } }*/ dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps)); // When we get here, we are not resolving using any configured proxy likely // because of individual proxy setting on the request or because the host is // excluded from proxying. Hence, force resolution despite global proxy-DNS // configuration. dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS; NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) || !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4"); LOG(("dnsFlags=%u", dnsFlags)); return dnsFlags; } void HappyEyeballsConnectionAttempt::MaybeSendTransportStatus( nsresult aStatus, nsITransport* aTransport, int64_t aProgress) { if (!mSentTransportStatuses.EnsureInserted(static_cast(aStatus)) || !mTransaction) { return; } mTransaction->OnTransportStatus(aTransport, aStatus, aProgress); } nsresult HappyEyeballsConnectionAttempt::DNSLookup( happy_eyeballs::DnsRecordType aType, nsIDNSService::DNSFlags aFlags, uint64_t aId) { nsCOMPtr dns = GetOrInitDNSService(); if (!dns) { return NS_ERROR_UNEXPECTED; } if (mDomainLookupStart.IsNull() && (aType == happy_eyeballs::DnsRecordType::A || aType == happy_eyeballs::DnsRecordType::Aaaa)) { mDomainLookupStart = TimeStamp::Now(); MaybeSendTransportStatus(NS_NET_STATUS_RESOLVING_HOST); } RefPtr requestInfo = new DnsRequestInfo(aId, aType); nsCOMPtr request; nsresult rv = NS_OK; switch (aType) { case happy_eyeballs::DnsRecordType::Https: { if (mCaps & NS_HTTP_DISALLOW_HTTPS_RR) { rv = NS_ERROR_NOT_AVAILABLE; } else { nsCOMPtr info; if (mConnInfo->OriginPort() != NS_HTTPS_DEFAULT_PORT) { dns->NewAdditionalInfo(""_ns, mConnInfo->OriginPort(), getter_AddRefs(info)); } rv = dns->AsyncResolveNative( mHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, aFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, info, this, gSocketTransportService, mConnInfo->GetOriginAttributes(), getter_AddRefs(request)); } break; } case happy_eyeballs::DnsRecordType::Aaaa: rv = dns->AsyncResolveNative( mHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, aFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, nullptr, this, gSocketTransportService, mConnInfo->GetOriginAttributes(), getter_AddRefs(request)); break; case happy_eyeballs::DnsRecordType::A: rv = dns->AsyncResolveNative( mHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, aFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, nullptr, this, gSocketTransportService, mConnInfo->GetOriginAttributes(), getter_AddRefs(request)); break; } if (NS_SUCCEEDED(rv) && request) { requestInfo->SetRequest(request); mDnsRequestTable.InsertOrUpdate(request, requestInfo); } else { // Notify the DNS response synchronously on failure. NS_DispatchToCurrentThread(NS_NewRunnableFunction( "HappyEyeballsConnectionAttempt::DNSLookup", [self = RefPtr{this}, aType, aId]() { switch (aType) { case happy_eyeballs::DnsRecordType::Https: (void)self->OnHTTPSRecord(nullptr, NS_ERROR_UNKNOWN_HOST, aId); break; case happy_eyeballs::DnsRecordType::Aaaa: (void)self->OnAAAARecord(nullptr, NS_ERROR_UNKNOWN_HOST, aId); break; case happy_eyeballs::DnsRecordType::A: (void)self->OnARecord(nullptr, NS_ERROR_UNKNOWN_HOST, aId); break; } })); } return NS_OK; } void HappyEyeballsConnectionAttempt::HandleTCPConnectionResult( Result, nsresult> aResult, TCPConnectionEstablisher* aEstablisher, uint64_t aId) { RefPtr establisher = aEstablisher; mConnectionEstablisherTable.Remove(aId); NetAddr addr = establisher->Addr(); LOG( ("HappyEyeballsConnectionAttempt::HandleTCPConnectionResult %p addr=[%s] " "family=[%d] id=%" PRIu64, this, addr.ToString().get(), addr.raw.family, aId)); if (aResult.isErr()) { establisher->Close(aResult.unwrapErr()); ProcessConnectionResult(addr, aResult.unwrapErr(), aId); return; } if (mDone) { establisher->Close(NS_BASE_STREAM_CLOSED); ProcessConnectionResult(addr, NS_BASE_STREAM_CLOSED, aId); return; } mOutputConn = aResult.unwrap(); mAddrFamily = addr.raw.family; // The ownership of connection is moved to HappyEyeballsConnectionAttempt now. establisher->ClearResultConnection(); ProcessConnectionResult(addr, NS_OK, aId); } nsresult HappyEyeballsConnectionAttempt::EstablishTCPConnection( NetAddr aAddr, uint16_t aPort, nsTArray&& aEchConfig, uint64_t aId) { // TODO: we always use happy_eyeballs::ConnectionAttemptHttpVersions::H2OrH1 // for now. Do we really want to race H2 and H1? RefPtr info = mConnInfo->CloneAndAdoptPortAndAlpn( aPort, happy_eyeballs::ConnectionAttemptHttpVersions::H2OrH1); if (!aEchConfig.IsEmpty()) { info->SetEchConfig( nsCString((const char*)aEchConfig.Elements(), aEchConfig.Length())); } RefPtr establisher = new TCPConnectionEstablisher( info, aAddr, mCaps, mSpeculative, mAllow1918); nsCOMPtr callbacks; mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks)); establisher->SetSecurityCallbacks(callbacks); establisher->SetTransportStatusCallback( [self = RefPtr{this}](nsITransport* trans, nsresult status, int64_t progress) { self->MaybeSendTransportStatus(status, trans, progress); }); auto callback = [self = RefPtr{this}, establisher, aId](Result, nsresult> aResult) { self->HandleTCPConnectionResult(std::move(aResult), establisher, aId); }; if (establisher->Start(std::move(callback))) { mConnectionEstablisherTable.InsertOrUpdate(aId, std::move(establisher)); } else { ProcessConnectionResult(aAddr, NS_ERROR_FAILURE, aId); } return NS_OK; } nsresult HappyEyeballsConnectionAttempt::EstablishUDPConnection( NetAddr aAddr, uint16_t aPort, nsTArray&& aEchConfig, uint64_t aId) { RefPtr info = mConnInfo->CloneAndAdoptPortAndAlpn( aPort, happy_eyeballs::ConnectionAttemptHttpVersions::H3); if (!aEchConfig.IsEmpty()) { info->SetEchConfig( nsCString((const char*)aEchConfig.Elements(), aEchConfig.Length())); } RefPtr establisher = new UDPConnectionEstablisher(info, aAddr, mCaps); establisher->SetTransportStatusCallback( [self = RefPtr{this}](nsITransport* trans, nsresult status, int64_t progress) { self->MaybeSendTransportStatus(status, trans, progress); }); auto callback = [self = RefPtr{this}, establisher, aId](Result, nsresult> aResult) { self->HandleUDPConnectionResult(std::move(aResult), establisher, aId); }; if (establisher->Start(std::move(callback))) { mConnectionEstablisherTable.InsertOrUpdate(aId, std::move(establisher)); } else { ProcessConnectionResult(aAddr, NS_ERROR_FAILURE, aId); } return NS_OK; } void HappyEyeballsConnectionAttempt::HandleUDPConnectionResult( Result, nsresult> aResult, UDPConnectionEstablisher* aEstablisher, uint64_t aId) { RefPtr establisher = aEstablisher; mConnectionEstablisherTable.Remove(aId); NetAddr addr = establisher->Addr(); LOG( ("HappyEyeballsConnectionAttempt::HandleUDPConnectionResult %p addr=[%s] " "family=[%d] id=%" PRIu64, this, addr.ToString().get(), addr.raw.family, aId)); if (aResult.isErr()) { establisher->Close(aResult.unwrapErr()); ProcessConnectionResult(addr, aResult.unwrapErr(), aId); return; } if (mDone) { establisher->Close(NS_BASE_STREAM_CLOSED); ProcessConnectionResult(addr, NS_BASE_STREAM_CLOSED, aId); return; } mOutputConn = aResult.unwrap(); mAddrFamily = addr.raw.family; // The ownership of connection is moved to HappyEyeballsConnectionAttempt now. establisher->ClearResultConnection(); ProcessConnectionResult(addr, NS_OK, aId); } void HappyEyeballsConnectionAttempt::CancelConnection(uint64_t aId) { LOG(("HappyEyeballsConnectionAttempt::CancelConnection id=%" PRIu64, aId)); RefPtr conn = mConnectionEstablisherTable.Get(aId); if (conn) { conn->Close(NS_ERROR_ABORT); mConnectionEstablisherTable.Remove(aId); } else { LOG(("No matching connection found for id=%" PRIu64, aId)); } } void HappyEyeballsConnectionAttempt::Abandon() { LOG(("HappyEyeballsConnectionAttempt::Abandon %p", this)); mDone = true; // Cancel all DNS requests for (auto iter = mDnsRequestTable.Iter(); !iter.Done(); iter.Next()) { iter.Data()->Cancel(); } mDnsRequestTable.Clear(); // Collect all connection establishers into a temporary array to avoid // iterator invalidation when Close() triggers callbacks that modify the table nsTArray> establishers; for (auto iter = mConnectionEstablisherTable.Iter(); !iter.Done(); iter.Next()) { establishers.AppendElement(iter.Data()); } mConnectionEstablisherTable.Clear(); // Now close all the connections without worrying about iterator invalidation for (auto& conn : establishers) { conn->Close(NS_ERROR_ABORT); } if (mTimer) { mTimer->Cancel(); } mTimer = nullptr; mEntry = nullptr; } void HappyEyeballsConnectionAttempt::ProcessTCPConn(nsHttpConnection* aConn, ConnectionEntry* aEntry) { RefPtr entry(mEntry); if (!entry) { return; } RefPtr connTCP = aConn; LOG(("Got connTCP:%p", connTCP.get())); entry->InsertIntoActiveConns(connTCP); RefPtr pendingTransInfo = gHttpHandler->ConnMgr()->FindTransactionHelper(true, entry, mTransaction); bool isHttp2 = connTCP->UsingSpdy(); if (pendingTransInfo) { MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction"); nsresult rv = gHttpHandler->ConnMgr()->DispatchTransaction( entry, pendingTransInfo->Transaction(), connTCP); if (NS_FAILED(rv)) { mTransaction->Close(rv); } } else if (!isHttp2) { // After about 1 second allow for the possibility of restarting a // transaction due to server close. Keep at sub 1 second as that is the // minimum granularity we can expect a server to be timing out with. connTCP->SetIsReusedAfter(950); LOG( ("ProcessTCPConn no transaction match " "returning conn %p to pool\n", connTCP.get())); gHttpHandler->ConnMgr()->OnMsgReclaimConnection(connTCP); } connTCP->SetIsRacing(false); if (isHttp2) { gHttpHandler->ConnMgr()->ReportSpdyConnection( connTCP, true, (mCaps & NS_HTTP_DISALLOW_HTTP3)); } else { gHttpHandler->ConnMgr()->ReportSpdyConnection(connTCP, false, false); } } void HappyEyeballsConnectionAttempt::ProcessUDPConn(HttpConnectionUDP* aConn, ConnectionEntry* aEntry) { RefPtr entry(mEntry); if (!entry) { return; } LOG(("Got connUDP:%p", aConn)); entry->InsertIntoActiveConns(aConn); RefPtr pendingTransInfo = gHttpHandler->ConnMgr()->FindTransactionHelper(true, entry, mTransaction); nsresult rv = NS_OK; if (pendingTransInfo) { MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction"); rv = gHttpHandler->ConnMgr()->DispatchTransaction( entry, pendingTransInfo->Transaction(), aConn); if (NS_FAILED(rv)) { mTransaction->Close(rv); } } else { rv = aConn->Activate(mTransaction, mCaps, 0); } aConn->SetIsRacing(false); gHttpHandler->ConnMgr()->ReportHttp3Connection(aConn, entry); } void HappyEyeballsConnectionAttempt::OnSucceeded() { LOG(("HappyEyeballsConnectionAttempt::OnSucceeded %p", this)); MOZ_ASSERT(!mDone); mDone = true; RefPtr self(this); RefPtr entry(mEntry); MOZ_ASSERT(entry); entry->RecordIPFamilyPreference(mAddrFamily); if (!mDomainLookupStart.IsNull()) { mOutputConn->SetDnsBootstrapTimings(mDomainLookupStart, mDomainLookupEnd); } RefPtr connTCP = do_QueryObject(mOutputConn); if (connTCP) { ProcessTCPConn(connTCP, entry); } else { RefPtr connUDP = do_QueryObject(mOutputConn); ProcessUDPConn(connUDP, entry); } mOutputConn = nullptr; // Make sure everything is released. Abandon(); entry->RemoveConnectionAttempt(this, false); } double HappyEyeballsConnectionAttempt::Duration(TimeStamp epoch) { if (mFirstConnectionStart.IsNull()) { return 0; } return (epoch - mFirstConnectionStart).ToMilliseconds(); } void HappyEyeballsConnectionAttempt::OnTimeout() { LOG(("HappyEyeballsConnectionAttempt::OnTimeout %p" PRIx32, this)); if (mTransaction) { mTransaction->Close(NS_ERROR_NET_TIMEOUT); } Abandon(); } void HappyEyeballsConnectionAttempt::PrintDiagnostics(nsCString& log) {} uint32_t HappyEyeballsConnectionAttempt::UnconnectedUDPConnsLength() const { uint32_t len = 0; for (auto iter = mConnectionEstablisherTable.ConstIter(); !iter.Done(); iter.Next()) { if (iter.Data()->IsUDP()) { len++; } } return len; } bool HappyEyeballsConnectionAttempt::Claim(nsHttpTransaction* newTransaction) { if (mSpeculative) { mSpeculative = false; mAllow1918 = true; for (auto iter = mConnectionEstablisherTable.Iter(); !iter.Done(); iter.Next()) { RefPtr conn = iter.Data(); conn->ResetSpeculativeFlags(); } } if (mFreeToUse) { mFreeToUse = false; if (newTransaction && mTransaction && mTransaction->QueryNullTransaction()) { LOG( ("HappyEyeballsConnectionAttempt::Claim %p replacing null " "transaction %p with %p", this, mTransaction.get(), newTransaction)); mTransaction->Close(NS_ERROR_ABORT); mTransaction = newTransaction; } return true; } return false; } NS_IMETHODIMP HappyEyeballsConnectionAttempt::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec, nsresult status) { LOG(("HappyEyeballsConnectionAttempt::OnLookupComplete")); if (!request) { return NS_OK; } RefPtr info = mDnsRequestTable.Get(request); if (!info) { LOG(("OnLookupComplete: Unknown DNS request")); return NS_OK; } uint64_t id = info->Id(); happy_eyeballs::DnsRecordType type = info->Type(); mDnsRequestTable.Remove(request); switch (type) { case happy_eyeballs::DnsRecordType::A: return OnARecord(rec, status, id); case happy_eyeballs::DnsRecordType::Aaaa: return OnAAAARecord(rec, status, id); case happy_eyeballs::DnsRecordType::Https: return OnHTTPSRecord(rec, status, id); } return NS_OK; } nsresult HappyEyeballsConnectionAttempt::OnARecord(nsIDNSRecord* aRecord, nsresult status, uint64_t aId) { LOG(("HappyEyeballsConnectionAttempt::OnARecord: this=%p status %" PRIx32 " id=%" PRIu64, this, static_cast(status), aId)); if (NS_SUCCEEDED(status)) { mDomainLookupEnd = TimeStamp::Now(); MaybeSendTransportStatus(NS_NET_STATUS_RESOLVED_HOST); } // TODO: use NS_ERROR_UNKNOWN_PROXY_HOST if stasus is failed and proxy is used nsCOMPtr addrRecord = do_QueryInterface(aRecord); nsresult rv; if (NS_FAILED(status) || !addrRecord) { nsTArray emptyArray; rv = happy_eyeballs::process_dns_response_a(mHappyEyeballs, aId, &emptyArray); if (NS_FAILED(rv)) { return rv; } return ProcessHappyEyeballsOutput(); } nsTArray addresses; addrRecord->GetAddresses(addresses); // Filter to only IPv4 addresses nsTArray ipv4Addresses; for (const auto& addr : addresses) { if (addr.raw.family == AF_INET) { LOG(("Addr=[%s]", addr.ToString().get())); ipv4Addresses.AppendElement(addr); } } rv = happy_eyeballs::process_dns_response_a(mHappyEyeballs, aId, &ipv4Addresses); if (NS_FAILED(rv)) { return rv; } return ProcessHappyEyeballsOutput(); } nsresult HappyEyeballsConnectionAttempt::OnAAAARecord(nsIDNSRecord* aRecord, nsresult status, uint64_t aId) { LOG(("HappyEyeballsConnectionAttempt::OnAAAARecord: this=%p status %" PRIx32 " id=%" PRIu64, this, static_cast(status), aId)); if (NS_SUCCEEDED(status)) { mDomainLookupEnd = TimeStamp::Now(); MaybeSendTransportStatus(NS_NET_STATUS_RESOLVED_HOST); } // TODO: use NS_ERROR_UNKNOWN_PROXY_HOST if stasus is failed and proxy is used nsCOMPtr addrRecord = do_QueryInterface(aRecord); nsresult rv; if (NS_FAILED(status) || !addrRecord) { nsTArray emptyArray; rv = happy_eyeballs::process_dns_response_aaaa(mHappyEyeballs, aId, &emptyArray); if (NS_FAILED(rv)) { return rv; } return ProcessHappyEyeballsOutput(); } nsTArray addresses; addrRecord->GetAddresses(addresses); // Filter to only IPv6 addresses nsTArray ipv6Addresses; for (const auto& addr : addresses) { if (addr.raw.family == AF_INET6) { LOG(("Addr=[%s]", addr.ToString().get())); ipv6Addresses.AppendElement(addr); } } rv = happy_eyeballs::process_dns_response_aaaa(mHappyEyeballs, aId, &ipv6Addresses); if (NS_FAILED(rv)) { return rv; } return ProcessHappyEyeballsOutput(); } // Helper function to convert ALPN string to HttpVersion enum static Maybe AlpnStringToProtocol( const nsACString& aAlpn) { if (aAlpn.EqualsLiteral("h3")) { return Some(happy_eyeballs::HttpVersion::H3); } if (aAlpn.EqualsLiteral("h2")) { return Some(happy_eyeballs::HttpVersion::H2); } if (aAlpn.EqualsLiteral("http/1.1")) { return Some(happy_eyeballs::HttpVersion::H1); } // Unknown ALPN protocol return Nothing(); } nsresult HappyEyeballsConnectionAttempt::OnHTTPSRecord(nsIDNSRecord* aRecord, nsresult status, uint64_t aId) { LOG(("HappyEyeballsConnectionAttempt::OnHTTPSRecord %p status=%x id=%" PRIu64, this, static_cast(status), aId)); nsCOMPtr httpsRecord = do_QueryInterface(aRecord); if (!httpsRecord || NS_FAILED(status)) { nsTArray emptyArray; (void)happy_eyeballs::process_dns_response_https(mHappyEyeballs, aId, &emptyArray); return ProcessHappyEyeballsOutput(); } nsTArray> svcbRecords; // TODO: Handle aNoHttp2, aNoHttp3, and aCname. (void)httpsRecord->GetRecords(svcbRecords); if (svcbRecords.IsEmpty()) { nsTArray emptyArray; (void)happy_eyeballs::process_dns_response_https(mHappyEyeballs, aId, &emptyArray); return ProcessHappyEyeballsOutput(); } nsTArray serviceInfos; for (const auto& svcbRecord : svcbRecords) { happy_eyeballs::ServiceInfo svcInfo; (void)svcbRecord->GetPriority(&svcInfo.priority); (void)svcbRecord->GetName(svcInfo.target_name); svcInfo.port = svcbRecord->GetPort().valueOr(0); nsTArray> values; (void)svcbRecord->GetValues(values); nsTArray alpn; nsTArray> ipv4Hint; nsTArray> ipv6Hint; for (const auto& value : values) { uint16_t type; (void)value->GetType(&type); switch (type) { case SvcParamKeyAlpn: { nsCOMPtr alpnParam = do_QueryInterface(value); (void)alpnParam->GetAlpn(alpn); break; } case SvcParamKeyNoDefaultAlpn: break; case SvcParamKeyIpv4Hint: { nsCOMPtr ipv4Param = do_QueryInterface(value); (void)ipv4Param->GetIpv4Hint(ipv4Hint); break; } case SvcParamKeyIpv6Hint: { nsCOMPtr ipv6Param = do_QueryInterface(value); (void)ipv6Param->GetIpv6Hint(ipv6Hint); break; } case SvcParamKeyEchConfig: { nsCOMPtr echConfigParam = do_QueryInterface(value); nsCString echConfig; (void)echConfigParam->GetEchconfig(echConfig); svcInfo.ech_config.AppendElements( reinterpret_cast(echConfig.BeginReading()), echConfig.Length()); break; } default: break; } } for (const auto& alpnStr : alpn) { auto protocol = AlpnStringToProtocol(alpnStr); if (protocol) { svcInfo.alpn_http_versions.AppendElement(protocol.ref()); } } for (const auto& addr : ipv4Hint) { NetAddr netAddr; addr->GetNetAddr(&netAddr); svcInfo.ipv4_hints.AppendElement(netAddr); } for (const auto& addr : ipv6Hint) { NetAddr netAddr; addr->GetNetAddr(&netAddr); svcInfo.ipv6_hints.AppendElement(netAddr); } serviceInfos.AppendElement(std::move(svcInfo)); } (void)happy_eyeballs::process_dns_response_https(mHappyEyeballs, aId, &serviceInfos); return ProcessHappyEyeballsOutput(); } NS_IMETHODIMP // method for nsITimerCallback HappyEyeballsConnectionAttempt::Notify(nsITimer* timer) { return ProcessHappyEyeballsOutput(); } NS_IMETHODIMP // method for nsINamed HappyEyeballsConnectionAttempt::GetName(nsACString& aName) { aName.AssignLiteral("HappyEyeballsConnectionAttempt"); return NS_OK; } void HappyEyeballsConnectionAttempt::SetupTimer(uint64_t aTimeout) { if (!aTimeout) { MOZ_ASSERT(false, "aTimeout should not be 0"); return; } LOG3(("HappyEyeballsConnectionAttempt::SetupTimer to %" PRIu64 "ms [this=%p].", aTimeout, this)); if (!mTimer) { // This can only fail on OOM and we'd crash. mTimer = NS_NewTimer(); } DebugOnly rv = mTimer->InitWithCallback(this, aTimeout, nsITimer::TYPE_ONE_SHOT); // There is no meaningful error handling we can do here. But an error here // should only be possible if the timer thread did already shut down. MOZ_ASSERT(NS_SUCCEEDED(rv)); } } // namespace mozilla::net