From 488e6932da096eb1f2f78066a1f6ef6c5d5980b0 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Thu, 13 Feb 2025 01:04:42 -0500 Subject: [PATCH] quic: Add option to disable QUIC early data (0-RTT) (#38434) This will be useful for certain mobile clients to disable early data / 0-RTT in the QUICHE layer itself. Signed-off-by: Ali Beyad --- .../quic_client_transport_socket_factory.cc | 4 ++ source/common/runtime/runtime_features.cc | 3 ++ .../multiplexed_upstream_integration_test.cc | 34 ++++++++++++++++ .../integration/quic_http_integration_test.cc | 39 +++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/source/common/quic/quic_client_transport_socket_factory.cc b/source/common/quic/quic_client_transport_socket_factory.cc index 0ec580f830b0..454c3c6c0618 100644 --- a/source/common/quic/quic_client_transport_socket_factory.cc +++ b/source/common/quic/quic_client_transport_socket_factory.cc @@ -88,6 +88,10 @@ std::shared_ptr QuicClientTransportSocketFactory:: std::make_unique(std::move(context), accept_untrusted), std::make_unique()); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.quic_disable_client_early_data")) { + tls_config.crypto_config_->ssl_config().early_data_enabled = false; + } CertCompression::registerSslContext(tls_config.crypto_config_->ssl_ctx()); } // Return the latest crypto config. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 1d85df4f10d1..efa9de6adf58 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -170,6 +170,9 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_allow_multiplexed_upstream_half_cl FALSE_RUNTIME_GUARD(envoy_reloadable_features_use_network_type_socket_option); // TODO(fredyw): Remove after prod testing. FALSE_RUNTIME_GUARD(envoy_reloadable_features_dns_nodata_noname_is_success); +// TODO(abeyad): Evaluate and either remove or make a config knob in +// https://github.com/envoyproxy/envoy/blob/main/api/envoy/extensions/transport_sockets/tls/v3/tls.proto#L29. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_disable_client_early_data); // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT diff --git a/test/integration/multiplexed_upstream_integration_test.cc b/test/integration/multiplexed_upstream_integration_test.cc index a73f3b56d617..5fc82f5ae0b9 100644 --- a/test/integration/multiplexed_upstream_integration_test.cc +++ b/test/integration/multiplexed_upstream_integration_test.cc @@ -791,6 +791,40 @@ TEST_P(MultiplexedUpstreamIntegrationTest, DisableUpstreamEarlyData) { ASSERT_TRUE(response2->waitForEndStream()); } +TEST_P(MultiplexedUpstreamIntegrationTest, ClientDisableUpstreamEarlyData) { +#ifdef WIN32 + // TODO: debug why waiting on the 2nd upstream connection times out on Windows. + GTEST_SKIP() << "Skipping on Windows"; +#endif + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_disable_client_early_data", + "true"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + upstream_request_.reset(); + + ASSERT_TRUE(fake_upstream_connection_->close()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + fake_upstream_connection_.reset(); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_destroy", 1); + + EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); + + default_request_headers_.addCopy("second_request", "1"); + auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + + EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_cx_connect_with_0_rtt")->value()); + EXPECT_EQ(0u, test_server_->counter("cluster.cluster_0.upstream_rq_0rtt")->value()); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response2->waitForEndStream()); +} + // Tests that Envoy will automatically retry a GET request sent over early data if the upstream // rejects it with TooEarly response. TEST_P(MultiplexedUpstreamIntegrationTest, UpstreamEarlyDataRejected) { diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index d3321e72861a..b9283fff629b 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -828,6 +828,45 @@ TEST_P(QuicHttpIntegrationTest, EarlyDataDisabled) { codec_client_->close(); } +// Envoy Mobile does not have listeners, so the above test is not applicable. +// This test ensures that a mobile client can connect when early data is disabled on the QUICHE +// layer. +TEST_P(QuicHttpIntegrationTest, ClientEarlyDataDisabled) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.quic_disable_client_early_data", + "true"); + // Make sure all connections use the same PersistentQuicInfoImpl. + concurrency_ = 1; + initialize(); + // Start the first connection. + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + EXPECT_EQ(transport_socket_factory_->clientContextConfig()->serverNameIndication(), + codec_client_->connection()->requestedServerName()); + // Send a complete request on the first connection. + auto response1 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response1->waitForEndStream()); + // Close the first connection. + codec_client_->close(); + + // Start a second connection. + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + // Send a complete request on the second connection. + auto response2 = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response2->waitForEndStream()); + // Ensure the 2nd connection is using resumption ticket but doesn't accept early data. + EnvoyQuicClientSession* quic_session = + static_cast(codec_client_->connection()); + EXPECT_TRUE(quic_session->IsResumption()); + EXPECT_FALSE(quic_session->EarlyDataAccepted()); + EXPECT_TRUE(upstream_request_->headers().get(Http::Headers::get().EarlyData).empty()); + + // Close the second connection. + codec_client_->close(); +} + // Not only test multiple quic connections, but disconnect and reconnect to // trigger resumption. TEST_P(QuicHttpIntegrationTest, MultipleUpstreamQuicConnections) {