diff --git a/CODEOWNERS b/CODEOWNERS index 62327d48ac91..10a2a8f4e8ee 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -52,7 +52,7 @@ extensions/filters/common/original_src @snowp @klarose # adaptive concurrency limit extension. /*/extensions/filters/http/adaptive_concurrency @tonya11en @mattklein123 # http inspector -/*/extensions/filters/listener/http_inspector @crazyxy @PiotrSikora @lizan +/*/extensions/filters/listener/http_inspector @yxue @PiotrSikora @lizan # attribute context /*/extensions/filters/common/expr @kyessenov @yangminzhu # webassembly common extension @@ -68,6 +68,7 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/http/ext_authz @gsagula @dio /*/extensions/filters/http/grpc_web @fengli79 @lizan /*/extensions/filters/http/squash @yuval-k @alyssawilk +/*/extensions/filters/http/alpn @yxue @lizan /*/extensions/filters/common/ext_authz @gsagula @dio /*/extensions/filters/common/original_src @klarose @snowp /*/extensions/filters/listener/tls_inspector @piotrsikora @htuch diff --git a/api/docs/BUILD b/api/docs/BUILD index 31ed1ee5bae6..0af4e5abbb6f 100644 --- a/api/docs/BUILD +++ b/api/docs/BUILD @@ -30,6 +30,7 @@ proto_library( "//envoy/config/filter/accesslog/v2:accesslog", "//envoy/config/filter/dubbo/router/v2alpha1:router", "//envoy/config/filter/fault/v2:fault", + "//envoy/config/filter/http/alpn/v2alpha:alpn", "//envoy/config/filter/http/buffer/v2:buffer", "//envoy/config/filter/http/csrf/v2:csrf", "//envoy/config/filter/http/dynamic_forward_proxy/v2alpha:dynamic_forward_proxy", diff --git a/api/envoy/config/filter/http/alpn/v2alpha/BUILD b/api/envoy/config/filter/http/alpn/v2alpha/BUILD new file mode 100644 index 000000000000..89e6b3012340 --- /dev/null +++ b/api/envoy/config/filter/http/alpn/v2alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "alpn", + srcs = ["alpn.proto"], +) diff --git a/api/envoy/config/filter/http/alpn/v2alpha/alpn.proto b/api/envoy/config/filter/http/alpn/v2alpha/alpn.proto new file mode 100644 index 000000000000..9e05cfa33b43 --- /dev/null +++ b/api/envoy/config/filter/http/alpn/v2alpha/alpn.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package envoy.config.filter.http.alpn.v2alpha; + +option java_outer_classname = "AlpnProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.alpn.v2alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Alpn Filter] + +// The filter overrides the ALPN in TLS context for upstream connections. +message FilterConfig { + // Downstream protocols + enum Protocol { + HTTP10 = 0; + HTTP11 = 1; + HTTP2 = 2; + } + + message Entry { + // Downstream protocol + Protocol downstream_protocol = 1; + + // Application protocols override. + repeated string alpn = 2 [(validate.rules).repeated = {min_items: 1}]; + } + + repeated Entry alpn_override = 1 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/api/envoy/config/filter/http/alpn/v3alpha/BUILD b/api/envoy/config/filter/http/alpn/v3alpha/BUILD new file mode 100644 index 000000000000..89e6b3012340 --- /dev/null +++ b/api/envoy/config/filter/http/alpn/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "alpn", + srcs = ["alpn.proto"], +) diff --git a/api/envoy/config/filter/http/alpn/v3alpha/alpn.proto b/api/envoy/config/filter/http/alpn/v3alpha/alpn.proto new file mode 100644 index 000000000000..a1355c4342a6 --- /dev/null +++ b/api/envoy/config/filter/http/alpn/v3alpha/alpn.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package envoy.config.filter.http.alpn.v3alpha; + +option java_outer_classname = "AlpnProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.alpn.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Alpn Filter] + +// The filter overrides the ALPN in TLS context for upstream connections. +message FilterConfig { + // Downstream protocols + enum Protocol { + HTTP10 = 0; + HTTP11 = 1; + HTTP2 = 2; + } + + message Entry { + // Downstream protocol + Protocol downstream_protocol = 1; + + // Application protocols override. + repeated string alpn = 2 [(validate.rules).repeated = {min_items: 1}]; + } + + repeated Entry alpn_override = 1 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/docs/root/configuration/http/http_filters/alpn_filter.rst b/docs/root/configuration/http/http_filters/alpn_filter.rst new file mode 100644 index 000000000000..37992cbfbebf --- /dev/null +++ b/docs/root/configuration/http/http_filters/alpn_filter.rst @@ -0,0 +1,33 @@ +.. _config_http_filters_alpn: + +ALPN +==== + +ALPN filter is an HTTP filter which enables Envoy to set ALPN in the TLS context used by upstream +connections. :ref:`alpn_override ` provides +the map from downstream protocol to the ALPN used by upstream connections. + + +Configuration +------------- + +A sample filter configuration could be: + +.. code-block:: yaml + + http_filters: + - name: envoy.filters.http.alpn + config: + alpn_override: + - downstream_protocol: HTTP10 + alpn: ["foo", "bar"] + - downstream_protocol: HTTP11 + alpn: ["baz"] + - downstream_protocol: HTTP2 + alpn: ["qux"] + - name: envoy.router + config: {} + +.. attention:: + + Make sure upstream protocol is the same as downstream protocol. Considering setting :ref:`USE_DOWNSTREAM_PROTOCOL ` \ No newline at end of file diff --git a/docs/root/configuration/http/http_filters/http_filters.rst b/docs/root/configuration/http/http_filters/http_filters.rst index 1ec3af560711..88c28e7dd8cb 100644 --- a/docs/root/configuration/http/http_filters/http_filters.rst +++ b/docs/root/configuration/http/http_filters/http_filters.rst @@ -6,6 +6,7 @@ HTTP filters .. toctree:: :maxdepth: 2 + alpn_filter buffer_filter cors_filter csrf_filter diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 0592bc78c1aa..08b23acc7e52 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -38,6 +38,7 @@ Version history * http: :ref:`AUTO ` codec protocol inference now requires the H2 magic bytes to be the first bytes transmitted by a downstream client. * http: remove h2c upgrade headers for HTTP/1 as h2c upgrades are currently not supported. * http: absolute URL support is now on by default. The prior behavior can be reinstated by setting :ref:`allow_absolute_url ` to false. +* http: added :ref:`ALPN HTTP filter `. * listeners: added :ref:`continue_on_listener_filters_timeout ` to configure whether a listener will still create a connection when listener filters time out. * listeners: added :ref:`HTTP inspector listener filter `. * lua: extended `httpCall()` and `respond()` APIs to accept headers with entry values that can be a string or table of strings. diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index fd78ee5b7811..360d8381cca7 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -32,6 +32,7 @@ EXTENSIONS = { # "envoy.filters.http.adaptive_concurrency": "//source/extensions/filters/http/adaptive_concurrency:config", + "envoy.filters.http.alpn": "//source/extensions/filters/http/alpn:config", "envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config", "envoy.filters.http.cors": "//source/extensions/filters/http/cors:config", "envoy.filters.http.csrf": "//source/extensions/filters/http/csrf:config", diff --git a/source/extensions/filters/http/alpn/BUILD b/source/extensions/filters/http/alpn/BUILD new file mode 100644 index 000000000000..a21b9cf0bdb7 --- /dev/null +++ b/source/extensions/filters/http/alpn/BUILD @@ -0,0 +1,36 @@ +licenses(["notice"]) # Apache 2 + +# A filter for overriding application protocols. + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":alpn_filter_lib", + "//include/envoy/registry", + "//include/envoy/server:filter_config_interface", + "//source/extensions/filters/http:well_known_names", + "//source/extensions/filters/http/common:factory_base_lib", + ], +) + +envoy_cc_library( + name = "alpn_filter_lib", + srcs = ["alpn_filter.cc"], + hdrs = ["alpn_filter.h"], + deps = [ + "//include/envoy/http:filter_interface", + "//source/common/network:application_protocol_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy_api//envoy/config/filter/http/alpn/v2alpha:alpn_cc", + ], +) diff --git a/source/extensions/filters/http/alpn/alpn_filter.cc b/source/extensions/filters/http/alpn/alpn_filter.cc new file mode 100644 index 000000000000..093245a51939 --- /dev/null +++ b/source/extensions/filters/http/alpn/alpn_filter.cc @@ -0,0 +1,63 @@ +#include "extensions/filters/http/alpn/alpn_filter.h" + +#include "common/network/application_protocol.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Alpn { + +AlpnFilterConfig::AlpnFilterConfig( + const envoy::config::filter::http::alpn::v2alpha::FilterConfig& proto_config) { + for (const auto& pair : proto_config.alpn_override()) { + std::vector application_protocols; + for (const auto& protocol : pair.alpn()) { + application_protocols.push_back(protocol); + } + + alpn_override_.insert( + {getHttpProtocol(pair.downstream_protocol()), std::move(application_protocols)}); + } +} + +Http::Protocol AlpnFilterConfig::getHttpProtocol( + const envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol& protocol) { + switch (protocol) { + case envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP10: + return Http::Protocol::Http10; + case envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP11: + return Http::Protocol::Http11; + case envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP2: + return Http::Protocol::Http2; + default: + // will not reach here. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } +} + +Http::FilterHeadersStatus AlpnFilter::decodeHeaders(Http::HeaderMap&, bool) { + auto protocol = decoder_callbacks_->streamInfo().protocol(); + if (protocol.has_value()) { + auto alpn_override = config_->getAlpnOverride()[protocol.value()]; + if (!alpn_override.empty()) { + decoder_callbacks_->streamInfo().filterState().setData( + Network::ApplicationProtocols::key(), + std::make_unique(alpn_override), + Envoy::StreamInfo::FilterState::StateType::ReadOnly); + } else { + ENVOY_LOG(debug, "alpn override is empty"); + } + } else { + ENVOY_LOG(debug, "downstream protocol is not set"); + } + + return Http::FilterHeadersStatus::Continue; +} + +} // namespace Alpn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/filters/http/alpn/alpn_filter.h b/source/extensions/filters/http/alpn/alpn_filter.h new file mode 100644 index 000000000000..366f3e6d920b --- /dev/null +++ b/source/extensions/filters/http/alpn/alpn_filter.h @@ -0,0 +1,46 @@ +#pragma once + +#include "envoy/config/filter/http/alpn/v2alpha/alpn.pb.h" + +#include "extensions/filters/http/common/pass_through_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Alpn { + +using AlpnOverride = absl::flat_hash_map>; + +class AlpnFilterConfig { +public: + AlpnFilterConfig() = default; + + explicit AlpnFilterConfig( + const envoy::config::filter::http::alpn::v2alpha::FilterConfig& proto_config); + + AlpnOverride& getAlpnOverride() { return alpn_override_; } + +private: + Http::Protocol getHttpProtocol( + const envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol& protocol); + + AlpnOverride alpn_override_; +}; + +using AlpnFilterConfigSharedPtr = std::shared_ptr; + +class AlpnFilter : public Http::PassThroughDecoderFilter, Logger::Loggable { +public: + explicit AlpnFilter(const AlpnFilterConfigSharedPtr& config) : config_(config) {} + + // Http::PassThroughDecoderFilter + Http::FilterHeadersStatus decodeHeaders(Http::HeaderMap& headers, bool end_stream) override; + +private: + const AlpnFilterConfigSharedPtr config_; +}; + +} // namespace Alpn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/config.cc b/source/extensions/filters/http/alpn/config.cc new file mode 100644 index 000000000000..163d4ca9ac50 --- /dev/null +++ b/source/extensions/filters/http/alpn/config.cc @@ -0,0 +1,27 @@ +#include "extensions/filters/http/alpn/config.h" + +#include "extensions/filters/http/alpn/alpn_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Alpn { + +Http::FilterFactoryCb AlpnConfigFactory::createFilterFactoryFromProtoTyped( + const envoy::config::filter::http::alpn::v2alpha::FilterConfig& proto_config, + const std::string&, Server::Configuration::FactoryContext&) { + AlpnFilterConfigSharedPtr filter_config{std::make_shared(proto_config)}; + return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_unique(filter_config)); + }; +} + +/** + * Static registration for the alpn override filter. @see RegisterFactory. + */ +REGISTER_FACTORY(AlpnConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace Alpn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/alpn/config.h b/source/extensions/filters/http/alpn/config.h new file mode 100644 index 000000000000..e4b1c64638aa --- /dev/null +++ b/source/extensions/filters/http/alpn/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/config/filter/http/alpn/v2alpha/alpn.pb.h" +#include "envoy/config/filter/http/alpn/v2alpha/alpn.pb.validate.h" + +#include "extensions/filters/http/common/factory_base.h" +#include "extensions/filters/http/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Alpn { + +/** + * Config registration for the alpn filter. + */ +class AlpnConfigFactory + : public Common::FactoryBase { +public: + AlpnConfigFactory() : FactoryBase(HttpFilterNames::get().Alpn) {} + + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::config::filter::http::alpn::v2alpha::FilterConfig& proto_config, + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; +}; + +} // namespace Alpn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/well_known_names.h b/source/extensions/filters/http/well_known_names.h index 82341e13739f..99693a06353b 100644 --- a/source/extensions/filters/http/well_known_names.h +++ b/source/extensions/filters/http/well_known_names.h @@ -60,6 +60,8 @@ class HttpFilterNameValues { const std::string OriginalSrc = "envoy.filters.http.original_src"; // Dynamic forward proxy filter const std::string DynamicForwardProxy = "envoy.filters.http.dynamic_forward_proxy"; + // ALPN filter + const std::string Alpn = "envoy.filters.http.alpn"; // Converts names from v1 to v2 const Config::V1Converter v1_converter_; diff --git a/test/extensions/filters/http/alpn/BUILD b/test/extensions/filters/http/alpn/BUILD new file mode 100644 index 000000000000..f35789fa39ff --- /dev/null +++ b/test/extensions/filters/http/alpn/BUILD @@ -0,0 +1,39 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.filters.http.alpn", + deps = [ + "//source/extensions/filters/http/alpn:config", + "//test/mocks/server:server_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/filter/http/alpn/v2alpha:alpn_cc", + ], +) + +envoy_extension_cc_test( + name = "alpn_filter_test", + srcs = ["alpn_filter_test.cc"], + extension_name = "envoy.filters.http.alpn", + deps = [ + "//source/extensions/filters/http/alpn:alpn_filter_lib", + "//test/mocks:common_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/network:network_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/filter/http/alpn/v2alpha:alpn_cc", + ], +) diff --git a/test/extensions/filters/http/alpn/alpn_filter_test.cc b/test/extensions/filters/http/alpn/alpn_filter_test.cc new file mode 100644 index 000000000000..91eac8523726 --- /dev/null +++ b/test/extensions/filters/http/alpn/alpn_filter_test.cc @@ -0,0 +1,134 @@ +#include "envoy/config/filter/http/alpn/v2alpha/alpn.pb.h" + +#include "common/network/application_protocol.h" +#include "common/network/socket_option_impl.h" +#include "common/network/utility.h" + +#include "extensions/filters/http/alpn/alpn_filter.h" + +#include "test/mocks/buffer/mocks.h" +#include "test/mocks/common.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/printers.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Alpn { +namespace { + +class AlpnFilterTest : public testing::Test { +public: + std::unique_ptr makeDefaultFilter() { + auto default_config = std::make_shared(); + auto filter = std::make_unique(default_config); + filter->setDecoderFilterCallbacks(callbacks_); + return filter; + } + + std::unique_ptr makeAlpnOverrideFilter(const AlpnOverride& alpn) { + envoy::config::filter::http::alpn::v2alpha::FilterConfig proto_config; + for (const auto& p : alpn) { + envoy::config::filter::http::alpn::v2alpha::FilterConfig_Entry entry; + entry.set_downstream_protocol(getProtocol(p.first)); + for (const auto& v : p.second) { + entry.add_alpn(v); + } + proto_config.mutable_alpn_override()->Add(std::move(entry)); + } + + auto config = std::make_shared(proto_config); + auto filter = std::make_unique(config); + filter->setDecoderFilterCallbacks(callbacks_); + return filter; + } + +protected: + envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol + getProtocol(Http::Protocol protocol) { + switch (protocol) { + case Http::Protocol::Http10: + return envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP10; + case Http::Protocol::Http11: + return envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP11; + case Http::Protocol::Http2: + return envoy::config::filter::http::alpn::v2alpha::FilterConfig::Protocol:: + FilterConfig_Protocol_HTTP2; + default: + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + } + + NiceMock callbacks_; + Http::TestHeaderMapImpl headers_; +}; + +TEST_F(AlpnFilterTest, NoDownstreamProtocol) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + auto filter = makeDefaultFilter(); + EXPECT_CALL(stream_info, filterState()).Times(0); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); +} + +TEST_F(AlpnFilterTest, NoAlpnOverride) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + auto filter = makeDefaultFilter(); + EXPECT_CALL(stream_info, protocol()).WillOnce(Return(Http::Protocol::Http10)); + EXPECT_CALL(stream_info, filterState()).Times(0); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); +} + +TEST_F(AlpnFilterTest, NoMatchedAlpnOverride) { + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + AlpnOverride alpn = {{Http::Protocol::Http11, {"foo", "bar"}}}; + auto filter = makeAlpnOverrideFilter(alpn); + EXPECT_CALL(stream_info, protocol()).WillOnce(Return(Http::Protocol::Http10)); + EXPECT_CALL(stream_info, filterState()).Times(0); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); +} + +TEST_F(AlpnFilterTest, OverrideAlpn) { + + NiceMock stream_info; + ON_CALL(callbacks_, streamInfo()).WillByDefault(ReturnRef(stream_info)); + AlpnOverride alpn = {{Http::Protocol::Http10, {"foo", "bar"}}, + {Http::Protocol::Http11, {"baz"}}, + {Http::Protocol::Http2, {"qux"}}}; + auto filter = makeAlpnOverrideFilter(alpn); + + auto protocols = {Http::Protocol::Http10, Http::Protocol::Http11, Http::Protocol::Http2}; + for (const auto p : protocols) { + EXPECT_CALL(stream_info, protocol()).WillOnce(Return(p)); + + Envoy::StreamInfo::FilterStateImpl filter_state; + EXPECT_CALL(stream_info, filterState()).WillOnce(ReturnRef(filter_state)); + EXPECT_EQ(filter->decodeHeaders(headers_, false), Http::FilterHeadersStatus::Continue); + EXPECT_TRUE( + filter_state.hasData(Network::ApplicationProtocols::key())); + auto alpn_override = + filter_state + .getDataReadOnly(Network::ApplicationProtocols::key()) + .value(); + + EXPECT_EQ(alpn_override, alpn[p]); + } +} + +} // namespace +} // namespace Alpn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/alpn/config_test.cc b/test/extensions/filters/http/alpn/config_test.cc new file mode 100644 index 000000000000..78f269ff3380 --- /dev/null +++ b/test/extensions/filters/http/alpn/config_test.cc @@ -0,0 +1,50 @@ +#include "envoy/config/filter/http/alpn/v2alpha/alpn.pb.h" +#include "envoy/config/filter/http/alpn/v2alpha/alpn.pb.validate.h" + +#include "extensions/filters/http/alpn/alpn_filter.h" +#include "extensions/filters/http/alpn/config.h" + +#include "test/mocks/server/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Alpn { +namespace { + +TEST(AlpnFilterConfigTest, OverrideAlpn) { + const std::string yaml = R"EOF( + alpn_override: + - downstream_protocol: HTTP10 + alpn: ["foo", "bar"] + - downstream_protocol: HTTP11 + alpn: ["baz"] + - downstream_protocol: HTTP2 + alpn: ["qux"] + )EOF"; + + envoy::config::filter::http::alpn::v2alpha::FilterConfig proto_config; + TestUtility::loadFromYaml(yaml, proto_config); + AlpnConfigFactory factory; + NiceMock context; + Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, "stats", context); + Http::MockFilterChainFactoryCallbacks filter_callback; + Http::StreamDecoderFilterSharedPtr added_filter; + EXPECT_CALL(filter_callback, addStreamDecoderFilter(_)) + .WillOnce(Invoke([&added_filter](Http::StreamDecoderFilterSharedPtr filter) { + added_filter = std::move(filter); + })); + + cb(filter_callback); + EXPECT_NE(dynamic_cast(added_filter.get()), nullptr); +} + +} // namespace +} // namespace Alpn +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file