diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 6dbff8c48f03..eda87d9408d5 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -56,7 +56,7 @@ message QuicKeepAliveSettings { } // QUIC protocol options which apply to both downstream and upstream connections. -// [#next-free-field: 9] +// [#next-free-field: 10] message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. @@ -111,6 +111,10 @@ message QuicProtocolOptions { lte {seconds: 600} gte {seconds: 1} }]; + + // Maximum packet length for QUIC connections. It refers to the largest size of a QUIC packet that can be transmitted over the connection. + // If not specified, one of the `default values in QUICHE `_ is used. + google.protobuf.UInt64Value max_packet_length = 9; } message UpstreamHttpProtocolOptions { diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 5fd9945a3ad6..48ce7a15eac8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -167,6 +167,11 @@ bug_fixes: - area: http_async_client change: | Fixed the local reply and destroy order crashes when using the http async client for websocket handshake. +- area: http3 + change: | + Fixed a bug in the CONNECT-UDP forwarding mode where Envoy reset the upstream stream when it + received HTTP/3 datagrams before receiving the SETTINGS frame from the upstream peer. Envoy now + drops the datagrams in this case instead of resetting the stream. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` @@ -382,5 +387,9 @@ new_features: Added support to provide an override :ref:`authentication_header ` to load Basic Auth credential. +- area: quic + change: | + Added ``max_packet_length`` to the QUIC protocol options to allow users to change the largest + size of a QUIC packet that can be transmitted over the QUIC connection. deprecated: diff --git a/configs/proxy_connect_udp_http3_downstream.yaml b/configs/proxy_connect_udp_http3_downstream.yaml index 24967e488e18..4455eb68441d 100644 --- a/configs/proxy_connect_udp_http3_downstream.yaml +++ b/configs/proxy_connect_udp_http3_downstream.yaml @@ -58,6 +58,10 @@ static_resources: explicit_http_config: http3_protocol_options: allow_extended_connect: true + quic_protocol_options: + # Increase the max packet length for tunneling. + # The example value matches the default value used by the MASQUE client in QUICHE. + max_packet_length: 1350 load_assignment: cluster_name: cluster_0 endpoints: diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index 17802078a16f..f0f1a91b022d 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -5,9 +5,10 @@ namespace Envoy { namespace Quic { -PersistentQuicInfoImpl::PersistentQuicInfoImpl(Event::Dispatcher& dispatcher, uint32_t buffer_limit) +PersistentQuicInfoImpl::PersistentQuicInfoImpl(Event::Dispatcher& dispatcher, uint32_t buffer_limit, + quic::QuicByteCount max_packet_length) : conn_helper_(dispatcher), alarm_factory_(dispatcher, *conn_helper_.GetClock()), - buffer_limit_(buffer_limit) { + buffer_limit_(buffer_limit), max_packet_length_(max_packet_length) { quiche::FlagRegistry::getInstance(); } @@ -16,7 +17,9 @@ createPersistentQuicInfoForCluster(Event::Dispatcher& dispatcher, const Upstream::ClusterInfo& cluster) { auto quic_info = std::make_unique( dispatcher, cluster.perConnectionBufferLimitBytes()); - Quic::convertQuicConfig(cluster.http3Options().quic_protocol_options(), quic_info->quic_config_); + const envoy::config::core::v3::QuicProtocolOptions& quic_config = + cluster.http3Options().quic_protocol_options(); + Quic::convertQuicConfig(quic_config, quic_info->quic_config_); quic::QuicTime::Delta crypto_timeout = quic::QuicTime::Delta::FromMilliseconds(cluster.connectTimeout().count()); @@ -25,6 +28,8 @@ createPersistentQuicInfoForCluster(Event::Dispatcher& dispatcher, quic_info->quic_config_.max_idle_time_before_crypto_handshake()) { quic_info->quic_config_.set_max_idle_time_before_crypto_handshake(crypto_timeout); } + quic_info->max_packet_length_ = + PROTOBUF_GET_WRAPPED_OR_DEFAULT(quic_config, max_packet_length, 0); return quic_info; } @@ -50,6 +55,10 @@ std::unique_ptr createQuicNetworkConnection( quic::QuicUtils::CreateRandomConnectionId(), server_addr, info_impl->conn_helper_, info_impl->alarm_factory_, quic_versions, local_addr, dispatcher, options, generator, Runtime::runtimeFeatureEnabled("envoy.reloadable_features.prefer_quic_client_udp_gro")); + // Override the max packet length of the QUIC connection if the option value is not 0. + if (info_impl->max_packet_length_ > 0) { + connection->SetMaxPacketLength(info_impl->max_packet_length_); + } // TODO (danzh) move this temporary config and initial RTT configuration to h3 pool. quic::QuicConfig config = info_impl->quic_config_; diff --git a/source/common/quic/client_connection_factory_impl.h b/source/common/quic/client_connection_factory_impl.h index 9e47687ac14b..7d955602c43f 100644 --- a/source/common/quic/client_connection_factory_impl.h +++ b/source/common/quic/client_connection_factory_impl.h @@ -21,7 +21,8 @@ namespace Quic { // TODO(danzh) considering exposing these QUICHE interfaces via base class virtual methods, so that // down casting can be avoided while passing around this object. struct PersistentQuicInfoImpl : public Http::PersistentQuicInfo { - PersistentQuicInfoImpl(Event::Dispatcher& dispatcher, uint32_t buffer_limit); + PersistentQuicInfoImpl(Event::Dispatcher& dispatcher, uint32_t buffer_limit, + quic::QuicByteCount max_packet_length = 0); EnvoyQuicConnectionHelper conn_helper_; EnvoyQuicAlarmFactory alarm_factory_; @@ -30,6 +31,9 @@ struct PersistentQuicInfoImpl : public Http::PersistentQuicInfo { const uint32_t buffer_limit_; // Hard code with the default crypto stream as there's no pluggable crypto for upstream Envoy. EnvoyQuicCryptoClientStreamFactoryImpl crypto_stream_factory_; + // Override the maximum packet length of connections for tunneling. Use the default length in + // QUICHE if this is set to 0. + quic::QuicByteCount max_packet_length_; }; std::unique_ptr diff --git a/source/common/quic/http_datagram_handler.cc b/source/common/quic/http_datagram_handler.cc index 929e848209dc..0c2fbe8912f1 100644 --- a/source/common/quic/http_datagram_handler.cc +++ b/source/common/quic/http_datagram_handler.cc @@ -56,7 +56,8 @@ bool HttpDatagramHandler::OnCapsule(const quiche::Capsule& capsule) { quic::MessageStatusToString(status))); return true; } - if (status == quic::MessageStatus::MESSAGE_STATUS_TOO_LARGE) { + if (status == quic::MessageStatus::MESSAGE_STATUS_TOO_LARGE || + status == quic::MessageStatus::MESSAGE_STATUS_SETTINGS_NOT_RECEIVED) { ENVOY_LOG(warn, fmt::format("SendHttpH3Datagram failed: status = {}, drops the Datagram.", quic::MessageStatusToString(status))); return true; diff --git a/test/common/quic/http_datagram_handler_test.cc b/test/common/quic/http_datagram_handler_test.cc index 19c7e9869c70..1efeeb8f07f2 100644 --- a/test/common/quic/http_datagram_handler_test.cc +++ b/test/common/quic/http_datagram_handler_test.cc @@ -100,12 +100,21 @@ TEST_F(HttpDatagramHandlerTest, SendCapsulesWithUnknownType) { /*end_stream=*/false)); } -TEST_F(HttpDatagramHandlerTest, SendHttp3DatagramError) { +TEST_F(HttpDatagramHandlerTest, SendHttp3DatagramInternalError) { EXPECT_CALL(stream_, SendHttp3Datagram(_)) .WillOnce(testing::Return(quic::MessageStatus::MESSAGE_STATUS_INTERNAL_ERROR)); EXPECT_FALSE( http_datagram_handler_.encodeCapsuleFragment(capsule_fragment_, /*end_stream*/ false)); } +TEST_F(HttpDatagramHandlerTest, SendHttp3DatagramTooEarly) { + // If SendHttp3Datagram is called before receiving SETTINGS from a peer, HttpDatagramHandler + // drops the datagram without resetting the stream. + EXPECT_CALL(stream_, SendHttp3Datagram(_)) + .WillOnce(testing::Return(quic::MessageStatus::MESSAGE_STATUS_SETTINGS_NOT_RECEIVED)); + EXPECT_TRUE( + http_datagram_handler_.encodeCapsuleFragment(capsule_fragment_, /*end_stream*/ false)); +} + } // namespace Quic } // namespace Envoy