Skip to content
Merged
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,7 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp
/*/extensions/filters/http/kill_request @qqustc @htuch
# Rate limit expression descriptor
/*/extensions/rate_limit_descriptors/expr @kyessenov @lizan
# hash input matcher
/*/extensions/matching/input_matchers/consistent_hashing @snowp @donyu
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

# user space socket pair and event
/*/extensions/io_socket/user_space @lambdai @antoniovicente
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ proto_library(
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
"//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg",
"//envoy/extensions/network/socket_interface/v3:pkg",
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
"//envoy/extensions/resource_monitors/fixed_heap/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions api/envoy/config/common/matcher/v3/matcher.proto
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ message Matcher {
type.matcher.v3.StringMatcher value_match = 2;

// Extension for custom matching logic.
// [#extension-category: envoy.matching.input_matchers]
core.v3.TypedExtensionConfig custom_match = 3;
}
}
Expand Down
1 change: 1 addition & 0 deletions api/envoy/config/common/matcher/v4alpha/matcher.proto

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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 = ["@com_github_cncf_udpa//udpa/annotations:pkg"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
syntax = "proto3";

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

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

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

// [#protodoc-title: Consistent Hashing Matcher]
// [#extension: envoy.matching.input_matchers.consistent_hashing]

// The consistent hashing matchers computes a consistent hash from the input and matches if the resulting hash
// is within the configured threshold.
// More specifically, this matcher evaluates to true if hash(input, seed) % modulo >= threshold.
// Note that the consistency of the match result relies on the internal hash function (xxhash) remaining
// unchanged. While this is unlikely to happen intentionally, this could cause inconsistent match results
// between deployments.
message ConsistentHashing {
// The threshold the resulting hash must be over in order for this matcher to evaluate to true.
// This value must be below the configured modulo value.
// Setting this to 0 is equivalent to this matcher always matching.
uint32 threshold = 1;

// The value to use for the modulus in the calculation. This effectively bounds the hash output,
// specifying the range of possible values.
// This value must be above the configured threshold.
uint32 modulo = 2 [(validate.rules).uint32 = {gt: 0}];

// Optional seed passed through the hash function. This allows using additional information when computing
// the hash value: by changing the seed value, a different partition of matching and non-matching inputs will
// be created that remains consistent for that seed value.
uint64 seed = 3;
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ proto_library(
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
"//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg",
"//envoy/extensions/network/socket_interface/v3:pkg",
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
"//envoy/extensions/resource_monitors/fixed_heap/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions bazel/envoy_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ EXTENSION_CATEGORIES = [
"envoy.health_checkers",
"envoy.internal_redirect_predicates",
"envoy.io_socket",
"envoy.matching.input_matchers",
"envoy.rate_limit_descriptors",
"envoy.resource_monitors",
"envoy.retry_host_predicates",
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v3/common_messages/common_messages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ Common messages
../extensions/common/matching/v3/extension_matcher.proto
../extensions/filters/common/dependency/v3/dependency.proto
../extensions/filters/common/matcher/action/v3/skip_action.proto
../extensions/matching/input_matchers/consistent_hashing/v3/consistent_hashing.proto
2 changes: 1 addition & 1 deletion generated_api_shadow/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ proto_library(
"//envoy/config/resource_monitor/injected_resource/v2alpha:pkg",
"//envoy/config/retry/omit_canary_hosts/v2:pkg",
"//envoy/config/retry/previous_hosts/v2:pkg",
"//envoy/config/retry/previous_hosts/v3:pkg",
"//envoy/config/route/v3:pkg",
"//envoy/config/tap/v3:pkg",
"//envoy/config/trace/v3:pkg",
Expand Down Expand Up @@ -244,6 +243,7 @@ proto_library(
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
"//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg",
"//envoy/extensions/network/socket_interface/v3:pkg",
"//envoy/extensions/rate_limit_descriptors/expr/v3:pkg",
"//envoy/extensions/resource_monitors/fixed_heap/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.

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.

2 changes: 1 addition & 1 deletion include/envoy/matcher/matcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class InputMatcherFactory : public Config::TypedFactory {
createInputMatcher(const Protobuf::Message& config,
Server::Configuration::FactoryContext& factory_context) PURE;

std::string category() const override { return "envoy.matching.input_matcher"; }
std::string category() const override { return "envoy.matching.input_matchers"; }
};

// The result of retrieving data from a DataInput. As the data is generally made available
Expand Down
6 changes: 6 additions & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ EXTENSIONS = {

"envoy.health_checkers.redis": "//source/extensions/health_checkers/redis:config",

#
# Input Matchers
#

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

#
# HTTP filters
#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 = "consistent_hashing_lib",
hdrs = ["matcher.h"],
deps = [
"//include/envoy/matcher:matcher_interface",
"//include/envoy/upstream:retry_interface",
"//source/common/common:hash_lib",
],
)

envoy_cc_extension(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
category = "envoy.matching.input_matchers",
security_posture = "robust_to_untrusted_downstream",
deps = [
":consistent_hashing_lib",
"//include/envoy/matcher:matcher_interface",
"//include/envoy/registry",
"//include/envoy/server:factory_context_interface",
"@envoy_api//envoy/extensions/matching/input_matchers/consistent_hashing/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "extensions/matching/input_matchers/consistent_hashing/config.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace ConsistentHashing {

Envoy::Matcher::InputMatcherPtr ConsistentHashingConfig::createInputMatcher(
const Protobuf::Message& config, Server::Configuration::FactoryContext& factory_context) {
const auto& consistent_hashing_config =
MessageUtil::downcastAndValidate<const envoy::extensions::matching::input_matchers::
consistent_hashing::v3::ConsistentHashing&>(
config, factory_context.messageValidationVisitor());

if (consistent_hashing_config.threshold() > consistent_hashing_config.modulo()) {
throw EnvoyException(fmt::format("threshold cannot be greater than modulo: {} > {}",
consistent_hashing_config.threshold(),
consistent_hashing_config.modulo()));
}

return std::make_unique<Matcher>(consistent_hashing_config.threshold(),
consistent_hashing_config.modulo(),
consistent_hashing_config.seed());
}
/**
* Static registration for the consistent hashing matcher. @see RegisterFactory.
*/
REGISTER_FACTORY(ConsistentHashingConfig, Envoy::Matcher::InputMatcherFactory);

} // namespace ConsistentHashing
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

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

#include "common/protobuf/utility.h"

#include "extensions/matching/input_matchers/consistent_hashing/matcher.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace ConsistentHashing {

class ConsistentHashingConfig : 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.consistent_hashing"; }

ProtobufTypes::MessagePtr createEmptyConfigProto() override {
return std::make_unique<
envoy::extensions::matching::input_matchers::consistent_hashing::v3::ConsistentHashing>();
}
};
} // namespace ConsistentHashing
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include "envoy/matcher/matcher.h"

#include "common/common/hash.h"

namespace Envoy {
namespace Extensions {
namespace Matching {
namespace InputMatchers {
namespace ConsistentHashing {

class Matcher : public Envoy::Matcher::InputMatcher {
public:
Matcher(uint32_t threshold, uint32_t modulo, uint64_t seed)
: threshold_(threshold), modulo_(modulo), seed_(seed) {}
bool match(absl::optional<absl::string_view> input) override {
// Only match if the value is present.
if (!input) {
return false;
}

// Otherwise, match if (hash(input) % modulo) >= threshold.
return HashUtil::xxHash64(*input, seed_) % modulo_ >= threshold_;
}

private:
const uint32_t threshold_;
const uint32_t modulo_;
const uint64_t seed_;
};
} // namespace ConsistentHashing
} // namespace InputMatchers
} // namespace Matching
} // namespace Extensions
} // namespace Envoy
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.consistent_hashing",
deps = [
"//source/extensions/matching/input_matchers/consistent_hashing:config",
"//test/mocks/server:factory_context_mocks",
],
)

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