From bb9e155841de0e0fce81183a2fe6471f5c4df288 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 11 Feb 2018 07:48:28 +0100 Subject: Bug 1322373 - TLS 1.3 early-data for http/2 --- netwerk/protocol/http/ASpdySession.cpp | 5 +- netwerk/protocol/http/ASpdySession.h | 3 +- netwerk/protocol/http/Http2Session.cpp | 135 ++++++++++++++++- netwerk/protocol/http/Http2Session.h | 9 +- netwerk/protocol/http/Http2Stream.cpp | 26 +++- netwerk/protocol/http/Http2Stream.h | 6 + netwerk/protocol/http/nsAHttpTransaction.h | 5 +- netwerk/protocol/http/nsHttpConnection.cpp | 222 +++++++++++++++++++--------- netwerk/protocol/http/nsHttpConnection.h | 9 ++ netwerk/protocol/http/nsHttpTransaction.cpp | 2 +- netwerk/protocol/http/nsHttpTransaction.h | 2 +- 11 files changed, 342 insertions(+), 82 deletions(-) (limited to 'netwerk/protocol/http') diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp index 6bd54c7c0..f22c326d1 100644 --- a/netwerk/protocol/http/ASpdySession.cpp +++ b/netwerk/protocol/http/ASpdySession.cpp @@ -32,7 +32,8 @@ ASpdySession::~ASpdySession() = default; ASpdySession * ASpdySession::NewSpdySession(uint32_t version, - nsISocketTransport *aTransport) + nsISocketTransport *aTransport, + bool attemptingEarlyData) { // This is a necko only interface, so we can enforce version // requests as a precondition @@ -46,7 +47,7 @@ ASpdySession::NewSpdySession(uint32_t version, Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version); - return new Http2Session(aTransport, version); + return new Http2Session(aTransport, version, attemptingEarlyData); } SpdyInformation::SpdyInformation() diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h index e116d423b..a3db61d15 100644 --- a/netwerk/protocol/http/ASpdySession.h +++ b/netwerk/protocol/http/ASpdySession.h @@ -28,8 +28,9 @@ public: virtual PRIntervalTime IdleTime() = 0; virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0; virtual void DontReuse() = 0; + virtual uint32_t SpdyVersion() = 0; - static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *); + static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *, bool); // MaybeReTunnel() is called by the connection manager when it cannot // dispatch a tunneled transaction. That might be because the tunnels it diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 5d3c22fc9..e649c3d8a 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -64,7 +64,7 @@ do { \ return NS_ERROR_ILLEGAL_VALUE; \ } while (0) -Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version) +Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version, bool attemptingEarlyData) : mSocketTransport(aSocketTransport) , mSegmentReader(nullptr) , mSegmentWriter(nullptr) @@ -112,6 +112,7 @@ Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t versio , mWaitingForSettingsAck(false) , mGoAwayOnPush(false) , mUseH2Deps(false) + , mAttemptingEarlyData(attemptingEarlyData) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); @@ -501,6 +502,12 @@ Http2Session::SetWriteCallbacks() void Http2Session::RealignOutputQueue() { + if (mAttemptingEarlyData) { + // We can't realign right now, because we may need what's in there if early + // data fails. + return; + } + mOutputQueueUsed -= mOutputQueueSent; memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent, @@ -518,6 +525,14 @@ Http2Session::FlushOutputQueue() uint32_t countRead; uint32_t avail = mOutputQueueUsed - mOutputQueueSent; + if (!avail && mAttemptingEarlyData) { + // This is kind of a hack, but there are cases where we'll have already + // written the data we want whlie doing early data, but we get called again + // with a reader, and we need to avoid calling the reader when there's + // nothing for it to read. + return; + } + rv = mSegmentReader-> OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead); @@ -528,14 +543,18 @@ Http2Session::FlushOutputQueue() if (NS_FAILED(rv)) return; + mOutputQueueSent += countRead; + + if (mAttemptingEarlyData) { + return; + } + if (countRead == avail) { mOutputQueueUsed = 0; mOutputQueueSent = 0; return; } - mOutputQueueSent += countRead; - // If the output queue is close to filling up and we have sent out a good // chunk of data from the beginning then realign it. @@ -554,6 +573,12 @@ Http2Session::DontReuse() Close(NS_OK); } +uint32_t +Http2Session::SpdyVersion() +{ + return HTTP_VERSION_2; +} + uint32_t Http2Session::GetWriteQueueSize() { @@ -2328,7 +2353,36 @@ Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader, Unused << ResumeRecv(); } SetWriteCallbacks(); - return NS_BASE_STREAM_WOULD_BLOCK; + if (mAttemptingEarlyData) { + // We can still try to send our preamble as early-data + *countRead = mOutputQueueUsed - mOutputQueueSent; + } + return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + uint32_t earlyDataUsed = 0; + if (mAttemptingEarlyData) { + if (!stream->Do0RTT()) { + LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X", + this, stream, stream->StreamID())); + FlushOutputQueue(); + SetWriteCallbacks(); + // We can still send our preamble + *countRead = mOutputQueueUsed - mOutputQueueSent; + return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (!m0RTTStreams.Contains(stream->StreamID())) { + m0RTTStreams.AppendElement(stream->StreamID()); + } + + // Need to adjust this to only take as much as we can fit in with the + // preamble/settings/priority stuff + count -= (mOutputQueueUsed - mOutputQueueSent); + + // Keep track of this to add it into countRead later, as + // stream->ReadSegments will likely change the value of mOutputQueueUsed. + earlyDataUsed = mOutputQueueUsed - mOutputQueueSent; } LOG3(("Http2Session %p will write from Http2Stream %p 0x%X " @@ -2337,6 +2391,13 @@ Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader, rv = stream->ReadSegments(this, count, countRead); + if (earlyDataUsed) { + // Do this here because countRead could get reset somewhere down the rabbit + // hole of stream->ReadSegments, and we want to make sure we return the + // proper value to our caller. + *countRead += earlyDataUsed; + } + // Not every permutation of stream->ReadSegents produces data (and therefore // tries to flush the output queue) - SENDING_FIN_STREAM can be an example // of that. But we might still have old data buffered that would be good @@ -2892,6 +2953,58 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, return WriteSegmentsAgain(writer, count, countWritten, &again); } +nsresult +Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) +{ + MOZ_ASSERT(mAttemptingEarlyData); + LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this, + aRestart, aAlpnChanged)); + + for (size_t i = 0; i < m0RTTStreams.Length(); ++i) { + // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for + // both arguments because as long as the alpn token stayed the same, we can + // just reuse what we have in our buffer to send instead of having to have + // the transaction rewind and read it all over again. We only need to rewind + // the transaction if we're switching to a new protocol, because our buffer + // won't get used in that case. + Http2Stream *stream = mStreamIDHash.Get(m0RTTStreams[i]); + if (stream) { + stream->Finish0RTT(aAlpnChanged, aAlpnChanged); + } + } + + if (aRestart) { + // 0RTT failed + if (aAlpnChanged) { + // This is a slightly more involved case - we need to get all our streams/ + // transactions back in the queue so they can restart as http/1 + + // These must be set this way to ensure we gracefully restart all streams + mGoAwayID = 0; + mCleanShutdown = true; + + // Close takes care of the rest of our work for us. The reason code here + // doesn't matter, as we aren't actually going to send a GOAWAY frame, but + // we use NS_ERROR_NET_RESET as it's closest to the truth. + Close(NS_ERROR_NET_RESET); + } else { + // This is the easy case - early data failed, but we're speaking h2, so + // we just need to rewind to the beginning of the preamble and try again. + mOutputQueueSent = 0; + } + } else { + // 0RTT succeeded + // Make sure we look for any incoming data in repsonse to our early data. + ResumeRecv(); + } + + mAttemptingEarlyData = false; + m0RTTStreams.Clear(); + RealignOutputQueue(); + + return NS_OK; +} + nsresult Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream, nsAHttpSegmentWriter * writer, @@ -3100,7 +3213,9 @@ Http2Session::Close(nsresult aReason) } else { goAwayReason = INTERNAL_ERROR; } - GenerateGoAway(goAwayReason); + if (!mAttemptingEarlyData) { + GenerateGoAway(goAwayReason); + } mConnection = nullptr; mSegmentReader = nullptr; mSegmentWriter = nullptr; @@ -3150,7 +3265,7 @@ Http2Session::OnReadSegment(const char *buf, // If we can release old queued data then we can try and write the new // data directly to the network without using the output queue at all - if (mOutputQueueUsed) + if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue(); if (!mOutputQueueUsed && mSegmentReader) { @@ -3521,12 +3636,18 @@ Http2Session::ALPNCallback(nsISupports *securityInfo) nsresult Http2Session::ConfirmTLSProfile() { - if (mTLSProfileConfirmed) + if (mTLSProfileConfirmed) { return NS_OK; + } LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this, mConnection.get())); + if (mAttemptingEarlyData) { + LOG3(("Http2Session::ConfirmTLSProfile %p temporarily passing due to early data\n", this)); + return NS_OK; + } + if (!gHttpHandler->EnforceHttp2TlsProfile()) { LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this)); mTLSProfileConfirmed = true; diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h index 60986381b..b4ddeb5ec 100644 --- a/netwerk/protocol/http/Http2Session.h +++ b/netwerk/protocol/http/Http2Session.h @@ -43,12 +43,13 @@ public: NS_DECL_NSAHTTPSEGMENTREADER NS_DECL_NSAHTTPSEGMENTWRITER - Http2Session(nsISocketTransport *, uint32_t version); + Http2Session(nsISocketTransport *, uint32_t version, bool attemptingEarlyData); bool AddStream(nsAHttpTransaction *, int32_t, bool, nsIInterfaceRequestor *) override; bool CanReuse() override { return !mShouldGoAway && !mClosed; } bool RoomForMoreStreams() override; + uint32_t SpdyVersion() override; // When the connection is active this is called up to once every 1 second // return the interval (in seconds) that the connection next wants to @@ -235,6 +236,8 @@ public: // overload of nsAHttpTransaction nsresult ReadSegmentsAgain(nsAHttpSegmentReader *, uint32_t, uint32_t *, bool *) override final; nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *, uint32_t , uint32_t *, bool *) override final; + bool Do0RTT() override final { return true; } + nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) override final; private: @@ -492,6 +495,10 @@ private: bool mUseH2Deps; + bool mAttemptingEarlyData; + // The ID(s) of the stream(s) that we are getting 0RTT data from. + nsTArray m0RTTStreams; + private: /// connect tunnels void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *); diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp index 5c562557c..f49c1f138 100644 --- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -70,6 +70,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, , mTotalSent(0) , mTotalRead(0) , mPushSource(nullptr) + , mAttempting0RTT(false) , mIsTunnel(false) , mPlainTextTunnel(false) { @@ -925,7 +926,9 @@ Http2Stream::TransmitFrame(const char *buf, *countUsed += mTxStreamFrameSize; } - mSession->FlushOutputQueue(); + if (!mAttempting0RTT) { + mSession->FlushOutputQueue(); + } // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); @@ -1468,5 +1471,26 @@ Http2Stream::MapStreamToHttpConnection() mTransaction->ConnectionInfo()); } +// ----------------------------------------------------------------------------- +// mirror nsAHttpTransaction +// ----------------------------------------------------------------------------- + +bool +Http2Stream::Do0RTT() +{ + MOZ_ASSERT(mTransaction); + mAttempting0RTT = true; + return mTransaction->Do0RTT(); +} + +nsresult +Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged) +{ + MOZ_ASSERT(mTransaction); + mAttempting0RTT = false; + return mTransaction->Finish0RTT(aRestart, aAlpnChanged); +} + + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h index 452db5fe0..968461ee4 100644 --- a/netwerk/protocol/http/Http2Stream.h +++ b/netwerk/protocol/http/Http2Stream.h @@ -154,6 +154,10 @@ public: const nsACString &origin, RefPtr &url); + // Mirrors nsAHttpTransaction + bool Do0RTT(); + nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored); + protected: static void CreatePushHashKey(const nsCString &scheme, const nsCString &hostHeader, @@ -328,6 +332,8 @@ private: // and flow control has not yet kicked in. SimpleBuffer mSimpleBuffer; + bool mAttempting0RTT; + /// connect tunnels public: bool IsTunnel() { return mIsTunnel; } diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index 7e42d191a..df998699a 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -216,8 +216,11 @@ public: // If aRestart parameter is true we need to restart the transaction, // otherwise the erly-data has been accepted and we can continue the // transaction. + // If aAlpnChanged is true (and we were assuming http/2), we'll need to take + // the transactions out of the session, rewind them all, and start them back + // over as http/1 transactions // The function will return success or failure of the transaction restart. - virtual nsresult Finish0RTT(bool aRestart) { + virtual nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) { return NS_ERROR_NOT_IMPLEMENTED; } }; diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index e80607d69..4676a3691 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -87,6 +87,7 @@ nsHttpConnection::nsHttpConnection() , mWaitingFor0RTTResponse(false) , mContentBytesWritten0RTT(0) , mEarlyDataNegotiated(false) + , mDid0RTTSpdy(false) { LOG(("Creating nsHttpConnection @%p\n", this)); @@ -158,16 +159,113 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info, return NS_OK; } +nsresult +nsHttpConnection::TryTakeSubTransactions(nsTArray > &list) +{ + nsresult rv = mTransaction->TakeSubTransactions(list); + + if (rv == NS_ERROR_ALREADY_OPENED) { + // Has the interface for TakeSubTransactions() changed? + LOG(("TakeSubTransactions somehow called after " + "nsAHttpTransaction began processing\n")); + MOZ_ASSERT(false, + "TakeSubTransactions somehow called after " + "nsAHttpTransaction began processing"); + mTransaction->Close(NS_ERROR_ABORT); + return rv; + } + + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + // Has the interface for TakeSubTransactions() changed? + LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()")); + MOZ_ASSERT(false, + "unexpected result from " + "nsAHttpTransaction::TakeSubTransactions()"); + mTransaction->Close(NS_ERROR_ABORT); + return rv; + } + + return rv; +} + +nsresult +nsHttpConnection::MoveTransactionsToSpdy(nsresult status, nsTArray > &list) +{ + if (NS_FAILED(status)) { // includes NS_ERROR_NOT_IMPLEMENTED + MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty"); + + // This is ok - treat mTransaction as a single real request. + // Wrap the old http transaction into the new spdy session + // as the first stream. + LOG(("nsHttpConnection::MoveTransactionsToSpdy moves single transaction %p " + "into SpdySession %p\n", mTransaction.get(), mSpdySession.get())); + nsresult rv = AddTransaction(mTransaction, mPriority); + if (NS_FAILED(rv)) { + return rv; + } + } else { + int32_t count = list.Length(); + + LOG(("nsHttpConnection::MoveTransactionsToSpdy moving transaction list len=%d " + "into SpdySession %p\n", count, mSpdySession.get())); + + if (!count) { + mTransaction->Close(NS_ERROR_ABORT); + return NS_ERROR_ABORT; + } + + for (int32_t index = 0; index < count; ++index) { + nsresult rv = AddTransaction(list[index], mPriority); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + return NS_OK; +} + +void +nsHttpConnection::Start0RTTSpdy(uint8_t spdyVersion) +{ + LOG(("nsHttpConnection::Start0RTTSpdy [this=%p]", this)); + mDid0RTTSpdy = true; + mUsingSpdyVersion = spdyVersion; + mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, + true); + + nsTArray > list; + nsresult rv = TryTakeSubTransactions(list); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed taking " + "subtransactions rv=%" PRIx32 , this, static_cast(rv))); + return; + } + + rv = MoveTransactionsToSpdy(rv, list); + if (NS_FAILED(rv)) { + LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed moving " + "transactions rv=%" PRIx32 , this, static_cast(rv))); + return; + } + + mTransaction = mSpdySession; +} + void nsHttpConnection::StartSpdy(uint8_t spdyVersion) { - LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this)); + LOG(("nsHttpConnection::StartSpdy [this=%p, mDid0RTTSpdy=%d]\n", this, mDid0RTTSpdy)); - MOZ_ASSERT(!mSpdySession); + MOZ_ASSERT(!mSpdySession || mDid0RTTSpdy); mUsingSpdyVersion = spdyVersion; mEverUsedSpdy = true; - mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport); + + if (!mDid0RTTSpdy) { + mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, + false); + } if (!mReportedSpdy) { mReportedSpdy = true; @@ -185,27 +283,13 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion) // pack them all into a new spdy session. nsTArray > list; - nsresult rv = mTransaction->TakeSubTransactions(list); - - if (rv == NS_ERROR_ALREADY_OPENED) { - // Has the interface for TakeSubTransactions() changed? - LOG(("TakeSubTransactions somehow called after " - "nsAHttpTransaction began processing\n")); - MOZ_ASSERT(false, - "TakeSubTransactions somehow called after " - "nsAHttpTransaction began processing"); - mTransaction->Close(NS_ERROR_ABORT); - return; - } + nsresult rv = NS_OK; + if (!mDid0RTTSpdy) { + rv = TryTakeSubTransactions(list); - if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { - // Has the interface for TakeSubTransactions() changed? - LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()")); - MOZ_ASSERT(false, - "unexpected result from " - "nsAHttpTransaction::TakeSubTransactions()"); - mTransaction->Close(NS_ERROR_ABORT); - return; + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + return; + } } if (NeedSpdyTunnel()) { @@ -227,35 +311,11 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion) mConnInfo = wildCardProxyCi; } - if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED - MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty"); - - // This is ok - treat mTransaction as a single real request. - // Wrap the old http transaction into the new spdy session - // as the first stream. - LOG(("nsHttpConnection::StartSpdy moves single transaction %p " - "into SpdySession %p\n", mTransaction.get(), mSpdySession.get())); - rv = AddTransaction(mTransaction, mPriority); + if (!mDid0RTTSpdy) { + rv = MoveTransactionsToSpdy(rv, list); if (NS_FAILED(rv)) { return; } - } else { - int32_t count = list.Length(); - - LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d " - "into SpdySession %p\n", count, mSpdySession.get())); - - if (!count) { - mTransaction->Close(NS_ERROR_ABORT); - return; - } - - for (int32_t index = 0; index < count; ++index) { - rv = AddTransaction(list[index], mPriority); - if (NS_FAILED(rv)) { - return; - } - } } // Disable TCP Keepalives - use SPDY ping instead. @@ -328,8 +388,7 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, // (AlpnEarlySelection), we are using HTTP/1, and the request data can // be safely retried. m0RTTChecked = true; - nsAutoCString earlyNegotiatedNPN; - nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN); + nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN); if (NS_FAILED(rvEarlyAlpn)) { // if ssl->DriveHandshake() has never been called the value // for AlpnEarlySelection is still not set. So call it here and @@ -346,7 +405,7 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, // Check NegotiatedNPN first. rv = ssl->GetNegotiatedNPN(negotiatedNPN); if (rv == NS_ERROR_NOT_CONNECTED) { - rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN); + rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN); } } @@ -356,19 +415,26 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, mEarlyDataNegotiated = false; } else { LOG(("nsHttpConnection::EnsureNPNComplete %p -" - "early selected alpn: %s", this, earlyNegotiatedNPN.get())); + "early selected alpn: %s", this, mEarlyNegotiatedALPN.get())); uint32_t infoIndex; const SpdyInformation *info = gHttpHandler->SpdyInfo(); - // We are doing 0RTT only with Http/1 right now! - if (NS_FAILED(info->GetNPNIndex(earlyNegotiatedNPN, &infoIndex))) { + if (NS_FAILED(info->GetNPNIndex(mEarlyNegotiatedALPN, &infoIndex))) { + // This is the HTTP/1 case. // Check if early-data is allowed for this transaction. if (mTransaction->Do0RTT()) { LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - We " - "can do 0RTT!", this)); + "can do 0RTT (http/1)!", this)); mWaitingFor0RTTResponse = true; } - mEarlyDataNegotiated = true; + } else { + // We have h2, we can at least 0-RTT the preamble and opening + // SETTINGS, etc, and maybe some of the first request + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - Starting " + "0RTT for h2!", this)); + mWaitingFor0RTTResponse = true; + Start0RTTSpdy(info->Version[infoIndex]); } + mEarlyDataNegotiated = true; } } @@ -398,16 +464,17 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, this, mConnInfo->HashKey().get(), negotiatedNPN.get(), mTLSFilter ? " [Double Tunnel]" : "")); - bool ealyDataAccepted = false; + bool earlyDataAccepted = false; if (mWaitingFor0RTTResponse) { // Check if early data has been accepted. - rv = ssl->GetEarlyDataAccepted(&ealyDataAccepted); + rv = ssl->GetEarlyDataAccepted(&earlyDataAccepted); LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data " - "that was sent during 0RTT %s been accepted.", - this, ealyDataAccepted ? "has" : "has not")); + "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].", + this, earlyDataAccepted ? "has" : "has not", static_cast(rv))); if (NS_FAILED(rv) || - NS_FAILED(mTransaction->Finish0RTT(!ealyDataAccepted))) { + NS_FAILED(mTransaction->Finish0RTT(!earlyDataAccepted, negotiatedNPN != mEarlyNegotiatedALPN))) { + LOG(("nsHttpConection::EnsureNPNComplete [this=%p] closing transaction %p", this, mTransaction.get())); mTransaction->Close(NS_ERROR_NET_RESET); goto npnComplete; } @@ -423,16 +490,17 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED)); if (mWaitingFor0RTTResponse) { Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED, - ealyDataAccepted); + earlyDataAccepted); } - if (ealyDataAccepted) { + if (earlyDataAccepted) { Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN, mContentBytesWritten0RTT); } } mWaitingFor0RTTResponse = false; - if (!ealyDataAccepted) { + if (!earlyDataAccepted) { + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] early data not accepted", this)); uint32_t infoIndex; const SpdyInformation *info = gHttpHandler->SpdyInfo(); if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) { @@ -442,13 +510,20 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %d bytes " "has been sent during 0RTT.", this, mContentBytesWritten0RTT)); mContentBytesWritten = mContentBytesWritten0RTT; + if (mSpdySession) { + // We had already started 0RTT-spdy, now we need to fully set up + // spdy, since we know we're sticking with it. + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - finishing " + "StartSpdy for 0rtt spdy session %p", this, mSpdySession.get())); + StartSpdy(mSpdySession->SpdyVersion()); + } } Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy()); } npnComplete: - LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true")); + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] setting complete to true", this)); mNPNComplete = true; mTransaction->OnTransportStatus(mSocketTransport, @@ -456,12 +531,25 @@ npnComplete: 0); if (mWaitingFor0RTTResponse) { + // Didn't get 0RTT OK, back out of the "attempting 0RTT" state mWaitingFor0RTTResponse = false; - if (NS_FAILED(mTransaction->Finish0RTT(true))) { + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] 0rtt failed", this)); + if (NS_FAILED(mTransaction->Finish0RTT(true, negotiatedNPN != mEarlyNegotiatedALPN))) { mTransaction->Close(NS_ERROR_NET_RESET); } mContentBytesWritten0RTT = 0; } + + if (mDid0RTTSpdy && negotiatedNPN != mEarlyNegotiatedALPN) { + // Reset the work done by Start0RTTSpdy + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] resetting Start0RTTSpdy", this)); + mUsingSpdyVersion = 0; + mTransaction = nullptr; + mSpdySession = nullptr; + // We have to reset this here, just in case we end up starting spdy again, + // so it can actually do everything it needs to do. + mDid0RTTSpdy = false; + } return true; } diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index 783b080b3..1f8500d75 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -248,6 +248,13 @@ private: // Start the Spdy transaction handler when NPN indicates spdy/* void StartSpdy(uint8_t versionLevel); + // Like the above, but do the bare minimum to do 0RTT data, so we can back + // it out, if necessary + void Start0RTTSpdy(uint8_t versionLevel); + + // Helpers for Start*Spdy + nsresult TryTakeSubTransactions(nsTArray > &list); + nsresult MoveTransactionsToSpdy(nsresult status, nsTArray > &list); // Directly Add a transaction to an active connection for SPDY nsresult AddTransaction(nsAHttpTransaction *, int32_t); @@ -370,6 +377,8 @@ private: // the handsake. int64_t mContentBytesWritten0RTT; bool mEarlyDataNegotiated; //Only used for telemetry + nsCString mEarlyNegotiatedALPN; + bool mDid0RTTSpdy; }; } // namespace net diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 16e8beee2..aad82e164 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -2489,7 +2489,7 @@ nsHttpTransaction::Do0RTT() } nsresult -nsHttpTransaction::Finish0RTT(bool aRestart) +nsHttpTransaction::Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) { MOZ_ASSERT(m0RTTInProgress); m0RTTInProgress = false; diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 18b2693ec..788d9c7b3 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -168,7 +168,7 @@ public: int64_t GetTransferSize() { return mTransferSize; } bool Do0RTT() override; - nsresult Finish0RTT(bool aRestart) override; + nsresult Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) override; private: friend class DeleteHttpTransaction; virtual ~nsHttpTransaction(); -- cgit v1.2.3