Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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
// HttpLog.h should generally be included first
#include "HttpLog.h"
// Log on level :5, instead of default :4.
#undef LOG
#define LOG(args) LOG5(args)
#undef LOG_ENABLED
#define LOG_ENABLED() LOG5_ENABLED()
#define TLS_EARLY_DATA_NOT_AVAILABLE 0
#define TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED 1
#define TLS_EARLY_DATA_AVAILABLE_AND_USED 2
#include "ASpdySession.h"
#include "ConnectionHandle.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/glean/NetwerkMetrics.h"
#include "HttpConnectionUDP.h"
#include "nsHttpHandler.h"
#include "Http3Session.h"
#include "nsComponentManagerUtils.h"
#include "nsIHttpChannelInternal.h"
#include "nsISocketProvider.h"
#include "nsNetAddr.h"
#include "nsINetAddr.h"
#include "nsStringStream.h"
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
// ConnectUDPTransaction
//-----------------------------------------------------------------------------
class ConnectUDPTransaction : public nsAHttpTransaction {
public:
NS_DECL_THREADSAFE_ISUPPORTS
explicit ConnectUDPTransaction(nsAHttpTransaction* aTrans,
nsIInputStream* aStream)
: mTransaction(aTrans), mProxyConnectStream(aStream) {
LOG(("ConnectUDPTransaction ctor: %p", this));
}
void SetConnection(nsAHttpConnection* aConn) override { mConnection = aConn; }
nsAHttpConnection* Connection() override { return mConnection; }
void GetSecurityCallbacks(nsIInterfaceRequestor** aCallbacks) override {
mTransaction->GetSecurityCallbacks(aCallbacks);
}
void OnTransportStatus(nsITransport* transport, nsresult status,
int64_t progress) override {
mTransaction->OnTransportStatus(transport, status, progress);
}
bool IsDone() override { return mIsDone; }
nsresult Status() override { return mTransaction->Status(); }
uint32_t Caps() override { return mTransaction->Caps(); }
static nsresult ReadRequestSegment(nsIInputStream* stream, void* closure,
const char* buf, uint32_t offset,
uint32_t count, uint32_t* countRead) {
ConnectUDPTransaction* trans = (ConnectUDPTransaction*)closure;
nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
return rv;
}
[[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader* reader,
uint32_t count,
uint32_t* countRead) override {
mReader = reader;
(void)mProxyConnectStream->ReadSegments(ReadRequestSegment, this, count,
countRead);
mReader = nullptr;
uint64_t avil = 0;
(void)mProxyConnectStream->Available(&avil);
if (!avil) {
mIsDone = true;
}
return NS_OK;
}
[[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter* writer,
uint32_t count,
uint32_t* countWritten) override {
// This is hacky. We should set Connection properly in
// nsHttpConnectionMgr::DispatchTransaction.
if (!mTransaction->Connection()) {
mTransaction->SetConnection(mConnection);
}
nsresult rv = mTransaction->WriteSegments(writer, count, countWritten);
mTransaction = nullptr;
return rv;
}
void Close(nsresult reason) override {
LOG(("ConnectUDPTransaction close mTransaction=%p", mTransaction.get()));
if (mTransaction) {
mTransaction->Close(reason);
}
}
nsHttpConnectionInfo* ConnectionInfo() override {
return mTransaction->ConnectionInfo();
}
void SetProxyConnectFailed() override {
mTransaction->SetProxyConnectFailed();
}
nsHttpRequestHead* RequestHead() override {
return mTransaction->RequestHead();
}
uint32_t Http1xTransactionCount() override { return 0; }
[[nodiscard]] nsresult TakeSubTransactions(
nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) override {
return NS_OK;
}
protected:
virtual ~ConnectUDPTransaction() {
LOG(("ConnectUDPTransaction dtor: %p", this));
}
RefPtr<nsAHttpTransaction> mTransaction;
nsCOMPtr<nsIInputStream> mProxyConnectStream;
RefPtr<nsAHttpConnection> mConnection;
bool mIsDone = false;
nsAHttpSegmentReader* mReader = nullptr;
};
NS_IMPL_ISUPPORTS(ConnectUDPTransaction, nsISupportsWeakReference)
//-----------------------------------------------------------------------------
// Http3ConnectTransaction
//-----------------------------------------------------------------------------
// We need a dummy transaction to keep the Http3StreamTunnel alive.
// Http3StreamTunnel instances are owned via the transaction-to-stream map
// in Http3Session::mStreamTransactionHash.
class Http3ConnectTransaction : public ConnectUDPTransaction {
public:
explicit Http3ConnectTransaction(uint32_t aCaps, nsHttpConnectionInfo* aInfo)
: ConnectUDPTransaction(nullptr, nullptr),
mCaps(aCaps),
mConnInfo(aInfo) {
LOG(("Http3ConnectTransaction ctor: %p", this));
}
uint32_t Caps() override { return mCaps; }
nsHttpConnectionInfo* ConnectionInfo() override { return mConnInfo; }
void OnTransportStatus(nsITransport* transport, nsresult status,
int64_t progress) override {}
[[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader* reader,
uint32_t count,
uint32_t* countRead) override {
MOZ_ASSERT_UNREACHABLE("Shouldn't be called");
return NS_ERROR_NOT_IMPLEMENTED;
}
[[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter* writer,
uint32_t count,
uint32_t* countWritten) override {
MOZ_ASSERT_UNREACHABLE("Shouldn't be called");
return NS_ERROR_NOT_IMPLEMENTED;
}
void Close(nsresult reason) override { mConnection = nullptr; }
private:
virtual ~Http3ConnectTransaction() {
LOG(("Http3ConnectTransaction dtor: %p", this));
}
uint32_t mCaps = 0;
RefPtr<nsHttpConnectionInfo> mConnInfo;
};
//-----------------------------------------------------------------------------
// HttpConnectionUDP <public>
//-----------------------------------------------------------------------------
HttpConnectionUDP::HttpConnectionUDP() : mHttpHandler(gHttpHandler) {
LOG(("Creating HttpConnectionUDP @%p\n", this));
}
HttpConnectionUDP::~HttpConnectionUDP() {
LOG(("Destroying HttpConnectionUDP @%p\n", this));
if (mForceSendTimer) {
mForceSendTimer->Cancel();
mForceSendTimer = nullptr;
}
MOZ_ASSERT(mQueuedHttpConnectTransaction.IsEmpty(),
"Should not have any queued transactions");
MOZ_ASSERT(mQueuedConnectUdpTransaction.IsEmpty(),
"Should not have any queued transactions");
}
nsresult HttpConnectionUDP::Init(nsHttpConnectionInfo* info,
nsIDNSRecord* dnsRecord, nsresult status,
nsIInterfaceRequestor* callbacks,
uint32_t caps) {
LOG1(("HttpConnectionUDP::Init this=%p", this));
NS_ENSURE_ARG_POINTER(info);
NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
mConnInfo = info;
MOZ_ASSERT(mConnInfo);
MOZ_ASSERT(mConnInfo->IsHttp3() || mConnInfo->IsHttp3ProxyConnection());
mErrorBeforeConnect = status;
mAlpnToken = mConnInfo->GetNPNToken();
if (NS_FAILED(mErrorBeforeConnect)) {
// See explanation for non-strictness of this operation in
// SetSecurityCallbacks.
mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
"HttpConnectionUDP::mCallbacks", callbacks, false);
SetCloseReason(ToCloseReason(mErrorBeforeConnect));
return mErrorBeforeConnect;
}
nsCOMPtr<nsIDNSAddrRecord> dnsAddrRecord = do_QueryInterface(dnsRecord);
if (!dnsAddrRecord) {
return NS_ERROR_FAILURE;
}
dnsAddrRecord->IsTRR(&mResolvedByTRR);
dnsAddrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
dnsAddrRecord->GetTrrSkipReason(&mTRRSkipReason);
NetAddr peerAddr;
uint16_t port =
mConnInfo->IsHttp3ProxyConnection()
? mConnInfo->ProxyPort()
: (!mConnInfo->GetRoutedHost().IsEmpty() ? mConnInfo->RoutedPort()
: mConnInfo->OriginPort());
nsresult rv = dnsAddrRecord->GetNextAddr(port, &peerAddr);
if (NS_FAILED(rv)) {
return rv;
}
// We are disabling 0.0.0.0 for non-test purposes.
if (peerAddr.IsIPAddrAny()) {
if (StaticPrefs::network_socket_ip_addr_any_disabled()) {
LOG(("Connection refused because of 0.0.0.0 IP address\n"));
return NS_ERROR_CONNECTION_REFUSED;
}
}
mSocket = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
return InitCommon(mSocket, peerAddr, callbacks, caps, false);
}
nsresult HttpConnectionUDP::InitWithSocket(nsHttpConnectionInfo* info,
nsIUDPSocket* aSocket,
NetAddr aPeerAddr,
nsIInterfaceRequestor* callbacks,
uint32_t caps) {
LOG1(("HttpConnectionUDP::InitWithSocket this=%p", this));
NS_ENSURE_ARG_POINTER(info);
NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
mConnInfo = info;
MOZ_ASSERT(mConnInfo->IsHttp3() || mConnInfo->IsHttp3ProxyConnection());
mErrorBeforeConnect = NS_OK;
mAlpnToken = mConnInfo->GetNPNToken();
return InitCommon(aSocket, aPeerAddr, callbacks, caps, true);
}
nsresult HttpConnectionUDP::InitCommon(nsIUDPSocket* aSocket,
const NetAddr& aPeerAddr,
nsIInterfaceRequestor* callbacks,
uint32_t caps, bool isInTunnel) {
mSocket = aSocket;
NetAddr local;
local.raw.family = aPeerAddr.raw.family;
nsresult rv = mSocket->InitWithAddress(&local, nullptr, false, 1);
if (NS_FAILED(rv)) {
mSocket = nullptr;
return rv;
}
rv = mSocket->SetRecvBufferSize(
StaticPrefs::network_http_http3_recvBufferSize());
if (NS_FAILED(rv)) {
LOG(("HttpConnectionUDP::InitCommon SetRecvBufferSize failed %d [this=%p]",
static_cast<uint32_t>(rv), this));
mSocket->Close();
mSocket = nullptr;
return rv;
}
if (aPeerAddr.raw.family == AF_INET) {
rv = mSocket->SetDontFragment(true);
if (NS_FAILED(rv)) {
LOG(("HttpConnectionUDP::InitCommon SetDontFragment failed %d [this=%p]",
static_cast<uint32_t>(rv), this));
}
}
// get the resulting socket address.
rv = mSocket->GetLocalAddr(getter_AddRefs(mSelfAddr));
if (NS_FAILED(rv)) {
mSocket->Close();
mSocket = nullptr;
return rv;
}
uint32_t providerFlags = 0;
if (caps & NS_HTTP_LOAD_ANONYMOUS) {
providerFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
}
if (mConnInfo->GetPrivate()) {
providerFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
}
if (((caps & NS_HTTP_BE_CONSERVATIVE) || mConnInfo->GetBeConservative()) &&
gHttpHandler->ConnMgr()->BeConservativeIfProxied(
mConnInfo->ProxyInfo())) {
providerFlags |= nsISocketProvider::BE_CONSERVATIVE;
}
if ((caps & NS_HTTP_IS_RETRY) ||
(mConnInfo->GetTlsFlags() &
nsIHttpChannelInternal::TLS_FLAG_CONFIGURE_AS_RETRY)) {
providerFlags |= nsISocketProvider::IS_RETRY;
}
if (mResolvedByTRR) {
providerFlags |= nsISocketProvider::USED_PRIVATE_DNS;
}
mPeerAddr = new nsNetAddr(&aPeerAddr);
mHttp3Session = new Http3Session();
rv = mHttp3Session->Init(mConnInfo, mSelfAddr, mPeerAddr, this, providerFlags,
callbacks, mSocket, isInTunnel);
if (NS_FAILED(rv)) {
LOG(
("HttpConnectionUDP::InitCommon Http3Session::Init failed [this=%p "
"rv=%x]",
this, static_cast<uint32_t>(rv)));
mSocket->Close();
mSocket = nullptr;
mHttp3Session = nullptr;
return rv;
}
mIsInTunnel = isInTunnel;
if (mIsInTunnel) {
mHttp3Session->SetIsInTunnel();
}
ChangeConnectionState(ConnectionState::INITED);
// See explanation for non-strictness of this operation in
// SetSecurityCallbacks.
mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
"HttpConnectionUDP::mCallbacks", callbacks, false);
// Call SyncListen at the end of this function. This call will actually
// attach the sockte to SocketTransportService.
rv = mSocket->SyncListen(this);
if (NS_FAILED(rv)) {
mSocket->Close();
mSocket = nullptr;
return rv;
}
return NS_OK;
}
// called on the socket thread
nsresult HttpConnectionUDP::Activate(nsAHttpTransaction* trans, uint32_t caps,
int32_t pri) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG1(("HttpConnectionUDP::Activate [this=%p trans=%p caps=%x]\n", this, trans,
caps));
nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
nsHttpConnectionInfo* transCI = trans->ConnectionInfo();
NetAddr peerAddr;
if (!transCI->UsingProxy() && hTrans &&
NS_SUCCEEDED(GetPeerAddr(&peerAddr))) {
// set the targetIpAddressSpace in the transaction object, this might be
// needed by the channel for determining the kind of LNA permissions and/or
// LNA telemetry
if (!hTrans->AllowedToConnectToIpAddressSpace(
peerAddr.GetIpAddressSpace())) {
// we could probably fail early and avoid recreating the H3 session
CloseTransaction(mHttp3Session, NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED);
trans->Close(NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED);
return NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED;
}
}
if (!mExperienced && !trans->IsNullTransaction()) {
mHasFirstHttpTransaction = true;
// For QUIC we have HttpConnectionUDP before the actual connection
// has been establish so wait for TLS handshake to be finished before
// we mark the connection 'experienced'.
if (!mExperienced && mHttp3Session && mHttp3Session->IsConnected()) {
mExperienced = true;
}
if (mBootstrappedTimingsSet) {
mBootstrappedTimingsSet = false;
if (hTrans) {
hTrans->BootstrapTimings(mBootstrappedTimings);
}
}
mBootstrappedTimings = TimingStruct();
}
mTransactionCaps = caps;
mPriority = pri;
NS_ENSURE_ARG_POINTER(trans);
mErrorBeforeConnect = CheckTunnelIsNeeded(trans);
// Connection failures are Activated() just like regular transacions.
// If we don't have a confirmation of a connected socket then test it
// with a write() to get relevant error code.
if (NS_FAILED(mErrorBeforeConnect)) {
CloseTransaction(nullptr, mErrorBeforeConnect);
trans->Close(mErrorBeforeConnect);
gHttpHandler->ExcludeHttp3(mConnInfo);
return mErrorBeforeConnect;
}
// When mIsInTunnel is false, this HttpConnectionUDP represents the *outer*
// connection to the proxy. If a proxy CONNECT is still in progress,
// we need to queue the transaction until the outer connection is fully
// established.
//
// Important: we must not reset the transaction while the outer connection
// is still connecting. Resetting here could lead to opening another HTTP/3
// connection.
if (IsProxyConnectInProgress() && !mIsInTunnel && hTrans) {
if (!mConnected) {
mQueuedHttpConnectTransaction.AppendElement(hTrans);
(void)ResumeSend();
} else {
// Don’t call ResetTransaction() directly here.
// HttpConnectionUDP::Activate() may be invoked from
// nsHttpConnectionMgr::DispatchSpdyPendingQ(), which could run while
// enumerating all connection entries. ResetTransaction() can insert a new
// “wild” entry, and modifying the connection-entry table during iteration
// is not allowed.
RefPtr<HttpConnectionUDP> self(this);
RefPtr<nsHttpTransaction> httpTransaction(hTrans);
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"HttpConnectionUDP::ResetTransaction",
[self{std::move(self)},
httpTransaction{std::move(httpTransaction)}]() {
self->ResetTransaction(httpTransaction);
}));
}
return NS_OK;
}
// This is the CONNECT-UDP case. When mIsInTunnel is true, this is
// the *inner* connection from the proxy tunnel to the destination website.
// If the proxy CONNECT is still in progress, we cannot send any data
// yet because Http3ConnectUDPStream is not allowed to transmit until the
// tunnel is established. In this case, we queue the transaction and will
// add it to the Http3Session once the proxy CONNECT completes.
if (mIsInTunnel && IsProxyConnectInProgress() && hTrans) {
LOG(("Queue trans %p due to proxy connct in progress", hTrans));
mQueuedConnectUdpTransaction.AppendElement(hTrans);
return NS_OK;
}
if (!mHttp3Session->AddStream(trans, pri, mCallbacks)) {
MOZ_ASSERT(false); // this cannot happen!
trans->Close(NS_ERROR_ABORT);
return NS_ERROR_FAILURE;
}
if (mHasFirstHttpTransaction && mExperienced) {
mHasFirstHttpTransaction = false;
mExperienceState |= ConnectionExperienceState::Experienced;
}
(void)ResumeSend();
return NS_OK;
}
void HttpConnectionUDP::OnConnected() {
LOG(("HttpConnectionUDP::OnConnected %p", this));
MOZ_ASSERT(!mConnected, "Called more than once");
mConnected = true;
if (mIsInTunnel) {
return;
}
for (const auto& trans : mQueuedHttpConnectTransaction) {
ResetTransaction(trans);
}
mQueuedHttpConnectTransaction.Clear();
}
already_AddRefed<nsIInputStream> HttpConnectionUDP::CreateProxyConnectStream(
nsAHttpTransaction* trans) {
UniquePtr<nsHttpRequestHead> request = MakeUnique<nsHttpRequestHead>();
nsAutoCString host;
DebugOnly<nsresult> rv{};
rv = nsHttpHandler::GenerateHostPort(
nsDependentCString(trans->ConnectionInfo()->Origin()),
trans->ConnectionInfo()->OriginPort(), host);
MOZ_ASSERT(NS_SUCCEEDED(rv));
request->SetMethod("CONNECT"_ns);
request->SetVersion(gHttpHandler->HttpVersion());
bool shouldResistFingerprinting = trans->Caps() & NS_HTTP_USE_RFP;
rv = request->SetHeader(nsHttp::User_Agent,
gHttpHandler->UserAgent(shouldResistFingerprinting));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = request->SetHeader(nsHttp::Host, host);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsAutoCString val;
if (NS_SUCCEEDED(
trans->RequestHead()->GetHeader(nsHttp::Proxy_Authorization, val))) {
// TODO: we should use Proxy_Authorization here.
rv = request->SetHeader(nsHttp::Authorization, val);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
nsAutoCString result;
request->Flatten(result, false);
if (LOG1_ENABLED()) {
LOG(("HttpConnectionUDP::MakeConnectString for transaction=%p[",
trans->QueryHttpTransaction()));
LogHeaders(result.BeginReading());
LOG(("]"));
}
result.AppendLiteral("\r\n");
nsCOMPtr<nsIInputStream> stream;
NS_NewCStringInputStream(getter_AddRefs(stream), std::move(result));
return stream.forget();
}
nsresult HttpConnectionUDP::CreateTunnelStream(
nsAHttpTransaction* httpTransaction, HttpConnectionBase** aHttpConnection,
bool aIsExtendedCONNECT) {
LOG(("HttpConnectionUDP::CreateTunnelStream %p", this));
if (!mHttp3Session) {
return NS_ERROR_UNEXPECTED;
}
bool isHttp3 = httpTransaction->ConnectionInfo()->IsHttp3();
if (!isHttp3) {
RefPtr<Http3ConnectTransaction> trans = new Http3ConnectTransaction(
httpTransaction->Caps(), httpTransaction->ConnectionInfo());
RefPtr<nsHttpConnection> conn =
mHttp3Session->CreateTunnelStream(trans, mCallbacks, mRtt, false);
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
trans->SetConnection(handle);
conn.forget(aHttpConnection);
return NS_OK;
}
nsCOMPtr<nsIInputStream> proxyConnectStream =
CreateProxyConnectStream(httpTransaction);
if (!proxyConnectStream) {
return NS_ERROR_OUT_OF_MEMORY;
}
RefPtr<ConnectUDPTransaction> trans =
new ConnectUDPTransaction(httpTransaction, proxyConnectStream);
RefPtr<HttpConnectionUDP> conn =
mHttp3Session->CreateTunnelStream(trans, mCallbacks);
RefPtr<ConnectionHandle> handle = new ConnectionHandle(conn);
trans->SetConnection(handle);
conn.forget(aHttpConnection);
return NS_OK;
}
void HttpConnectionUDP::Close(nsresult reason, bool aIsShutdown) {
LOG(("HttpConnectionUDP::Close [this=%p reason=%" PRIx32 "]\n", this,
static_cast<uint32_t>(reason)));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mConnectionState != ConnectionState::CLOSED) {
RecordConnectionCloseTelemetry(reason);
ChangeConnectionState(ConnectionState::CLOSED);
}
if (mForceSendTimer) {
mForceSendTimer->Cancel();
mForceSendTimer = nullptr;
}
if (!mTrafficCategory.IsEmpty()) {
HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
if (hta) {
hta->IncrementHttpConnection(std::move(mTrafficCategory));
MOZ_ASSERT(mTrafficCategory.IsEmpty());
}
}
nsCOMPtr<nsIUDPSocket> socket = std::move(mSocket);
if (socket) {
socket->Close();
}
for (const auto& trans : mQueuedHttpConnectTransaction) {
trans->Close(reason);
}
mQueuedHttpConnectTransaction.Clear();
for (const auto& trans : mQueuedConnectUdpTransaction) {
trans->Close(reason);
}
mQueuedConnectUdpTransaction.Clear();
}
void HttpConnectionUDP::DontReuse() {
LOG(("HttpConnectionUDP::DontReuse %p http3session=%p\n", this,
mHttp3Session.get()));
mDontReuse = true;
if (mHttp3Session) {
mHttp3Session->DontReuse();
}
}
bool HttpConnectionUDP::TestJoinConnection(const nsACString& hostname,
int32_t port) {
if (mHttp3Session && CanDirectlyActivate()) {
return mHttp3Session->TestJoinConnection(hostname, port);
}
return false;
}
bool HttpConnectionUDP::JoinConnection(const nsACString& hostname,
int32_t port) {
if (mHttp3Session && CanDirectlyActivate()) {
return mHttp3Session->JoinConnection(hostname, port);
}
return false;
}
bool HttpConnectionUDP::CanReuse() {
if (NS_FAILED(mErrorBeforeConnect)) {
return false;
}
if (mDontReuse) {
return false;
}
if (mHttp3Session) {
return mHttp3Session->CanReuse();
}
return false;
}
bool HttpConnectionUDP::CanDirectlyActivate() {
// return true if a new transaction can be addded to ths connection at any
// time through Activate(). In practice this means this is a healthy SPDY
// connection with room for more concurrent streams.
if (mHttp3Session) {
return CanReuse();
}
return false;
}
//----------------------------------------------------------------------------
// HttpConnectionUDP::nsAHttpConnection compatible methods
//----------------------------------------------------------------------------
nsresult HttpConnectionUDP::OnHeadersAvailable(nsAHttpTransaction* trans,
nsHttpRequestHead* requestHead,
nsHttpResponseHead* responseHead,
bool* reset) {
LOG(
("HttpConnectionUDP::OnHeadersAvailable [this=%p trans=%p "
"response-head=%p]\n",
this, trans, responseHead));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
NS_ENSURE_ARG_POINTER(trans);
MOZ_ASSERT(responseHead, "No response head?");
DebugOnly<nsresult> rv =
responseHead->SetHeader(nsHttp::X_Firefox_Http3, mAlpnToken);
MOZ_ASSERT(NS_SUCCEEDED(rv));
uint16_t responseStatus = responseHead->Status();
nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
if (mState == HttpConnectionState::SETTING_UP_TUNNEL) {
HandleTunnelResponse(hTrans, responseStatus, reset);
return NS_OK;
}
// deal with 408 Server Timeouts
static const PRIntervalTime k1000ms = PR_MillisecondsToInterval(1000);
if (responseStatus == 408) {
// If this error could be due to a persistent connection reuse then
// we pass an error code of NS_ERROR_NET_RESET to
// trigger the transaction 'restart' mechanism. We tell it to reset its
// response headers so that it will be ready to receive the new response.
if (mIsReused &&
((PR_IntervalNow() - mHttp3Session->LastWriteTime()) < k1000ms)) {
Close(NS_ERROR_NET_RESET);
*reset = true;
return NS_OK;
}
}
return NS_OK;
}
void HttpConnectionUDP::HandleTunnelResponse(
nsHttpTransaction* aHttpTransaction, uint16_t responseStatus, bool* reset) {
LOG(("HttpConnectionUDP::HandleTunnelResponse mIsInTunnel=%d", mIsInTunnel));
MOZ_ASSERT(TunnelSetupInProgress());
MOZ_ASSERT(mIsInTunnel);
if (responseStatus == 200) {
ChangeState(HttpConnectionState::REQUEST);
}
bool onlyConnect = mTransactionCaps & NS_HTTP_CONNECT_ONLY;
aHttpTransaction->OnProxyConnectComplete(responseStatus);
if (responseStatus == 200) {
LOG(("proxy CONNECT succeeded! onlyconnect=%d mIsInTunnel=%d\n",
onlyConnect, mIsInTunnel));
// If we're only connecting, we don't need to reset the transaction
// state. We need to upgrade the socket now without doing the actual
// http request.
if (!onlyConnect) {
*reset = true;
}
for (const auto& trans : mQueuedConnectUdpTransaction) {
LOG(("add trans=%p", trans.get()));
if (!mHttp3Session->AddStream(trans, trans->Priority(), mCallbacks)) {
MOZ_ASSERT(false); // this cannot happen!
trans->Close(NS_ERROR_ABORT);
}
}
mQueuedConnectUdpTransaction.Clear();
mProxyConnectSucceeded = true;
(void)ResumeSend();
} else {
LOG(("proxy CONNECT failed! onlyconnect=%d\n", onlyConnect));
aHttpTransaction->SetProxyConnectFailed();
mQueuedConnectUdpTransaction.Clear();
}
}
void HttpConnectionUDP::ResetTransaction(nsHttpTransaction* aHttpTransaction) {
LOG(("HttpConnectionUDP::ResetTransaction [this=%p mState=%d]\n", this,
static_cast<uint32_t>(mState)));
RefPtr<nsHttpConnectionInfo> wildCardProxyCi;
nsresult rv = mConnInfo->CreateWildCard(getter_AddRefs(wildCardProxyCi));
if (NS_FAILED(rv)) {
CloseTransaction(mHttp3Session, rv);
aHttpTransaction->Close(rv);
return;
}
// Both Http3Session and nsHttpTransaction keeps a strong reference to a
// ConnectionHandle, which itself holds a strong ref back to the concrete
// connection, and when the last reference to the handle drops, the underlying
// connection is released.
// Notes:
// 1) SetConnection() may drop the previous
// connection reference, which can immediately destroy that handle.
// Avoid calling it unless we actually need to create/attach a handle.
// 2) For speculative connections, the transaction may already have a handle.
// Reuse it rather than creating a new one.
// 3) Only set the session's connection if it doesn't already have one.
if (!mHttp3Session->Connection()) {
if (!aHttpTransaction->Connection()) {
RefPtr<ConnectionHandle> handle = new ConnectionHandle(this);
aHttpTransaction->SetConnection(handle);
}
mHttp3Session->SetConnection(aHttpTransaction->Connection());
}
aHttpTransaction->SetConnection(nullptr);
gHttpHandler->ConnMgr()->MoveToWildCardConnEntry(mConnInfo, wildCardProxyCi,
this);
mConnInfo = wildCardProxyCi;
aHttpTransaction->DoNotRemoveAltSvc();
aHttpTransaction->Close(NS_ERROR_NET_RESET);
}
bool HttpConnectionUDP::IsReused() { return mIsReused; }
nsresult HttpConnectionUDP::TakeTransport(
nsISocketTransport** aTransport, nsIAsyncInputStream** aInputStream,
nsIAsyncOutputStream** aOutputStream) {
return NS_ERROR_FAILURE;
}
void HttpConnectionUDP::GetTLSSocketControl(nsITLSSocketControl** secinfo) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("HttpConnectionUDP::GetTLSSocketControl http3Session=%p\n",
mHttp3Session.get()));
if (mHttp3Session &&
NS_SUCCEEDED(mHttp3Session->GetTransactionTLSSocketControl(secinfo))) {
return;
}
*secinfo = nullptr;
}
nsresult HttpConnectionUDP::PushBack(const char* data, uint32_t length) {
LOG(("HttpConnectionUDP::PushBack [this=%p, length=%d]\n", this, length));
return NS_ERROR_UNEXPECTED;
}
class HttpConnectionUDPForceIO : public Runnable {
public:
HttpConnectionUDPForceIO(HttpConnectionUDP* aConn, bool doRecv)
: Runnable("net::HttpConnectionUDPForceIO"),
mConn(aConn),
mDoRecv(doRecv) {}
NS_IMETHOD Run() override {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (mDoRecv) {
return mConn->RecvData();
}
MOZ_ASSERT(mConn->mForceSendPending);
mConn->mForceSendPending = false;
return mConn->SendData();
}
private:
RefPtr<HttpConnectionUDP> mConn;
bool mDoRecv;
};
nsresult HttpConnectionUDP::ResumeSend() {
LOG(("HttpConnectionUDP::ResumeSend [this=%p]\n", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
RefPtr<HttpConnectionUDP> self(this);
NS_DispatchToCurrentThread(
NS_NewRunnableFunction("HttpConnectionUDP::CallSendData",
[self{std::move(self)}]() { self->SendData(); }));
return NS_OK;
}
nsresult HttpConnectionUDP::ResumeRecv() { return NS_OK; }
void HttpConnectionUDP::ForceSendIO(nsITimer* aTimer, void* aClosure) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
HttpConnectionUDP* self = static_cast<HttpConnectionUDP*>(aClosure);
MOZ_ASSERT(aTimer == self->mForceSendTimer);
self->mForceSendTimer = nullptr;
NS_DispatchToCurrentThread(new HttpConnectionUDPForceIO(self, false));
}
nsresult HttpConnectionUDP::MaybeForceSendIO() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// NSPR derived I/O events are ready and this can cause a deadlock with
// https over https proxying. Normally we would expect the write callback to
// be invoked before this timer goes off, but set it at the old windows
// tick interval (kForceDelay) as a backup for those circumstances.
static const uint32_t kForceDelay = 17; // ms
if (mForceSendPending) {
return NS_OK;
}
MOZ_ASSERT(!mForceSendTimer);
mForceSendPending = true;
return NS_NewTimerWithFuncCallback(
getter_AddRefs(mForceSendTimer), HttpConnectionUDP::ForceSendIO, this,
kForceDelay, nsITimer::TYPE_ONE_SHOT,
"net::HttpConnectionUDP::MaybeForceSendIO"_ns);
}
// trigger an asynchronous read
nsresult HttpConnectionUDP::ForceRecv() {
LOG(("HttpConnectionUDP::ForceRecv [this=%p]\n", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
return NS_DispatchToCurrentThread(new HttpConnectionUDPForceIO(this, true));
}
// trigger an asynchronous write
nsresult HttpConnectionUDP::ForceSend() {
LOG(("HttpConnectionUDP::ForceSend [this=%p]\n", this));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
return MaybeForceSendIO();
}
HttpVersion HttpConnectionUDP::Version() { return HttpVersion::v3_0; }
PRIntervalTime HttpConnectionUDP::LastWriteTime() {
return mHttp3Session->LastWriteTime();
}
//-----------------------------------------------------------------------------
// HttpConnectionUDP <private>
//-----------------------------------------------------------------------------
void HttpConnectionUDP::CloseTransaction(nsAHttpTransaction* trans,
nsresult reason, bool aIsShutdown) {
LOG(("HttpConnectionUDP::CloseTransaction[this=%p trans=%p reason=%" PRIx32
"]\n",
this, trans, static_cast<uint32_t>(reason)));
// CloseTransaction may be called by nsHttpTransaction when a fallback
// is needed. In this case, the transaction is still in mQueuedTransaction
// and the proxy connect is still in progress.
bool transInQueue = mQueuedHttpConnectTransaction.Contains(trans) ||
mQueuedConnectUdpTransaction.Contains(trans);
MOZ_ASSERT(trans == mHttp3Session ||
(transInQueue && IsProxyConnectInProgress()));
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if (NS_SUCCEEDED(reason) || (reason == NS_BASE_STREAM_CLOSED)) {
return;
}
// The connection and security errors clear out alt-svc mappings
// in case any previously validated ones are now invalid
if (((reason == NS_ERROR_NET_RESET) ||
(NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY)) &&
mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
gHttpHandler->ClearHostMapping(mConnInfo);
}
mDontReuse = true;
if (mHttp3Session) {
// When proxy connnect failed, we call Http3Session::SetCleanShutdown to
// force Http3Session to release this UDP connection.
mHttp3Session->SetCleanShutdown(aIsShutdown || transInQueue ||
(mIsInTunnel && !mProxyConnectSucceeded));
mHttp3Session->Close(reason);
if (!mHttp3Session->IsClosed()) {
// During closing phase we still keep mHttp3Session session,
// to resend CLOSE_CONNECTION frames.
return;
}
}
mHttp3Session = nullptr;
{
MutexAutoLock lock(mCallbacksLock);
mCallbacks = nullptr;
}
Close(reason, aIsShutdown);
// flag the connection as reused here for convenience sake. certainly
// it might be going away instead ;-)
mIsReused = true;
}
void HttpConnectionUDP::OnQuicTimeoutExpired() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("HttpConnectionUDP::OnQuicTimeoutExpired [this=%p]\n", this));
// if the transaction was dropped...
if (!mHttp3Session) {
LOG((" no transaction; ignoring event\n"));
return;
}
nsresult rv = mHttp3Session->ProcessOutputAndEvents(mSocket);
if (NS_FAILED(rv)) {
CloseTransaction(mHttp3Session, rv);
}
}
//-----------------------------------------------------------------------------
// HttpConnectionUDP::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF(HttpConnectionUDP)
NS_IMPL_RELEASE(HttpConnectionUDP)
NS_INTERFACE_MAP_BEGIN(HttpConnectionUDP)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIUDPSocketSyncListener)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(HttpConnectionBase)
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpConnectionUDP)
NS_INTERFACE_MAP_END
void HttpConnectionUDP::NotifyDataRead() {
mExperienceState |= ConnectionExperienceState::First_Response_Received;
}
void HttpConnectionUDP::NotifyDataWrite() {
mExperienceState |= ConnectionExperienceState::First_Request_Sent;
}
// called on the socket transport thread
nsresult HttpConnectionUDP::RecvData() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// if the transaction was dropped...
if (!mHttp3Session) {
LOG((" no Http3Session; ignoring event\n"));
return NS_OK;
}
nsresult rv = mHttp3Session->RecvData(mSocket);
LOG(("HttpConnectionUDP::OnInputReady %p rv=%" PRIx32, this,
static_cast<uint32_t>(rv)));
if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
return NS_OK;
}
// called on the socket transport thread
nsresult HttpConnectionUDP::SendData() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
// if the transaction was dropped...
if (!mHttp3Session) {
LOG((" no Http3Session; ignoring event\n"));
return NS_OK;
}
nsresult rv = mHttp3Session->SendData(mSocket);
LOG(("HttpConnectionUDP::OnInputReady %p rv=%" PRIx32, this,
static_cast<uint32_t>(rv)));
if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpConnectionUDP::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
// not called on the socket transport thread
NS_IMETHODIMP
HttpConnectionUDP::GetInterface(const nsIID& iid, void** result) {
// NOTE: This function is only called on the UI thread via sync proxy from
// the socket transport thread. If that weren't the case, then we'd
// have to worry about the possibility of mHttp3Session going away
// part-way through this function call. See CloseTransaction.
// NOTE - there is a bug here, the call to getinterface is proxied off the
// nss thread, not the ui thread as the above comment says. So there is
MOZ_ASSERT(!OnSocketThread(), "on socket thread");
nsCOMPtr<nsIInterfaceRequestor> callbacks;
{
MutexAutoLock lock(mCallbacksLock);
callbacks = mCallbacks;
}
if (callbacks) return callbacks->GetInterface(iid, result);
return NS_ERROR_NO_INTERFACE;
}
void HttpConnectionUDP::SetEvent(nsresult aStatus) {
switch (aStatus) {
case NS_NET_STATUS_RESOLVING_HOST:
mBootstrappedTimings.domainLookupStart = TimeStamp::Now();
break;
case NS_NET_STATUS_RESOLVED_HOST:
mBootstrappedTimings.domainLookupEnd = TimeStamp::Now();
break;
case NS_NET_STATUS_CONNECTING_TO:
mBootstrappedTimings.connectStart = TimeStamp::Now();
mBootstrappedTimings.secureConnectionStart =
mBootstrappedTimings.connectStart;
break;
case NS_NET_STATUS_CONNECTED_TO:
mBootstrappedTimings.connectEnd = TimeStamp::Now();
break;
default:
break;
}
}
bool HttpConnectionUDP::LastTransactionExpectedNoContent() {
return mLastTransactionExpectedNoContent;
}
void HttpConnectionUDP::SetLastTransactionExpectedNoContent(bool val) {
mLastTransactionExpectedNoContent = val;
}
bool HttpConnectionUDP::IsPersistent() { return !mDontReuse; }
nsAHttpTransaction* HttpConnectionUDP::Transaction() { return mHttp3Session; }
int64_t HttpConnectionUDP::BytesWritten() {
if (!mHttp3Session) {
return 0;
}
return mHttp3Session->GetBytesWritten();
}
NS_IMETHODIMP HttpConnectionUDP::OnPacketReceived(nsIUDPSocket* aSocket) {
RecvData();
return NS_OK;
}
NS_IMETHODIMP HttpConnectionUDP::OnStopListening(nsIUDPSocket* aSocket,
nsresult aStatus) {
CloseTransaction(mHttp3Session, aStatus);
return NS_OK;
}
nsresult HttpConnectionUDP::GetSelfAddr(NetAddr* addr) {
if (mSelfAddr) {
return mSelfAddr->GetNetAddr(addr);
}
return NS_ERROR_FAILURE;
}
nsresult HttpConnectionUDP::GetPeerAddr(NetAddr* addr) {
if (mPeerAddr) {
return mPeerAddr->GetNetAddr(addr);
}
return NS_ERROR_FAILURE;
}
bool HttpConnectionUDP::ResolvedByTRR() { return mResolvedByTRR; }
nsIRequest::TRRMode HttpConnectionUDP::EffectiveTRRMode() {
return mEffectiveTRRMode;
}
TRRSkippedReason HttpConnectionUDP::TRRSkipReason() { return mTRRSkipReason; }
Http3Stats HttpConnectionUDP::GetStats() {
if (!mHttp3Session) {
return Http3Stats();
}
return mHttp3Session->GetStats();
}
} // namespace net
} // namespace mozilla