For IETF QUIC client: When 0-RTT is rejected, check the server-sent transport params and close connection if server's limit exceeds what the client has already used.
Client side change only. not protected.
PiperOrigin-RevId: 315406554
Change-Id: I6e313df0ed10ef64c3f72edb5e4d1433804b8e87
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 1a6ed6c..ca9b1d5 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -194,6 +194,18 @@
session_->GetMutableCryptoStream());
}
+ void CompleteFirstConnection() {
+ CompleteCryptoHandshake();
+ EXPECT_FALSE(session_->GetCryptoStream()->IsResumption());
+ if (session_->version().UsesHttp3()) {
+ SettingsFrame settings;
+ settings.values[SETTINGS_QPACK_MAX_TABLE_CAPACITY] = 2;
+ settings.values[SETTINGS_MAX_HEADER_LIST_SIZE] = 5;
+ settings.values[256] = 4; // unknown setting
+ session_->OnSettingsFrame(settings);
+ }
+ }
+
// Owned by |session_|.
QuicCryptoClientStream* crypto_stream_;
std::unique_ptr<QuicCryptoServerConfig> server_crypto_config_;
@@ -979,15 +991,7 @@
return;
}
- CompleteCryptoHandshake();
- EXPECT_FALSE(session_->GetCryptoStream()->IsResumption());
- if (session_->version().UsesHttp3()) {
- SettingsFrame settings;
- settings.values[SETTINGS_QPACK_MAX_TABLE_CAPACITY] = 2;
- settings.values[SETTINGS_MAX_HEADER_LIST_SIZE] = 5;
- settings.values[256] = 4; // unknown setting
- session_->OnSettingsFrame(settings);
- }
+ CompleteFirstConnection();
CreateConnection();
// Session configs should be in initial state.
@@ -1064,15 +1068,7 @@
return;
}
- CompleteCryptoHandshake();
- EXPECT_FALSE(session_->GetCryptoStream()->IsResumption());
- if (session_->version().UsesHttp3()) {
- SettingsFrame settings;
- settings.values[SETTINGS_QPACK_MAX_TABLE_CAPACITY] = 2;
- settings.values[SETTINGS_MAX_HEADER_LIST_SIZE] = 5;
- settings.values[256] = 4; // unknown setting
- session_->OnSettingsFrame(settings);
- }
+ CompleteFirstConnection();
// Create a second connection, but disable 0-RTT on the server.
CreateConnection();
@@ -1108,6 +1104,136 @@
EXPECT_TRUE(session_->GetCryptoStream()->IsResumption());
}
+// When IETF QUIC 0-RTT is rejected, a server-sent fresh transport params is
+// available. If the new transport params reduces stream/flow control limit to
+// lower than what the client has already used, connection will be closed.
+TEST_P(QuicSpdyClientSessionTest, ZeroRttRejectReducesStreamLimitTooMuch) {
+ // This feature is TLS-only.
+ if (session_->version().UsesQuicCrypto()) {
+ return;
+ }
+
+ CompleteFirstConnection();
+
+ // Create a second connection, but disable 0-RTT on the server.
+ CreateConnection();
+ QuicConfig config = DefaultQuicConfig();
+ // Server doesn't allow any bidirectional streams.
+ config.SetMaxBidirectionalStreamsToSend(0);
+ SSL_CTX_set_early_data_enabled(server_crypto_config_->ssl_ctx(), false);
+ session_->CryptoConnect();
+ EXPECT_TRUE(session_->IsEncryptionEstablished());
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_TRUE(stream);
+
+ if (session_->version().UsesHttp3()) {
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ "Server rejected 0-RTT, aborting because new bidirectional initial "
+ "stream limit 0 is less than current open streams: 1",
+ _))
+ .WillOnce(testing::Invoke(connection_,
+ &MockQuicConnection::ReallyCloseConnection));
+ } else {
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_INTERNAL_ERROR,
+ "Server rejected 0-RTT, aborting because new stream "
+ "limit 0 is less than current open streams: 1",
+ _))
+ .WillOnce(testing::Invoke(connection_,
+ &MockQuicConnection::ReallyCloseConnection));
+ }
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _));
+
+ crypto_test_utils::HandshakeWithFakeServer(
+ &config, server_crypto_config_.get(), &helper_, &alarm_factory_,
+ connection_, crypto_stream_, AlpnForVersion(connection_->version()));
+}
+
+TEST_P(QuicSpdyClientSessionTest,
+ ZeroRttRejectReducesStreamFlowControlTooMuch) {
+ // This feature is TLS-only.
+ if (session_->version().UsesQuicCrypto()) {
+ return;
+ }
+
+ CompleteFirstConnection();
+
+ // Create a second connection, but disable 0-RTT on the server.
+ CreateConnection();
+ QuicConfig config = DefaultQuicConfig();
+ // Server doesn't allow any outgoing streams.
+ config.SetInitialMaxStreamDataBytesIncomingBidirectionalToSend(1);
+ config.SetInitialMaxStreamDataBytesUnidirectionalToSend(1);
+ SSL_CTX_set_early_data_enabled(server_crypto_config_->ssl_ctx(), false);
+ session_->CryptoConnect();
+ EXPECT_TRUE(session_->IsEncryptionEstablished());
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_TRUE(stream);
+ // Let the stream write more than 1 byte of data.
+ stream->WriteOrBufferData("hello", true, nullptr);
+
+ if (session_->version().UsesHttp3()) {
+ // Both control stream and the request stream will report errors.
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INTERNAL_ERROR, _, _))
+ .Times(2)
+ .WillOnce(testing::Invoke(connection_,
+ &MockQuicConnection::ReallyCloseConnection));
+ } else {
+ EXPECT_CALL(*connection_,
+ CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ "Server rejected 0-RTT, aborting because new stream max "
+ "data 1 for stream 3 is less than currently used: 5",
+ _))
+ .Times(1)
+ .WillOnce(testing::Invoke(connection_,
+ &MockQuicConnection::ReallyCloseConnection));
+ }
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _));
+
+ crypto_test_utils::HandshakeWithFakeServer(
+ &config, server_crypto_config_.get(), &helper_, &alarm_factory_,
+ connection_, crypto_stream_, AlpnForVersion(connection_->version()));
+}
+
+TEST_P(QuicSpdyClientSessionTest,
+ ZeroRttRejectReducesSessionFlowControlTooMuch) {
+ // This feature is TLS-only.
+ if (session_->version().UsesQuicCrypto()) {
+ return;
+ }
+
+ CompleteFirstConnection();
+
+ // Create a second connection, but disable 0-RTT on the server.
+ CreateConnection();
+ QuicConfig config = DefaultQuicConfig();
+ // Server doesn't allow minimum data in session.
+ config.SetInitialSessionFlowControlWindowToSend(
+ kMinimumFlowControlSendWindow);
+ SSL_CTX_set_early_data_enabled(server_crypto_config_->ssl_ctx(), false);
+ session_->CryptoConnect();
+ EXPECT_TRUE(session_->IsEncryptionEstablished());
+ QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
+ ASSERT_TRUE(stream);
+ std::string data_to_send(kMinimumFlowControlSendWindow + 1, 'x');
+ // Let the stream write some data.
+ stream->WriteOrBufferData(data_to_send, true, nullptr);
+
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_INTERNAL_ERROR, _, _))
+ .WillOnce(testing::Invoke(connection_,
+ &MockQuicConnection::ReallyCloseConnection));
+ EXPECT_CALL(*connection_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _));
+
+ crypto_test_utils::HandshakeWithFakeServer(
+ &config, server_crypto_config_.get(), &helper_, &alarm_factory_,
+ connection_, crypto_stream_, AlpnForVersion(connection_->version()));
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_flow_controller.h b/quic/core/quic_flow_controller.h
index 29c3c02..daad3d9 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_; }
+ QuicByteCount bytes_sent() const { return bytes_sent_; }
+
QuicStreamOffset send_window_offset() const { return send_window_offset_; }
QuicStreamOffset highest_received_byte_offset() const {
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 912b0cb..a697dc7 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -97,7 +97,8 @@
supported_versions_(supported_versions),
use_http2_priority_write_scheduler_(false),
is_configured_(false),
- enable_round_robin_scheduling_(false) {
+ enable_round_robin_scheduling_(false),
+ was_zero_rtt_rejected_(false) {
closed_streams_clean_up_alarm_ =
QuicWrapUnique<QuicAlarm>(connection_->alarm_factory()->CreateAlarm(
new ClosedStreamsCleanUpDelegate(this)));
@@ -1022,6 +1023,19 @@
if (config_.HasReceivedMaxBidirectionalStreams()) {
max_streams = config_.ReceivedMaxBidirectionalStreams();
}
+ if (was_zero_rtt_rejected_ &&
+ max_streams <
+ v99_streamid_manager_.outgoing_bidirectional_stream_count()) {
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ quiche::QuicheStrCat(
+ "Server rejected 0-RTT, aborting because new bidirectional "
+ "initial stream limit ",
+ max_streams, " is less than current open streams: ",
+ v99_streamid_manager_.outgoing_bidirectional_stream_count()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
QUIC_DVLOG(1) << ENDPOINT
<< "Setting Bidirectional outgoing_max_streams_ to "
<< max_streams;
@@ -1046,6 +1060,21 @@
if (config_.HasReceivedMaxUnidirectionalStreams()) {
max_streams = config_.ReceivedMaxUnidirectionalStreams();
}
+
+ if (was_zero_rtt_rejected_ &&
+ max_streams <
+ v99_streamid_manager_.outgoing_unidirectional_stream_count()) {
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ quiche::QuicheStrCat(
+ "Server rejected 0-RTT, aborting because new unidirectional "
+ "initial stream limit ",
+ max_streams, " is less than current open streams: ",
+ v99_streamid_manager_.outgoing_unidirectional_stream_count()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+
if (max_streams <
v99_streamid_manager_.max_outgoing_unidirectional_streams()) {
connection_->CloseConnection(
@@ -1071,6 +1100,17 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Setting max_open_outgoing_streams_ to "
<< max_streams;
+ if (was_zero_rtt_rejected_ &&
+ max_streams < stream_id_manager_.num_open_outgoing_streams()) {
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ quiche::QuicheStrCat(
+ "Server rejected 0-RTT, aborting because new stream limit ",
+ max_streams, " is less than current open streams: ",
+ stream_id_manager_.num_open_outgoing_streams()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
stream_id_manager_.set_max_open_outgoing_streams(max_streams);
}
@@ -1281,6 +1321,18 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Informing unidirectional stream " << id
<< " of new stream flow control window " << new_window;
+ if (was_zero_rtt_rejected_ &&
+ new_window < kv.second->flow_controller()->bytes_sent()) {
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ quiche::QuicheStrCat(
+ "Server rejected 0-RTT, aborting because new stream max data ",
+ new_window, " for stream ", kv.first,
+ " is less than currently used: ",
+ kv.second->flow_controller()->bytes_sent()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
if (!kv.second->ConfigSendWindowOffset(new_window)) {
return;
}
@@ -1305,6 +1357,18 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Informing outgoing bidirectional stream "
<< id << " of new stream flow control window " << new_window;
+ if (was_zero_rtt_rejected_ &&
+ new_window < kv.second->flow_controller()->bytes_sent()) {
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ quiche::QuicheStrCat(
+ "Server rejected 0-RTT, aborting because new stream max data ",
+ new_window, " for stream ", kv.first,
+ " is less than currently used: ",
+ kv.second->flow_controller()->bytes_sent()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
if (!kv.second->ConfigSendWindowOffset(new_window)) {
return;
}
@@ -1329,6 +1393,18 @@
}
QUIC_DVLOG(1) << ENDPOINT << "Informing incoming bidirectional stream "
<< id << " of new stream flow control window " << new_window;
+ if (was_zero_rtt_rejected_ &&
+ new_window < kv.second->flow_controller()->bytes_sent()) {
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR,
+ quiche::QuicheStrCat(
+ "Server rejected 0-RTT, aborting because new stream max data ",
+ new_window, " for stream ", kv.first,
+ " is less than currently used: ",
+ kv.second->flow_controller()->bytes_sent()),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
if (!kv.second->ConfigSendWindowOffset(new_window)) {
return;
}
@@ -1337,28 +1413,40 @@
void QuicSession::OnNewSessionFlowControlWindow(QuicStreamOffset new_window) {
QUIC_DVLOG(1) << ENDPOINT << "OnNewSessionFlowControlWindow " << new_window;
- 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()) {
+
+ if (was_zero_rtt_rejected_ && new_window < flow_controller_.bytes_sent()) {
+ std::string error_details = quiche::QuicheStrCat(
+ "Server rejected 0-RTT. Aborting because the client received session "
+ "flow control send window: ",
+ new_window,
+ ", which is below currently used: ", flow_controller_.bytes_sent());
+ QUIC_LOG(ERROR) << error_details;
+ connection_->CloseConnection(
+ QUIC_INTERNAL_ERROR, error_details,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ if (!connection_->version().AllowsLowFlowControlLimits() &&
+ new_window < kMinimumFlowControlSendWindow) {
+ std::string error_details = quiche::QuicheStrCat(
+ "Peer sent us an invalid session flow control send window: ",
+ new_window, ", below minimum: ", kMinimumFlowControlSendWindow);
+ QUIC_LOG_FIRST_N(ERROR, 1) << error_details;
+ connection_->CloseConnection(
+ QUIC_FLOW_CONTROL_INVALID_WINDOW, error_details,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ 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 current: " << flow_controller_.send_window_offset();
- close_connection = true;
- }
- if (close_connection) {
+ std::string error_details = quiche::QuicheStrCat(
+ "Peer sent us an invalid session flow control send window: ",
+ new_window, ", below current: ", flow_controller_.send_window_offset());
+ QUIC_LOG(ERROR) << error_details;
connection_->CloseConnection(
- QUIC_FLOW_CONTROL_INVALID_WINDOW,
- quiche::QuicheStrCat("New connection window too low: ", new_window),
+ QUIC_FLOW_CONTROL_INVALID_WINDOW, error_details,
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return;
}
@@ -1486,9 +1574,7 @@
}
void QuicSession::OnZeroRttRejected() {
- // TODO(b/153726130): Read stream limit and flow control limit from server
- // transport params, and close the connection proactively if client has used
- // too many.
+ was_zero_rtt_rejected_ = true;
connection_->RetransmitZeroRttPackets();
if (connection_->encryption_level() == ENCRYPTION_FORWARD_SECURE) {
QUIC_BUG << "1-RTT keys already available when 0-RTT is rejected.";
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 61019a4..33abcfc 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -795,6 +795,9 @@
// If true, enables round robin scheduling.
bool enable_round_robin_scheduling_;
+
+ // Whether the session has received a 0-RTT rejection (QUIC+TLS only).
+ bool was_zero_rtt_rejected_;
};
} // namespace quic
diff --git a/quic/core/uber_quic_stream_id_manager.cc b/quic/core/uber_quic_stream_id_manager.cc
index 6951b2c..64cbd58 100644
--- a/quic/core/uber_quic_stream_id_manager.cc
+++ b/quic/core/uber_quic_stream_id_manager.cc
@@ -162,4 +162,14 @@
return unidirectional_stream_id_manager_.incoming_advertised_max_streams();
}
+QuicStreamCount UberQuicStreamIdManager::outgoing_bidirectional_stream_count()
+ const {
+ return bidirectional_stream_id_manager_.outgoing_stream_count();
+}
+
+QuicStreamCount UberQuicStreamIdManager::outgoing_unidirectional_stream_count()
+ const {
+ return unidirectional_stream_id_manager_.outgoing_stream_count();
+}
+
} // namespace quic
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h
index b1fc126..aafe0b6 100644
--- a/quic/core/uber_quic_stream_id_manager.h
+++ b/quic/core/uber_quic_stream_id_manager.h
@@ -87,6 +87,9 @@
QuicStreamCount advertised_max_incoming_bidirectional_streams() const;
QuicStreamCount advertised_max_incoming_unidirectional_streams() const;
+ QuicStreamCount outgoing_bidirectional_stream_count() const;
+ QuicStreamCount outgoing_unidirectional_stream_count() const;
+
private:
friend class test::QuicSessionPeer;
friend class test::UberQuicStreamIdManagerPeer;