diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp index a5ffb6d5b0cd..91ebee042950 100644 --- a/netwerk/protocol/http/Http2Compression.cpp +++ b/netwerk/protocol/http/Http2Compression.cpp @@ -898,7 +898,7 @@ nsresult Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput, const nsACString &method, const nsACString &path, const nsACString &host, const nsACString &scheme, - nsACString &output) + bool connectForm, nsACString &output) { mAlternateReferenceSet.Clear(); mImpliedReferenceSet.Clear(); @@ -917,10 +917,15 @@ Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput, } // colon headers first - ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false); - ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), false); - ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false); - ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false); + if (!connectForm) { + ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false); + ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), false); + ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false); + ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false); + } else { + ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false); + ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false); + } // now the non colon headers const char *beginBuffer = nvInput.BeginReading(); diff --git a/netwerk/protocol/http/Http2Compression.h b/netwerk/protocol/http/Http2Compression.h index 7081a1126e75..cbe2ce9b2bb2 100644 --- a/netwerk/protocol/http/Http2Compression.h +++ b/netwerk/protocol/http/Http2Compression.h @@ -164,7 +164,7 @@ public: nsresult EncodeHeaderBlock(const nsCString &nvInput, const nsACString &method, const nsACString &path, const nsACString &host, const nsACString &scheme, - nsACString &output); + bool connectForm, nsACString &output); int64_t GetParsedContentLength() { return mParsedContentLength; } // -1 on not found diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 8299b97801c0..ea5fd0bdbc83 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -386,23 +386,23 @@ Http2Session::AddStream(nsAHttpTransaction *aHttpTransaction, return false; } - // assert that - // a] in the case we have a connection, that the new transaction connection - // is either undefined or on the same connection - // b] in the case we don't have a connection, that the new transaction - // connection is defined so we can adopt it - MOZ_ASSERT((mConnection && (!aHttpTransaction->Connection() || - mConnection == aHttpTransaction->Connection())) || - (!mConnection && aHttpTransaction->Connection())); - if (!mConnection) { mConnection = aHttpTransaction->Connection(); } + aHttpTransaction->SetConnection(this); + + if (aUseTunnel) { + LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel", + this, aHttpTransaction)); + DispatchOnTunnel(aHttpTransaction, aCallbacks); + return true; + } + Http2Stream *stream = new Http2Stream(aHttpTransaction, this, aPriority); - LOG3(("Http2Session::AddStream session=%p stream=%p NextID=0x%X (tentative)", - this, stream, mNextStreamID)); + LOG3(("Http2Session::AddStream session=%p stream=%p serial=%u " + "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID)); mStreamTransactionHash.Put(aHttpTransaction, stream); @@ -991,6 +991,10 @@ Http2Session::CloseStream(Http2Stream *aStream, nsresult aResult) RemoveStreamFromQueues(aStream); + if (aStream->IsTunnel()) { + UnRegisterTunnel(aStream); + } + // Send the stream the close() indication aStream->Close(aResult); } @@ -1138,7 +1142,11 @@ Http2Session::ResponseHeadersComplete() // only do this once, afterwards ignore trailers if (mInputFrameDataStream->AllHeadersReceived()) return NS_OK; - mInputFrameDataStream->SetAllHeadersReceived(true); + nsresult rv = mInputFrameDataStream->SetAllHeadersReceived(true); + + if (NS_FAILED(rv)) { + return rv; + } // The stream needs to see flattened http headers // Uncompressed http/2 format headers currently live in @@ -1146,11 +1154,23 @@ Http2Session::ResponseHeadersComplete() // mFlatHTTPResponseHeaders via ConvertHeaders() mFlatHTTPResponseHeadersOut = 0; - nsresult rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor, - mDecompressBuffer, - mFlatHTTPResponseHeaders); - if (NS_FAILED(rv)) + rv = mInputFrameDataStream->ConvertResponseHeaders(&mDecompressor, + mDecompressBuffer, + mFlatHTTPResponseHeaders); + if (rv == NS_ERROR_ABORT) { + LOG(("Http2Session::ResponseHeadersComplete ConvertResponseHeaders aborted\n")); + if (mInputFrameDataStream->IsTunnel()) { + gHttpHandler->ConnMgr()->CancelTransactions( + mInputFrameDataStream->Transaction()->ConnectionInfo(), + NS_ERROR_CONNECTION_REFUSED); + } + CleanupStream(mInputFrameDataStream, rv, CANCEL_ERROR); + ResetDownstreamState(); + return NS_OK; + } + else if (NS_FAILED(rv)) { return rv; + } ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); return NS_OK; @@ -2278,6 +2298,7 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly"); mNeedsCleanup = nullptr; /* just in case */ + Http2Stream *stream = mInputFrameDataStream; mSegmentWriter = writer; rv = mInputFrameDataStream->WriteSegments(this, count, countWritten); mSegmentWriter = nullptr; @@ -2288,7 +2309,6 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, // This will happen when the transaction figures out it is EOF, generally // due to a content-length match being made. Return OK from this function // otherwise the whole session would be torn down. - Http2Stream *stream = mInputFrameDataStream; // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state // back to PROCESSING_DATA_FRAME where we came from @@ -2302,8 +2322,8 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, this, stream, stream ? stream->StreamID() : 0, mNeedsCleanup, rv)); CleanupStream(stream, NS_OK, CANCEL_ERROR); - MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame"); - mNeedsCleanup = nullptr; /* just in case */ + MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup == stream); + mNeedsCleanup = nullptr; return NS_OK; } @@ -2794,6 +2814,80 @@ Http2Session::ConnectPushedStream(Http2Stream *stream) ForceRecv(); } +uint32_t +Http2Session::FindTunnelCount(nsHttpConnectionInfo *aConnInfo) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + uint32_t rv = 0; + mTunnelHash.Get(aConnInfo->HashKey(), &rv); + return rv; +} + +void +Http2Session::RegisterTunnel(Http2Stream *aTunnel) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo(); + uint32_t newcount = FindTunnelCount(ci) + 1; + mTunnelHash.Remove(ci->HashKey()); + mTunnelHash.Put(ci->HashKey(), newcount); + LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]", + this, aTunnel, newcount, ci->HashKey().get())); +} + +void +Http2Session::UnRegisterTunnel(Http2Stream *aTunnel) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo(); + MOZ_ASSERT(FindTunnelCount(ci)); + uint32_t newcount = FindTunnelCount(ci) - 1; + mTunnelHash.Remove(ci->HashKey()); + if (newcount) { + mTunnelHash.Put(ci->HashKey(), newcount); + } + LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]", + this, aTunnel, newcount, ci->HashKey().get())); +} + +void +Http2Session::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction, + nsIInterfaceRequestor *aCallbacks) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction(); + nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo(); + MOZ_ASSERT(trans); + + LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans)); + + aHttpTransaction->SetConnection(nullptr); + + // this transaction has done its work of setting up a tunnel, let + // the connection manager queue it if necessary + trans->SetDontRouteViaWildCard(true); + + if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) { + LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s", + this, ci->HashKey().get())); + nsRefPtr connectTrans = + new SpdyConnectTransaction(ci, aCallbacks, + trans->Caps(), trans, this); + AddStream(connectTrans, trans->Priority(), + false, nullptr); + Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans); + MOZ_ASSERT(tunnel); + RegisterTunnel(tunnel); + } + + // requeue it. The connection manager is responsible for actually putting + // this on the tunnel connection with the specific ci now that it + // has DontRouteViaWildCard set. + trans->EnableKeepAlive(); + gHttpHandler->InitiateTransaction(trans, trans->Priority()); +} + + nsresult Http2Session::BufferOutput(const char *buf, uint32_t count, @@ -2894,6 +2988,12 @@ Http2Session::TransactionHasDataToWrite(nsAHttpTransaction *caller) mReadyForWrite.Push(stream); SetWriteCallbacks(); + + // NSPR poll will not poll the network if there are non system PR_FileDesc's + // that are ready - so we can get into a deadlock waiting for the system IO + // to come back here if we don't force the send loop manually. + ForceSend(); + } void @@ -2905,6 +3005,7 @@ Http2Session::TransactionHasDataToWrite(Http2Stream *stream) mReadyForWrite.Push(stream); SetWriteCallbacks(); + ForceSend(); } bool diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h index dc58f50c3008..d9fa5960d49d 100644 --- a/netwerk/protocol/http/Http2Session.h +++ b/netwerk/protocol/http/Http2Session.h @@ -25,6 +25,7 @@ namespace net { class Http2PushedStream; class Http2Stream; +class nsHttpTransaction; class Http2Session MOZ_FINAL : public ASpdySession , public nsAHttpConnection @@ -445,6 +446,15 @@ private: // by the load group and the serial number can be used as part of the cache key // to make sure streams aren't shared across sessions. uint64_t mSerial; + +private: +/// connect tunnels + void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *); + void RegisterTunnel(Http2Stream *); + void UnRegisterTunnel(Http2Stream *); + uint32_t FindTunnelCount(nsHttpConnectionInfo *); + + nsDataHashtable mTunnelHash; }; } // namespace mozilla::net diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp index 1f6c443ae5bf..ce16f67ad96d 100644 --- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -19,6 +19,7 @@ #include "Http2Session.h" #include "Http2Stream.h" #include "Http2Push.h" +#include "TunnelUtils.h" #include "mozilla/Telemetry.h" #include "nsAlgorithm.h" @@ -67,6 +68,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, , mTotalSent(0) , mTotalRead(0) , mPushSource(nullptr) + , mIsTunnel(false) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); @@ -96,6 +98,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, Http2Stream::~Http2Stream() { + ClearTransactionsBlockedOnTunnel(); mStreamID = Http2Session::kDeadStreamID; } @@ -141,6 +144,9 @@ Http2Stream::ReadSegments(nsAHttpSegmentReader *reader, rv = mTransaction->ReadSegments(this, count, countRead); mSegmentReader = nullptr; + LOG3(("Http2Stream::ReadSegments %p trans readsegments rv %x read=%d\n", + this, rv, *countRead)); + // Check to see if the transaction's request could be written out now. // If not, mark the stream for callback when writing can proceed. if (NS_SUCCEEDED(rv) && @@ -165,7 +171,7 @@ Http2Stream::ReadSegments(nsAHttpSegmentReader *reader, if (!mBlockedOnRwin && !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, " - "mUpstreamState=%x",this, mStreamID, mUpstreamState)); + "mUpstreamState=%x\n",this, mStreamID, mUpstreamState)); if (mSentFin) { ChangeState(UPSTREAM_COMPLETE); } else { @@ -268,6 +274,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, this, avail, mUpstreamState)); mFlatHttpRequestHeaders.Append(buf, avail); + nsHttpRequestHead *head = mTransaction->RequestHead(); // We can use the simple double crlf because firefox is the // only client we are parsing @@ -290,17 +297,17 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, *countUsed = avail - (oldLen - endHeader) + 4; mAllHeadersSent = 1; - nsAutoCString hostHeader; + nsAutoCString authorityHeader; nsAutoCString hashkey; - mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); + head->GetHeader(nsHttp::Host, authorityHeader); - CreatePushHashKey(NS_LITERAL_CSTRING("https"), - hostHeader, mSession->Serial(), - mTransaction->RequestHead()->RequestURI(), + CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"), + authorityHeader, mSession->Serial(), + head->RequestURI(), mOrigin, hashkey); // check the push cache for GET - if (mTransaction->RequestHead()->IsGet()) { + if (head->IsGet()) { // from :scheme, :authority, :path nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); SpdyPushCache *cache = nullptr; @@ -347,7 +354,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd"); LOG3(("Stream ID 0x%X [session=%p] for URI %s\n", mStreamID, mSession, - nsCString(mTransaction->RequestHead()->RequestURI()).get())); + nsCString(head->RequestURI()).get())); if (mStreamID >= 0x80000000) { // streamID must fit in 31 bits. Evading This is theoretically possible @@ -366,28 +373,46 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, // of HTTP/2 headers by writing to mTxInlineFrame{sz} nsCString compressedData; + nsDependentCString scheme(head->IsHTTPS() ? "https" : "http"); + if (head->IsConnect()) { + MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction()); + mIsTunnel = true; + mRequestBodyLenRemaining = 0x0fffffffffffffffULL; + + // Our normal authority has an implicit port, best to use an + // explicit one with a tunnel + nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo(); + if (!ci) { + return NS_ERROR_UNEXPECTED; + } + authorityHeader = ci->GetHost(); + authorityHeader.AppendLiteral(":"); + authorityHeader.AppendInt(ci->Port()); + } + mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, - mTransaction->RequestHead()->Method(), - mTransaction->RequestHead()->RequestURI(), - hostHeader, - NS_LITERAL_CSTRING("https"), + head->Method(), + head->RequestURI(), + authorityHeader, + scheme, + head->IsConnect(), compressedData); // Determine whether to put the fin bit on the header frame or whether // to wait for a data packet to put it on. uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY; - if (mTransaction->RequestHead()->IsGet() || - mTransaction->RequestHead()->IsConnect() || - mTransaction->RequestHead()->IsHead()) { - // for GET, CONNECT, and HEAD place the fin bit right on the + if (head->IsGet() || + head->IsHead()) { + // for GET and HEAD place the fin bit right on the // header packet SetSentFin(true); firstFrameFlags |= Http2Session::kFlag_END_STREAM; - } else if (mTransaction->RequestHead()->IsPost() || - mTransaction->RequestHead()->IsPut() || - mTransaction->RequestHead()->IsOptions()) { + } else if (head->IsPost() || + head->IsPut() || + head->IsConnect() || + head->IsOptions()) { // place fin in a data frame even for 0 length messages for iterop } else if (!mRequestBodyLenRemaining) { // for other HTTP extension methods, rely on the content-length @@ -473,7 +498,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, // The size of the input headers is approximate uint32_t ratio = compressedData.Length() * 100 / - (11 + mTransaction->RequestHead()->RequestURI().Length() + + (11 + head->RequestURI().Length() + mFlatHttpRequestHeaders.Length()); const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); @@ -599,6 +624,10 @@ void Http2Stream::UpdateTransportReadEvents(uint32_t count) { mTotalRead += count; + if (!mSocketTransport) { + return; + } + mTransaction->OnTransportStatus(mSocketTransport, NS_NET_STATUS_RECEIVING_FROM, mTotalRead); @@ -831,6 +860,14 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor, return NS_ERROR_ILLEGAL_VALUE; } + if (mIsTunnel) { + nsresult errcode; + if (status.ToInteger(&errcode) != 200) { + LOG3(("Http2Stream %p Tunnel not 200", this)); + return NS_ERROR_ABORT; + } + } + if (aHeadersIn.Length() && aHeadersOut.Length()) { Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length()); uint32_t ratio = @@ -841,6 +878,11 @@ Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor, aHeadersIn.Truncate(); aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n")); LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading())); + if (mIsTunnel) { + aHeadersOut.Truncate(); + LOG(("SpdyStream3::ConvertHeaders %p 0x%X headers removed for tunnel\n", + this, mStreamID)); + } return NS_OK; } @@ -891,6 +933,13 @@ Http2Stream::Close(nsresult reason) mTransaction->Close(reason); } +nsresult +Http2Stream::SetAllHeadersReceived(bool aStatus) +{ + mAllHeadersReceived = aStatus ? 1 : 0; + return NS_OK; +} + bool Http2Stream::AllowFlowControlledWrite() { @@ -1057,11 +1106,15 @@ Http2Stream::OnReadSegment(const char *buf, mSession->DecrementServerSessionWindow(dataLength); mServerReceiveWindow -= dataLength; - LOG3(("Http2Stream %p id %x request len remaining %d, " - "count avail %d, chunk used %d", + LOG3(("Http2Stream %p id %x request len remaining %u, " + "count avail %u, chunk used %u", this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); - if (dataLength > mRequestBodyLenRemaining) + if (!dataLength && mRequestBodyLenRemaining) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + if (dataLength > mRequestBodyLenRemaining) { return NS_ERROR_UNEXPECTED; + } mRequestBodyLenRemaining -= dataLength; GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); ChangeState(SENDING_BODY); @@ -1127,6 +1180,28 @@ Http2Stream::OnWriteSegment(char *buf, return NS_OK; } +/// connect tunnels + +void +Http2Stream::ClearTransactionsBlockedOnTunnel() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (!mIsTunnel) { + return; + } + gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo()); +} + +void +Http2Stream::MapStreamToHttpConnection() +{ + nsRefPtr qiTrans(mTransaction->QuerySpdyConnectTransaction()); + MOZ_ASSERT(qiTrans); + qiTrans->MapStreamToHttpConnection(mSocketTransport, + mTransaction->ConnectionInfo()); +} + } // namespace mozilla::net } // namespace mozilla diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h index f26a179067af..20139ab3c0de 100644 --- a/netwerk/protocol/http/Http2Stream.h +++ b/netwerk/protocol/http/Http2Stream.h @@ -81,12 +81,15 @@ public: void SetCountAsActive(bool aStatus) { mCountAsActive = aStatus ? 1 : 0; } bool CountAsActive() { return mCountAsActive; } - void SetAllHeadersReceived(bool aStatus) { mAllHeadersReceived = aStatus ? 1 : 0; } + // returns failure if stream cannot be made ready and stream + // should be canceled + nsresult SetAllHeadersReceived(bool aStatus); bool AllHeadersReceived() { return mAllHeadersReceived; } void UpdateTransportSendEvents(uint32_t count); void UpdateTransportReadEvents(uint32_t count); + // NS_ERROR_ABORT terminates stream, other failure terminates session nsresult ConvertResponseHeaders(Http2Decompressor *, nsACString &, nsACString &); nsresult ConvertPushHeaders(Http2Decompressor *, nsACString &, nsACString &); @@ -234,7 +237,8 @@ private: // place the fin flag on the last data packet instead of waiting // for a stream closed indication. Relying on stream close results // in an extra 0-length runt packet and seems to have some interop - // problems with the google servers. + // problems with the google servers. Connect does rely on stream + // close by setting this to the max value. int64_t mRequestBodyLenRemaining; uint32_t mPriority; @@ -267,6 +271,15 @@ private: // For Http2Push Http2PushedStream *mPushSource; + +/// connect tunnels +public: + bool IsTunnel() { return mIsTunnel; } +private: + void ClearTransactionsBlockedOnTunnel(); + void MapStreamToHttpConnection(); + + bool mIsTunnel; }; } // namespace mozilla::net diff --git a/netwerk/protocol/http/SpdySession31.cpp b/netwerk/protocol/http/SpdySession31.cpp index 960078cc3052..35f15d7b6246 100644 --- a/netwerk/protocol/http/SpdySession31.cpp +++ b/netwerk/protocol/http/SpdySession31.cpp @@ -351,23 +351,23 @@ SpdySession31::AddStream(nsAHttpTransaction *aHttpTransaction, return false; } - // assert that - // a] in the case we have a connection, that the new transaction connection - // is either undefined or on the same connection - // b] in the case we don't have a connection, that the new transaction - // connection is defined so we can adopt it - MOZ_ASSERT((mConnection && (!aHttpTransaction->Connection() || - mConnection == aHttpTransaction->Connection())) || - (!mConnection && aHttpTransaction->Connection())); - if (!mConnection) { mConnection = aHttpTransaction->Connection(); } + aHttpTransaction->SetConnection(this); + + if (aUseTunnel) { + LOG3(("SpdySession31::AddStream session=%p trans=%p OnTunnel", + this, aHttpTransaction)); + DispatchOnTunnel(aHttpTransaction, aCallbacks); + return true; + } + SpdyStream31 *stream = new SpdyStream31(aHttpTransaction, this, aPriority); - LOG3(("SpdySession31::AddStream session=%p stream=%p NextID=0x%X (tentative)", - this, stream, mNextStreamID)); + LOG3(("SpdySession31::AddStream session=%p stream=%p serial=%u " + "NextID=0x%X (tentative)", this, stream, mSerial, mNextStreamID)); mStreamTransactionHash.Put(aHttpTransaction, stream); @@ -946,6 +946,10 @@ SpdySession31::CloseStream(SpdyStream31 *aStream, nsresult aResult) RemoveStreamFromQueues(aStream); + if (aStream->IsTunnel()) { + UnRegisterTunnel(aStream); + } + // Send the stream the close() indication aStream->Close(aResult); } @@ -1067,7 +1071,13 @@ SpdySession31::HandleSynStream(SpdySession31 *self) self->mPushedStreams.AppendElement(pushedStream); // The pushed stream is unidirectional so it is fully open immediately - pushedStream->SetFullyOpen(); + rv = pushedStream->SetFullyOpen(); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleSynStream pushedstream fully open failed\n")); + self->CleanupStream(pushedStream, rv, RST_CANCEL); + self->ResetDownstreamState(); + return NS_OK; + } // Uncompress the response headers into a stream specific buffer, leaving them // in spdy format for the time being. @@ -1140,10 +1150,10 @@ SpdySession31::HandleSynReply(SpdySession31 *self) return NS_ERROR_ILLEGAL_VALUE; } - LOG3(("SpdySession31::HandleSynReply %p lookup via streamID in syn_reply.\n", - self)); uint32_t streamID = PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + LOG3(("SpdySession31::HandleSynReply %p lookup via streamID 0x%X in syn_reply.\n", + self, streamID)); nsresult rv = self->SetInputFrameDataStream(streamID); if (NS_FAILED(rv)) return rv; @@ -1206,7 +1216,19 @@ SpdySession31::HandleSynReply(SpdySession31 *self) self->ResetDownstreamState(); return NS_OK; } - self->mInputFrameDataStream->SetFullyOpen(); + + rv = self->mInputFrameDataStream->SetFullyOpen(); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleSynReply SetFullyOpen failed\n")); + if (self->mInputFrameDataStream->IsTunnel()) { + gHttpHandler->ConnMgr()->CancelTransactions( + self->mInputFrameDataStream->Transaction()->ConnectionInfo(), + NS_ERROR_CONNECTION_REFUSED); + } + self->CleanupStream(self->mInputFrameDataStream, rv, RST_CANCEL); + self->ResetDownstreamState(); + return NS_OK; + } self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize); @@ -2067,6 +2089,7 @@ SpdySession31::WriteSegments(nsAHttpSegmentWriter *writer, MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly"); mNeedsCleanup = nullptr; /* just in case */ + SpdyStream31 *stream = mInputFrameDataStream; mSegmentWriter = writer; rv = mInputFrameDataStream->WriteSegments(this, count, countWritten); mSegmentWriter = nullptr; @@ -2077,7 +2100,6 @@ SpdySession31::WriteSegments(nsAHttpSegmentWriter *writer, // This will happen when the transaction figures out it is EOF, generally // due to a content-length match being made. Return OK from this function // otherwise the whole session would be torn down. - SpdyStream31 *stream = mInputFrameDataStream; // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state // back to PROCESSING_DATA_FRAME where we came from @@ -2091,8 +2113,8 @@ SpdySession31::WriteSegments(nsAHttpSegmentWriter *writer, this, stream, stream ? stream->StreamID() : 0, mNeedsCleanup, rv)); CleanupStream(stream, NS_OK, RST_CANCEL); - MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame"); - mNeedsCleanup = nullptr; /* just in case */ + MOZ_ASSERT(!mNeedsCleanup || mNeedsCleanup == stream); + mNeedsCleanup = nullptr; return NS_OK; } @@ -2579,6 +2601,79 @@ SpdySession31::ConnectPushedStream(SpdyStream31 *stream) ForceRecv(); } +uint32_t +SpdySession31::FindTunnelCount(nsHttpConnectionInfo *aConnInfo) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + uint32_t rv = 0; + mTunnelHash.Get(aConnInfo->HashKey(), &rv); + return rv; +} + +void +SpdySession31::RegisterTunnel(SpdyStream31 *aTunnel) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo(); + uint32_t newcount = FindTunnelCount(ci) + 1; + mTunnelHash.Remove(ci->HashKey()); + mTunnelHash.Put(ci->HashKey(), newcount); + LOG3(("SpdySession31::RegisterTunnel %p stream=%p tunnels=%d [%s]", + this, aTunnel, newcount, ci->HashKey().get())); +} + +void +SpdySession31::UnRegisterTunnel(SpdyStream31 *aTunnel) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsHttpConnectionInfo *ci = aTunnel->Transaction()->ConnectionInfo(); + MOZ_ASSERT(FindTunnelCount(ci)); + uint32_t newcount = FindTunnelCount(ci) - 1; + mTunnelHash.Remove(ci->HashKey()); + if (newcount) { + mTunnelHash.Put(ci->HashKey(), newcount); + } + LOG3(("SpdySession31::UnRegisterTunnel %p stream=%p tunnels=%d [%s]", + this, aTunnel, newcount, ci->HashKey().get())); +} + +void +SpdySession31::DispatchOnTunnel(nsAHttpTransaction *aHttpTransaction, + nsIInterfaceRequestor *aCallbacks) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + nsHttpTransaction *trans = aHttpTransaction->QueryHttpTransaction(); + nsHttpConnectionInfo *ci = aHttpTransaction->ConnectionInfo(); + MOZ_ASSERT(trans); + + LOG3(("SpdySession31::DispatchOnTunnel %p trans=%p", this, trans)); + + aHttpTransaction->SetConnection(nullptr); + + // this transaction has done its work of setting up a tunnel, let + // the connection manager queue it if necessary + trans->SetDontRouteViaWildCard(true); + + if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) { + LOG3(("SpdySession31::DispatchOnTunnel %p create on new tunnel %s", + this, ci->HashKey().get())); + nsRefPtr connectTrans = + new SpdyConnectTransaction(ci, aCallbacks, + trans->Caps(), trans, this); + AddStream(connectTrans, trans->Priority(), + false, nullptr); + SpdyStream31 *tunnel = mStreamTransactionHash.Get(connectTrans); + MOZ_ASSERT(tunnel); + RegisterTunnel(tunnel); + } + + // requeue it. The connection manager is responsible for actually putting + // this on the tunnel connection with the specific ci now that it + // has DontRouteViaWildCard set. + trans->EnableKeepAlive(); + gHttpHandler->InitiateTransaction(trans, trans->Priority()); +} + nsresult SpdySession31::BufferOutput(const char *buf, uint32_t count, @@ -2616,6 +2711,11 @@ SpdySession31::TransactionHasDataToWrite(nsAHttpTransaction *caller) mReadyForWrite.Push(stream); SetWriteCallbacks(); + + // NSPR poll will not poll the network if there are non system PR_FileDesc's + // that are ready - so we can get into a deadlock waiting for the system IO + // to come back here if we don't force the send loop manually. + ForceSend(); } void @@ -2627,6 +2727,7 @@ SpdySession31::TransactionHasDataToWrite(SpdyStream31 *stream) mReadyForWrite.Push(stream); SetWriteCallbacks(); + ForceSend(); } bool diff --git a/netwerk/protocol/http/SpdySession31.h b/netwerk/protocol/http/SpdySession31.h index 814a2e7e6bb9..440617616427 100644 --- a/netwerk/protocol/http/SpdySession31.h +++ b/netwerk/protocol/http/SpdySession31.h @@ -23,6 +23,7 @@ namespace mozilla { namespace net { class SpdyPushedStream31; class SpdyStream31; +class nsHttpTransaction; class SpdySession31 MOZ_FINAL : public ASpdySession , public nsAHttpConnection @@ -402,6 +403,15 @@ private: // by the load group and the serial number can be used as part of the cache key // to make sure streams aren't shared across sessions. uint64_t mSerial; + +private: +/// connect tunnels + void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *); + void RegisterTunnel(SpdyStream31 *); + void UnRegisterTunnel(SpdyStream31 *); + uint32_t FindTunnelCount(nsHttpConnectionInfo *); + + nsDataHashtable mTunnelHash; }; }} // namespace mozilla::net diff --git a/netwerk/protocol/http/SpdyStream31.cpp b/netwerk/protocol/http/SpdyStream31.cpp index f723c65b49d2..88678f110425 100644 --- a/netwerk/protocol/http/SpdyStream31.cpp +++ b/netwerk/protocol/http/SpdyStream31.cpp @@ -24,6 +24,7 @@ #include "SpdyPush31.h" #include "SpdySession31.h" #include "SpdyStream31.h" +#include "TunnelUtils.h" #include @@ -68,6 +69,7 @@ SpdyStream31::SpdyStream31(nsAHttpTransaction *httpTransaction, , mTotalSent(0) , mTotalRead(0) , mPushSource(nullptr) + , mIsTunnel(false) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); @@ -82,6 +84,7 @@ SpdyStream31::SpdyStream31(nsAHttpTransaction *httpTransaction, SpdyStream31::~SpdyStream31() { + ClearTransactionsBlockedOnTunnel(); mStreamID = SpdySession31::kDeadStreamID; } @@ -119,7 +122,8 @@ SpdyStream31::ReadSegments(nsAHttpSegmentReader *reader, mSegmentReader = reader; rv = mTransaction->ReadSegments(this, count, countRead); mSegmentReader = nullptr; - + LOG3(("SpdyStream31::ReadSegments %p trans readsegments rv %x read=%d\n", + this, rv, *countRead)); // Check to see if the transaction's request could be written out now. // If not, mark the stream for callback when writing can proceed. if (NS_SUCCEEDED(rv) && @@ -144,7 +148,8 @@ SpdyStream31::ReadSegments(nsAHttpSegmentReader *reader, if (!mBlockedOnRwin && !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { LOG3(("SpdyStream31::ReadSegments %p 0x%X: Sending request data complete, " - "mUpstreamState=%x",this, mStreamID, mUpstreamState)); + "mUpstreamState=%x finondata=%d",this, mStreamID, + mUpstreamState, mSentFinOnData)); if (mSentFinOnData) { ChangeState(UPSTREAM_COMPLETE); } @@ -464,24 +469,51 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf, // headers at this same level so it is not necessary to do so here. const char *methodHeader = mTransaction->RequestHead()->Method().get(); + LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, methodHeader)); // The header block length - uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */ + uint16_t count = hdrHash.Count() + 4; /* :method, :path, :version, :host */ + if (mTransaction->RequestHead()->IsConnect()) { + mRequestBodyLenRemaining = 0x0fffffffffffffffULL; + } else { + ++count; // :scheme used if not connect + } CompressToFrame(count); // :method, :path, :version comprise a HTTP/1 request line, so send those first // to make life easy for any gateways CompressToFrame(NS_LITERAL_CSTRING(":method")); CompressToFrame(methodHeader, strlen(methodHeader)); + CompressToFrame(NS_LITERAL_CSTRING(":path")); - CompressToFrame(mTransaction->RequestHead()->RequestURI()); + if (!mTransaction->RequestHead()->IsConnect()) { + CompressToFrame(mTransaction->RequestHead()->RequestURI()); + } else { + MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction()); + mIsTunnel = true; + // Connect places host:port in :path. Don't use default port. + nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo(); + if (!ci) { + return NS_ERROR_UNEXPECTED; + } + nsAutoCString route; + route = ci->GetHost(); + route.AppendLiteral(":"); + route.AppendInt(ci->Port()); + CompressToFrame(route); + } + CompressToFrame(NS_LITERAL_CSTRING(":version")); CompressToFrame(versionHeader); CompressToFrame(NS_LITERAL_CSTRING(":host")); CompressToFrame(hostHeader); - CompressToFrame(NS_LITERAL_CSTRING(":scheme")); - CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http")); + + if (!mTransaction->RequestHead()->IsConnect()) { + // no :scheme with connect + CompressToFrame(NS_LITERAL_CSTRING(":scheme")); + CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http")); + } hdrHash.Enumerate(hdrHashEnumerate, this); CompressFlushFrame(); @@ -496,9 +528,8 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf, // to wait for a data packet to put it on. if (mTransaction->RequestHead()->IsGet() || - mTransaction->RequestHead()->IsConnect() || mTransaction->RequestHead()->IsHead()) { - // for GET, CONNECT, and HEAD place the fin bit right on the + // for GET and HEAD place the fin bit right on the // syn stream packet mSentFinOnData = 1; @@ -506,6 +537,7 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf, } else if (mTransaction->RequestHead()->IsPost() || mTransaction->RequestHead()->IsPut() || + mTransaction->RequestHead()->IsConnect() || mTransaction->RequestHead()->IsOptions()) { // place fin in a data frame even for 0 length messages, I've seen // the google gateway be unhappy with fin-on-syn for 0 length POST @@ -600,6 +632,9 @@ void SpdyStream31::UpdateTransportReadEvents(uint32_t count) { mTotalRead += count; + if (!mSocketTransport) { + return; + } mTransaction->OnTransportStatus(mSocketTransport, NS_NET_STATUS_RECEIVING_FROM, @@ -1262,6 +1297,12 @@ SpdyStream31::ConvertHeaders(nsACString &aHeadersOut) mDecompressBufferSize = 0; mDecompressBufferUsed = 0; + if (mIsTunnel) { + aHeadersOut.Truncate(); + LOG(("SpdyStream31::ConvertHeaders %p 0x%X headers removed for tunnel\n", + this, mStreamID)); + } + return NS_OK; } @@ -1327,6 +1368,38 @@ SpdyStream31::CompressFlushFrame() ExecuteCompress(Z_SYNC_FLUSH); } +bool +SpdyStream31::GetFullyOpen() +{ + return mFullyOpen; +} + +nsresult +SpdyStream31::SetFullyOpen() +{ + MOZ_ASSERT(!mFullyOpen); + mFullyOpen = 1; + if (mIsTunnel) { + nsDependentCSubstring statusSubstring; + nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"), + statusSubstring); + if (NS_SUCCEEDED(rv)) { + nsCString status(statusSubstring); + nsresult errcode; + + if (status.ToInteger(&errcode) != 200) { + LOG3(("SpdyStream31::SetFullyOpen %p Tunnel not 200", this)); + return NS_ERROR_FAILURE; + } + LOG3(("SpdyStream31::SetFullyOpen %p Tunnel 200 OK", this)); + } + + MapStreamToHttpConnection(); + ClearTransactionsBlockedOnTunnel(); + } + return NS_OK; +} + void SpdyStream31::Close(nsresult reason) { @@ -1420,11 +1493,15 @@ SpdyStream31::OnReadSegment(const char *buf, mRemoteWindow -= dataLength; mSession->DecrementRemoteSessionWindow(dataLength); - LOG3(("SpdyStream31 %p id %x request len remaining %d, " - "count avail %d, chunk used %d", + LOG3(("SpdyStream31 %p id %x request len remaining %u, " + "count avail %u, chunk used %u", this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); - if (dataLength > mRequestBodyLenRemaining) + if (!dataLength && mRequestBodyLenRemaining) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + if (dataLength > mRequestBodyLenRemaining) { return NS_ERROR_UNEXPECTED; + } mRequestBodyLenRemaining -= dataLength; GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); ChangeState(SENDING_REQUEST_BODY); @@ -1490,6 +1567,27 @@ SpdyStream31::OnWriteSegment(char *buf, return NS_OK; } +/// connect tunnels + +void +SpdyStream31::ClearTransactionsBlockedOnTunnel() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (!mIsTunnel) { + return; + } + gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo()); +} + +void +SpdyStream31::MapStreamToHttpConnection() +{ + nsRefPtr qiTrans(mTransaction->QuerySpdyConnectTransaction()); + MOZ_ASSERT(qiTrans); + qiTrans->MapStreamToHttpConnection(mSocketTransport, + mTransaction->ConnectionInfo()); +} + } // namespace mozilla::net } // namespace mozilla - diff --git a/netwerk/protocol/http/SpdyStream31.h b/netwerk/protocol/http/SpdyStream31.h index 089ad709543e..bbfbfe5591ab 100644 --- a/netwerk/protocol/http/SpdyStream31.h +++ b/netwerk/protocol/http/SpdyStream31.h @@ -34,13 +34,10 @@ public: return static_cast(mRequestBlockedOnRead); } - // returns false if called more than once - bool GetFullyOpen() {return mFullyOpen;} - void SetFullyOpen() - { - MOZ_ASSERT(!mFullyOpen); - mFullyOpen = 1; - } + bool GetFullyOpen(); + // returns failure if stream cannot be made ready and stream + // should be canceled + nsresult SetFullyOpen(); bool HasRegisteredID() { return mStreamID != 0; } @@ -215,7 +212,8 @@ private: // place the fin flag on the last data packet instead of waiting // for a stream closed indication. Relying on stream close results // in an extra 0-length runt packet and seems to have some interop - // problems with the google servers. + // problems with the google servers. Connect does rely on stream + // close by setting this to the max value. int64_t mRequestBodyLenRemaining; // based on nsISupportsPriority definitions @@ -248,6 +246,15 @@ private: // For SpdyPush SpdyPushedStream31 *mPushSource; + +/// connect tunnels +public: + bool IsTunnel() { return mIsTunnel; } +private: + void ClearTransactionsBlockedOnTunnel(); + void MapStreamToHttpConnection(); + + bool mIsTunnel; }; }} // namespace mozilla::net