Bug 1264578 - Networking support for http with TLS 1.3. r=mcmanus
authorDragana Damjanovic <dd.mozilla@gmail.com>
Thu, 11 Aug 2016 08:28:00 -0400
changeset 352013 16bcc176a1439cc6ba5b70cd62d0d049243c41b8
parent 352012 c45edf68dc4c20ba9eb4f76820d2c410b9caa2f5
child 352014 a1fcbe1fc10c17fb0b7c6e31a46ffc0d8f7f18ef
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs1264578
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1264578 - Networking support for http with TLS 1.3. r=mcmanus
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -199,16 +199,32 @@ public:
     // authoritative.
     virtual nsresult GetTransactionSecurityInfo(nsISupports **)
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     virtual void DisableSpdy() { }
     virtual void ReuseConnectionOnRestartOK(bool) { }
+
+    // Returns true if early-data is possible.
+    virtual bool Do0RTT() {
+        return false;
+    }
+    // This function will be called when a tls handshake has been finished and
+    // we know whether early-data that was sent has been accepted or not, e.g.
+    // do we need to restart a transaction. This will be called only if Do0RTT
+    // returns true.
+    // If aRestart parameter is true we need to restart the transaction,
+    // otherwise the erly-data has been accepted and we can continue the
+    // transaction.
+    // The function will return success or failure of the transaction restart.
+    virtual nsresult Finish0RTT(bool aRestart) {
+        return NS_ERROR_NOT_IMPLEMENTED;
+    }
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpTransaction, NS_AHTTPTRANSACTION_IID)
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *) override; \
     nsAHttpConnection *Connection() override; \
     void GetSecurityCallbacks(nsIInterfaceRequestor **) override;       \
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -74,16 +74,19 @@ nsHttpConnection::nsHttpConnection()
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
     , mEverUsedSpdy(false)
     , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
     , mTransactionCaps(0)
     , mResponseTimeoutEnabled(false)
     , mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
     , mForceSendPending(false)
+    , m0RTTChecked(false)
+    , mWaitingFor0RTTResponse(false)
+    , mContentBytesWritten0RTT(0)
 {
     LOG(("Creating nsHttpConnection @%p\n", this));
 
     // the default timeout is for when this connection has not yet processed a
     // transaction
     static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
     mIdleTimeout =
         (k5Sec < gHttpHandler->IdleTimeout()) ? k5Sec : gHttpHandler->IdleTimeout();
@@ -266,21 +269,25 @@ nsHttpConnection::StartSpdy(uint8_t spdy
         mTLSFilter->SetProxiedTransaction(mSpdySession);
     }
     if (mDontReuse) {
         mSpdySession->DontReuse();
     }
 }
 
 bool
-nsHttpConnection::EnsureNPNComplete()
+nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
+                                    uint32_t &aOut0RTTBytesWritten)
 {
     // If for some reason the components to check on NPN aren't available,
     // this function will just return true to continue on and disable SPDY
 
+    aOut0RTTWriteHandshakeValue = NS_OK;
+    aOut0RTTBytesWritten = 0;
+
     MOZ_ASSERT(mSocketTransport);
     if (!mSocketTransport) {
         // this cannot happen
         mNPNComplete = true;
         return true;
     }
 
     if (mNPNComplete) {
@@ -297,45 +304,130 @@ nsHttpConnection::EnsureNPNComplete()
         goto npnComplete;
     }
 
     ssl = do_QueryInterface(securityInfo, &rv);
     if (NS_FAILED(rv))
         goto npnComplete;
 
     rv = ssl->GetNegotiatedNPN(negotiatedNPN);
+    if (!m0RTTChecked && (rv == NS_ERROR_NOT_CONNECTED) &&
+        !mConnInfo->UsingProxy()) {
+        // There is no ALPN info (yet!). We need to consider doing 0RTT. We
+        // will do so if there is ALPN information from a previous session
+        // (AlpnEarlySelection), we are using HTTP/1, and the request data can
+        // be safely retried.
+        m0RTTChecked = true;
+        nsAutoCString earlyNegotiatedNPN;
+        nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+        if (NS_FAILED(rvEarlyAlpn)) {
+            // if ssl->DriveHandshake() has never been called the value
+            // for AlpnEarlySelection is still not set. So call it here and
+            // check again.
+            LOG(("nsHttpConnection::EnsureNPNComplete %p - "
+                 "early selected alpn not available, we will try one more time.",
+                 this));
+            // Let's do DriveHandshake again.
+            rv = ssl->DriveHandshake();
+            if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+                goto npnComplete;
+            }
+
+            // Check NegotiatedNPN first.
+            rv = ssl->GetNegotiatedNPN(negotiatedNPN);
+            if (rv == NS_ERROR_NOT_CONNECTED) {
+                rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN);
+            }
+        }
+
+        if (NS_FAILED(rvEarlyAlpn)) {
+            LOG(("nsHttpConnection::EnsureNPNComplete %p - "
+                 "early selected alpn not available", this));
+        } else {
+            LOG(("nsHttpConnection::EnsureNPNComplete %p -"
+                 "early selected alpn: %s", this, earlyNegotiatedNPN.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))) {
+                // Check if early-data is allowed for this transaction.
+                if (mTransaction->Do0RTT()) {
+                    LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - We "
+                         "can do 0RTT!", this));
+                    mWaitingFor0RTTResponse = true;
+                }
+            }
+        }
+    }
+
     if (rv == NS_ERROR_NOT_CONNECTED) {
-        // By writing 0 bytes to the socket the SSL handshake machine is
-        // pushed forward.
-        uint32_t count = 0;
-        rv = mSocketOut->Write("", 0, &count);
+        if (mWaitingFor0RTTResponse) {
+            aOut0RTTWriteHandshakeValue = mTransaction->ReadSegments(this,
+                nsIOService::gDefaultSegmentSize, &aOut0RTTBytesWritten);
+            if (NS_FAILED(aOut0RTTWriteHandshakeValue) &&
+                aOut0RTTWriteHandshakeValue != NS_BASE_STREAM_WOULD_BLOCK) {
+                goto npnComplete;
+            }
+            LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - written %d "
+                 "bytes during 0RTT", this, aOut0RTTBytesWritten));
+            mContentBytesWritten0RTT += aOut0RTTBytesWritten;
+        }
+
+        rv = ssl->DriveHandshake();
         if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
             goto npnComplete;
         }
 
         return false;
     }
 
     if (NS_SUCCEEDED(rv)) {
         LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
              this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
              mTLSFilter ? " [Double Tunnel]" : ""));
 
-        uint32_t infoIndex;
-        const SpdyInformation *info = gHttpHandler->SpdyInfo();
-        if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
-            StartSpdy(info->Version[infoIndex]);
+        bool ealyDataAccepted = false;
+        if (mWaitingFor0RTTResponse) {
+            mWaitingFor0RTTResponse = false;
+            // Check if early data has been accepted.
+            rv = ssl->GetEarlyDataAccepted(&ealyDataAccepted);
+            LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data "
+                 "that was sent during 0RTT %s been accepted.",
+                 this, ealyDataAccepted ? "has" : "has not"));
+            if (NS_FAILED(rv) ||
+                NS_FAILED(mTransaction->Finish0RTT(!ealyDataAccepted))) {
+                mTransaction->Close(NS_ERROR_NET_RESET);
+                goto npnComplete;
+            }
+        }
+        if (!ealyDataAccepted) {
+            uint32_t infoIndex;
+            const SpdyInformation *info = gHttpHandler->SpdyInfo();
+            if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
+                StartSpdy(info->Version[infoIndex]);
+            }
+        } else {
+          LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %d bytes "
+               "has been sent during 0RTT.", this, mContentBytesWritten0RTT));
+          mContentBytesWritten = mContentBytesWritten0RTT;
         }
 
         Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
     }
 
 npnComplete:
     LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
     mNPNComplete = true;
+    if (mWaitingFor0RTTResponse) {
+        mWaitingFor0RTTResponse = false;
+        if (NS_FAILED(mTransaction->Finish0RTT(true))) {
+            mTransaction->Close(NS_ERROR_NET_RESET);
+        }
+        mContentBytesWritten0RTT = 0;
+    }
     return true;
 }
 
 void
 nsHttpConnection::OnTunnelNudged(TLSFilterTransaction *trans)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
@@ -1581,27 +1673,32 @@ nsHttpConnection::OnSocketWritable()
         rv = mSocketOutCondition = NS_OK;
         transactionBytes = 0;
 
         // The SSL handshake must be completed before the transaction->readsegments()
         // processing can proceed because we need to know how to format the
         // request differently for http/1, http/2, spdy, etc.. and that is
         // negotiated with NPN/ALPN in the SSL handshake.
 
-        if (mConnInfo->UsingHttpsProxy() && !EnsureNPNComplete()) {
+        if (mConnInfo->UsingHttpsProxy() &&
+            !EnsureNPNComplete(rv, transactionBytes)) {
+            MOZ_ASSERT(!transactionBytes);
             mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
         } else if (mProxyConnectStream) {
             // If we're need an HTTP/1 CONNECT tunnel through a proxy
             // send it before doing the SSL handshake
             LOG(("  writing CONNECT request stream\n"));
             rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
                                                    nsIOService::gDefaultSegmentSize,
                                                    &transactionBytes);
-        } else if (!EnsureNPNComplete()) {
-            mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+        } else if (!EnsureNPNComplete(rv, transactionBytes)) {
+            if (NS_SUCCEEDED(rv) && !transactionBytes &&
+                NS_SUCCEEDED(mSocketOutCondition)) {
+                mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+            }
         } else {
 
             // for non spdy sessions let the connection manager know
             if (!mReportedSpdy) {
                 mReportedSpdy = true;
                 MOZ_ASSERT(!mEverUsedSpdy);
                 gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false);
             }
@@ -1639,17 +1736,17 @@ nsHttpConnection::OnSocketWritable()
                 }
             } else {
                 rv = mSocketOutCondition;
             }
             again = false;
         } else if (!transactionBytes) {
             rv = NS_OK;
 
-            if (mTransaction) { // in case the ReadSegments stack called CloseTransaction()
+            if (mTransaction && !mWaitingFor0RTTResponse) { // in case the ReadSegments stack called CloseTransaction()
                 //
                 // at this point we've written out the entire transaction, and now we
                 // must wait for the server's response.  we manufacture a status message
                 // here to reflect the fact that we are waiting.  this message will be
                 // trumped (overwritten) if the server responds quickly.
                 //
                 mTransaction->OnTransportStatus(mSocketTransport,
                                                 NS_NET_STATUS_WAITING_FOR,
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -237,17 +237,18 @@ private:
     nsresult SetupProxyConnect();
 
     PRIntervalTime IdleTime();
     bool     IsAlive();
     bool     SupportsPipelining(nsHttpResponseHead *);
 
     // Makes certain the SSL handshake is complete and NPN negotiation
     // has had a chance to happen
-    bool     EnsureNPNComplete();
+    bool     EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue,
+                               uint32_t &aOut0RTTBytesWritten);
     void     SetupSSL();
 
     // Start the Spdy transaction handler when NPN indicates spdy/*
     void     StartSpdy(uint8_t versionLevel);
 
     // Directly Add a transaction to an active connection for SPDY
     nsresult AddTransaction(nsAHttpTransaction *, int32_t);
 
@@ -327,17 +328,17 @@ private:
 
     // SPDY related
     bool                            mNPNComplete;
     bool                            mSetupSSLCalled;
 
     // version level in use, 0 if unused
     uint8_t                         mUsingSpdyVersion;
 
-    RefPtr<ASpdySession>          mSpdySession;
+    RefPtr<ASpdySession>            mSpdySession;
     int32_t                         mPriority;
     bool                            mReportedSpdy;
 
     // mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
     bool                            mEverUsedSpdy;
 
     // mLastHttpResponseVersion stores the last response's http version seen.
     uint8_t                         mLastHttpResponseVersion;
@@ -352,14 +353,25 @@ private:
     nsCOMPtr<nsITimer>              mTCPKeepaliveTransitionTimer;
 
 private:
     // For ForceSend()
     static void                     ForceSendIO(nsITimer *aTimer, void *aClosure);
     nsresult                        MaybeForceSendIO();
     bool                            mForceSendPending;
     nsCOMPtr<nsITimer>              mForceSendTimer;
+
+    // Helper variable for 0RTT handshake;
+    bool                            m0RTTChecked; // Possible 0RTT has been
+                                                  // checked.
+    bool                            mWaitingFor0RTTResponse; // We have are
+                                                             // sending 0RTT
+                                                             // data and we
+                                                             // are waiting
+                                                             // for the end of
+                                                             // the handsake.
+    int64_t                        mContentBytesWritten0RTT;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -134,16 +134,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mSubmittedRatePacing(false)
     , mPassedRatePacing(false)
     , mSynchronousRatePaceRequest(false)
     , mCountRecv(0)
     , mCountSent(0)
     , mAppId(NECKO_NO_APP_ID)
     , mIsInIsolatedMozBrowser(false)
     , mClassOfService(0)
+    , m0RTTInProgress(false)
 {
     LOG(("Creating nsHttpTransaction @%p\n", this));
     gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
 
 #ifdef MOZ_VALGRIND
     memset(&mSelfAddr, 0, sizeof(NetAddr));
     memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
@@ -687,17 +688,17 @@ nsHttpTransaction::ReadSegments(nsAHttpS
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     if (mTransactionDone) {
         *countRead = 0;
         return mStatus;
     }
 
-    if (!mConnected) {
+    if (!mConnected && !m0RTTInProgress) {
         mConnected = true;
         mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
     }
 
     mDeferredSendProgress = false;
     mReader = reader;
     nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
     mReader = nullptr;
@@ -2315,10 +2316,38 @@ nsHttpTransaction::RestartVerifier::Set(
 void
 nsHttpTransaction::GetNetworkAddresses(NetAddr &self, NetAddr &peer)
 {
     MutexAutoLock lock(mLock);
     self = mSelfAddr;
     peer = mPeerAddr;
 }
 
+bool
+nsHttpTransaction::Do0RTT()
+{
+   if (mRequestHead->IsSafeMethod() &&
+       !mConnection->IsProxyConnectInProgress()) {
+     m0RTTInProgress = true;
+   }
+   return m0RTTInProgress;
+}
+
+nsresult
+nsHttpTransaction::Finish0RTT(bool aRestart)
+{
+    MOZ_ASSERT(m0RTTInProgress);
+    m0RTTInProgress = false;
+    if (aRestart) {
+        // Reset request headers to be sent again.
+        nsCOMPtr<nsISeekableStream> seekable =
+            do_QueryInterface(mRequestStream);
+        if (seekable) {
+            seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+        } else {
+            return NS_ERROR_FAILURE;
+        }
+    }
+    return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -161,16 +161,18 @@ public:
     mozilla::TimeStamp GetConnectStart();
     mozilla::TimeStamp GetConnectEnd();
     mozilla::TimeStamp GetRequestStart();
     mozilla::TimeStamp GetResponseStart();
     mozilla::TimeStamp GetResponseEnd();
 
     int64_t GetTransferSize() { return mTransferSize; }
 
+    bool Do0RTT() override;
+    nsresult Finish0RTT(bool aRestart) override;
 private:
     friend class DeleteHttpTransaction;
     virtual ~nsHttpTransaction();
 
     nsresult Restart();
     nsresult RestartInProgress();
     char    *LocateHttpStart(char *buf, uint32_t len,
                              bool aAllowPartialMatch);
@@ -455,14 +457,16 @@ private:
     RefPtr<ASpdySession> mTunnelProvider;
 
 public:
     void GetNetworkAddresses(NetAddr &self, NetAddr &peer);
 
 private:
     NetAddr                         mSelfAddr;
     NetAddr                         mPeerAddr;
+
+    bool                            m0RTTInProgress;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpTransaction_h__