diff --git a/api/envoy/type/matcher/v3/network_inputs.proto b/api/envoy/type/matcher/v3/network_inputs.proto new file mode 100644 index 000000000000..2fa0895329f5 --- /dev/null +++ b/api/envoy/type/matcher/v3/network_inputs.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +package envoy.type.matcher.v3; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.type.matcher.v3"; +option java_outer_classname = "NetworkInputsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3;matcherv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Common Network Matching Inputs] + +// Specifies that matching should be performed by the destination IP address. +message DestinationIPInput { +} + +// Specifies that matching should be performed by the destination port. +message DestinationPortInput { +} + +// Specifies that matching should be performed by the source IP address. +message SourceIPInput { +} + +// Specifies that matching should be performed by the source port. +message SourcePortInput { +} + +// Input that matches by the directly connected source IP address (this +// will only be different from the source IP address when using a listener +// filter that overrides the source address, such as the :ref:`Proxy Protocol +// listener filter `). +message DirectSourceIPInput { +} + +// Input that matches by the source IP type. +// Specifies the source IP match type. The values include: +// +// * ``local`` - matches a connection originating from the same host, +message SourceTypeInput { +} + +// Input that matches by the requested server name (e.g. SNI in TLS), when detected by one of the listener filters. +// +// :ref:`TLS Inspector ` provides the requested server name based on SNI. +message ServerNameInput { +} + +// Input that matches by the transport protocol, when +// detected by one of the listener filters. +// +// Suggested values include: +// +// * ``raw_buffer`` - default, used when no transport protocol is detected, +// * ``tls`` - set by :ref:`envoy.filters.listener.tls_inspector ` +// when TLS protocol is detected. +message TransportProtocolInput { +} + +// List of comma-separated requested application protocols, when detected by one of the listener filters. +// +// Suggested values in the list include: +// +// * ``http/1.1`` - set by :ref:`envoy.filters.listener.tls_inspector +// ` and :ref:`envoy.filters.listener.http_inspector +// `, +// * ``h2`` - set by :ref:`envoy.filters.listener.tls_inspector ` +// * ``h2c`` - set by :ref:`envoy.filters.listener.http_inspector ` +// +// .. attention:: +// +// Currently, :ref:`TLS Inspector ` provides +// application protocol detection based on the requested +// `ALPN `_ values. +// +// However, the use of ALPN is pretty much limited to the HTTP/2 traffic on the Internet, +// and matching on values other than ``h2`` is going to lead to a lot of false negatives, +// unless all connecting clients are known to use ALPN. +message ApplicationProtocolInput { +} diff --git a/envoy/network/filter.h b/envoy/network/filter.h index 16a5de4c2e3e..a41a76cad45a 100644 --- a/envoy/network/filter.h +++ b/envoy/network/filter.h @@ -525,6 +525,10 @@ class FilterChainFactory { class MatchingData { public: static absl::string_view name() { return "network"; } + + virtual ~MatchingData() = default; + + virtual const ConnectionSocket& socket() const PURE; }; } // namespace Network diff --git a/source/common/network/matching/BUILD b/source/common/network/matching/BUILD new file mode 100644 index 000000000000..2611c953b2d7 --- /dev/null +++ b/source/common/network/matching/BUILD @@ -0,0 +1,30 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "data_impl_lib", + hdrs = ["data_impl.h"], + deps = [ + "//envoy/network:filter_interface", + ], +) + +envoy_cc_library( + name = "inputs_lib", + srcs = ["inputs.cc"], + hdrs = ["inputs.h"], + deps = [ + "//envoy/matcher:matcher_interface", + "//envoy/network:filter_interface", + "//envoy/registry", + "//source/common/network:utility_lib", + "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", + ], +) diff --git a/source/common/network/matching/data_impl.h b/source/common/network/matching/data_impl.h new file mode 100644 index 000000000000..bf362f0974a6 --- /dev/null +++ b/source/common/network/matching/data_impl.h @@ -0,0 +1,27 @@ +#pragma once + +#include "envoy/network/filter.h" + +namespace Envoy { +namespace Network { +namespace Matching { + +/** + * Implementation of Network::MatchingData, providing connection-level data to + * the match tree. + */ +class MatchingDataImpl : public MatchingData { +public: + static absl::string_view name() { return "network"; } + + void onSocket(const ConnectionSocket& socket) { socket_ = &socket; } + + virtual const ConnectionSocket& socket() { return *socket_; } + +private: + const ConnectionSocket* socket_{}; +}; + +} // namespace Matching +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/matching/inputs.cc b/source/common/network/matching/inputs.cc new file mode 100644 index 000000000000..ac321fc1f130 --- /dev/null +++ b/source/common/network/matching/inputs.cc @@ -0,0 +1,99 @@ +#include "source/common/network/matching/inputs.h" + +#include "envoy/registry/registry.h" + +#include "source/common/network/utility.h" + +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace Network { +namespace Matching { + +Matcher::DataInputGetResult DestinationIPInput::get(const MatchingData& data) const { + const auto& address = data.socket().connectionInfoProvider().localAddress(); + if (address->type() != Network::Address::Type::Ip) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + address->ip()->addressAsString()}; +} + +Matcher::DataInputGetResult DestinationPortInput::get(const MatchingData& data) const { + const auto& address = data.socket().connectionInfoProvider().localAddress(); + if (address->type() != Network::Address::Type::Ip) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + absl::StrCat(address->ip()->port())}; +} + +Matcher::DataInputGetResult SourceIPInput::get(const MatchingData& data) const { + const auto& address = data.socket().connectionInfoProvider().remoteAddress(); + if (address->type() != Network::Address::Type::Ip) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + address->ip()->addressAsString()}; +} + +Matcher::DataInputGetResult SourcePortInput::get(const MatchingData& data) const { + const auto& address = data.socket().connectionInfoProvider().remoteAddress(); + if (address->type() != Network::Address::Type::Ip) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + absl::StrCat(address->ip()->port())}; +} + +Matcher::DataInputGetResult DirectSourceIPInput::get(const MatchingData& data) const { + const auto& address = data.socket().connectionInfoProvider().directRemoteAddress(); + if (address->type() != Network::Address::Type::Ip) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + address->ip()->addressAsString()}; +} + +Matcher::DataInputGetResult SourceTypeInput::get(const MatchingData& data) const { + const bool is_local_connection = Network::Utility::isSameIpOrLoopback(data.socket()); + if (is_local_connection) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, "local"}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; +} + +Matcher::DataInputGetResult ServerNameInput::get(const MatchingData& data) const { + if (!data.socket().requestedServerName().empty()) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + std::string(data.socket().requestedServerName())}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; +} + +Matcher::DataInputGetResult TransportProtocolInput::get(const MatchingData& data) const { + if (!data.socket().detectedTransportProtocol().empty()) { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + std::string(data.socket().detectedTransportProtocol())}; + } + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}; +} + +Matcher::DataInputGetResult ApplicationProtocolInput::get(const MatchingData& data) const { + return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, + absl::StrJoin(data.socket().requestedApplicationProtocols(), ",")}; +} + +REGISTER_FACTORY(DestinationIPInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(DestinationPortInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(SourceIPInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(SourcePortInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(DirectSourceIPInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(SourceTypeInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(ServerNameInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(TransportProtocolInputFactory, Matcher::DataInputFactory); +REGISTER_FACTORY(ApplicationProtocolInputFactory, Matcher::DataInputFactory); + +} // namespace Matching +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/matching/inputs.h b/source/common/network/matching/inputs.h new file mode 100644 index 000000000000..83aeba60411a --- /dev/null +++ b/source/common/network/matching/inputs.h @@ -0,0 +1,176 @@ +#pragma once + +#include "envoy/matcher/matcher.h" +#include "envoy/network/filter.h" +#include "envoy/type/matcher/v3/network_inputs.pb.h" +#include "envoy/type/matcher/v3/network_inputs.pb.validate.h" + +namespace Envoy { +namespace Network { +namespace Matching { + +class DestinationIPInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class DestinationIPInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "destination-ip"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class DestinationPortInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class DestinationPortInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "destination-port"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class SourceIPInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class SourceIPInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "source-ip"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class SourcePortInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class SourcePortInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "source-port"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class DirectSourceIPInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class DirectSourceIPInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "direct-source-ip"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class SourceTypeInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class SourceTypeInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "source-type"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class ServerNameInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class ServerNameInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "server-name"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class TransportProtocolInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class TransportProtocolInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "transport-protocol"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +class ApplicationProtocolInput : public Matcher::DataInput { +public: + Matcher::DataInputGetResult get(const MatchingData& data) const override; +}; + +class ApplicationProtocolInputFactory : public Matcher::DataInputFactory { +public: + std::string name() const override { return "application-protocol"; } + + Matcher::DataInputFactoryCb + createDataInputFactoryCb(const Protobuf::Message&, ProtobufMessage::ValidationVisitor&) override { + return []() { return std::make_unique(); }; + }; + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +} // namespace Matching +} // namespace Network +} // namespace Envoy