From 05c4dcd62f28242dcdd088c85d018f7e6a424e77 Mon Sep 17 00:00:00 2001 From: Dragana Damjanovic Date: Thu, 10 Sep 2020 20:48:56 +0000 Subject: [PATCH] Bug 1650388 - Send 0RTT data. r=kershaw Differential Revision: https://phabricator.services.mozilla.com/D87231 --- netwerk/protocol/http/Http3Session.cpp | 103 ++++++++++++++++++++--- netwerk/protocol/http/Http3Session.h | 13 ++- netwerk/protocol/http/Http3Stream.cpp | 31 +++++++ netwerk/protocol/http/Http3Stream.h | 8 ++ netwerk/socket/neqo_glue/NeqoHttp3Conn.h | 4 + netwerk/socket/neqo_glue/src/lib.rs | 5 ++ 6 files changed, 150 insertions(+), 14 deletions(-) diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp index 5c3b157acebe..76b60b6a531a 100644 --- a/netwerk/protocol/http/Http3Session.cpp +++ b/netwerk/protocol/http/Http3Session.cpp @@ -156,6 +156,24 @@ nsresult Http3Session::Init(const nsACString& aOrigin, if (NS_SUCCEEDED(SSLTokensCache::Get(peerId, token))) { LOG(("Found a resumption token in the cache.")); mHttp3Connection->SetResumptionToken(token); + if (mHttp3Connection->IsZeroRtt()) { + LOG(("Can send ZeroRtt data")); + RefPtr self(this); + mState = ZERORTT; + // Let the nsHttpConnectionMgr know that the connection can accept + // transactions. + // We need to dispatch the following function to this thread so that + // it is executed after the current function. At this point a + // Http3Session is still being initialized and ReportHttp3Connection + // will try to dispatch transaction on this session therefore it + // needs to be executed after the initializationg is done. + DebugOnly rv = NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "Http3Session::ReportHttp3Connection", + [self]() { + self->ReportHttp3Connection(); + })); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed"); + } } return NS_OK; } @@ -360,11 +378,10 @@ nsresult Http3Session::ProcessEvents(uint32_t count) { static_cast(rv))); return rv; } - break; } case Http3Event::Tag::DataWritable: - MOZ_ASSERT(mState == CONNECTED); + MOZ_ASSERT(CanSandData()); LOG(("Http3Session::ProcessEvents - DataWritable")); if (mReadyForWriteButBlocked.RemoveElement( event.data_writable.stream_id)) { @@ -417,12 +434,23 @@ nsresult Http3Session::ProcessEvents(uint32_t count) { CallCertVerification(); } break; + case Http3Event::Tag::ZeroRttRejected: + LOG(("Http3Session::ProcessEvents - ZeroRttRejected")); + if (mState == ZERORTT) { + mState = INITIALIZING; + Finish0Rtt(true); + } + break; case Http3Event::Tag::ConnectionConnected: { LOG(("Http3Session::ProcessEvents - ConnectionConnected")); + bool was0RTT = mState == ZERORTT; mState = CONNECTED; SetSecInfo(); mSocketControl->HandshakeCompleted(); + if (was0RTT) { + Finish0Rtt(false); + } nsTArray token; mHttp3Connection->GetResumptionToken(token); if (!token.IsEmpty()) { @@ -436,8 +464,7 @@ nsresult Http3Session::ProcessEvents(uint32_t count) { } } - gHttpHandler->ConnMgr()->ReportHttp3Connection(mSegmentReaderWriter); - MaybeResumeSend(); + ReportHttp3Connection(); } break; case Http3Event::Tag::GoawayReceived: @@ -600,13 +627,25 @@ bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction, Http3Stream* stream = new Http3Stream(aHttpTransaction, this); mStreamTransactionHash.Put(aHttpTransaction, RefPtr{stream}); + if (mState == ZERORTT) { + if (!stream->Do0RTT()) { + LOG(("Http3Session %p will not get early data from Http3Stream %p", + this, stream)); + if (!mCannotDo0RTTStreams.Contains(stream)) { + mCannotDo0RTTStreams.AppendElement(stream); + } + return true; + } else { + m0RTTStreams.AppendElement(stream); + } + } StreamReadyToWrite(stream); return true; } bool Http3Session::CanReuse() { - return (mState == CONNECTED) && !(mGoawayReceived || mShouldClose); + return CanSandData() && !(mGoawayReceived || mShouldClose); } void Http3Session::QueueStream(Http3Stream* stream) { @@ -677,6 +716,13 @@ nsresult Http3Session::TryActivating( return NS_BASE_STREAM_WOULD_BLOCK; } + if (mState == ZERORTT) { + if (!aStream->Do0RTT()) { + MOZ_ASSERT(!mCannotDo0RTTStreams.Contains(aStream)); + return NS_BASE_STREAM_WOULD_BLOCK; + } + } + nsresult rv = mHttp3Connection->Fetch(aMethod, aScheme, aAuthorityHeader, aPath, aHeaders, aStreamId); if (NS_FAILED(rv)) { @@ -808,10 +854,7 @@ nsresult Http3Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader, Http3Stream* stream = nullptr; // Step 1) - while ( - (mState == - CONNECTED) && // Do not send transaction data untill we are connected. - (stream = mReadyForWrite.PopFront())) { + while (CanSandData() && (stream = mReadyForWrite.PopFront())) { LOG( ("Http3Session::ReadSegmentsAgain call ReadSegments from stream=%p " "[this=%p]", @@ -882,13 +925,13 @@ nsresult Http3Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader, void Http3Session::StreamReadyToWrite(Http3Stream* aStream) { MOZ_ASSERT(aStream); mReadyForWrite.Push(aStream); - if ((mState == CONNECTED) && mConnection) { + if (CanSandData() && mConnection) { Unused << mConnection->ResumeSend(); } } void Http3Session::MaybeResumeSend() { - if ((mReadyForWrite.GetSize() > 0) && (mState == CONNECTED) && mConnection) { + if ((mReadyForWrite.GetSize() > 0) && CanSandData() && mConnection) { Unused << mConnection->ResumeSend(); } } @@ -1271,7 +1314,7 @@ bool Http3Session::JoinConnection(const nsACString& hostname, int32_t port) { bool Http3Session::RealJoinConnection(const nsACString& hostname, int32_t port, bool justKidding) { MOZ_ASSERT(OnSocketThread(), "not on socket thread"); - if (!mConnection || (mState != CONNECTED) || mShouldClose || + if (!mConnection || !CanSandData() || mShouldClose || mGoawayReceived) { return false; } @@ -1374,7 +1417,7 @@ void Http3Session::CallCertVerification() { void Http3Session::Authenticated(int32_t aError) { LOG(("Http3Session::Authenticated error=0x%" PRIx32 " [this=%p].", aError, this)); - if (mState == INITIALIZING) { + if ((mState == INITIALIZING) || (mState == ZERORTT)) { if (psm::IsNSSErrorCode(aError)) { mError = psm::GetXPCOMFromNSSError(aError); LOG(("Http3Session::Authenticated psm-error=0x%" PRIx32 " [this=%p].", @@ -1452,5 +1495,39 @@ void Http3Session::CloseConnectionTelemetry(CloseError& aError, bool aClosing) { aClosing ? "closing"_ns : "closed"_ns, value); } +void Http3Session::Finish0Rtt(bool aRestart) { + for (size_t i = 0; i < m0RTTStreams.Length(); ++i) { + if (m0RTTStreams[i]) { + if (aRestart) { + // When we need ot restart transactions remove them from all lists. + if (m0RTTStreams[i]->HasStreamId()) { + mStreamIdHash.Remove(m0RTTStreams[i]->StreamId()); + } + RemoveStreamFromQueues(m0RTTStreams[i]); + // The stream is ready to write again. + mReadyForWrite.Push(m0RTTStreams[i]); + } + m0RTTStreams[i]->Finish0RTT(aRestart); + } + } + + for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) { + if (mCannotDo0RTTStreams[i]) { + mReadyForWrite.Push(mCannotDo0RTTStreams[i]); + } + } + m0RTTStreams.Clear(); + mCannotDo0RTTStreams.Clear(); + MaybeResumeSend(); +} + +void Http3Session::ReportHttp3Connection() { + if (CanSandData() && !mHttp3ConnectionReported) { + mHttp3ConnectionReported = true; + gHttpHandler->ConnMgr()->ReportHttp3Connection(mSegmentReaderWriter); + MaybeResumeSend(); + } +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h index 69597f244a54..698c9e0a93b7 100644 --- a/netwerk/protocol/http/Http3Session.h +++ b/netwerk/protocol/http/Http3Session.h @@ -51,6 +51,7 @@ class Http3Session final : public nsAHttpTransaction, HttpConnectionUDP* readerWriter); bool IsConnected() const { return mState == CONNECTED; } + bool CanSandData() const { return (mState == CONNECTED) || (mState == ZERORTT); } bool IsClosing() const { return (mState == CLOSING || mState == CLOSED); } bool IsClosed() const { return mState == CLOSED; } @@ -100,6 +101,8 @@ class Http3Session final : public nsAHttpTransaction, const nsCString& GetAlpnToken() { return mAlpnToken; } + void ReportHttp3Connection(); + private: ~Http3Session(); @@ -137,6 +140,7 @@ class Http3Session final : public nsAHttpTransaction, void MaybeResumeSend(); void CloseConnectionTelemetry(CloseError& aError, bool aClosing); + void Finish0Rtt(bool aRestart); RefPtr mHttp3Connection; RefPtr mConnection; @@ -149,13 +153,14 @@ class Http3Session final : public nsAHttpTransaction, nsTArray> mSlowConsumersReadyForRead; nsDeque mQueuedStreams; - enum State { INITIALIZING, CONNECTED, CLOSING, CLOSED } mState; + enum State { INITIALIZING, ZERORTT, CONNECTED, CLOSING, CLOSED } mState; bool mAuthenticationStarted; bool mCleanShutdown; bool mGoawayReceived; bool mShouldClose; bool mIsClosedByNeqo; + bool mHttp3ConnectionReported = false; // mError is neqo error (a protocol error) and that may mean that we will // send some packets after that. nsresult mError; @@ -180,6 +185,12 @@ class Http3Session final : public nsAHttpTransaction, nsCString mAlpnToken; uint64_t mTransactionCount = 0; + + // The stream(s) that we are getting 0RTT data from. + nsTArray> m0RTTStreams; + // The stream(s) that are not able to send 0RTT data. We need to + // remember them put them into mReadyForWrite queue when 0RTT finishes. + nsTArray> mCannotDo0RTTStreams; }; NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID); diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp index ab9ecd7a9914..02c506d62050 100644 --- a/netwerk/protocol/http/Http3Stream.cpp +++ b/netwerk/protocol/http/Http3Stream.cpp @@ -375,5 +375,36 @@ nsresult Http3Stream::WriteSegments(nsAHttpSegmentWriter* writer, return rv; } +bool Http3Stream::Do0RTT() { + MOZ_ASSERT(mTransaction); + mAttempting0RTT = mTransaction->Do0RTT(); + return mAttempting0RTT; +} + +nsresult Http3Stream::Finish0RTT(bool aRestart) { + MOZ_ASSERT(mTransaction); + mAttempting0RTT = false; + nsresult rv = mTransaction->Finish0RTT(aRestart, false); + if (aRestart) { + nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); + if (trans) { + trans->Refused0RTT(); + } + } + mSendState = PREPARING_HEADERS; + mRecvState = READING_HEADERS; + mStreamId = UINT64_MAX; + mQueued = false; + mRequestBlockedOnRead = false; + mDataReceived = false; + mResetRecv = false; + mRequestBodyLenRemaining = 0; + mTotalSent = 0; + mTotalRead = 0; + mFin = false; + + return rv; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/Http3Stream.h b/netwerk/protocol/http/Http3Stream.h index ca5ce45d0c1e..d0dd504c0102 100644 --- a/netwerk/protocol/http/Http3Stream.h +++ b/netwerk/protocol/http/Http3Stream.h @@ -8,6 +8,7 @@ #include "nsAHttpTransaction.h" #include "ARefBase.h" +#include "mozilla/WeakPtr.h" namespace mozilla { namespace net { @@ -16,6 +17,7 @@ class Http3Session; class Http3Stream final : public nsAHttpSegmentReader, public nsAHttpSegmentWriter, + public SupportsWeakPtr, public ARefBase { public: NS_DECL_NSAHTTPSEGMENTREADER @@ -60,6 +62,10 @@ class Http3Stream final : public nsAHttpSegmentReader, void SetResponseHeaders(nsTArray& aResponseHeaders, bool fin); + // Mirrors nsAHttpTransaction + bool Do0RTT(); + nsresult Finish0RTT(bool aRestart); + private: ~Http3Stream() = default; @@ -142,6 +148,8 @@ class Http3Stream final : public nsAHttpSegmentReader, uint64_t mTotalRead; bool mFin; + + bool mAttempting0RTT = false; }; } // namespace net diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h index eec0e22069ff..2f249d1c0a95 100644 --- a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h +++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h @@ -88,6 +88,10 @@ class NeqoHttp3Conn final { neqo_http3conn_set_resumption_token(this, &aToken); } + bool IsZeroRtt() { + return neqo_http3conn_is_zero_rtt(this); + } + nsrefcnt AddRef() { return neqo_http3conn_addref(this); } nsrefcnt Release() { return neqo_http3conn_release(this); } diff --git a/netwerk/socket/neqo_glue/src/lib.rs b/netwerk/socket/neqo_glue/src/lib.rs index eef5018b6b51..e3a13bf408ee 100644 --- a/netwerk/socket/neqo_glue/src/lib.rs +++ b/netwerk/socket/neqo_glue/src/lib.rs @@ -690,3 +690,8 @@ pub extern "C" fn neqo_http3conn_resumption_token(conn: &mut NeqoHttp3Conn, toke pub extern "C" fn neqo_http3conn_set_resumption_token(conn: &mut NeqoHttp3Conn, token: &mut ThinVec,) { let _ = conn.conn.set_resumption_token(Instant::now(), token); } + +#[no_mangle] +pub extern "C" fn neqo_http3conn_is_zero_rtt(conn: &mut NeqoHttp3Conn) -> bool { + conn.conn.state() == Http3State::ZeroRtt +}