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..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,9 +206,12 @@ 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. + bool flush_access_log_on_start = 3; } reserved 6; diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a49fbf2954fe4..df6d19c3e9864 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -96,5 +96,8 @@ new_features: change: | Added ``%UPSTREAM_DETECTED_CLOSE_TYPE%`` and ``%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/docs/root/configuration/advanced/substitution_formatter.rst b/docs/root/configuration/advanced/substitution_formatter.rst index 25ebce9c931c3..6d988206a2bea 100644 --- a/docs/root/configuration/advanced/substitution_formatter.rst +++ b/docs/root/configuration/advanced/substitution_formatter.rst @@ -1477,6 +1477,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. diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 05e6cfd264a8c..ded2d89514585 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 3173ae132472e..1c35153cc4104 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -637,6 +637,43 @@ 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",