diff --git a/api/envoy/data/core/v3/tlv_metadata.proto b/api/envoy/data/core/v3/tlv_metadata.proto new file mode 100644 index 000000000000..8f99b004e3f3 --- /dev/null +++ b/api/envoy/data/core/v3/tlv_metadata.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package envoy.data.core.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.data.core.v3"; +option java_outer_classname = "TlvMetadataProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/data/core/v3;corev3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Proxy Protocol Filter Typed Metadata] +// PROXY protocol filter typed metadata. + +message TlvsMetadata { + // Typed metadata for :ref:`Proxy protocol filter `, that represents a map of TLVs. + // Each entry in the map consists of a key which corresponds to a configured + // :ref:`rule key ` and a value (TLV value in bytes). + // When runtime flag ``envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener`` is enabled, + // :ref:`Proxy protocol filter ` + // will populate typed metadata and regular metadata. By default filter will populate typed and untyped metadata. + map typed_metadata = 1; +} diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 5300deb60298..2a4d1c464585 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -16,6 +16,12 @@ behavior_changes: change: | Changes the default value of ``envoy.reloadable_features.http2_use_oghttp2`` to true. This changes the codec used for HTTP/2 requests and responses. This behavior can be reverted by setting the feature to false. +- area: proxy_protocol + change: | + Populate typed metadata by default in proxy protocol listener. Typed metadata can be consumed as + :ref:`TlvsMetadata type `. + This change can be temporarily disabled by setting the runtime flag + ``envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener`` to ``false``. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/docs/root/api-v3/data/core/core.rst b/docs/root/api-v3/data/core/core.rst index 15fa7ab1ed16..b8316705297f 100644 --- a/docs/root/api-v3/data/core/core.rst +++ b/docs/root/api-v3/data/core/core.rst @@ -6,3 +6,4 @@ Core data :maxdepth: 2 v3/health_check_event.proto + v3/tlv_metadata.proto diff --git a/envoy/network/filter.h b/envoy/network/filter.h index 989e4a49ba5e..ebda7ce666d7 100644 --- a/envoy/network/filter.h +++ b/envoy/network/filter.h @@ -299,6 +299,14 @@ class ListenerFilterCallbacks { */ virtual void setDynamicMetadata(const std::string& name, const ProtobufWkt::Struct& value) PURE; + /** + * @param name the namespace used in the metadata in reverse DNS format, for example: + * envoy.test.my_filter. + * @param value of type protobuf any to set on the namespace. A merge will be performed with new + * values for the same key overriding existing. + */ + virtual void setDynamicTypedMetadata(const std::string& name, const ProtobufWkt::Any& value) PURE; + /** * @return const envoy::config::core::v3::Metadata& the dynamic metadata associated with this * connection. diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h index b6efacb79529..bdb8007642f4 100644 --- a/envoy/stream_info/stream_info.h +++ b/envoy/stream_info/stream_info.h @@ -838,6 +838,13 @@ class StreamInfo { */ virtual void setDynamicMetadata(const std::string& name, const ProtobufWkt::Struct& value) PURE; + /** + * @param name the namespace used in the metadata in reverse DNS format, for example: + * envoy.test.my_filter. + * @param value of type protobuf any to set on the namespace. + */ + virtual void setDynamicTypedMetadata(const std::string& name, const ProtobufWkt::Any& value) PURE; + /** * Object on which filters can share data on a per-request basis. For singleton data objects, only * one filter can produce a named data object. List data objects can be updated by multiple diff --git a/source/common/listener_manager/active_tcp_socket.cc b/source/common/listener_manager/active_tcp_socket.cc index a55fdff53a6f..910bf6c8f29b 100644 --- a/source/common/listener_manager/active_tcp_socket.cc +++ b/source/common/listener_manager/active_tcp_socket.cc @@ -175,6 +175,11 @@ void ActiveTcpSocket::setDynamicMetadata(const std::string& name, stream_info_->setDynamicMetadata(name, value); } +void ActiveTcpSocket::setDynamicTypedMetadata(const std::string& name, + const ProtobufWkt::Any& value) { + stream_info_->setDynamicTypedMetadata(name, value); +} + void ActiveTcpSocket::newConnection() { connected_ = true; diff --git a/source/common/listener_manager/active_tcp_socket.h b/source/common/listener_manager/active_tcp_socket.h index 1d4efa3ac79d..6423f3ba54bd 100644 --- a/source/common/listener_manager/active_tcp_socket.h +++ b/source/common/listener_manager/active_tcp_socket.h @@ -74,6 +74,7 @@ class ActiveTcpSocket : public Network::ListenerFilterManager, void startFilterChain() { continueFilterChain(true); } void setDynamicMetadata(const std::string& name, const ProtobufWkt::Struct& value) override; + void setDynamicTypedMetadata(const std::string& name, const ProtobufWkt::Any& value) override; envoy::config::core::v3::Metadata& dynamicMetadata() override { return stream_info_->dynamicMetadata(); }; diff --git a/source/common/quic/envoy_quic_server_connection.h b/source/common/quic/envoy_quic_server_connection.h index b98f00561669..82e41ab44ff3 100644 --- a/source/common/quic/envoy_quic_server_connection.h +++ b/source/common/quic/envoy_quic_server_connection.h @@ -63,6 +63,9 @@ class QuicListenerFilterManagerImpl : public Network::QuicListenerFilterManager, void setDynamicMetadata(const std::string& name, const ProtobufWkt::Struct& value) override { stream_info_.setDynamicMetadata(name, value); } + void setDynamicTypedMetadata(const std::string& name, const ProtobufWkt::Any& value) override { + stream_info_.setDynamicTypedMetadata(name, value); + } envoy::config::core::v3::Metadata& dynamicMetadata() override { return stream_info_.dynamicMetadata(); }; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 922ff4e4dc1c..4cac40375d2b 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -101,6 +101,7 @@ RUNTIME_GUARD(envoy_reloadable_features_upstream_allow_connect_with_2xx); RUNTIME_GUARD(envoy_reloadable_features_upstream_remote_address_use_connection); RUNTIME_GUARD(envoy_reloadable_features_upstream_wait_for_response_headers_before_disabling_read); RUNTIME_GUARD(envoy_reloadable_features_use_http3_header_normalisation); +RUNTIME_GUARD(envoy_reloadable_features_use_typed_metadata_in_proxy_protocol_listener); RUNTIME_GUARD(envoy_reloadable_features_validate_connect); RUNTIME_GUARD(envoy_reloadable_features_validate_grpc_header_before_log_grpc_status); RUNTIME_GUARD(envoy_reloadable_features_validate_upstream_headers); diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 8ea7df0e0b2f..8cffc20d977f 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -292,6 +292,10 @@ struct StreamInfoImpl : public StreamInfo { (*metadata_.mutable_filter_metadata())[name].MergeFrom(value); }; + void setDynamicTypedMetadata(const std::string& name, const ProtobufWkt::Any& value) override { + (*metadata_.mutable_typed_filter_metadata())[name].MergeFrom(value); + } + const FilterStateSharedPtr& filterState() override { return filter_state_; } const FilterState& filterState() const override { return *filter_state_; } diff --git a/source/extensions/filters/listener/proxy_protocol/BUILD b/source/extensions/filters/listener/proxy_protocol/BUILD index 7401b9156501..ddc3ee86d1fa 100644 --- a/source/extensions/filters/listener/proxy_protocol/BUILD +++ b/source/extensions/filters/listener/proxy_protocol/BUILD @@ -33,8 +33,11 @@ envoy_cc_library( "//source/common/network:address_lib", "//source/common/network:proxy_protocol_filter_state_lib", "//source/common/network:utility_lib", + "//source/common/protobuf:utility_lib", + "//source/common/runtime:runtime_features_lib", "//source/extensions/common/proxy_protocol:proxy_protocol_header_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/data/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/listener/proxy_protocol/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc index 953e38561e1e..847ce6d45e7b 100644 --- a/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc @@ -10,6 +10,7 @@ #include "envoy/common/exception.h" #include "envoy/common/platform.h" #include "envoy/config/core/v3/proxy_protocol.pb.h" +#include "envoy/data/core/v3/tlv_metadata.pb.h" #include "envoy/event/dispatcher.h" #include "envoy/network/listen_socket.h" #include "envoy/stats/scope.h" @@ -26,6 +27,7 @@ #include "source/common/network/proxy_protocol_filter_state.h" #include "source/common/network/utility.h" #include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" #include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" using envoy::config::core::v3::ProxyProtocolConfig; @@ -533,15 +535,38 @@ bool Filter::parseTlvs(const uint8_t* buf, size_t len) { absl::string_view tlv_value(reinterpret_cast(buf + idx), tlv_value_length); auto key_value_pair = config_->isTlvTypeNeeded(tlv_type); if (nullptr != key_value_pair) { + std::string metadata_key = key_value_pair->metadata_namespace().empty() + ? "envoy.filters.listener.proxy_protocol" + : key_value_pair->metadata_namespace(); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener")) { + auto& typed_filter_metadata = (*cb_->dynamicMetadata().mutable_typed_filter_metadata()); + + const auto typed_proxy_filter_metadata = typed_filter_metadata.find(metadata_key); + envoy::data::core::v3::TlvsMetadata tlvs_metadata; + auto status = absl::OkStatus(); + if (typed_proxy_filter_metadata != typed_filter_metadata.end()) { + status = MessageUtil::unpackTo(typed_proxy_filter_metadata->second, tlvs_metadata); + } + if (!status.ok()) { + ENVOY_LOG_PERIODIC(warn, std::chrono::seconds(1), + "proxy_protocol: Failed to unpack typed metadata for TLV type ", + tlv_type); + } else { + Protobuf::BytesValue tlv_byte_value; + tlv_byte_value.set_value(tlv_value.data(), tlv_value.size()); + tlvs_metadata.mutable_typed_metadata()->insert( + {key_value_pair->key(), tlv_byte_value.value()}); + ProtobufWkt::Any typed_metadata; + typed_metadata.PackFrom(tlvs_metadata); + cb_->setDynamicTypedMetadata(metadata_key, typed_metadata); + } + } + // Always populate untyped metadata for backwards compatibility. ProtobufWkt::Value metadata_value; // Sanitize any non utf8 characters. auto sanitised_tlv_value = MessageUtil::sanitizeUtf8String(tlv_value); metadata_value.set_string_value(sanitised_tlv_value.data(), sanitised_tlv_value.size()); - - std::string metadata_key = key_value_pair->metadata_namespace().empty() - ? "envoy.filters.listener.proxy_protocol" - : key_value_pair->metadata_namespace(); - ProtobufWkt::Struct metadata( (*cb_->dynamicMetadata().mutable_filter_metadata())[metadata_key]); metadata.mutable_fields()->insert({key_value_pair->key(), metadata_value}); diff --git a/test/extensions/filters/listener/proxy_protocol/BUILD b/test/extensions/filters/listener/proxy_protocol/BUILD index 0babe857b97b..3f1b35dcaefc 100644 --- a/test/extensions/filters/listener/proxy_protocol/BUILD +++ b/test/extensions/filters/listener/proxy_protocol/BUILD @@ -35,9 +35,11 @@ envoy_extension_cc_test( "//test/mocks/server:listener_factory_context_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/data/core/v3:pkg_cc_proto", ], ) @@ -81,6 +83,7 @@ envoy_extension_cc_test( "//source/extensions/filters/listener/proxy_protocol:config", "//source/extensions/filters/network/tcp_proxy:config", "//test/integration:http_integration_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_proto_integration_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_proto_integration_test.cc index 2eb53b88f229..8ad20b803384 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_proto_integration_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_proto_integration_test.cc @@ -9,6 +9,7 @@ #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "fmt/format.h" diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index bd40f59e11eb..1ce19c5dc6e5 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -5,6 +5,7 @@ #include "envoy/common/platform.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/core/v3/proxy_protocol.pb.h" +#include "envoy/data/core/v3/tlv_metadata.pb.h" #include "envoy/stats/scope.h" #include "source/common/api/os_sys_calls_impl.h" @@ -28,6 +29,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" @@ -1523,6 +1525,10 @@ TEST_P(ProxyProtocolTest, V2ParseExtensionsLargeThanInitMaxReadBytes) { } TEST_P(ProxyProtocolTest, V2ExtractTlvOfInterest) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({ + {"envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener", "false"}, + }); // A well-formed ipv4/tcp with a pair of TLV extensions is accepted constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, 0x11, 0x00, 0x1a, 0x01, 0x02, 0x03, 0x04, @@ -1547,6 +1553,7 @@ TEST_P(ProxyProtocolTest, V2ExtractTlvOfInterest) { expectData("DATA"); EXPECT_EQ(1, server_connection_->streamInfo().dynamicMetadata().filter_metadata_size()); + EXPECT_EQ(0, server_connection_->streamInfo().dynamicMetadata().typed_filter_metadata_size()); auto metadata = server_connection_->streamInfo().dynamicMetadata().filter_metadata(); EXPECT_EQ(1, metadata.size()); @@ -1604,6 +1611,10 @@ TEST_P(ProxyProtocolTest, V2ExtractTlvOfInterestAndEmitWithSpecifiedMetadataName } TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterest) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({ + {"envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener", "false"}, + }); // A well-formed ipv4/tcp with a pair of TLV extensions is accepted constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, 0x11, 0x00, 0x39, 0x01, 0x02, 0x03, 0x04, @@ -1642,6 +1653,7 @@ TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterest) { expectData("DATA"); EXPECT_EQ(1, server_connection_->streamInfo().dynamicMetadata().filter_metadata_size()); + EXPECT_EQ(0, server_connection_->streamInfo().dynamicMetadata().typed_filter_metadata_size()); auto metadata = server_connection_->streamInfo().dynamicMetadata().filter_metadata(); EXPECT_EQ(1, metadata.size()); @@ -1664,6 +1676,10 @@ TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterest) { } TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterestAndSanitiseNonUtf8) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({ + {"envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener", "false"}, + }); // A well-formed ipv4/tcp with a pair of TLV extensions is accepted. constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, 0x11, 0x00, 0x39, 0x01, 0x02, 0x03, 0x04, @@ -1704,6 +1720,7 @@ TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterestAndSanitiseNonUtf8) { expectData("DATA"); EXPECT_EQ(1, server_connection_->streamInfo().dynamicMetadata().filter_metadata_size()); + EXPECT_EQ(0, server_connection_->streamInfo().dynamicMetadata().typed_filter_metadata_size()); auto metadata = server_connection_->streamInfo().dynamicMetadata().filter_metadata(); EXPECT_EQ(1, metadata.size()); @@ -1728,6 +1745,166 @@ TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterestAndSanitiseNonUtf8) { EXPECT_EQ(stats_store_.counter("proxy_proto.versions.v2.found").value(), 1); } +TEST_P(ProxyProtocolTest, V2ExtractMultipleTlvsOfInterestAndEmitTypedAndUntypedMetadata) { + // A well-formed ipv4/tcp with a pair of TLV extensions is accepted + constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, + 0x54, 0x0a, 0x21, 0x11, 0x00, 0x39, 0x01, 0x02, 0x03, 0x04, + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x00, 0x02}; + // a TLV of type 0x00 with size of 4 (1 byte is value) + constexpr uint8_t tlv1[] = {0x00, 0x00, 0x01, 0xff}; + // a TLV of type 0x02 with size of 10 bytes (7 bytes are value) + constexpr uint8_t tlv_type_authority[] = {0x02, 0x00, 0x07, 0x66, 0x6f, + 0x6f, 0x2e, 0x63, 0x6f, 0x6d}; + // a TLV of type 0x0f with size of 6 bytes (3 bytes are value) + constexpr uint8_t tlv3[] = {0x0f, 0x00, 0x03, 0xf0, 0x00, 0x0f}; + // a TLV of type 0xea with size of 25 bytes (22 bytes are value) + constexpr uint8_t tlv_vpc_id[] = {0xea, 0x00, 0x16, 0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, + 0x32, 0x35, 0x74, 0x65, 0x73, 0x74, 0x32, 0x66, 0x61, + 0x36, 0x63, 0x36, 0x33, 0x68, 0x61, 0x37}; + constexpr uint8_t data[] = {'D', 'A', 'T', 'A'}; + + envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol proto_config; + auto rule_type_authority = proto_config.add_rules(); + rule_type_authority->set_tlv_type(0x02); + rule_type_authority->mutable_on_tlv_present()->set_key("PP2 type authority"); + + auto rule_vpc_id = proto_config.add_rules(); + rule_vpc_id->set_tlv_type(0xea); + rule_vpc_id->mutable_on_tlv_present()->set_key("PP2 vpc id"); + + connect(true, &proto_config); + write(buffer, sizeof(buffer)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + write(tlv1, sizeof(tlv1)); + write(tlv_type_authority, sizeof(tlv_type_authority)); + write(tlv3, sizeof(tlv3)); + write(tlv_vpc_id, sizeof(tlv_vpc_id)); + write(data, sizeof(data)); + expectData("DATA"); + + EXPECT_EQ(1, server_connection_->streamInfo().dynamicMetadata().filter_metadata_size()); + + auto typed_metadata = server_connection_->streamInfo().dynamicMetadata().typed_filter_metadata(); + EXPECT_EQ(1, typed_metadata.size()); + EXPECT_EQ(1, typed_metadata.count(ProxyProtocol)); + envoy::data::core::v3::TlvsMetadata tlvs_metadata; + auto status = MessageUtil::unpackTo(typed_metadata[ProxyProtocol], tlvs_metadata); + EXPECT_EQ(absl::OkStatus(), status); + EXPECT_EQ(2, tlvs_metadata.typed_metadata().size()); + + auto value_type_authority = (tlvs_metadata.typed_metadata()).at("PP2 type authority"); + ASSERT_THAT(value_type_authority, ElementsAre(0x66, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, 0x6d)); + + auto value_type_vpc_id = (tlvs_metadata.typed_metadata()).at("PP2 vpc id"); + ASSERT_THAT(value_type_vpc_id, + ElementsAre(0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, 0x32, 0x35, 0x74, 0x65, 0x73, 0x74, + 0x32, 0x66, 0x61, 0x36, 0x63, 0x36, 0x33, 0x68, 0x61, 0x37)); + + auto metadata = server_connection_->streamInfo().dynamicMetadata().filter_metadata(); + EXPECT_EQ(1, metadata.size()); + EXPECT_EQ(1, metadata.count(ProxyProtocol)); + + auto fields = metadata.at(ProxyProtocol).fields(); + EXPECT_EQ(2, fields.size()); + EXPECT_EQ(1, fields.count("PP2 type authority")); + EXPECT_EQ(1, fields.count("PP2 vpc id")); + + value_type_authority = fields.at("PP2 type authority").string_value(); + ASSERT_THAT(value_type_authority, ElementsAre(0x66, 0x6f, 0x6f, 0x2e, 0x63, 0x6f, 0x6d)); + + value_type_vpc_id = fields.at("PP2 vpc id").string_value(); + ASSERT_THAT(value_type_vpc_id, + ElementsAre(0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, 0x32, 0x35, 0x74, 0x65, 0x73, 0x74, + 0x32, 0x66, 0x61, 0x36, 0x63, 0x36, 0x33, 0x68, 0x61, 0x37)); + disconnect(); + EXPECT_EQ(stats_store_.counter("proxy_proto.versions.v2.found").value(), 1); +} + +TEST_P(ProxyProtocolTest, + V2ExtractMultipleTlvsOfInterestWithNonUtf8CharsAndEmitTypedAndUntypedMetadata) { + // A well-formed ipv4/tcp with a pair of TLV extensions is accepted + constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, + 0x54, 0x0a, 0x21, 0x11, 0x00, 0x39, 0x01, 0x02, 0x03, 0x04, + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x00, 0x02}; + // a TLV of type 0x00 with size of 4 (1 byte is value) + constexpr uint8_t tlv1[] = {0x00, 0x00, 0x01, 0xff}; + // a TLV of type 0x02 with size of 10 bytes (7 bytes are value). + // 2nd and 7th bytes in the value are not utf-8. + constexpr uint8_t tlv_type_authority[] = {0x02, 0x00, 0x07, 0x66, 0xfe, + 0x6f, 0x2e, 0x63, 0x6f, 0xc1}; + // a TLV of type 0x0f with size of 6 bytes (3 bytes are value) + constexpr uint8_t tlv3[] = {0x0f, 0x00, 0x03, 0xf0, 0x00, 0x0f}; + // a TLV of type 0xea with size of 25 bytes (22 bytes are value) + // 7th and 21st bytes in the value are not utf-8. + constexpr uint8_t tlv_vpc_id[] = {0xea, 0x00, 0x16, 0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, + 0xc0, 0x35, 0x74, 0x65, 0x73, 0x74, 0x32, 0x66, 0x61, + 0x36, 0x63, 0x36, 0x33, 0x68, 0xf9, 0x37}; + constexpr uint8_t data[] = {'D', 'A', 'T', 'A'}; + + envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol proto_config; + auto rule_type_authority = proto_config.add_rules(); + rule_type_authority->set_tlv_type(0x02); + rule_type_authority->mutable_on_tlv_present()->set_key("PP2 type authority"); + + auto rule_vpc_id = proto_config.add_rules(); + rule_vpc_id->set_tlv_type(0xea); + rule_vpc_id->mutable_on_tlv_present()->set_key("PP2 vpc id"); + + connect(true, &proto_config); + write(buffer, sizeof(buffer)); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + + write(tlv1, sizeof(tlv1)); + write(tlv_type_authority, sizeof(tlv_type_authority)); + write(tlv3, sizeof(tlv3)); + write(tlv_vpc_id, sizeof(tlv_vpc_id)); + write(data, sizeof(data)); + expectData("DATA"); + + EXPECT_EQ(1, server_connection_->streamInfo().dynamicMetadata().filter_metadata_size()); + + auto metadata = server_connection_->streamInfo().dynamicMetadata().filter_metadata(); + EXPECT_EQ(1, metadata.size()); + EXPECT_EQ(1, metadata.count(ProxyProtocol)); + + auto fields = metadata.at(ProxyProtocol).fields(); + EXPECT_EQ(2, fields.size()); + EXPECT_EQ(1, fields.count("PP2 type authority")); + EXPECT_EQ(1, fields.count("PP2 vpc id")); + + const char replacement = 0x21; + auto value_type_authority = fields.at("PP2 type authority").string_value(); + // Non utf8 characters have been replaced with `0x21` (`!` character). + ASSERT_THAT(value_type_authority, + ElementsAre(0x66, replacement, 0x6f, 0x2e, 0x63, 0x6f, replacement)); + + auto value_type_vpc_id = fields.at("PP2 vpc id").string_value(); + ASSERT_THAT(value_type_vpc_id, + ElementsAre(0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, replacement, 0x35, 0x74, 0x65, 0x73, + 0x74, 0x32, 0x66, 0x61, 0x36, 0x63, 0x36, 0x33, 0x68, replacement, 0x37)); + + auto typed_metadata = server_connection_->streamInfo().dynamicMetadata().typed_filter_metadata(); + EXPECT_EQ(1, typed_metadata.size()); + EXPECT_EQ(1, typed_metadata.count(ProxyProtocol)); + + envoy::data::core::v3::TlvsMetadata tlvs_metadata; + auto status = MessageUtil::unpackTo(typed_metadata[ProxyProtocol], tlvs_metadata); + EXPECT_EQ(absl::OkStatus(), status); + EXPECT_EQ(2, tlvs_metadata.typed_metadata().size()); + + value_type_authority = (tlvs_metadata.typed_metadata()).at("PP2 type authority"); + ASSERT_THAT(value_type_authority, ElementsAre(0x66, 0xfe, 0x6f, 0x2e, 0x63, 0x6f, 0xc1)); + + value_type_vpc_id = (tlvs_metadata.typed_metadata()).at("PP2 vpc id"); + ASSERT_THAT(value_type_vpc_id, + ElementsAre(0x01, 0x76, 0x70, 0x63, 0x2d, 0x30, 0xc0, 0x35, 0x74, 0x65, 0x73, 0x74, + 0x32, 0x66, 0x61, 0x36, 0x63, 0x36, 0x33, 0x68, 0xf9, 0x37)); + + disconnect(); + EXPECT_EQ(stats_store_.counter("proxy_proto.versions.v2.found").value(), 1); +} + TEST_P(ProxyProtocolTest, V2WillNotOverwriteTLV) { // A well-formed ipv4/tcp with a pair of TLV extensions is accepted constexpr uint8_t buffer[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index 5a8009e94899..eabcfc9b55ea 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -424,6 +424,7 @@ class MockListenerFilterCallbacks : public ListenerFilterCallbacks { MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); MOCK_METHOD(void, continueFilterChain, (bool)); MOCK_METHOD(void, setDynamicMetadata, (const std::string&, const ProtobufWkt::Struct&)); + MOCK_METHOD(void, setDynamicTypedMetadata, (const std::string&, const ProtobufWkt::Any& value)); MOCK_METHOD(envoy::config::core::v3::Metadata&, dynamicMetadata, ()); MOCK_METHOD(const envoy::config::core::v3::Metadata&, dynamicMetadata, (), (const)); MOCK_METHOD(StreamInfo::FilterState&, filterState, (), ()); diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index c3c8518ac152..43416421f021 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -144,6 +144,7 @@ class MockStreamInfo : public StreamInfo { MOCK_METHOD(void, setDynamicMetadata, (const std::string&, const ProtobufWkt::Struct&)); MOCK_METHOD(void, setDynamicMetadata, (const std::string&, const std::string&, const std::string&)); + MOCK_METHOD(void, setDynamicTypedMetadata, (const std::string&, const ProtobufWkt::Any& value)); MOCK_METHOD(const FilterStateSharedPtr&, filterState, ()); MOCK_METHOD(const FilterState&, filterState, (), (const)); MOCK_METHOD(void, setRequestHeaders, (const Http::RequestHeaderMap&));