Implement restrictions at https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-7.3.1.
This prepares for 0-rtt on the client side.
This change should be no-op for current deployment in gfe because config is currently only set once.
gfe-relnote: unused code. not protected.
PiperOrigin-RevId: 308861599
Change-Id: I0c8182f3d11330f40024dca76de9fed6d383f8d1
diff --git a/quic/core/quic_flow_controller.h b/quic/core/quic_flow_controller.h
index e627c8c..29c3c02 100644
--- a/quic/core/quic_flow_controller.h
+++ b/quic/core/quic_flow_controller.h
@@ -91,6 +91,8 @@
QuicByteCount bytes_consumed() const { return bytes_consumed_; }
+ QuicStreamOffset send_window_offset() const { return send_window_offset_; }
+
QuicStreamOffset highest_received_byte_offset() const {
return highest_received_byte_offset_;
}
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 4862d50..3524242 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -1119,6 +1119,18 @@
QUIC_DVLOG(1) << ENDPOINT
<< "Setting Bidirectional outgoing_max_streams_ to "
<< max_streams;
+ if (perspective_ == Perspective::IS_CLIENT &&
+ max_streams <
+ v99_streamid_manager_.max_outgoing_bidirectional_streams()) {
+ connection_->CloseConnection(
+ QUIC_MAX_STREAMS_ERROR,
+ quiche::QuicheStrCat(
+ "new bidirectional limit ", max_streams,
+ " decreases the current limit: ",
+ v99_streamid_manager_.max_outgoing_bidirectional_streams()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
if (v99_streamid_manager_.MaybeAllowNewOutgoingBidirectionalStreams(
max_streams)) {
OnCanCreateNewOutgoingStream(/*unidirectional = */ false);
@@ -1128,6 +1140,8 @@
if (config_.HasReceivedMaxUnidirectionalStreams()) {
max_streams = config_.ReceivedMaxUnidirectionalStreams();
}
+ // TODO(b/153726130): remove this check and
+ // num_expected_unidirectional_static_streams_.
if (max_streams < num_expected_unidirectional_static_streams_) {
QUIC_DLOG(ERROR) << "Received unidirectional stream limit of "
<< max_streams << " < "
@@ -1138,6 +1152,18 @@
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
+ if (perspective_ == Perspective::IS_CLIENT &&
+ max_streams <
+ v99_streamid_manager_.max_outgoing_unidirectional_streams()) {
+ connection_->CloseConnection(
+ QUIC_MAX_STREAMS_ERROR,
+ quiche::QuicheStrCat(
+ "new unidirectional limit ", max_streams,
+ " decreases the current limit: ",
+ v99_streamid_manager_.max_outgoing_unidirectional_streams()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
QUIC_DVLOG(1) << ENDPOINT
<< "Setting Unidirectional outgoing_max_streams_ to "
<< max_streams;
@@ -1310,6 +1336,7 @@
}
void QuicSession::OnNewStreamFlowControlWindow(QuicStreamOffset new_window) {
+ DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_QUIC_CRYPTO);
QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamFlowControlWindow " << new_window;
if (new_window < kMinimumFlowControlSendWindow &&
!connection_->version().AllowsLowFlowControlLimits()) {
@@ -1326,19 +1353,22 @@
for (auto const& kv : stream_map_) {
QUIC_DVLOG(1) << ENDPOINT << "Informing stream " << kv.first
<< " of new stream flow control window " << new_window;
- kv.second->UpdateSendWindowOffset(new_window);
+ if (!kv.second->ConfigSendWindowOffset(new_window)) {
+ return;
+ }
}
if (!QuicVersionUsesCryptoFrames(transport_version())) {
QUIC_DVLOG(1)
<< ENDPOINT
<< "Informing crypto stream of new stream flow control window "
<< new_window;
- GetMutableCryptoStream()->UpdateSendWindowOffset(new_window);
+ GetMutableCryptoStream()->ConfigSendWindowOffset(new_window);
}
}
void QuicSession::OnNewStreamUnidirectionalFlowControlWindow(
QuicStreamOffset new_window) {
+ DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3);
QUIC_DVLOG(1) << ENDPOINT << "OnNewStreamUnidirectionalFlowControlWindow "
<< new_window;
// Inform all existing outgoing unidirectional streams about the new window.
@@ -1353,12 +1383,15 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Informing unidirectional stream " << id
<< " of new stream flow control window " << new_window;
- kv.second->UpdateSendWindowOffset(new_window);
+ if (!kv.second->ConfigSendWindowOffset(new_window)) {
+ return;
+ }
}
}
void QuicSession::OnNewStreamOutgoingBidirectionalFlowControlWindow(
QuicStreamOffset new_window) {
+ DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3);
QUIC_DVLOG(1) << ENDPOINT
<< "OnNewStreamOutgoingBidirectionalFlowControlWindow "
<< new_window;
@@ -1374,12 +1407,15 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Informing outgoing bidirectional stream "
<< id << " of new stream flow control window " << new_window;
- kv.second->UpdateSendWindowOffset(new_window);
+ if (!kv.second->ConfigSendWindowOffset(new_window)) {
+ return;
+ }
}
}
void QuicSession::OnNewStreamIncomingBidirectionalFlowControlWindow(
QuicStreamOffset new_window) {
+ DCHECK_EQ(connection_->version().handshake_protocol, PROTOCOL_TLS1_3);
QUIC_DVLOG(1) << ENDPOINT
<< "OnNewStreamIncomingBidirectionalFlowControlWindow "
<< new_window;
@@ -1395,19 +1431,36 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Informing incoming bidirectional stream "
<< id << " of new stream flow control window " << new_window;
- kv.second->UpdateSendWindowOffset(new_window);
+ if (!kv.second->ConfigSendWindowOffset(new_window)) {
+ return;
+ }
}
}
void QuicSession::OnNewSessionFlowControlWindow(QuicStreamOffset new_window) {
QUIC_DVLOG(1) << ENDPOINT << "OnNewSessionFlowControlWindow " << new_window;
- if (new_window < kMinimumFlowControlSendWindow &&
- !connection_->version().AllowsLowFlowControlLimits()) {
+ bool close_connection = false;
+ if (!connection_->version().AllowsLowFlowControlLimits()) {
+ if (new_window < kMinimumFlowControlSendWindow) {
+ close_connection = true;
+ QUIC_LOG_FIRST_N(ERROR, 1)
+ << "Peer sent us an invalid session flow control send window: "
+ << new_window << ", below default: " << kMinimumFlowControlSendWindow;
+ }
+ } else if (perspective_ == Perspective::IS_CLIENT &&
+ new_window < flow_controller_.send_window_offset()) {
+ // The client receives a lower limit than remembered, violating
+ // https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-7.3.1
QUIC_LOG_FIRST_N(ERROR, 1)
<< "Peer sent us an invalid session flow control send window: "
- << new_window << ", below default: " << kMinimumFlowControlSendWindow;
+ << new_window
+ << ", below current: " << flow_controller_.send_window_offset();
+ close_connection = true;
+ }
+ if (close_connection) {
connection_->CloseConnection(
- QUIC_FLOW_CONTROL_INVALID_WINDOW, "New connection window too low",
+ QUIC_FLOW_CONTROL_INVALID_WINDOW,
+ quiche::QuicheStrCat("New connection window too low: ", new_window),
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 372fa07..db24eca 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -1776,21 +1776,6 @@
session_.OnConfigNegotiated();
}
-TEST_P(QuicSessionTestServer, InvalidSessionFlowControlWindowInHandshake) {
- // Test that receipt of an invalid (< default) session flow control window
- // from the peer results in the connection being torn down.
- const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
- QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
- kInvalidWindow);
- if (!connection_->version().AllowsLowFlowControlLimits()) {
- EXPECT_CALL(*connection_,
- CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
- } else {
- EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
- }
- session_.OnConfigNegotiated();
-}
-
// Test negotiation of custom server initial flow control window.
TEST_P(QuicSessionTestServer, CustomFlowControlWindow) {
QuicTagVector copt;
@@ -2053,6 +2038,57 @@
&session_, GetNthClientInitiatedBidirectionalId(1)));
}
+TEST_P(QuicSessionTestClient, InvalidSessionFlowControlWindowInHandshake) {
+ // Test that receipt of an invalid (< default for gQUIC, < current for TLS)
+ // session flow control window from the peer results in the connection being
+ // torn down.
+ const uint32_t kInvalidWindow = kMinimumFlowControlSendWindow - 1;
+ QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
+ kInvalidWindow);
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_FLOW_CONTROL_INVALID_WINDOW, _, _));
+ session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTestClient, InvalidBidiStreamLimitInHandshake) {
+ // IETF QUIC only feature.
+ if (!VersionHasIetfQuicFrames(transport_version())) {
+ return;
+ }
+ QuicConfigPeer::SetReceivedMaxBidirectionalStreams(
+ session_.config(), kDefaultMaxStreamsPerConnection - 1);
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAMS_ERROR, _, _));
+ session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTestClient, InvalidUniStreamLimitInHandshake) {
+ // IETF QUIC only feature.
+ if (!VersionHasIetfQuicFrames(transport_version())) {
+ return;
+ }
+ QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(
+ session_.config(), kDefaultMaxStreamsPerConnection - 1);
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_MAX_STREAMS_ERROR, _, _));
+ session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTestClient, InvalidStreamFlowControlWindowInHandshake) {
+ // IETF QUIC only feature.
+ if (!VersionHasIetfQuicFrames(transport_version())) {
+ return;
+ }
+ session_.CreateOutgoingBidirectionalStream();
+ session_.CreateOutgoingBidirectionalStream();
+ QuicConfigPeer::SetReceivedInitialMaxStreamDataBytesOutgoingBidirectional(
+ session_.config(), kMinimumFlowControlSendWindow - 1);
+
+ EXPECT_CALL(*connection_, CloseConnection(_, _, _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
+ session_.OnConfigNegotiated();
+}
+
TEST_P(QuicSessionTestClient, OnMaxStreamFrame) {
if (!VersionUsesHttp3(transport_version())) {
return;
diff --git a/quic/core/quic_stream.cc b/quic/core/quic_stream.cc
index 15212f9..c43d914 100644
--- a/quic/core/quic_stream.cc
+++ b/quic/core/quic_stream.cc
@@ -904,11 +904,22 @@
}
}
-void QuicStream::UpdateSendWindowOffset(QuicStreamOffset new_window) {
- if (flow_controller_->UpdateSendWindowOffset(new_window)) {
+bool QuicStream::ConfigSendWindowOffset(QuicStreamOffset new_offset) {
+ if (perspective_ == Perspective::IS_CLIENT &&
+ session()->version().AllowsLowFlowControlLimits() &&
+ new_offset < flow_controller_->send_window_offset()) {
+ OnUnrecoverableError(
+ QUIC_FLOW_CONTROL_INVALID_WINDOW,
+ quiche::QuicheStrCat("New stream max data ", new_offset,
+ " decreases current limit: ",
+ flow_controller_->send_window_offset()));
+ return false;
+ }
+ if (flow_controller_->UpdateSendWindowOffset(new_offset)) {
// Let session unblock this stream.
session_->MarkConnectionLevelWriteBlocked(id_);
}
+ return true;
}
void QuicStream::AddRandomPaddingAfterFin() {
diff --git a/quic/core/quic_stream.h b/quic/core/quic_stream.h
index 90dac69..cc7ce4d 100644
--- a/quic/core/quic_stream.h
+++ b/quic/core/quic_stream.h
@@ -244,9 +244,8 @@
// Returns true if the highest offset did increase.
bool MaybeIncreaseHighestReceivedOffset(QuicStreamOffset new_offset);
- // Updates the flow controller's send window offset and calls OnCanWrite if
- // it was blocked before.
- void UpdateSendWindowOffset(QuicStreamOffset new_offset);
+ // Set the flow controller's send window offset from session config.
+ bool ConfigSendWindowOffset(QuicStreamOffset new_offset);
// Returns true if the stream has received either a RST_STREAM or a FIN -
// either of which gives a definitive number of bytes which the peer has