diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 70c52010efa0..ebb5b8a01029 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -370,7 +370,7 @@ message WeightedCluster { string runtime_key_prefix = 2; } -// [#next-free-field: 12] +// [#next-free-field: 13] message RouteMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RouteMatch"; @@ -392,6 +392,11 @@ message RouteMatch { google.protobuf.BoolValue validated = 2; } + // [#not-implemented-hide:] + // An extensible message for matching CONNECT requests. + message ConnectMatcher { + } + reserved 5, 3; reserved "regex"; @@ -420,6 +425,16 @@ message RouteMatch { // on :path, etc. The issue with that is it is unclear how to generically deal with query string // stripping. This needs more thought.] type.matcher.v3.RegexMatcher safe_regex = 10 [(validate.rules).message = {required: true}]; + + // [#not-implemented-hide:] + // If this is used as the matcher, the matcher will only match CONNECT requests. + // Note that this will not match HTTP/2 upgrade-style CONNECT requests + // (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + // upgrades. + // This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + // where CONNECT requests may have a path, the path matchers will work if + // there is a path present. + ConnectMatcher connect_matcher = 12; } // Indicates that prefix/path matching should be case insensitive. The default @@ -705,6 +720,13 @@ message RouteAction { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RouteAction.UpgradeConfig"; + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + message ConnectConfig { + // TODO(alyssawilk) add proxy proto configuration here. + } + // The case-insensitive name of this upgrade, e.g. "websocket". // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. @@ -713,6 +735,11 @@ message RouteAction { // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; + + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + ConnectConfig connect_config = 3; } reserved 12, 18, 19, 16, 22, 21, 10; diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index e813b632edb0..4a54ff847063 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -371,7 +371,7 @@ message WeightedCluster { string runtime_key_prefix = 2; } -// [#next-free-field: 12] +// [#next-free-field: 13] message RouteMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RouteMatch"; @@ -393,6 +393,13 @@ message RouteMatch { google.protobuf.BoolValue validated = 2; } + // [#not-implemented-hide:] + // An extensible message for matching CONNECT requests. + message ConnectMatcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.RouteMatch.ConnectMatcher"; + } + reserved 5, 3; reserved "regex"; @@ -421,6 +428,16 @@ message RouteMatch { // on :path, etc. The issue with that is it is unclear how to generically deal with query string // stripping. This needs more thought.] type.matcher.v3.RegexMatcher safe_regex = 10 [(validate.rules).message = {required: true}]; + + // [#not-implemented-hide:] + // If this is used as the matcher, the matcher will only match CONNECT requests. + // Note that this will not match HTTP/2 upgrade-style CONNECT requests + // (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + // upgrades. + // This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + // where CONNECT requests may have a path, the path matchers will work if + // there is a path present. + ConnectMatcher connect_matcher = 12; } // Indicates that prefix/path matching should be case insensitive. The default @@ -706,6 +723,16 @@ message RouteAction { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RouteAction.UpgradeConfig"; + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + message ConnectConfig { + // TODO(alyssawilk) add proxy proto configuration here. + + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.RouteAction.UpgradeConfig.ConnectConfig"; + } + // The case-insensitive name of this upgrade, e.g. "websocket". // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. @@ -714,6 +741,11 @@ message RouteAction { // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; + + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + ConnectConfig connect_config = 3; } reserved 12, 18, 19, 16, 22, 21, 10; diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index 616e76af302e..631aa9af8602 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -373,7 +373,7 @@ message WeightedCluster { string runtime_key_prefix = 2; } -// [#next-free-field: 12] +// [#next-free-field: 13] message RouteMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RouteMatch"; @@ -395,6 +395,11 @@ message RouteMatch { google.protobuf.BoolValue validated = 2; } + // [#not-implemented-hide:] + // An extensible message for matching CONNECT requests. + message ConnectMatcher { + } + reserved 5; // If specified, the route is a prefix rule meaning that the prefix must @@ -419,9 +424,19 @@ message RouteMatch { // stripping. This needs more thought.] repeated HeaderMatcher headers = 6; + // [#not-implemented-hide:] + // If this is used as the matcher, the matcher will only match CONNECT requests. + // Note that this will not match HTTP/2 upgrade-style CONNECT requests + // (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + // upgrades. + // This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + // where CONNECT requests may have a path, the path matchers will work if + // there is a path present. + repeated QueryParameterMatcher query_parameters = 7; + // Indicates that prefix/path matching should be case insensitive. The default // is true. - repeated QueryParameterMatcher query_parameters = 7; + GrpcRouteMatchOptions grpc = 8; // Indicates that the route should additionally match on a runtime key. Every time the route // is considered for a match, it must also fall under the percentage of matches indicated by @@ -439,35 +454,35 @@ message RouteMatch { // integer with the assumption that the value is an integral percentage out of 100. For // instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent // whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. - GrpcRouteMatchOptions grpc = 8; - - // Specifies a set of headers that the route should match on. The router will - // check the request’s headers against all the specified headers in the route - // config. A match will happen if all the headers in the route are present in - // the request with the same values (or based on presence if the value field - // is not in the config). TlsContextMatchOptions tls_context = 11; oneof path_specifier { option (validate.required) = true; + // Specifies a set of headers that the route should match on. The router will + // check the request’s headers against all the specified headers in the route + // config. A match will happen if all the headers in the route are present in + // the request with the same values (or based on presence if the value field + // is not in the config). + string prefix = 1; + // Specifies a set of URL query parameters on which the route should // match. The router will check the query string from the *path* header // against all the specified query parameters. If the number of specified // query parameters is nonzero, they all must match the *path* header's // query string for a match to occur. - string prefix = 1; + string path = 2; // If specified, only gRPC requests will be matched. The router will check // that the content-type header has a application/grpc or one of the various // application/grpc+ values. - string path = 2; + type.matcher.v3.RegexMatcher safe_regex = 10 [(validate.rules).message = {required: true}]; // If specified, the client tls context will be matched against the defined // match options. // // [#next-major-version: unify with RBAC] - type.matcher.v3.RegexMatcher safe_regex = 10 [(validate.rules).message = {required: true}]; + ConnectMatcher connect_matcher = 12; string hidden_envoy_deprecated_regex = 3 [ deprecated = true, @@ -716,6 +731,13 @@ message RouteAction { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.route.RouteAction.UpgradeConfig"; + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + message ConnectConfig { + // TODO(alyssawilk) add proxy proto configuration here. + } + // The case-insensitive name of this upgrade, e.g. "websocket". // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. @@ -724,6 +746,11 @@ message RouteAction { // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; + + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + ConnectConfig connect_config = 3; } reserved 12, 18, 19, 16, 22, 21; diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index e813b632edb0..4a54ff847063 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -371,7 +371,7 @@ message WeightedCluster { string runtime_key_prefix = 2; } -// [#next-free-field: 12] +// [#next-free-field: 13] message RouteMatch { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RouteMatch"; @@ -393,6 +393,13 @@ message RouteMatch { google.protobuf.BoolValue validated = 2; } + // [#not-implemented-hide:] + // An extensible message for matching CONNECT requests. + message ConnectMatcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.RouteMatch.ConnectMatcher"; + } + reserved 5, 3; reserved "regex"; @@ -421,6 +428,16 @@ message RouteMatch { // on :path, etc. The issue with that is it is unclear how to generically deal with query string // stripping. This needs more thought.] type.matcher.v3.RegexMatcher safe_regex = 10 [(validate.rules).message = {required: true}]; + + // [#not-implemented-hide:] + // If this is used as the matcher, the matcher will only match CONNECT requests. + // Note that this will not match HTTP/2 upgrade-style CONNECT requests + // (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + // upgrades. + // This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + // where CONNECT requests may have a path, the path matchers will work if + // there is a path present. + ConnectMatcher connect_matcher = 12; } // Indicates that prefix/path matching should be case insensitive. The default @@ -706,6 +723,16 @@ message RouteAction { option (udpa.annotations.versioning).previous_message_type = "envoy.config.route.v3.RouteAction.UpgradeConfig"; + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + message ConnectConfig { + // TODO(alyssawilk) add proxy proto configuration here. + + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.route.v3.RouteAction.UpgradeConfig.ConnectConfig"; + } + // The case-insensitive name of this upgrade, e.g. "websocket". // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. @@ -714,6 +741,11 @@ message RouteAction { // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; + + // [#not-implemented-hide:] + // Configuration for sending data upstream as a raw data payload. This is used for + // CONNECT requests, when forwarding CONNECT payload as raw TCP. + ConnectConfig connect_config = 3; } reserved 12, 18, 19, 16, 22, 21, 10; diff --git a/include/envoy/router/BUILD b/include/envoy/router/BUILD index 6ed49171af71..b829997d24aa 100644 --- a/include/envoy/router/BUILD +++ b/include/envoy/router/BUILD @@ -66,6 +66,7 @@ envoy_cc_library( "//source/common/protobuf", "//source/common/protobuf:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", ], ) diff --git a/include/envoy/router/router.h b/include/envoy/router/router.h index 13032173d929..39bffffa50d1 100644 --- a/include/envoy/router/router.h +++ b/include/envoy/router/router.h @@ -11,6 +11,7 @@ #include "envoy/access_log/access_log.h" #include "envoy/common/matchers.h" #include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/route/v3/route_components.pb.h" #include "envoy/config/typed_metadata.h" #include "envoy/http/codec.h" #include "envoy/http/codes.h" @@ -825,6 +826,12 @@ class RouteEntry : public ResponseEntry { */ virtual const UpgradeMap& upgradeMap() const PURE; + using ConnectConfig = envoy::config::route::v3::RouteAction::UpgradeConfig::ConnectConfig; + /** + * If present, informs how to handle proxying CONNECT requests on this route. + */ + virtual const absl::optional& connectConfig() const PURE; + /** * @returns the internal redirect action which should be taken on this route. */ diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index b2e40ab397f6..3ef9de94a806 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -30,6 +30,8 @@ const Config::TypedMetadataImpl AsyncStreamImpl::RouteEntryImpl::typed_metadata_({}); const AsyncStreamImpl::NullPathMatchCriterion AsyncStreamImpl::RouteEntryImpl::path_match_criterion_; +const absl::optional + AsyncStreamImpl::RouteEntryImpl::connect_config_nullopt_; const std::list AsyncStreamImpl::NullConfig::internal_only_headers_; AsyncClientImpl::AsyncClientImpl(Upstream::ClusterInfoConstSharedPtr cluster, diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 608b2188dc1b..143fd8c29541 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -268,6 +268,9 @@ class AsyncStreamImpl : public AsyncClient::Stream, const Router::RouteSpecificFilterConfig* perFilterConfig(const std::string&) const override { return nullptr; } + const absl::optional& connectConfig() const override { + return connect_config_nullopt_; + } bool includeAttemptCountInRequest() const override { return false; } bool includeAttemptCountInResponse() const override { return false; } @@ -292,6 +295,7 @@ class AsyncStreamImpl : public AsyncClient::Stream, Router::RouteEntry::UpgradeMap upgrade_map_; const std::string& cluster_name_; absl::optional timeout_; + static const absl::optional connect_config_nullopt_; const std::string route_name_; }; diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 0287a5b6c974..7344c12f45d1 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -386,6 +386,12 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, if (!success) { throw EnvoyException(absl::StrCat("Duplicate upgrade ", upgrade_config.upgrade_type())); } + if (upgrade_config.upgrade_type() == Http::Headers::get().MethodValues.Connect) { + connect_config_ = upgrade_config.connect_config(); + } else if (upgrade_config.has_connect_config()) { + throw EnvoyException(absl::StrCat("Non-CONNECT upgrade type ", upgrade_config.upgrade_type(), + " has ConnectConfig")); + } } if (route.route().has_regex_rewrite()) { @@ -917,6 +923,27 @@ RouteConstSharedPtr RegexRouteEntryImpl::matches(const Http::RequestHeaderMap& h return nullptr; } +ConnectRouteEntryImpl::ConnectRouteEntryImpl( + const VirtualHostImpl& vhost, const envoy::config::route::v3::Route& route, + Server::Configuration::ServerFactoryContext& factory_context, + ProtobufMessage::ValidationVisitor& validator) + : RouteEntryImplBase(vhost, route, factory_context, validator) {} + +void ConnectRouteEntryImpl::rewritePathHeader(Http::RequestHeaderMap& headers, + bool insert_envoy_original_path) const { + const absl::string_view path = Http::PathUtil::removeQueryAndFragment(getPath(headers)); + finalizePathHeader(headers, path, insert_envoy_original_path); +} + +RouteConstSharedPtr ConnectRouteEntryImpl::matches(const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo&, + uint64_t random_value) const { + if (Http::HeaderUtility::isConnect(headers)) { + return clusterEntry(headers, random_value); + } + return nullptr; +} + VirtualHostImpl::VirtualHostImpl(const envoy::config::route::v3::VirtualHost& virtual_host, const ConfigImpl& global_route_config, Server::Configuration::ServerFactoryContext& factory_context, @@ -976,6 +1003,10 @@ VirtualHostImpl::VirtualHostImpl(const envoy::config::route::v3::VirtualHost& vi routes_.emplace_back(new RegexRouteEntryImpl(*this, route, factory_context, validator)); break; } + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kConnectMatcher: { + routes_.emplace_back(new ConnectRouteEntryImpl(*this, route, factory_context, validator)); + break; + } case envoy::config::route::v3::RouteMatch::PathSpecifierCase::PATH_SPECIFIER_NOT_SET: NOT_REACHED_GCOVR_EXCL_LINE; } @@ -1118,9 +1149,8 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const Http::RequestHead // Check for a route that matches the request. for (const RouteEntryImplBaseConstSharedPtr& route : routes_) { - if (!headers.Path()) { - // TODO(alyssawilk) allow specifically for kConnectMatcher routes. - return nullptr; + if (!headers.Path() && !route->supportsPathlessHeaders()) { + continue; } RouteConstSharedPtr route_entry = route->matches(headers, stream_info, random_value); if (nullptr != route_entry) { diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 89add8a903e5..54ed2be8a533 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -53,6 +53,9 @@ class Matchable { virtual RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo& stream_info, uint64_t random_value) const PURE; + + // By default, matchers do not support null Path headers. + virtual bool supportsPathlessHeaders() const { return false; } }; class PerFilterConfigs { @@ -465,6 +468,7 @@ class RouteEntryImplBase : public RouteEntry, bool includeAttemptCountInResponse() const override { return vhost_.includeAttemptCountInResponse(); } + const absl::optional& connectConfig() const override { return connect_config_; } const UpgradeMap& upgradeMap() const override { return upgrade_map_; } InternalRedirectAction internalRedirectAction() const override { return internal_redirect_action_; @@ -494,6 +498,7 @@ class RouteEntryImplBase : public RouteEntry, std::string regex_rewrite_substitution_; const std::string host_rewrite_; bool include_vh_rate_limits_; + absl::optional connect_config_; RouteConstSharedPtr clusterEntry(const Http::HeaderMap& headers, uint64_t random_value) const; @@ -591,6 +596,9 @@ class RouteEntryImplBase : public RouteEntry, bool includeAttemptCountInResponse() const override { return parent_->includeAttemptCountInResponse(); } + const absl::optional& connectConfig() const override { + return parent_->connectConfig(); + } const UpgradeMap& upgradeMap() const override { return parent_->upgradeMap(); } InternalRedirectAction internalRedirectAction() const override { return parent_->internalRedirectAction(); @@ -824,6 +832,29 @@ class RegexRouteEntryImpl : public RouteEntryImplBase { std::string regex_str_; }; +/** + * Route entry implementation for CONNECT requests. + */ +class ConnectRouteEntryImpl : public RouteEntryImplBase { +public: + ConnectRouteEntryImpl(const VirtualHostImpl& vhost, const envoy::config::route::v3::Route& route, + Server::Configuration::ServerFactoryContext& factory_context, + ProtobufMessage::ValidationVisitor& validator); + + // Router::PathMatchCriterion + const std::string& matcher() const override { return EMPTY_STRING; } + PathMatchType matchType() const override { return PathMatchType::None; } + + // Router::Matchable + RouteConstSharedPtr matches(const Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& stream_info, + uint64_t random_value) const override; + + // Router::DirectResponseEntry + void rewritePathHeader(Http::RequestHeaderMap&, bool) const override; + + bool supportsPathlessHeaders() const override { return true; } +}; /** * Wraps the route configuration which matches an incoming request headers to a backend cluster. * This is split out mainly to help with unit testing. diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index e282f6b9ae8c..02772af8eb86 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -441,6 +441,88 @@ TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestLegacyRoutes)) { } } +TEST_F(RouteMatcherTest, TestConnectRoutes) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: connect + domains: + - bat3.com + routes: + - match: + safe_regex: + google_re2: {} + regex: "foobar" + route: + cluster: connect_break + - match: + connect_matcher: + {} + route: + cluster: connect_match + prefix_rewrite: "/rewrote" + - match: + safe_regex: + google_re2: {} + regex: ".*" + route: + cluster: connect_fallthrough +- name: connect2 + domains: + - bat4.com + routes: + - match: + connect_matcher: + {} + redirect: { path_redirect: /new_path } +- name: default + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: instant-server + timeout: 30s + virtual_clusters: + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/users/\\d+/location$" + - name: ":method" + exact_match: POST + name: ulu + )EOF"; + NiceMock stream_info; + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); + + // Connect matching + EXPECT_EQ("connect_match", + config.route(genHeaders("bat3.com", " ", "CONNECT"), 0)->routeEntry()->clusterName()); + EXPECT_EQ( + "connect_match", + config.route(genPathlessHeaders("bat3.com", "CONNECT"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("connect_fallthrough", + config.route(genHeaders("bat3.com", " ", "GET"), 0)->routeEntry()->clusterName()); + + // Prefix rewrite for CONNECT with path (for HTTP/2) + { + Http::TestRequestHeaderMapImpl headers = + genHeaders("bat3.com", "/api/locations?works=true", "CONNECT"); + const RouteEntry* route = config.route(headers, 0)->routeEntry(); + route->finalizeRequestHeaders(headers, stream_info, true); + EXPECT_EQ("/rewrote?works=true", headers.get_(Http::Headers::get().Path)); + } + // Prefix rewrite for CONNECT without path (for non-crashing) + { + Http::TestRequestHeaderMapImpl headers = genPathlessHeaders("bat4.com", "CONNECT"); + const DirectResponseEntry* redirect = config.route(headers, 0)->directResponseEntry(); + ASSERT(redirect != nullptr); + redirect->rewritePathHeader(headers, true); + EXPECT_EQ("http://bat4.com/new_path", redirect->newPath(headers)); + } +} + TEST_F(RouteMatcherTest, TestRoutes) { const std::string yaml = R"EOF( virtual_hosts: @@ -696,7 +778,6 @@ TEST_F(RouteMatcherTest, TestRoutes) { exact_match: POST name: ulu )EOF"; - NiceMock stream_info; TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); @@ -6707,6 +6788,30 @@ name: RetriableStatusCodes EnvoyException, "Duplicate upgrade WebSocket"); } +TEST_F(RouteConfigurationV2, BadConnectConfig) { + const std::string yaml = R"EOF( +name: RetriableStatusCodes +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + upgrade_configs: + - upgrade_type: Websocket + connect_config: {} + enabled: false + )EOF"; + + EXPECT_THROW_WITH_MESSAGE( + TestConfigImpl(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true), + EnvoyException, "Non-CONNECT upgrade type Websocket has ConnectConfig"); +} + // Verifies that we're creating a new instance of the retry plugins on each call instead of always // returning the same one. TEST_F(RouteConfigurationV2, RetryPluginsAreNotReused) { diff --git a/test/mocks/router/mocks.cc b/test/mocks/router/mocks.cc index 5f4f8d487c1e..32e5ce7ba79e 100644 --- a/test/mocks/router/mocks.cc +++ b/test/mocks/router/mocks.cc @@ -97,6 +97,7 @@ MockRouteEntry::MockRouteEntry() { ON_CALL(*this, upgradeMap()).WillByDefault(ReturnRef(upgrade_map_)); ON_CALL(*this, hedgePolicy()).WillByDefault(ReturnRef(hedge_policy_)); ON_CALL(*this, routeName()).WillByDefault(ReturnRef(route_name_)); + ON_CALL(*this, connectConfig()).WillByDefault(ReturnRef(connect_config_)); } MockRouteEntry::~MockRouteEntry() = default; diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 9ed7b8ead74b..3b2ec8ee189a 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -353,6 +353,7 @@ class MockRouteEntry : public RouteEntry { MOCK_METHOD(const RouteSpecificFilterConfig*, perFilterConfig, (const std::string&), (const)); MOCK_METHOD(bool, includeAttemptCountInRequest, (), (const)); MOCK_METHOD(bool, includeAttemptCountInResponse, (), (const)); + MOCK_METHOD(const absl::optional&, connectConfig, (), (const)); MOCK_METHOD(const UpgradeMap&, upgradeMap, (), (const)); MOCK_METHOD(InternalRedirectAction, internalRedirectAction, (), (const)); MOCK_METHOD(uint32_t, maxInternalRedirects, (), (const)); @@ -374,6 +375,7 @@ class MockRouteEntry : public RouteEntry { testing::NiceMock path_match_criterion_; envoy::config::core::v3::Metadata metadata_; UpgradeMap upgrade_map_; + absl::optional connect_config_; }; class MockDecorator : public Decorator {