From 09d7ce1dccab2b42eab56891d2224ffdc9a34274 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Fri, 15 Nov 2024 21:59:28 +0000 Subject: [PATCH 01/22] Introduce custom TLV support for upstream PP2 headers 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 --- .../v3/upstream_proxy_protocol.proto | 19 ++ changelogs/current.yaml | 5 + source/common/config/well_known_names.h | 3 + .../proxy_protocol/proxy_protocol_header.cc | 37 +++- .../proxy_protocol/proxy_protocol_header.h | 3 +- .../listener/proxy_protocol/proxy_protocol.cc | 4 + .../transport_sockets/proxy_protocol/BUILD | 2 + .../proxy_protocol/proxy_protocol.cc | 49 +++- .../proxy_protocol/proxy_protocol.h | 1 + .../proxy_protocol_header_test.cc | 120 +++++++++- .../transport_sockets/proxy_protocol/BUILD | 1 + .../proxy_protocol_integration_test.cc | 209 +++++++++++++++++- .../proxy_protocol/proxy_protocol_test.cc | 195 +++++++++++++++- test/integration/fake_upstream.cc | 3 +- 14 files changed, 624 insertions(+), 27 deletions(-) diff --git a/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto b/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto index 12c0e92dc19de..a66d8b62a4281 100644 --- a/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto +++ b/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto @@ -17,6 +17,21 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Upstream Proxy Protocol] // [#extension: envoy.transport_sockets.upstream_proxy_protocol] +// Metadata for custom Type-Length-Value (TLV) entries to be sent in the upstream PROXY protocol header. +message CustomTlvMetadata { + // A list of TLV entries. At least one entry must be provided. + repeated TlvEntry entries = 1 [(validate.rules).repeated = {min_items: 1}]; +} + +// 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}]; +} + // Configuration for PROXY protocol socket message ProxyProtocolUpstreamTransport { // The PROXY protocol settings @@ -33,4 +48,8 @@ message ProxyProtocolUpstreamTransport { // If true, all the TLVs are encoded in the connection pool key. // [#not-implemented-hide:] bool tlv_as_pool_key = 4; + + // Custom TLV metadata to be sent in the upstream PROXY protocol header. + // [#not-implemented-hide:] + CustomTlvMetadata custom_tlv_metadata = 5; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b4e472cc1f151..d63b058441a91 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -310,6 +310,11 @@ new_features: change: | Added new health check filter stats including total requests, successful/failed checks, cached responses, and cluster health status counters. These stats help track health check behavior and cluster health state. +- area: proxy_protocol + change: | + Added support for injecting custom TLVs into the Proxy Protocol v2 (PP2) header for upstream transport sockets. + This feature allows dynamic customization of PP2 headersby defining TLV key-value pairs in an endpoint host's + typed metadata under the ``envoy.transport_sockets.proxy_protocol`` namespace. deprecated: - area: rbac diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index 02c4a0b2640c9..c31de828da517 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -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"; diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc index 1c8537964f720..35a9ad348a1d5 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc @@ -111,12 +111,36 @@ 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& 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& pass_through_tlvs, + const std::vector& custom_tlvs) { + std::vector combined_tlv_vector; + combined_tlv_vector.reserve(custom_tlvs.size() + proxy_proto_data.tlv_vector_.size()); + + absl::flat_hash_set seen_types; + for (const auto& tlv : custom_tlvs) { + if (seen_types.contains(tlv.type)) { + ENVOY_LOG_MISC(warn, "Ignoring duplicate custom TLV type {}", tlv.type); + continue; + } + seen_types.insert(tlv.type); + combined_tlv_vector.emplace_back(tlv); + } + for (const auto& tlv : proxy_proto_data.tlv_vector_) { if (!pass_all_tlvs && !pass_through_tlvs.contains(tlv.type)) { + // Skip any TLV when pass_all_tlvs is disabled, or the TLV is not in the pass_through_tlvs. continue; } + if (seen_types.contains(tlv.type)) { + // Skip any duplicate TLVs from being added to the combined TLV vector. + ENVOY_LOG_MISC(warn, "Ignoring duplicate custom TLV type {}", tlv.type); + continue; + } + seen_types.insert(tlv.type); + combined_tlv_vector.emplace_back(tlv); + } + + uint64_t extension_length = 0; + for (auto&& tlv : combined_tlv_vector) { extension_length += PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN + tlv.value.size(); if (extension_length > std::numeric_limits::max()) { ENVOY_LOG_MISC( @@ -141,16 +165,13 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer generateV2Header(src.addressAsString(), dst.addressAsString(), src.port(), dst.port(), src.version(), static_cast(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(tlv.value.size())); out.add(&size, sizeof(uint16_t)); out.add(&tlv.value.front(), tlv.value.size()); } + return true; } diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.h b/source/extensions/common/proxy_protocol/proxy_protocol_header.h index a4a09f46f98c7..669ede766fdc3 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.h +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.h @@ -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& pass_through_tlvs); + bool pass_all_tlvs, const absl::flat_hash_set& pass_through_tlvs, + const std::vector& custom_tlvs); } // namespace ProxyProtocol } // namespace Common diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc index 17bf1342b1a66..3e1d5a8eee885 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc @@ -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; } diff --git a/source/extensions/transport_sockets/proxy_protocol/BUILD b/source/extensions/transport_sockets/proxy_protocol/BUILD index 4e91f1ad363b4..4f3654de437f3 100644 --- a/source/extensions/transport_sockets/proxy_protocol/BUILD +++ b/source/extensions/transport_sockets/proxy_protocol/BUILD @@ -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", ], ) diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index 672d856d33d72..ccb76e7a33172 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -3,13 +3,17 @@ #include #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; @@ -93,7 +97,7 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { } else { const auto options = options_->proxyProtocolOptions().value(); if (!Common::ProxyProtocol::generateV2Header(options, header_buffer_, pass_all_tlvs_, - pass_through_tlvs_)) { + pass_through_tlvs_, buildCustomTLVs())) { // There is a warn log in generateV2Header method. stats_.v2_tlvs_exceed_max_length_.inc(); } @@ -165,6 +169,49 @@ void UpstreamProxyProtocolSocketFactory::hashKey( } } +std::vector UpstreamProxyProtocolSocket::buildCustomTLVs() { + std::vector custom_tlvs; + + const auto& upstream_info = callbacks_->connection().streamInfo().upstreamInfo(); + if (upstream_info == nullptr) { + return custom_tlvs; + } + Upstream::HostDescriptionConstSharedPtr host = upstream_info->upstreamHost(); + if (host == nullptr) { + return custom_tlvs; + } + auto metadata = host->metadata(); + if (metadata == nullptr) { + return custom_tlvs; + } + + 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()) { + ENVOY_LOG(trace, "no custom TLVs found in upstream host metadata"); + return custom_tlvs; + } + + envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_metadata; + if (!filter_it->second.UnpackTo(&custom_metadata)) { + ENVOY_LOG(warn, "failed to unpack custom TLVs from upstream host metadata"); + return custom_tlvs; + } + for (const auto& tlv : custom_metadata.entries()) { + // prevent empty TLV values + if (tlv.value().empty()) { + ENVOY_LOG(warn, "empty custom TLV value found in upstream host metadata for type {}", + tlv.type()); + continue; + } + custom_tlvs.emplace_back(Network::ProxyProtocolTLV{ + static_cast(tlv.type()), + std::vector(tlv.value().begin(), tlv.value().end())}); + } + + return custom_tlvs; +} + } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index f86c9e9cce299..1c09efae3be7d 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -44,6 +44,7 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, void generateHeaderV1(); void generateHeaderV2(); Network::IoResult writeHeader(); + std::vector buildCustomTLVs(); Network::TransportSocketOptionsConstSharedPtr options_; Network::TransportSocketCallbacks* callbacks_{}; diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc index 53eb70cc001de..37fcfd0654126 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc @@ -133,8 +133,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2IPv4HeaderWithTLVPassAll) { Network::ProxyProtocolTLV tlv{0x5, {0x06, 0x07}}; Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; Buffer::OwnedImpl buff{}; - - ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, true, {})); + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, true, {}, {})); EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } @@ -153,7 +152,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2IPv4HeaderWithTLVPassEmpty) { Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; Buffer::OwnedImpl buff{}; - ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {})); + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {}, {})); EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } @@ -172,7 +171,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2IPv4HeaderWithTLVPassSpecific) { Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; Buffer::OwnedImpl buff{}; - ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0x5})); + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0x5}, {})); EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } @@ -192,7 +191,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2IPv6HeaderWithTLV) { Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; Buffer::OwnedImpl buff{}; - ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, true, {})); + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, true, {}, {})); EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } @@ -208,7 +207,116 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithTLVExceedingLengthLimit) { Buffer::OwnedImpl buff{}; EXPECT_LOG_CONTAINS("warn", "Generating Proxy Protocol V2 header: TLVs exceed length limit 65535", - generateV2Header(proxy_proto_data, buff, true, {})); + generateV2Header(proxy_proto_data, buff, true, {}, {})); +} + +TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVs) { + const uint8_t v2_protocol[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, + 0x11, 0x00, 0x15, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, + 0x02, 0x01, 0x08, 0x00, 0x01, 0x08, 0xD3, 0x00, 0x02, 0x06, 0x07, + }; + + const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); + auto dst_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); + Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; + std::vector custom_tlvs = { + {0x8, {0x08}}, + }; + Buffer::OwnedImpl buff{}; + + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); + EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); +} + +// Verify duplicate custom TLV keys are properly handled. +TEST(ProxyProtocolHeaderTest, GeneratesV2WithDuplicateCustomTLVKeys) { + const uint8_t v2_protocol[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, + 0x11, 0x00, 0x15, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, + 0x02, 0x01, 0x08, 0x00, 0x01, 0x09, 0xD3, 0x00, 0x02, 0x06, 0x07, + }; + + const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); + auto dst_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); + Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; + std::vector custom_tlvs = { + {0x8, {0x09}}, + {0x8, {0x08}}, + }; + Buffer::OwnedImpl buff{}; + + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); + EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); +} + +TEST(ProxyProtocolHeaderTest, GeneratesV2WithSharedProxyAndCustomTLVKeys) { + const uint8_t v2_protocol[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, + 0x0a, 0x21, 0x11, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, + 0x01, 0x02, 0x03, 0x05, 0x02, 0x01, 0xD3, 0x00, 0x01, 0x09, + }; + + const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); + auto dst_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); + Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; + std::vector custom_tlvs = { + {0xD3, {0x09}}, + }; + Buffer::OwnedImpl buff{}; + + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); + EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); +} + +TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVExceedingLengthLimit) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); + auto dst_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); + Network::ProxyProtocolTLV tlv{0x5, {0x06, 0x07}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; + Buffer::OwnedImpl buff{}; + std::vector custom_tlvs = { + {0x8, std::vector(65536, 'a')}, + }; + EXPECT_LOG_CONTAINS("warn", "Generating Proxy Protocol V2 header: TLVs exceed length limit 65535", + generateV2Header(proxy_proto_data, buff, true, {}, custom_tlvs)); +} + +TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVsNoPassthrough) { + const uint8_t v2_protocol[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, + 0x0a, 0x21, 0x11, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, + 0x01, 0x02, 0x03, 0x05, 0x02, 0x01, 0xD3, 0x00, 0x01, 0x09, + }; + + const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); + auto dst_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); + Network::ProxyProtocolTLV tlv{0xD5, {0x06, 0x07}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; + std::vector custom_tlvs = { + {0xD3, {0x09}}, + }; + Buffer::OwnedImpl buff{}; + + ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {}, custom_tlvs)); + EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } } // namespace diff --git a/test/extensions/transport_sockets/proxy_protocol/BUILD b/test/extensions/transport_sockets/proxy_protocol/BUILD index 0aa2be2b939f8..057a8538978a3 100644 --- a/test/extensions/transport_sockets/proxy_protocol/BUILD +++ b/test/extensions/transport_sockets/proxy_protocol/BUILD @@ -25,6 +25,7 @@ envoy_extension_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/network:transport_socket_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/proxy_protocol/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index ef2875dc8e390..40a378c084fc8 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -416,10 +416,12 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParam& tlvs_listener, - const std::vector& tlvs_upstream) { + const std::vector& tlvs_upstream, + const std::vector>>& custom_tlvs) { pass_all_tlvs_ = pass_all_tlvs; tlvs_listener_.assign(tlvs_listener.begin(), tlvs_listener.end()); tlvs_upstream_.assign(tlvs_upstream.begin(), tlvs_upstream.end()); + custom_tlvs_.assign(custom_tlvs.begin(), custom_tlvs.end()); } void initialize() override { @@ -463,6 +465,31 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParammutable_clusters(0) + ->mutable_load_assignment() + ->mutable_endpoints(0) + ->mutable_lb_endpoints(0) + ->mutable_metadata(); + + envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata + custom_tlv_metadata; + for (const auto& tlv : custom_tlvs_) { + auto* tlv_entry = custom_tlv_metadata.add_entries(); + tlv_entry->set_type(tlv.first); + tlv_entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); + } + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(custom_tlv_metadata); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + metadata->mutable_typed_filter_metadata()->emplace( + std::make_pair(metadata_key, typed_metadata)); + } + envoy::extensions::transport_sockets::proxy_protocol::v3::ProxyProtocolUpstreamTransport proxy_proto_transport; proxy_proto_transport.mutable_transport_socket()->MergeFrom(inner_socket); @@ -479,6 +506,7 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParam tlvs_listener_; std::vector tlvs_upstream_; + std::vector>> custom_tlvs_; }; INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtocolTLVsIntegrationTest, @@ -488,7 +516,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtocolTLVsIntegrationTest, // This test adding the listener proxy protocol filter and upstream proxy filter, the TLVs // are passed by listener and re-generated in transport socket based on API config. TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassSepcificTLVs) { - setup(false, {0x05, 0x06}, {0x06}); + setup(false, {0x05, 0x06}, {0x06}, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -559,7 +587,7 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassSepcificTLVs) } TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassAll) { - setup(true, {}, {}); + setup(true, {}, {}, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -641,7 +669,7 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassAll) { } TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2ProxyProtocolPassWithTypeLocal) { - setup(true, {}, {}); + setup(true, {}, {}, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -681,5 +709,178 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2ProxyProtocolPassWithTypeLocal) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } +TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithCustomMetadata) { + std::vector>> custom_tlvs = { + {0x96, {'f', 'o', 'o'}}, {0x97, {'b', 'a', 'r'}}}; + setup(false, {}, {}, custom_tlvs); + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + std::string observed_data; + + if (GetParam() == Envoy::Network::Address::IpVersion::v4) { + // Expected downstream proxy protocol header (IPv4). + const uint8_t v2_protocol[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, 0x11, 0x00, + 0x0c, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0x02, 0x1f, 0x90, 0x23, 0xc4, + }; + Buffer::OwnedImpl buffer(v2_protocol, sizeof(v2_protocol)); + ASSERT_TRUE(tcp_client->write(buffer.toString())); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForData( + 42, &observed_data)); // 30 inbound size + 12 for custom TLVs. + // Verify the custom TLV was injected from filter metadata. + EXPECT_EQ(observed_data.size(), 42); + size_t offset = 28; // Start after the header + // Verify custom TLV 0x96 + EXPECT_EQ(static_cast(observed_data[offset]), 0x96); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "foo"); + offset += 6; + // Verify custom TLV 0x97 + EXPECT_EQ(static_cast(observed_data[offset]), 0x97); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "bar"); + offset += 6; + } else if (GetParam() == Envoy::Network::Address::IpVersion::v6) { + // Expected downstream proxy protocol header (IPv6). + const uint8_t v2_protocol_ipv6[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, + 0x21, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x02, + }; + Buffer::OwnedImpl buffer(v2_protocol_ipv6, sizeof(v2_protocol_ipv6)); + ASSERT_TRUE(tcp_client->write(buffer.toString())); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForData( + 64, &observed_data)); // 52 inbound size + 12 for custom TLVs. + // Verify the custom TLV was injected from filter metadata. + EXPECT_EQ(observed_data.size(), 64); + size_t offset = 52; // Start after the header + // Verify custom TLV 0x96 + EXPECT_EQ(static_cast(observed_data[offset]), 0x96); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "foo"); + offset += 6; + // Verify custom TLV 0x97 + EXPECT_EQ(static_cast(observed_data[offset]), 0x97); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "bar"); + offset += 6; + } + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); +} + +TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithPassthroughAndCustomTLVs) { + // Setup with passthrough TLVs (0x05, 0x06) and custom TLVs (0x96, 0x97). + std::vector>> custom_tlvs = { + {0x96, {'f', 'o', 'o'}}, {0x97, {'b', 'a', 'r'}}}; + setup(true, {}, {}, custom_tlvs); + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + std::string observed_data; + + if (GetParam() == Envoy::Network::Address::IpVersion::v4) { + // 2 TLVs are included: + // 0x05, 0x00, 0x02, 0x06, 0x07 + // 0x06, 0x00, 0x02, 0x11, 0x12 + const uint8_t v2_protocol[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, + 0x11, 0x00, 0x16, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x03, 0x05, + 0x02, 0x01, 0x05, 0x00, 0x02, 0x06, 0x07, 0x06, 0x00, 0x02, 0x11, 0x12, + }; + Buffer::OwnedImpl buffer(v2_protocol, sizeof(v2_protocol)); + ASSERT_TRUE(tcp_client->write(buffer.toString())); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + // Expected data size: 28 header + 10 pass through TLVs + 12 custom TLVs = 50 bytes + ASSERT_TRUE(fake_upstream_connection_->waitForData(50, &observed_data)); + EXPECT_EQ(observed_data.size(), 50); + + size_t offset = 28; // Start after the header + // Verify custom TLV 0x96. Note: we insert custom TLVs before passthrough TLVs in the header. + EXPECT_EQ(static_cast(observed_data[offset]), 0x96); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "foo"); + offset += 6; + // Verify custom TLV 0x97 + EXPECT_EQ(static_cast(observed_data[offset]), 0x97); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "bar"); + offset += 6; + // Verify passthrough TLV 0x05 + EXPECT_EQ(static_cast(observed_data[offset]), 0x05); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); + EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x06); + EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x07); + offset += 5; + // Verify passthrough TLV 0x06 + EXPECT_EQ(static_cast(observed_data[offset]), 0x06); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); + EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x11); + EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x12); + offset += 5; + } else if (GetParam() == Envoy::Network::Address::IpVersion::v6) { + // 2 TLVs are included: + // 0x05, 0x00, 0x02, 0x06, 0x07 + // 0x06, 0x00, 0x02, 0x09, 0x0A + const uint8_t v2_protocol_ipv6[] = { + 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, + 0x21, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x02, + 0x05, 0x00, 0x02, 0x06, 0x07, 0x06, 0x00, 0x02, 0x09, 0x0A, + }; + Buffer::OwnedImpl buffer(v2_protocol_ipv6, sizeof(v2_protocol_ipv6)); + ASSERT_TRUE(tcp_client->write(buffer.toString())); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + // Expected data size: 52 header + 10 passthrough + 12 custom TLVs = 74 bytes. + ASSERT_TRUE(fake_upstream_connection_->waitForData(74, &observed_data)); + EXPECT_EQ(observed_data.size(), 74); + + size_t offset = 52; // Start after the header + // Verify custom TLV 0x96 + EXPECT_EQ(static_cast(observed_data[offset]), 0x96); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "foo"); + offset += 6; + // Verify custom TLV 0x97 + EXPECT_EQ(static_cast(observed_data[offset]), 0x97); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "bar"); + offset += 6; + // Verify passthrough TLV 0x05 + EXPECT_EQ(static_cast(observed_data[offset]), 0x05); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); + EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x06); + EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x07); + offset += 5; + // Verify passthrough TLV 0x06 + EXPECT_EQ(static_cast(observed_data[offset]), 0x06); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); + EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x09); + EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x0A); + offset += 5; + } + + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); +} + } // namespace } // namespace Envoy diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 748a4113373da..45e2f4ee9cd0f 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -1,4 +1,6 @@ #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/proxy_protocol.h" #include "source/common/buffer/buffer_impl.h" @@ -491,7 +493,7 @@ TEST_F(ProxyProtocolTest, V2IPV4DownstreamAddressesAndTLVs) { ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://3.3.3.3:80")); Buffer::OwnedImpl expected_buff{}; absl::flat_hash_set pass_tlvs_set{}; - Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, pass_tlvs_set); + Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, pass_tlvs_set, {}); ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); @@ -530,7 +532,8 @@ TEST_F(ProxyProtocolTest, V2IPV4PassSpecificTLVs) { ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://3.3.3.3:80")); Buffer::OwnedImpl expected_buff{}; absl::flat_hash_set pass_tlvs_set{0x05}; - Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, pass_tlvs_set); + Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, pass_tlvs_set, + {}); ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); @@ -570,7 +573,8 @@ TEST_F(ProxyProtocolTest, V2IPV4PassEmptyTLVs) { ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://3.3.3.3:80")); Buffer::OwnedImpl expected_buff{}; absl::flat_hash_set pass_tlvs_set{}; - Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, pass_tlvs_set); + Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, pass_tlvs_set, + {}); ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); @@ -638,7 +642,8 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVs) { ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); Buffer::OwnedImpl expected_buff{}; absl::flat_hash_set pass_through_tlvs{}; - Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, pass_through_tlvs); + Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, pass_through_tlvs, + {}); ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); @@ -676,8 +681,8 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVsWithoutPassConfig) { ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); Buffer::OwnedImpl expected_buff{}; absl::flat_hash_set pass_through_tlvs{}; - Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, - pass_through_tlvs); + Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, pass_through_tlvs, + {}); ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); @@ -695,6 +700,184 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVsWithoutPassConfig) { proxy_protocol_socket_->doWrite(msg, false); } +// Test injects V2 PROXY protocol with custom TLVs from host metadata. +TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + + envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_tlv_metadata; + auto entry = custom_tlv_metadata.add_entries(); + entry->set_type(0x96); + entry->set_value("moredata"); + + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(custom_tlv_metadata); + metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); + EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{}; + std::vector custom_tlvs = { + {0x96, {'m', 'o', 'r', 'e', 'd', 'a', 't', 'a'}}}; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, + pass_through_tlvs, custom_tlvs)); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + +// Test injects V2 PROXY protocol with pass through & custom TLVs. +TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + + envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_tlv_metadata; + auto entry = custom_tlv_metadata.add_entries(); + entry->set_type(0x96); + entry->set_value("moredata"); + + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(custom_tlv_metadata); + metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); + EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{0x5}; + std::vector custom_tlvs = { + {0x96, {'m', 'o', 'r', 'e', 'd', 'a', 't', 'a'}}}; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, + pass_through_tlvs, custom_tlvs)); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + +TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadata) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + + envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_tlv_metadata; + auto valid_entry = custom_tlv_metadata.add_entries(); + valid_entry->set_type(0x96); + valid_entry->set_value("cluster_0"); + auto invalid_entry_empty_val = custom_tlv_metadata.add_entries(); + invalid_entry_empty_val->set_type(0x97); + invalid_entry_empty_val->set_value(""); + auto invalid_entry_duplicate = custom_tlv_metadata.add_entries(); + invalid_entry_duplicate->set_type(0x96); + invalid_entry_duplicate->set_value("cluster_1"); + + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(custom_tlv_metadata); + metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); + EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{0x5}; + std::vector custom_tlvs = { + {0x96, {'c', 'l', 'u', 's', 't', 'e', 'r', '_', '0'}}}; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, + pass_through_tlvs, custom_tlvs)); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + class ProxyProtocolSocketFactoryTest : public testing::Test { public: void initialize() { diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index fe2dfa867ede8..49e09a2b0ca52 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -1037,7 +1037,8 @@ AssertionResult FakeRawConnection::waitForData(uint64_t num_bytes, std::string* ENVOY_LOG(debug, "waiting for {} bytes of data", num_bytes); if (!time_system_.waitFor(lock_, absl::Condition(&reached), timeout)) { return AssertionFailure() << fmt::format( - "Timed out waiting for data. Got '{}', waiting for {} bytes.", data_, num_bytes); + "Timed out waiting for data. Got '{}', expected {} bytes, waiting for {} bytes.", + data_, data_.size(), num_bytes); } if (data != nullptr) { *data = data_; From d0a3b622e5727e52cee9117de5dba73b8a57419b Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 11 Dec 2024 00:53:00 +0000 Subject: [PATCH 02/22] pp2: Add custom TLV entries field to protocol protocol config API Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 13 ++ .../v3/upstream_proxy_protocol.proto | 19 --- .../proxy_protocol/proxy_protocol.cc | 64 ++++++--- .../proxy_protocol/proxy_protocol.h | 4 +- .../proxy_protocol_header_test.cc | 2 +- .../proxy_protocol_integration_test.cc | 126 ++++++++++------- .../proxy_protocol/proxy_protocol_test.cc | 128 +++++++++++++++++- 7 files changed, 257 insertions(+), 99 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index 32747dd2288da..0a7415684ac79 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -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. @@ -47,4 +56,8 @@ 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; + + // Custom TLV metadata to be sent in the upstream PROXY protocol header. This field + // is only valid when the version is V2. + repeated TlvEntry entries = 3; } diff --git a/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto b/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto index a66d8b62a4281..12c0e92dc19de 100644 --- a/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto +++ b/api/envoy/extensions/transport_sockets/proxy_protocol/v3/upstream_proxy_protocol.proto @@ -17,21 +17,6 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Upstream Proxy Protocol] // [#extension: envoy.transport_sockets.upstream_proxy_protocol] -// Metadata for custom Type-Length-Value (TLV) entries to be sent in the upstream PROXY protocol header. -message CustomTlvMetadata { - // A list of TLV entries. At least one entry must be provided. - repeated TlvEntry entries = 1 [(validate.rules).repeated = {min_items: 1}]; -} - -// 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}]; -} - // Configuration for PROXY protocol socket message ProxyProtocolUpstreamTransport { // The PROXY protocol settings @@ -48,8 +33,4 @@ message ProxyProtocolUpstreamTransport { // If true, all the TLVs are encoded in the connection pool key. // [#not-implemented-hide:] bool tlv_as_pool_key = 4; - - // Custom TLV metadata to be sent in the upstream PROXY protocol header. - // [#not-implemented-hide:] - CustomTlvMetadata custom_tlv_metadata = 5; } diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index ccb76e7a33172..f0560e831a1d3 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -40,6 +40,15 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( pass_through_tlvs_.insert(0xFF & tlv_type); } } + for (const auto& entry : config.entries()) { + if (entry.value().empty()) { + ENVOY_LOG(warn, "Skipping custom TLV with type {} due to empty value", entry.type()); + continue; + } + config_tlvs_.emplace_back(Network::ProxyProtocolTLV{ + static_cast(entry.type()), + std::vector(entry.value().begin(), entry.value().end())}); + } } void UpstreamProxyProtocolSocket::setTransportSocketCallbacks( @@ -95,9 +104,21 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { if (!options_ || !options_->proxyProtocolOptions().has_value()) { Common::ProxyProtocol::generateV2LocalHeader(header_buffer_); } else { + // process any custom TLVs from the host metadata. + auto host_metadata_tlv_types = processCustomTLVsFromHost(); + // backfill any custom TLVs defined the config that are not in the host metadata. + for (const auto& tlv : config_tlvs_) { + // Skip any TLV that is already in the custom TLVs. We want the host + // metadata value to take precedence when there is a conflict. + if (host_metadata_tlv_types.contains(tlv.type)) { + continue; + } + custom_tlvs_.push_back(tlv); + } + const auto options = options_->proxyProtocolOptions().value(); if (!Common::ProxyProtocol::generateV2Header(options, header_buffer_, pass_all_tlvs_, - pass_through_tlvs_, buildCustomTLVs())) { + pass_through_tlvs_, custom_tlvs_)) { // There is a warn log in generateV2Header method. stats_.v2_tlvs_exceed_max_length_.inc(); } @@ -169,47 +190,48 @@ void UpstreamProxyProtocolSocketFactory::hashKey( } } -std::vector UpstreamProxyProtocolSocket::buildCustomTLVs() { - std::vector custom_tlvs; +absl::flat_hash_set UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { + absl::flat_hash_set host_metadata_tlv_types; const auto& upstream_info = callbacks_->connection().streamInfo().upstreamInfo(); if (upstream_info == nullptr) { - return custom_tlvs; + return host_metadata_tlv_types; } Upstream::HostDescriptionConstSharedPtr host = upstream_info->upstreamHost(); if (host == nullptr) { - return custom_tlvs; + return host_metadata_tlv_types; } auto metadata = host->metadata(); if (metadata == nullptr) { - return custom_tlvs; + return host_metadata_tlv_types; } 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()) { - ENVOY_LOG(trace, "no custom TLVs found in upstream host metadata"); - return custom_tlvs; + ENVOY_LOG(trace, "No custom TLVs found in upstream host metadata"); + return host_metadata_tlv_types; } - envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_metadata; - if (!filter_it->second.UnpackTo(&custom_metadata)) { - ENVOY_LOG(warn, "failed to unpack custom TLVs from upstream host metadata"); - return custom_tlvs; + ProxyProtocolConfig tlvs_metadata; + if (!filter_it->second.UnpackTo(&tlvs_metadata)) { + ENVOY_LOG(warn, "Failed to unpack custom TLVs from upstream host metadata"); + return host_metadata_tlv_types; } - for (const auto& tlv : custom_metadata.entries()) { - // prevent empty TLV values - if (tlv.value().empty()) { - ENVOY_LOG(warn, "empty custom TLV value found in upstream host metadata for type {}", - tlv.type()); + + // process the custom TLVs from the host metadata first. + for (const auto& entry : tlvs_metadata.entries()) { + // prevent empty values from being added to custom TLVs. + if (entry.value().empty()) { + ENVOY_LOG(warn, "Skipping custom TLV with type {} due to empty value", entry.type()); continue; } - custom_tlvs.emplace_back(Network::ProxyProtocolTLV{ - static_cast(tlv.type()), - std::vector(tlv.value().begin(), tlv.value().end())}); + custom_tlvs_.emplace_back(Network::ProxyProtocolTLV{ + static_cast(entry.type()), + std::vector(entry.value().begin(), entry.value().end())}); } - return custom_tlvs; + return host_metadata_tlv_types; } } // namespace ProxyProtocol diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index 1c09efae3be7d..08a11646e932c 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -43,8 +43,8 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, void generateHeader(); void generateHeaderV1(); void generateHeaderV2(); + absl::flat_hash_set processCustomTLVsFromHost(); Network::IoResult writeHeader(); - std::vector buildCustomTLVs(); Network::TransportSocketOptionsConstSharedPtr options_; Network::TransportSocketCallbacks* callbacks_{}; @@ -53,6 +53,8 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, const UpstreamProxyProtocolStats& stats_; const bool pass_all_tlvs_; absl::flat_hash_set pass_through_tlvs_{}; + std::vector custom_tlvs_{}; + std::vector config_tlvs_{}; }; class UpstreamProxyProtocolSocketFactory : public PassthroughFactory { diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc index 37fcfd0654126..7298a822a29e2 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc @@ -258,7 +258,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithDuplicateCustomTLVKeys) { EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } -TEST(ProxyProtocolHeaderTest, GeneratesV2WithSharedProxyAndCustomTLVKeys) { +TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVsPrecendence) { const uint8_t v2_protocol[] = { 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, 0x11, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index 40a378c084fc8..6cf7765e839a4 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -415,13 +415,16 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParam& tlvs_listener, - const std::vector& tlvs_upstream, - const std::vector>>& custom_tlvs) { + void setup( + bool pass_all_tlvs, const std::vector& tlvs_listener, + const std::vector& tlvs_upstream, + const std::vector>>& custom_tlvs_from_host, + const std::vector>>& custom_tlvs_from_config) { pass_all_tlvs_ = pass_all_tlvs; tlvs_listener_.assign(tlvs_listener.begin(), tlvs_listener.end()); tlvs_upstream_.assign(tlvs_upstream.begin(), tlvs_upstream.end()); - custom_tlvs_.assign(custom_tlvs.begin(), custom_tlvs.end()); + custom_tlvs_from_host_.assign(custom_tlvs_from_host.begin(), custom_tlvs_from_host.end()); + custom_tlvs_from_config_.assign(custom_tlvs_from_config.begin(), custom_tlvs_from_config.end()); } void initialize() override { @@ -465,8 +468,8 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParammutable_clusters(0) @@ -475,20 +478,27 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParammutable_lb_endpoints(0) ->mutable_metadata(); - envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata - custom_tlv_metadata; - for (const auto& tlv : custom_tlvs_) { - auto* tlv_entry = custom_tlv_metadata.add_entries(); - tlv_entry->set_type(tlv.first); - tlv_entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); + envoy::config::core::v3::ProxyProtocolConfig tlvs_metadata; + for (const auto& tlv : custom_tlvs_from_host_) { + auto entry = tlvs_metadata.add_entries(); + entry->set_type(tlv.first); + entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); } ProtobufWkt::Any typed_metadata; - typed_metadata.PackFrom(custom_tlv_metadata); + typed_metadata.PackFrom(tlvs_metadata); const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; metadata->mutable_typed_filter_metadata()->emplace( std::make_pair(metadata_key, typed_metadata)); } + // Add custom TLVs to proxy protocol config if needed. + if (!custom_tlvs_from_config_.empty()) { + for (const auto& tlv : custom_tlvs_from_config_) { + auto entry = proxy_protocol.add_entries(); + entry->set_type(tlv.first); + entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); + } + } envoy::extensions::transport_sockets::proxy_protocol::v3::ProxyProtocolUpstreamTransport proxy_proto_transport; @@ -506,7 +516,8 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParam tlvs_listener_; std::vector tlvs_upstream_; - std::vector>> custom_tlvs_; + std::vector>> custom_tlvs_from_host_; + std::vector>> custom_tlvs_from_config_; }; INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtocolTLVsIntegrationTest, @@ -516,7 +527,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, ProxyProtocolTLVsIntegrationTest, // This test adding the listener proxy protocol filter and upstream proxy filter, the TLVs // are passed by listener and re-generated in transport socket based on API config. TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassSepcificTLVs) { - setup(false, {0x05, 0x06}, {0x06}, {}); + setup(false, {0x05, 0x06}, {0x06}, {}, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -587,7 +598,7 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassSepcificTLVs) } TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassAll) { - setup(true, {}, {}, {}); + setup(true, {}, {}, {}, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -669,7 +680,7 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolPassAll) { } TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2ProxyProtocolPassWithTypeLocal) { - setup(true, {}, {}, {}); + setup(true, {}, {}, {}, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -712,7 +723,7 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2ProxyProtocolPassWithTypeLocal) { TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithCustomMetadata) { std::vector>> custom_tlvs = { {0x96, {'f', 'o', 'o'}}, {0x97, {'b', 'a', 'r'}}}; - setup(false, {}, {}, custom_tlvs); + setup(false, {}, {}, custom_tlvs, {}); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -778,11 +789,20 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithCustomMetadat ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } -TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithPassthroughAndCustomTLVs) { - // Setup with passthrough TLVs (0x05, 0x06) and custom TLVs (0x96, 0x97). - std::vector>> custom_tlvs = { - {0x96, {'f', 'o', 'o'}}, {0x97, {'b', 'a', 'r'}}}; - setup(true, {}, {}, custom_tlvs); +TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithPrecedence) { + // Setup with passthrough TLVs (0x05, 0x06), config-level TLVs (0x96, 0x97), + // and host metadata TLVs (0x96 overrides config, 0x98, and 0x05 overrides passthrough). + std::vector>> host_metadata_tlvs = { + {0x96, {'o', 'v', 'r'}}, + {0x98, {'n', 'e', 'w'}}, + {0x05, {'f', 'o', 'o'}}, + }; + std::vector>> config_tlvs = { + {0x96, {'f', 'o', 'o'}}, + {0x97, {'b', 'a', 'r'}}, + }; + + setup(true, {}, {}, host_metadata_tlvs, config_tlvs); initialize(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -800,37 +820,40 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithPassthroughAn Buffer::OwnedImpl buffer(v2_protocol, sizeof(v2_protocol)); ASSERT_TRUE(tcp_client->write(buffer.toString())); ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); - // Expected data size: 28 header + 10 pass through TLVs + 12 custom TLVs = 50 bytes - ASSERT_TRUE(fake_upstream_connection_->waitForData(50, &observed_data)); - EXPECT_EQ(observed_data.size(), 50); + ASSERT_TRUE(fake_upstream_connection_->waitForData(57, &observed_data)); + EXPECT_EQ(observed_data.size(), 57); size_t offset = 28; // Start after the header - // Verify custom TLV 0x96. Note: we insert custom TLVs before passthrough TLVs in the header. + // Verify host metadata TLV 0x96 overrides config TLV 0x96 value. EXPECT_EQ(static_cast(observed_data[offset]), 0x96); EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "ovr"); + offset += 6; + // Verify host metadata TLV 0x98 is present. + EXPECT_EQ(static_cast(observed_data[offset]), 0x98); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "new"); + offset += 6; + // Verify passthrough TLV 0x05 was overridden by host metadata. + EXPECT_EQ(static_cast(observed_data[offset]), 0x05); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); EXPECT_EQ(observed_data.substr(offset + 3, 3), "foo"); offset += 6; - // Verify custom TLV 0x97 + // Verify config TLV 0x97 is present. EXPECT_EQ(static_cast(observed_data[offset]), 0x97); EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); EXPECT_EQ(observed_data.substr(offset + 3, 3), "bar"); offset += 6; - // Verify passthrough TLV 0x05 - EXPECT_EQ(static_cast(observed_data[offset]), 0x05); - EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); - EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); - EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x06); - EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x07); - offset += 5; - // Verify passthrough TLV 0x06 + // Verify passthrough TLV 0x06 is present. EXPECT_EQ(static_cast(observed_data[offset]), 0x06); EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x11); EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x12); - offset += 5; } else if (GetParam() == Envoy::Network::Address::IpVersion::v6) { // 2 TLVs are included: // 0x05, 0x00, 0x02, 0x06, 0x07 @@ -845,37 +868,40 @@ TEST_P(ProxyProtocolTLVsIntegrationTest, TestV2TLVProxyProtocolWithPassthroughAn Buffer::OwnedImpl buffer(v2_protocol_ipv6, sizeof(v2_protocol_ipv6)); ASSERT_TRUE(tcp_client->write(buffer.toString())); ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); - // Expected data size: 52 header + 10 passthrough + 12 custom TLVs = 74 bytes. - ASSERT_TRUE(fake_upstream_connection_->waitForData(74, &observed_data)); - EXPECT_EQ(observed_data.size(), 74); + ASSERT_TRUE(fake_upstream_connection_->waitForData(81, &observed_data)); + EXPECT_EQ(observed_data.size(), 81); size_t offset = 52; // Start after the header - // Verify custom TLV 0x96 + // Verify host metadata TLV 0x96 overrides config TLV 0x96 value. EXPECT_EQ(static_cast(observed_data[offset]), 0x96); EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "ovr"); + offset += 6; + // Verify host metadata TLV 0x98 is present. + EXPECT_EQ(static_cast(observed_data[offset]), 0x98); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); + EXPECT_EQ(observed_data.substr(offset + 3, 3), "new"); + offset += 6; + // Verify passthrough TLV 0x05 was overridden by host metadata. + EXPECT_EQ(static_cast(observed_data[offset]), 0x05); + EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); + EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); EXPECT_EQ(observed_data.substr(offset + 3, 3), "foo"); offset += 6; - // Verify custom TLV 0x97 + // Verify config TLV 0x97 is present. EXPECT_EQ(static_cast(observed_data[offset]), 0x97); EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x03); EXPECT_EQ(observed_data.substr(offset + 3, 3), "bar"); offset += 6; - // Verify passthrough TLV 0x05 - EXPECT_EQ(static_cast(observed_data[offset]), 0x05); - EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); - EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); - EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x06); - EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x07); - offset += 5; // Verify passthrough TLV 0x06 EXPECT_EQ(static_cast(observed_data[offset]), 0x06); EXPECT_EQ(static_cast(observed_data[offset + 1]), 0x00); EXPECT_EQ(static_cast(observed_data[offset + 2]), 0x02); EXPECT_EQ(static_cast(observed_data[offset + 3]), 0x09); EXPECT_EQ(static_cast(observed_data[offset + 4]), 0x0A); - offset += 5; } tcp_client->close(); diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 45e2f4ee9cd0f..493608f28b6c8 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -700,6 +700,51 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVsWithoutPassConfig) { proxy_protocol_socket_->doWrite(msg, false); } +// Test injects V2 PROXY protocol with custom TLVs from the config-level field. +TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + absl::flat_hash_set pass_through_tlvs{}; + std::vector custom_tlvs = { + {0x96, {'m', 'o', 'r', 'e', 'd', 'a', 't', 'a'}}, + }; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, + pass_through_tlvs, custom_tlvs)); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + auto tlv_entry = config.add_entries(); + tlv_entry->set_type(0x96); + tlv_entry->set_value("moredata"); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + // Test injects V2 PROXY protocol with custom TLVs from host metadata. TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { auto src_addr = @@ -722,7 +767,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_tlv_metadata; + ProxyProtocolConfig custom_tlv_metadata; auto entry = custom_tlv_metadata.add_entries(); entry->set_type(0x96); entry->set_value("moredata"); @@ -735,7 +780,8 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { absl::flat_hash_set pass_through_tlvs{}; std::vector custom_tlvs = { - {0x96, {'m', 'o', 'r', 'e', 'd', 'a', 't', 'a'}}}; + {0x96, {'m', 'o', 'r', 'e', 'd', 'a', 't', 'a'}}, + }; Buffer::OwnedImpl expected_buff{}; EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, pass_through_tlvs, custom_tlvs)); @@ -757,6 +803,72 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } +// Test injects V2 PROXY protocol with custom TLVs and validates that host metadata will +// overwrite any TLVs set in the config. +TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + + ProxyProtocolConfig custom_tlv_metadata; + auto entry = custom_tlv_metadata.add_entries(); + entry->set_type(0x96); + entry->set_value("foo"); + + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(custom_tlv_metadata); + metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); + EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{}; + std::vector custom_tlvs = { + {0x96, {'f', 'o', 'o'}}, + {0x97, {'b', 'a', 'z'}}, + }; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, + pass_through_tlvs, custom_tlvs)); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + auto tlv_entry = config.add_entries(); + tlv_entry->set_type(0x96); + tlv_entry->set_value("bar"); + tlv_entry = config.add_entries(); + tlv_entry->set_type(0x97); + tlv_entry->set_value("baz"); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + // Test injects V2 PROXY protocol with pass through & custom TLVs. TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { auto src_addr = @@ -779,10 +891,10 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_tlv_metadata; + ProxyProtocolConfig custom_tlv_metadata; auto entry = custom_tlv_metadata.add_entries(); entry->set_type(0x96); - entry->set_value("moredata"); + entry->set_value("foo"); ProtobufWkt::Any typed_metadata; typed_metadata.PackFrom(custom_tlv_metadata); @@ -792,7 +904,8 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { absl::flat_hash_set pass_through_tlvs{0x5}; std::vector custom_tlvs = { - {0x96, {'m', 'o', 'r', 'e', 'd', 'a', 't', 'a'}}}; + {0x96, {'f', 'o', 'o'}}, + }; Buffer::OwnedImpl expected_buff{}; EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, pass_through_tlvs, custom_tlvs)); @@ -836,7 +949,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadata) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - envoy::extensions::transport_sockets::proxy_protocol::v3::CustomTlvMetadata custom_tlv_metadata; + ProxyProtocolConfig custom_tlv_metadata; auto valid_entry = custom_tlv_metadata.add_entries(); valid_entry->set_type(0x96); valid_entry->set_value("cluster_0"); @@ -855,7 +968,8 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadata) { absl::flat_hash_set pass_through_tlvs{0x5}; std::vector custom_tlvs = { - {0x96, {'c', 'l', 'u', 's', 't', 'e', 'r', '_', '0'}}}; + {0x96, {'c', 'l', 'u', 's', 't', 'e', 'r', '_', '0'}}, + }; Buffer::OwnedImpl expected_buff{}; EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, pass_through_tlvs, custom_tlvs)); From abc5088a2806fb1b377c0be6a8f25a1da9be5edb Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 11 Dec 2024 17:40:28 +0000 Subject: [PATCH 03/22] pp2: Silence linting violation Signed-off-by: timflannagan --- .../common/proxy_protocol/proxy_protocol_header.cc | 2 +- .../transport_sockets/proxy_protocol/proxy_protocol.cc | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc index 35a9ad348a1d5..938af9964ae92 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc @@ -127,7 +127,7 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer } for (const auto& tlv : proxy_proto_data.tlv_vector_) { if (!pass_all_tlvs && !pass_through_tlvs.contains(tlv.type)) { - // Skip any TLV when pass_all_tlvs is disabled, or the TLV is not in the pass_through_tlvs. + // Skip any TLV that is not in the set of passthrough TLVs. continue; } if (seen_types.contains(tlv.type)) { diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index f0560e831a1d3..52caef09d7d33 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -104,9 +104,9 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { if (!options_ || !options_->proxyProtocolOptions().has_value()) { Common::ProxyProtocol::generateV2LocalHeader(header_buffer_); } else { - // process any custom TLVs from the host metadata. + // Process any custom TLVs from the host metadata. auto host_metadata_tlv_types = processCustomTLVsFromHost(); - // backfill any custom TLVs defined the config that are not in the host metadata. + // Populate any custom TLVs defined the config that are not in the host metadata. for (const auto& tlv : config_tlvs_) { // Skip any TLV that is already in the custom TLVs. We want the host // metadata value to take precedence when there is a conflict. @@ -214,7 +214,9 @@ absl::flat_hash_set UpstreamProxyProtocolSocket::processCustomTLVsFromH } ProxyProtocolConfig tlvs_metadata; - if (!filter_it->second.UnpackTo(&tlvs_metadata)) { + auto status = absl::OkStatus(); + status = MessageUtil::unpackTo(filter_it->second, tlvs_metadata); + if (!status.ok()) { ENVOY_LOG(warn, "Failed to unpack custom TLVs from upstream host metadata"); return host_metadata_tlv_types; } From 6d94e1d61288de02ae14015d9299e9aaa537b864 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Fri, 13 Dec 2024 00:32:12 +0000 Subject: [PATCH 04/22] pp2: Add additional unit tests Signed-off-by: timflannagan --- .../proxy_protocol/proxy_protocol.cc | 29 ++-- .../proxy_protocol/proxy_protocol.h | 3 +- .../proxy_protocol/proxy_protocol_test.cc | 137 +++++++++++++++++- 3 files changed, 143 insertions(+), 26 deletions(-) diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index 52caef09d7d33..95a4470301d94 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -42,7 +42,6 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( } for (const auto& entry : config.entries()) { if (entry.value().empty()) { - ENVOY_LOG(warn, "Skipping custom TLV with type {} due to empty value", entry.type()); continue; } config_tlvs_.emplace_back(Network::ProxyProtocolTLV{ @@ -105,12 +104,12 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { Common::ProxyProtocol::generateV2LocalHeader(header_buffer_); } else { // Process any custom TLVs from the host metadata. - auto host_metadata_tlv_types = processCustomTLVsFromHost(); + processCustomTLVsFromHost(); // Populate any custom TLVs defined the config that are not in the host metadata. for (const auto& tlv : config_tlvs_) { // Skip any TLV that is already in the custom TLVs. We want the host // metadata value to take precedence when there is a conflict. - if (host_metadata_tlv_types.contains(tlv.type)) { + if (host_metadata_tlv_types_.contains(tlv.type)) { continue; } custom_tlvs_.push_back(tlv); @@ -190,27 +189,21 @@ void UpstreamProxyProtocolSocketFactory::hashKey( } } -absl::flat_hash_set UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { - absl::flat_hash_set host_metadata_tlv_types; - +void UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { const auto& upstream_info = callbacks_->connection().streamInfo().upstreamInfo(); - if (upstream_info == nullptr) { - return host_metadata_tlv_types; - } - Upstream::HostDescriptionConstSharedPtr host = upstream_info->upstreamHost(); - if (host == nullptr) { - return host_metadata_tlv_types; + if (upstream_info == nullptr || upstream_info->upstreamHost() == nullptr) { + return; } - auto metadata = host->metadata(); + auto metadata = upstream_info->upstreamHost()->metadata(); if (metadata == nullptr) { - return host_metadata_tlv_types; + return; } 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()) { ENVOY_LOG(trace, "No custom TLVs found in upstream host metadata"); - return host_metadata_tlv_types; + return; } ProxyProtocolConfig tlvs_metadata; @@ -218,22 +211,22 @@ absl::flat_hash_set UpstreamProxyProtocolSocket::processCustomTLVsFromH status = MessageUtil::unpackTo(filter_it->second, tlvs_metadata); if (!status.ok()) { ENVOY_LOG(warn, "Failed to unpack custom TLVs from upstream host metadata"); - return host_metadata_tlv_types; + return; } // process the custom TLVs from the host metadata first. for (const auto& entry : tlvs_metadata.entries()) { // prevent empty values from being added to custom TLVs. if (entry.value().empty()) { - ENVOY_LOG(warn, "Skipping custom TLV with type {} due to empty value", entry.type()); continue; } custom_tlvs_.emplace_back(Network::ProxyProtocolTLV{ static_cast(entry.type()), std::vector(entry.value().begin(), entry.value().end())}); + host_metadata_tlv_types_.insert(entry.type()); } - return host_metadata_tlv_types; + return; } } // namespace ProxyProtocol diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index 08a11646e932c..81a4bf6e5ce07 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -43,7 +43,7 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, void generateHeader(); void generateHeaderV1(); void generateHeaderV2(); - absl::flat_hash_set processCustomTLVsFromHost(); + void processCustomTLVsFromHost(); Network::IoResult writeHeader(); Network::TransportSocketOptionsConstSharedPtr options_; @@ -53,6 +53,7 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, const UpstreamProxyProtocolStats& stats_; const bool pass_all_tlvs_; absl::flat_hash_set pass_through_tlvs_{}; + absl::flat_hash_set host_metadata_tlv_types_{}; std::vector custom_tlvs_{}; std::vector config_tlvs_{}; }; diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 493608f28b6c8..91fd3a34bed7a 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -727,9 +727,12 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto tlv_entry = config.add_entries(); - tlv_entry->set_type(0x96); - tlv_entry->set_value("moredata"); + auto valid_tlv_entry = config.add_entries(); + valid_tlv_entry->set_type(0x96); + valid_tlv_entry->set_value("moredata"); + auto invalid_tlv_entry = config.add_entries(); + invalid_tlv_entry->set_type(0x97); + invalid_tlv_entry->set_value(""); initialize(config, socket_options); EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) @@ -768,9 +771,12 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_entries(); - entry->set_type(0x96); - entry->set_value("moredata"); + auto valid_entry = custom_tlv_metadata.add_entries(); + valid_entry->set_type(0x96); + valid_entry->set_value("moredata"); + auto invalid_entry = custom_tlv_metadata.add_entries(); + invalid_entry->set_type(0x97); + invalid_entry->set_value(""); ProtobufWkt::Any typed_metadata; typed_metadata.PackFrom(custom_tlv_metadata); @@ -928,7 +934,7 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } -TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadata) { +TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); auto dst_addr = Network::Address::InstanceConstSharedPtr( @@ -992,6 +998,123 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadata) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } +TEST_F(ProxyProtocolTest, V2CustomTLVInvalidTypedMetadata) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + + // bogus typed metadata in the well-known host metadata field. + envoy::config::core::v3::Address addr_proto; + addr_proto.mutable_socket_address()->set_address("0.0.0.0"); + addr_proto.mutable_socket_address()->set_port_value(1234); + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(addr_proto); + metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); + EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{0x5}; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, + pass_through_tlvs, {})); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + +TEST_F(ProxyProtocolTest, V2CustomTLVHostMetadataMissing) { + // Setup source and destination addresses + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + + // Setup Proxy Protocol TLVs + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + + // Setup Transport Socket Options with Proxy Protocol Data + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + + // Configure connection addresses + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + // Intentionally add host metadata with a different key, and not the expected key. + envoy::config::core::v3::Metadata socket_match_metadata; + TestUtility::loadFromYaml(R"EOF( +filter_metadata: + envoy.transport_socket_match: + outbound-proxy-protocol: true +)EOF", socket_match_metadata); + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(socket_match_metadata); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + metadata->mutable_typed_filter_metadata()->emplace( + "envoy.transport_socket_match", typed_metadata); + EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection().streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{0x5}; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, + pass_through_tlvs, {})); + + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + class ProxyProtocolSocketFactoryTest : public testing::Test { public: void initialize() { From 2406b71de566d9f8b0fa4ebf6451bcf6c19fa74b Mon Sep 17 00:00:00 2001 From: timflannagan Date: Fri, 13 Dec 2024 02:52:11 +0000 Subject: [PATCH 05/22] test: Fix linting violations Signed-off-by: timflannagan --- .../proxy_protocol/proxy_protocol_test.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 91fd3a34bed7a..f35cb4325180d 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -1081,14 +1081,15 @@ TEST_F(ProxyProtocolTest, V2CustomTLVHostMetadataMissing) { filter_metadata: envoy.transport_socket_match: outbound-proxy-protocol: true -)EOF", socket_match_metadata); +)EOF", + socket_match_metadata); ProtobufWkt::Any typed_metadata; typed_metadata.PackFrom(socket_match_metadata); auto host = std::make_shared>(); auto metadata = std::make_shared(); - metadata->mutable_typed_filter_metadata()->emplace( - "envoy.transport_socket_match", typed_metadata); + metadata->mutable_typed_filter_metadata()->emplace("envoy.transport_socket_match", + typed_metadata); EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); transport_callbacks_.connection().streamInfo().upstreamInfo()->setUpstreamHost(host); From 9dbb9ff1c8bddc7578288ceeb5d7c8dfbc6cba8f Mon Sep 17 00:00:00 2001 From: timflannagan Date: Mon, 16 Dec 2024 17:19:11 +0000 Subject: [PATCH 06/22] Update changelog entry Signed-off-by: timflannagan --- changelogs/current.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 1caecc597d773..8c95b3410870e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -337,9 +337,11 @@ new_features: Added a new http filter for :ref:`gRPC to JSON transcoding `. - area: proxy_protocol change: | - Added support for injecting custom TLVs into the Proxy Protocol v2 (PP2) header for upstream transport sockets. - This feature allows dynamic customization of PP2 headersby defining TLV key-value pairs in an endpoint host's - typed metadata under the ``envoy.transport_sockets.proxy_protocol`` namespace. + 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 + `entries` 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. deprecated: - area: rbac From b656b365f64ba0babf61d29a0a7ce762ba1400e7 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Fri, 3 Jan 2025 19:05:20 +0000 Subject: [PATCH 07/22] *: Rename entries -> custom_tlvs in proxy_protocol.proto Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 2 +- changelogs/current.yaml | 2 +- .../proxy_protocol/proxy_protocol.cc | 4 ++-- .../proxy_protocol_integration_test.cc | 4 ++-- .../proxy_protocol/proxy_protocol_test.cc | 22 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index 0a7415684ac79..b19a8851b071e 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -59,5 +59,5 @@ message ProxyProtocolConfig { // Custom TLV metadata to be sent in the upstream PROXY protocol header. This field // is only valid when the version is V2. - repeated TlvEntry entries = 3; + repeated TlvEntry custom_tlvs = 3; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f1c8f59a2c329..2c048f551c304 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -393,7 +393,7 @@ new_features: 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 - ``entries`` field. Host-level TLV definitions override config-level entries when the same type is specified, allowing + ``custom_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: attributes change: | diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index 95a4470301d94..5707281317fe1 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -40,7 +40,7 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( pass_through_tlvs_.insert(0xFF & tlv_type); } } - for (const auto& entry : config.entries()) { + for (const auto& entry : config.custom_tlvs()) { if (entry.value().empty()) { continue; } @@ -215,7 +215,7 @@ void UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { } // process the custom TLVs from the host metadata first. - for (const auto& entry : tlvs_metadata.entries()) { + for (const auto& entry : tlvs_metadata.custom_tlvs()) { // prevent empty values from being added to custom TLVs. if (entry.value().empty()) { continue; diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index 6cf7765e839a4..ad56414b526c9 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -480,7 +480,7 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParamset_type(tlv.first); entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); } @@ -494,7 +494,7 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParamset_type(tlv.first); entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); } diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index f35cb4325180d..a709af2cce770 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -727,10 +727,10 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto valid_tlv_entry = config.add_entries(); + auto valid_tlv_entry = config.add_custom_tlvs(); valid_tlv_entry->set_type(0x96); valid_tlv_entry->set_value("moredata"); - auto invalid_tlv_entry = config.add_entries(); + auto invalid_tlv_entry = config.add_custom_tlvs(); invalid_tlv_entry->set_type(0x97); invalid_tlv_entry->set_value(""); initialize(config, socket_options); @@ -771,10 +771,10 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto valid_entry = custom_tlv_metadata.add_entries(); + auto valid_entry = custom_tlv_metadata.add_custom_tlvs(); valid_entry->set_type(0x96); valid_entry->set_value("moredata"); - auto invalid_entry = custom_tlv_metadata.add_entries(); + auto invalid_entry = custom_tlv_metadata.add_custom_tlvs(); invalid_entry->set_type(0x97); invalid_entry->set_value(""); @@ -833,7 +833,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_entries(); + auto entry = custom_tlv_metadata.add_custom_tlvs(); entry->set_type(0x96); entry->set_value("foo"); @@ -854,10 +854,10 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto tlv_entry = config.add_entries(); + auto tlv_entry = config.add_custom_tlvs(); tlv_entry->set_type(0x96); tlv_entry->set_value("bar"); - tlv_entry = config.add_entries(); + tlv_entry = config.add_custom_tlvs(); tlv_entry->set_type(0x97); tlv_entry->set_value("baz"); initialize(config, socket_options); @@ -898,7 +898,7 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_entries(); + auto entry = custom_tlv_metadata.add_custom_tlvs(); entry->set_type(0x96); entry->set_value("foo"); @@ -956,13 +956,13 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto valid_entry = custom_tlv_metadata.add_entries(); + auto valid_entry = custom_tlv_metadata.add_custom_tlvs(); valid_entry->set_type(0x96); valid_entry->set_value("cluster_0"); - auto invalid_entry_empty_val = custom_tlv_metadata.add_entries(); + auto invalid_entry_empty_val = custom_tlv_metadata.add_custom_tlvs(); invalid_entry_empty_val->set_type(0x97); invalid_entry_empty_val->set_value(""); - auto invalid_entry_duplicate = custom_tlv_metadata.add_entries(); + auto invalid_entry_duplicate = custom_tlv_metadata.add_custom_tlvs(); invalid_entry_duplicate->set_type(0x96); invalid_entry_duplicate->set_value("cluster_1"); From d23df4f9ea7d7d9b566853a51dfbef916cbd01f9 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Tue, 7 Jan 2025 20:30:31 +0000 Subject: [PATCH 08/22] Fix changelog format errors and expand on precendence behavior in proto definition Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 4 +++- changelogs/current.yaml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index b19a8851b071e..ad7a0b73a4c34 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -58,6 +58,8 @@ message ProxyProtocolConfig { ProxyProtocolPassThroughTLVs pass_through_tlvs = 2; // Custom TLV metadata to be sent in the upstream PROXY protocol header. This field - // is only valid when the version is V2. + // is only valid when the version is V2. These TLVs can be defined both in the + // endpoint host's typed metadata and at the config-level. In the case that the same + // TLV is defined in both places, the endpoint host's typed metadata will take precedence. repeated TlvEntry custom_tlvs = 3; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2c048f551c304..0533baf41b43b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -392,7 +392,7 @@ new_features: 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 + ``envoy.transport_sockets.proxy_protocol`` namespace and at the configuration level via the ``ProxyProtocolConfig``'s ``custom_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: attributes From 1c02dc137b56a1131cd815accdb62cec2a4e55c7 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Tue, 7 Jan 2025 22:02:18 +0000 Subject: [PATCH 09/22] *: Rename custom_tlvs -> added_tlvs in proxy_protocol.proto Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 2 +- changelogs/current.yaml | 2 +- .../proxy_protocol/proxy_protocol.cc | 4 ++-- .../proxy_protocol_integration_test.cc | 4 ++-- .../proxy_protocol/proxy_protocol_test.cc | 22 +++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index ad7a0b73a4c34..496dd9fa41454 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -61,5 +61,5 @@ message ProxyProtocolConfig { // is only valid when the version is V2. These TLVs can be defined both in the // endpoint host's typed metadata and at the config-level. In the case that the same // TLV is defined in both places, the endpoint host's typed metadata will take precedence. - repeated TlvEntry custom_tlvs = 3; + repeated TlvEntry added_tlvs = 3; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0533baf41b43b..8f10e4a5de864 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -393,7 +393,7 @@ new_features: 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 - ``custom_tlvs`` field. Host-level TLV definitions override config-level entries when the same type is specified, allowing + ``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: attributes change: | diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index 5707281317fe1..020ca87648b39 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -40,7 +40,7 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( pass_through_tlvs_.insert(0xFF & tlv_type); } } - for (const auto& entry : config.custom_tlvs()) { + for (const auto& entry : config.added_tlvs()) { if (entry.value().empty()) { continue; } @@ -215,7 +215,7 @@ void UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { } // process the custom TLVs from the host metadata first. - for (const auto& entry : tlvs_metadata.custom_tlvs()) { + for (const auto& entry : tlvs_metadata.added_tlvs()) { // prevent empty values from being added to custom TLVs. if (entry.value().empty()) { continue; diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index ad56414b526c9..1e63bb78edf50 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -480,7 +480,7 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParamset_type(tlv.first); entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); } @@ -494,7 +494,7 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParamset_type(tlv.first); entry->set_value(std::string(tlv.second.begin(), tlv.second.end())); } diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index a709af2cce770..2612b79caa3d3 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -727,10 +727,10 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto valid_tlv_entry = config.add_custom_tlvs(); + auto valid_tlv_entry = config.add_added_tlvs(); valid_tlv_entry->set_type(0x96); valid_tlv_entry->set_value("moredata"); - auto invalid_tlv_entry = config.add_custom_tlvs(); + auto invalid_tlv_entry = config.add_added_tlvs(); invalid_tlv_entry->set_type(0x97); invalid_tlv_entry->set_value(""); initialize(config, socket_options); @@ -771,10 +771,10 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto valid_entry = custom_tlv_metadata.add_custom_tlvs(); + auto valid_entry = custom_tlv_metadata.add_added_tlvs(); valid_entry->set_type(0x96); valid_entry->set_value("moredata"); - auto invalid_entry = custom_tlv_metadata.add_custom_tlvs(); + auto invalid_entry = custom_tlv_metadata.add_added_tlvs(); invalid_entry->set_type(0x97); invalid_entry->set_value(""); @@ -833,7 +833,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_custom_tlvs(); + auto entry = custom_tlv_metadata.add_added_tlvs(); entry->set_type(0x96); entry->set_value("foo"); @@ -854,10 +854,10 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto tlv_entry = config.add_custom_tlvs(); + auto tlv_entry = config.add_added_tlvs(); tlv_entry->set_type(0x96); tlv_entry->set_value("bar"); - tlv_entry = config.add_custom_tlvs(); + tlv_entry = config.add_added_tlvs(); tlv_entry->set_type(0x97); tlv_entry->set_value("baz"); initialize(config, socket_options); @@ -898,7 +898,7 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_custom_tlvs(); + auto entry = custom_tlv_metadata.add_added_tlvs(); entry->set_type(0x96); entry->set_value("foo"); @@ -956,13 +956,13 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; ProxyProtocolConfig custom_tlv_metadata; - auto valid_entry = custom_tlv_metadata.add_custom_tlvs(); + auto valid_entry = custom_tlv_metadata.add_added_tlvs(); valid_entry->set_type(0x96); valid_entry->set_value("cluster_0"); - auto invalid_entry_empty_val = custom_tlv_metadata.add_custom_tlvs(); + auto invalid_entry_empty_val = custom_tlv_metadata.add_added_tlvs(); invalid_entry_empty_val->set_type(0x97); invalid_entry_empty_val->set_value(""); - auto invalid_entry_duplicate = custom_tlv_metadata.add_custom_tlvs(); + auto invalid_entry_duplicate = custom_tlv_metadata.add_added_tlvs(); invalid_entry_duplicate->set_type(0x96); invalid_entry_duplicate->set_value("cluster_1"); From e50c959cf46fc35e54faaff24963f931944ddfb4 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Tue, 7 Jan 2025 22:25:29 +0000 Subject: [PATCH 10/22] api: Revisit added_tlvs proto field documentation to clarify behavior Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index 496dd9fa41454..3bd4b17e32405 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -57,9 +57,39 @@ message ProxyProtocolConfig { // V2 header. If there is no setting for this field, no TLVs will be passed through. ProxyProtocolPassThroughTLVs pass_through_tlvs = 2; - // Custom TLV metadata to be sent in the upstream PROXY protocol header. This field - // is only valid when the version is V2. These TLVs can be defined both in the - // endpoint host's typed metadata and at the config-level. In the case that the same - // TLV is defined in both places, the endpoint host's typed metadata will take precedence. + // 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. See the following example for how to + // define TLVs at the host level: + // + // ```yaml + // clusters: + // - name: cluster_0 + // load_assignment: + // endpoints: + // - lbEndpoints: + // - metadata: + // typed_filter_metadata: + // envoy.transport_sockets.proxy_protocol: + // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig + // custom_tlvs: + // - type: 0xD7 + // value: b3Zy + // - type: 0xD8 + // value: bmV3 + // ``` + // + // Precedence behavior: + // - 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; } From f1ed670553ee6dfd6750d6ff051d8432d893bca5 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 8 Jan 2025 17:11:25 +0000 Subject: [PATCH 11/22] source,test: Refactor added_tlvs code Signed-off-by: timflannagan --- .../proxy_protocol/proxy_protocol_header.cc | 10 ++- .../proxy_protocol/proxy_protocol.cc | 69 +++++++++++-------- .../proxy_protocol/proxy_protocol.h | 10 +-- .../proxy_protocol_header_test.cc | 50 +++++++------- .../proxy_protocol/proxy_protocol_test.cc | 12 +--- 5 files changed, 79 insertions(+), 72 deletions(-) diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc index 938af9964ae92..77d9e8cebc086 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc @@ -118,13 +118,11 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer absl::flat_hash_set seen_types; for (const auto& tlv : custom_tlvs) { - if (seen_types.contains(tlv.type)) { - ENVOY_LOG_MISC(warn, "Ignoring duplicate custom TLV type {}", tlv.type); - continue; - } - seen_types.insert(tlv.type); + // TODO(tim): ASSERT(!seen_types.contains(tlv.type)); combined_tlv_vector.emplace_back(tlv); + seen_types.insert(tlv.type); } + 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. @@ -132,7 +130,7 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer } if (seen_types.contains(tlv.type)) { // Skip any duplicate TLVs from being added to the combined TLV vector. - ENVOY_LOG_MISC(warn, "Ignoring duplicate custom TLV type {}", tlv.type); + ENVOY_LOG_EVERY_POW_2_MISC(info, "Skipping duplicate TLV type {}", tlv.type); continue; } seen_types.insert(tlv.type); diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index 020ca87648b39..d6024a3fa1c46 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -41,10 +41,8 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( } } for (const auto& entry : config.added_tlvs()) { - if (entry.value().empty()) { - continue; - } - config_tlvs_.emplace_back(Network::ProxyProtocolTLV{ + ENVOY_LOG(trace, "adding config-level TLV"); + added_tlvs_.emplace_back(Network::ProxyProtocolTLV{ static_cast(entry.type()), std::vector(entry.value().begin(), entry.value().end())}); } @@ -104,20 +102,11 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { Common::ProxyProtocol::generateV2LocalHeader(header_buffer_); } else { // Process any custom TLVs from the host metadata. - processCustomTLVsFromHost(); - // Populate any custom TLVs defined the config that are not in the host metadata. - for (const auto& tlv : config_tlvs_) { - // Skip any TLV that is already in the custom TLVs. We want the host - // metadata value to take precedence when there is a conflict. - if (host_metadata_tlv_types_.contains(tlv.type)) { - continue; - } - custom_tlvs_.push_back(tlv); - } + std::vector custom_tlvs = processCustomTLVs(); const auto options = options_->proxyProtocolOptions().value(); if (!Common::ProxyProtocol::generateV2Header(options, header_buffer_, pass_all_tlvs_, - pass_through_tlvs_, custom_tlvs_)) { + pass_through_tlvs_, custom_tlvs)) { // There is a warn log in generateV2Header method. stats_.v2_tlvs_exceed_max_length_.inc(); } @@ -189,7 +178,19 @@ void UpstreamProxyProtocolSocketFactory::hashKey( } } -void UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { +std::vector UpstreamProxyProtocolSocket::processCustomTLVs() { + std::vector custom_tlvs; + absl::flat_hash_set processed_tlv_types; + + processHostLevelTLVs(custom_tlvs, processed_tlv_types); + processConfigLevelTLVs(custom_tlvs, processed_tlv_types); + + return custom_tlvs; +} + +void UpstreamProxyProtocolSocket::processHostLevelTLVs( + std::vector& custom_tlvs, + absl::flat_hash_set& processed_tlv_types) const { const auto& upstream_info = callbacks_->connection().streamInfo().upstreamInfo(); if (upstream_info == nullptr || upstream_info->upstreamHost() == nullptr) { return; @@ -202,31 +203,43 @@ void UpstreamProxyProtocolSocket::processCustomTLVsFromHost() { 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()) { - ENVOY_LOG(trace, "No custom TLVs found in upstream host metadata"); return; } - ProxyProtocolConfig tlvs_metadata; - auto status = absl::OkStatus(); - status = MessageUtil::unpackTo(filter_it->second, tlvs_metadata); + // TODO(tim): unpack only the metadata name we need. I'm not sure how to do that yet. + 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"); + ENVOY_LOG(warn, + "Failed to unpack custom TLVs from upstream host metadata for host {}. Error: {}", + upstream_info->upstreamHost()->address()->asString(), status.message()); return; } - // process the custom TLVs from the host metadata first. - for (const auto& entry : tlvs_metadata.added_tlvs()) { - // prevent empty values from being added to custom TLVs. - if (entry.value().empty()) { + 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_.emplace_back(Network::ProxyProtocolTLV{ + custom_tlvs.emplace_back(Network::ProxyProtocolTLV{ static_cast(entry.type()), std::vector(entry.value().begin(), entry.value().end())}); - host_metadata_tlv_types_.insert(entry.type()); + processed_tlv_types.insert(entry.type()); } +} - return; +void UpstreamProxyProtocolSocket::processConfigLevelTLVs( + std::vector& custom_tlvs, + absl::flat_hash_set& processed_tlv_types) const { + 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); + } } } // namespace ProxyProtocol diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index 81a4bf6e5ce07..2f4b8fc73cf49 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -43,7 +43,11 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, void generateHeader(); void generateHeaderV1(); void generateHeaderV2(); - void processCustomTLVsFromHost(); + void processHostLevelTLVs(std::vector& custom_tlvs, + absl::flat_hash_set& processed_tlv_types) const; + void processConfigLevelTLVs(std::vector& custom_tlvs, + absl::flat_hash_set& processed_tlv_types) const; + std::vector processCustomTLVs(); Network::IoResult writeHeader(); Network::TransportSocketOptionsConstSharedPtr options_; @@ -53,9 +57,7 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, const UpstreamProxyProtocolStats& stats_; const bool pass_all_tlvs_; absl::flat_hash_set pass_through_tlvs_{}; - absl::flat_hash_set host_metadata_tlv_types_{}; - std::vector custom_tlvs_{}; - std::vector config_tlvs_{}; + std::vector added_tlvs_{}; }; class UpstreamProxyProtocolSocketFactory : public PassthroughFactory { diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc index 7298a822a29e2..b798896a4a300 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc @@ -233,30 +233,32 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVs) { EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } -// Verify duplicate custom TLV keys are properly handled. -TEST(ProxyProtocolHeaderTest, GeneratesV2WithDuplicateCustomTLVKeys) { - const uint8_t v2_protocol[] = { - 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, - 0x11, 0x00, 0x15, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, - 0x02, 0x01, 0x08, 0x00, 0x01, 0x09, 0xD3, 0x00, 0x02, 0x06, 0x07, - }; - - const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); - auto src_addr = - Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); - auto dst_addr = - Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); - Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; - Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; - std::vector custom_tlvs = { - {0x8, {0x09}}, - {0x8, {0x08}}, - }; - Buffer::OwnedImpl buff{}; - - ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); - EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); -} +// // Verify duplicate custom TLV keys are properly handled. +// TEST(ProxyProtocolHeaderTest, GeneratesV2WithDuplicateCustomTLVKeys) { +// const uint8_t v2_protocol[] = { +// 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, +// 0x11, 0x00, 0x15, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, +// 0x02, 0x01, 0x08, 0x00, 0x01, 0x09, 0xD3, 0x00, 0x02, 0x06, 0x07, +// }; + +// const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); +// auto src_addr = +// Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", +// 773)); +// auto dst_addr = +// Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", +// 513)); +// Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; +// Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; +// std::vector custom_tlvs = { +// {0x8, {0x09}}, +// {0x8, {0x08}}, +// }; +// Buffer::OwnedImpl buff{}; + +// ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); +// EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); +// } TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVsPrecendence) { const uint8_t v2_protocol[] = { diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 2612b79caa3d3..b01a966bdd56c 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -730,9 +730,6 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { auto valid_tlv_entry = config.add_added_tlvs(); valid_tlv_entry->set_type(0x96); valid_tlv_entry->set_value("moredata"); - auto invalid_tlv_entry = config.add_added_tlvs(); - invalid_tlv_entry->set_type(0x97); - invalid_tlv_entry->set_value(""); initialize(config, socket_options); EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) @@ -774,9 +771,6 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { auto valid_entry = custom_tlv_metadata.add_added_tlvs(); valid_entry->set_type(0x96); valid_entry->set_value("moredata"); - auto invalid_entry = custom_tlv_metadata.add_added_tlvs(); - invalid_entry->set_type(0x97); - invalid_entry->set_value(""); ProtobufWkt::Any typed_metadata; typed_metadata.PackFrom(custom_tlv_metadata); @@ -934,6 +928,7 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } +// TODO(tim): Extend this test to verify invalid host metadata doesn't affect added_tlvs. TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); @@ -959,9 +954,6 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { auto valid_entry = custom_tlv_metadata.add_added_tlvs(); valid_entry->set_type(0x96); valid_entry->set_value("cluster_0"); - auto invalid_entry_empty_val = custom_tlv_metadata.add_added_tlvs(); - invalid_entry_empty_val->set_type(0x97); - invalid_entry_empty_val->set_value(""); auto invalid_entry_duplicate = custom_tlv_metadata.add_added_tlvs(); invalid_entry_duplicate->set_type(0x96); invalid_entry_duplicate->set_value("cluster_1"); @@ -998,7 +990,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } -TEST_F(ProxyProtocolTest, V2CustomTLVInvalidTypedMetadata) { +TEST_F(ProxyProtocolTest, V2CustomTLVIgnoredMetadataValues) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); auto dst_addr = Network::Address::InstanceConstSharedPtr( From 5ec9dd88f2a5cd9fbebe174a70813db5dcfd2c00 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 8 Jan 2025 19:49:31 +0000 Subject: [PATCH 12/22] api: Attempt to fix docs sanity check Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index 3bd4b17e32405..d23b0fde04cf5 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -58,38 +58,38 @@ message ProxyProtocolConfig { 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 + // 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. See the following example for how to - // define TLVs at the host level: + // Host-level TLVs are specified in the ``metadata.typed_filter_metadata`` field under the + // ``envoy.transport_sockets.proxy_protocol`` namespace. See the following example for how to + // define TLVs at the host level. // - // ```yaml - // clusters: - // - name: cluster_0 - // load_assignment: - // endpoints: - // - lbEndpoints: - // - metadata: - // typed_filter_metadata: - // envoy.transport_sockets.proxy_protocol: - // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig - // custom_tlvs: - // - type: 0xD7 - // value: b3Zy - // - type: 0xD8 - // value: bmV3 - // ``` + // .. code-block:: yaml // - // Precedence behavior: + // clusters: + // - name: cluster_0 + // load_assignment: + // endpoints: + // - lbEndpoints: + // - metadata: + // typed_filter_metadata: + // envoy.transport_sockets.proxy_protocol: + // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig + // added_tlvs: + // - type: 0xD7 + // value: b3Zy + // - type: 0xD8 + // value: bmV3 + // + // **Precedence behavior**: // - 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. + // - 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; } From 118792b314fa832124794695848b1a2f6ebf4bca Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 8 Jan 2025 22:02:20 +0000 Subject: [PATCH 13/22] api: Fix docs precheck Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index d23b0fde04cf5..bb33316d2985c 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -65,27 +65,27 @@ message ProxyProtocolConfig { // 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. See the following example for how to - // define TLVs at the host level. + // ``envoy.transport_sockets.proxy_protocol`` namespace. // // .. code-block:: yaml // - // clusters: - // - name: cluster_0 - // load_assignment: - // endpoints: - // - lbEndpoints: - // - metadata: - // typed_filter_metadata: - // envoy.transport_sockets.proxy_protocol: - // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig - // added_tlvs: - // - type: 0xD7 - // value: b3Zy - // - type: 0xD8 - // value: bmV3 + // clusters: + // - name: cluster_0 + // load_assignment: + // endpoints: + // - lbEndpoints: + // - metadata: + // typed_filter_metadata: + // envoy.transport_sockets.proxy_protocol: + // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig + // added_tlvs: + // - type: 0xD7 + // value: b3Zy + // - type: 0xD8 + // value: bmV3 // // **Precedence behavior**: + // // - 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. From c5a5619c693e09c52760d7695f907b00ea109ecb Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 8 Jan 2025 22:49:45 +0000 Subject: [PATCH 14/22] pp2: Add ASSERT, move duplicate test case, remove TODOs Signed-off-by: timflannagan --- .../proxy_protocol/proxy_protocol_header.cc | 2 +- .../proxy_protocol/proxy_protocol.cc | 3 - .../proxy_protocol_header_test.cc | 27 ------- .../proxy_protocol/proxy_protocol_test.cc | 74 ++++++++++++++++++- 4 files changed, 74 insertions(+), 32 deletions(-) diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc index 77d9e8cebc086..2302f2290fd7c 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc @@ -118,7 +118,7 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer absl::flat_hash_set seen_types; for (const auto& tlv : custom_tlvs) { - // TODO(tim): ASSERT(!seen_types.contains(tlv.type)); + ASSERT(!seen_types.contains(tlv.type)); combined_tlv_vector.emplace_back(tlv); seen_types.insert(tlv.type); } diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index d6024a3fa1c46..457da37d215f6 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -41,7 +41,6 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( } } for (const auto& entry : config.added_tlvs()) { - ENVOY_LOG(trace, "adding config-level TLV"); added_tlvs_.emplace_back(Network::ProxyProtocolTLV{ static_cast(entry.type()), std::vector(entry.value().begin(), entry.value().end())}); @@ -101,7 +100,6 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { if (!options_ || !options_->proxyProtocolOptions().has_value()) { Common::ProxyProtocol::generateV2LocalHeader(header_buffer_); } else { - // Process any custom TLVs from the host metadata. std::vector custom_tlvs = processCustomTLVs(); const auto options = options_->proxyProtocolOptions().value(); @@ -206,7 +204,6 @@ void UpstreamProxyProtocolSocket::processHostLevelTLVs( return; } - // TODO(tim): unpack only the metadata name we need. I'm not sure how to do that yet. ProxyProtocolConfig host_tlv_metadata; auto status = MessageUtil::unpackTo(filter_it->second, host_tlv_metadata); if (!status.ok()) { diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc index b798896a4a300..5c97e1678cae1 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc @@ -233,33 +233,6 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVs) { EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } -// // Verify duplicate custom TLV keys are properly handled. -// TEST(ProxyProtocolHeaderTest, GeneratesV2WithDuplicateCustomTLVKeys) { -// const uint8_t v2_protocol[] = { -// 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, -// 0x11, 0x00, 0x15, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, -// 0x02, 0x01, 0x08, 0x00, 0x01, 0x09, 0xD3, 0x00, 0x02, 0x06, 0x07, -// }; - -// const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); -// auto src_addr = -// Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", -// 773)); -// auto dst_addr = -// Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", -// 513)); -// Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; -// Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; -// std::vector custom_tlvs = { -// {0x8, {0x09}}, -// {0x8, {0x08}}, -// }; -// Buffer::OwnedImpl buff{}; - -// ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); -// EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); -// } - TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVsPrecendence) { const uint8_t v2_protocol[] = { 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index b01a966bdd56c..28e6bf10caa90 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -869,6 +869,79 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } +// Test verifies that duplicate TLVs within the config and host metadata are properly handled. +TEST_F(ProxyProtocolTest, V2DuplicateTLVsInConfigAndMetadataHandledProperly) { + auto src_addr = + Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); + auto dst_addr = Network::Address::InstanceConstSharedPtr( + new Network::Address::Ipv6Instance("1:100:200:3::", 2)); + Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; + Network::TransportSocketOptionsConstSharedPtr socket_options = + std::make_shared( + "", std::vector{}, std::vector{}, std::vector{}, + absl::optional(proxy_proto_data)); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); + transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ + ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); + + auto host = std::make_shared>(); + auto metadata = std::make_shared(); + const std::string metadata_key = + Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; + + ProxyProtocolConfig host_metadata_config; + auto host_entry = host_metadata_config.add_added_tlvs(); + host_entry->set_type(0x98); + host_entry->set_value("d1"); + auto duplicate_host_entry = host_metadata_config.add_added_tlvs(); + duplicate_host_entry->set_type(0x98); + duplicate_host_entry->set_value("d2"); // Last duplicate value + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(host_metadata_config); + metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); + EXPECT_CALL(*host, metadata()).WillRepeatedly(Return(metadata)); + transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); + + absl::flat_hash_set pass_through_tlvs{}; + // The output buffer will include the host TLVs before the config TLVs. + std::vector custom_tlvs = { + {0x98, {'d', '1'}}, + {0x96, {'b', 'a', 'r'}}, + {0x97, {'b', 'a', 'z'}}, + }; + Buffer::OwnedImpl expected_buff{}; + EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, + pass_through_tlvs, custom_tlvs)); + + // Configure duplicate TLVs in the configuration. + ProxyProtocolConfig config; + config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + auto tlv_entry = config.add_added_tlvs(); + tlv_entry->set_type(0x96); + tlv_entry->set_value("bar"); + auto duplicate_tlv_entry = config.add_added_tlvs(); + duplicate_tlv_entry->set_type(0x96); + duplicate_tlv_entry->set_value("baz"); // Last duplicate value for type 0x96 + auto unique_tlv_entry = config.add_added_tlvs(); + unique_tlv_entry->set_type(0x97); + unique_tlv_entry->set_value("baz"); + initialize(config, socket_options); + + EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); + EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); + + auto resp = proxy_protocol_socket_->doWrite(msg, false); + EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); +} + // Test injects V2 PROXY protocol with pass through & custom TLVs. TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { auto src_addr = @@ -928,7 +1001,6 @@ TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } -// TODO(tim): Extend this test to verify invalid host metadata doesn't affect added_tlvs. TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); From 7e5454a58a7bd5d6e69a5fad7dfd04d2ff70b88e Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 8 Jan 2025 23:17:39 +0000 Subject: [PATCH 15/22] pp2: Consolidate tests to add explicit precedence & override cases Signed-off-by: timflannagan --- .../proxy_protocol/proxy_protocol_test.cc | 176 +++--------------- 1 file changed, 25 insertions(+), 151 deletions(-) diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 28e6bf10caa90..45a4cdbc18928 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -700,7 +700,7 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddressesAndTLVsWithoutPassConfig) { proxy_protocol_socket_->doWrite(msg, false); } -// Test injects V2 PROXY protocol with custom TLVs from the config-level field. +// Test verifies the happy path for custom TLVs defined in the config. TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); @@ -745,7 +745,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } -// Test injects V2 PROXY protocol with custom TLVs from host metadata. +// Test verifies the happy path for TLVs added from host metadata. TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); @@ -803,14 +803,15 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } -// Test injects V2 PROXY protocol with custom TLVs and validates that host metadata will -// overwrite any TLVs set in the config. -TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { +// Test verifies combined precedence: host-level > config-level > passthrough-level TLVs when keys +// overlap. +TEST_F(ProxyProtocolTest, V2CombinedPrecedenceHostConfigPassthrough) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); auto dst_addr = Network::Address::InstanceConstSharedPtr( new Network::Address::Ipv6Instance("1:100:200:3::", 2)); - Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; + Network::ProxyProtocolTLVVector tlv_vector{ + Network::ProxyProtocolTLV{0x99, {'p', 'a', 's', 's', 'V', 'a', 'l', 'u', 'e'}}}; Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; Network::TransportSocketOptionsConstSharedPtr socket_options = std::make_shared( @@ -826,34 +827,31 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsOverwriteHostMetadata) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_added_tlvs(); - entry->set_type(0x96); - entry->set_value("foo"); + ProxyProtocolConfig host_metadata_config; + auto host_entry = host_metadata_config.add_added_tlvs(); + host_entry->set_type(0x99); + host_entry->set_value("hostValue"); ProtobufWkt::Any typed_metadata; - typed_metadata.PackFrom(custom_tlv_metadata); + typed_metadata.PackFrom(host_metadata_config); metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); - EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); + EXPECT_CALL(*host, metadata()).WillRepeatedly(Return(metadata)); transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); - absl::flat_hash_set pass_through_tlvs{}; + absl::flat_hash_set pass_through_tlvs{0x99}; std::vector custom_tlvs = { - {0x96, {'f', 'o', 'o'}}, - {0x97, {'b', 'a', 'z'}}, - }; + {0x99, {'p', 'a', 's', 's', 'V', 'a', 'l', 'u', 'e'}}}; + std::vector expected_custom_tlvs = { + {0x99, {'h', 'o', 's', 't', 'V', 'a', 'l', 'u', 'e'}}}; Buffer::OwnedImpl expected_buff{}; EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, false, - pass_through_tlvs, custom_tlvs)); + pass_through_tlvs, expected_custom_tlvs)); ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto tlv_entry = config.add_added_tlvs(); - tlv_entry->set_type(0x96); - tlv_entry->set_value("bar"); - tlv_entry = config.add_added_tlvs(); - tlv_entry->set_type(0x97); - tlv_entry->set_value("baz"); + auto config_entry = config.add_added_tlvs(); + config_entry->set_type(0x99); + config_entry->set_value("configValue"); initialize(config, socket_options); EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) @@ -942,127 +940,9 @@ TEST_F(ProxyProtocolTest, V2DuplicateTLVsInConfigAndMetadataHandledProperly) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } -// Test injects V2 PROXY protocol with pass through & custom TLVs. -TEST_F(ProxyProtocolTest, V2CustomAndPassthroughTLVs) { - auto src_addr = - Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); - auto dst_addr = Network::Address::InstanceConstSharedPtr( - new Network::Address::Ipv6Instance("1:100:200:3::", 2)); - Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; - Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; - Network::TransportSocketOptionsConstSharedPtr socket_options = - std::make_shared( - "", std::vector{}, std::vector{}, std::vector{}, - absl::optional(proxy_proto_data)); - transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ - ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); - transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ - ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); - - auto host = std::make_shared>(); - auto metadata = std::make_shared(); - const std::string metadata_key = - Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - - ProxyProtocolConfig custom_tlv_metadata; - auto entry = custom_tlv_metadata.add_added_tlvs(); - entry->set_type(0x96); - entry->set_value("foo"); - - ProtobufWkt::Any typed_metadata; - typed_metadata.PackFrom(custom_tlv_metadata); - metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); - EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); - transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); - - absl::flat_hash_set pass_through_tlvs{0x5}; - std::vector custom_tlvs = { - {0x96, {'f', 'o', 'o'}}, - }; - Buffer::OwnedImpl expected_buff{}; - EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, - pass_through_tlvs, custom_tlvs)); - - ProxyProtocolConfig config; - config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); - initialize(config, socket_options); - - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) - .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { - auto length = buffer.length(); - buffer.drain(length); - return {length, Api::IoError::none()}; - })); - auto msg = Buffer::OwnedImpl("some data"); - EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); - - auto resp = proxy_protocol_socket_->doWrite(msg, false); - EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); -} - -TEST_F(ProxyProtocolTest, V2CustomTLVInvalidMetadataValues) { - auto src_addr = - Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); - auto dst_addr = Network::Address::InstanceConstSharedPtr( - new Network::Address::Ipv6Instance("1:100:200:3::", 2)); - Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; - Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; - Network::TransportSocketOptionsConstSharedPtr socket_options = - std::make_shared( - "", std::vector{}, std::vector{}, std::vector{}, - absl::optional(proxy_proto_data)); - transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ - ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); - transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ - ->setRemoteAddress(*Network::Utility::resolveUrl("tcp://[e:b:c:f::]:8080")); - - auto host = std::make_shared>(); - auto metadata = std::make_shared(); - const std::string metadata_key = - Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - - ProxyProtocolConfig custom_tlv_metadata; - auto valid_entry = custom_tlv_metadata.add_added_tlvs(); - valid_entry->set_type(0x96); - valid_entry->set_value("cluster_0"); - auto invalid_entry_duplicate = custom_tlv_metadata.add_added_tlvs(); - invalid_entry_duplicate->set_type(0x96); - invalid_entry_duplicate->set_value("cluster_1"); - - ProtobufWkt::Any typed_metadata; - typed_metadata.PackFrom(custom_tlv_metadata); - metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); - EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); - transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); - - absl::flat_hash_set pass_through_tlvs{0x5}; - std::vector custom_tlvs = { - {0x96, {'c', 'l', 'u', 's', 't', 'e', 'r', '_', '0'}}, - }; - Buffer::OwnedImpl expected_buff{}; - EXPECT_TRUE(Common::ProxyProtocol::generateV2Header(proxy_proto_data, expected_buff, true, - pass_through_tlvs, custom_tlvs)); - - ProxyProtocolConfig config; - config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); - initialize(config, socket_options); - - EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) - .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { - auto length = buffer.length(); - buffer.drain(length); - return {length, Api::IoError::none()}; - })); - auto msg = Buffer::OwnedImpl("some data"); - EXPECT_CALL(*inner_socket_, doWrite(BufferEqual(&msg), false)); - - auto resp = proxy_protocol_socket_->doWrite(msg, false); - EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); -} - -TEST_F(ProxyProtocolTest, V2CustomTLVIgnoredMetadataValues) { +// Test handles edge case where the well-known host metadata namespace is present, but the +// TLVs are invalid and cannot be unpacked properly. +TEST_F(ProxyProtocolTest, V2CustomTLVMetadataInvalidFormat) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); auto dst_addr = Network::Address::InstanceConstSharedPtr( @@ -1116,24 +996,18 @@ TEST_F(ProxyProtocolTest, V2CustomTLVIgnoredMetadataValues) { EXPECT_EQ(resp.bytes_processed_, expected_buff.length()); } +// Test verifies edge case where host has metadata available, but does not include the expected key. TEST_F(ProxyProtocolTest, V2CustomTLVHostMetadataMissing) { - // Setup source and destination addresses auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); auto dst_addr = Network::Address::InstanceConstSharedPtr( new Network::Address::Ipv6Instance("1:100:200:3::", 2)); - - // Setup Proxy Protocol TLVs Network::ProxyProtocolTLVVector tlv_vector{Network::ProxyProtocolTLV{0x5, {'a', 'b', 'c'}}}; Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, tlv_vector}; - - // Setup Transport Socket Options with Proxy Protocol Data Network::TransportSocketOptionsConstSharedPtr socket_options = std::make_shared( "", std::vector{}, std::vector{}, std::vector{}, absl::optional(proxy_proto_data)); - - // Configure connection addresses transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ ->setLocalAddress(*Network::Utility::resolveUrl("tcp://[1:100:200:3::]:50000")); transport_callbacks_.connection_.stream_info_.downstream_connection_info_provider_ From ad46d237feaee356618a904f327601d8f5efac1d Mon Sep 17 00:00:00 2001 From: timflannagan Date: Thu, 16 Jan 2025 20:29:33 +0000 Subject: [PATCH 16/22] pp2: Refactor code, address review comments, etc Signed-off-by: timflannagan --- .../proxy_protocol/proxy_protocol_header.cc | 22 +++-- .../proxy_protocol/proxy_protocol.cc | 83 ++++++++----------- .../proxy_protocol/proxy_protocol.h | 11 ++- .../proxy_protocol_header_test.cc | 27 +----- .../proxy_protocol/proxy_protocol_test.cc | 9 ++ 5 files changed, 66 insertions(+), 86 deletions(-) diff --git a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc index 2302f2290fd7c..a2f7b88d70fe5 100644 --- a/source/extensions/common/proxy_protocol/proxy_protocol_header.cc +++ b/source/extensions/common/proxy_protocol/proxy_protocol_header.cc @@ -114,6 +114,7 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer bool pass_all_tlvs, const absl::flat_hash_set& pass_through_tlvs, const std::vector& custom_tlvs) { std::vector combined_tlv_vector; + std::vector final_tlvs; combined_tlv_vector.reserve(custom_tlvs.size() + proxy_proto_data.tlv_vector_.size()); absl::flat_hash_set seen_types; @@ -123,6 +124,7 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer 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. @@ -137,15 +139,19 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer 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) { - extension_length += PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN + tlv.value.size(); - if (extension_length > std::numeric_limits::max()) { - ENVOY_LOG_MISC( - warn, "Generating Proxy Protocol V2 header: TLVs exceed length limit {}, already got {}", - std::numeric_limits::max(), extension_length); - return false; + uint64_t new_size = extension_length + PROXY_PROTO_V2_TLV_TYPE_LENGTH_LEN + tlv.value.size(); + if (new_size > std::numeric_limits::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); } ASSERT(extension_length <= std::numeric_limits::max()); @@ -170,7 +176,9 @@ bool generateV2Header(const Network::ProxyProtocolData& proxy_proto_data, Buffer 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, diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index 457da37d215f6..aae7f216717d3 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -41,7 +41,7 @@ UpstreamProxyProtocolSocket::UpstreamProxyProtocolSocket( } } for (const auto& entry : config.added_tlvs()) { - added_tlvs_.emplace_back(Network::ProxyProtocolTLV{ + added_tlvs_.push_back(Network::ProxyProtocolTLV{ static_cast(entry.type()), std::vector(entry.value().begin(), entry.value().end())}); } @@ -100,7 +100,7 @@ void UpstreamProxyProtocolSocket::generateHeaderV2() { if (!options_ || !options_->proxyProtocolOptions().has_value()) { Common::ProxyProtocol::generateV2LocalHeader(header_buffer_); } else { - std::vector custom_tlvs = processCustomTLVs(); + std::vector custom_tlvs = buildCustomTLVs(); const auto options = options_->proxyProtocolOptions().value(); if (!Common::ProxyProtocol::generateV2Header(options, header_buffer_, pass_all_tlvs_, @@ -176,59 +176,44 @@ void UpstreamProxyProtocolSocketFactory::hashKey( } } -std::vector UpstreamProxyProtocolSocket::processCustomTLVs() { +std::vector UpstreamProxyProtocolSocket::buildCustomTLVs() const { std::vector custom_tlvs; absl::flat_hash_set processed_tlv_types; - processHostLevelTLVs(custom_tlvs, processed_tlv_types); - processConfigLevelTLVs(custom_tlvs, processed_tlv_types); - - return custom_tlvs; -} - -void UpstreamProxyProtocolSocket::processHostLevelTLVs( - std::vector& custom_tlvs, - absl::flat_hash_set& processed_tlv_types) const { + // Attempt to parse host-level TLVs first. const auto& upstream_info = callbacks_->connection().streamInfo().upstreamInfo(); - if (upstream_info == nullptr || upstream_info->upstreamHost() == nullptr) { - return; - } - auto metadata = upstream_info->upstreamHost()->metadata(); - if (metadata == nullptr) { - return; - } - - 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()) { - return; - } - - 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: {}", - upstream_info->upstreamHost()->address()->asString(), status.message()); - return; - } - - 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; + 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(entry.type()), + std::vector(entry.value().begin(), entry.value().end())}); + processed_tlv_types.insert(entry.type()); + } + } + } } - custom_tlvs.emplace_back(Network::ProxyProtocolTLV{ - static_cast(entry.type()), - std::vector(entry.value().begin(), entry.value().end())}); - processed_tlv_types.insert(entry.type()); } -} -void UpstreamProxyProtocolSocket::processConfigLevelTLVs( - std::vector& custom_tlvs, - absl::flat_hash_set& processed_tlv_types) const { + // 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); @@ -237,6 +222,8 @@ void UpstreamProxyProtocolSocket::processConfigLevelTLVs( custom_tlvs.push_back(tlv); processed_tlv_types.insert(tlv.type); } + + return custom_tlvs; } } // namespace ProxyProtocol diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index 2f4b8fc73cf49..a5edfd62ae24e 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -43,11 +43,10 @@ class UpstreamProxyProtocolSocket : public TransportSockets::PassthroughSocket, void generateHeader(); void generateHeaderV1(); void generateHeaderV2(); - void processHostLevelTLVs(std::vector& custom_tlvs, - absl::flat_hash_set& processed_tlv_types) const; - void processConfigLevelTLVs(std::vector& custom_tlvs, - absl::flat_hash_set& processed_tlv_types) const; - std::vector processCustomTLVs(); + // 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 buildCustomTLVs() const; Network::IoResult writeHeader(); Network::TransportSocketOptionsConstSharedPtr options_; @@ -86,4 +85,4 @@ class UpstreamProxyProtocolSocketFactory : public PassthroughFactory { } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc index 5c97e1678cae1..3698f0d42c10b 100644 --- a/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc +++ b/test/extensions/common/proxy_protocol/proxy_protocol_header_test.cc @@ -206,7 +206,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithTLVExceedingLengthLimit) { Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; Buffer::OwnedImpl buff{}; - EXPECT_LOG_CONTAINS("warn", "Generating Proxy Protocol V2 header: TLVs exceed length limit 65535", + EXPECT_LOG_CONTAINS("warn", "Skipping TLV type 5 because adding it would exceed the 65535 limit", generateV2Header(proxy_proto_data, buff, true, {}, {})); } @@ -233,29 +233,6 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVs) { EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); } -TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVsPrecendence) { - const uint8_t v2_protocol[] = { - 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, - 0x0a, 0x21, 0x11, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, - 0x01, 0x02, 0x03, 0x05, 0x02, 0x01, 0xD3, 0x00, 0x01, 0x09, - }; - - const Buffer::OwnedImpl expectedBuff(v2_protocol, sizeof(v2_protocol)); - auto src_addr = - Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); - auto dst_addr = - Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); - Network::ProxyProtocolTLV tlv{0xD3, {0x06, 0x07}}; - Network::ProxyProtocolData proxy_proto_data{src_addr, dst_addr, {tlv}}; - std::vector custom_tlvs = { - {0xD3, {0x09}}, - }; - Buffer::OwnedImpl buff{}; - - ASSERT_TRUE(generateV2Header(proxy_proto_data, buff, false, {0xD3}, custom_tlvs)); - EXPECT_TRUE(TestUtility::buffersEqual(expectedBuff, buff)); -} - TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVExceedingLengthLimit) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("1.2.3.4", 773)); @@ -267,7 +244,7 @@ TEST(ProxyProtocolHeaderTest, GeneratesV2WithCustomTLVExceedingLengthLimit) { std::vector custom_tlvs = { {0x8, std::vector(65536, 'a')}, }; - EXPECT_LOG_CONTAINS("warn", "Generating Proxy Protocol V2 header: TLVs exceed length limit 65535", + EXPECT_LOG_CONTAINS("warn", "Skipping TLV type 8 because adding it would exceed the 65535 limit", generateV2Header(proxy_proto_data, buff, true, {}, custom_tlvs)); } diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 45a4cdbc18928..5ba466fd0e29e 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -618,6 +618,15 @@ TEST_F(ProxyProtocolTest, V2IPV4TLVsExceedLengthLimit) { config.mutable_pass_through_tlvs()->set_match_type(ProxyProtocolPassThroughTLVs::INCLUDE_ALL); initialize(config, socket_options); + // expect the counter to be incremented but the output header to be written + // without the large TLV. + EXPECT_CALL(io_handle_, write(_)) + .WillOnce(Invoke([&](Buffer::Instance& buffer) -> Api::IoCallUint64Result { + auto length = buffer.length(); + buffer.drain(length); + return {length, Api::IoError::none()}; + })); + auto msg = Buffer::OwnedImpl("some data"); proxy_protocol_socket_->doWrite(msg, false); EXPECT_EQ(stats_store_.counter("upstream.proxyprotocol.v2_tlvs_exceed_max_length").value(), 1); From a6de0351edd3cadec8c5f281dcffc251c227754b Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 22 Jan 2025 19:03:27 +0000 Subject: [PATCH 17/22] Fix format errors Signed-off-by: timflannagan --- .../transport_sockets/proxy_protocol/proxy_protocol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index a5edfd62ae24e..647ee8d63dc91 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -85,4 +85,4 @@ class UpstreamProxyProtocolSocketFactory : public PassthroughFactory { } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy \ No newline at end of file +} // namespace Envoy From b343e673b7fe376f3afd8da6affd998b4b5750c9 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 22 Jan 2025 20:26:48 +0000 Subject: [PATCH 18/22] api: Fix YAML identation in PP2 code block Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index bb33316d2985c..09735bf5954e0 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -70,19 +70,19 @@ message ProxyProtocolConfig { // .. code-block:: yaml // // clusters: - // - name: cluster_0 - // load_assignment: - // endpoints: - // - lbEndpoints: - // - metadata: - // typed_filter_metadata: - // envoy.transport_sockets.proxy_protocol: - // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig - // added_tlvs: - // - type: 0xD7 - // value: b3Zy - // - type: 0xD8 - // value: bmV3 + // - name: cluster_0 + // load_assignment: + // endpoints: + // - lbEndpoints: + // - metadata: + // typed_filter_metadata: + // envoy.transport_sockets.proxy_protocol: + // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig + // added_tlvs: + // - type: 0xD7 + // value: b3Zy + // - type: 0xD8 + // value: bmV3 // // **Precedence behavior**: // From 7c5d4edcf980db7f2d21f20880449bc32a2bd92c Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 22 Jan 2025 21:38:11 +0000 Subject: [PATCH 19/22] api: Use literalinclude for added_tlvs code example Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 22 ++---- configs/proxy_protocol.yaml | 70 +++++++++++++++++++ 2 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 configs/proxy_protocol.yaml diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index 09735bf5954e0..42762f1a46511 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -67,22 +67,12 @@ message ProxyProtocolConfig { // 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 - // load_assignment: - // endpoints: - // - lbEndpoints: - // - metadata: - // typed_filter_metadata: - // envoy.transport_sockets.proxy_protocol: - // "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig - // added_tlvs: - // - type: 0xD7 - // value: b3Zy - // - type: 0xD8 - // value: bmV3 + // .. literalinclude:: /_configs/repo/proxy_protocol.yaml + // :language: yaml + // :lines: 49-57 + // :linenos: + // :lineno-start: 49 + // :caption: :download:`proxy_protocol.yaml ` // // **Precedence behavior**: // diff --git a/configs/proxy_protocol.yaml b/configs/proxy_protocol.yaml new file mode 100644 index 0000000000000..0a4be35f66537 --- /dev/null +++ b/configs/proxy_protocol.yaml @@ -0,0 +1,70 @@ +admin: + address: + socket_address: + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" + stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.stdout + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog + route_config: + name: local_route + virtual_hosts: + - name: default + domains: + - "*" + routes: + - match: + prefix: "/" + direct_response: + status: 200 + body: + inline_string: "OK" + clusters: + - name: cluster_0 + type: STRICT_DNS + connect_timeout: 2s + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + metadata: + typed_filter_metadata: + envoy.transport_sockets.proxy_protocol: + "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig + added_tlvs: + - type: 0xEE + value: b3ZlcnJpZGU= + - type: 0xEF + value: bmV3dmFsdWU= + transport_socket: + name: envoy.transport_sockets.upstream_proxy_protocol + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.proxy_protocol.v3.ProxyProtocolUpstreamTransport + config: + version: V2 + added_tlvs: + - type: 0xEE + value: ZGVmYXVsdA== + transport_socket: + name: envoy.transport_sockets.raw_buffer + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer From 876af003bc8589a8214ebc0d747d8e89e8919bf9 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Wed, 22 Jan 2025 22:38:23 +0000 Subject: [PATCH 20/22] configs: Fix proxy_protocol.yaml format errors Signed-off-by: timflannagan --- configs/proxy_protocol.yaml | 43 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/configs/proxy_protocol.yaml b/configs/proxy_protocol.yaml index 0a4be35f66537..097ec3f5ae352 100644 --- a/configs/proxy_protocol.yaml +++ b/configs/proxy_protocol.yaml @@ -12,27 +12,28 @@ static_resources: address: 0.0.0.0 port_value: 10000 filter_chains: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" - stat_prefix: ingress_http - access_log: - - name: envoy.access_loggers.stdout - typed_config: - "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog - route_config: - name: local_route - virtual_hosts: - - name: default - domains: - - "*" - routes: - - match: - prefix: "/" - direct_response: - status: 200 - body: - inline_string: "OK" + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" + stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.stdout + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog + route_config: + name: local_route + virtual_hosts: + - name: default + domains: + - "*" + routes: + - match: + prefix: "/" + direct_response: + status: 200 + body: + inline_string: "OK" clusters: - name: cluster_0 type: STRICT_DNS From 5ebdb87db489532cb2f593c6231197cffef269e5 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Fri, 24 Jan 2025 22:23:20 +0000 Subject: [PATCH 21/22] Fix invalid proxy_protocol.yaml config Signed-off-by: timflannagan --- configs/proxy_protocol.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/proxy_protocol.yaml b/configs/proxy_protocol.yaml index 097ec3f5ae352..e05d48fef5b31 100644 --- a/configs/proxy_protocol.yaml +++ b/configs/proxy_protocol.yaml @@ -40,6 +40,7 @@ static_resources: connect_timeout: 2s lb_policy: ROUND_ROBIN load_assignment: + cluster_name: cluster_0 endpoints: - lb_endpoints: - endpoint: From 1677c7e9041c339752e6110511f6be8e89ec1a57 Mon Sep 17 00:00:00 2001 From: timflannagan Date: Mon, 27 Jan 2025 22:20:47 +0000 Subject: [PATCH 22/22] *: Add PerHostConfig message to proxy_protocol.proto Signed-off-by: timflannagan --- api/envoy/config/core/v3/proxy_protocol.proto | 5 ++ configs/proxy_protocol.yaml | 2 +- .../proxy_protocol/proxy_protocol.cc | 7 +-- .../proxy_protocol_integration_test.cc | 3 +- .../proxy_protocol/proxy_protocol_test.cc | 52 ++++++++++--------- 5 files changed, 40 insertions(+), 29 deletions(-) diff --git a/api/envoy/config/core/v3/proxy_protocol.proto b/api/envoy/config/core/v3/proxy_protocol.proto index 42762f1a46511..564e76cb1e569 100644 --- a/api/envoy/config/core/v3/proxy_protocol.proto +++ b/api/envoy/config/core/v3/proxy_protocol.proto @@ -83,3 +83,8 @@ message ProxyProtocolConfig { // or transport socket-level TLV. repeated TlvEntry added_tlvs = 3; } + +message PerHostConfig { + // Enables per-host configuration for Proxy Protocol. + repeated TlvEntry added_tlvs = 1; +} diff --git a/configs/proxy_protocol.yaml b/configs/proxy_protocol.yaml index e05d48fef5b31..ab2e10a796cb4 100644 --- a/configs/proxy_protocol.yaml +++ b/configs/proxy_protocol.yaml @@ -51,7 +51,7 @@ static_resources: metadata: typed_filter_metadata: envoy.transport_sockets.proxy_protocol: - "@type": type.googleapis.com/envoy.config.core.v3.ProxyProtocolConfig + "@type": type.googleapis.com/envoy.config.core.v3.PerHostConfig added_tlvs: - type: 0xEE value: b3ZlcnJpZGU= diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index aae7f216717d3..ba34b1a616647 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -16,6 +16,7 @@ #include "source/common/protobuf/utility.h" #include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" +using envoy::config::core::v3::PerHostConfig; using envoy::config::core::v3::ProxyProtocolConfig; using envoy::config::core::v3::ProxyProtocolConfig_Version; using envoy::config::core::v3::ProxyProtocolPassThroughTLVs; @@ -188,7 +189,7 @@ std::vector UpstreamProxyProtocolSocket::build 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; + PerHostConfig host_tlv_metadata; auto status = MessageUtil::unpackTo(filter_it->second, host_tlv_metadata); if (!status.ok()) { ENVOY_LOG(warn, @@ -196,7 +197,7 @@ std::vector UpstreamProxyProtocolSocket::build "Error: {}. Will still use config-level TLVs.", upstream_info->upstreamHost()->address()->asString(), status.message()); } else { - // Insert host-level TLVs + // 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 {}", @@ -213,7 +214,7 @@ std::vector UpstreamProxyProtocolSocket::build } } - // If host-level parse failed or was not present, we still read config-level TLVs + // 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); diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index 1e63bb78edf50..04a59d2490a00 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -9,6 +9,7 @@ #include "test/integration/http_integration.h" #include "test/integration/integration.h" +using envoy::config::core::v3::PerHostConfig; using envoy::config::core::v3::ProxyProtocolPassThroughTLVs; namespace Envoy { namespace { @@ -478,7 +479,7 @@ class ProxyProtocolTLVsIntegrationTest : public testing::TestWithParammutable_lb_endpoints(0) ->mutable_metadata(); - envoy::config::core::v3::ProxyProtocolConfig tlvs_metadata; + PerHostConfig tlvs_metadata; for (const auto& tlv : custom_tlvs_from_host_) { auto entry = tlvs_metadata.add_added_tlvs(); entry->set_type(tlv.first); diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 5ba466fd0e29e..9c8abb2a695b6 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -25,10 +25,10 @@ using testing::Return; using testing::ReturnNull; using testing::ReturnRef; +using envoy::config::core::v3::PerHostConfig; using envoy::config::core::v3::ProxyProtocolConfig; using envoy::config::core::v3::ProxyProtocolConfig_Version; using envoy::config::core::v3::ProxyProtocolPassThroughTLVs; - namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -736,9 +736,9 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromConfig) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto valid_tlv_entry = config.add_added_tlvs(); - valid_tlv_entry->set_type(0x96); - valid_tlv_entry->set_value("moredata"); + auto host_added_tlvs = config.add_added_tlvs(); + host_added_tlvs->set_type(0x96); + host_added_tlvs->set_value("moredata"); initialize(config, socket_options); EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) @@ -776,13 +776,13 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - ProxyProtocolConfig custom_tlv_metadata; - auto valid_entry = custom_tlv_metadata.add_added_tlvs(); - valid_entry->set_type(0x96); - valid_entry->set_value("moredata"); + PerHostConfig host_metadata_config; + auto host_added_tlvs = host_metadata_config.add_added_tlvs(); + host_added_tlvs->set_type(0x96); + host_added_tlvs->set_value("moredata"); ProtobufWkt::Any typed_metadata; - typed_metadata.PackFrom(custom_tlv_metadata); + typed_metadata.PackFrom(host_metadata_config); metadata->mutable_typed_filter_metadata()->emplace(std::make_pair(metadata_key, typed_metadata)); EXPECT_CALL(*host, metadata()).Times(testing::AnyNumber()).WillRepeatedly(Return(metadata)); transport_callbacks_.connection_.streamInfo().upstreamInfo()->setUpstreamHost(host); @@ -797,6 +797,9 @@ TEST_F(ProxyProtocolTest, V2CustomTLVsFromHostMetadata) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); + auto config_added_tlvs = config.add_added_tlvs(); + config_added_tlvs->set_type(0x96); + config_added_tlvs->set_value("moredata"); initialize(config, socket_options); EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) @@ -836,10 +839,10 @@ TEST_F(ProxyProtocolTest, V2CombinedPrecedenceHostConfigPassthrough) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - ProxyProtocolConfig host_metadata_config; - auto host_entry = host_metadata_config.add_added_tlvs(); - host_entry->set_type(0x99); - host_entry->set_value("hostValue"); + PerHostConfig host_metadata_config; + auto host_added_tlvs = host_metadata_config.add_added_tlvs(); + host_added_tlvs->set_type(0x99); + host_added_tlvs->set_value("hostValue"); ProtobufWkt::Any typed_metadata; typed_metadata.PackFrom(host_metadata_config); @@ -858,9 +861,9 @@ TEST_F(ProxyProtocolTest, V2CombinedPrecedenceHostConfigPassthrough) { ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto config_entry = config.add_added_tlvs(); - config_entry->set_type(0x99); - config_entry->set_value("configValue"); + auto config_added_tlvs = config.add_added_tlvs(); + config_added_tlvs->set_type(0x99); + config_added_tlvs->set_value("configValue"); initialize(config, socket_options); EXPECT_CALL(io_handle_, write(BufferStringEqual(expected_buff.toString()))) @@ -898,10 +901,10 @@ TEST_F(ProxyProtocolTest, V2DuplicateTLVsInConfigAndMetadataHandledProperly) { const std::string metadata_key = Config::MetadataFilters::get().ENVOY_TRANSPORT_SOCKETS_PROXY_PROTOCOL; - ProxyProtocolConfig host_metadata_config; - auto host_entry = host_metadata_config.add_added_tlvs(); - host_entry->set_type(0x98); - host_entry->set_value("d1"); + PerHostConfig host_metadata_config; + auto host_added_tlvs = host_metadata_config.add_added_tlvs(); + host_added_tlvs->set_type(0x98); + host_added_tlvs->set_value("d1"); auto duplicate_host_entry = host_metadata_config.add_added_tlvs(); duplicate_host_entry->set_type(0x98); duplicate_host_entry->set_value("d2"); // Last duplicate value @@ -925,9 +928,9 @@ TEST_F(ProxyProtocolTest, V2DuplicateTLVsInConfigAndMetadataHandledProperly) { // Configure duplicate TLVs in the configuration. ProxyProtocolConfig config; config.set_version(ProxyProtocolConfig_Version::ProxyProtocolConfig_Version_V2); - auto tlv_entry = config.add_added_tlvs(); - tlv_entry->set_type(0x96); - tlv_entry->set_value("bar"); + auto tlv = config.add_added_tlvs(); + tlv->set_type(0x96); + tlv->set_value("bar"); auto duplicate_tlv_entry = config.add_added_tlvs(); duplicate_tlv_entry->set_type(0x96); duplicate_tlv_entry->set_value("baz"); // Last duplicate value for type 0x96 @@ -950,7 +953,7 @@ TEST_F(ProxyProtocolTest, V2DuplicateTLVsInConfigAndMetadataHandledProperly) { } // Test handles edge case where the well-known host metadata namespace is present, but the -// TLVs are invalid and cannot be unpacked properly. +// TLVs are invalid and cannot be unpacked properly. Needed for code coverage. TEST_F(ProxyProtocolTest, V2CustomTLVMetadataInvalidFormat) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8)); @@ -1006,6 +1009,7 @@ TEST_F(ProxyProtocolTest, V2CustomTLVMetadataInvalidFormat) { } // Test verifies edge case where host has metadata available, but does not include the expected key. +// Needed for code coverage. TEST_F(ProxyProtocolTest, V2CustomTLVHostMetadataMissing) { auto src_addr = Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("1:2:3::4", 8));