// Copyright 2025 Envoy Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package envoy.extensions.filters.udp.udp_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/udp_socket_config.proto";

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

import "xds/annotations/v3/status.proto";
import "xds/type/matcher/v3/matcher.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.udp.udp_proxy.v3";
option java_outer_classname = "UdpProxyProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/udp/udp_proxy/v3;udp_proxyv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: UDP proxy]
// UDP proxy :ref:`configuration overview <config_udp_listener_filters_udp_proxy>`.
// [#extension: envoy.filters.udp_listener.udp_proxy]

// Configuration for the UDP proxy filter.
// [#next-free-field: 14]
message UdpProxyConfig {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.config.filter.udp.udp_proxy.v2alpha.UdpProxyConfig";

  // Specifies the UDP hash policy.
  // The packets can be routed by hash policy.
  message HashPolicy {
    oneof policy_specifier {
      option (validate.required) = true;

      // The source IP will be used to compute the hash used by hash-based load balancing algorithms.
      bool source_ip = 1 [(validate.rules).bool = {const: true}];

      // A given key will be used to compute the hash used by hash-based load balancing algorithms.
      // In certain cases there is a need to direct different UDP streams jointly towards the selected set of endpoints.
      // A possible use-case is VoIP telephony, where media (RTP) and its corresponding control (RTCP) belong to the same logical session,
      // although they travel in separate streams. To ensure that these pair of streams are load-balanced on session level
      // (instead of individual stream level), dynamically created listeners can use the same hash key for each stream in the session.
      string key = 2 [(validate.rules).string = {min_len: 1}];
    }
  }

  // Configuration for UDP session filters.
  message SessionFilter {
    // The name of the filter configuration.
    string name = 1 [(validate.rules).string = {min_len: 1}];

    oneof config_type {
      // Filter specific configuration which depends on the filter being
      // instantiated. See the supported filters for further documentation.
      google.protobuf.Any typed_config = 2;

      // Configuration source specifier for an extension configuration discovery
      // service. In case of a failure and without the default configuration, the
      // UDP session will be removed.
      config.core.v3.ExtensionConfigSource config_discovery = 3;
    }
  }

  // Configuration for tunneling UDP over other transports or application layers.
  // Tunneling is currently supported over HTTP/2.
  // [#next-free-field: 12]
  message UdpTunnelingConfig {
    // Configuration for UDP datagrams buffering.
    message BufferOptions {
      // If set, the filter will only buffer datagrams up to the requested limit, and will drop
      // new UDP datagrams if the buffer contains the max_buffered_datagrams value at the time
      // of a new datagram arrival. If not set, the default value is 1024 datagrams.
      google.protobuf.UInt32Value max_buffered_datagrams = 1;

      // If set, the filter will only buffer datagrams up to the requested total buffered bytes limit,
      // and will drop new UDP datagrams if the buffer contains the max_buffered_datagrams value
      // at the time of a new datagram arrival. If not set, the default value is 16,384 (16KB).
      google.protobuf.UInt64Value max_buffered_bytes = 2;
    }

    message RetryOptions {
      // 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 = 1;

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

    // The hostname to send in the synthesized CONNECT headers to the upstream proxy.
    // This field evaluates command operators if set, otherwise returns hostname as is.
    //
    // Example: dynamically set hostname using filter state
    //
    // .. code-block:: yaml
    //
    //    tunneling_config:
    //      proxy_host: "%FILTER_STATE(proxy.host.key:PLAIN)%"
    //
    string proxy_host = 1 [(validate.rules).string = {min_len: 1}];

    // Optional port value to add to the HTTP request URI.
    // This value can be overridden per-session by setting the required port value for
    // the filter state key ``udp.connect.proxy_port``.
    google.protobuf.UInt32Value proxy_port = 2;

    // The target host to send in the synthesized CONNECT headers to the upstream proxy.
    // This field evaluates command operators if set, otherwise returns hostname as is.
    //
    // Example: dynamically set target host using filter state
    //
    // .. code-block:: yaml
    //
    //    tunneling_config:
    //      target_host: "%FILTER_STATE(target.host.key:PLAIN)%"
    //
    string target_host = 3 [(validate.rules).string = {min_len: 1}];

    // The default target port to send in the CONNECT headers to the upstream proxy.
    // This value can be overridden per-session by setting the required port value for
    // the filter state key ``udp.connect.target_port``.
    uint32 default_target_port = 4 [(validate.rules).uint32 = {lte: 65535 gt: 0}];

    // Use POST method instead of CONNECT method to tunnel the UDP stream.
    //
    // .. note::
    //   If use_post is set, the upstream stream does not comply with the connect-udp RFC, and
    //   instead it will be a POST request. the path used in the headers will be set from the
    //   post_path field, and the headers will not contain the target host and target port, as
    //   required by the connect-udp protocol. This flag should be used carefully.
    //
    bool use_post = 5;

    // The path used with POST method. Default path is ``/``. If post path is specified and
    // use_post field isn't true, it will be rejected.
    string post_path = 6;

    // Optional retry options, in case connecting to the upstream failed.
    RetryOptions retry_options = 7;

    // Additional request headers to upstream proxy. Neither ``:-prefixed`` pseudo-headers
    // nor the Host: header can be overridden. Values of the added headers evaluates command
    // operators if they are set in the value template.
    //
    // Example: dynamically set a header with the local port
    //
    // .. code-block:: yaml
    //
    //    headers_to_add:
    //    - header:
    //        key: original_dst_port
    //        value: "%DOWNSTREAM_LOCAL_PORT%"
    //
    repeated config.core.v3.HeaderValueOption headers_to_add = 8
        [(validate.rules).repeated = {max_items: 1000}];

    // If configured, the filter will buffer datagrams in case that it is waiting for the upstream to be
    // ready, whether if it is during the connection process or due to upstream buffer watermarks.
    // If this field is not configured, there will be no buffering and downstream datagrams that arrive
    // while the upstream is not ready will be dropped. In case this field is set but the options
    // are not configured, the default values will be applied as described in the ``BufferOptions``.
    BufferOptions buffer_options = 9;

    // Save the response headers to the downstream info filter state for consumption
    // by the session filters. The filter state key is ``envoy.udp_proxy.propagate_response_headers``.
    bool propagate_response_headers = 10;

    // Save the response trailers to the downstream info filter state for consumption
    // by the session filters. The filter state key is ``envoy.udp_proxy.propagate_response_trailers``.
    bool propagate_response_trailers = 11;
  }

  message UdpAccessLogOptions {
    // The interval to flush access log. The UDP proxy will flush only one access log when the session
    // is ended by default. If this field is set, the UDP proxy will flush access log periodically with
    // the specified interval.
    // This field does not require on-tunnel-connected access logging enabled, and the other way around.
    // 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 and UDP tunneling is configured, access log will be flushed when the UDP proxy has successfully
    // established a connection tunnel with the upstream. If the connection failed, the access log will not be flushed.
    bool flush_access_log_on_tunnel_connected = 2;
  }

  // The stat prefix used when emitting UDP proxy filter stats.
  string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];

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

    // The upstream cluster to connect to.
    // This field is deprecated in favor of
    // :ref:`matcher <envoy_v3_api_field_extensions.filters.udp.udp_proxy.v3.UdpProxyConfig.matcher>`.
    string cluster = 2 [
      deprecated = true,
      (validate.rules).string = {min_len: 1},
      (envoy.annotations.deprecated_at_minor_version) = "3.0"
    ];

    // The match tree to use when resolving route actions for incoming requests.
    // See :ref:`Routing <config_udp_listener_filters_udp_proxy_routing>` for more information.
    xds.type.matcher.v3.Matcher matcher = 9
        [(xds.annotations.v3.field_status).work_in_progress = true];
  }

  // The idle timeout for sessions. Idle is defined as no datagrams between received or sent by
  // the session. The default if not specified is 1 minute.
  google.protobuf.Duration idle_timeout = 3;

  // Use the remote downstream IP address as the sender IP address when sending packets to upstream hosts.
  // This option requires Envoy to be run with the ``CAP_NET_ADMIN`` capability on Linux.
  // And the IPv6 stack must be enabled on Linux kernel.
  // This option does not preserve the remote downstream port.
  // If this option is enabled, the IP address of sent datagrams will be changed to the remote downstream IP address.
  // This means that Envoy will not receive packets that are sent by upstream hosts because the upstream hosts
  // will send the packets with the remote downstream IP address as the destination. All packets will be routed
  // to the remote downstream directly if there are route rules on the upstream host side.
  // There are two options to return the packets back to the remote downstream.
  // The first one is to use DSR (Direct Server Return).
  // The other one is to configure routing rules on the upstream hosts to forward
  // all packets back to Envoy and configure iptables rules on the host running Envoy to
  // forward all packets from upstream hosts to the Envoy process so that Envoy can forward the packets to the downstream.
  // If the platform does not support this option, Envoy will raise a configuration error.
  bool use_original_src_ip = 4;

  // Optional configuration for UDP proxy hash policies. If hash_policies 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 HashPolicy hash_policies = 5 [(validate.rules).repeated = {max_items: 1}];

  // UDP socket configuration for upstream sockets. The default for
  // :ref:`prefer_gro <envoy_v3_api_field_config.core.v3.UdpSocketConfig.prefer_gro>` is true for upstream
  // sockets as the assumption is datagrams will be received from a single source.
  config.core.v3.UdpSocketConfig upstream_socket_config = 6;

  // Perform per packet load balancing (upstream host selection) on each received data chunk.
  // The default if not specified is false, that means each data chunk is forwarded
  // to upstream host selected on first chunk receival for that "session" (identified by source IP/port and local IP/port).
  // Only one of use_per_packet_load_balancing or session_filters can be used.
  bool use_per_packet_load_balancing = 7;

  // Configuration for session access logs emitted by the UDP proxy. Note that certain UDP specific data is emitted as :ref:`Dynamic Metadata <config_access_log_format_dynamic_metadata>`.
  repeated config.accesslog.v3.AccessLog access_log = 8;

  // Configuration for proxy access logs emitted by the UDP proxy. Note that certain UDP specific data is emitted as :ref:`Dynamic Metadata <config_access_log_format_dynamic_metadata>`.
  repeated config.accesslog.v3.AccessLog proxy_access_log = 10;

  // Optional session filters that will run for each UDP session.
  // Only one of use_per_packet_load_balancing or session_filters can be used.
  // [#extension-category: envoy.filters.udp.session]
  repeated SessionFilter session_filters = 11;

  // If set, this configures UDP tunneling. See `Proxying UDP in HTTP <https://www.rfc-editor.org/rfc/rfc9298.html>`_.
  // More information can be found in the UDP Proxy and HTTP upgrade documentation.
  UdpTunnelingConfig tunneling_config = 12;

  // Additional access log options for UDP Proxy.
  UdpAccessLogOptions access_log_options = 13;
}
