Skip to content
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

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
09d7ce1
Introduce custom TLV support for upstream PP2 headers
timflannagan Nov 15, 2024
d0a3b62
pp2: Add custom TLV entries field to protocol protocol config API
timflannagan Dec 11, 2024
abc5088
pp2: Silence linting violation
timflannagan Dec 11, 2024
bdc38a9
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Dec 11, 2024
6d94e1d
pp2: Add additional unit tests
timflannagan Dec 13, 2024
2406b71
test: Fix linting violations
timflannagan Dec 13, 2024
9dbb9ff
Update changelog entry
timflannagan Dec 16, 2024
37f6be1
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Dec 16, 2024
9c5ac97
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Dec 17, 2024
da65ca7
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Jan 3, 2025
b656b36
*: Rename entries -> custom_tlvs in proxy_protocol.proto
timflannagan Jan 3, 2025
d23df4f
Fix changelog format errors and expand on precendence behavior in pro…
timflannagan Jan 7, 2025
1c02dc1
*: Rename custom_tlvs -> added_tlvs in proxy_protocol.proto
timflannagan Jan 7, 2025
e50c959
api: Revisit added_tlvs proto field documentation to clarify behavior
timflannagan Jan 7, 2025
f1ed670
source,test: Refactor added_tlvs code
timflannagan Jan 8, 2025
5ec9dd8
api: Attempt to fix docs sanity check
timflannagan Jan 8, 2025
118792b
api: Fix docs precheck
timflannagan Jan 8, 2025
c5a5619
pp2: Add ASSERT, move duplicate test case, remove TODOs
timflannagan Jan 8, 2025
7e5454a
pp2: Consolidate tests to add explicit precedence & override cases
timflannagan Jan 8, 2025
ad46d23
pp2: Refactor code, address review comments, etc
timflannagan Jan 16, 2025
3122ab3
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Jan 16, 2025
a6de035
Fix format errors
timflannagan Jan 22, 2025
c1e9f63
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Jan 22, 2025
b343e67
api: Fix YAML identation in PP2 code block
timflannagan Jan 22, 2025
7c5d4ed
api: Use literalinclude for added_tlvs code example
timflannagan Jan 22, 2025
876af00
configs: Fix proxy_protocol.yaml format errors
timflannagan Jan 22, 2025
977bb4b
Merge remote-tracking branch 'upstream/main' into feat/custom-pp2-ups…
timflannagan Jan 24, 2025
5ebdb87
Fix invalid proxy_protocol.yaml config
timflannagan Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions api/envoy/config/core/v3/proxy_protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ message ProxyProtocolPassThroughTLVs {
repeated uint32 tlv_type = 2 [(validate.rules).repeated = {items {uint32 {lt: 256}}}];
}

// Represents a single Type-Length-Value (TLV) entry.
message TlvEntry {
// The type of the TLV. Must be a uint8 (0-255) as per the Proxy Protocol v2 specification.
uint32 type = 1 [(validate.rules).uint32 = {lt: 256}];

// The value of the TLV. Must be at least one byte long.
bytes value = 2 [(validate.rules).bytes = {min_len: 1}];
}

message ProxyProtocolConfig {
enum Version {
// PROXY protocol version 1. Human readable format.
Expand All @@ -47,4 +56,40 @@ message ProxyProtocolConfig {
// This config controls which TLVs can be passed to upstream if it is Proxy Protocol
// V2 header. If there is no setting for this field, no TLVs will be passed through.
ProxyProtocolPassThroughTLVs pass_through_tlvs = 2;

// This config allows additional TLVs to be included in the upstream PROXY protocol
// V2 header. Unlike ``pass_through_tlvs``, which passes TLVs from the downstream request,
// ``added_tlvs`` provides an extension mechanism for defining new TLVs that are included
// with the upstream request. These TLVs may not be present in the downstream request and
// can be defined at either the transport socket level or the host level to provide more
// granular control over the TLVs that are included in the upstream request.
//
// Host-level TLVs are specified in the ``metadata.typed_filter_metadata`` field under the
// ``envoy.transport_sockets.proxy_protocol`` namespace.
//
// .. code-block:: yaml
//
// clusters:
// - name: cluster_0
timflannagan marked this conversation as resolved.
Show resolved Hide resolved
// load_assignment:
// endpoints:
// - lbEndpoints:
// - metadata:
// typed_filter_metadata:
// envoy.transport_sockets.proxy_protocol:
// "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig
timflannagan marked this conversation as resolved.
Show resolved Hide resolved
// added_tlvs:
// - type: 0xD7
// value: b3Zy
// - type: 0xD8
// value: bmV3
//
// **Precedence behavior**:
Copy link
Member

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

Copy link
Author

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.

Copy link
Member

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:

https://storage.googleapis.com/envoy-pr/3122ab3/docs/api-v3/config/core/v3/proxy_protocol.proto.html#envoy-v3-api-msg-config-core-v3-proxyprotocolpassthroughtlvs

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 ...

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Author

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.

Copy link
Member

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

Copy link
Member

Choose a reason for hiding this comment

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

thanks! really appreciated

Copy link
Author

Choose a reason for hiding this comment

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

Fixed the indentation issues in b343e67. I also took a stab at the literalinclude approach in 7c5d4ed after mirroring the examples from #35532.

Copy link
Member

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

Copy link
Author

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.

//
// - When a TLV is defined at both the host level and the transport socket level, the value
// from the host level configuration takes precedence. This allows users to define default TLVs
// at the transport socket level and override them at the host level.
// - Any TLV defined in the ``pass_through_tlvs`` field will be overridden by either the host-level
// or transport socket-level TLV.
repeated TlvEntry added_tlvs = 3;
}
7 changes: 7 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ new_features:
change: |
Adding support for a new body mode: FULL_DUPLEX_STREAMED in the ext_proc filter
:ref:`processing_mode <envoy_v3_api_field_extensions.filters.http.ext_proc.v3.ExternalProcessor.processing_mode>`.
- area: proxy_protocol
change: |
Added support for injecting custom Type-Length-Value (TLV) entries into the Proxy Protocol v2 header for upstream
transport sockets. Custom TLVs can be defined both in the endpoint host's typed metadata under the
``envoy.transport_sockets.proxy_protocol`` namespace and at the configuration level via the ``ProxyProtocolConfig``'s
``added_tlvs`` field. Host-level TLV definitions override config-level entries when the same type is specified, allowing
default TLVs to be set globally, while enabling further per-endpoint customizations.
- area: http
change: |
Added :ref:`max_metadata_size <envoy_v3_api_field_config.core.v3.Http2ProtocolOptions.max_metadata_size>` to make
Expand Down
3 changes: 3 additions & 0 deletions source/common/config/well_known_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class MetadataFilterValues {
const std::string ENVOY_LB = "envoy.lb";
// Filter namespace for built-in transport socket match in cluster.
const std::string ENVOY_TRANSPORT_SOCKET_MATCH = "envoy.transport_socket_match";
// Filter namespace for storing custom upstream PP TLVs in metadata.
const std::string ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL =
"envoy.transport_sockets.proxy_protocol";
// Proxy address configuration namespace for HTTP/1.1 proxy transport sockets.
const std::string ENVOY_HTTP11_PROXY_TRANSPORT_SOCKET_ADDR =
"envoy.http11_proxy_transport_socket.proxy_address";
Expand Down
57 changes: 42 additions & 15 deletions source/extensions/common/proxy_protocol/proxy_protocol_header.cc
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,47 @@ void generateV2Header(const Network::Address::Ip& source_address,
}

bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer::Instance& out,
bool pass_all_tlvs, const absl::flat_hash_set<uint8_t>& pass_through_tlvs) {
uint64_t extension_length = 0;
for (auto&& tlv : proxy_proto_data.tlv_vector_) {
bool pass_all_tlvs, const absl::flat_hash_set<uint8_t>& pass_through_tlvs,
const std::vector<Envoy::Network::ProxyProtocolTLV>& custom_tlvs) {
std::vector<Envoy::Network::ProxyProtocolTLV> combined_tlv_vector;
std::vector<Envoy::Network::ProxyProtocolTLV> final_tlvs;
combined_tlv_vector.reserve(custom_tlvs.size() + proxy_proto_data.tlv_vector_.size());

absl::flat_hash_set<uint8_t> seen_types;
for (const auto& tlv : custom_tlvs) {
ASSERT(!seen_types.contains(tlv.type));
combined_tlv_vector.emplace_back(tlv);
seen_types.insert(tlv.type);
}

// Combine TLVs from the proxy_proto_data with the custom TLVs.
for (const auto& tlv : proxy_proto_data.tlv_vector_) {
if (!pass_all_tlvs && !pass_through_tlvs.contains(tlv.type)) {
// Skip any TLV that is not in the set of passthrough TLVs.
continue;
}
extension_length += PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN + tlv.value.size();
if (extension_length > std::numeric_limits<uint16_t>::max()) {
ENVOY_LOG_MISC(
warn, "Generating Proxy Protocol V2 header: TLVs exceed length limit {}, already got {}",
std::numeric_limits<uint16_t>::max(), extension_length);
return false;
if (seen_types.contains(tlv.type)) {
// Skip any duplicate TLVs from being added to the combined TLV vector.
ENVOY_LOG_EVERY_POW_2_MISC(info, "Skipping duplicate TLV type {}", tlv.type);
continue;
}
seen_types.insert(tlv.type);
combined_tlv_vector.emplace_back(tlv);
}

// 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);
}
Comment on lines +142 to 155
Copy link
Author

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

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();
expects this function to return false to increment the stats.

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.


ASSERT(extension_length <= std::numeric_limits<uint16_t>::max());
Expand All @@ -141,17 +169,16 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer
generateV2Header(src.addressAsString(), dst.addressAsString(), src.port(), dst.port(),
src.version(), static_cast<uint16_t>(extension_length), out);

// Generate the TLV vector.
for (auto&& tlv : proxy_proto_data.tlv_vector_) {
if (!pass_all_tlvs && !pass_through_tlvs.contains(tlv.type)) {
continue;
}
for (auto&& tlv : combined_tlv_vector) {
out.add(&tlv.type, 1);
uint16_t size = htons(static_cast<uint16_t>(tlv.value.size()));
out.add(&size, sizeof(uint16_t));
out.add(&tlv.value.front(), tlv.value.size());
}
return true;

// return true if no TLVs were skipped, otherwise false to increment the counter
// in the upstream proxy protocol transport socket stats.
return !skipped_tlvs;
}

void generateProxyProtoHeader(const envoy::config::core::v3::ProxyProtocolConfig& config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ void generateV2LocalHeader(Buffer::Instance& out);

// Generates the v2 PROXY protocol header including the TLV vector into the specified buffer.
bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer::Instance& out,
bool pass_all_tlvs, const absl::flat_hash_set<uint8_t>& pass_through_tlvs);
bool pass_all_tlvs, const absl::flat_hash_set<uint8_t>& pass_through_tlvs,
const std::vector<Envoy::Network::ProxyProtocolTLV>& custom_tlvs);

} // namespace ProxyProtocol
} // namespace Common
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,10 @@ ReadOrParseState Filter::readExtensions(Network::ListenerFilterBuffer& buffer) {
auto raw_slice = buffer.rawSlice();
// waiting for more data if there is no enough data for extensions.
if (raw_slice.len_ < (proxy_protocol_header_.value().wholeHeaderLength())) {
ENVOY_LOG(
trace,
"waiting for more data to read extensions. Buffer length: {}, extension header length {}",
raw_slice.len_, proxy_protocol_header_.value().wholeHeaderLength());
return ReadOrParseState::TryAgainLater;
}

Expand Down
2 changes: 2 additions & 0 deletions source/extensions/transport_sockets/proxy_protocol/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ envoy_cc_library(
"//source/common/common:hex_lib",
"//source/common/common:scalar_to_byte_vector_lib",
"//source/common/common:utility_lib",
"//source/common/config:well_known_names",
"//source/common/network:address_lib",
"//source/extensions/common/proxy_protocol:proxy_protocol_header_lib",
"//source/extensions/transport_sockets/common:passthrough_lib",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/transport_sockets/proxy_protocol/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
#include <sstream>

#include "envoy/config/core/v3/proxy_protocol.pb.h"
#include "envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.pb.h"
#include "envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.pb.validate.h"
#include "envoy/network/transport_socket.h"

#include "source/common/buffer/buffer_impl.h"
#include "source/common/common/hex.h"
#include "source/common/common/scalar_to_byte_vector.h"
#include "source/common/common/utility.h"
#include "source/common/config/well_known_names.h"
#include "source/common/network/address_impl.h"
#include "source/common/protobuf/utility.h"
#include "source/extensions/common/proxy_protocol/proxy_protocol_header.h"

using envoy::config::core::v3::ProxyProtocolConfig;
Expand All @@ -36,6 +40,11 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket(
pass_through_tlvs_.insert(0xFF & tlv_type);
}
}
for (const auto& entry : config.added_tlvs()) {
added_tlvs_.push_back(Network::ProxyProtocolTLV{
static_cast<uint8_t>(entry.type()),
std::vector<unsigned char>(entry.value().begin(), entry.value().end())});
timflannagan marked this conversation as resolved.
Show resolved Hide resolved
}
}

void UpstreamProxyProtocolSocket::setTransportSocketCallbacks(
Expand Down Expand Up @@ -91,9 +100,11 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() {
if (!options_ || !options_->proxyProtocolOptions().has_value()) {
Common::ProxyProtocol::generateV2LocalHeader(header_buffer_);
} else {
std::vector<Envoy::Network::ProxyProtocolTLV> custom_tlvs = buildCustomTLVs();

const auto options = options_->proxyProtocolOptions().value();
if (!Common::ProxyProtocol::generateV2Header(options, header_buffer_, pass_all_tlvs_,
pass_through_tlvs_)) {
pass_through_tlvs_, custom_tlvs)) {
// There is a warn log in generateV2Header method.
stats_.v2_tlvs_exceed_max_length_.inc();
}
Expand Down Expand Up @@ -165,6 +176,56 @@ void UpstreamProxyProtocolSocketFactory::hashKey(
}
}

std::vector<Envoy::Network::ProxyProtocolTLV> UpstreamProxyProtocolSocket::buildCustomTLVs() const {
std::vector<Envoy::Network::ProxyProtocolTLV> custom_tlvs;
absl::flat_hash_set<uint8_t> processed_tlv_types;

timflannagan marked this conversation as resolved.
Show resolved Hide resolved
// Attempt to parse host-level TLVs first.
const auto& upstream_info = callbacks_->connection().streamInfo().upstreamInfo();
if (upstream_info && upstream_info->upstreamHost()) {
auto metadata = upstream_info->upstreamHost()->metadata();
if (metadata) {
const auto filter_it = metadata->typed_filter_metadata().find(
Envoy::Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL);
if (filter_it != metadata->typed_filter_metadata().end()) {
ProxyProtocolConfig host_tlv_metadata;
auto status = MessageUtil::unpackTo(filter_it->second, host_tlv_metadata);
if (!status.ok()) {
ENVOY_LOG(warn,
"Failed to unpack custom TLVs from upstream host metadata for host {}. "
"Error: {}. Will still use config-level TLVs.",
upstream_info->upstreamHost()->address()->asString(), status.message());
} else {
// Insert host-level TLVs
for (const auto& entry : host_tlv_metadata.added_tlvs()) {
if (processed_tlv_types.contains(entry.type())) {
ENVOY_LOG_EVERY_POW_2_MISC(info, "Skipping duplicate TLV type from host metadata {}",
entry.type());
continue;
}
custom_tlvs.push_back(Network::ProxyProtocolTLV{
static_cast<uint8_t>(entry.type()),
std::vector<unsigned char>(entry.value().begin(), entry.value().end())});
processed_tlv_types.insert(entry.type());
}
}
}
}
}

// If host-level parse failed or was not present, we still read config-level TLVs
for (const auto& tlv : added_tlvs_) {
if (processed_tlv_types.contains(tlv.type)) {
ENVOY_LOG_EVERY_POW_2_MISC(info, "Skipping duplicate TLV type from added_tlvs {}", tlv.type);
continue;
}
custom_tlvs.push_back(tlv);
processed_tlv_types.insert(tlv.type);
}

return custom_tlvs;
}

} // namespace ProxyProtocol
} // namespace TransportSockets
} // namespace Extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket,
void generateHeader();
void generateHeaderV1();
void generateHeaderV2();
// Combine host-level and config-level TLVs, with fallback if metadata fails to unpack.
// Host-level has precedence over config-level TLVs.
// If we fail to parse host metadata, we still read config TLVs.
std::vector<Envoy::Network::ProxyProtocolTLV> buildCustomTLVs() const;
Network::IoResult writeHeader();

Network::TransportSocketOptionsConstSharedPtr options_;
Expand All @@ -52,6 +56,7 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket,
const UpstreamProxyProtocolStats& stats_;
const bool pass_all_tlvs_;
absl::flat_hash_set<uint8_t> pass_through_tlvs_{};
Copy link
Contributor

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.

std::vector<Envoy::Network::ProxyProtocolTLV> added_tlvs_{};
};

class UpstreamProxyProtocolSocketFactory : public PassthroughFactory {
Expand Down
Loading