gfe-relnote: No longer send a RESET_STREAM in response to a STOP_SENDING if the stream is write closed. Protected by disabled gfe2_reloadable_flag_quic_enable_version_99. PiperOrigin-RevId: 293529515 Change-Id: I0a837b79a776233d7a21fb8f87dbb7e173241a26
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc index 7621288..97623a7 100644 --- a/quic/core/http/end_to_end_test.cc +++ b/quic/core/http/end_to_end_test.cc
@@ -4013,10 +4013,8 @@ } } -// Test that STOP_SENDING makes it to the other side. Set up a client & server, -// create a stream (do not close it), and then send a STOP_SENDING from one -// side. The other side should get a call to QuicStream::OnStopSending. -// (aside, test cribbed from RequestAndStreamRstInOnePacket) +// Test that STOP_SENDING makes it to the peer. Create a stream and send a +// STOP_SENDING. The receiver should get a call to QuicStream::OnStopSending. TEST_P(EndToEndTest, SimpleStopSendingTest) { const uint16_t kStopSendingTestCode = 123; ASSERT_TRUE(Initialize()); @@ -4028,9 +4026,6 @@ QuicConnection* client_connection = client_session->connection(); ASSERT_NE(nullptr, client_connection); - // STOP_SENDING will cause the server to not to send the trailer - // (and the FIN) after the response body. Instead, it sends a STOP_SENDING - // frame for the stream. std::string response_body(1305, 'a'); SpdyHeaderBlock response_headers; response_headers[":status"] = quiche::QuicheTextUtils::Uint64ToString(200); @@ -4057,14 +4052,13 @@ // Wait for the connection to become idle. client_->WaitForDelayedAcks(); - // The real expectation is the test does not crash or timeout. EXPECT_THAT(client_->connection_error(), IsQuicNoError()); - // And that the stop-sending code is received. QuicSimpleClientStream* client_stream = static_cast<QuicSimpleClientStream*>(client_->latest_created_stream()); ASSERT_NE(nullptr, client_stream); - // Make sure we have the correct stream + // Ensure the stream has been write closed upon receiving STOP_SENDING. EXPECT_EQ(stream_id, client_stream->id()); + EXPECT_TRUE(client_stream->write_side_closed()); EXPECT_EQ(kStopSendingTestCode, static_cast<uint16_t>(client_stream->stream_error())); }
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc index b8de367..b63f729 100644 --- a/quic/core/quic_session.cc +++ b/quic/core/quic_session.cc
@@ -221,9 +221,13 @@ void QuicSession::OnStopSendingFrame(const QuicStopSendingFrame& frame) { // STOP_SENDING is in IETF QUIC only. DCHECK(VersionHasIetfQuicFrames(transport_version())); + DCHECK(QuicVersionUsesCryptoFrames(transport_version())); QuicStreamId stream_id = frame.stream_id; // If Stream ID is invalid then close the connection. + // TODO(ianswett): This check is redundant to checks for IsClosedStream, + // but removing it requires removing multiple DCHECKs. + // TODO(ianswett): Multiple QUIC_DVLOGs could be QUIC_PEER_BUGs. if (stream_id == QuicUtils::GetInvalidStreamId(transport_version())) { QUIC_DVLOG(1) << ENDPOINT << "Received STOP_SENDING with invalid stream_id: " @@ -234,6 +238,19 @@ return; } + // If stream_id is READ_UNIDIRECTIONAL, close the connection. + if (QuicUtils::GetStreamType(stream_id, perspective(), + IsIncomingStream(stream_id)) == + READ_UNIDIRECTIONAL) { + QUIC_DVLOG(1) << ENDPOINT + << "Received STOP_SENDING for a read-only stream_id: " + << stream_id << "."; + connection()->CloseConnection( + QUIC_INVALID_STREAM_ID, "Received STOP_SENDING for a read-only stream", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + if (visitor_) { visitor_->OnStopSendingReceived(frame); } @@ -246,7 +263,9 @@ << stream_id << " Ignoring."; return; } - // If stream is non-existent, close the connection + // If stream is non-existent, close the connection. + // TODO(b/148842616): IETF QUIC allows a STOP_SENDING to arrive before a + // STREAM frame for peer-intiated bidirectional steams StreamMap::iterator it = stream_map_.find(stream_id); if (it == stream_map_.end()) { QUIC_DVLOG(1) << ENDPOINT @@ -260,10 +279,22 @@ } QuicStream* stream = it->second.get(); + // If the stream is null, it's an implementation error. if (stream == nullptr) { QUIC_BUG << ENDPOINT << "Received STOP_SENDING for NULL QuicStream, stream_id: " << stream_id << ". Ignoring."; + connection()->CloseConnection( + QUIC_INTERNAL_ERROR, "Received STOP_SENDING for a null stream", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + + // Do not reset the sream if all data has been sent and acknowledged. + if (stream->write_side_closed() && !stream->IsWaitingForAcks()) { + QUIC_DVLOG(1) << ENDPOINT + << "Ignoring STOP_SENDING for a write closed stream, id: " + << stream_id; return; }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc index 5bfa76b..153c787 100644 --- a/quic/core/quic_session_test.cc +++ b/quic/core/quic_session_test.cc
@@ -1623,10 +1623,8 @@ QUIC_STREAM_CANCELLED, kByteOffset); session_.OnRstStream(rst_frame); if (VersionHasIetfQuicFrames(transport_version())) { - // The test is predicated on the stream being fully closed. For IETF QUIC, - // the RST_STREAM only does one side (the read side from the perspective of - // the node receiving the RST_STREAM). This is needed to fully close the - // stream and therefore fulfill all of the expects. + // The test requires the stream to be fully closed in both directions. For + // IETF QUIC, the RST_STREAM only closes one side. QuicStopSendingFrame frame(kInvalidControlFrameId, stream->id(), QUIC_STREAM_CANCELLED); EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0); @@ -2138,10 +2136,8 @@ stream2->OnStreamReset(rst_frame); if (VersionHasIetfQuicFrames(transport_version())) { - // The test is predicated on the stream being fully closed. For IETF QUIC, - // the RST_STREAM only does one side (the read side from the perspective of - // the node receiving the RST_STREAM). This is needed to fully close the - // stream and therefore fulfill all of the expects. + // The test requires the stream to be fully closed in both directions. For + // IETF QUIC, the RST_STREAM only closes one side. QuicStopSendingFrame frame(kInvalidControlFrameId, stream2->id(), QUIC_STREAM_CANCELLED); EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0); @@ -2641,15 +2637,12 @@ session_.OnStreamFrame(unidirectional_stream_frame); } -// Check that the OnStopSendingFrame upcall handles bad input properly -// First test checks that invalid stream ids are handled. -TEST_P(QuicSessionTestServer, OnStopSendingInputInvalidStreamId) { +// Checks that invalid stream ids are handled. +TEST_P(QuicSessionTestServer, OnStopSendingInvalidStreamId) { if (!VersionHasIetfQuicFrames(transport_version())) { - // Applicable only to IETF QUIC return; } // Check that "invalid" stream ids are rejected. - // Note that the notion of an invalid stream id is Google-specific. QuicStopSendingFrame frame(1, -1, 123); EXPECT_CALL( *connection_, @@ -2658,11 +2651,22 @@ session_.OnStopSendingFrame(frame); } -// Second test, streams in the static stream map are not subject to -// STOP_SENDING; it's ignored. -TEST_P(QuicSessionTestServer, OnStopSendingInputStaticStreams) { +TEST_P(QuicSessionTestServer, OnStopSendingReadUnidirectional) { if (!VersionHasIetfQuicFrames(transport_version())) { - // Applicable only to IETF QUIC + return; + } + // It's illegal to send STOP_SENDING with a stream ID that is read-only. + QuicStopSendingFrame frame(1, GetNthClientInitiatedUnidirectionalId(1), 123); + EXPECT_CALL( + *connection_, + CloseConnection(QUIC_INVALID_STREAM_ID, + "Received STOP_SENDING for a read-only stream", _)); + session_.OnStopSendingFrame(frame); +} + +// Static streams ignore STOP_SENDING. +TEST_P(QuicSessionTestServer, OnStopSendingStaticStreams) { + if (!VersionHasIetfQuicFrames(transport_version())) { return; } QuicStreamId stream_id = 0; @@ -2670,7 +2674,6 @@ stream_id, &session_, /*is_static*/ true, BIDIRECTIONAL); QuicSessionPeer::ActivateStream(&session_, std::move(fake_static_stream)); // Check that a stream id in the static stream map is ignored. - // Note that the notion of a static stream is Google-specific. QuicStopSendingFrame frame(1, stream_id, 123); EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_STREAM_ID, @@ -2678,31 +2681,41 @@ session_.OnStopSendingFrame(frame); } -// Third test, if stream id specifies a closed stream: -// return true and do not close the connection. -TEST_P(QuicSessionTestServer, OnStopSendingInputClosedStream) { +// If stream is write closed, do not send a RESET_STREAM frame. +TEST_P(QuicSessionTestServer, OnStopSendingForWriteClosedStream) { if (!VersionHasIetfQuicFrames(transport_version())) { - // Applicable only to IETF QUIC return; } TestStream* stream = session_.CreateOutgoingBidirectionalStream(); QuicStreamId stream_id = stream->id(); - // Expect these as side effect of the close operations. - EXPECT_CALL(*connection_, SendControlFrame(_)); - EXPECT_CALL(*connection_, OnStreamReset(_, _)); stream->CloseWriteSide(); - stream->CloseReadSide(); + EXPECT_TRUE(stream->write_side_closed()); QuicStopSendingFrame frame(1, stream_id, 123); EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0); session_.OnStopSendingFrame(frame); } -// Fourth test, if stream id specifies a nonexistent stream, return false and -// close the connection +// If stream is closed, return true and do not close the connection. +TEST_P(QuicSessionTestServer, OnStopSendingClosedStream) { + if (!VersionHasIetfQuicFrames(transport_version())) { + return; + } + + TestStream* stream = session_.CreateOutgoingBidirectionalStream(); + QuicStreamId stream_id = stream->id(); + // Expect these as side effect of closing the stream. + EXPECT_CALL(*connection_, SendControlFrame(_)); + EXPECT_CALL(*connection_, OnStreamReset(_, _)); + session_.CloseStream(stream_id); + QuicStopSendingFrame frame(1, stream_id, 123); + EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0); + session_.OnStopSendingFrame(frame); +} + +// If stream id is a nonexistent stream, return false and close the connection. TEST_P(QuicSessionTestServer, OnStopSendingInputNonExistentStream) { if (!VersionHasIetfQuicFrames(transport_version())) { - // Applicable only to IETF QUIC return; }