Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: fix issues in CONNECT-UDP forwarding mode. #36174

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
6 changes: 5 additions & 1 deletion api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 <https://github.com/google/quiche/blob/main/quiche/quic/core/quic_constants.h>`_ is used.
google.protobuf.UInt64Value max_packet_length = 9;
Copy link
Contributor Author

@jeongseokson jeongseokson Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comments, @RyanTheOptimist and @DavidSchinazi. I will send more commits soon but want to quickly check if we should set a reasonable bound for the max_packet_length option like other options in this config. One range I can think of is 1 <= max_packet_length <= 65,535 (max IP packet size), but I'm wondering if we should just not set the bound here. I used UInt64Value since the corresponding QUICHE variable and constants (e.g. https://github.com/google/quiche/blob/4249f8025caed1e3d71d04d9cadf42251acb7cac/quiche/quic/core/quic_constants.h#L32) use QuicByteCount, which is uint64_t internally. Let me know if you have any thoughts on these.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think Envoy needs to put a cap on this value. Internally, QUICHE will cap it at 1452, but we shouldn't duplicate that constant or logic here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM. Let me know if you have any other comments on the new code.

}

message UpstreamHttpProtocolOptions {
Expand Down
9 changes: 9 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <deprecated>`
Expand Down Expand Up @@ -382,5 +387,9 @@ new_features:
Added support to provide an override
:ref:`authentication_header <envoy_v3_api_field_extensions.filters.http.basic_auth.v3.BasicAuth.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:
4 changes: 4 additions & 0 deletions configs/proxy_connect_udp_http3_downstream.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 12 additions & 3 deletions source/common/quic/client_connection_factory_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -16,7 +17,9 @@ createPersistentQuicInfoForCluster(Event::Dispatcher& dispatcher,
const Upstream::ClusterInfo& cluster) {
auto quic_info = std::make_unique<Quic::PersistentQuicInfoImpl>(
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());

Expand All @@ -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;
}

Expand All @@ -50,6 +55,10 @@ std::unique_ptr<Network::ClientConnection> 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_;
Expand Down
6 changes: 5 additions & 1 deletion source/common/quic/client_connection_factory_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand All @@ -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<PersistentQuicInfoImpl>
Expand Down
3 changes: 2 additions & 1 deletion source/common/quic/http_datagram_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 10 additions & 1 deletion test/common/quic/http_datagram_handler_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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