-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
upstream_proxy_protocol: Introduce custom TLV support #37591
base: main
Are you sure you want to change the base?
upstream_proxy_protocol: Introduce custom TLV support #37591
Conversation
Hi @timflannagan, welcome and thank you for your contribution. We will try to review your Pull Request as quickly as possible. In the meantime, please take a look at the contribution guidelines if you have not done so already. |
CC @envoyproxy/api-shepherds: Your approval is needed for changes made to |
For more discussion on some of the implementation choices, see timflannagan#1 for an initial round of review. Also, I haven't written C++ in a while so I'm happy to make any style-related changes. |
Looks like integration tests are failing. I'll fix that tomorrow. At a glance, the assertions are expecting a certain ordering for the custom TLVs that are being defined, and there's deterministic output issues. |
This commit introduces support for injecting custom TLVs into the Proxy Protocol v2 (PP2) header for upstream transport sockets. This enables xDS control planes to build upstream PP2 headers with greater flexibility. Previously, upstream PP2 headers only passed through TLVs from downstream connections when using the Proxy Protocol listener, limiting customization. With this change, users can define custom TLVs by specifying host metadata in a well-known namespace, providing dynamic, granular control over PP2 header content. For example: ```yaml clusters: - name: httpbin load_assignment: ... endpoints: - lbEndpoints: - metadata: filter_metadata: envoy.transport_socket_match: outbound-proxy: true typed_filter_metadata: envoy.transport_sockets.proxy_protocol: "@type": type.googleapis.com/envoy.extensions.transport_sockets.proxy_protocol.v3.CustomTlvMetadata entries: - type: 0x96 value: Zm9v # foo - type: 0x97 value: YmFy # bar ... ``` By decoupling upstream PP2 customization from downstream listener config, this unlocks more flexible use cases for Proxy Protocol in upstream paths. Earlier approaches considered extending upstream_proxy_protocol to support TLV configuration but were rejected due to added control plane complexity. Similarly, reusing the envoy.network.proxy_protocol_options namespace was evaluated but required significant refactoring and risk. Signed-off-by: timflannagan <timflannagan@gmail.com>
ba3c628
to
09d7ce1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/wait
api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto
Outdated
Show resolved
Hide resolved
api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto
Outdated
Show resolved
Hide resolved
Signed-off-by: timflannagan <timflannagan@gmail.com>
Looks like the format check is failing:
I don't see that function defined anywhere, but I see several PRs that seem relevant here and I think I can figure out how to silence that linting violation. Does that linting rule need to be updated as well? |
Signed-off-by: timflannagan <timflannagan@gmail.com>
/api lgtm |
@ggreenway Can you take another pass when you get a second? I need to look into getting the codecov check green, but the rest of the CI checks looked good last run. |
Signed-off-by: timflannagan <timflannagan@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/wait
source/extensions/common/proxy_protocol/proxy_protocol_header.cc
Outdated
Show resolved
Hide resolved
source/extensions/common/proxy_protocol/proxy_protocol_header.cc
Outdated
Show resolved
Hide resolved
source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc
Outdated
Show resolved
Hide resolved
source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc
Outdated
Show resolved
Hide resolved
source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc
Outdated
Show resolved
Hide resolved
source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc
Outdated
Show resolved
Hide resolved
@@ -52,6 +53,9 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, | |||
const UpstreamProxyProtocolStats& stats_; | |||
const bool pass_all_tlvs_; | |||
absl::flat_hash_set<uint8_t> pass_through_tlvs_{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't related to this PR, but instead of this (and your new config) being copied by value here, the normal pattern is to hold a shared_ptr to a Config object that has already converted the protobuf config to the internal represenation, and done validation such as no duplicates.
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/wait
source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc
Outdated
Show resolved
Hide resolved
test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc
Outdated
Show resolved
Hide resolved
std::vector<Envoy::Network::ProxyProtocolTLV> custom_tlvs = { | ||
{0x8, std::vector<unsigned char>(65536, 'a')}, | ||
}; | ||
EXPECT_LOG_CONTAINS("warn", "Generating Proxy Protocol V2 header: TLVs exceed length limit 65535", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case was probably unreachable before this change, but now a bad config could result in a connection not generating a PP header at all. I think this case needs to be handled better, probably with the connection being aborted, or alternately generate a PP header omitting the TLV that is too long. cc @botengyao who added the TLV passthrough code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this becomes more problematic with these changes. I think I'd lean towards truncating the custom (and passthrough) TLVs for the upstream PP2 header. At a minimum, I think we'd need to log and possible document this behavior so users aren't caught over guard. cc @yuval-k in case you have any thoughts as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Signed-off-by: timflannagan <timflannagan@gmail.com>
// Filter out TLVs that would exceed the 65535 limit. | ||
uint64_t extension_length = 0; | ||
bool skipped_tlvs = false; | ||
for (auto&& tlv : combined_tlv_vector) { | ||
uint64_t new_size = extension_length + PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN + tlv.value.size(); | ||
if (new_size > std::numeric_limits<uint16_t>::max()) { | ||
ENVOY_LOG_MISC(warn, "Skipping TLV type {} because adding it would exceed the 65535 limit.", | ||
tlv.type); | ||
skipped_tlvs = true; | ||
continue; | ||
} | ||
extension_length = new_size; | ||
final_tlvs.push_back(tlv); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Truncated custom or passthrough TLVs that exceed the max length in ad46d23. There were some header unit tests that were failing as a result as
envoy/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc
Lines 106 to 109 in 3122ab3
if (!Common::ProxyProtocol::generateV2Header(options, header_buffer_, pass_all_tlvs_, | |
pass_through_tlvs_, custom_tlvs)) { | |
// There is a warn log in generateV2Header method. | |
stats_.v2_tlvs_exceed_max_length_.inc(); |
I went back and forth on whether returning false here would be ambiguous when writing the output header was successful, but I didn't want to extend the function signature to return (bool written, bool skipped_tlvs) to handle this edge case at the call sites.
ping @ggreenway i think this is awaiting further review @timflannagan requires main merge |
// - type: 0xD8 | ||
// value: bmV3 | ||
// | ||
// **Precedence behavior**: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering if this should be a header
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any examples on how to do that? I tried following https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections, but ci/do_ci.sh docs
was failing when I ran it and I can't seem to find where the generated files live on my local box.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generated files live in bazel ether - unless you build them directly (eg bazel build //docs:rst
inside the container)
you can view the rendered docs eg here:
thinking more about my heading suggestion, im wondering if we should move some of this to a reference page and link it - eg the config example
i tried setting the "heading" to h5 - it kinda works but probs we shouldnt have headings inside the descriptions - they should be relatively terse
ill comment on the config now ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for ref this is the Proxy Protocol ref page https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/proxy_protocol.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@phlax I like the reference page idea looking more at the current docset. Is this something we can tackle as a follow-up or is this a blocker in your mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a blocker - but please at least fix the config indents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks! really appreciated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah - using literalinclude
is what is super appreciated - its much more maintainable
probs we should add some docs somewhere - i have been pondering a plan to work on the tech debt here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think the reference docs or some high-level field documentation in the prot is still worth chasing -- I just have limited capacity right now. At a minimum, I think it would be good to have a header that explains pass_through_tlvs & added_tlvs and their interlope as there's some nuance in their behavior.
// The PROXY protocol transport socket allows carrying connection metadata (such as
// the original source IP address) to upstream services in either a human-readable
// (Version 1) or binary (Version 2) header.
//
// Behavior
// ========
// When the PROXY protocol transport socket is configured, Envoy can prepend a PROXY
// protocol header to connections going upstream. Depending on the protocol version,
// additional metadata such as custom Type-Length-Value (TLV) entries may also be sent.
//
// Configuration
// =============
// .. code-block:: yaml
//
// transport_socket:
// name: envoy.transport_sockets.proxy_protocol
// typed_config:
// "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig
// version: V2
// pass_through_tlvs:
// match_type: INCLUDE_ALL
//
// Passing & Adding TLVs
// ---------------------
// - *pass_through_tlvs*:
// Controls which TLVs from the downstream PROXY protocol request are forwarded
// to the upstream connection.
// - *added_tlvs*:
// Defines new TLVs to add into the header for the upstream request. TLVs defined
// at a host level take precedence over those defined at the transport socket level.
And then the individual added_tlvs field documentation could introduce the two config options and the precedence behavior. I'm not sure what's best w.r.t organization so I stopped messing around with it locally.
re
it would seem it does |
yeah looks like that rule should have been updated with https://github.com/envoyproxy/envoy/pull/32775/files #38138 should fix |
Signed-off-by: timflannagan <timflannagan@gmail.com>
/docs |
Docs for this Pull Request will be rendered here: https://storage.googleapis.com/envoy-pr/37591/docs/index.html The docs are (re-)rendered each time the CI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for configs - we strongly encourage using a literalinclude
with a full bootstrap yaml, as these are linted and tested variously for validity.
this avoids bitrot and is much more helpful for end users
we also enforce least indent on yaml formatting which is missed in code-block
s
if we shift the example to a reference page its ~easier, but it can be done for api also
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Signed-off-by: timflannagan <timflannagan@gmail.com>
Commit Message:
This commit introduces support for injecting custom TLVs into the Proxy Protocol v2 (PP2) header for upstream transport sockets. This enables xDS control planes to build upstream PP2 headers with greater flexibility. Previously, upstream PP2 headers only passed through TLVs from downstream connections when using the Proxy Protocol listener, limiting customization.
With this change, users can define custom TLVs by configuring the
custom_tlvs
field in the upstream_proxy_protocol transport socket config, or specifying host metadata in a well-known namespace in order to provide dynamic and more granular control over PP2 header content. For example:And
By decoupling upstream PP2 customization from downstream listener config, this unlocks more flexible use cases for Proxy Protocol in upstream paths.
Additional Description: N/A
Risk Level: Low
Testing: Unit & integration
Docs Changes: Include in the protobuf docs
Release Notes: Included in the changelog as a new feature
Platform Specific Features: N/A
[Optional Runtime guard:]
[Optional Fixes #Issue] #18520
[Optional Fixes commit #PR or SHA]
[Optional Deprecated:]
[Optional API Considerations:]