From 8252e86db2f46df011deb2c671e666763ead9a36 Mon Sep 17 00:00:00 2001 From: Kuat Yessenov Date: Sat, 24 Jan 2026 00:12:48 +0000 Subject: [PATCH 1/3] tcp_proxy: add log on start Change-Id: Ib8485b0d970f05dc9e4f93b01f8dfd81c54662dd Signed-off-by: Kuat Yessenov --- api/envoy/data/accesslog/v3/accesslog.proto | 1 + .../network/tcp_proxy/v3/tcp_proxy.proto | 3 ++ changelogs/current.yaml | 3 ++ source/common/tcp_proxy/tcp_proxy.cc | 6 ++- source/common/tcp_proxy/tcp_proxy.h | 5 ++- test/common/tcp_proxy/config_test.cc | 26 +++++++++++++ .../integration/tcp_proxy_integration_test.cc | 39 +++++++++++++++++++ 7 files changed, 81 insertions(+), 2 deletions(-) diff --git a/api/envoy/data/accesslog/v3/accesslog.proto b/api/envoy/data/accesslog/v3/accesslog.proto index da029b7da2e8d..6f476c6ed6436 100644 --- a/api/envoy/data/accesslog/v3/accesslog.proto +++ b/api/envoy/data/accesslog/v3/accesslog.proto @@ -36,6 +36,7 @@ enum AccessLogType { NotSet = 0; TcpUpstreamConnected = 1; TcpPeriodic = 2; + TcpConnectionStart = 14; TcpConnectionEnd = 3; DownstreamStart = 4; DownstreamPeriodic = 5; diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index ea04cd9172a6f..32b9d76b8d519 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -209,6 +209,9 @@ message TcpProxy { // If set to true, the access log is flushed when the TCP proxy successfully establishes a // connection with the upstream. If the connection fails, the access log is not flushed. bool flush_access_log_on_connected = 2; + + // If set to true, the access log is flushed when the TCP proxy accepts a connection. + bool flush_access_log_on_start = 3; } reserved 6; diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 7a52db5b3b299..77f24acbb3d0c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -75,5 +75,8 @@ new_features: change: | Added ``%DOWNSTREAM_DETECTED_CLOSE_TYPE%`` to expose the detected close type of downstream and connections. The possible values are ``Normal``, ``LocalReset``, and ``RemoteReset``. +- area: tcp_proxy + change: | + Added an option to emit a log entry when the connection is accepted. deprecated: diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 6e38448531984..bf37241b071d8 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -119,7 +119,8 @@ Config::SharedConfig::SharedConfig( const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config, Server::Configuration::FactoryContext& context) : stats_scope_(context.scope().createScope(fmt::format("tcp.{}", config.stat_prefix()))), - stats_(generateStats(*stats_scope_)) { + stats_(generateStats(*stats_scope_)), + flush_access_log_on_start_(config.access_log_options().flush_access_log_on_start()) { if (config.has_idle_timeout()) { const uint64_t timeout = DurationUtil::durationToMilliseconds(config.idle_timeout()); if (timeout > 0) { @@ -1071,6 +1072,9 @@ Network::FilterStatus Filter::onNewConnection() { // Set UUID for the connection. This is used for logging and tracing. getStreamInfo().setStreamIdProvider( std::make_shared(config_->randomGenerator().uuid())); + if (config_->flushAccessLogOnStart()) { + flushAccessLog(AccessLog::AccessLogType::TcpConnectionStart); + } ASSERT(upstream_ == nullptr); diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 0d105136388c1..0605073381b8e 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -244,6 +244,7 @@ class Config { const TcpProxyStats& stats() { return stats_; } const absl::optional& idleTimeout() { return idle_timeout_; } bool flushAccessLogOnConnected() const { return flush_access_log_on_connected_; } + bool flushAccessLogOnStart() const { return flush_access_log_on_start_; } const absl::optional& maxDownstreamConnectionDuration() const { return max_downstream_connection_duration_; } @@ -287,7 +288,8 @@ class Config { const Stats::ScopeSharedPtr stats_scope_; const TcpProxyStats stats_; - bool flush_access_log_on_connected_; + bool flush_access_log_on_connected_ : 1; + const bool flush_access_log_on_start_ : 1; absl::optional idle_timeout_; absl::optional max_downstream_connection_duration_; absl::optional max_downstream_connection_duration_jitter_percentage_; @@ -355,6 +357,7 @@ class Config { const OnDemandStats& onDemandStats() const { return shared_config_->onDemandConfig()->stats(); } Random::RandomGenerator& randomGenerator() { return random_generator_; } bool flushAccessLogOnConnected() const { return shared_config_->flushAccessLogOnConnected(); } + bool flushAccessLogOnStart() const { return shared_config_->flushAccessLogOnStart(); } Regex::Engine& regexEngine() const { return regex_engine_; } const BackOffStrategyPtr& backoffStrategy() const { return shared_config_->backoffStrategy(); }; const Network::ProxyProtocolTLVVector& proxyProtocolTLVs() const { diff --git a/test/common/tcp_proxy/config_test.cc b/test/common/tcp_proxy/config_test.cc index 5c2007b0b11e9..f9e06b3bfae1a 100644 --- a/test/common/tcp_proxy/config_test.cc +++ b/test/common/tcp_proxy/config_test.cc @@ -72,6 +72,32 @@ TEST(ConfigTest, FlushAccessLogOnConnected) { } } +TEST(ConfigTest, FlushAccessLogOnStart) { + NiceMock factory_context; + + { + const std::string yaml = R"EOF( + stat_prefix: name + cluster: foo + )EOF"; + + Config config_obj(constructConfigFromYaml(yaml, factory_context)); + EXPECT_FALSE(config_obj.sharedConfig()->flushAccessLogOnStart()); + } + + { + const std::string yaml = R"EOF( + stat_prefix: name + cluster: foo + access_log_options: + flush_access_log_on_start: true + )EOF"; + + Config config_obj(constructConfigFromYaml(yaml, factory_context)); + EXPECT_TRUE(config_obj.sharedConfig()->flushAccessLogOnStart()); + } +} + TEST(ConfigTest, DEPRECATED_FEATURE_TEST(DeprecatedFlushAccessLogOnConnected)) { NiceMock factory_context; diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index 89561d9ea97f4..301a9dd0443b5 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -590,6 +590,45 @@ TEST_P(TcpProxyIntegrationTest, AccessLogOnUpstreamConnect) { EXPECT_GT(upstream_connection_id, 0); } +TEST_P(TcpProxyIntegrationTest, AccessLogOnStart) { + std::string access_log_path = TestEnvironment::temporaryPath( + fmt::format("access_log{}{}.txt", version_ == Network::Address::IpVersion::v4 ? "v4" : "v6", + TestUtility::uniqueFilename())); + + setupByteMeterAccessLog(); + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter_chain = listener->mutable_filter_chains(0); + auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); + + ASSERT_TRUE(config_blob->Is()); + auto tcp_proxy_config = + MessageUtil::anyConvert( + *config_blob); + + tcp_proxy_config.mutable_access_log_options()->set_flush_access_log_on_start(true); + auto* access_log = tcp_proxy_config.add_access_log(); + access_log->set_name("accesslog"); + envoy::extensions::access_loggers::file::v3::FileAccessLog access_log_config; + access_log_config.set_path(access_log_path); + access_log_config.mutable_log_format()->mutable_text_format_source()->set_inline_string( + "%ACCESS_LOG_TYPE%\n"); + access_log->mutable_typed_config()->PackFrom(access_log_config); + config_blob->PackFrom(tcp_proxy_config); + }); + + initialize(); + auto tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + auto log_result = waitForAccessLog(access_log_path); + EXPECT_EQ( + AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionStart), + log_result); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->close()); +} + TEST_P(TcpProxyIntegrationTest, PeriodicAccessLog) { std::string access_log_path = TestEnvironment::temporaryPath( fmt::format("access_log{}{}.txt", version_ == Network::Address::IpVersion::v4 ? "v4" : "v6", From b902df37997dedbc1664bdb3a919130644809ffe Mon Sep 17 00:00:00 2001 From: Kuat Yessenov Date: Sat, 24 Jan 2026 00:18:23 +0000 Subject: [PATCH 2/3] forgot doc Change-Id: I064939e1c6c455385a9fe3b4a34dff30e56a4598 Signed-off-by: Kuat Yessenov --- docs/root/configuration/advanced/substitution_formatter.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/root/configuration/advanced/substitution_formatter.rst b/docs/root/configuration/advanced/substitution_formatter.rst index ab005b6cfff93..3ff3dea3f3968 100644 --- a/docs/root/configuration/advanced/substitution_formatter.rst +++ b/docs/root/configuration/advanced/substitution_formatter.rst @@ -1474,6 +1474,7 @@ Current supported substitution commands include: * ``TcpUpstreamConnected`` - When TCP Proxy filter has successfully established an upstream connection. * ``TcpPeriodic`` - On any TCP Proxy filter periodic log record. * ``TcpConnectionEnd`` - When a TCP connection is ended on TCP Proxy filter. + * ``TcpConnectionStart`` - When a TCP connection is accepted by the TCP Proxy filter. * ``DownstreamStart`` - When HTTP Connection Manager filter receives a new HTTP request. * ``DownstreamTunnelSuccessfullyEstablished`` - When the HTTP Connection Manager sends response headers indicating a successful HTTP tunnel. * ``DownstreamPeriodic`` - On any HTTP Connection Manager periodic log record. From 8b95f9d48cbf7e7a63ac6372b94a709b5986d5a2 Mon Sep 17 00:00:00 2001 From: Kuat Yessenov Date: Sat, 24 Jan 2026 04:16:39 +0000 Subject: [PATCH 3/3] review Change-Id: I6707365009f06fb503c4fbd7018013c188c925d7 Signed-off-by: Kuat Yessenov --- .../extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto | 6 +++--- test/integration/tcp_proxy_integration_test.cc | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index 32b9d76b8d519..7b5fc45d2eace 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -141,7 +141,7 @@ message TcpProxy { // The path used with the POST method. The default path is ``/``. If this field is specified and // :ref:`use_post field ` - // is not set to true, the configuration will be rejected. + // is not set to ``true``, the configuration will be rejected. string post_path = 5; // Save response trailers to the downstream connection's filter state for consumption @@ -206,11 +206,11 @@ message TcpProxy { google.protobuf.Duration access_log_flush_interval = 1 [(validate.rules).duration = {gte {nanos: 1000000}}]; - // If set to true, the access log is flushed when the TCP proxy successfully establishes a + // If set to ``true``, the access log is flushed when the TCP proxy successfully establishes a // connection with the upstream. If the connection fails, the access log is not flushed. bool flush_access_log_on_connected = 2; - // If set to true, the access log is flushed when the TCP proxy accepts a connection. + // If set to ``true``, the access log is flushed when the TCP proxy accepts a connection. bool flush_access_log_on_start = 3; } diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index ccb88210a4cbb..1c35153cc4104 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -667,9 +667,7 @@ TEST_P(TcpProxyIntegrationTest, AccessLogOnStart) { initialize(); auto tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); auto log_result = waitForAccessLog(access_log_path); - EXPECT_EQ( - AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionStart), - log_result); + EXPECT_EQ(AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionStart), log_result); FakeRawConnectionPtr fake_upstream_connection; ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); tcp_client->close();