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 12c0e92dc19d..a66d8b62a428 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 b4e472cc1f15..d63b058441a9 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 02c4a0b2640c..c31de828da51 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 1c8537964f72..35a9ad348a1d 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 a4a09f46f98c..669ede766fdc 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 17bf1342b1a6..3e1d5a8eee88 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 4e91f1ad363b..4f3654de437f 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 672d856d33d7..ccb76e7a3317 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 f86c9e9cce29..1c09efae3be7 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 53eb70cc001d..37fcfd065412 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 0aa2be2b939f..057a8538978a 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 ef2875dc8e39..40a378c084fc 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 748a4113373d..45e2f4ee9cd0 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 fe2dfa867ede..49e09a2b0ca5 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_;