syntax = "proto3";

package envoy.extensions.filters.network.tcp_proxy.v3;

import "envoy/config/accesslog/v3/accesslog.proto";
import "envoy/config/core/v3/backoff.proto";
import "envoy/config/core/v3/base.proto";
import "envoy/config/core/v3/config_source.proto";
import "envoy/config/core/v3/proxy_protocol.proto";
import "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto";
import "envoy/type/v3/hash_policy.proto";
import "envoy/type/v3/percent.proto";

import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";

import "envoy/annotations/deprecation.proto";
import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.network.tcp_proxy.v3";
option java_outer_classname = "TcpProxyProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3;tcp_proxyv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: TCP Proxy]
// TCP Proxy :ref:`configuration overview <config_network_filters_tcp_proxy>`.
// [#extension: envoy.filters.network.tcp_proxy]

// Specifies when the TCP proxy establishes the upstream connection.
enum UpstreamConnectMode {
  // Establish the upstream connection immediately when the downstream connection is accepted.
  // This is the default behavior and provides the lowest latency.
  IMMEDIATE = 0;

  // Wait for initial data from the downstream connection before establishing the upstream connection.
  // This allows preceding filters to inspect the initial data (e.g., extracting SNI from TLS ClientHello)
  // before the upstream connection is established.
  //
  // This mode requires ``max_early_data_bytes`` to be set.
  //
  // .. warning::
  //   This mode is not suitable for server-first protocols (e.g., SMTP, MySQL, POP3) where the
  //   server sends the initial greeting. For such protocols, use ``IMMEDIATE`` mode.
  ON_DOWNSTREAM_DATA = 1;

  // Wait for the downstream TLS handshake to complete before establishing the upstream connection.
  // This allows access to the full TLS connection information, including client certificates
  // and negotiated parameters, which can be used for routing decisions or passed as metadata
  // to the upstream.
  //
  // This mode requires ``max_early_data_bytes`` to be set (can be zero to disable buffering).
  //
  // .. note::
  //   This mode is only effective when the downstream connection uses TLS. For non-TLS
  //   connections, it behaves the same as ``IMMEDIATE``.
  ON_DOWNSTREAM_TLS_HANDSHAKE = 2;
}

// Specifies how TLVs in ``proxy_protocol_tlvs`` are merged with existing PROXY protocol state
// (e.g., downstream TLVs parsed by the proxy_protocol listener filter).
enum ProxyProtocolTlvMergePolicy {
  // Add configured TLVs only if no PROXY protocol state exists (e.g., no downstream TLVs).
  // If state exists, ignore configured TLVs and use only the existing TLVs.
  // This is the default for backward compatibility.
  ADD_IF_ABSENT = 0;

  // Overwrite existing TLVs (e.g., downstream TLVs) by type with configured TLVs.
  // Non-conflicting TLVs from both sources are preserved.
  // If no state exists, add all configured TLVs.
  // Source/destination addresses from existing state are preserved.
  OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD = 1;

  // Append configured TLVs to existing TLVs (e.g., downstream TLVs), preserving all TLVs
  // from both sources (PROXY protocol v2 allows duplicate types).
  // If no state exists, add all configured TLVs.
  // Source/destination addresses from existing state are preserved.
  APPEND_IF_EXISTS_OR_ADD = 2;
}

// [#next-free-field: 24]
message TcpProxy {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.config.filter.network.tcp_proxy.v2.TcpProxy";

  // Allows specification of multiple upstream clusters along with weights indicating the percentage of
  // traffic forwarded to each cluster. The cluster selection is based on these weights.
  message WeightedCluster {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.config.filter.network.tcp_proxy.v2.TcpProxy.WeightedCluster";

    message ClusterWeight {
      option (udpa.annotations.versioning).previous_message_type =
          "envoy.config.filter.network.tcp_proxy.v2.TcpProxy.WeightedCluster.ClusterWeight";

      // Name of the upstream cluster.
      string name = 1 [(validate.rules).string = {min_len: 1}];

      // When a request matches the route, the choice of an upstream cluster is
      // determined by its weight. The sum of weights across all entries in the
      // clusters array determines the total weight.
      uint32 weight = 2 [(validate.rules).uint32 = {gte: 1}];

      // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints
      // in the upstream cluster with metadata matching what is set in this field will be considered
      // for load balancing. Note that this will be merged with what's provided in
      // :ref:`TcpProxy.metadata_match
      // <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.metadata_match>`, with values
      // here taking precedence. The filter name should be specified as ``envoy.lb``.
      config.core.v3.Metadata metadata_match = 3;
    }

    // Specifies the upstream clusters associated with this configuration.
    repeated ClusterWeight clusters = 1 [(validate.rules).repeated = {min_items: 1}];
  }

  // Configuration for tunneling TCP over other transports or application layers.
  // Tunneling is supported over HTTP/1.1 and HTTP/2. The upstream protocol is
  // determined by the cluster configuration.
  // [#next-free-field: 10]
  message TunnelingConfig {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.config.filter.network.tcp_proxy.v2.TcpProxy.TunnelingConfig";

    // The hostname to send in the synthesized CONNECT headers to the upstream proxy.
    // This field evaluates command operators if present; otherwise, the value is used as-is.
    //
    // For example, dynamically set the hostname using downstream SNI:
    //
    // .. code-block:: yaml
    //
    //    tunneling_config:
    //      hostname: "%REQUESTED_SERVER_NAME%:443"
    //
    // For example, dynamically set the hostname using dynamic metadata:
    //
    // .. code-block:: yaml
    //
    //    tunneling_config:
    //      hostname: "%DYNAMIC_METADATA(tunnel:address)%"
    //
    string hostname = 1 [(validate.rules).string = {min_len: 1}];

    // Use the ``POST`` method instead of the ``CONNECT`` method to tunnel the TCP stream.
    // The ``protocol: bytestream`` header is not set for HTTP/2 to comply with the specification.
    //
    // The upstream proxy is expected to interpret the POST payload as raw TCP.
    bool use_post = 2;

    // Additional request headers to send to the upstream proxy. This is mainly used to
    // trigger the upstream to convert POST requests back to CONNECT requests.
    //
    // Neither ``:``-prefixed pseudo-headers like ``:path`` nor the ``host`` header can be overridden.
    repeated config.core.v3.HeaderValueOption headers_to_add = 3
        [(validate.rules).repeated = {max_items: 1000}];

    // Save response headers to the downstream connection's filter state for consumption
    // by network filters. The filter state key is ``envoy.tcp_proxy.propagate_response_headers``.
    bool propagate_response_headers = 4;

    // The path used with the POST method. The default path is ``/``. If this field is specified and
    // :ref:`use_post field <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TunnelingConfig.use_post>`
    // 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
    // by network filters. The filter state key is ``envoy.tcp_proxy.propagate_response_trailers``.
    bool propagate_response_trailers = 6;

    // The configuration of the request ID extension used for generation, validation, and
    // associated tracing operations when tunneling.
    //
    // If this field is set, a request ID is generated using the specified extension. If
    // this field is not set, no request ID is generated.
    //
    // When a request ID is generated, it is also stored in the downstream connection's
    // dynamic metadata under the namespace ``envoy.filters.network.tcp_proxy`` with the key
    // ``tunnel_request_id`` to allow emission from TCP proxy access logs via the
    // ``%DYNAMIC_METADATA(envoy.filters.network.tcp_proxy:tunnel_request_id)%`` formatter.
    // [#extension-category: envoy.request_id]
    http_connection_manager.v3.RequestIDExtension request_id_extension = 7;

    // The request header name to use for emitting the generated request ID on the tunneling
    // HTTP request.
    //
    // If not specified or set to an empty string, the default header name ``x-request-id`` is
    // used.
    //
    // .. note::
    //   This setting does not alter the internal request ID handling elsewhere in Envoy and
    //   only controls the header emitted on the tunneling request.
    string request_id_header = 8;

    // The dynamic metadata key to use when storing the generated request ID. The metadata is
    // stored under the namespace ``envoy.filters.network.tcp_proxy``.
    //
    // If not specified or set to an empty string, the default key ``tunnel_request_id`` is used.
    // This enables customizing the key used by access log formatters such as
    // ``%DYNAMIC_METADATA(envoy.filters.network.tcp_proxy:<key>)%``.
    string request_id_metadata_key = 9;
  }

  message OnDemand {
    // Optional configuration for the on-demand cluster discovery service.
    // If not specified, on-demand cluster discovery is disabled. When specified, the filter pauses a request
    // to an unknown cluster and begins a cluster discovery process. When discovery completes (successfully
    // or not), the request is resumed.
    config.core.v3.ConfigSource odcds_config = 1;

    // xdstp:// resource locator for on-demand cluster collection.
    // [#not-implemented-hide:]
    string resources_locator = 2;

    // The timeout for on-demand cluster lookup. If the CDS cannot return the required cluster,
    // the downstream request will be closed with the error code detail NO_CLUSTER_FOUND.
    // [#not-implemented-hide:]
    google.protobuf.Duration timeout = 3;
  }

  message TcpAccessLogOptions {
    // The interval for flushing access logs. By default, the TCP proxy flushes a single access log when the
    // connection is closed. If this field is set, the TCP proxy flushes access logs periodically at the
    // specified interval.
    // The interval must be at least 1ms.
    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
    // 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;

  reserved "deprecated_v1";

  // The prefix to use when emitting :ref:`statistics
  // <config_network_filters_tcp_proxy_stats>`.
  string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];

  oneof cluster_specifier {
    option (validate.required) = true;

    // The upstream cluster to connect to.
    string cluster = 2;

    // Multiple upstream clusters can be specified. The request is routed to one of the upstream clusters
    // based on the weights assigned to each cluster.
    WeightedCluster weighted_clusters = 10;
  }

  // The on demand policy for the upstream cluster.
  // It applies to both
  // :ref:`TcpProxy.cluster <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.cluster>`
  // and
  // :ref:`TcpProxy.weighted_clusters <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.weighted_clusters>`.
  OnDemand on_demand = 14;

  // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints
  // in the upstream cluster with metadata matching what is set in this field will be considered
  // for load balancing. The filter name should be specified as ``envoy.lb``.
  config.core.v3.Metadata metadata_match = 9;

  // The idle timeout for connections managed by the TCP proxy filter. The idle timeout is defined as the
  // period in which there are no bytes sent or received on either the upstream or downstream connection.
  // If not set, the default idle timeout is 1 hour. If set to ``0s``, the timeout is disabled.
  // It is possible to dynamically override this configuration by setting a per-connection filter state
  // object for the key ``envoy.tcp_proxy.per_connection_idle_timeout_ms``.
  //
  // .. warning::
  //   Disabling this timeout is likely to yield connection leaks due to lost TCP FIN packets, etc.
  google.protobuf.Duration idle_timeout = 8;

  // [#not-implemented-hide:] The idle timeout for connections managed by the TCP proxy
  // filter. The idle timeout is defined as the period in which there is no
  // active traffic. If not set, there is no idle timeout. When the idle timeout
  // is reached the connection will be closed. The distinction between
  // downstream_idle_timeout/upstream_idle_timeout provides a means to set
  // timeout based on the last byte sent on the downstream/upstream connection.
  google.protobuf.Duration downstream_idle_timeout = 3;

  // [#not-implemented-hide:]
  google.protobuf.Duration upstream_idle_timeout = 4;

  // Configuration for :ref:`access logs <arch_overview_access_logs>` emitted by this TCP proxy.
  repeated config.accesslog.v3.AccessLog access_log = 5;

  // The maximum number of unsuccessful connection attempts that will be made before
  // giving up. If the parameter is not specified, 1 connection attempt will be made.
  google.protobuf.UInt32Value max_connect_attempts = 7 [(validate.rules).uint32 = {gte: 1}];

  // Sets the backoff strategy. If not set, the retries are performed without backoff.
  config.core.v3.BackoffStrategy backoff_options = 18;

  // Optional configuration for TCP proxy hash policy. If hash_policy is not set, the hash-based
  // load balancing algorithms will select a host randomly. Currently the number of hash policies is
  // limited to 1.
  repeated type.v3.HashPolicy hash_policy = 11 [(validate.rules).repeated = {max_items: 1}];

  // If set, this configures tunneling, for example configuration options to tunnel TCP payload over
  // HTTP CONNECT. If this message is absent, the payload is proxied upstream as usual.
  // It is possible to dynamically override this configuration and disable tunneling per connection by
  // setting a per-connection filter state object for the key ``envoy.tcp_proxy.disable_tunneling``.
  TunnelingConfig tunneling_config = 12;

  // The maximum duration of a connection. The duration is defined as the period since a connection was
  // established. If not set, there is no maximum duration. When ``max_downstream_connection_duration`` is
  // reached, the connection is closed. The duration must be at least ``1ms``.
  google.protobuf.Duration max_downstream_connection_duration = 13
      [(validate.rules).duration = {gte {nanos: 1000000}}];

  // Percentage-based jitter for ``max_downstream_connection_duration``. The jitter increases the
  // ``max_downstream_connection_duration`` by a random duration up to the provided percentage.
  // This field is ignored if ``max_downstream_connection_duration`` is not set. If not set, no jitter
  // is added.
  type.v3.Percent max_downstream_connection_duration_jitter_percentage = 20;

  // If both this field and :ref:`access_log_flush_interval
  // <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TcpAccessLogOptions.access_log_flush_interval>`
  // are specified, the former (deprecated field) is ignored.
  //
  // .. attention::
  //   This field is deprecated in favor of
  //   :ref:`access_log_flush_interval
  //   <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TcpAccessLogOptions.access_log_flush_interval>`.
  google.protobuf.Duration access_log_flush_interval = 15 [
    deprecated = true,
    (validate.rules).duration = {gte {nanos: 1000000}},
    (envoy.annotations.deprecated_at_minor_version) = "3.0"
  ];

  // If both this field and :ref:`flush_access_log_on_connected
  // <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TcpAccessLogOptions.flush_access_log_on_connected>`
  // are specified, the former (deprecated field) is ignored.
  //
  // .. attention::
  //   This field is deprecated in favor of
  //   :ref:`flush_access_log_on_connected
  //   <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TcpAccessLogOptions.flush_access_log_on_connected>`.
  bool flush_access_log_on_connected = 16
      [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

  // Additional access log options for the TCP proxy.
  TcpAccessLogOptions access_log_options = 17;

  // TLVs to add to the PROXY protocol header sent upstream. Behavior when PROXY protocol
  // state already exists (e.g., downstream TLVs from proxy_protocol listener filter) is
  // controlled by ``proxy_protocol_tlv_merge_policy``.
  //
  // .. note::
  //   To ensure the TLVs are allowed upstream, configure passthrough TLVs on the upstream
  //   proxy protocol transport. See :ref:`core.v3.ProxyProtocolConfig.pass_through_tlvs
  //   <envoy_v3_api_field_config.core.v3.ProxyProtocolConfig.pass_through_tlvs>` for details.
  repeated config.core.v3.TlvEntry proxy_protocol_tlvs = 19;

  // Specifies how TLVs in ``proxy_protocol_tlvs`` are merged with existing PROXY protocol state
  // (e.g., downstream TLVs from the proxy_protocol listener filter). See
  // :ref:`ProxyProtocolTlvMergePolicy
  // <envoy_v3_api_enum_extensions.filters.network.tcp_proxy.v3.ProxyProtocolTlvMergePolicy>`.
  ProxyProtocolTlvMergePolicy proxy_protocol_tlv_merge_policy = 23
      [(validate.rules).enum = {defined_only: true}];

  // Specifies when to establish the upstream connection.
  //
  // When not specified, defaults to ``IMMEDIATE`` for backward compatibility.
  //
  // .. attention::
  //   Server-first protocols (e.g., SMTP, MySQL, POP3) require ``IMMEDIATE`` mode.
  UpstreamConnectMode upstream_connect_mode = 21 [(validate.rules).enum = {defined_only: true}];

  // Maximum bytes of early data to buffer from the downstream connection before
  // the upstream connection is established.
  //
  // If not set, the TCP proxy will read-disable the downstream connection until the
  // upstream connection is established (legacy behavior).
  //
  // If set, enables ``receive_before_connect`` mode where the filter allows the filter
  // chain to read downstream data before the upstream connection exists. The data is
  // buffered and forwarded once the upstream connection is ready. When the buffer exceeds
  // this limit, the downstream connection is read-disabled to prevent excessive memory usage.
  //
  // This field is required when ``upstream_connect_mode`` is not ``IMMEDIATE``.
  //
  // .. note::
  //   Use this carefully with server-first protocols. The upstream may send data before
  //   receiving anything from downstream, which could fill the early data buffer.
  google.protobuf.UInt32Value max_early_data_bytes = 22 [(validate.rules).uint32 = {lte: 1048576}];
}
