diff --git a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index f99f4059166b5..271dcfbe49cec 100644 --- a/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -77,6 +77,9 @@ message HttpProtocolOptions { // If this is used, the cluster can use either of the configured protocols, and // will use whichever protocol was used by the downstream connection. + // + // If HTTP/3 is configured for downstream and not configured for upstream, + // HTTP/3 requests will fail over to HTTP/2. message UseDownstreamHttpConfig { config.core.v3.Http1ProtocolOptions http_protocol_options = 1; diff --git a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index 10971c2587f04..d69966ef92d36 100644 --- a/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/api/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -84,6 +84,9 @@ message HttpProtocolOptions { // If this is used, the cluster can use either of the configured protocols, and // will use whichever protocol was used by the downstream connection. + // + // If HTTP/3 is configured for downstream and not configured for upstream, + // HTTP/3 requests will fail over to HTTP/2. message UseDownstreamHttpConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions.UseDownstreamHttpConfig"; diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto index f99f4059166b5..271dcfbe49cec 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v3/http_protocol_options.proto @@ -77,6 +77,9 @@ message HttpProtocolOptions { // If this is used, the cluster can use either of the configured protocols, and // will use whichever protocol was used by the downstream connection. + // + // If HTTP/3 is configured for downstream and not configured for upstream, + // HTTP/3 requests will fail over to HTTP/2. message UseDownstreamHttpConfig { config.core.v3.Http1ProtocolOptions http_protocol_options = 1; diff --git a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto index 10971c2587f04..d69966ef92d36 100644 --- a/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto +++ b/generated_api_shadow/envoy/extensions/upstreams/http/v4alpha/http_protocol_options.proto @@ -84,6 +84,9 @@ message HttpProtocolOptions { // If this is used, the cluster can use either of the configured protocols, and // will use whichever protocol was used by the downstream connection. + // + // If HTTP/3 is configured for downstream and not configured for upstream, + // HTTP/3 requests will fail over to HTTP/2. message UseDownstreamHttpConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions.UseDownstreamHttpConfig"; diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 63c68663ee0f3..e0ed1d8058d4f 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -886,6 +886,10 @@ std::vector ClusterInfoImpl::upstreamHttpProtocol(absl::optional downstream_protocol) const { if (downstream_protocol.has_value() && features_ & Upstream::ClusterInfo::Features::USE_DOWNSTREAM_PROTOCOL) { + if (downstream_protocol.value() == Http::Protocol::Http3 && + !(features_ & Upstream::ClusterInfo::Features::HTTP3)) { + return {Http::Protocol::Http2}; + } return {downstream_protocol.value()}; } diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 969fd4211e966..e47c67ff66085 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -3131,7 +3131,7 @@ TEST_F(ClusterInfoImplTest, ExtensionProtocolOptionsForFilterWithOptions) { } } -TEST_F(ClusterInfoImplTest, UseDownstreamHttpProtocol) { +TEST_F(ClusterInfoImplTest, UseDownstreamHttpProtocolWithDowngrade) { const std::string yaml = R"EOF( name: name connect_timeout: 0.25s @@ -3148,7 +3148,8 @@ TEST_F(ClusterInfoImplTest, UseDownstreamHttpProtocol) { cluster->info()->upstreamHttpProtocol({Http::Protocol::Http11})[0]); EXPECT_EQ(Http::Protocol::Http2, cluster->info()->upstreamHttpProtocol({Http::Protocol::Http2})[0]); - EXPECT_EQ(Http::Protocol::Http3, + // This will get downgraded because the cluster does not support HTTP/3 + EXPECT_EQ(Http::Protocol::Http2, cluster->info()->upstreamHttpProtocol({Http::Protocol::Http3})[0]); } @@ -3367,6 +3368,58 @@ TEST_F(ClusterInfoImplTest, Http3Auto) { auto_h3->info()->http3Options().quic_protocol_options().max_concurrent_streams().value(), 2); } +TEST_F(ClusterInfoImplTest, UseDownstreamHttpProtocolWithoutDowngrade) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + name: name + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: MAGLEV + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + transport_socket: + name: envoy.transport_sockets.quic + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport + upstream_tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + match_subject_alt_names: + - exact: localhost + - exact: 127.0.0.1 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + use_downstream_protocol_config: + http3_protocol_options: {} + common_http_protocol_options: + idle_timeout: 1s + )EOF", + Network::Address::IpVersion::v4); + auto cluster = makeCluster(yaml); + + EXPECT_EQ(Http::Protocol::Http10, + cluster->info()->upstreamHttpProtocol({Http::Protocol::Http10})[0]); + EXPECT_EQ(Http::Protocol::Http11, + cluster->info()->upstreamHttpProtocol({Http::Protocol::Http11})[0]); + EXPECT_EQ(Http::Protocol::Http2, + cluster->info()->upstreamHttpProtocol({Http::Protocol::Http2})[0]); + EXPECT_EQ(Http::Protocol::Http3, + cluster->info()->upstreamHttpProtocol({Http::Protocol::Http3})[0]); +} + #else TEST_F(ClusterInfoImplTest, Http3BadConfig) { const std::string yaml = TestEnvironment::substitute(R"EOF(