1406 lines
49 KiB
C++
1406 lines
49 KiB
C++
/* 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 "ConnectionHandle.h"
|
|
#include "DnsAndConnectSocket.h"
|
|
#include "nsHttpConnection.h"
|
|
#include "nsIClassOfService.h"
|
|
#include "nsIDNSRecord.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsIHttpActivityObserver.h"
|
|
#include "nsSocketTransportService2.h"
|
|
#include "nsDNSService2.h"
|
|
#include "nsQueryObject.h"
|
|
#include "nsURLHelper.h"
|
|
#include "mozilla/Components.h"
|
|
#include "mozilla/StaticPrefs_network.h"
|
|
#include "mozilla/SyncRunnable.h"
|
|
#include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
|
|
#include "nsHttpHandler.h"
|
|
#include "ConnectionEntry.h"
|
|
#include "HttpConnectionUDP.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "mozilla/net/NeckoChannelParams.h" // For HttpActivityArgs.
|
|
|
|
// 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 {
|
|
namespace net {
|
|
|
|
//////////////////////// DnsAndConnectSocket
|
|
NS_IMPL_ADDREF(DnsAndConnectSocket)
|
|
NS_IMPL_RELEASE(DnsAndConnectSocket)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(DnsAndConnectSocket)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
|
|
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
|
|
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
|
|
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
|
|
NS_INTERFACE_MAP_ENTRY(nsINamed)
|
|
NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
|
|
NS_INTERFACE_MAP_ENTRY_CONCRETE(DnsAndConnectSocket)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
static void NotifyActivity(nsHttpConnectionInfo* aConnInfo, uint32_t aSubtype) {
|
|
HttpConnectionActivity activity(
|
|
aConnInfo->HashKey(), aConnInfo->GetOrigin(), aConnInfo->OriginPort(),
|
|
aConnInfo->EndToEndSSL(), !aConnInfo->GetEchConfig().IsEmpty(),
|
|
aConnInfo->IsHttp3());
|
|
gHttpHandler->ObserveHttpActivityWithArgs(
|
|
activity, NS_ACTIVITY_TYPE_HTTP_CONNECTION, aSubtype, PR_Now(), 0, ""_ns);
|
|
}
|
|
|
|
DnsAndConnectSocket::DnsAndConnectSocket(nsHttpConnectionInfo* ci,
|
|
nsAHttpTransaction* trans,
|
|
uint32_t caps, bool speculative,
|
|
bool isFromPredictor, bool urgentStart)
|
|
: mTransaction(trans),
|
|
mCaps(caps),
|
|
mSpeculative(speculative),
|
|
mUrgentStart(urgentStart),
|
|
mIsFromPredictor(isFromPredictor),
|
|
mConnInfo(ci) {
|
|
MOZ_ASSERT(ci && trans, "constructor with null arguments");
|
|
LOG(("Creating DnsAndConnectSocket [this=%p trans=%p ent=%s key=%s]\n", this,
|
|
trans, mConnInfo->Origin(), mConnInfo->HashKey().get()));
|
|
|
|
mIsHttp3 = mConnInfo->IsHttp3();
|
|
|
|
MOZ_ASSERT(mConnInfo);
|
|
NotifyActivity(mConnInfo,
|
|
mSpeculative
|
|
? NS_HTTP_ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED
|
|
: NS_HTTP_ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED);
|
|
}
|
|
|
|
void DnsAndConnectSocket::CheckIsDone() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mSocketTransport);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mStreamOut);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mPrimaryTransport.mDNSRequest);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mSocketTransport);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mStreamOut);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mBackupTransport.mDNSRequest);
|
|
}
|
|
|
|
DnsAndConnectSocket::~DnsAndConnectSocket() {
|
|
LOG(("Destroying DnsAndConnectSocket [this=%p]\n", this));
|
|
MOZ_ASSERT(mState == DnsAndSocketState::DONE);
|
|
CheckIsDone();
|
|
// Check in case something goes wrong that we decrease
|
|
// the nsHttpConnectionMgr active connection number.
|
|
mPrimaryTransport.MaybeSetConnectingDone();
|
|
mBackupTransport.MaybeSetConnectingDone();
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::Init(ConnectionEntry* ent) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
MOZ_ASSERT(mState == DnsAndSocketState::INIT);
|
|
|
|
if (mConnInfo->GetRoutedHost().IsEmpty()) {
|
|
mPrimaryTransport.mHost = mConnInfo->GetOrigin();
|
|
mBackupTransport.mHost = mConnInfo->GetOrigin();
|
|
} else {
|
|
mPrimaryTransport.mHost = mConnInfo->GetRoutedHost();
|
|
mBackupTransport.mHost = mConnInfo->GetRoutedHost();
|
|
}
|
|
|
|
CheckProxyConfig();
|
|
|
|
if (!mSkipDnsResolution) {
|
|
nsresult rv = SetupDnsFlags(ent);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
return SetupEvent(SetupEvents::INIT_EVENT);
|
|
}
|
|
|
|
void DnsAndConnectSocket::CheckProxyConfig() {
|
|
if (nsCOMPtr<nsProxyInfo> proxyInfo = mConnInfo->ProxyInfo()) {
|
|
nsAutoCString proxyType(proxyInfo->Type());
|
|
|
|
bool proxyTransparent = false;
|
|
if (proxyType.EqualsLiteral("socks") || proxyType.EqualsLiteral("socks4")) {
|
|
proxyTransparent = true;
|
|
if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
|
|
mProxyTransparentResolvesHost = true;
|
|
}
|
|
}
|
|
|
|
if (mProxyTransparentResolvesHost) {
|
|
// Name resolution is done on the server side. Just pretend
|
|
// client resolution is complete, this will get picked up later.
|
|
// since we don't need to do DNS now, we bypass the resolving
|
|
// step by initializing mNetAddr to an empty address, but we
|
|
// must keep the port. The SOCKS IO layer will use the hostname
|
|
// we send it when it's created, rather than the empty address
|
|
// we send with the connect call.
|
|
mPrimaryTransport.mSkipDnsResolution = true;
|
|
mBackupTransport.mSkipDnsResolution = true;
|
|
mSkipDnsResolution = true;
|
|
}
|
|
|
|
if (!proxyTransparent && !proxyInfo->Host().IsEmpty()) {
|
|
mProxyNotTransparent = true;
|
|
mPrimaryTransport.mHost = proxyInfo->Host();
|
|
mBackupTransport.mHost = proxyInfo->Host();
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::SetupDnsFlags(ConnectionEntry* ent) {
|
|
LOG(("DnsAndConnectSocket::SetupDnsFlags [this=%p] ", this));
|
|
|
|
nsIDNSService::DNSFlags dnsFlags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
|
|
bool disableIpv6ForBackup = false;
|
|
if (mCaps & NS_HTTP_REFRESH_DNS) {
|
|
dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
|
|
}
|
|
if (mCaps & NS_HTTP_DISABLE_IPV4) {
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
|
|
} else if (mCaps & NS_HTTP_DISABLE_IPV6) {
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
|
|
} else if (ent->PreferenceKnown()) {
|
|
if (ent->mPreferIPv6) {
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
|
|
} else if (ent->mPreferIPv4) {
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
|
|
}
|
|
mPrimaryTransport.mRetryWithDifferentIPFamily = true;
|
|
mBackupTransport.mRetryWithDifferentIPFamily = true;
|
|
} else if (gHttpHandler->FastFallbackToIPv4()) {
|
|
// For backup connections, we disable IPv6. That's because some users have
|
|
// broken IPv6 connectivity (leading to very long timeouts), and disabling
|
|
// IPv6 on the backup connection gives them a much better user experience
|
|
// with dual-stack hosts, though they still pay the 250ms delay for each new
|
|
// connection. This strategy is also known as "happy eyeballs".
|
|
disableIpv6ForBackup = true;
|
|
}
|
|
|
|
if (ent->mConnInfo->HasIPHintAddress()) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIDNSService> 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<nsIDNSRecord> 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");
|
|
|
|
mPrimaryTransport.mDnsFlags = dnsFlags;
|
|
mBackupTransport.mDnsFlags = dnsFlags;
|
|
if (disableIpv6ForBackup) {
|
|
mBackupTransport.mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
|
|
}
|
|
LOG(("DnsAndConnectSocket::SetupDnsFlags flags=%u flagsBackup=%u [this=%p]",
|
|
mPrimaryTransport.mDnsFlags, mBackupTransport.mDnsFlags, this));
|
|
NS_ASSERTION(
|
|
!(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
|
|
!(mBackupTransport.mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
|
|
"Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::SetupEvent(SetupEvents event) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
LOG(("DnsAndConnectSocket::SetupEvent state=%d event=%d this=%p", mState,
|
|
event, this));
|
|
nsresult rv = NS_OK;
|
|
switch (event) {
|
|
case SetupEvents::INIT_EVENT:
|
|
MOZ_ASSERT(mState == DnsAndSocketState::INIT);
|
|
rv = mPrimaryTransport.Init(this);
|
|
if (NS_FAILED(rv)) {
|
|
mState = DnsAndSocketState::DONE;
|
|
} else if (mPrimaryTransport.FirstResolving()) {
|
|
mState = DnsAndSocketState::RESOLVING;
|
|
} else if (!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) {
|
|
mState = DnsAndSocketState::CONNECTING;
|
|
SetupBackupTimer();
|
|
} else {
|
|
MOZ_ASSERT(false);
|
|
mState = DnsAndSocketState::DONE;
|
|
Abandon();
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
}
|
|
break;
|
|
case SetupEvents::RESOLVED_PRIMARY_EVENT:
|
|
// This event may be posted multiple times if a DNS lookup is
|
|
// retriggered, e.g with different parameter.
|
|
if (!mIsHttp3 && (mState == DnsAndSocketState::RESOLVING)) {
|
|
mState = DnsAndSocketState::CONNECTING;
|
|
SetupBackupTimer();
|
|
}
|
|
break;
|
|
case SetupEvents::PRIMARY_DONE_EVENT:
|
|
MOZ_ASSERT((mState == DnsAndSocketState::RESOLVING) ||
|
|
(mState == DnsAndSocketState::CONNECTING) ||
|
|
(mState == DnsAndSocketState::ONE_CONNECTED));
|
|
CancelBackupTimer();
|
|
mBackupTransport.CancelDnsResolution();
|
|
if (mBackupTransport.ConnectingOrRetry()) {
|
|
mState = DnsAndSocketState::ONE_CONNECTED;
|
|
} else {
|
|
mState = DnsAndSocketState::DONE;
|
|
}
|
|
break;
|
|
case SetupEvents::BACKUP_DONE_EVENT:
|
|
MOZ_ASSERT((mState == DnsAndSocketState::CONNECTING) ||
|
|
(mState == DnsAndSocketState::ONE_CONNECTED));
|
|
if (mPrimaryTransport.ConnectingOrRetry()) {
|
|
mState = DnsAndSocketState::ONE_CONNECTED;
|
|
} else {
|
|
mState = DnsAndSocketState::DONE;
|
|
}
|
|
break;
|
|
case SetupEvents::BACKUP_TIMER_FIRED_EVENT:
|
|
MOZ_ASSERT(mState == DnsAndSocketState::CONNECTING);
|
|
mBackupTransport.Init(this);
|
|
}
|
|
LOG(("DnsAndConnectSocket::SetupEvent state=%d", mState));
|
|
|
|
if (mState == DnsAndSocketState::DONE) {
|
|
CheckIsDone();
|
|
RefPtr<DnsAndConnectSocket> self(this);
|
|
RefPtr<ConnectionEntry> ent =
|
|
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
|
|
if (ent) {
|
|
ent->RemoveDnsAndConnectSocket(this, false);
|
|
}
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void DnsAndConnectSocket::SetupBackupTimer() {
|
|
uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
|
|
MOZ_ASSERT(!mSynTimer, "timer already initd");
|
|
|
|
// When using Fast Open the correct transport will be setup for sure (it is
|
|
// guaranteed), but it can be that it will happened a bit later.
|
|
if (timeout && (!mSpeculative || mConnInfo->GetFallbackConnection()) &&
|
|
!mIsHttp3) {
|
|
// Setup the timer that will establish a backup socket
|
|
// if we do not get a writable event on the main one.
|
|
// We do this because a lost SYN takes a very long time
|
|
// to repair at the TCP level.
|
|
//
|
|
// Failure to setup the timer is something we can live with,
|
|
// so don't return an error in that case.
|
|
NS_NewTimerWithCallback(getter_AddRefs(mSynTimer), this, timeout,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p]", this));
|
|
} else if (timeout) {
|
|
LOG(("DnsAndConnectSocket::SetupBackupTimer() [this=%p], did not arm\n",
|
|
this));
|
|
}
|
|
}
|
|
|
|
void DnsAndConnectSocket::CancelBackupTimer() {
|
|
// If the syntimer is still armed, we can cancel it because no backup
|
|
// socket should be formed at this point
|
|
if (!mSynTimer) {
|
|
return;
|
|
}
|
|
|
|
LOG(("DnsAndConnectSocket::CancelBackupTimer()"));
|
|
mSynTimer->Cancel();
|
|
|
|
// Keeping the reference to the timer to remember we have already
|
|
// performed the backup connection.
|
|
}
|
|
|
|
void DnsAndConnectSocket::Abandon() {
|
|
LOG(("DnsAndConnectSocket::Abandon [this=%p ent=%s] %p %p %p %p", this,
|
|
mConnInfo->Origin(), mPrimaryTransport.mSocketTransport.get(),
|
|
mBackupTransport.mSocketTransport.get(),
|
|
mPrimaryTransport.mStreamOut.get(), mBackupTransport.mStreamOut.get()));
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
// Tell socket (and backup socket) to forget the half open socket.
|
|
mPrimaryTransport.Abandon();
|
|
mBackupTransport.Abandon();
|
|
|
|
// Stop the timer - we don't want any new backups.
|
|
CancelBackupTimer();
|
|
|
|
mState = DnsAndSocketState::DONE;
|
|
}
|
|
|
|
double DnsAndConnectSocket::Duration(TimeStamp epoch) {
|
|
if (mPrimaryTransport.mSynStarted.IsNull()) {
|
|
return 0;
|
|
}
|
|
|
|
return (epoch - mPrimaryTransport.mSynStarted).ToMilliseconds();
|
|
}
|
|
|
|
NS_IMETHODIMP // method for nsITimerCallback
|
|
DnsAndConnectSocket::Notify(nsITimer* timer) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
MOZ_ASSERT(timer == mSynTimer, "wrong timer");
|
|
|
|
MOZ_ASSERT(!mBackupTransport.mDNSRequest);
|
|
MOZ_ASSERT(!mBackupTransport.mSocketTransport);
|
|
MOZ_ASSERT(mSynTimer);
|
|
|
|
DebugOnly<nsresult> rv = SetupEvent(BACKUP_TIMER_FIRED_EVENT);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Keeping the reference to the timer to remember we have already
|
|
// performed the backup connection.
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP // method for nsINamed
|
|
DnsAndConnectSocket::GetName(nsACString& aName) {
|
|
aName.AssignLiteral("DnsAndConnectSocket");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DnsAndConnectSocket::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
|
|
nsresult status) {
|
|
LOG((
|
|
"DnsAndConnectSocket::OnLookupComplete: this=%p mState=%d status %" PRIx32
|
|
".",
|
|
this, mState, static_cast<uint32_t>(status)));
|
|
|
|
if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface((rec))) {
|
|
nsIRequest::TRRMode effectivemode = nsIRequest::TRR_DEFAULT_MODE;
|
|
addrRecord->GetEffectiveTRRMode(&effectivemode);
|
|
nsITRRSkipReason::value skipReason = nsITRRSkipReason::TRR_UNSET;
|
|
addrRecord->GetTrrSkipReason(&skipReason);
|
|
if (mTransaction) {
|
|
mTransaction->SetTRRInfo(effectivemode, skipReason);
|
|
}
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(request);
|
|
RefPtr<DnsAndConnectSocket> deleteProtector(this);
|
|
|
|
if (!request || (!IsPrimary(request) && !IsBackup(request))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (IsPrimary(request) && NS_SUCCEEDED(status)) {
|
|
mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RESOLVED_HOST, 0);
|
|
}
|
|
|
|
// When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
|
|
// proxy host is not found, so we fixup the error code.
|
|
// For SOCKS proxies (mProxyTransparent == true), the socket
|
|
// transport resolves the real host here, so there's no fixup
|
|
// (see bug 226943).
|
|
if (mProxyNotTransparent && (status == NS_ERROR_UNKNOWN_HOST)) {
|
|
status = NS_ERROR_UNKNOWN_PROXY_HOST;
|
|
}
|
|
|
|
nsresult rv;
|
|
// remember if it was primary because TransportSetups will delete the ref to
|
|
// the DNS request and check cannot be performed later.
|
|
bool isPrimary = IsPrimary(request);
|
|
if (isPrimary) {
|
|
rv = mPrimaryTransport.OnLookupComplete(this, rec, status);
|
|
if ((!mIsHttp3 && mPrimaryTransport.ConnectingOrRetry()) ||
|
|
(mIsHttp3 && mPrimaryTransport.Resolved())) {
|
|
SetupEvent(SetupEvents::RESOLVED_PRIMARY_EVENT);
|
|
}
|
|
} else {
|
|
rv = mBackupTransport.OnLookupComplete(this, rec, status);
|
|
}
|
|
|
|
if (NS_FAILED(rv) || mIsHttp3) {
|
|
// If we are retrying DNS, we should not setup the connection.
|
|
if (mIsHttp3 && mPrimaryTransport.mState ==
|
|
TransportSetup::TransportSetupState::RETRY_RESOLVING) {
|
|
LOG(("Retry DNS for Http3"));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Before calling SetupConn we need to hold reference to this, i.e. a
|
|
// delete protector, because the corresponding ConnectionEntry may be
|
|
// abandoned and that will abandon this DnsAndConnectSocket.
|
|
SetupConn(isPrimary, rv);
|
|
// During a connection dispatch that will happen in SetupConn,
|
|
// a ConnectionEntry may be abandon and that will abandon this
|
|
// DnsAndConnectSocket. In that case mState will already be
|
|
// DnsAndSocketState::DONE and we do not need to set it again.
|
|
if (mState != DnsAndSocketState::DONE) {
|
|
if (isPrimary) {
|
|
SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
|
|
} else {
|
|
SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// method for nsIAsyncOutputStreamCallback
|
|
NS_IMETHODIMP
|
|
DnsAndConnectSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
MOZ_DIAGNOSTIC_ASSERT(mPrimaryTransport.mSocketTransport ||
|
|
mBackupTransport.mSocketTransport);
|
|
MOZ_DIAGNOSTIC_ASSERT(IsPrimary(out) || IsBackup(out), "stream mismatch");
|
|
|
|
RefPtr<ConnectionEntry> ent =
|
|
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
|
|
MOZ_DIAGNOSTIC_ASSERT(ent);
|
|
Unused << ent;
|
|
|
|
RefPtr<DnsAndConnectSocket> deleteProtector(this);
|
|
|
|
LOG(("DnsAndConnectSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this,
|
|
mConnInfo->Origin(), IsPrimary(out) ? "primary" : "backup"));
|
|
|
|
// Remember if it was primary or backup reuest.
|
|
bool isPrimary = IsPrimary(out);
|
|
nsresult rv = NS_OK;
|
|
if (isPrimary) {
|
|
rv = mPrimaryTransport.CheckConnectedResult(this);
|
|
if (!mPrimaryTransport.DoneConnecting()) {
|
|
return NS_OK;
|
|
}
|
|
MOZ_ASSERT((NS_SUCCEEDED(rv) &&
|
|
(mPrimaryTransport.mState ==
|
|
TransportSetup::TransportSetupState::CONNECTING_DONE) &&
|
|
mPrimaryTransport.mSocketTransport) ||
|
|
(NS_FAILED(rv) &&
|
|
(mPrimaryTransport.mState ==
|
|
TransportSetup::TransportSetupState::DONE) &&
|
|
!mPrimaryTransport.mSocketTransport));
|
|
} else if (IsBackup(out)) {
|
|
rv = mBackupTransport.CheckConnectedResult(this);
|
|
if (!mBackupTransport.DoneConnecting()) {
|
|
return NS_OK;
|
|
}
|
|
MOZ_ASSERT((NS_SUCCEEDED(rv) &&
|
|
(mBackupTransport.mState ==
|
|
TransportSetup::TransportSetupState::CONNECTING_DONE) &&
|
|
mBackupTransport.mSocketTransport) ||
|
|
(NS_FAILED(rv) &&
|
|
(mBackupTransport.mState ==
|
|
TransportSetup::TransportSetupState::DONE) &&
|
|
!mBackupTransport.mSocketTransport));
|
|
} else {
|
|
MOZ_ASSERT(false, "unexpected stream");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult socketStatus = out->StreamStatus();
|
|
if (StaticPrefs::network_http_retry_with_another_half_open() &&
|
|
NS_FAILED(socketStatus) && socketStatus != NS_BASE_STREAM_WOULD_BLOCK) {
|
|
if (isPrimary) {
|
|
if (mBackupTransport.mState ==
|
|
TransportSetup::TransportSetupState::CONNECTING) {
|
|
mPrimaryTransport.Abandon();
|
|
return NS_OK;
|
|
}
|
|
} else if (IsBackup(out)) {
|
|
if (mPrimaryTransport.mState ==
|
|
TransportSetup::TransportSetupState::CONNECTING) {
|
|
mBackupTransport.Abandon();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Before calling SetupConn we need to hold a reference to this, i.e. a
|
|
// delete protector, because the corresponding ConnectionEntry may be
|
|
// abandoned and that will abandon this DnsAndConnectSocket.
|
|
rv = SetupConn(isPrimary, rv);
|
|
if (mState != DnsAndSocketState::DONE) {
|
|
// During a connection dispatch that will happen in SetupConn,
|
|
// a ConnectionEntry may be abandon and that will abandon this
|
|
// DnsAndConnectSocket. In that case mState will already be
|
|
// DnsAndSocketState::DONE and we do not need to set it again.
|
|
if (isPrimary) {
|
|
SetupEvent(SetupEvents::PRIMARY_DONE_EVENT);
|
|
} else {
|
|
SetupEvent(SetupEvents::BACKUP_DONE_EVENT);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::SetupConn(bool isPrimary, nsresult status) {
|
|
// assign the new socket to the http connection
|
|
|
|
RefPtr<ConnectionEntry> ent =
|
|
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
|
|
MOZ_DIAGNOSTIC_ASSERT(ent);
|
|
if (!ent) {
|
|
Abandon();
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<HttpConnectionBase> conn;
|
|
|
|
nsresult rv = NS_OK;
|
|
if (isPrimary) {
|
|
rv = mPrimaryTransport.SetupConn(mTransaction, ent, status, mCaps,
|
|
getter_AddRefs(conn));
|
|
} else {
|
|
rv = mBackupTransport.SetupConn(mTransaction, ent, status, mCaps,
|
|
getter_AddRefs(conn));
|
|
}
|
|
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
|
|
|
|
if (NS_FAILED(rv)) {
|
|
LOG(
|
|
("DnsAndConnectSocket::SetupConn "
|
|
"conn->init (%p) failed %" PRIx32 "\n",
|
|
conn.get(), static_cast<uint32_t>(rv)));
|
|
|
|
if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) {
|
|
if (mIsHttp3 && !mConnInfo->GetWebTransport()) {
|
|
trans->DisableHttp3(true);
|
|
gHttpHandler->ExcludeHttp3(mConnInfo);
|
|
}
|
|
// The transaction's connection info is changed after DisableHttp3(), so
|
|
// this is the only point we can remove this transaction from its conn
|
|
// entry.
|
|
ent->RemoveTransFromPendingQ(trans);
|
|
}
|
|
mTransaction->Close(rv);
|
|
|
|
return rv;
|
|
}
|
|
|
|
// This half-open socket has created a connection. This flag excludes it
|
|
// from counter of actual connections used for checking limits.
|
|
mHasConnected = true;
|
|
|
|
// if this is still in the pending list, remove it and dispatch it
|
|
RefPtr<PendingTransactionInfo> pendingTransInfo =
|
|
gHttpHandler->ConnMgr()->FindTransactionHelper(true, ent, mTransaction);
|
|
if (pendingTransInfo) {
|
|
MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
|
|
|
|
ent->InsertIntoActiveConns(conn);
|
|
if (mIsHttp3) {
|
|
// Each connection must have a ConnectionHandle wrapper.
|
|
// In case of Http < 2 the a ConnectionHandle is created for each
|
|
// transaction in DispatchAbstractTransaction.
|
|
// In case of Http2/ and Http3, ConnectionHandle is created only once.
|
|
// Http2 connection always starts as http1 connection and the first
|
|
// transaction use DispatchAbstractTransaction to be dispatched and
|
|
// a ConnectionHandle is created. All consecutive transactions for
|
|
// Http2 use a short-cut in DispatchTransaction and call
|
|
// HttpConnectionBase::Activate (DispatchAbstractTransaction is never
|
|
// called).
|
|
// In case of Http3 the short-cut HttpConnectionBase::Activate is always
|
|
// used also for the first transaction, therefore we need to create
|
|
// ConnectionHandle here.
|
|
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
|
|
pendingTransInfo->Transaction()->SetConnection(handle);
|
|
}
|
|
rv = gHttpHandler->ConnMgr()->DispatchTransaction(
|
|
ent, pendingTransInfo->Transaction(), conn);
|
|
} else {
|
|
// this transaction was dispatched off the pending q before all the
|
|
// sockets established themselves.
|
|
|
|
// 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.
|
|
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
|
|
if (connTCP) {
|
|
connTCP->SetIsReusedAfter(950);
|
|
}
|
|
|
|
// if we are using ssl and no other transactions are waiting right now,
|
|
// then form a null transaction to drive the SSL handshake to
|
|
// completion. Afterwards the connection will be 100% ready for the next
|
|
// transaction to use it. Make an exception for SSL tunneled HTTP proxy as
|
|
// the NullHttpTransaction does not know how to drive Connect
|
|
// Http3 cannot be dispatched using OnMsgReclaimConnection (see below),
|
|
// therefore we need to use a Nulltransaction.
|
|
if (!connTCP ||
|
|
(ent->mConnInfo->FirstHopSSL() && !ent->UrgentStartQueueLength() &&
|
|
!ent->PendingQueueLength() && !ent->mConnInfo->UsingConnect())) {
|
|
LOG(
|
|
("DnsAndConnectSocket::SetupConn null transaction will "
|
|
"be used to finish SSL handshake on conn %p\n",
|
|
conn.get()));
|
|
RefPtr<nsAHttpTransaction> trans;
|
|
if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
|
|
// null transactions cannot be put in the entry queue, so that
|
|
// explains why it is not present.
|
|
mDispatchedMTransaction = true;
|
|
trans = mTransaction;
|
|
} else {
|
|
trans = new NullHttpTransaction(mConnInfo, callbacks, mCaps);
|
|
}
|
|
|
|
ent->InsertIntoActiveConns(conn);
|
|
rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(ent, trans,
|
|
mCaps, conn, 0);
|
|
} else {
|
|
// otherwise just put this in the persistent connection pool
|
|
LOG(
|
|
("DnsAndConnectSocket::SetupConn no transaction match "
|
|
"returning conn %p to pool\n",
|
|
conn.get()));
|
|
gHttpHandler->ConnMgr()->OnMsgReclaimConnection(conn);
|
|
|
|
// We expect that there is at least one tranasction in the pending
|
|
// queue that can take this connection, but it can happened that
|
|
// all transactions are blocked or they have took other idle
|
|
// connections. In that case the connection has been added to the
|
|
// idle queue.
|
|
// If the connection is in the idle queue but it is using ssl, make
|
|
// a nulltransaction for it to finish ssl handshake!
|
|
if (ent->mConnInfo->FirstHopSSL() && !ent->mConnInfo->UsingConnect()) {
|
|
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
|
|
// If RemoveIdleConnection succeeds that means that conn is in the
|
|
// idle queue.
|
|
if (connTCP && NS_SUCCEEDED(ent->RemoveIdleConnection(connTCP))) {
|
|
RefPtr<nsAHttpTransaction> trans;
|
|
if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
|
|
mDispatchedMTransaction = true;
|
|
trans = mTransaction;
|
|
} else {
|
|
trans = new NullHttpTransaction(ent->mConnInfo, callbacks, mCaps);
|
|
}
|
|
ent->InsertIntoActiveConns(conn);
|
|
rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
|
|
ent, trans, mCaps, conn, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this halfOpenConn was speculative, but at the end the conn got a
|
|
// non-null transaction than this halfOpen is not speculative anymore!
|
|
if (conn->Transaction() && !conn->Transaction()->IsNullTransaction()) {
|
|
Claim();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// method for nsITransportEventSink
|
|
NS_IMETHODIMP
|
|
DnsAndConnectSocket::OnTransportStatus(nsITransport* trans, nsresult status,
|
|
int64_t progress, int64_t progressMax) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
MOZ_ASSERT(IsPrimary(trans) || IsBackup(trans));
|
|
if (mTransaction) {
|
|
if (IsPrimary(trans) ||
|
|
(IsBackup(trans) && (status == NS_NET_STATUS_CONNECTED_TO) &&
|
|
mPrimaryTransport.mSocketTransport)) {
|
|
// Send this status event only if the transaction is still pending,
|
|
// i.e. it has not found a free already connected socket.
|
|
// Sockets in halfOpen state can only get following events:
|
|
// NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
|
|
// mBackupTransport is only started after
|
|
// NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore
|
|
// NS_NET_STATUS_CONNECTING_TO event for mBackupTransport and
|
|
// send NS_NET_STATUS_CONNECTED_TO.
|
|
// mBackupTransport must be connected before mSocketTransport(e.g.
|
|
// mPrimaryTransport.mSocketTransport != nullpttr).
|
|
mTransaction->OnTransportStatus(trans, status, progress);
|
|
}
|
|
}
|
|
|
|
if (status == NS_NET_STATUS_CONNECTED_TO) {
|
|
if (IsPrimary(trans)) {
|
|
mPrimaryTransport.mConnectedOK = true;
|
|
} else {
|
|
mBackupTransport.mConnectedOK = true;
|
|
}
|
|
}
|
|
|
|
// The rest of this method only applies to the primary transport
|
|
if (!IsPrimary(trans)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// if we are doing spdy coalescing and haven't recorded the ip address
|
|
// for this entry before then make the hash key if our dns lookup
|
|
// just completed. We can't do coalescing if using a proxy because the
|
|
// ip addresses are not available to the client.
|
|
|
|
nsCOMPtr<nsIDNSAddrRecord> dnsRecord(
|
|
do_GetInterface(mPrimaryTransport.mSocketTransport));
|
|
if (status == NS_NET_STATUS_CONNECTING_TO &&
|
|
StaticPrefs::network_http_http2_enabled() &&
|
|
StaticPrefs::network_http_http2_coalesce_hostnames()) {
|
|
RefPtr<ConnectionEntry> ent =
|
|
gHttpHandler->ConnMgr()->FindConnectionEntry(mConnInfo);
|
|
MOZ_DIAGNOSTIC_ASSERT(ent);
|
|
if (ent) {
|
|
if (ent->MaybeProcessCoalescingKeys(dnsRecord)) {
|
|
gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::IsPrimary(nsITransport* trans) {
|
|
return trans == mPrimaryTransport.mSocketTransport;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::IsPrimary(nsIAsyncOutputStream* out) {
|
|
return out == mPrimaryTransport.mStreamOut;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::IsPrimary(nsICancelable* dnsRequest) {
|
|
return dnsRequest == mPrimaryTransport.mDNSRequest;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::IsBackup(nsITransport* trans) {
|
|
return trans == mBackupTransport.mSocketTransport;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::IsBackup(nsIAsyncOutputStream* out) {
|
|
return out == mBackupTransport.mStreamOut;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::IsBackup(nsICancelable* dnsRequest) {
|
|
return dnsRequest == mBackupTransport.mDNSRequest;
|
|
}
|
|
|
|
// method for nsIInterfaceRequestor
|
|
NS_IMETHODIMP
|
|
DnsAndConnectSocket::GetInterface(const nsIID& iid, void** result) {
|
|
if (mTransaction) {
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
|
|
if (callbacks) {
|
|
return callbacks->GetInterface(iid, result);
|
|
}
|
|
}
|
|
return NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::AcceptsTransaction(nsHttpTransaction* trans) {
|
|
// When marked as urgent start, only accept urgent start marked transactions.
|
|
// Otherwise, accept any kind of transaction.
|
|
return !mUrgentStart || (trans->Caps() & nsIClassOfService::UrgentStart);
|
|
}
|
|
|
|
bool DnsAndConnectSocket::Claim() {
|
|
if (mSpeculative) {
|
|
mSpeculative = false;
|
|
mAllow1918 = true;
|
|
auto resetFlag = [](TransportSetup& transport) {
|
|
uint32_t flags;
|
|
if (transport.mSocketTransport &&
|
|
NS_SUCCEEDED(
|
|
transport.mSocketTransport->GetConnectionFlags(&flags))) {
|
|
flags &= ~nsISocketTransport::DISABLE_RFC1918;
|
|
flags &= ~nsISocketTransport::IS_SPECULATIVE_CONNECTION;
|
|
transport.mSocketTransport->SetConnectionFlags(flags);
|
|
}
|
|
};
|
|
resetFlag(mPrimaryTransport);
|
|
resetFlag(mBackupTransport);
|
|
|
|
// Http3 has its own syn-retransmission, therefore it does not need a
|
|
// backup connection.
|
|
if (mPrimaryTransport.ConnectingOrRetry() &&
|
|
!mBackupTransport.mSocketTransport && !mSynTimer && !mIsHttp3) {
|
|
SetupBackupTimer();
|
|
}
|
|
}
|
|
|
|
if (mFreeToUse) {
|
|
mFreeToUse = false;
|
|
|
|
if (mPrimaryTransport.mSocketTransport) {
|
|
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
|
|
if (NS_SUCCEEDED(mPrimaryTransport.mSocketTransport->GetTlsSocketControl(
|
|
getter_AddRefs(tlsSocketControl))) &&
|
|
tlsSocketControl) {
|
|
Unused << tlsSocketControl->Claim();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DnsAndConnectSocket::Unclaim() {
|
|
MOZ_ASSERT(!mSpeculative && !mFreeToUse);
|
|
// We will keep the backup-timer running. Most probably this halfOpen will
|
|
// be used by a transaction from which this transaction took the halfOpen.
|
|
// (this is happening because of the transaction priority.)
|
|
mFreeToUse = true;
|
|
}
|
|
|
|
void DnsAndConnectSocket::CloseTransports(nsresult error) {
|
|
if (mPrimaryTransport.mSocketTransport) {
|
|
mPrimaryTransport.mSocketTransport->Close(error);
|
|
}
|
|
if (mBackupTransport.mSocketTransport) {
|
|
mBackupTransport.mSocketTransport->Close(error);
|
|
}
|
|
}
|
|
|
|
DnsAndConnectSocket::TransportSetup::TransportSetup(bool isBackup)
|
|
: mState(TransportSetup::TransportSetupState::INIT), mIsBackup(isBackup) {}
|
|
|
|
nsresult DnsAndConnectSocket::TransportSetup::Init(
|
|
DnsAndConnectSocket* dnsAndSock) {
|
|
nsresult rv;
|
|
mSynStarted = TimeStamp::Now();
|
|
if (mSkipDnsResolution) {
|
|
mState = TransportSetup::TransportSetupState::CONNECTING;
|
|
rv = SetupStreams(dnsAndSock);
|
|
} else {
|
|
mState = TransportSetup::TransportSetupState::RESOLVING;
|
|
rv = ResolveHost(dnsAndSock);
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
CloseAll();
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void DnsAndConnectSocket::TransportSetup::CancelDnsResolution() {
|
|
if (mDNSRequest) {
|
|
mDNSRequest->Cancel(NS_ERROR_ABORT);
|
|
mDNSRequest = nullptr;
|
|
}
|
|
if (mState == TransportSetup::TransportSetupState::RESOLVING) {
|
|
mState = TransportSetup::TransportSetupState::INIT;
|
|
}
|
|
}
|
|
|
|
void DnsAndConnectSocket::TransportSetup::Abandon() {
|
|
CloseAll();
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
}
|
|
|
|
void DnsAndConnectSocket::TransportSetup::SetConnecting() {
|
|
MOZ_ASSERT(!mWaitingForConnect);
|
|
mWaitingForConnect = true;
|
|
gHttpHandler->ConnMgr()->StartedConnect();
|
|
}
|
|
|
|
void DnsAndConnectSocket::TransportSetup::MaybeSetConnectingDone() {
|
|
if (mWaitingForConnect) {
|
|
mWaitingForConnect = false;
|
|
gHttpHandler->ConnMgr()->RecvdConnect();
|
|
}
|
|
}
|
|
|
|
void DnsAndConnectSocket::TransportSetup::CloseAll() {
|
|
MaybeSetConnectingDone();
|
|
|
|
// Tell socket (and backup socket) to forget the half open socket.
|
|
if (mSocketTransport) {
|
|
mSocketTransport->SetEventSink(nullptr, nullptr);
|
|
mSocketTransport->SetSecurityCallbacks(nullptr);
|
|
mSocketTransport = nullptr;
|
|
}
|
|
|
|
// Tell output stream (and backup) to forget the half open socket.
|
|
if (mStreamOut) {
|
|
mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
|
|
mStreamOut = nullptr;
|
|
}
|
|
|
|
// Lose references to input stream (and backup).
|
|
if (mStreamIn) {
|
|
mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
|
|
mStreamIn = nullptr;
|
|
}
|
|
|
|
if (mDNSRequest) {
|
|
mDNSRequest->Cancel(NS_ERROR_ABORT);
|
|
mDNSRequest = nullptr;
|
|
}
|
|
|
|
mConnectedOK = false;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::TransportSetup::ToggleIpFamilyFlagsIfRetryEnabled() {
|
|
if (!mRetryWithDifferentIPFamily) {
|
|
return false;
|
|
}
|
|
|
|
LOG(
|
|
("DnsAndConnectSocket::TransportSetup::ToggleIpFamilyFlagsIfRetryEnabled"
|
|
"[this=%p dnsFlags=%u]",
|
|
this, mDnsFlags));
|
|
mRetryWithDifferentIPFamily = false;
|
|
|
|
// Toggle the RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4 flags in mDnsFlags
|
|
// This ensures we switch the IP family for the DNS resolution
|
|
mDnsFlags ^= (nsIDNSService::RESOLVE_DISABLE_IPV6 |
|
|
nsIDNSService::RESOLVE_DISABLE_IPV4);
|
|
|
|
if ((mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) &&
|
|
(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4)) {
|
|
// Clear both flags to prevent an invalid state
|
|
mDnsFlags &= ~(nsIDNSService::RESOLVE_DISABLE_IPV6 |
|
|
nsIDNSService::RESOLVE_DISABLE_IPV4);
|
|
LOG(
|
|
("DnsAndConnectSocket::TransportSetup::"
|
|
"ToggleIpFamilyFlagsIfRetryEnabled "
|
|
"[this=%p] both v6 and v4 are disabled",
|
|
this));
|
|
MOZ_DIAGNOSTIC_CRASH("both v6 and v4 addresses are disabled");
|
|
}
|
|
|
|
// Indicate that the IP family preference should be reset
|
|
mResetFamilyPreference = true;
|
|
return true;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::TransportSetup::CheckConnectedResult(
|
|
DnsAndConnectSocket* dnsAndSock) {
|
|
mState = TransportSetup::TransportSetupState::CONNECTING_DONE;
|
|
MaybeSetConnectingDone();
|
|
|
|
if (mSkipDnsResolution) {
|
|
return NS_OK;
|
|
}
|
|
bool retryDns = false;
|
|
mSocketTransport->GetRetryDnsIfPossible(&retryDns);
|
|
if (!retryDns) {
|
|
return NS_OK;
|
|
}
|
|
|
|
bool retry = false;
|
|
if (ToggleIpFamilyFlagsIfRetryEnabled()) {
|
|
retry = true;
|
|
} else if (!(mDnsFlags & nsIDNSService::RESOLVE_DISABLE_TRR)) {
|
|
bool trrEnabled;
|
|
mDNSRecord->IsTRR(&trrEnabled);
|
|
if (trrEnabled) {
|
|
nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
|
|
mDNSRecord->GetEffectiveTRRMode(&trrMode);
|
|
// If current trr mode is trr only, we should not retry.
|
|
if (trrMode != nsIRequest::TRR_ONLY_MODE) {
|
|
// Drop state to closed. This will trigger a new round of
|
|
// DNS resolving. Bypass the cache this time since the
|
|
// cached data came from TRR and failed already!
|
|
LOG((" failed to connect with TRR enabled, try w/o\n"));
|
|
mDnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR |
|
|
nsIDNSService::RESOLVE_BYPASS_CACHE |
|
|
nsIDNSService::RESOLVE_REFRESH_CACHE;
|
|
retry = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (retry) {
|
|
LOG((" retry DNS, mDnsFlags=%u", mDnsFlags));
|
|
CloseAll();
|
|
mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
|
|
nsresult rv = ResolveHost(dnsAndSock);
|
|
if (NS_FAILED(rv)) {
|
|
CloseAll();
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::TransportSetup::SetupConn(
|
|
nsAHttpTransaction* transaction, ConnectionEntry* ent, nsresult status,
|
|
uint32_t cap, HttpConnectionBase** connection) {
|
|
RefPtr<HttpConnectionBase> conn;
|
|
if (!ent->mConnInfo->IsHttp3()) {
|
|
conn = new nsHttpConnection();
|
|
} else {
|
|
conn = new HttpConnectionUDP();
|
|
}
|
|
|
|
NotifyActivity(ent->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_CONNECTION_CREATED);
|
|
|
|
LOG(
|
|
("DnsAndConnectSocket::SocketTransport::SetupConn "
|
|
"Created new nshttpconnection %p %s\n",
|
|
conn.get(), ent->mConnInfo->IsHttp3() ? "using http3" : ""));
|
|
|
|
NullHttpTransaction* nullTrans = transaction->QueryNullTransaction();
|
|
if (nullTrans) {
|
|
conn->BootstrapTimings(nullTrans->Timings());
|
|
}
|
|
|
|
// Some capabilities are needed before a transaction actually gets
|
|
// scheduled (e.g. how to negotiate false start)
|
|
conn->SetTransactionCaps(transaction->Caps());
|
|
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
transaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
|
|
nsresult rv = NS_OK;
|
|
if (!ent->mConnInfo->IsHttp3()) {
|
|
RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
|
|
rv =
|
|
connTCP->Init(ent->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
|
|
mSocketTransport, mStreamIn, mStreamOut, mConnectedOK,
|
|
status, callbacks,
|
|
PR_MillisecondsToInterval(static_cast<uint32_t>(
|
|
(TimeStamp::Now() - mSynStarted).ToMilliseconds())),
|
|
cap & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE);
|
|
} else {
|
|
RefPtr<HttpConnectionUDP> connUDP = do_QueryObject(conn);
|
|
rv = connUDP->Init(ent->mConnInfo, mDNSRecord, status, callbacks, cap);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (nsHttpHandler::IsHttp3Enabled() &&
|
|
StaticPrefs::network_http_http2_coalesce_hostnames()) {
|
|
if (ent->MaybeProcessCoalescingKeys(mDNSRecord, true)) {
|
|
gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(ent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool resetPreference = false;
|
|
if (mResetFamilyPreference ||
|
|
(mSocketTransport &&
|
|
NS_SUCCEEDED(
|
|
mSocketTransport->GetResetIPFamilyPreference(&resetPreference)) &&
|
|
resetPreference)) {
|
|
ent->ResetIPFamilyPreference();
|
|
}
|
|
|
|
NetAddr peeraddr;
|
|
if (mSocketTransport &&
|
|
NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
|
|
ent->RecordIPFamilyPreference(peeraddr.raw.family);
|
|
}
|
|
|
|
conn.forget(connection);
|
|
mSocketTransport = nullptr;
|
|
mStreamOut = nullptr;
|
|
mStreamIn = nullptr;
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
return rv;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::TransportSetup::SetupStreams(
|
|
DnsAndConnectSocket* dnsAndSock) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
|
|
|
|
nsresult rv;
|
|
nsTArray<nsCString> socketTypes;
|
|
const nsHttpConnectionInfo* ci = dnsAndSock->mConnInfo;
|
|
if (dnsAndSock->mIsHttp3) {
|
|
socketTypes.AppendElement("quic"_ns);
|
|
} else {
|
|
if (ci->FirstHopSSL()) {
|
|
socketTypes.AppendElement("ssl"_ns);
|
|
} else {
|
|
const nsCString& defaultType = gHttpHandler->DefaultSocketType();
|
|
if (!defaultType.IsVoid()) {
|
|
socketTypes.AppendElement(defaultType);
|
|
}
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsISocketTransport> socketTransport;
|
|
nsCOMPtr<nsISocketTransportService> sts;
|
|
|
|
sts = components::SocketTransport::Service();
|
|
if (!sts) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
LOG(
|
|
("DnsAndConnectSocket::SetupStreams [this=%p ent=%s] "
|
|
"setup routed transport to origin %s:%d via %s:%d\n",
|
|
this, ci->HashKey().get(), ci->Origin(), ci->OriginPort(),
|
|
ci->RoutedHost(), ci->RoutedPort()));
|
|
|
|
nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
|
|
if (routedSTS) {
|
|
rv = routedSTS->CreateRoutedTransport(
|
|
socketTypes, ci->GetOrigin(), ci->OriginPort(), ci->GetRoutedHost(),
|
|
ci->RoutedPort(), ci->ProxyInfo(), mDNSRecord,
|
|
getter_AddRefs(socketTransport));
|
|
} else {
|
|
if (!ci->GetRoutedHost().IsEmpty()) {
|
|
// There is a route requested, but the legacy nsISocketTransportService
|
|
// can't handle it.
|
|
// Origin should be reachable on origin host name, so this should
|
|
// not be a problem - but log it.
|
|
LOG(
|
|
("DnsAndConnectSocket this=%p using legacy nsISocketTransportService "
|
|
"means explicit route %s:%d will be ignored.\n",
|
|
this, ci->RoutedHost(), ci->RoutedPort()));
|
|
}
|
|
|
|
rv = sts->CreateTransport(socketTypes, ci->GetOrigin(), ci->OriginPort(),
|
|
ci->ProxyInfo(), mDNSRecord,
|
|
getter_AddRefs(socketTransport));
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t tmpFlags = 0;
|
|
if (dnsAndSock->mCaps & NS_HTTP_REFRESH_DNS) {
|
|
tmpFlags = nsISocketTransport::BYPASS_CACHE;
|
|
}
|
|
|
|
tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
|
|
NS_HTTP_TRR_MODE_FROM_FLAGS(dnsAndSock->mCaps));
|
|
|
|
if (dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS) {
|
|
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
|
|
}
|
|
|
|
// When we are making a speculative connection we do not propagate all flags
|
|
// in mCaps, so we need to query nsHttpConnectionInfo directly as well.
|
|
if ((dnsAndSock->mCaps & NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) ||
|
|
ci->GetAnonymousAllowClientCert()) {
|
|
tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
|
|
}
|
|
|
|
if (ci->GetPrivate()) {
|
|
tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
|
|
}
|
|
|
|
Unused << socketTransport->SetIsPrivate(ci->GetPrivate());
|
|
|
|
if (dnsAndSock->mCaps & NS_HTTP_DISALLOW_ECH) {
|
|
tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
|
|
}
|
|
|
|
if (dnsAndSock->mCaps & NS_HTTP_IS_RETRY) {
|
|
tmpFlags |= nsISocketTransport::IS_RETRY;
|
|
}
|
|
|
|
if (((dnsAndSock->mCaps & NS_HTTP_BE_CONSERVATIVE) ||
|
|
ci->GetBeConservative()) &&
|
|
gHttpHandler->ConnMgr()->BeConservativeIfProxied(ci->ProxyInfo())) {
|
|
LOG(("Setting Socket to BE_CONSERVATIVE"));
|
|
tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
|
|
}
|
|
|
|
if (ci->HasIPHintAddress()) {
|
|
nsCOMPtr<nsIDNSService> dns;
|
|
dns = mozilla::components::DNS::Service(&rv);
|
|
NS_ENSURE_SUCCESS(rv, 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<nsIDNSRecord> record;
|
|
rv = dns->ResolveNative(mHost, nsIDNSService::RESOLVE_OFFLINE,
|
|
dnsAndSock->mConnInfo->GetOriginAttributes(),
|
|
getter_AddRefs(record));
|
|
if (NS_FAILED(rv) || !record) {
|
|
LOG(("Setting Socket to use IP hint address"));
|
|
tmpFlags |= nsISocketTransport::USE_IP_HINT_ADDRESS;
|
|
}
|
|
}
|
|
|
|
if (mRetryWithDifferentIPFamily) {
|
|
// From the same reason, let the backup socket fail faster to try the other
|
|
// family.
|
|
uint16_t fallbackTimeout =
|
|
mIsBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
|
|
if (fallbackTimeout) {
|
|
socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
|
|
fallbackTimeout);
|
|
}
|
|
}
|
|
|
|
if (!dnsAndSock->Allow1918()) {
|
|
tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
|
|
}
|
|
|
|
if (dnsAndSock->mSpeculative) {
|
|
tmpFlags |= nsISocketTransport::IS_SPECULATIVE_CONNECTION;
|
|
}
|
|
|
|
socketTransport->SetConnectionFlags(tmpFlags);
|
|
socketTransport->SetTlsFlags(ci->GetTlsFlags());
|
|
|
|
const OriginAttributes& originAttributes =
|
|
dnsAndSock->mConnInfo->GetOriginAttributes();
|
|
if (originAttributes != OriginAttributes()) {
|
|
socketTransport->SetOriginAttributes(originAttributes);
|
|
}
|
|
|
|
socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
|
|
|
|
rv = socketTransport->SetEventSink(dnsAndSock, nullptr);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = socketTransport->SetSecurityCallbacks(dnsAndSock);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (nsHttpHandler::EchConfigEnabled() && !ci->GetEchConfig().IsEmpty()) {
|
|
MOZ_ASSERT(!ci->IsHttp3());
|
|
LOG(("Setting ECH"));
|
|
rv = socketTransport->SetEchConfig(ci->GetEchConfig());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
NotifyActivity(dnsAndSock->mConnInfo, NS_HTTP_ACTIVITY_SUBTYPE_ECH_SET);
|
|
}
|
|
|
|
RefPtr<ConnectionEntry> ent =
|
|
gHttpHandler->ConnMgr()->FindConnectionEntry(ci);
|
|
MOZ_DIAGNOSTIC_ASSERT(ent);
|
|
if (ent) {
|
|
glean::http::connection_entry_cache_hit
|
|
.EnumGet(static_cast<glean::http::ConnectionEntryCacheHitLabel>(
|
|
ent->mUsedForConnection))
|
|
.Add();
|
|
ent->mUsedForConnection = true;
|
|
}
|
|
|
|
nsCOMPtr<nsIOutputStream> sout;
|
|
rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
|
|
getter_AddRefs(sout));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIInputStream> sin;
|
|
rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
|
|
getter_AddRefs(sin));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mSocketTransport = socketTransport.forget();
|
|
mStreamIn = do_QueryInterface(sin);
|
|
mStreamOut = do_QueryInterface(sout);
|
|
|
|
rv = mStreamOut->AsyncWait(dnsAndSock, 0, 0, nullptr);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
SetConnecting();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::TransportSetup::ResolveHost(
|
|
DnsAndConnectSocket* dnsAndSock) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mSocketTransport);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mStreamOut);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mDNSRequest);
|
|
LOG(("DnsAndConnectSocket::TransportSetup::ResolveHost [this=%p %s%s]", this,
|
|
PromiseFlatCString(mHost).get(),
|
|
(mDnsFlags & nsIDNSService::RESOLVE_BYPASS_CACHE) ? " bypass cache"
|
|
: ""));
|
|
nsCOMPtr<nsIDNSService> dns = GetOrInitDNSService();
|
|
if (!dns) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!mIsBackup) {
|
|
dnsAndSock->mTransaction->OnTransportStatus(
|
|
nullptr, NS_NET_STATUS_RESOLVING_HOST, 0);
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
do {
|
|
rv = dns->AsyncResolveNative(
|
|
mHost, nsIDNSService::RESOLVE_TYPE_DEFAULT,
|
|
mDnsFlags | nsIDNSService::RESOLVE_WANT_RECORD_ON_ERROR, nullptr,
|
|
dnsAndSock, gSocketTransportService,
|
|
dnsAndSock->mConnInfo->GetOriginAttributes(),
|
|
getter_AddRefs(mDNSRequest));
|
|
} while (NS_FAILED(rv) && ShouldRetryDNS());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
mDNSRequest = nullptr;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
bool DnsAndConnectSocket::TransportSetup::ShouldRetryDNS() {
|
|
if (mDnsFlags & nsIDNSService::RESOLVE_IP_HINT) {
|
|
mDnsFlags &= ~nsIDNSService::RESOLVE_IP_HINT;
|
|
return true;
|
|
}
|
|
|
|
if (ToggleIpFamilyFlagsIfRetryEnabled()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
nsresult DnsAndConnectSocket::TransportSetup::OnLookupComplete(
|
|
DnsAndConnectSocket* dnsAndSock, nsIDNSRecord* rec, nsresult status) {
|
|
mDNSRequest = nullptr;
|
|
if (NS_SUCCEEDED(status)) {
|
|
mDNSRecord = do_QueryInterface(rec);
|
|
MOZ_ASSERT(mDNSRecord);
|
|
|
|
if (dnsAndSock->mConnInfo->IsHttp3()) {
|
|
mState = TransportSetup::TransportSetupState::RESOLVED;
|
|
return status;
|
|
}
|
|
nsresult rv = SetupStreams(dnsAndSock);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mState = TransportSetup::TransportSetupState::CONNECTING;
|
|
} else {
|
|
CloseAll();
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// DNS lookup status failed
|
|
|
|
if (ShouldRetryDNS()) {
|
|
mState = TransportSetup::TransportSetupState::RETRY_RESOLVING;
|
|
nsresult rv = ResolveHost(dnsAndSock);
|
|
if (NS_FAILED(rv)) {
|
|
CloseAll();
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
mState = TransportSetup::TransportSetupState::DONE;
|
|
return status;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|