From d0f177317453026859788a37f00cc67156a86604 Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Tue, 13 Jan 2026 14:30:05 +0530 Subject: [PATCH] feat: add formatters for masked IP addresses Signed-off-by: Rudrakh Panigrahi --- changelogs/current.yaml | 5 + .../advanced/substitution_formatter.rst | 78 +++++++++++- .../common/formatter/stream_info_formatter.cc | 97 ++++++++++---- source/common/stream_info/BUILD | 1 + source/common/stream_info/utility.cc | 38 +++++- source/common/stream_info/utility.h | 10 +- .../formatter/substitution_formatter_test.cc | 119 ++++++++++++++++++ 7 files changed, 310 insertions(+), 38 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 30d514caeddff..67a7efae931ea 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -40,5 +40,10 @@ removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` new_features: +- area: formatter + change: | + Extended ``*_WITHOUT_PORT`` address formatters to accept an optional ``MASK_PREFIX_LEN`` parameter that masks IP addresses + and returns them in CIDR notation (e.g., ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for + client IP ``10.1.10.23``). deprecated: diff --git a/docs/root/configuration/advanced/substitution_formatter.rst b/docs/root/configuration/advanced/substitution_formatter.rst index 7e5d9770f2ae1..0ca8d8646211d 100644 --- a/docs/root/configuration/advanced/substitution_formatter.rst +++ b/docs/root/configuration/advanced/substitution_formatter.rst @@ -561,10 +561,21 @@ Current supported substitution commands include: Local address of the upstream connection. If the address is an IP address, it includes both address and port. -``%UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%`` +``%UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT(MASK_PREFIX_LEN)%`` Local address of the upstream connection, without any port component. IP addresses are the only address type with a port component. + - If ``MASK_PREFIX_LEN`` is specified, the IP address is masked to that many bits and returned in CIDR notation. + - If ``MASK_PREFIX_LEN`` is omitted, the unmasked address is returned (without port). + - For IPv4, ``MASK_PREFIX_LEN`` must be between 0-32. + - For IPv6, ``MASK_PREFIX_LEN`` must be between 0-128. + + Examples: + + - ``%UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for source IP ``10.1.10.23`` + - ``%UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT(64)%`` returns ``2001:db8:1234:5678::/64`` for source IP ``2001:db8:1234:5678:9abc:def0:1234:5678`` + - ``%UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%`` returns ``10.1.10.23`` for source IP ``10.1.10.23`` + ``%UPSTREAM_LOCAL_PORT%`` Local port of the upstream connection. IP addresses are the only address type with a port component. @@ -576,10 +587,21 @@ Current supported substitution commands include: address and port. Identical to the :ref:`UPSTREAM_HOST ` value if the upstream host only has one address and connection is established successfully. -``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%`` +``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(MASK_PREFIX_LEN)%`` Remote address of the upstream connection, without any port component. IP addresses are the only address type with a port component. + - If ``MASK_PREFIX_LEN`` is specified, the IP address is masked to that many bits and returned in CIDR notation. + - If ``MASK_PREFIX_LEN`` is omitted, the unmasked address is returned (without port). + - For IPv4, ``MASK_PREFIX_LEN`` must be between 0-32. + - For IPv6, ``MASK_PREFIX_LEN`` must be between 0-128. + + Examples: + + - ``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for upstream IP ``10.1.10.23`` + - ``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(64)%`` returns ``2001:db8:1234:5678::/64`` for upstream IP ``2001:db8:1234:5678:9abc:def0:1234:5678`` + - ``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%`` returns ``10.1.10.23`` for upstream IP ``10.1.10.23`` + ``%UPSTREAM_REMOTE_PORT%`` Remote port of the upstream connection. IP addresses are the only address type with a port component. @@ -624,10 +646,21 @@ Current supported substitution commands include: :ref:`Proxy Protocol filter ` or :ref:`x-forwarded-for `. -``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%`` +``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(MASK_PREFIX_LEN)%`` Remote address of the downstream connection, without any port component. IP addresses are the only address type with a port component. + - If ``MASK_PREFIX_LEN`` is specified, the IP address is masked to that many bits and returned in CIDR notation. + - If ``MASK_PREFIX_LEN`` is omitted, the unmasked address is returned (without port). + - For IPv4, ``MASK_PREFIX_LEN`` must be between 0-32. + - For IPv6, ``MASK_PREFIX_LEN`` must be between 0-128. + + Examples: + + - ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for client IP ``10.1.10.23`` + - ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT(64)%`` returns ``2001:db8:1234:5678::/64`` for client IP ``2001:db8:1234:5678:9abc:def0:1234:5678`` + - ``%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%`` returns ``10.1.10.23`` for client IP ``10.1.10.23`` + .. note:: This may not be the physical remote address of the peer if the address has been inferred from @@ -654,10 +687,21 @@ Current supported substitution commands include: been inferred from :ref:`Proxy Protocol filter ` or :ref:`x-forwarded-for `. -``%DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT%`` +``%DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT(MASK_PREFIX_LEN)%`` Direct remote address of the downstream connection, without any port component. IP addresses are the only address type with a port component. + - If ``MASK_PREFIX_LEN`` is specified, the IP address is masked to that many bits and returned in CIDR notation. + - If ``MASK_PREFIX_LEN`` is omitted, the unmasked address is returned (without port). + - For IPv4, ``MASK_PREFIX_LEN`` must be between 0-32. + - For IPv6, ``MASK_PREFIX_LEN`` must be between 0-128. + + Examples: + + - ``%DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for client IP ``10.1.10.23`` + - ``%DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT(64)%`` returns ``2001:db8:1234:5678::/64`` for client IP ``2001:db8:1234:5678:9abc:def0:1234:5678`` + - ``%DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT%`` returns ``10.1.10.23`` for client IP ``10.1.10.23`` + .. note:: This is always the physical remote address of the peer even if the downstream remote address has @@ -697,18 +741,40 @@ Current supported substitution commands include: This is always the physical local address even if the downstream remote address has been inferred from :ref:`Proxy Protocol filter `. -``%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%`` +``%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT(MASK_PREFIX_LEN)%`` Local address of the downstream connection, without any port component. IP addresses are the only address type with a port component. + - If ``MASK_PREFIX_LEN`` is specified, the IP address is masked to that many bits and returned in CIDR notation. + - If ``MASK_PREFIX_LEN`` is omitted, the unmasked address is returned (without port). + - For IPv4, ``MASK_PREFIX_LEN`` must be between 0-32. + - For IPv6, ``MASK_PREFIX_LEN`` must be between 0-128. + + Examples: + + - ``%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for local IP ``10.1.10.23`` + - ``%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT(64)%`` returns ``2001:db8:1234:5678::/64`` for local IP ``2001:db8:1234:5678:9abc:def0:1234:5678`` + - ``%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%`` returns ``10.1.10.23`` for local IP ``10.1.10.23`` + .. note:: This may not be the physical local address if the downstream local address has been inferred from :ref:`Proxy Protocol filter `. -``%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT%`` +``%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT(MASK_PREFIX_LEN)%`` Direct local address of the downstream connection, without any port component. + - If ``MASK_PREFIX_LEN`` is specified, the IP address is masked to that many bits and returned in CIDR notation. + - If ``MASK_PREFIX_LEN`` is omitted, the unmasked address is returned (without port). + - For IPv4, ``MASK_PREFIX_LEN`` must be between 0-32. + - For IPv6, ``MASK_PREFIX_LEN`` must be between 0-128. + + Examples: + + - ``%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT(16)%`` returns ``10.1.0.0/16`` for local IP ``10.1.10.23`` + - ``%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT(64)%`` returns ``2001:db8:1234:5678::/64`` for local IP ``2001:db8:1234:5678:9abc:def0:1234:5678`` + - ``%DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT%`` returns ``10.1.10.23`` for local IP ``10.1.10.23`` + .. note:: This is always the physical local address even if the downstream local address has been inferred from diff --git a/source/common/formatter/stream_info_formatter.cc b/source/common/formatter/stream_info_formatter.cc index 9dc4c24a9d50e..24afd0d611aae 100644 --- a/source/common/formatter/stream_info_formatter.cc +++ b/source/common/formatter/stream_info_formatter.cc @@ -802,9 +802,10 @@ class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { f, StreamInfoAddressFieldExtractionType::WithPort); } - static std::unique_ptr withoutPort(FieldExtractor f) { + static std::unique_ptr + withoutPort(FieldExtractor f, absl::optional mask_prefix_len = absl::nullopt) { return std::make_unique( - f, StreamInfoAddressFieldExtractionType::WithoutPort); + f, StreamInfoAddressFieldExtractionType::WithoutPort, mask_prefix_len); } static std::unique_ptr justPort(FieldExtractor f) { @@ -818,8 +819,9 @@ class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { } StreamInfoAddressFormatterProvider(FieldExtractor f, - StreamInfoAddressFieldExtractionType extraction_type) - : field_extractor_(f), extraction_type_(extraction_type) {} + StreamInfoAddressFieldExtractionType extraction_type, + absl::optional mask_prefix_len = absl::nullopt) + : field_extractor_(f), extraction_type_(extraction_type), mask_prefix_len_(mask_prefix_len) {} // StreamInfoFormatterProvider // Don't hide the other structure of format and formatValue. @@ -854,7 +856,7 @@ class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { std::string toString(const Network::Address::Instance& address) const { switch (extraction_type_) { case StreamInfoAddressFieldExtractionType::WithoutPort: - return StreamInfo::Utility::formatDownstreamAddressNoPort(address); + return StreamInfo::Utility::formatDownstreamAddressNoPort(address, mask_prefix_len_); case StreamInfoAddressFieldExtractionType::JustPort: return StreamInfo::Utility::formatDownstreamAddressJustPort(address); case StreamInfoAddressFieldExtractionType::JustEndpointId: @@ -867,6 +869,7 @@ class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { FieldExtractor field_extractor_; const StreamInfoAddressFieldExtractionType extraction_type_; + const absl::optional mask_prefix_len_; }; // Ssl::ConnectionInfo std::string field extractor. @@ -1368,8 +1371,15 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide }); }}}, {"UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](absl::string_view, absl::optional) { + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](absl::string_view format, absl::optional) { + absl::optional mask_prefix_len; + if (!format.empty()) { + int len; + if (absl::SimpleAtoi(format, &len)) { + mask_prefix_len = len; + } + } return StreamInfoAddressFormatterProvider::withoutPort( [](const StreamInfo::StreamInfo& stream_info) -> Network::Address::InstanceConstSharedPtr { @@ -1377,7 +1387,8 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); } return nullptr; - }); + }, + mask_prefix_len); }}}, {"UPSTREAM_LOCAL_PORT", {CommandSyntaxChecker::COMMAND_ONLY, @@ -1401,13 +1412,21 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide }); }}}, {"UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](absl::string_view, absl::optional) { + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](absl::string_view format, absl::optional) { + absl::optional mask_prefix_len; + if (!format.empty()) { + int len; + if (absl::SimpleAtoi(format, &len)) { + mask_prefix_len = len; + } + } return StreamInfoAddressFormatterProvider::withoutPort( [](const StreamInfo::StreamInfo& stream_info) -> Network::Address::InstanceConstSharedPtr { return getUpstreamRemoteAddress(stream_info); - }); + }, + mask_prefix_len); }}}, {"UPSTREAM_REMOTE_PORT", {CommandSyntaxChecker::COMMAND_ONLY, @@ -1500,20 +1519,36 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide }); }}}, {"DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](absl::string_view, absl::optional) { + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](absl::string_view format, absl::optional) { + absl::optional mask_prefix_len; + if (!format.empty()) { + int len; + if (absl::SimpleAtoi(format, &len)) { + mask_prefix_len = len; + } + } return StreamInfoAddressFormatterProvider::withoutPort( [](const Envoy::StreamInfo::StreamInfo& stream_info) { return stream_info.downstreamAddressProvider().localAddress(); - }); + }, + mask_prefix_len); }}}, {"DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](absl::string_view, absl::optional) { + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](absl::string_view format, absl::optional) { + absl::optional mask_prefix_len; + if (!format.empty()) { + int len; + if (absl::SimpleAtoi(format, &len)) { + mask_prefix_len = len; + } + } return StreamInfoAddressFormatterProvider::withoutPort( [](const Envoy::StreamInfo::StreamInfo& stream_info) { return stream_info.downstreamAddressProvider().directLocalAddress(); - }); + }, + mask_prefix_len); }}}, {"DOWNSTREAM_LOCAL_PORT", {CommandSyntaxChecker::COMMAND_ONLY, @@ -1556,12 +1591,20 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide }); }}}, {"DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](absl::string_view, absl::optional) { + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](absl::string_view format, absl::optional) { + absl::optional mask_prefix_len; + if (!format.empty()) { + int len; + if (absl::SimpleAtoi(format, &len)) { + mask_prefix_len = len; + } + } return StreamInfoAddressFormatterProvider::withoutPort( [](const StreamInfo::StreamInfo& stream_info) { return stream_info.downstreamAddressProvider().remoteAddress(); - }); + }, + mask_prefix_len); }}}, {"DOWNSTREAM_REMOTE_PORT", {CommandSyntaxChecker::COMMAND_ONLY, @@ -1580,12 +1623,20 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide }); }}}, {"DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT", - {CommandSyntaxChecker::COMMAND_ONLY, - [](absl::string_view, absl::optional) { + {CommandSyntaxChecker::PARAMS_OPTIONAL, + [](absl::string_view format, absl::optional) { + absl::optional mask_prefix_len; + if (!format.empty()) { + int len; + if (absl::SimpleAtoi(format, &len)) { + mask_prefix_len = len; + } + } return StreamInfoAddressFormatterProvider::withoutPort( [](const StreamInfo::StreamInfo& stream_info) { return stream_info.downstreamAddressProvider().directRemoteAddress(); - }); + }, + mask_prefix_len); }}}, {"DOWNSTREAM_DIRECT_REMOTE_PORT", {CommandSyntaxChecker::COMMAND_ONLY, diff --git a/source/common/stream_info/BUILD b/source/common/stream_info/BUILD index 175913e022cc3..cabef2991a3f8 100644 --- a/source/common/stream_info/BUILD +++ b/source/common/stream_info/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( "//envoy/http:codes_interface", "//envoy/stream_info:stream_info_interface", "//source/common/http:default_server_string_lib", + "//source/common/network:cidr_range_lib", "//source/common/runtime:runtime_features_lib", "@com_google_absl//absl/container:node_hash_map", "@com_google_absl//absl/types:optional", diff --git a/source/common/stream_info/utility.cc b/source/common/stream_info/utility.cc index f66f8f77c29d9..6786f672b872b 100644 --- a/source/common/stream_info/utility.cc +++ b/source/common/stream_info/utility.cc @@ -5,8 +5,10 @@ #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "source/common/http/default_server_string.h" +#include "source/common/network/cidr_range.h" #include "source/common/runtime/runtime_features.h" +#include "absl/status/statusor.h" #include "absl/strings/str_format.h" namespace Envoy { @@ -240,13 +242,37 @@ absl::optional TimingUtility::lastDownstreamAckReceive return duration(timing.value().get().lastDownstreamAckReceived(), stream_info_); } -const std::string& -Utility::formatDownstreamAddressNoPort(const Network::Address::Instance& address) { - if (address.type() == Network::Address::Type::Ip) { - return address.ip()->addressAsString(); - } else { - return address.asString(); +const std::string Utility::formatDownstreamAddressNoPort(const Network::Address::Instance& address, + absl::optional mask_prefix_len) { + // No masking - return address without port + if (!mask_prefix_len.has_value()) { + if (address.type() == Network::Address::Type::Ip) { + return address.ip()->addressAsString(); + } else { + return address.asString(); + } + } + + std::string masked_address; + if (address.type() != Network::Address::Type::Ip) { + return masked_address; + } + + int length = mask_prefix_len.value_or( + address.ip()->version() == Network::Address::IpVersion::v4 ? 32 : 128); + + // CidrRange::create() requires a shared_ptr. We create one with a no-op deleter since we don't + // own the address and shouldn't delete it. + Network::Address::InstanceConstSharedPtr address_ptr(&address, + [](const Network::Address::Instance*) {}); + + auto cidr_range_or_error = + Network::Address::CidrRange::create(address_ptr, length, absl::nullopt); + + if (cidr_range_or_error.ok()) { + masked_address = cidr_range_or_error.value().asString(); } + return masked_address; } const std::string diff --git a/source/common/stream_info/utility.h b/source/common/stream_info/utility.h index 2f1d729fa98ee..a9b8083d84409 100644 --- a/source/common/stream_info/utility.h +++ b/source/common/stream_info/utility.h @@ -240,10 +240,14 @@ class Utility { public: /** * @param address supplies the downstream address. - * @return a properly formatted address for logs, header expansion, etc. + * @param mask_prefix_len optional CIDR prefix length to mask the address. If not provided, + * returns the unmasked IP address (without port). + * @return the IP address without port, or masked IP address in CIDR notation if mask_prefix_len + * is specified (e.g., "10.1.0.0/16"), or empty string if masking fails. */ - static const std::string& - formatDownstreamAddressNoPort(const Network::Address::Instance& address); + static const std::string + formatDownstreamAddressNoPort(const Network::Address::Instance& address, + absl::optional mask_prefix_len = absl::nullopt); /** * @param address supplies the downstream address. diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 7d80dd630fd4d..1ae8117aefe06 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -1005,6 +1005,125 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { ProtoEq(ValueUtil::numberValue(63443))); } + { + StreamInfoFormatter format("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "24"); + EXPECT_EQ("127.0.0.0/24", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.0/24"))); + } + + { + StreamInfoFormatter format("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "16"); + EXPECT_EQ("127.0.0.0/16", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.0/16"))); + } + + { + StreamInfoFormatter format("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "32"); + EXPECT_EQ("127.0.0.1/32", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.1/32"))); + } + + { + auto original_address = stream_info.downstreamAddressProvider().remoteAddress(); + auto masked_address = Network::Address::InstanceConstSharedPtr{ + new Network::Address::Ipv4Instance("10.1.10.23", 8080)}; + stream_info.downstream_connection_info_provider_->setRemoteAddress(masked_address); + + StreamInfoFormatter format("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "16"); + EXPECT_EQ("10.1.0.0/16", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("10.1.0.0/16"))); + + stream_info.downstream_connection_info_provider_->setRemoteAddress(original_address); + } + + { + auto original_address = stream_info.downstreamAddressProvider().remoteAddress(); + auto ipv6_address = Network::Address::InstanceConstSharedPtr{ + new Network::Address::Ipv6Instance("2001:db8:1234:5678::1", 8080)}; + stream_info.downstream_connection_info_provider_->setRemoteAddress(ipv6_address); + + StreamInfoFormatter format128("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "128"); + EXPECT_EQ("2001:db8:1234:5678::1/128", format128.format({}, stream_info)); + + StreamInfoFormatter format64("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "64"); + EXPECT_EQ("2001:db8:1234:5678::/64", format64.format({}, stream_info)); + EXPECT_THAT(format64.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("2001:db8:1234:5678::/64"))); + + StreamInfoFormatter format48("DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "48"); + EXPECT_EQ("2001:db8:1234::/48", format48.format({}, stream_info)); + + stream_info.downstream_connection_info_provider_->setRemoteAddress(original_address); + } + + { + StreamInfoFormatter format("DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT", "24"); + EXPECT_EQ("127.0.0.0/24", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.0/24"))); + } + + { + StreamInfoFormatter format("DOWNSTREAM_DIRECT_REMOTE_ADDRESS_WITHOUT_PORT", "16"); + EXPECT_EQ("127.0.0.0/16", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.0/16"))); + } + + { + if (stream_info.downstreamAddressProvider().localAddress() && + stream_info.downstreamAddressProvider().localAddress()->type() == + Network::Address::Type::Ip) { + StreamInfoFormatter format("DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", "24"); + auto result = format.format({}, stream_info); + if (result.has_value()) { + EXPECT_TRUE(result.value().find('/') != std::string::npos); + } + } + } + + { + if (stream_info.downstreamAddressProvider().directLocalAddress() && + stream_info.downstreamAddressProvider().directLocalAddress()->type() == + Network::Address::Type::Ip) { + StreamInfoFormatter format("DOWNSTREAM_DIRECT_LOCAL_ADDRESS_WITHOUT_PORT", "16"); + auto result = format.format({}, stream_info); + if (result.has_value()) { + EXPECT_TRUE(result.value().find("127.0.0.0/16") != std::string::npos || + result.value().find('/') != std::string::npos); + } + } + } + + { + StreamInfoFormatter format("UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "24"); + EXPECT_EQ("10.0.0.0/24", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("10.0.0.0/24"))); + } + + { + StreamInfoFormatter format("UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", "16"); + EXPECT_EQ("10.0.0.0/16", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("10.0.0.0/16"))); + } + + { + auto address = Network::Address::InstanceConstSharedPtr{ + new Network::Address::Ipv4Instance("127.0.0.5", 8443)}; + stream_info.upstreamInfo()->setUpstreamLocalAddress(address); + + StreamInfoFormatter format("UPSTREAM_LOCAL_ADDRESS_WITHOUT_PORT", "24"); + EXPECT_EQ("127.0.0.0/24", format.format({}, stream_info)); + EXPECT_THAT(format.formatValue({}, stream_info), + ProtoEq(ValueUtil::stringValue("127.0.0.0/24"))); + } + { StreamInfoFormatter downstream_format("DOWNSTREAM_LOCAL_ADDRESS_ENDPOINT_ID"); auto internal_address =