Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ proto_library(
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
"//envoy/extensions/matching/common_inputs/environment_variable/v3:pkg",
"//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg",
"//envoy/extensions/matching/input_matchers/ip/v3:pkg",
"//envoy/extensions/network/socket_interface/v3:pkg",
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
"//envoy/extensions/request_id/uuid/v3:pkg",
Expand Down
12 changes: 12 additions & 0 deletions api/envoy/extensions/matching/input_matchers/ip/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/config/core/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
31 changes: 31 additions & 0 deletions api/envoy/extensions/matching/input_matchers/ip/v3/ip.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
syntax = "proto3";

package envoy.extensions.matching.input_matchers.ip.v3;

import "envoy/config/core/v3/address.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.matching.input_matchers.ip.v3";
option java_outer_classname = "IpProto";
option java_multiple_files = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: IP matcher]
// [#extension: envoy.matching.input_matchers.ip]

// This input matcher matches IPv4 or IPv6 addresses against a list of CIDR
// ranges. It returns true if and only if the input IP belongs to at least one
// of these CIDR ranges. Internally, it uses a Level-Compressed trie, as
// described in the paper `IP-address lookup using LC-tries
// <https://www.nada.kth.se/~snilsson/publications/IP-address-lookup-using-LC-tries/>`_
// by S. Nilsson and G. Karlsson. For "big" lists of IPs, this matcher is more
// efficient than multiple single IP matcher, that would have a linear cost.
message Ip {
// Match if the IP belongs to any of these CIDR ranges.
repeated config.core.v3.CidrRange cidr_ranges = 1 [(validate.rules).repeated = {min_items: 1}];

// If specified, emits statistics using this prefix.
string stat_prefix = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we include docs that explain what stats are emitted? Normally we'd have a RST page explaining this extension, but I think inlining these docs in the proto file is fine for now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7960aa3

}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ proto_library(
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
"//envoy/extensions/matching/common_inputs/environment_variable/v3:pkg",
"//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg",
"//envoy/extensions/matching/input_matchers/ip/v3:pkg",
"//envoy/extensions/network/socket_interface/v3:pkg",
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
"//envoy/extensions/request_id/uuid/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions generated_api_shadow/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ proto_library(
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
"//envoy/extensions/matching/common_inputs/environment_variable/v3:pkg",
"//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg",
"//envoy/extensions/matching/input_matchers/ip/v3:pkg",
"//envoy/extensions/network/socket_interface/v3:pkg",
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
"//envoy/extensions/request_id/uuid/v3:pkg",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ EXTENSIONS = {
#

"envoy.matching.input_matchers.consistent_hashing": "//source/extensions/matching/input_matchers/consistent_hashing:config",
"envoy.matching.input_matchers.ip": "//source/extensions/matching/input_matchers/ip:config",

#
# Generic Inputs
Expand Down
5 changes: 5 additions & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,11 @@ envoy.matching.input_matchers.consistent_hashing:
- envoy.matching.input_matchers
security_posture: robust_to_untrusted_downstream
status: stable
envoy.matching.input_matchers.ip:
categories:
- envoy.matching.input_matchers
security_posture: robust_to_untrusted_downstream_and_upstream
status: stable
envoy.rate_limit_descriptors.expr:
categories:
- envoy.rate_limit_descriptors
Expand Down
33 changes: 33 additions & 0 deletions source/extensions/matching/input_matchers/ip/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_library(
name = "ip_lib",
srcs = ["matcher.cc"],
hdrs = ["matcher.h"],
deps = [
"//envoy/matcher:matcher_interface",
"//source/common/network:lc_trie_lib",
],
)

envoy_cc_extension(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
deps = [
":ip_lib",
"//envoy/matcher:matcher_interface",
"//envoy/registry",
"//envoy/server:factory_context_interface",
"@envoy_api//envoy/extensions/matching/input_matchers/ip/v3:pkg_cc_proto",
],
)
41 changes: 41 additions & 0 deletions source/extensions/matching/input_matchers/ip/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "source/extensions/matching/input_matchers/ip/config.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace IP {

Envoy::Matcher::InputMatcherPtr
Config::createInputMatcher(const Protobuf::Message& config,
Server::Configuration::FactoryContext& context) {
const auto& ip_config = MessageUtil::downcastAndValidate<
const envoy::extensions::matching::input_matchers::ip::v3::Ip&>(
config, context.messageValidationVisitor());

const auto& cidr_ranges = ip_config.cidr_ranges();
std::vector<Network::Address::CidrRange> ranges;
ranges.reserve(cidr_ranges.size());
for (const auto& cidr_range : cidr_ranges) {
const std::string& address = cidr_range.address_prefix();
const uint32_t prefix_len = cidr_range.prefix_len().value();
const auto range = Network::Address::CidrRange::create(address, prefix_len);
if (!range.isValid()) {
throw EnvoyException(fmt::format("ip range {}/{} is invalid", address, prefix_len));
}
ranges.emplace_back(std::move(range));
}

const auto stat_prefix = ip_config.stat_prefix();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: this incurs an additional copy, either const auto& or just pass this inline

@aguinet aguinet Jun 12, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 551ddcb , by using an explicit string_view

return std::make_unique<Matcher>(std::move(ranges), stat_prefix, context.scope());
}
/**
* Static registration for the consistent hashing matcher. @see RegisterFactory.
*/
REGISTER_FACTORY(Config, Envoy::Matcher::InputMatcherFactory);

} // namespace IP
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
33 changes: 33 additions & 0 deletions source/extensions/matching/input_matchers/ip/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include "envoy/extensions/matching/input_matchers/ip/v3/ip.pb.h"
#include "envoy/extensions/matching/input_matchers/ip/v3/ip.pb.validate.h"
#include "envoy/matcher/matcher.h"
#include "envoy/server/factory_context.h"

#include "source/common/protobuf/utility.h"
#include "source/extensions/matching/input_matchers/ip/matcher.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace IP {

class Config : public Envoy::Matcher::InputMatcherFactory {
public:
Envoy::Matcher::InputMatcherPtr
createInputMatcher(const Protobuf::Message& config,
Server::Configuration::FactoryContext& factory_context) override;

std::string name() const override { return "envoy.matching.matchers.ip"; }

ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<envoy::extensions::matching::input_matchers::ip::v3::Ip>();
}
};
} // namespace IP
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
50 changes: 50 additions & 0 deletions source/extensions/matching/input_matchers/ip/matcher.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "source/extensions/matching/input_matchers/ip/matcher.h"

#include "source/common/network/utility.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace IP {

Matcher::Matcher(std::vector<Network::Address::CidrRange>&& ranges, absl::string_view stat_prefix,
Stats::Scope& stat_scope)
: // We could put "false" instead of "true". What matters is that the IP
// belongs to the trie. We could further optimize the storage of LcTrie in
// this case by implementing an LcTrie<void> specialization that doesn't
// store any associated data.
trie_({{true, std::move(ranges)}}) {
if (!stat_prefix.empty()) {
stats_.emplace(generateStats(stat_prefix, stat_scope));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think its more common to emit stats by default for extensions, so maybe make stats_prefix required so that it's always set? Or is there a good reason to avoid emitting this stat?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've seen this in another extension, so I just made the same :) I have no other rational than this, I'm happy to make it mandatory!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 7960aa3 !

}

MatcherStats Matcher::generateStats(absl::string_view prefix, Stats::Scope& scope) {
return MatcherStats{IP_MATCHER_STATS(POOL_COUNTER_PREFIX(scope, prefix))};
}

bool Matcher::match(absl::optional<absl::string_view> input) {
if (!input) {
return false;
}
const absl::string_view ip_str = *input;
if (ip_str.empty()) {
return false;
}
const auto ip = Network::Utility::parseInternetAddressNoThrow(std::string{ip_str});
if (!ip) {
if (stats_) {
stats_->ip_parsing_failed_.inc();
}
ENVOY_LOG(warn, "IP matcher: unable to parse address '{}'", ip_str);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd suggest dropping down this down to debug or use one of the rate limited loggers, this could be extremely spammy if deployed with bad configuration which would impact proxy performance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Dropped down to debug in 551ddcb

return false;
}
return !trie_.getData(ip).empty();
}

} // namespace IP
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
41 changes: 41 additions & 0 deletions source/extensions/matching/input_matchers/ip/matcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include <vector>

#include "envoy/matcher/matcher.h"
#include "envoy/network/address.h"
#include "envoy/stats/stats_macros.h"

#include "source/common/network/lc_trie.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace IP {

#define IP_MATCHER_STATS(COUNTER) COUNTER(ip_parsing_failed)

struct MatcherStats {
IP_MATCHER_STATS(GENERATE_COUNTER_STRUCT);
};

class Matcher : public Envoy::Matcher::InputMatcher, Logger::Loggable<Logger::Id::filter> {
public:
Matcher(std::vector<Network::Address::CidrRange>&& ranges, absl::string_view stat_prefix,
Stats::Scope& stat_scope);
bool match(absl::optional<absl::string_view> input) override;
absl::optional<const MatcherStats> stats() const { return stats_; }

private:
MatcherStats generateStats(absl::string_view prefix, Stats::Scope& scope);

const Network::LcTrie::LcTrie<bool> trie_;
absl::optional<MatcherStats> stats_;
};

} // namespace IP
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
31 changes: 31 additions & 0 deletions test/extensions/matching/input_matchers/ip/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_package",
)
load(
"//test/extensions:extensions_build_system.bzl",
"envoy_extension_cc_test",
)

licenses(["notice"]) # Apache 2

envoy_package()

envoy_extension_cc_test(
name = "config_test",
srcs = ["config_test.cc"],
extension_name = "envoy.matching.input_matchers.ip",
deps = [
"//source/extensions/matching/input_matchers/ip:config",
"//test/mocks/server:factory_context_mocks",
],
)

envoy_extension_cc_test(
name = "matcher_test",
srcs = ["matcher_test.cc"],
extension_name = "envoy.matching.input_matchers.ip",
deps = [
"//source/extensions/matching/input_matchers/ip:ip_lib",
],
)
Loading