Skip to content

tcp_proxy: added changes to support ppv2 tlvs to be merged when downstream proxy protocol values are present#42824

Merged
agrawroh merged 17 commits intoenvoyproxy:mainfrom
ivpr:feat-tcp-proxy-ppv2-merge
Feb 13, 2026
Merged

tcp_proxy: added changes to support ppv2 tlvs to be merged when downstream proxy protocol values are present#42824
agrawroh merged 17 commits intoenvoyproxy:mainfrom
ivpr:feat-tcp-proxy-ppv2-merge

Conversation

@ivpr
Copy link
Copy Markdown
Contributor

@ivpr ivpr commented Jan 2, 2026

Currently proxy_protocol_tlvs field in tcp_proxy ignores the TLVs specified when downstream has proxy protocol data. This change adds support to be able to merge the TLVs through an explicit enum field proxy_protocol_tlv_merge_policy.
Added support for 3 options

  • ADD_IF_ABSENT (default): 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.
  • OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD: 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..
  • APPEND_IF_EXISTS_OR_ADD: 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.

Commit Message: tcp_proxy: added changes to support ppv2 tlvs to be merged when downstream proxy protocol values are present
Additional Description: Currently proxy_protocol_tlvs field in tcp_proxy ignores the TLVs specified when downstream has proxy protocol data. This changes adds support to merge the TLVs through an explicit field proxy_protocol_tlv_merge_policy .
Risk Level: Low
Testing: Unit tests
Docs Changes: Added
Release Notes: Added
Platform Specific Features: N/A
[Optional Runtime guard:]
[Optional Fixes #Issue]
[Optional Fixes commit #PR or SHA]
[Optional Deprecated:]
[Optional API Considerations:]

Prasad I V added 2 commits December 30, 2025 16:46
…tocol values are present

Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @markdroth
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #42824 was opened by ivpr.

see: more, trace.

Prasad I V added 3 commits January 5, 2026 08:51
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Comment on lines +340 to +355
// When set to true and the connection already contains ``PROXY`` protocol state
// (including TLVs) from a downstream proxy protocol listener filter, the TLVs
// specified in ``proxy_protocol_tlvs`` are merged with the downstream TLVs.
//
// **Precedence behavior**:
//
// - TLVs specified in ``proxy_protocol_tlvs`` take precedence over downstream TLVs
// with the same type.
// - This allows adding or overriding specific TLV values while preserving
// other downstream TLVs.
// - The source/destination addresses from the downstream ``PROXY`` protocol state
// are preserved (not modified).
//
// When set to false (default), the ``proxy_protocol_tlvs`` are ignored if
// downstream ``PROXY`` protocol state already exists.
bool merge_proxy_protocol_tlvs = 23;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest calling it merge_with_downstream_tlvs to be more clear and also add more info to the docs.

Suggested change
// When set to true and the connection already contains ``PROXY`` protocol state
// (including TLVs) from a downstream proxy protocol listener filter, the TLVs
// specified in ``proxy_protocol_tlvs`` are merged with the downstream TLVs.
//
// **Precedence behavior**:
//
// - TLVs specified in ``proxy_protocol_tlvs`` take precedence over downstream TLVs
// with the same type.
// - This allows adding or overriding specific TLV values while preserving
// other downstream TLVs.
// - The source/destination addresses from the downstream ``PROXY`` protocol state
// are preserved (not modified).
//
// When set to false (default), the ``proxy_protocol_tlvs`` are ignored if
// downstream ``PROXY`` protocol state already exists.
bool merge_proxy_protocol_tlvs = 23;
// Controls whether TLVs specified in ``proxy_protocol_tlvs`` are merged with downstream
// ``PROXY`` protocol TLVs when downstream ``PROXY`` protocol state already exists.
//
// When set to ``true`` and the connection already contains ``PROXY`` protocol state
// (including TLVs) from a downstream proxy protocol listener filter, the TLVs
// specified in ``proxy_protocol_tlvs`` are merged with the downstream TLVs.
//
// When set to ``false`` (default), the TLVs specified in ``proxy_protocol_tlvs`` are
// ignored if downstream ``PROXY`` protocol state already exists. In this case, only
// the downstream TLVs are forwarded to the upstream.
//
// **Precedence behavior**:
//
// - TLVs specified in ``proxy_protocol_tlvs`` take precedence over downstream TLVs
// with the same type. This allows adding or overriding specific TLV values while
// preserving other downstream TLVs.
// - The source/destination addresses from the downstream ``PROXY`` protocol state
// are preserved (not modified).
// - TLVs with types that do not conflict are combined, resulting in a merged set
// of TLVs from both sources.
//
// .. note::
// This field only takes effect when downstream ``PROXY`` protocol state exists.
// If the TCP proxy filter is creating new ``PROXY`` protocol state, this field
// has no effect and the TLVs specified in ``proxy_protocol_tlvs`` are always
// included.
//
// .. note::
// To ensure the merged TLVs are allowed in the upstream ``PROXY`` protocol header,
// you must also 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.
bool merge_with_downstream_tlvs = 23;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I've made the changes to rename the field along with the doc suggestions

@agrawroh
Copy link
Copy Markdown
Member

agrawroh commented Jan 6, 2026

/assign @ggreenway

…ith_downstream_tlvs

Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/wait

// you must also 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.
bool merge_with_downstream_tlvs = 23;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to be careful with how we handle merge vs override, when considering #42075. Would it ever make sense to add an additional TLV of the same key? Maybe we should use an enum instead of a bool here so that if different behaviors are needed in the future we can easily add it to the API.

Copy link
Copy Markdown
Contributor Author

@ivpr ivpr Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to add the enum config option. I've now updated the code to use enum downstream_tlv_merge_policy with 3 different options(KEEP_DOWNSTREAM_ONLY, OVERRIDE_DOWNSTREAM_TLVS_BY_TYPE, APPEND_TO_DOWNSTREAM_TLVS)

Comment thread source/common/tcp_proxy/tcp_proxy.cc Outdated

// Add downstream TLVs that don't conflict with tcp_proxy TLVs.
for (const auto& tlv : existing_data.tlv_vector_) {
if (!seen_types.contains(tlv.type)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will introduce the bug reported in #42075 to tcp_proxy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed this now with explicit enum values

Signed-off-by: Prasad I V <prasad.iv@databricks.com>
@ivpr
Copy link
Copy Markdown
Contributor Author

ivpr commented Jan 12, 2026

@ggreenway I've addressed the review comments. Can you please have a look

@tyxia
Copy link
Copy Markdown
Member

tyxia commented Jan 15, 2026

Gentle ping @ggreenway from maintainer oncall. Please merge main to resolve the conflict as well @ivpr

Thanks!

@ggreenway
Copy link
Copy Markdown
Member

Apologies; I've been very busy and haven't had time to review this yet. I'm hoping to get to it early next week.

Signed-off-by: ivpr <ivijayprasad@gmail.com>
Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the delayed review.

This looks good at a high level, but there's a little bit of clarifying/simplifying the docs and semantics to do.

/wait

Comment on lines +63 to +65
// Keep only the downstream TLVs and ignore the TLVs specified in ``proxy_protocol_tlvs``
// if downstream PROXY protocol state already exists. Only the downstream TLVs are forwarded
// to the upstream. This is the default behavior for backward compatibility.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there is no downstream PROXY protocol, then the configured TLVs are used instead. Please document/clarify this behavior.

Comment on lines +368 to +380
// - ``KEEP_DOWNSTREAM_ONLY`` (default): The TLVs specified in ``proxy_protocol_tlvs`` are
// ignored if downstream ``PROXY`` protocol state already exists. Only the downstream TLVs
// are forwarded to the upstream.
//
// - ``OVERRIDE_DOWNSTREAM_TLVS_BY_TYPE``: TLVs specified in ``proxy_protocol_tlvs`` override
// downstream TLVs with the same type. TLVs with non-conflicting types from both sources are
// preserved. The source/destination addresses from the downstream ``PROXY`` protocol state
// are preserved (not modified).
//
// - ``APPEND_TO_DOWNSTREAM_TLVS``: The TLVs specified in ``proxy_protocol_tlvs`` are appended
// to the downstream TLVs. This preserves all downstream TLVs and adds the tcp_proxy TLVs.
// The PROXY protocol v2 specification allows multiple TLVs with the same type, so this mode
// preserves all TLVs from both sources.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docs should all be in the enum definition above; no need to duplicate here

Comment on lines +382 to +386
// .. note::
// This field only takes effect when downstream ``PROXY`` protocol state exists.
// If the TCP proxy filter is creating new ``PROXY`` protocol state, this field
// has no effect and the TLVs specified in ``proxy_protocol_tlvs`` are always
// included.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing behavior.

I think it would be clearer if the default option was something like ADD_IF_ABSENT (taken from https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-enum-config-core-v3-headervalueoption-headerappendaction), and the other two options then can be said to be in effect, regardless of whether there's downstream TLVs or not (because if there aren't, both of those options just add all the configured TLVs).

I think the net effect is the same, but the mental model for how it works is simpler. WDYT?

Comment thread source/common/tcp_proxy/tcp_proxy.cc Outdated
parseTLVs(config.proxy_protocol_tlvs(), context, dynamic_tlv_formatters_);
}

downstream_tlv_merge_policy_ = config.downstream_tlv_merge_policy();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be set in the initializer list at the top of the constructor, because it has no validation or complicated conversion logic.

Comment thread source/common/tcp_proxy/tcp_proxy.cc
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: ivpr <ivijayprasad@gmail.com>
Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/wait

Comment on lines +173 to +178
* ``ADD_IF_ABSENT`` (default): Add configured TLVs only if no state exists.
* ``OVERWRITE_BY_TYPE_IF_EXISTS_OR_ADD``: Overwrite existing TLVs by type with configured TLVs.
Non-conflicting TLVs from both sources are preserved.
* ``APPEND_IF_EXISTS_OR_ADD``: Append configured TLVs to existing TLVs, preserving all
TLVs from both sources (allows duplicate types per PROXY protocol v2 spec).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to repeat this from the proto docs; just link to the enum type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. done

Comment thread source/common/tcp_proxy/tcp_proxy.cc Outdated
Comment thread source/common/tcp_proxy/tcp_proxy.cc Outdated
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: ivpr <ivijayprasad@gmail.com>
Prasad I V added 2 commits February 9, 2026 18:28
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
@ivpr
Copy link
Copy Markdown
Contributor Author

ivpr commented Feb 10, 2026

/retest

Prasad I V and others added 3 commits February 10, 2026 03:56
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: Prasad I V <prasad.iv@databricks.com>
Signed-off-by: ivpr <ivijayprasad@gmail.com>
@ivpr
Copy link
Copy Markdown
Contributor Author

ivpr commented Feb 11, 2026

/retest

Copy link
Copy Markdown
Member

@ggreenway ggreenway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. @markdroth can you review API please?

@markdroth
Copy link
Copy Markdown
Contributor

/lgtm api

@agrawroh agrawroh merged commit 4d8363f into envoyproxy:main Feb 13, 2026
28 checks passed
nickshokri pushed a commit to nickshokri/envoy that referenced this pull request Mar 17, 2026
…tream proxy protocol values are present (envoyproxy#42824)

Signed-off-by: nick <nickshokri@google.com>
nickshokri pushed a commit to nickshokri/envoy that referenced this pull request Mar 17, 2026
…tream proxy protocol values are present (envoyproxy#42824)

Signed-off-by: nick <nickshokri@google.com>
fishcakez pushed a commit to fishcakez/envoy that referenced this pull request Mar 25, 2026
Network::ProxyProtocolFilterState::key())) {
// Evaluate dynamic TLVs with the connection's stream info.

auto* existing_state = filter_state->getDataMutable<Network::ProxyProtocolFilterState>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that the ProxyProtocolFilterState was added as a mutable element. What behavior do you expect if it was added as an immutable element?

krinkinmu pushed a commit to grnmeira/envoy that referenced this pull request Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants