Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ip-tagging filter: add support for an optional ip-tag-header field #36434

Merged
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
49b77ae
add the new header field in proto
Radha13 Oct 2, 2024
7febf6a
use the new header filed + make a getter
Radha13 Oct 2, 2024
0da4790
use the new header if it's provided instead of x-envoy-ip-tags
Radha13 Oct 2, 2024
131ee4d
fix format
Radha13 Oct 2, 2024
68bb688
fix syntax here as well
Radha13 Oct 2, 2024
e59ea3a
add tests
Radha13 Oct 3, 2024
5ea7f0f
change the name of header field to something more consistent with the
Radha13 Oct 3, 2024
ab02a10
fix format
Radha13 Oct 3, 2024
8da6c4f
Update test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc
Radha13 Oct 8, 2024
eccb613
address review comments
Radha13 Oct 8, 2024
8b21da5
Update api/envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto
Radha13 Oct 9, 2024
3711e4b
add release notes
Radha13 Oct 9, 2024
5f62aea
make yaml lint test happy
Radha13 Oct 9, 2024
8c17a44
Merge remote-tracking branch 'upstream/main' into add-optional-header…
Radha13 Oct 10, 2024
a02c2f5
update docs
Radha13 Oct 11, 2024
921226a
Merge branch 'main' into add-optional-header-ip-tagging-filter
Radha13 Oct 16, 2024
d0adb65
add reference to the new proto field in docs and changelog
Radha13 Oct 16, 2024
8ce800f
lets not make yaml lint unhappy
Radha13 Oct 16, 2024
2f7ddec
try suggested link
Oct 28, 2024
c93feaa
implement SANTIZE/APPEND_FORWARD ip_tag_header_action
Oct 29, 2024
813fb6c
Merge branch 'main' into add-optional-header-ip-tagging-filter
Oct 29, 2024
89ca195
Document the ip_tag_header_action field.
Oct 29, 2024
437802e
review feedback: rewording
Oct 29, 2024
90750fe
update reference that I missed
Oct 29, 2024
664f46e
apply review feedback
Oct 30, 2024
80988fb
use an optional to indicate the ip-tag-header might be unset
Oct 30, 2024
84716e8
review feedback
Oct 30, 2024
7d74269
Merge branch 'main' into add-optional-header-ip-tagging-filter
Oct 30, 2024
1bcb45b
use opt-ref instead of optional
Oct 30, 2024
7d59e1f
apply review feedback
Nov 4, 2024
9303f56
Merge branch 'main' into add-optional-header-ip-tagging-filter
Nov 4, 2024
c1cc54b
Use camelcase for local variable.
Nov 4, 2024
8dc3bdd
add multiple headers, to confirm they're all removed
Nov 4, 2024
649d54d
Add "unless it has an x-envoy prefix"
Nov 4, 2024
3033d27
Function names are camel-case.
Nov 4, 2024
a9c92b7
Documentation fixes for proto, per review comments.
Nov 5, 2024
159fcf0
rename append action to APPEND_IF_EXISTS_OR_ADD
Nov 5, 2024
97b059c
Move header fields to its own message.
Nov 5, 2024
d6b807b
Update docs with new references/names.
Nov 5, 2024
c611942
Apparently v3 protobuf all messages are optional? Anyway, this does w…
Nov 5, 2024
99819b6
Reorder items in proto file.
Nov 5, 2024
44f3d4e
Fixed broken reference.
Nov 5, 2024
7f9ccea
Merge branch 'main' into add-optional-header-ip-tagging-filter
Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions api/envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// IP tagging :ref:`configuration overview <config_http_filters_ip_tagging>`.
// [#extension: envoy.filters.http.ip_tagging]

// [#next-free-field: 7]
message IPTagging {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.ip_tagging.v2.IPTagging";
Expand All @@ -39,6 +40,22 @@ message IPTagging {
EXTERNAL = 2;
}

// Describes how to apply the tags to the headers.
enum HeaderAction {
// (DEFAULT) The header specified in :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`
// will be dropped, before the tags are applied. Sanitize will be run regardless of if the source is a trusted IP or not.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// will be dropped, before the tags are applied. Sanitize will be run regardless of if the source is a trusted IP or not.
// will be dropped, before the tags are applied. The incoming header will be "sanitized" regardless of whether the request is an internal or external.

I'm trying to stick to the same terminology introduced in https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_sanitizing. I'm assuming that is the goal, and if so, I suggest trying to stick with the same terms.

//
// Note that the header will be visible unsanitized to any filters downstream of the ip-tag-header filter.
Copy link
Contributor

Choose a reason for hiding this comment

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

oh, mind adding
unless it has an x-envoy prefix

SANITIZE = 0;
alyssawilk marked this conversation as resolved.
Show resolved Hide resolved

// Tags will be appended to the header specified in
// :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`,
// leaving original values in place.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`,
// leaving original values in place.
// :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`.

//
// Please note that this could cause the header to retain values set by the http client even if the client is from an untrusted IP.
Copy link
Contributor

Choose a reason for hiding this comment

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

RE "untrusted IP", see comment above regarding the terms.

APPEND_FORWARD = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

What does "FORWARD" imply?

I'm assuming the goal is to have something similar to APPEND_IF_EXISTS_OR_ADD. Will it make sense to either use the same enum (probably not as the one suggested adds sanitization), or use the same enum name?

}

// Supplies the IP tag name and the IP address subnets.
message IPTag {
option (udpa.annotations.versioning).previous_message_type =
Expand All @@ -52,6 +69,23 @@ message IPTagging {
repeated config.core.v3.CidrRange ip_list = 2;
}

// Optional header to use for ip-tagging instead of using
// default header ``x-envoy-ip-tags``.
Copy link
Contributor

Choose a reason for hiding this comment

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

Again if sanitization is different for CustomHeader than DefaultHeader let's call out here. "This header will be sanitized based on the config in ip_tag_custom_header_action rather than the defaults for x-envoy prefixed headers

//
// This header will be sanitized based on the config in
// :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header_action>`
// rather than the defaults for x-envoy prefixed headers.
string ip_tag_header = 5
[(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}];

// Control if the header in :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`
// will be sanitized, or be appended to.
alyssawilk marked this conversation as resolved.
Show resolved Hide resolved
//
// This is ignored if :ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>` is empty.
Copy link
Contributor

Choose a reason for hiding this comment

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

As this field is closely related to the ip_tag_header field, it will be better to have them both under the same message type, and make the ip_tag_header field in that message type required (and probably move the HeaderAction to that inner message as well).

//
// Default: *SANITIZE*.
HeaderAction ip_tag_header_action = 6;

// The type of request the filter should apply to.
RequestType request_type = 1 [(validate.rules).enum = {defined_only: true}];

Expand Down
5 changes: 5 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,10 @@ new_features:
change: |
Added support for dynamic cluster selection in UDP proxy. The cluster can be set by one of the session filters
by setting a per-session state object under the key ``envoy.udp_proxy.cluster``.
- area: ip-tagging
change: |
Adds support for specifying an alternate header
:ref:`ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`
for appending IP tags via ip-tagging filter instead of using the default header ``x-envoy-ip-tags``.

deprecated:
15 changes: 12 additions & 3 deletions docs/root/configuration/http/http_filters/ip_tagging_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
IP Tagging
==========

The HTTP IP Tagging filter sets the header *x-envoy-ip-tags* with the string tags for the trusted address from
:ref:`x-forwarded-for <config_http_conn_man_headers_x-forwarded-for>`. If there are no tags for an address,
the header is not set.
The HTTP IP Tagging filter sets the *x-envoy-ip-tags* header or the provided :ref: `ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`
with the string tags for the trusted address from :ref:`x-forwarded-for <config_http_conn_man_headers_x-forwarded-for>`.

If the :ref: `ip_tag_header_action <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header_action>`
is set to *SANITIZE* (the default), the header mentioned in :ref: `ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`
will be replaced with the new tags, and clearing it if there are no tags.
If it is instead set to *APPEND_FORWARD*, the header will only be appended to, retaining any existing values.

Due to backward compatibility, if the :ref: `ip_tag_header <envoy_v3_api_field_extensions.filters.http.ip_tagging.v3.IPTagging.ip_tag_header>`
is empty, the tags will be appended to the *x-envoy-ip-tags* header.
This header is cleared at the start of the filter chain, so this is in effect the same as sanitize.
When applying this filter multiple times within the same filter chain, this retains the old behaviour which combines the tags from each invocation.

The implementation for IP Tagging provides a scalable way to compare an IP address to a large list of CIDR
ranges efficiently. The underlying algorithm for storing tags and IP address subnets is a Level-Compressed trie
Expand Down
50 changes: 43 additions & 7 deletions source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ IpTaggingFilterConfig::IpTaggingFilterConfig(
stat_name_set_(scope.symbolTable().makeSet("IpTagging")),
stats_prefix_(stat_name_set_->add(stat_prefix + "ip_tagging")),
no_hit_(stat_name_set_->add("no_hit")), total_(stat_name_set_->add("total")),
unknown_tag_(stat_name_set_->add("unknown_tag.hit")) {
unknown_tag_(stat_name_set_->add("unknown_tag.hit")), ip_tag_header_(config.ip_tag_header()),
ip_tag_header_action_(config.ip_tag_header_action()) {

// Once loading IP tags from a file system is supported, the restriction on the size
// of the set should be removed and observability into what tags are loaded needs
Expand Down Expand Up @@ -80,13 +81,8 @@ Http::FilterHeadersStatus IpTaggingFilter::decodeHeaders(Http::RequestHeaderMap&
std::vector<std::string> tags =
config_->trie().getData(callbacks_->streamInfo().downstreamAddressProvider().remoteAddress());

applyTags(headers, tags);
if (!tags.empty()) {
const std::string tags_join = absl::StrJoin(tags, ",");
headers.appendEnvoyIpTags(tags_join, ",");

// We must clear the route cache or else we can't match on x-envoy-ip-tags.
callbacks_->downstreamCallbacks()->clearRouteCache();

// For a large number(ex > 1000) of tags, stats cardinality will be an issue.
// If there are use cases with a large set of tags, a way to opt into these stats
// should be exposed and other observability options like logging tags need to be implemented.
Expand All @@ -112,6 +108,46 @@ void IpTaggingFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbac
callbacks_ = &callbacks;
}

void IpTaggingFilter::applyTags(Http::RequestHeaderMap& headers,
const std::vector<std::string>& tags) {
using HeaderAction = IpTaggingFilterConfig::HeaderAction;

OptRef<const Http::LowerCaseString> header_name = config_->ip_tag_header();

if (tags.empty()) {
bool mustSanitize =
Copy link
Contributor

Choose a reason for hiding this comment

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

this isn't really mustSanitize given we'll only do it if there's a header name configured. Also snake case for Envoy variables
maybe_sanitize?

Copy link
Contributor

Choose a reason for hiding this comment

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

(I wish there was a linter for this.) <3

config_->ip_tag_header_action() == HeaderAction::IPTagging_HeaderAction_SANITIZE;
if (header_name.has_value() && mustSanitize) {
if (headers.remove(header_name.value()) != 0) {
// We must clear the route cache in case it held a decision based on the now-removed header.
callbacks_->downstreamCallbacks()->clearRouteCache();
}
}
return;
}

const std::string tags_join = absl::StrJoin(tags, ",");
if (!header_name.has_value()) {
// The x-envoy-ip-tags header was cleared at the start of the filter chain.
// We only do append here, so that if multiple ip-tagging filters are run sequentially,
// the behaviour will be backwards compatible.
headers.appendEnvoyIpTags(tags_join, ",");
} else {
switch (config_->ip_tag_header_action()) {
PANIC_ON_PROTO_ENUM_SENTINEL_VALUES;
case HeaderAction::IPTagging_HeaderAction_SANITIZE:
headers.setCopy(header_name.value(), tags_join);
break;
case HeaderAction::IPTagging_HeaderAction_APPEND_FORWARD:
headers.appendCopy(header_name.value(), tags_join);
break;
}
}

// We must clear the route cache so it can match on the updated value of the header.
callbacks_->downstreamCallbacks()->clearRouteCache();
}

} // namespace IpTagging
} // namespace HttpFilters
} // namespace Extensions
Expand Down
16 changes: 16 additions & 0 deletions source/extensions/filters/http/ip_tagging/ip_tagging_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <vector>

#include "envoy/common/exception.h"
#include "envoy/common/optref.h"
#include "envoy/extensions/filters/http/ip_tagging/v3/ip_tagging.pb.h"
#include "envoy/http/filter.h"
#include "envoy/runtime/runtime.h"
Expand All @@ -31,6 +32,8 @@ enum class FilterRequestType { INTERNAL, EXTERNAL, BOTH };
*/
class IpTaggingFilterConfig {
public:
using HeaderAction = envoy::extensions::filters::http::ip_tagging::v3::IPTagging::HeaderAction;

IpTaggingFilterConfig(const envoy::extensions::filters::http::ip_tagging::v3::IPTagging& config,
const std::string& stat_prefix, Stats::Scope& scope,
Runtime::Loader& runtime);
Expand All @@ -39,6 +42,14 @@ class IpTaggingFilterConfig {
FilterRequestType requestType() const { return request_type_; }
const Network::LcTrie::LcTrie<std::string>& trie() const { return *trie_; }

OptRef<const Http::LowerCaseString> ip_tag_header() const {
Copy link
Contributor

Choose a reason for hiding this comment

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

and for functions ipTagHeader and ipTagHeaderAction

if (ip_tag_header_.get().empty()) {
return absl::nullopt;
}
return ip_tag_header_;
}
HeaderAction ip_tag_header_action() const { return ip_tag_header_action_; }

void incHit(absl::string_view tag) {
incCounter(stat_name_set_->getBuiltin(absl::StrCat(tag, ".hit"), unknown_tag_));
}
Expand Down Expand Up @@ -71,6 +82,9 @@ class IpTaggingFilterConfig {
const Stats::StatName total_;
const Stats::StatName unknown_tag_;
std::unique_ptr<Network::LcTrie::LcTrie<std::string>> trie_;
const Http::LowerCaseString
ip_tag_header_; // An empty string indicates that no ip_tag_header is set.
const HeaderAction ip_tag_header_action_;
};

using IpTaggingFilterConfigSharedPtr = std::shared_ptr<IpTaggingFilterConfig>;
Expand All @@ -95,6 +109,8 @@ class IpTaggingFilter : public Http::StreamDecoderFilter {
void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override;

private:
void applyTags(Http::RequestHeaderMap& headers, const std::vector<std::string>& tags);

IpTaggingFilterConfigSharedPtr config_;
Http::StreamDecoderFilterCallbacks* callbacks_{};
};
Expand Down
142 changes: 142 additions & 0 deletions test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,148 @@ TEST_F(IpTaggingFilterTest, AppendEntry) {
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers));
}

TEST_F(IpTaggingFilterTest, ReplaceAlternateHeaderWhenActionIsDefaulted) {
const std::string internal_request_yaml = R"EOF(
request_type: internal
ip_tag_header: x-envoy-optional-header
ip_tags:
- ip_tag_name: internal_request_with_optional_header
ip_list:
- {address_prefix: 1.2.3.4, prefix_len: 32}
)EOF";

initializeFilter(internal_request_yaml);

Http::TestRequestHeaderMapImpl request_headers{
{"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; // foo will be removed
Copy link
Contributor

Choose a reason for hiding this comment

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

mind adding multiple just so we regression test that rmove does remove all?

Network::Address::InstanceConstSharedPtr remote_address =
Network::Utility::parseInternetAddressNoThrow("1.2.3.4");
filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(
remote_address);

EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false));
EXPECT_EQ("internal_request_with_optional_header",
request_headers.get_("x-envoy-optional-header"));

EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
Http::TestRequestTrailerMapImpl request_trailers;
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers));
}

TEST_F(IpTaggingFilterTest, ReplaceAlternateHeader) {
const std::string internal_request_yaml = R"EOF(
request_type: internal
ip_tag_header: x-envoy-optional-header
ip_tag_header_action: SANITIZE
ip_tags:
- ip_tag_name: internal_request_with_optional_header
ip_list:
- {address_prefix: 1.2.3.4, prefix_len: 32}
)EOF";

initializeFilter(internal_request_yaml);

Http::TestRequestHeaderMapImpl request_headers{
{"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; // foo will be removed
Network::Address::InstanceConstSharedPtr remote_address =
Network::Utility::parseInternetAddressNoThrow("1.2.3.4");
filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(
remote_address);

EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false));
EXPECT_EQ("internal_request_with_optional_header",
request_headers.get_("x-envoy-optional-header"));

EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
Http::TestRequestTrailerMapImpl request_trailers;
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers));
}

TEST_F(IpTaggingFilterTest, ClearAlternateHeaderWhenUnmatchedAndSanitized) {
const std::string internal_request_yaml = R"EOF(
request_type: internal
ip_tag_header: x-envoy-optional-header
ip_tag_header_action: SANITIZE
ip_tags:
- ip_tag_name: internal_request_with_optional_header
ip_list:
- {address_prefix: 1.2.3.4, prefix_len: 32}
)EOF";

initializeFilter(internal_request_yaml);

Http::TestRequestHeaderMapImpl request_headers{
{"x-envoy-internal", "true"}, {"x-envoy-optional-header", "foo"}}; // header will be removed
Network::Address::InstanceConstSharedPtr remote_address =
Network::Utility::parseInternetAddressNoThrow("1.2.3.5");
filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(
remote_address);

EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false));
EXPECT_FALSE(request_headers.has("x-envoy-optional-header"));

EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
Http::TestRequestTrailerMapImpl request_trailers;
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers));
}

TEST_F(IpTaggingFilterTest, AppendForwardAlternateHeader) {
const std::string internal_request_yaml = R"EOF(
request_type: internal
ip_tag_header: x-envoy-optional-header
ip_tag_header_action: APPEND_FORWARD
ip_tags:
- ip_tag_name: internal_request_with_optional_header
ip_list:
- {address_prefix: 1.2.3.4, prefix_len: 32}
)EOF";

initializeFilter(internal_request_yaml);

Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"},
{"x-envoy-optional-header", "foo"}};
Network::Address::InstanceConstSharedPtr remote_address =
Network::Utility::parseInternetAddressNoThrow("1.2.3.4");
filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(
remote_address);

EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false));
EXPECT_EQ("foo,internal_request_with_optional_header",
request_headers.get_("x-envoy-optional-header"));

EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
Http::TestRequestTrailerMapImpl request_trailers;
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers));
}

TEST_F(IpTaggingFilterTest, RetainAlternateHeaderWhenUnmatchedAndAppendForwarded) {
const std::string internal_request_yaml = R"EOF(
request_type: internal
ip_tag_header: x-envoy-optional-header
ip_tag_header_action: APPEND_FORWARD
ip_tags:
- ip_tag_name: internal_request_with_optional_header
ip_list:
- {address_prefix: 1.2.3.4, prefix_len: 32}
)EOF";

initializeFilter(internal_request_yaml);

Http::TestRequestHeaderMapImpl request_headers{{"x-envoy-internal", "true"},
{"x-envoy-optional-header", "foo"}};
Network::Address::InstanceConstSharedPtr remote_address =
Network::Utility::parseInternetAddressNoThrow("1.2.3.5");
filter_callbacks_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(
remote_address);

EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false));
EXPECT_EQ("foo", request_headers.get_("x-envoy-optional-header"));

EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data_, false));
Http::TestRequestTrailerMapImpl request_trailers;
EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers));
}

TEST_F(IpTaggingFilterTest, NestedPrefixes) {
const std::string duplicate_request_yaml = R"EOF(
request_type: both
Expand Down