From b2f1a847fd13c2dd7a3558deddd2338758cb0c01 Mon Sep 17 00:00:00 2001 From: Rama Chavali Date: Fri, 15 Mar 2024 06:25:34 +0530 Subject: [PATCH 01/36] minor variable rename (#32868) Signed-off-by: Rama Chavali --- .../envoy_default/http1_header_validator_test.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc b/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc index 8556ec7d1173..e8a6ad73950d 100644 --- a/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc +++ b/test/extensions/http/header_validators/envoy_default/http1_header_validator_test.cc @@ -78,12 +78,12 @@ TEST_F(Http1HeaderValidatorTest, ValidateTransferEncodingInRequest) { TEST_F(Http1HeaderValidatorTest, ValidateTransferEncodingInResponse) { auto uhv = createH1(empty_config); - TestResponseHeaderMapImpl request_headers = makeGoodResponseHeaders(); - request_headers.setCopy(LowerCaseString("transfer-encoding"), "ChuNKeD"); - EXPECT_ACCEPT(uhv->validateResponseHeaders(request_headers)); + TestResponseHeaderMapImpl response_headers = makeGoodResponseHeaders(); + response_headers.setCopy(LowerCaseString("transfer-encoding"), "ChuNKeD"); + EXPECT_ACCEPT(uhv->validateResponseHeaders(response_headers)); - request_headers.setCopy(LowerCaseString("transfer-encoding"), "gzip"); - EXPECT_REJECT_WITH_DETAILS(uhv->validateResponseHeaders(request_headers), + response_headers.setCopy(LowerCaseString("transfer-encoding"), "gzip"); + EXPECT_REJECT_WITH_DETAILS(uhv->validateResponseHeaders(response_headers), "http1.invalid_transfer_encoding"); } From 8b293ba506611eb13da4064427235e2c457ca334 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 15 Mar 2024 03:32:52 +0000 Subject: [PATCH 02/36] fips/build: Add `-fPIC` (#32901) Signed-off-by: Ryan Northey --- bazel/external/boringssl_fips.genrule_cmd | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bazel/external/boringssl_fips.genrule_cmd b/bazel/external/boringssl_fips.genrule_cmd index 46526a9a84de..44f21fc9c515 100755 --- a/bazel/external/boringssl_fips.genrule_cmd +++ b/bazel/external/boringssl_fips.genrule_cmd @@ -119,7 +119,9 @@ rm -rf boringssl/build # Build BoringSSL. cd boringssl -mkdir build && cd build && cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=${HOME}/toolchain -DFIPS=1 -DCMAKE_BUILD_TYPE=Release .. +# Setting -fPIC only affects the compilation of the non-module code in libcrypto.a, +# because the FIPS module itself is already built with -fPIC. +mkdir build && cd build && cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=${HOME}/toolchain -DFIPS=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_FLAGS="-fPIC" .. ninja ninja run_tests ./crypto/crypto_test From 49e8a5970fb0b942b7940895528e1168755b2929 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 15 Mar 2024 01:39:56 -0400 Subject: [PATCH 03/36] logs: removing a hard linkage on file based access logs (#32882) logs: removing a hard linkage on file based access logs Signed-off-by: Alyssa Wilk --- .github/workflows/mobile-perf.yml | 1 + source/server/BUILD | 2 +- source/server/configuration_impl.cc | 14 ++++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/mobile-perf.yml b/.github/workflows/mobile-perf.yml index 807f1eda861b..241199383f08 100644 --- a/.github/workflows/mobile-perf.yml +++ b/.github/workflows/mobile-perf.yml @@ -70,6 +70,7 @@ jobs: source/server/guarddog_impl.h source/server/watchdog_impl.h source/server/options_impl.cc + source/extensions/access_loggers/common/file_access_log_impl.h target: size-current - name: Main size args: >- diff --git a/source/server/BUILD b/source/server/BUILD index f656303df03e..c86be8637fc7 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -54,10 +54,10 @@ envoy_cc_library( "//source/common/network:socket_option_lib", "//source/common/network:utility_lib", "//source/common/protobuf:utility_lib", - "//source/extensions/access_loggers/common:file_access_log_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/metrics/v3:pkg_cc_proto", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", ], ) diff --git a/source/server/configuration_impl.cc b/source/server/configuration_impl.cc index b5c01e101646..7070baf2097d 100644 --- a/source/server/configuration_impl.cc +++ b/source/server/configuration_impl.cc @@ -10,6 +10,7 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "envoy/config/metrics/v3/stats.pb.h" #include "envoy/config/trace/v3/http_tracer.pb.h" +#include "envoy/extensions/access_loggers/file/v3/file.pb.h" #include "envoy/network/connection.h" #include "envoy/runtime/runtime.h" #include "envoy/server/instance.h" @@ -23,7 +24,6 @@ #include "source/common/config/utility.h" #include "source/common/network/socket_option_factory.h" #include "source/common/protobuf/utility.h" -#include "source/extensions/access_loggers/common/file_access_log_impl.h" namespace Envoy { namespace Server { @@ -276,11 +276,13 @@ void InitialImpl::initAdminAccessLog(const envoy::config::bootstrap::v3::Bootstr } if (!admin.access_log_path().empty()) { - Filesystem::FilePathAndType file_info{Filesystem::DestinationType::File, - admin.access_log_path()}; - admin_.access_logs_.emplace_back(new Extensions::AccessLoggers::File::FileAccessLog( - file_info, {}, Formatter::HttpSubstitutionFormatUtils::defaultSubstitutionFormatter(), - factory_context.serverFactoryContext().accessLogManager())); + envoy::extensions::access_loggers::file::v3::FileAccessLog config; + config.mutable_format(); + config.set_path(admin.access_log_path()); + + auto factory = Config::Utility::getFactoryByName( + "envoy.file_access_log"); + admin_.access_logs_.emplace_back(factory->createAccessLogInstance(config, {}, factory_context)); } } From 56ace997da32d1ccdc0f08868f3df8c1abb1f4c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:53:50 +0000 Subject: [PATCH 04/36] build(deps): bump node from `fbdb66d` to `54836e4` in /examples/shared/node (#32836) build(deps): bump node in /examples/shared/node Bumps node from `fbdb66d` to `54836e4`. --- updated-dependencies: - dependency-name: node dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index 3d497d08223a..4ef89898f771 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:21.7-bookworm-slim@sha256:fbdb66de323c6aa010c6afc04dda9a4031d113ebc623588f42633b124b3a5ac6 as node-base +FROM node:21.7-bookworm-slim@sha256:54836e4f9d2442d9dd01a220ba5f4d56d7afcfc86e7c129f9a79a5e4cdbb96b9 as node-base FROM node-base as node-http-auth From 136d7528a318aa85b770b4303c2eb2e7a37b35d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:54:06 +0000 Subject: [PATCH 05/36] build(deps): bump openzipkin/zipkin from `be6eedb` to `8448c6d` in /examples/zipkin (#32839) build(deps): bump openzipkin/zipkin in /examples/zipkin Bumps openzipkin/zipkin from `be6eedb` to `8448c6d`. --- updated-dependencies: - dependency-name: openzipkin/zipkin dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/zipkin/Dockerfile-zipkin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zipkin/Dockerfile-zipkin b/examples/zipkin/Dockerfile-zipkin index 5e3813094aee..a3d2df5247fb 100644 --- a/examples/zipkin/Dockerfile-zipkin +++ b/examples/zipkin/Dockerfile-zipkin @@ -1 +1 @@ -FROM openzipkin/zipkin:latest@sha256:be6eedb41ecae348c1d61abe9b9f22e5af582604fec4ad12e4fa782e99746a6f +FROM openzipkin/zipkin:latest@sha256:8448c6d50247a413ea5d38393307b4a751896bb0fcfb31a21e95ecd194c0b4c7 From ab0be5c989158dd2b8dd0c5f225bba4db2c40163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:54:43 +0000 Subject: [PATCH 06/36] build(deps): bump debian from `d02c76d` to `ccb33c3` in /examples/shared/websocket (#32840) build(deps): bump debian in /examples/shared/websocket Bumps debian from `d02c76d` to `ccb33c3`. --- updated-dependencies: - dependency-name: debian dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/websocket/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/websocket/Dockerfile b/examples/shared/websocket/Dockerfile index 79ab5716f451..eec89e154fc0 100644 --- a/examples/shared/websocket/Dockerfile +++ b/examples/shared/websocket/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim@sha256:d02c76d82364cedca16ba3ed6f9102406fa9fa8833076a609cabf14270f43dfc as websocket-base +FROM debian:bookworm-slim@sha256:ccb33c3ac5b02588fc1d9e4fc09b952e433d0c54d8618d0ee1afadf1f3cf2455 as websocket-base ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ From d77ee908bc3c475efc017b831fc2d8e255c6ab15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:55:04 +0000 Subject: [PATCH 07/36] build(deps): bump debian from `d02c76d` to `ccb33c3` in /examples/shared/golang (#32841) build(deps): bump debian in /examples/shared/golang Bumps debian from `d02c76d` to `ccb33c3`. --- updated-dependencies: - dependency-name: debian dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/golang/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index 5a940c392906..3d06d98e72c2 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim@sha256:d02c76d82364cedca16ba3ed6f9102406fa9fa8833076a609cabf14270f43dfc as os-base +FROM debian:bookworm-slim@sha256:ccb33c3ac5b02588fc1d9e4fc09b952e433d0c54d8618d0ee1afadf1f3cf2455 as os-base RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache From e698a76362eab523968e2eaffcc685bcbf1f9ed6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:56:08 +0000 Subject: [PATCH 08/36] build(deps): bump jaegertracing/all-in-one from `2c8e4a0` to `15b4256` in /examples/shared/jaeger (#32897) build(deps): bump jaegertracing/all-in-one in /examples/shared/jaeger Bumps jaegertracing/all-in-one from `2c8e4a0` to `15b4256`. --- updated-dependencies: - dependency-name: jaegertracing/all-in-one dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/jaeger/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/jaeger/Dockerfile b/examples/shared/jaeger/Dockerfile index 13fb4ff4b21d..996f0b6c83dc 100644 --- a/examples/shared/jaeger/Dockerfile +++ b/examples/shared/jaeger/Dockerfile @@ -1,4 +1,4 @@ -FROM jaegertracing/all-in-one@sha256:2c8e4a0bec794046d92d0487f9abc423c60ebf0e970b832eec1bbb623f11134a +FROM jaegertracing/all-in-one@sha256:15b4256cc70141664c34c7aaa7e261588bbbfba7951393a82a49ff3db9f0f402 HEALTHCHECK \ --interval=1s \ --timeout=1s \ From 3121ef0ea334a8e7dc3e78575941a7ea6f569c05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:56:19 +0000 Subject: [PATCH 09/36] build(deps): bump the examples-local-ratelimit group in /examples/local_ratelimit with 1 update (#32895) build(deps): bump the examples-local-ratelimit group Bumps the examples-local-ratelimit group in /examples/local_ratelimit with 1 update: nginx. Updates `nginx` from `c26ae74` to `6db391d` --- updated-dependencies: - dependency-name: nginx dependency-type: direct:production dependency-group: examples-local-ratelimit ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/local_ratelimit/Dockerfile-nginx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/local_ratelimit/Dockerfile-nginx b/examples/local_ratelimit/Dockerfile-nginx index eff6acc5fa7d..2794de868137 100644 --- a/examples/local_ratelimit/Dockerfile-nginx +++ b/examples/local_ratelimit/Dockerfile-nginx @@ -1 +1 @@ -FROM nginx@sha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107 +FROM nginx@sha256:6db391d1c0cfb30588ba0bf72ea999404f2764febf0f1f196acd5867ac7efa7e From e0b82a7c4a0c2f2022d1d7fea9f11952d82bb1c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:56:29 +0000 Subject: [PATCH 10/36] build(deps): bump postgres from `f58300a` to `4784d9b` in /examples/shared/postgres (#32866) build(deps): bump postgres in /examples/shared/postgres Bumps postgres from `f58300a` to `4784d9b`. --- updated-dependencies: - dependency-name: postgres dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/postgres/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index eb29c21895f4..b01031b6cadb 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:f58300ac8d393b2e3b09d36ea12d7d24ee9440440e421472a300e929ddb63460 +FROM postgres:latest@sha256:4784d9bcd8e603179d03732c9d2cd145c3c90e42f29847a95b5317fb37ac4765 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] From 9e65865e39b2a9a242c0434f3a2de61e73ba4453 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Fri, 15 Mar 2024 08:41:02 -0700 Subject: [PATCH 11/36] Stringmatcher: factory context for http cache filter (#32923) Signed-off-by: Greg Greenway --- .../filters/http/cache/cache_filter.cc | 7 ++- .../filters/http/cache/cache_filter.h | 3 +- .../filters/http/cache/cache_headers_utils.cc | 8 ++- .../filters/http/cache/cache_headers_utils.h | 3 +- .../extensions/filters/http/cache/config.cc | 4 +- test/extensions/filters/http/cache/BUILD | 3 + .../filters/http/cache/cache_filter_test.cc | 18 +++--- .../http/cache/cache_headers_utils_test.cc | 56 ++++++++++++++----- .../http/cache/cacheability_utils_test.cc | 6 +- .../http_cache_implementation_test_common.cc | 6 +- .../http_cache_implementation_test_common.h | 2 + .../filters/http/cache/http_cache_test.cc | 4 +- .../file_system_http_cache_test.cc | 3 +- 13 files changed, 85 insertions(+), 38 deletions(-) diff --git a/source/extensions/filters/http/cache/cache_filter.cc b/source/extensions/filters/http/cache/cache_filter.cc index a920ac3ddbb2..8628a68e23c9 100644 --- a/source/extensions/filters/http/cache/cache_filter.cc +++ b/source/extensions/filters/http/cache/cache_filter.cc @@ -42,10 +42,11 @@ struct CacheResponseCodeDetailValues { using CacheResponseCodeDetails = ConstSingleton; CacheFilter::CacheFilter(const envoy::extensions::filters::http::cache::v3::CacheConfig& config, - const std::string&, Stats::Scope&, TimeSource& time_source, + const std::string&, Stats::Scope&, + Server::Configuration::CommonFactoryContext& context, std::shared_ptr http_cache) - : time_source_(time_source), cache_(http_cache), - vary_allow_list_(config.allowed_vary_headers()) {} + : time_source_(context.timeSource()), cache_(http_cache), + vary_allow_list_(config.allowed_vary_headers(), context) {} void CacheFilter::onDestroy() { filter_state_ = FilterState::Destroyed; diff --git a/source/extensions/filters/http/cache/cache_filter.h b/source/extensions/filters/http/cache/cache_filter.h index cedf5a60ee28..5cac2781fa10 100644 --- a/source/extensions/filters/http/cache/cache_filter.h +++ b/source/extensions/filters/http/cache/cache_filter.h @@ -54,7 +54,8 @@ class CacheFilter : public Http::PassThroughFilter, public std::enable_shared_from_this { public: CacheFilter(const envoy::extensions::filters::http::cache::v3::CacheConfig& config, - const std::string& stats_prefix, Stats::Scope& scope, TimeSource& time_source, + const std::string& stats_prefix, Stats::Scope& scope, + Server::Configuration::CommonFactoryContext& context, std::shared_ptr http_cache); // Http::StreamFilterBase void onDestroy() override; diff --git a/source/extensions/filters/http/cache/cache_headers_utils.cc b/source/extensions/filters/http/cache/cache_headers_utils.cc index 582746215dc2..5c622462c4e7 100644 --- a/source/extensions/filters/http/cache/cache_headers_utils.cc +++ b/source/extensions/filters/http/cache/cache_headers_utils.cc @@ -284,12 +284,14 @@ CacheHeadersUtils::parseCommaDelimitedHeader(const Http::HeaderMap::GetResult& e } VaryAllowList::VaryAllowList( - const Protobuf::RepeatedPtrField& allow_list) { + const Protobuf::RepeatedPtrField& allow_list, + Server::Configuration::CommonFactoryContext& context) { for (const auto& rule : allow_list) { allow_list_.emplace_back( - std::make_unique>( - rule)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + rule, context)); } } diff --git a/source/extensions/filters/http/cache/cache_headers_utils.h b/source/extensions/filters/http/cache/cache_headers_utils.h index 440b58843d1a..5f96d24c54e2 100644 --- a/source/extensions/filters/http/cache/cache_headers_utils.h +++ b/source/extensions/filters/http/cache/cache_headers_utils.h @@ -128,7 +128,8 @@ class VaryAllowList { public: // Parses the allow list from the Cache Config into the object's private allow_list_. VaryAllowList( - const Protobuf::RepeatedPtrField& allow_list); + const Protobuf::RepeatedPtrField& allow_list, + Server::Configuration::CommonFactoryContext& context); // Checks if the headers contain an allowed value in the Vary header. bool allowsHeaders(const Http::ResponseHeaderMap& headers) const; diff --git a/source/extensions/filters/http/cache/config.cc b/source/extensions/filters/http/cache/config.cc index bfb64ad4bfb7..e379b4c05d1a 100644 --- a/source/extensions/filters/http/cache/config.cc +++ b/source/extensions/filters/http/cache/config.cc @@ -28,8 +28,8 @@ Http::FilterFactoryCb CacheFilterFactory::createFilterFactoryFromProtoTyped( return [config, stats_prefix, &context, cache](Http::FilterChainFactoryCallbacks& callbacks) -> void { - callbacks.addStreamFilter(std::make_shared( - config, stats_prefix, context.scope(), context.serverFactoryContext().timeSource(), cache)); + callbacks.addStreamFilter(std::make_shared(config, stats_prefix, context.scope(), + context.serverFactoryContext(), cache)); }; } diff --git a/test/extensions/filters/http/cache/BUILD b/test/extensions/filters/http/cache/BUILD index f45131e56f5d..1d1f5f0ab270 100644 --- a/test/extensions/filters/http/cache/BUILD +++ b/test/extensions/filters/http/cache/BUILD @@ -25,6 +25,7 @@ envoy_extension_cc_test( "//envoy/http:header_map_interface", "//source/common/http:header_map_lib", "//source/extensions/filters/http/cache:cache_headers_utils_lib", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], @@ -56,6 +57,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/cache:http_cache_lib", "//source/extensions/http/cache/simple_http_cache:config", "//test/mocks/http:http_mocks", + "//test/mocks/server:factory_context_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", @@ -96,6 +98,7 @@ envoy_extension_cc_test( extension_names = ["envoy.filters.http.cache"], deps = [ "//source/extensions/filters/http/cache:cacheability_utils_lib", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/cache/cache_filter_test.cc b/test/extensions/filters/http/cache/cache_filter_test.cc index 9c4e1afb273d..9ddf067b4d58 100644 --- a/test/extensions/filters/http/cache/cache_filter_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_test.cc @@ -30,15 +30,15 @@ class CacheFilterTest : public ::testing::Test { // The filter has to be created as a shared_ptr to enable shared_from_this() which is used in the // cache callbacks. CacheFilterSharedPtr makeFilter(std::shared_ptr cache, bool auto_destroy = true) { - std::shared_ptr filter( - new CacheFilter(config_, /*stats_prefix=*/"", context_.scope(), - context_.server_factory_context_.timeSource(), cache), - [auto_destroy](CacheFilter* f) { - if (auto_destroy) { - f->onDestroy(); - } - delete f; - }); + std::shared_ptr filter(new CacheFilter(config_, /*stats_prefix=*/"", + context_.scope(), + context_.server_factory_context_, cache), + [auto_destroy](CacheFilter* f) { + if (auto_destroy) { + f->onDestroy(); + } + delete f; + }); filter_state_ = std::make_shared( StreamInfo::FilterState::LifeSpan::FilterChain); filter->setDecoderFilterCallbacks(decoder_callbacks_); diff --git a/test/extensions/filters/http/cache/cache_headers_utils_test.cc b/test/extensions/filters/http/cache/cache_headers_utils_test.cc index 2b5a0de3304e..453ab301513a 100644 --- a/test/extensions/filters/http/cache/cache_headers_utils_test.cc +++ b/test/extensions/filters/http/cache/cache_headers_utils_test.cc @@ -10,6 +10,7 @@ #include "source/common/http/header_utility.h" #include "source/extensions/filters/http/cache/cache_headers_utils.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" @@ -652,8 +653,11 @@ TEST_P(ParseCommaDelimitedHeaderTest, ParseCommaDelimitedHeader) { } TEST(CreateVaryIdentifier, IsStableForAllowListOrder) { - VaryAllowList vary_allow_list1(toStringMatchers({"width", "accept", "accept-language"})); - VaryAllowList vary_allow_list2(toStringMatchers({"accept", "width", "accept-language"})); + NiceMock factory_context; + VaryAllowList vary_allow_list1(toStringMatchers({"width", "accept", "accept-language"}), + factory_context); + VaryAllowList vary_allow_list2(toStringMatchers({"accept", "width", "accept-language"}), + factory_context); Http::TestRequestHeaderMapImpl request_headers{ {"accept", "image/*"}, {"accept-language", "en-us"}, {"width", "640"}}; @@ -710,16 +714,20 @@ TEST(HasVary, NotEmpty) { } TEST(CreateVaryIdentifier, EmptyVaryEntry) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{{"accept", "image/*"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {}, request_headers), "vary-id\n"); } TEST(CreateVaryIdentifier, SingleHeaderExists) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{{"accept", "image/*"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {"accept"}, request_headers), "vary-id\naccept\r" @@ -727,17 +735,21 @@ TEST(CreateVaryIdentifier, SingleHeaderExists) { } TEST(CreateVaryIdentifier, SingleHeaderMissing) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {"accept"}, request_headers), "vary-id\naccept\r\n"); } TEST(CreateVaryIdentifier, MultipleHeadersAllExist) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{ {"accept", "image/*"}, {"accept-language", "en-us"}, {"width", "640"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier( vary_allow_list, {"accept", "accept-language", "width"}, request_headers), @@ -747,9 +759,11 @@ TEST(CreateVaryIdentifier, MultipleHeadersAllExist) { } TEST(CreateVaryIdentifier, MultipleHeadersSomeExist) { + NiceMock factory_context; Http::TestResponseHeaderMapImpl response_headers{{"vary", "accept, accept-language, width"}}; Http::TestRequestHeaderMapImpl request_headers{{"accept", "image/*"}, {"width", "640"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier( vary_allow_list, {"accept", "accept-language", "width"}, request_headers), @@ -758,9 +772,11 @@ TEST(CreateVaryIdentifier, MultipleHeadersSomeExist) { } TEST(CreateVaryIdentifier, ExtraRequestHeaders) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{ {"accept", "image/*"}, {"heigth", "1280"}, {"width", "640"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ( VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {"accept", "width"}, request_headers), @@ -769,8 +785,10 @@ TEST(CreateVaryIdentifier, ExtraRequestHeaders) { } TEST(CreateVaryIdentifier, MultipleHeadersNoneExist) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier( vary_allow_list, {"accept", "accept-language", "width"}, request_headers), @@ -778,9 +796,12 @@ TEST(CreateVaryIdentifier, MultipleHeadersNoneExist) { } TEST(CreateVaryIdentifier, DifferentHeadersSameValue) { + NiceMock factory_context; + // Two requests with the same value for different headers must have different // vary-ids. - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); Http::TestRequestHeaderMapImpl request_headers1{{"accept", "foo"}}; absl::optional vary_identifier1 = VaryHeaderUtils::createVaryIdentifier( @@ -796,8 +817,10 @@ TEST(CreateVaryIdentifier, DifferentHeadersSameValue) { } TEST(CreateVaryIdentifier, MultiValueSameHeader) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{{"width", "foo"}, {"width", "bar"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {"width"}, request_headers), "vary-id\nwidth\r" @@ -806,16 +829,20 @@ TEST(CreateVaryIdentifier, MultiValueSameHeader) { } TEST(CreateVaryIdentifier, DisallowedHeader) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{{"width", "foo"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ(VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {"disallowed"}, request_headers), absl::nullopt); } TEST(CreateVaryIdentifier, DisallowedHeaderWithAllowedHeader) { + NiceMock factory_context; Http::TestRequestHeaderMapImpl request_headers{{"width", "foo"}}; - VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"})); + VaryAllowList vary_allow_list(toStringMatchers({"accept", "accept-language", "width"}), + factory_context); EXPECT_EQ( VaryHeaderUtils::createVaryIdentifier(vary_allow_list, {"disallowed,width"}, request_headers), @@ -840,8 +867,9 @@ envoy::extensions::filters::http::cache::v3::CacheConfig getConfig() { class VaryAllowListTest : public testing::Test { protected: - VaryAllowListTest() : vary_allow_list_(getConfig().allowed_vary_headers()) {} + VaryAllowListTest() : vary_allow_list_(getConfig().allowed_vary_headers(), factory_context_) {} + NiceMock factory_context_; VaryAllowList vary_allow_list_; Http::TestRequestHeaderMapImpl request_headers_; Http::TestResponseHeaderMapImpl response_headers_; diff --git a/test/extensions/filters/http/cache/cacheability_utils_test.cc b/test/extensions/filters/http/cache/cacheability_utils_test.cc index e5bbf9061b8d..d43223f4075c 100644 --- a/test/extensions/filters/http/cache/cacheability_utils_test.cc +++ b/test/extensions/filters/http/cache/cacheability_utils_test.cc @@ -2,6 +2,7 @@ #include "source/extensions/filters/http/cache/cacheability_utils.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -35,13 +36,16 @@ envoy::extensions::filters::http::cache::v3::CacheConfig getConfig() { class IsCacheableResponseTest : public testing::Test { public: - IsCacheableResponseTest() : vary_allow_list_(getConfig().allowed_vary_headers()) {} + IsCacheableResponseTest() + : vary_allow_list_(getConfig().allowed_vary_headers(), factory_context_) {} protected: std::string cache_control_ = "max-age=3600"; Http::TestResponseHeaderMapImpl response_headers_ = {{":status", "200"}, {"date", "Sun, 06 Nov 1994 08:49:37 GMT"}, {"cache-control", cache_control_}}; + + NiceMock factory_context_; VaryAllowList vary_allow_list_; }; diff --git a/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc b/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc index 1101e451cabe..fdb4c3f240cf 100644 --- a/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc +++ b/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc @@ -41,7 +41,8 @@ MATCHER(IsOk, "") { return arg.ok(); } } // namespace HttpCacheImplementationTest::HttpCacheImplementationTest() - : delegate_(GetParam()()), vary_allow_list_(getConfig().allowed_vary_headers()) { + : delegate_(GetParam()()), + vary_allow_list_(getConfig().allowed_vary_headers(), factory_context_) { request_headers_.setMethod("GET"); request_headers_.setHost("example.com"); request_headers_.setScheme("https"); @@ -490,7 +491,8 @@ TEST_P(HttpCacheImplementationTest, VaryResponses) { Protobuf::RepeatedPtrField<::envoy::type::matcher::v3::StringMatcher> proto_allow_list; ::envoy::type::matcher::v3::StringMatcher* matcher = proto_allow_list.Add(); matcher->set_exact("width"); - vary_allow_list_ = VaryAllowList(proto_allow_list); + NiceMock factory_context; + vary_allow_list_ = VaryAllowList(proto_allow_list, factory_context); lookup(request_path); EXPECT_EQ(lookup_result_.cache_entry_status_, CacheEntryStatus::Unusable); } diff --git a/test/extensions/filters/http/cache/http_cache_implementation_test_common.h b/test/extensions/filters/http/cache/http_cache_implementation_test_common.h index 85c4ba8981fd..53553010fbaf 100644 --- a/test/extensions/filters/http/cache/http_cache_implementation_test_common.h +++ b/test/extensions/filters/http/cache/http_cache_implementation_test_common.h @@ -9,6 +9,7 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" @@ -89,6 +90,7 @@ class HttpCacheImplementationTest expectLookupSuccessWithBodyAndTrailers(LookupContext* lookup, absl::string_view body, Http::TestResponseTrailerMapImpl trailers = {}); + NiceMock factory_context_; std::unique_ptr delegate_; VaryAllowList vary_allow_list_; LookupResult lookup_result_; diff --git a/test/extensions/filters/http/cache/http_cache_test.cc b/test/extensions/filters/http/cache/http_cache_test.cc index 54c53fb5f2cf..fa22525396ca 100644 --- a/test/extensions/filters/http/cache/http_cache_test.cc +++ b/test/extensions/filters/http/cache/http_cache_test.cc @@ -5,6 +5,7 @@ #include "source/extensions/filters/http/cache/http_cache.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" @@ -39,12 +40,13 @@ envoy::extensions::filters::http::cache::v3::CacheConfig getConfig() { class LookupRequestTest : public testing::TestWithParam { public: - LookupRequestTest() : vary_allow_list_(getConfig().allowed_vary_headers()) {} + LookupRequestTest() : vary_allow_list_(getConfig().allowed_vary_headers(), factory_context_) {} DateFormatter formatter_{"%a, %d %b %Y %H:%M:%S GMT"}; Http::TestRequestHeaderMapImpl request_headers_{ {":path", "/"}, {":method", "GET"}, {":scheme", "https"}, {":authority", "example.com"}}; + NiceMock factory_context_; VaryAllowList vary_allow_list_; static const SystemTime& currentTime() { diff --git a/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc b/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc index 004eb996d33e..dad95f3f046c 100644 --- a/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc +++ b/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc @@ -370,7 +370,8 @@ class FileSystemHttpCacheTestWithMockFiles : public FileSystemHttpCacheTest { NiceMock encoder_callbacks_; Event::SimulatedTimeSystem time_system_; Http::TestRequestHeaderMapImpl request_headers_; - VaryAllowList vary_allow_list_{varyAllowListConfig().allowed_vary_headers()}; + NiceMock factory_context_; + VaryAllowList vary_allow_list_{varyAllowListConfig().allowed_vary_headers(), factory_context_}; DateFormatter formatter_{"%a, %d %b %Y %H:%M:%S GMT"}; Http::TestResponseHeaderMapImpl response_headers_{ {":status", "200"}, From 52fa026749bb080bacb1eb456f88061caddde473 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Fri, 15 Mar 2024 08:47:56 -0700 Subject: [PATCH 12/36] Stringmatcher: factory context for rbac, dubbo_proxy (#32920) Signed-off-by: Greg Greenway --- .../filters/common/rbac/engine_impl.cc | 5 +- .../filters/common/rbac/engine_impl.h | 1 + .../filters/common/rbac/matchers.cc | 43 +++--- .../extensions/filters/common/rbac/matchers.h | 64 +++++---- .../extensions/filters/common/rbac/utility.h | 4 +- .../dubbo_proxy/router/route_matcher.cc | 9 +- .../dubbo_proxy/router/route_matcher.h | 6 +- test/extensions/filters/common/rbac/BUILD | 1 + .../filters/common/rbac/engine_impl_test.cc | 67 +++++---- .../filters/common/rbac/matchers_test.cc | 133 +++++++++++------- test/extensions/filters/common/rbac/mocks.h | 3 +- .../filters/http/rbac/rbac_filter_test.cc | 3 +- 12 files changed, 208 insertions(+), 131 deletions(-) diff --git a/source/extensions/filters/common/rbac/engine_impl.cc b/source/extensions/filters/common/rbac/engine_impl.cc index 76d010c562c7..f4b1f9e11bdd 100644 --- a/source/extensions/filters/common/rbac/engine_impl.cc +++ b/source/extensions/filters/common/rbac/engine_impl.cc @@ -42,7 +42,8 @@ void generateLog(StreamInfo::StreamInfo& info, EnforcementMode mode, bool log) { RoleBasedAccessControlEngineImpl::RoleBasedAccessControlEngineImpl( const envoy::config::rbac::v3::RBAC& rules, - ProtobufMessage::ValidationVisitor& validation_visitor, const EnforcementMode mode) + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context, const EnforcementMode mode) : action_(rules.action()), mode_(mode) { // guard expression builder by presence of a condition in policies for (const auto& policy : rules.policies()) { @@ -54,7 +55,7 @@ RoleBasedAccessControlEngineImpl::RoleBasedAccessControlEngineImpl( for (const auto& policy : rules.policies()) { policies_.emplace(policy.first, std::make_unique(policy.second, builder_.get(), - validation_visitor)); + validation_visitor, context)); } } diff --git a/source/extensions/filters/common/rbac/engine_impl.h b/source/extensions/filters/common/rbac/engine_impl.h index c748142c8a97..7f51648caa47 100644 --- a/source/extensions/filters/common/rbac/engine_impl.h +++ b/source/extensions/filters/common/rbac/engine_impl.h @@ -65,6 +65,7 @@ class RoleBasedAccessControlEngineImpl : public RoleBasedAccessControlEngine, No public: RoleBasedAccessControlEngineImpl(const envoy::config::rbac::v3::RBAC& rules, ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context, const EnforcementMode mode = EnforcementMode::Enforced); bool handleAction(const Network::Connection& connection, diff --git a/source/extensions/filters/common/rbac/matchers.cc b/source/extensions/filters/common/rbac/matchers.cc index 5df6db1bfe37..47ffb5ec57b7 100644 --- a/source/extensions/filters/common/rbac/matchers.cc +++ b/source/extensions/filters/common/rbac/matchers.cc @@ -13,12 +13,13 @@ namespace Common { namespace RBAC { MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Permission& permission, - ProtobufMessage::ValidationVisitor& validation_visitor) { + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context) { switch (permission.rule_case()) { case envoy::config::rbac::v3::Permission::RuleCase::kAndRules: - return std::make_shared(permission.and_rules(), validation_visitor); + return std::make_shared(permission.and_rules(), validation_visitor, context); case envoy::config::rbac::v3::Permission::RuleCase::kOrRules: - return std::make_shared(permission.or_rules(), validation_visitor); + return std::make_shared(permission.or_rules(), validation_visitor, context); case envoy::config::rbac::v3::Permission::RuleCase::kHeader: return std::make_shared(permission.header()); case envoy::config::rbac::v3::Permission::RuleCase::kDestinationIp: @@ -33,9 +34,10 @@ MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Permission& case envoy::config::rbac::v3::Permission::RuleCase::kMetadata: return std::make_shared(permission.metadata()); case envoy::config::rbac::v3::Permission::RuleCase::kNotRule: - return std::make_shared(permission.not_rule(), validation_visitor); + return std::make_shared(permission.not_rule(), validation_visitor, context); case envoy::config::rbac::v3::Permission::RuleCase::kRequestedServerName: - return std::make_shared(permission.requested_server_name()); + return std::make_shared(permission.requested_server_name(), + context); case envoy::config::rbac::v3::Permission::RuleCase::kUrlPath: return std::make_shared(permission.url_path()); case envoy::config::rbac::v3::Permission::RuleCase::kUriTemplate: { @@ -56,14 +58,15 @@ MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Permission& PANIC_DUE_TO_CORRUPT_ENUM; } -MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Principal& principal) { +MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Principal& principal, + Server::Configuration::CommonFactoryContext& context) { switch (principal.identifier_case()) { case envoy::config::rbac::v3::Principal::IdentifierCase::kAndIds: - return std::make_shared(principal.and_ids()); + return std::make_shared(principal.and_ids(), context); case envoy::config::rbac::v3::Principal::IdentifierCase::kOrIds: - return std::make_shared(principal.or_ids()); + return std::make_shared(principal.or_ids(), context); case envoy::config::rbac::v3::Principal::IdentifierCase::kAuthenticated: - return std::make_shared(principal.authenticated()); + return std::make_shared(principal.authenticated(), context); case envoy::config::rbac::v3::Principal::IdentifierCase::kSourceIp: return std::make_shared(principal.source_ip(), IPMatcher::Type::ConnectionRemote); @@ -80,7 +83,7 @@ MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Principal& case envoy::config::rbac::v3::Principal::IdentifierCase::kMetadata: return std::make_shared(principal.metadata()); case envoy::config::rbac::v3::Principal::IdentifierCase::kNotId: - return std::make_shared(principal.not_id()); + return std::make_shared(principal.not_id(), context); case envoy::config::rbac::v3::Principal::IdentifierCase::kUrlPath: return std::make_shared(principal.url_path()); case envoy::config::rbac::v3::Principal::IdentifierCase::kFilterState: @@ -92,15 +95,17 @@ MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Principal& } AndMatcher::AndMatcher(const envoy::config::rbac::v3::Permission::Set& set, - ProtobufMessage::ValidationVisitor& validation_visitor) { + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context) { for (const auto& rule : set.rules()) { - matchers_.push_back(Matcher::create(rule, validation_visitor)); + matchers_.push_back(Matcher::create(rule, validation_visitor, context)); } } -AndMatcher::AndMatcher(const envoy::config::rbac::v3::Principal::Set& set) { +AndMatcher::AndMatcher(const envoy::config::rbac::v3::Principal::Set& set, + Server::Configuration::CommonFactoryContext& context) { for (const auto& id : set.ids()) { - matchers_.push_back(Matcher::create(id)); + matchers_.push_back(Matcher::create(id, context)); } } @@ -117,15 +122,17 @@ bool AndMatcher::matches(const Network::Connection& connection, } OrMatcher::OrMatcher(const Protobuf::RepeatedPtrField& rules, - ProtobufMessage::ValidationVisitor& validation_visitor) { + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context) { for (const auto& rule : rules) { - matchers_.push_back(Matcher::create(rule, validation_visitor)); + matchers_.push_back(Matcher::create(rule, validation_visitor, context)); } } -OrMatcher::OrMatcher(const Protobuf::RepeatedPtrField& ids) { +OrMatcher::OrMatcher(const Protobuf::RepeatedPtrField& ids, + Server::Configuration::CommonFactoryContext& context) { for (const auto& id : ids) { - matchers_.push_back(Matcher::create(id)); + matchers_.push_back(Matcher::create(id, context)); } } diff --git a/source/extensions/filters/common/rbac/matchers.h b/source/extensions/filters/common/rbac/matchers.h index b5914f397f3f..bc39bc94c97e 100644 --- a/source/extensions/filters/common/rbac/matchers.h +++ b/source/extensions/filters/common/rbac/matchers.h @@ -49,13 +49,15 @@ class Matcher { * proto message. */ static MatcherConstSharedPtr create(const envoy::config::rbac::v3::Permission& permission, - ProtobufMessage::ValidationVisitor& validation_visitor); + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context); /** * Creates a shared instance of a matcher based off the rules defined in the Principal config * proto message. */ - static MatcherConstSharedPtr create(const envoy::config::rbac::v3::Principal& principal); + static MatcherConstSharedPtr create(const envoy::config::rbac::v3::Principal& principal, + Server::Configuration::CommonFactoryContext& context); }; /** @@ -76,8 +78,10 @@ class AlwaysMatcher : public Matcher { class AndMatcher : public Matcher { public: AndMatcher(const envoy::config::rbac::v3::Permission::Set& rules, - ProtobufMessage::ValidationVisitor& validation_visitor); - AndMatcher(const envoy::config::rbac::v3::Principal::Set& ids); + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context); + AndMatcher(const envoy::config::rbac::v3::Principal::Set& ids, + Server::Configuration::CommonFactoryContext& context); bool matches(const Network::Connection& connection, const Envoy::Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const override; @@ -93,12 +97,17 @@ class AndMatcher : public Matcher { class OrMatcher : public Matcher { public: OrMatcher(const envoy::config::rbac::v3::Permission::Set& set, - ProtobufMessage::ValidationVisitor& validation_visitor) - : OrMatcher(set.rules(), validation_visitor) {} - OrMatcher(const envoy::config::rbac::v3::Principal::Set& set) : OrMatcher(set.ids()) {} + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context) + : OrMatcher(set.rules(), validation_visitor, context) {} + OrMatcher(const envoy::config::rbac::v3::Principal::Set& set, + Server::Configuration::CommonFactoryContext& context) + : OrMatcher(set.ids(), context) {} OrMatcher(const Protobuf::RepeatedPtrField& rules, - ProtobufMessage::ValidationVisitor& validation_visitor); - OrMatcher(const Protobuf::RepeatedPtrField& ids); + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context); + OrMatcher(const Protobuf::RepeatedPtrField& ids, + Server::Configuration::CommonFactoryContext& context); bool matches(const Network::Connection& connection, const Envoy::Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const override; @@ -110,10 +119,12 @@ class OrMatcher : public Matcher { class NotMatcher : public Matcher { public: NotMatcher(const envoy::config::rbac::v3::Permission& permission, - ProtobufMessage::ValidationVisitor& validation_visitor) - : matcher_(Matcher::create(permission, validation_visitor)) {} - NotMatcher(const envoy::config::rbac::v3::Principal& principal) - : matcher_(Matcher::create(principal)) {} + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context) + : matcher_(Matcher::create(permission, validation_visitor, context)) {} + NotMatcher(const envoy::config::rbac::v3::Principal& principal, + Server::Configuration::CommonFactoryContext& context) + : matcher_(Matcher::create(principal, context)) {} bool matches(const Network::Connection& connection, const Envoy::Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const override; @@ -188,18 +199,19 @@ class PortRangeMatcher : public Matcher { */ class AuthenticatedMatcher : public Matcher { public: - AuthenticatedMatcher(const envoy::config::rbac::v3::Principal::Authenticated& auth) + AuthenticatedMatcher(const envoy::config::rbac::v3::Principal::Authenticated& auth, + Server::Configuration::CommonFactoryContext& context) : matcher_(auth.has_principal_name() - ? absl::make_optional< - Matchers::StringMatcherImpl>( - auth.principal_name()) + ? absl::make_optional>(auth.principal_name(), context) : absl::nullopt) {} bool matches(const Network::Connection& connection, const Envoy::Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const override; private: - const absl::optional> + const absl::optional< + Matchers::StringMatcherImplWithContext> matcher_; }; @@ -211,9 +223,10 @@ class AuthenticatedMatcher : public Matcher { class PolicyMatcher : public Matcher, NonCopyable { public: PolicyMatcher(const envoy::config::rbac::v3::Policy& policy, Expr::Builder* builder, - ProtobufMessage::ValidationVisitor& validation_visitor) - : permissions_(policy.permissions(), validation_visitor), principals_(policy.principals()), - condition_(policy.condition()) { + ProtobufMessage::ValidationVisitor& validation_visitor, + Server::Configuration::CommonFactoryContext& context) + : permissions_(policy.permissions(), validation_visitor, context), + principals_(policy.principals(), context), condition_(policy.condition()) { if (policy.has_condition()) { expr_ = Expr::createExpression(*builder, condition_); } @@ -258,11 +271,12 @@ class FilterStateMatcher : public Matcher { */ class RequestedServerNameMatcher : public Matcher, - Envoy::Matchers::StringMatcherImpl { + Envoy::Matchers::StringMatcherImplWithContext { public: - RequestedServerNameMatcher(const envoy::type::matcher::v3::StringMatcher& requested_server_name) - : Envoy::Matchers::StringMatcherImpl( - requested_server_name) {} + RequestedServerNameMatcher(const envoy::type::matcher::v3::StringMatcher& requested_server_name, + Server::Configuration::CommonFactoryContext& context) + : Envoy::Matchers::StringMatcherImplWithContext( + requested_server_name, context) {} bool matches(const Network::Connection& connection, const Envoy::Http::RequestHeaderMap& headers, const StreamInfo::StreamInfo&) const override; diff --git a/source/extensions/filters/common/rbac/utility.h b/source/extensions/filters/common/rbac/utility.h index ac2a339226ca..79cd8be7d2bf 100644 --- a/source/extensions/filters/common/rbac/utility.h +++ b/source/extensions/filters/common/rbac/utility.h @@ -51,7 +51,7 @@ createEngine(const ConfigType& config, Server::Configuration::ServerFactoryConte } if (config.has_rules()) { return std::make_unique(config.rules(), validation_visitor, - EnforcementMode::Enforced); + context, EnforcementMode::Enforced); } return nullptr; @@ -71,7 +71,7 @@ createShadowEngine(const ConfigType& config, Server::Configuration::ServerFactor } if (config.has_shadow_rules()) { return std::make_unique( - config.shadow_rules(), validation_visitor, EnforcementMode::Shadow); + config.shadow_rules(), validation_visitor, context, EnforcementMode::Shadow); } return nullptr; diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc index 334236048f21..b58abc50dfc1 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc @@ -166,8 +166,9 @@ ParameterRouteEntryImpl::ParameterData::ParameterData(uint32_t index, } MethodRouteEntryImpl::MethodRouteEntryImpl( - const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route) - : RouteEntryImplBase(route), method_name_(route.match().method().name()) { + const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route, + Server::Configuration::CommonFactoryContext& context) + : RouteEntryImplBase(route), method_name_(route.match().method().name(), context) { if (route.match().method().params_match_size() != 0) { parameter_route_ = std::make_shared(route); } @@ -206,12 +207,12 @@ RouteConstSharedPtr MethodRouteEntryImpl::matches(const MessageMetadata& metadat } SingleRouteMatcherImpl::SingleRouteMatcherImpl(const RouteConfig& config, - Server::Configuration::ServerFactoryContext&) + Server::Configuration::ServerFactoryContext& context) : interface_matcher_(config.interface()), group_(config.group()), version_(config.version()) { using envoy::extensions::filters::network::dubbo_proxy::v3::RouteMatch; for (const auto& route : config.routes()) { - routes_.emplace_back(std::make_shared(route)); + routes_.emplace_back(std::make_shared(route, context)); } ENVOY_LOG(debug, "dubbo route matcher: routes list size {}", routes_.size()); } diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h index 8b37eb2a2f23..b5fbcb2c7004 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h @@ -118,7 +118,8 @@ class ParameterRouteEntryImpl : public RouteEntryImplBase { class MethodRouteEntryImpl : public RouteEntryImplBase { public: - MethodRouteEntryImpl(const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route); + MethodRouteEntryImpl(const envoy::extensions::filters::network::dubbo_proxy::v3::Route& route, + Server::Configuration::CommonFactoryContext& context); ~MethodRouteEntryImpl() override; // RoutEntryImplBase @@ -126,7 +127,8 @@ class MethodRouteEntryImpl : public RouteEntryImplBase { uint64_t random_value) const override; private: - const Matchers::StringMatcherImpl method_name_; + const Matchers::StringMatcherImplWithContext + method_name_; std::shared_ptr parameter_route_; }; diff --git a/test/extensions/filters/common/rbac/BUILD b/test/extensions/filters/common/rbac/BUILD index 3cdc7f4eb9ff..ca59ee1f59d8 100644 --- a/test/extensions/filters/common/rbac/BUILD +++ b/test/extensions/filters/common/rbac/BUILD @@ -22,6 +22,7 @@ envoy_extension_cc_test( "//source/extensions/filters/common/expr:evaluator_lib", "//source/extensions/filters/common/rbac:matchers_lib", "//test/mocks/network:network_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/extensions/filters/common/rbac/engine_impl_test.cc b/test/extensions/filters/common/rbac/engine_impl_test.cc index 872336d6744f..957229877617 100644 --- a/test/extensions/filters/common/rbac/engine_impl_test.cc +++ b/test/extensions/filters/common/rbac/engine_impl_test.cc @@ -100,15 +100,16 @@ void onMetadata(NiceMock& info) { } TEST(RoleBasedAccessControlEngineImpl, Disabled) { + Server::Configuration::MockServerFactoryContext factory_context; envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); RBAC::RoleBasedAccessControlEngineImpl engine_allow( - rbac, ProtobufMessage::getStrictValidationVisitor()); + rbac, ProtobufMessage::getStrictValidationVisitor(), factory_context); checkEngine(engine_allow, false, LogResult::Undecided); rbac.set_action(envoy::config::rbac::v3::RBAC::DENY); - RBAC::RoleBasedAccessControlEngineImpl engine_deny(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine_deny( + rbac, ProtobufMessage::getStrictValidationVisitor(), factory_context); checkEngine(engine_deny, true, LogResult::Undecided); } @@ -199,6 +200,7 @@ TEST(RoleBasedAccessControlEngineImpl, InvalidConfig) { } TEST(RoleBasedAccessControlEngineImpl, AllowedAllowlist) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_destination_port(123); policy.add_principals()->set_any(true); @@ -206,8 +208,8 @@ TEST(RoleBasedAccessControlEngineImpl, AllowedAllowlist) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Network::MockConnection conn; Envoy::Http::TestRequestHeaderMapImpl headers; @@ -223,6 +225,7 @@ TEST(RoleBasedAccessControlEngineImpl, AllowedAllowlist) { } TEST(RoleBasedAccessControlEngineImpl, DeniedDenylist) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_destination_port(123); policy.add_principals()->set_any(true); @@ -230,8 +233,8 @@ TEST(RoleBasedAccessControlEngineImpl, DeniedDenylist) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::DENY); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Network::MockConnection conn; Envoy::Http::TestRequestHeaderMapImpl headers; @@ -247,6 +250,7 @@ TEST(RoleBasedAccessControlEngineImpl, DeniedDenylist) { } TEST(RoleBasedAccessControlEngineImpl, BasicCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -259,12 +263,13 @@ TEST(RoleBasedAccessControlEngineImpl, BasicCondition) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); checkEngine(engine, false, LogResult::Undecided); } TEST(RoleBasedAccessControlEngineImpl, MalformedCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -282,16 +287,17 @@ TEST(RoleBasedAccessControlEngineImpl, MalformedCondition) { (*rbac.mutable_policies())["foo"] = policy; EXPECT_THROW_WITH_REGEX(RBAC::RoleBasedAccessControlEngineImpl engine( - rbac, ProtobufMessage::getStrictValidationVisitor()), + rbac, ProtobufMessage::getStrictValidationVisitor(), factory_context), EnvoyException, "failed to create an expression: .*"); rbac.set_action(envoy::config::rbac::v3::RBAC::LOG); EXPECT_THROW_WITH_REGEX(RBAC::RoleBasedAccessControlEngineImpl engine_log( - rbac, ProtobufMessage::getStrictValidationVisitor()), + rbac, ProtobufMessage::getStrictValidationVisitor(), factory_context), EnvoyException, "failed to create an expression: .*"); } TEST(RoleBasedAccessControlEngineImpl, MistypedCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -304,12 +310,13 @@ TEST(RoleBasedAccessControlEngineImpl, MistypedCondition) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); checkEngine(engine, false, LogResult::Undecided); } TEST(RoleBasedAccessControlEngineImpl, EvaluationFailure) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -325,12 +332,13 @@ TEST(RoleBasedAccessControlEngineImpl, EvaluationFailure) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); checkEngine(engine, false, LogResult::Undecided); } TEST(RoleBasedAccessControlEngineImpl, ErrorCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -351,12 +359,13 @@ TEST(RoleBasedAccessControlEngineImpl, ErrorCondition) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); checkEngine(engine, false, LogResult::Undecided, Envoy::Network::MockConnection()); } TEST(RoleBasedAccessControlEngineImpl, HeaderCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -382,8 +391,8 @@ TEST(RoleBasedAccessControlEngineImpl, HeaderCondition) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Http::TestRequestHeaderMapImpl headers; Envoy::Http::LowerCaseString key("foo"); @@ -394,6 +403,7 @@ TEST(RoleBasedAccessControlEngineImpl, HeaderCondition) { } TEST(RoleBasedAccessControlEngineImpl, MetadataCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_any(true); policy.add_principals()->set_any(true); @@ -424,8 +434,8 @@ TEST(RoleBasedAccessControlEngineImpl, MetadataCondition) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Http::TestRequestHeaderMapImpl headers; NiceMock info; @@ -440,6 +450,7 @@ TEST(RoleBasedAccessControlEngineImpl, MetadataCondition) { } TEST(RoleBasedAccessControlEngineImpl, ConjunctiveCondition) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_destination_port(123); policy.add_principals()->set_any(true); @@ -452,8 +463,8 @@ TEST(RoleBasedAccessControlEngineImpl, ConjunctiveCondition) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::ALLOW); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Network::MockConnection conn; Envoy::Http::TestRequestHeaderMapImpl headers; @@ -572,17 +583,19 @@ TEST(RoleBasedAccessControlMatcherEngineImpl, DeniedDenylist) { // Log tests TEST(RoleBasedAccessControlEngineImpl, DisabledLog) { + NiceMock factory_context; NiceMock info; onMetadata(info); envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::LOG); - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); checkEngine(engine, true, RBAC::LogResult::No, info); } TEST(RoleBasedAccessControlEngineImpl, LogIfMatched) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_destination_port(123); policy.add_principals()->set_any(true); @@ -590,8 +603,8 @@ TEST(RoleBasedAccessControlEngineImpl, LogIfMatched) { envoy::config::rbac::v3::RBAC rbac; rbac.set_action(envoy::config::rbac::v3::RBAC::LOG); (*rbac.mutable_policies())["foo"] = policy; - RBAC::RoleBasedAccessControlEngineImpl engine(rbac, - ProtobufMessage::getStrictValidationVisitor()); + RBAC::RoleBasedAccessControlEngineImpl engine(rbac, ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Network::MockConnection conn; Envoy::Http::TestRequestHeaderMapImpl headers; diff --git a/test/extensions/filters/common/rbac/matchers_test.cc b/test/extensions/filters/common/rbac/matchers_test.cc index 2ef2b577fcea..c64d6ec9818f 100644 --- a/test/extensions/filters/common/rbac/matchers_test.cc +++ b/test/extensions/filters/common/rbac/matchers_test.cc @@ -11,6 +11,7 @@ #include "source/extensions/filters/common/rbac/matchers.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "gmock/gmock.h" @@ -40,11 +41,13 @@ PortRangeMatcher createPortRangeMatcher(envoy::type::v3::Int32Range range) { ret TEST(AlwaysMatcher, AlwaysMatches) { checkMatcher(RBAC::AlwaysMatcher(), true); } TEST(AndMatcher, Permission_Set) { + NiceMock factory_context; envoy::config::rbac::v3::Permission::Set set; envoy::config::rbac::v3::Permission* perm = set.add_rules(); perm->set_any(true); - checkMatcher(RBAC::AndMatcher(set, ProtobufMessage::getStrictValidationVisitor()), true); + checkMatcher( + RBAC::AndMatcher(set, ProtobufMessage::getStrictValidationVisitor(), factory_context), true); perm = set.add_rules(); perm->set_destination_port(123); @@ -56,22 +59,25 @@ TEST(AndMatcher, Permission_Set) { Envoy::Network::Utility::parseInternetAddress("1.2.3.4", 123, false); info.downstream_connection_info_provider_->setLocalAddress(addr); - checkMatcher(RBAC::AndMatcher(set, ProtobufMessage::getStrictValidationVisitor()), true, conn, - headers, info); + checkMatcher( + RBAC::AndMatcher(set, ProtobufMessage::getStrictValidationVisitor(), factory_context), true, + conn, headers, info); addr = Envoy::Network::Utility::parseInternetAddress("1.2.3.4", 8080, false); info.downstream_connection_info_provider_->setLocalAddress(addr); - checkMatcher(RBAC::AndMatcher(set, ProtobufMessage::getStrictValidationVisitor()), false, conn, - headers, info); + checkMatcher( + RBAC::AndMatcher(set, ProtobufMessage::getStrictValidationVisitor(), factory_context), false, + conn, headers, info); } TEST(AndMatcher, Principal_Set) { + NiceMock factory_context; envoy::config::rbac::v3::Principal::Set set; envoy::config::rbac::v3::Principal* principal = set.add_ids(); principal->set_any(true); - checkMatcher(RBAC::AndMatcher(set), true); + checkMatcher(RBAC::AndMatcher(set, factory_context), true); principal = set.add_ids(); auto* cidr = principal->mutable_direct_remote_ip(); @@ -85,15 +91,16 @@ TEST(AndMatcher, Principal_Set) { Envoy::Network::Utility::parseInternetAddress("1.2.3.4", 123, false); info.downstream_connection_info_provider_->setDirectRemoteAddressForTest(addr); - checkMatcher(RBAC::AndMatcher(set), true, conn, headers, info); + checkMatcher(RBAC::AndMatcher(set, factory_context), true, conn, headers, info); addr = Envoy::Network::Utility::parseInternetAddress("1.2.4.6", 123, false); info.downstream_connection_info_provider_->setDirectRemoteAddressForTest(addr); - checkMatcher(RBAC::AndMatcher(set), false, conn, headers, info); + checkMatcher(RBAC::AndMatcher(set, factory_context), false, conn, headers, info); } TEST(OrMatcher, Permission_Set) { + NiceMock factory_context; envoy::config::rbac::v3::Permission::Set set; envoy::config::rbac::v3::Permission* perm = set.add_rules(); perm->set_destination_port(123); @@ -105,21 +112,21 @@ TEST(OrMatcher, Permission_Set) { Envoy::Network::Utility::parseInternetAddress("1.2.3.4", 456, false); info.downstream_connection_info_provider_->setLocalAddress(addr); - checkMatcher(RBAC::OrMatcher(set, ProtobufMessage::getStrictValidationVisitor()), false, conn, - headers, info); + checkMatcher(RBAC::OrMatcher(set, ProtobufMessage::getStrictValidationVisitor(), factory_context), + false, conn, headers, info); perm = set.add_rules(); perm->mutable_destination_port_range()->set_start(123); perm->mutable_destination_port_range()->set_end(456); - checkMatcher(RBAC::OrMatcher(set, ProtobufMessage::getStrictValidationVisitor()), false, conn, - headers, info); + checkMatcher(RBAC::OrMatcher(set, ProtobufMessage::getStrictValidationVisitor(), factory_context), + false, conn, headers, info); perm = set.add_rules(); perm->set_any(true); - checkMatcher(RBAC::OrMatcher(set, ProtobufMessage::getStrictValidationVisitor()), true, conn, - headers, info); + checkMatcher(RBAC::OrMatcher(set, ProtobufMessage::getStrictValidationVisitor(), factory_context), + true, conn, headers, info); } TEST(OrMatcher, Principal_Set) { @@ -129,6 +136,7 @@ TEST(OrMatcher, Principal_Set) { cidr->set_address_prefix("1.2.3.0"); cidr->mutable_prefix_len()->set_value(24); + NiceMock factory_context; Envoy::Network::MockConnection conn; Envoy::Http::TestRequestHeaderMapImpl headers; NiceMock info; @@ -136,27 +144,31 @@ TEST(OrMatcher, Principal_Set) { Envoy::Network::Utility::parseInternetAddress("1.2.4.6", 456, false); info.downstream_connection_info_provider_->setDirectRemoteAddressForTest(addr); - checkMatcher(RBAC::OrMatcher(set), false, conn, headers, info); + checkMatcher(RBAC::OrMatcher(set, factory_context), false, conn, headers, info); id = set.add_ids(); id->set_any(true); - checkMatcher(RBAC::OrMatcher(set), true, conn, headers, info); + checkMatcher(RBAC::OrMatcher(set, factory_context), true, conn, headers, info); } TEST(NotMatcher, Permission) { + NiceMock factory_context; envoy::config::rbac::v3::Permission perm; perm.set_any(true); - checkMatcher(RBAC::NotMatcher(perm, ProtobufMessage::getStrictValidationVisitor()), false, - Envoy::Network::MockConnection()); + checkMatcher( + RBAC::NotMatcher(perm, ProtobufMessage::getStrictValidationVisitor(), factory_context), false, + Envoy::Network::MockConnection()); } TEST(NotMatcher, Principal) { + NiceMock factory_context; envoy::config::rbac::v3::Principal principal; principal.set_any(true); - checkMatcher(RBAC::NotMatcher(principal), false, Envoy::Network::MockConnection()); + checkMatcher(RBAC::NotMatcher(principal, factory_context), false, + Envoy::Network::MockConnection()); } TEST(HeaderMatcher, HeaderMatcher) { @@ -315,15 +327,16 @@ TEST(AuthenticatedMatcher, uriSanPeerCertificate) { EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); // We should check if any URI SAN matches. + NiceMock factory_context; envoy::config::rbac::v3::Principal::Authenticated auth; auth.mutable_principal_name()->set_exact("foo"); - checkMatcher(AuthenticatedMatcher(auth), true, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); auth.mutable_principal_name()->set_exact("baz"); - checkMatcher(AuthenticatedMatcher(auth), true, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); auth.mutable_principal_name()->set_exact("bar"); - checkMatcher(AuthenticatedMatcher(auth), false, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), false, conn); } TEST(AuthenticatedMatcher, dnsSanPeerCertificate) { @@ -344,14 +357,15 @@ TEST(AuthenticatedMatcher, dnsSanPeerCertificate) { // We should get check if any DNS SAN matches as URI SAN is not available. envoy::config::rbac::v3::Principal::Authenticated auth; + NiceMock factory_context; auth.mutable_principal_name()->set_exact("foo"); - checkMatcher(AuthenticatedMatcher(auth), true, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); auth.mutable_principal_name()->set_exact("baz"); - checkMatcher(AuthenticatedMatcher(auth), true, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); auth.mutable_principal_name()->set_exact("bar"); - checkMatcher(AuthenticatedMatcher(auth), false, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), false, conn); } TEST(AuthenticatedMatcher, subjectPeerCertificate) { @@ -366,11 +380,12 @@ TEST(AuthenticatedMatcher, subjectPeerCertificate) { EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); envoy::config::rbac::v3::Principal::Authenticated auth; + NiceMock factory_context; auth.mutable_principal_name()->set_exact("bar"); - checkMatcher(AuthenticatedMatcher(auth), true, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); auth.mutable_principal_name()->set_exact("foo"); - checkMatcher(AuthenticatedMatcher(auth), false, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), false, conn); } TEST(AuthenticatedMatcher, AnySSLSubject) { @@ -381,16 +396,18 @@ TEST(AuthenticatedMatcher, AnySSLSubject) { EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); envoy::config::rbac::v3::Principal::Authenticated auth; - checkMatcher(AuthenticatedMatcher(auth), true, conn); + NiceMock factory_context; + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); auth.mutable_principal_name()->MergeFrom(TestUtility::createRegexMatcher(".*")); - checkMatcher(AuthenticatedMatcher(auth), true, conn); + checkMatcher(AuthenticatedMatcher(auth, factory_context), true, conn); } TEST(AuthenticatedMatcher, NoSSL) { + NiceMock factory_context; Envoy::Network::MockConnection conn; EXPECT_CALL(Const(conn), ssl()).WillOnce(Return(nullptr)); - checkMatcher(AuthenticatedMatcher({}), false, conn); + checkMatcher(AuthenticatedMatcher({}, factory_context), false, conn); } TEST(MetadataMatcher, MetadataMatcher) { @@ -417,6 +434,7 @@ TEST(MetadataMatcher, MetadataMatcher) { } TEST(PolicyMatcher, PolicyMatcher) { + NiceMock factory_context; envoy::config::rbac::v3::Policy policy; policy.add_permissions()->set_destination_port(123); policy.add_permissions()->set_destination_port(456); @@ -424,7 +442,8 @@ TEST(PolicyMatcher, PolicyMatcher) { policy.add_principals()->mutable_authenticated()->mutable_principal_name()->set_exact("bar"); Expr::BuilderPtr builder = Expr::createBuilder(nullptr); - RBAC::PolicyMatcher matcher(policy, builder.get(), ProtobufMessage::getStrictValidationVisitor()); + RBAC::PolicyMatcher matcher(policy, builder.get(), ProtobufMessage::getStrictValidationVisitor(), + factory_context); Envoy::Network::MockConnection conn; Envoy::Http::TestRequestHeaderMapImpl headers; @@ -457,36 +476,52 @@ TEST(PolicyMatcher, PolicyMatcher) { } TEST(RequestedServerNameMatcher, ValidRequestedServerName) { + NiceMock factory_context; Envoy::Network::MockConnection conn; EXPECT_CALL(conn, requestedServerName()) .Times(9) .WillRepeatedly(Return(absl::string_view("www.cncf.io"))); - checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*cncf.io")), true, - conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*cncf.*")), true, conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher("www.*")), true, conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*io")), true, conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*")), true, conn); - - checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher("")), false, conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher("www.cncf.io")), true, - conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher("xyz.cncf.io")), false, - conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher("example.com")), false, - conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*cncf.io"), factory_context), + true, conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*cncf.*"), factory_context), + true, conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createRegexMatcher("www.*"), factory_context), true, + conn); + checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*io"), factory_context), + true, conn); + checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*"), factory_context), + true, conn); + + checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher(""), factory_context), + false, conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createExactMatcher("www.cncf.io"), factory_context), + true, conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createExactMatcher("xyz.cncf.io"), factory_context), + false, conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createExactMatcher("example.com"), factory_context), + false, conn); } TEST(RequestedServerNameMatcher, EmptyRequestedServerName) { + NiceMock factory_context; Envoy::Network::MockConnection conn; EXPECT_CALL(conn, requestedServerName()).Times(3).WillRepeatedly(Return(absl::string_view(""))); - checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*")), true, conn); + checkMatcher(RequestedServerNameMatcher(TestUtility::createRegexMatcher(".*"), factory_context), + true, conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher("")), true, conn); - checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher("example.com")), false, - conn); + checkMatcher(RequestedServerNameMatcher(TestUtility::createExactMatcher(""), factory_context), + true, conn); + checkMatcher( + RequestedServerNameMatcher(TestUtility::createExactMatcher("example.com"), factory_context), + false, conn); } TEST(PathMatcher, NoPathInHeader) { diff --git a/test/extensions/filters/common/rbac/mocks.h b/test/extensions/filters/common/rbac/mocks.h index 83088d8ab6ef..c1f9ac0916e9 100644 --- a/test/extensions/filters/common/rbac/mocks.h +++ b/test/extensions/filters/common/rbac/mocks.h @@ -17,9 +17,10 @@ namespace RBAC { class MockEngine : public RoleBasedAccessControlEngineImpl { public: MockEngine(const envoy::config::rbac::v3::RBAC& rules, + Server::Configuration::CommonFactoryContext& context, const EnforcementMode mode = EnforcementMode::Enforced) : RoleBasedAccessControlEngineImpl(rules, ProtobufMessage::getStrictValidationVisitor(), - mode){}; + context, mode){}; MOCK_METHOD(bool, handleAction, (const Envoy::Network::Connection&, const Envoy::Http::RequestHeaderMap&, diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index 29e638179176..c8578f2e2622 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -422,7 +422,8 @@ TEST_F(RoleBasedAccessControlFilterTest, RouteLocalOverride) { envoy::extensions::filters::http::rbac::v3::RBACPerRoute route_config; route_config.mutable_rbac()->mutable_rules()->set_action(envoy::config::rbac::v3::RBAC::DENY); - NiceMock engine{route_config.rbac().rules()}; + NiceMock factory_context; + NiceMock engine{route_config.rbac().rules(), factory_context}; NiceMock per_route_config_{route_config, context_}; From 9f6bc78508005f25ef7d7c0d3a24dc85bd4ce004 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Fri, 15 Mar 2024 08:48:16 -0700 Subject: [PATCH 13/36] Stringmatcher: factory context for ext_authz (#32922) Signed-off-by: Greg Greenway --- .../common/ext_authz/check_request_utils.cc | 18 ++++--- .../common/ext_authz/check_request_utils.h | 6 ++- .../common/ext_authz/ext_authz_http_impl.cc | 48 ++++++++++++------- .../common/ext_authz/ext_authz_http_impl.h | 15 ++++-- .../filters/http/ext_authz/config.cc | 7 ++- .../filters/http/ext_authz/ext_authz.h | 13 ++--- .../extensions/filters/common/ext_authz/BUILD | 2 + .../ext_authz/check_request_utils_test.cc | 5 +- .../ext_authz/ext_authz_http_impl_test.cc | 4 +- test/extensions/filters/http/ext_authz/BUILD | 2 + .../http/ext_authz/ext_authz_fuzz_test.cc | 9 ++-- .../filters/http/ext_authz/ext_authz_test.cc | 31 ++++++------ test/mocks/server/instance.cc | 1 + test/mocks/server/instance.h | 1 + 14 files changed, 98 insertions(+), 64 deletions(-) diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.cc b/source/extensions/filters/common/ext_authz/check_request_utils.cc index 8a62aa1cdc98..0e24523eccb8 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.cc +++ b/source/extensions/filters/common/ext_authz/check_request_utils.cc @@ -247,8 +247,9 @@ void CheckRequestUtils::createTcpCheck( MatcherSharedPtr CheckRequestUtils::toRequestMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, - bool add_http_headers) { - std::vector matchers(createStringMatchers(list)); + bool add_http_headers, + Server::Configuration::CommonFactoryContext& context) { + std::vector matchers(createStringMatchers(list, context)); if (add_http_headers) { const std::vector keys{ @@ -259,8 +260,9 @@ CheckRequestUtils::toRequestMatchers(const envoy::type::matcher::v3::ListStringM envoy::type::matcher::v3::StringMatcher matcher; matcher.set_exact(key.get()); matchers.push_back( - std::make_unique>( - matcher)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + matcher, context)); } } @@ -268,12 +270,14 @@ CheckRequestUtils::toRequestMatchers(const envoy::type::matcher::v3::ListStringM } std::vector -CheckRequestUtils::createStringMatchers(const envoy::type::matcher::v3::ListStringMatcher& list) { +CheckRequestUtils::createStringMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context) { std::vector matchers; for (const auto& matcher : list.patterns()) { matchers.push_back( - std::make_unique>( - matcher)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + matcher, context)); } return matchers; } diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.h b/source/extensions/filters/common/ext_authz/check_request_utils.h index 1390485c0ae0..9f436d2e3857 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.h +++ b/source/extensions/filters/common/ext_authz/check_request_utils.h @@ -113,9 +113,11 @@ class CheckRequestUtils { const Protobuf::Map& destination_labels); static MatcherSharedPtr toRequestMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, - bool add_http_headers); + bool add_http_headers, + Server::Configuration::CommonFactoryContext& context); static std::vector - createStringMatchers(const envoy::type::matcher::v3::ListStringMatcher& list); + createStringMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context); private: static void setAttrContextPeer(envoy::service::auth::v3::AttributeContext::Peer& peer, diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index bd6bfa0e26bd..101a991dc100 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -103,17 +103,20 @@ struct SuccessResponse { // Config ClientConfig::ClientConfig(const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& config, - uint32_t timeout, absl::string_view path_prefix) + uint32_t timeout, absl::string_view path_prefix, + Server::Configuration::CommonFactoryContext& context) : client_header_matchers_(toClientMatchers( - config.http_service().authorization_response().allowed_client_headers())), + config.http_service().authorization_response().allowed_client_headers(), context)), client_header_on_success_matchers_(toClientMatchersOnSuccess( - config.http_service().authorization_response().allowed_client_headers_on_success())), + config.http_service().authorization_response().allowed_client_headers_on_success(), + context)), to_dynamic_metadata_matchers_(toDynamicMetadataMatchers( - config.http_service().authorization_response().dynamic_metadata_from_headers())), + config.http_service().authorization_response().dynamic_metadata_from_headers(), context)), upstream_header_matchers_(toUpstreamMatchers( - config.http_service().authorization_response().allowed_upstream_headers())), + config.http_service().authorization_response().allowed_upstream_headers(), context)), upstream_header_to_append_matchers_(toUpstreamMatchers( - config.http_service().authorization_response().allowed_upstream_headers_to_append())), + config.http_service().authorization_response().allowed_upstream_headers_to_append(), + context)), cluster_name_(config.http_service().server_uri().cluster()), timeout_(timeout), path_prefix_(path_prefix), tracing_name_(fmt::format("async {} egress", config.http_service().server_uri().cluster())), @@ -122,20 +125,26 @@ ClientConfig::ClientConfig(const envoy::extensions::filters::http::ext_authz::v3 envoy::config::core::v3::HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD)) {} MatcherSharedPtr -ClientConfig::toClientMatchersOnSuccess(const envoy::type::matcher::v3::ListStringMatcher& list) { - std::vector matchers(CheckRequestUtils::createStringMatchers(list)); +ClientConfig::toClientMatchersOnSuccess(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context) { + std::vector matchers( + CheckRequestUtils::createStringMatchers(list, context)); return std::make_shared(std::move(matchers)); } MatcherSharedPtr -ClientConfig::toDynamicMetadataMatchers(const envoy::type::matcher::v3::ListStringMatcher& list) { - std::vector matchers(CheckRequestUtils::createStringMatchers(list)); +ClientConfig::toDynamicMetadataMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context) { + std::vector matchers( + CheckRequestUtils::createStringMatchers(list, context)); return std::make_shared(std::move(matchers)); } MatcherSharedPtr -ClientConfig::toClientMatchers(const envoy::type::matcher::v3::ListStringMatcher& list) { - std::vector matchers(CheckRequestUtils::createStringMatchers(list)); +ClientConfig::toClientMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context) { + std::vector matchers( + CheckRequestUtils::createStringMatchers(list, context)); // If list is empty, all authorization response headers, except Host, should be added to // the client response. @@ -143,8 +152,9 @@ ClientConfig::toClientMatchers(const envoy::type::matcher::v3::ListStringMatcher envoy::type::matcher::v3::StringMatcher matcher; matcher.set_exact(Http::Headers::get().Host.get()); matchers.push_back( - std::make_unique>( - matcher)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + matcher, context)); return std::make_shared(std::move(matchers)); } @@ -159,16 +169,18 @@ ClientConfig::toClientMatchers(const envoy::type::matcher::v3::ListStringMatcher envoy::type::matcher::v3::StringMatcher matcher; matcher.set_exact(key.get()); matchers.push_back( - std::make_unique>( - matcher)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + matcher, context)); } return std::make_shared(std::move(matchers)); } MatcherSharedPtr -ClientConfig::toUpstreamMatchers(const envoy::type::matcher::v3::ListStringMatcher& list) { - return std::make_unique(CheckRequestUtils::createStringMatchers(list)); +ClientConfig::toUpstreamMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context) { + return std::make_unique(CheckRequestUtils::createStringMatchers(list, context)); } RawHttpClientImpl::RawHttpClientImpl(Upstream::ClusterManager& cm, ClientConfigSharedPtr config) diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h index db611f4fad73..4211fc9f4ae4 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h @@ -25,7 +25,8 @@ namespace ExtAuthz { class ClientConfig { public: ClientConfig(const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& config, - uint32_t timeout, absl::string_view path_prefix); + uint32_t timeout, absl::string_view path_prefix, + Server::Configuration::CommonFactoryContext& context); /** * Returns the name of the authorization cluster. @@ -87,13 +88,17 @@ class ClientConfig { const Router::HeaderParser& requestHeaderParser() const { return *request_headers_parser_; } private: - static MatcherSharedPtr toClientMatchers(const envoy::type::matcher::v3::ListStringMatcher& list); + static MatcherSharedPtr toClientMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context); static MatcherSharedPtr - toClientMatchersOnSuccess(const envoy::type::matcher::v3::ListStringMatcher& list); + toClientMatchersOnSuccess(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context); static MatcherSharedPtr - toDynamicMetadataMatchers(const envoy::type::matcher::v3::ListStringMatcher& list); + toDynamicMetadataMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context); static MatcherSharedPtr - toUpstreamMatchers(const envoy::type::matcher::v3::ListStringMatcher& list); + toUpstreamMatchers(const envoy::type::matcher::v3::ListStringMatcher& list, + Server::Configuration::CommonFactoryContext& context); const MatcherSharedPtr request_header_matchers_; const MatcherSharedPtr client_header_matchers_; diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index 881609481e96..87a20cbd12e9 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -25,9 +25,8 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { auto& server_context = context.serverFactoryContext(); - const auto filter_config = std::make_shared( - proto_config, context.scope(), server_context.runtime(), server_context.httpContext(), - stats_prefix, server_context.bootstrap()); + const auto filter_config = + std::make_shared(proto_config, context.scope(), stats_prefix, server_context); // The callback is created in main thread and executed in worker thread, variables except factory // context must be captured by value into the callback. Http::FilterFactoryCb callback; @@ -38,7 +37,7 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( timeout, DefaultTimeout); const auto client_config = std::make_shared( - proto_config, timeout_ms, proto_config.http_service().path_prefix()); + proto_config, timeout_ms, proto_config.http_service().path_prefix(), server_context); callback = [filter_config, client_config, &server_context](Http::FilterChainFactoryCallbacks& callbacks) { auto client = std::make_unique( diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 7a24e2d84deb..a3d78bd2787c 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -56,8 +56,8 @@ class FilterConfig { public: FilterConfig(const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& config, - Stats::Scope& scope, Runtime::Loader& runtime, Http::Context& http_context, - const std::string& stats_prefix, envoy::config::bootstrap::v3::Bootstrap& bootstrap) + Stats::Scope& scope, const std::string& stats_prefix, + Server::Configuration::ServerFactoryContext& factory_context) : allow_partial_message_(config.with_request_body().allow_partial_message()), failure_mode_allow_(config.failure_mode_allow()), failure_mode_allow_header_add_(config.failure_mode_allow_header_add()), @@ -70,7 +70,7 @@ class FilterConfig { pack_as_bytes_(config.has_http_service() || config.with_request_body().pack_as_bytes()), status_on_error_(toErrorCode(config.status_on_error().code())), scope_(scope), - runtime_(runtime), http_context_(http_context), + runtime_(factory_context.runtime()), http_context_(factory_context.httpContext()), filter_enabled_(config.has_filter_enabled() ? absl::optional( Runtime::FractionalPercent(config.filter_enabled(), runtime_)) @@ -103,6 +103,7 @@ class FilterConfig { ext_authz_error_(pool_.add(createPoolStatName(config.stat_prefix(), "error"))), ext_authz_failure_mode_allowed_( pool_.add(createPoolStatName(config.stat_prefix(), "failure_mode_allowed"))) { + auto bootstrap = factory_context.bootstrap(); auto labels_key_it = bootstrap.node().metadata().fields().find(config.bootstrap_metadata_labels_key()); if (labels_key_it != bootstrap.node().metadata().fields().end()) { @@ -123,14 +124,14 @@ class FilterConfig { // Method, Path and Host). if (config.has_grpc_service() && config.has_allowed_headers()) { request_header_matchers_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( - config.allowed_headers(), false); + config.allowed_headers(), false, factory_context); } else if (config.has_http_service()) { if (config.http_service().authorization_request().has_allowed_headers()) { request_header_matchers_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( - config.http_service().authorization_request().allowed_headers(), true); + config.http_service().authorization_request().allowed_headers(), true, factory_context); } else { request_header_matchers_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( - config.allowed_headers(), true); + config.allowed_headers(), true, factory_context); } } } diff --git a/test/extensions/filters/common/ext_authz/BUILD b/test/extensions/filters/common/ext_authz/BUILD index 5c87d0b63ad4..2802d9397a4f 100644 --- a/test/extensions/filters/common/ext_authz/BUILD +++ b/test/extensions/filters/common/ext_authz/BUILD @@ -19,6 +19,7 @@ envoy_cc_test( "//source/extensions/filters/common/ext_authz:ext_authz_interface", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:utility_lib", @@ -45,6 +46,7 @@ envoy_cc_test( deps = [ "//source/extensions/filters/common/ext_authz:ext_authz_http_lib", "//test/extensions/filters/common/ext_authz:ext_authz_test_common", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:cluster_manager_mocks", "@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg_cc_proto", diff --git a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc index 9486088ba523..e87ede32c7ae 100644 --- a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc +++ b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" @@ -110,11 +111,13 @@ class CheckRequestUtilsTest : public testing::Test { } MatcherSharedPtr createRequestHeaderMatchers() { + NiceMock factory_context; envoy::extensions::filters::http::ext_authz::v3::ExtAuthz ext_autz_proto_; ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("foo"); ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("hello"); ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("duplicate"); - return CheckRequestUtils::toRequestMatchers(ext_autz_proto_.allowed_headers(), false); + return CheckRequestUtils::toRequestMatchers(ext_autz_proto_.allowed_headers(), false, + factory_context); } Network::Address::InstanceConstSharedPtr addr_; diff --git a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc index e7ca63a9a97b..6322721b6729 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc @@ -10,6 +10,7 @@ #include "test/extensions/filters/common/ext_authz/mocks.h" #include "test/extensions/filters/common/ext_authz/test_common.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/cluster_manager.h" @@ -90,7 +91,7 @@ class ExtAuthzHttpClientTest : public testing::Test { } cm_.initializeThreadLocalClusters({"ext_authz"}); - return std::make_shared(proto_config, timeout, path_prefix); + return std::make_shared(proto_config, timeout, path_prefix, factory_context_); } void dynamicMetadataTest(CheckStatus status, const std::string& http_status) { @@ -178,6 +179,7 @@ class ExtAuthzHttpClientTest : public testing::Test { return message_ptr; } + NiceMock factory_context_; NiceMock cm_; NiceMock async_client_; NiceMock async_request_; diff --git a/test/extensions/filters/http/ext_authz/BUILD b/test/extensions/filters/http/ext_authz/BUILD index 6aaad4beb600..3ba58efb6997 100644 --- a/test/extensions/filters/http/ext_authz/BUILD +++ b/test/extensions/filters/http/ext_authz/BUILD @@ -32,6 +32,7 @@ envoy_extension_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/proto:helloworld_proto_cc_proto", @@ -110,6 +111,7 @@ envoy_cc_fuzz_test( "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", + "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc index d8723088ef81..7745a37cb4d5 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc @@ -11,6 +11,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "gmock/gmock.h" @@ -74,7 +75,7 @@ class FuzzerMocks { filter_metadata_.CopyFrom(metadata); } - NiceMock runtime_; + NiceMock factory_context_; NiceMock decoder_callbacks_; NiceMock encoder_callbacks_; Network::Address::InstanceConstSharedPtr addr_; @@ -95,16 +96,14 @@ DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::ext_authz::ExtAuthzT static FuzzerMocks mocks; NiceMock stats_store; static ScopedInjectableLoader engine(std::make_unique()); - envoy::config::bootstrap::v3::Bootstrap bootstrap; - Http::ContextImpl http_context(stats_store.symbolTable()); // Prepare filter. const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config = input.config(); FilterConfigSharedPtr config; try { - config = std::make_shared(proto_config, *stats_store.rootScope(), mocks.runtime_, - http_context, "ext_authz_prefix", bootstrap); + config = std::make_shared(proto_config, *stats_store.rootScope(), + "ext_authz_prefix", mocks.factory_context_); } catch (const EnvoyException& e) { ENVOY_LOG_MISC(debug, "EnvoyException during filter config validation: {}", e.what()); return; diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index b7164fa12778..6b66f26bda40 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -24,6 +24,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/proto/helloworld.pb.h" @@ -54,15 +55,15 @@ namespace { template class HttpFilterTestBase : public T { public: - HttpFilterTestBase() : http_context_(stats_store_.symbolTable()) {} + HttpFilterTestBase() {} void initialize(std::string&& yaml) { envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config{}; if (!yaml.empty()) { TestUtility::loadFromYaml(yaml, proto_config); } - config_ = std::make_shared(proto_config, *stats_store_.rootScope(), runtime_, - http_context_, "ext_authz_prefix", bootstrap_); + config_ = std::make_shared(proto_config, *stats_store_.rootScope(), + "ext_authz_prefix", factory_context_); client_ = new Filters::Common::ExtAuthz::MockClient(); filter_ = std::make_unique(config_, Filters::Common::ExtAuthz::ClientPtr{client_}); filter_->setDecoderFilterCallbacks(decoder_filter_callbacks_); @@ -122,7 +123,6 @@ template class HttpFilterTestBase : public T { NiceMock stats_store_; Stats::Scope& stats_scope_{*stats_store_.rootScope()}; - envoy::config::bootstrap::v3::Bootstrap bootstrap_; FilterConfigSharedPtr config_; Filters::Common::ExtAuthz::MockClient* client_; std::unique_ptr filter_; @@ -132,11 +132,10 @@ template class HttpFilterTestBase : public T { Http::TestRequestHeaderMapImpl request_headers_; Http::TestRequestTrailerMapImpl request_trailers_; Buffer::OwnedImpl data_; - NiceMock runtime_; + NiceMock factory_context_; NiceMock cm_; Network::Address::InstanceConstSharedPtr addr_; NiceMock connection_; - Http::ContextImpl http_context_; }; class HttpFilterTest : public HttpFilterTestBase { @@ -1693,7 +1692,7 @@ TEST_F(HttpFilterTest, FilterDisabled) { denominator: HUNDRED )EOF"); - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(0)))) .WillByDefault(Return(false)); @@ -1720,7 +1719,7 @@ TEST_F(HttpFilterTest, FilterEnabled) { prepareCheck(); - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(100)))) .WillByDefault(Return(true)); @@ -1824,7 +1823,7 @@ TEST_F(HttpFilterTest, FilterEnabledButMetadataDisabled) { )EOF"); // Enable in filter_enabled. - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(100)))) .WillByDefault(Return(true)); @@ -1869,7 +1868,7 @@ TEST_F(HttpFilterTest, FilterDisabledButMetadataEnabled) { )EOF"); // Disable in filter_enabled. - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(0)))) .WillByDefault(Return(false)); @@ -1914,7 +1913,7 @@ TEST_F(HttpFilterTest, FilterEnabledAndMetadataEnabled) { )EOF"); // Enable in filter_enabled. - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(100)))) .WillByDefault(Return(true)); @@ -1957,12 +1956,13 @@ TEST_F(HttpFilterTest, FilterDenyAtDisable) { value: true )EOF"); - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(0)))) .WillByDefault(Return(false)); - ON_CALL(runtime_.snapshot_, featureEnabled("http.ext_authz.enabled", false)) + ON_CALL(factory_context_.runtime_loader_.snapshot_, + featureEnabled("http.ext_authz.enabled", false)) .WillByDefault(Return(true)); // Make sure check is not called. @@ -1990,12 +1990,13 @@ TEST_F(HttpFilterTest, FilterAllowAtDisable) { value: false )EOF"); - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("http.ext_authz.enabled", testing::Matcher(Percent(0)))) .WillByDefault(Return(false)); - ON_CALL(runtime_.snapshot_, featureEnabled("http.ext_authz.enabled", false)) + ON_CALL(factory_context_.runtime_loader_.snapshot_, + featureEnabled("http.ext_authz.enabled", false)) .WillByDefault(Return(false)); // Make sure check is not called. diff --git a/test/mocks/server/instance.cc b/test/mocks/server/instance.cc index eca664bd89a8..ebee702e5dbb 100644 --- a/test/mocks/server/instance.cc +++ b/test/mocks/server/instance.cc @@ -48,6 +48,7 @@ MockInstance::MockInstance() ON_CALL(*this, overloadManager()).WillByDefault(ReturnRef(overload_manager_)); ON_CALL(*this, messageValidationContext()).WillByDefault(ReturnRef(validation_context_)); ON_CALL(*this, statsConfig()).WillByDefault(ReturnRef(*stats_config_)); + ON_CALL(*this, regexEngine()).WillByDefault(ReturnRef(regex_engine_)); ON_CALL(*this, serverFactoryContext()).WillByDefault(ReturnRef(*server_factory_context_)); ON_CALL(*this, transportSocketFactoryContext()) .WillByDefault(ReturnRef(*transport_socket_factory_context_)); diff --git a/test/mocks/server/instance.h b/test/mocks/server/instance.h index de109c9c5a97..b5418fd5f6ab 100644 --- a/test/mocks/server/instance.h +++ b/test/mocks/server/instance.h @@ -102,6 +102,7 @@ class MockInstance : public Instance { std::shared_ptr> transport_socket_factory_context_; Extensions::TransportSockets::Tls::ContextManagerImpl ssl_context_manager_; + Regex::GoogleReEngine regex_engine_; }; } // namespace Server From fa21d1148cc6531e0c0203fbcae55b18d47f802b Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Fri, 15 Mar 2024 09:09:40 -0700 Subject: [PATCH 14/36] stringmatcher: import updated xds types and support custom extension (#32856) Signed-off-by: Greg Greenway --- api/bazel/repository_locations.bzl | 6 +-- source/common/common/matchers.h | 78 ++++++++++-------------------- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 4ba96d3a72f0..902923662e0d 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -39,9 +39,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "xDS API Working Group (xDS-WG)", project_url = "https://github.com/cncf/xds", # During the UDPA -> xDS migration, we aren't working with releases. - version = "3a472e524827f72d1ad621c4983dd5af54c46776", - sha256 = "dc305e20c9fa80822322271b50aa2ffa917bf4fd3973bcec52bfc28dc32c5927", - release_date = "2023-11-16", + version = "ee0267137e252710af66562e0d54bcf8669b74b1", + sha256 = "0adcd7a74d5158f710612f38e5b9ec8bd4aabd2f53ff7905e0c198028ca68dfc", + release_date = "2024-03-12", strip_prefix = "xds-{version}", urls = ["https://github.com/cncf/xds/archive/{version}.tar.gz"], use_category = ["api"], diff --git a/source/common/common/matchers.h b/source/common/common/matchers.h index 49b68c705a42..1ee93a032295 100644 --- a/source/common/common/matchers.h +++ b/source/common/common/matchers.h @@ -114,13 +114,35 @@ class PrivateStringMatcherImpl : public ValueMatcher, public StringMatcher { // Cache the lowercase conversion of the Contains matcher for future use lowercase_contains_match_ = absl::AsciiStrToLower(matcher_.contains()); } - } else { - initialize(matcher, tls, api); + } else if (matcher.has_custom()) { + custom_ = getExtensionStringMatcher(matcher.custom(), *tls, *api); } } // StringMatcher - bool match(const absl::string_view value) const override { return match(value, matcher_); } + bool match(const absl::string_view value) const override { + switch (matcher_.match_pattern_case()) { + case StringMatcherType::MatchPatternCase::kExact: + return matcher_.ignore_case() ? absl::EqualsIgnoreCase(value, matcher_.exact()) + : value == matcher_.exact(); + case StringMatcherType::MatchPatternCase::kPrefix: + return matcher_.ignore_case() ? absl::StartsWithIgnoreCase(value, matcher_.prefix()) + : absl::StartsWith(value, matcher_.prefix()); + case StringMatcherType::MatchPatternCase::kSuffix: + return matcher_.ignore_case() ? absl::EndsWithIgnoreCase(value, matcher_.suffix()) + : absl::EndsWith(value, matcher_.suffix()); + case StringMatcherType::MatchPatternCase::kContains: + return matcher_.ignore_case() + ? absl::StrContains(absl::AsciiStrToLower(value), lowercase_contains_match_) + : absl::StrContains(value, matcher_.contains()); + case StringMatcherType::MatchPatternCase::kSafeRegex: + return regex_->match(value); + case StringMatcherType::MatchPatternCase::kCustom: + return custom_->match(value); + default: + PANIC("unexpected"); + } + } bool match(const ProtobufWkt::Value& value) const override { @@ -151,56 +173,6 @@ class PrivateStringMatcherImpl : public ValueMatcher, public StringMatcher { } private: - // Type `xds::type::matcher::v3::StringMatcher` doesn't have an extension type, so use function - // overloading to only handle that case for type `envoy::type::matcher::v3::StringMatcher` to - // prevent compilation errors on use of `kCustom`. - - void initialize(const xds::type::matcher::v3::StringMatcher&, ThreadLocal::SlotAllocator*, - Api::Api*) {} - - void initialize(const envoy::type::matcher::v3::StringMatcher& matcher, - ThreadLocal::SlotAllocator* tls, Api::Api* api) { - if (matcher.has_custom()) { - custom_ = getExtensionStringMatcher(matcher.custom(), *tls, *api); - } - } - - bool match(const absl::string_view value, const xds::type::matcher::v3::StringMatcher&) const { - return matchCommon(value); - } - - bool match(const absl::string_view value, - const envoy::type::matcher::v3::StringMatcher& matcher) const { - if (matcher.match_pattern_case() == - envoy::type::matcher::v3::StringMatcher::MatchPatternCase::kCustom) { - return custom_->match(value); - } - return matchCommon(value); - } - - // StringMatcher - bool matchCommon(const absl::string_view value) const { - switch (matcher_.match_pattern_case()) { - case StringMatcherType::MatchPatternCase::kExact: - return matcher_.ignore_case() ? absl::EqualsIgnoreCase(value, matcher_.exact()) - : value == matcher_.exact(); - case StringMatcherType::MatchPatternCase::kPrefix: - return matcher_.ignore_case() ? absl::StartsWithIgnoreCase(value, matcher_.prefix()) - : absl::StartsWith(value, matcher_.prefix()); - case StringMatcherType::MatchPatternCase::kSuffix: - return matcher_.ignore_case() ? absl::EndsWithIgnoreCase(value, matcher_.suffix()) - : absl::EndsWith(value, matcher_.suffix()); - case StringMatcherType::MatchPatternCase::kContains: - return matcher_.ignore_case() - ? absl::StrContains(absl::AsciiStrToLower(value), lowercase_contains_match_) - : absl::StrContains(value, matcher_.contains()); - case StringMatcherType::MatchPatternCase::kSafeRegex: - return regex_->match(value); - default: - PANIC("unexpected"); - } - } - const StringMatcherType matcher_; Regex::CompiledMatcherPtr regex_; std::string lowercase_contains_match_; From e8ea33a86a0271ff410659ae3755dc513222322a Mon Sep 17 00:00:00 2001 From: Xie Zhihao Date: Sat, 16 Mar 2024 01:14:10 +0800 Subject: [PATCH 15/36] io_uring: add io_uring socket handle (#32265) * io_uring: add io_uring socket io handle Signed-off-by: Xie Zhihao --- source/common/io/io_uring_worker_impl.h | 2 + source/common/network/BUILD | 24 +- .../network/io_uring_socket_handle_impl.cc | 415 ++++++++ .../network/io_uring_socket_handle_impl.h | 94 ++ .../common/network/socket_interface_impl.cc | 28 +- source/common/network/socket_interface_impl.h | 6 +- test/common/network/BUILD | 31 + ...ing_socket_handle_impl_integration_test.cc | 973 ++++++++++++++++++ .../io_uring_socket_handle_impl_test.cc | 133 +++ tools/spelling/spelling_dictionary.txt | 1 + 10 files changed, 1700 insertions(+), 7 deletions(-) create mode 100644 source/common/network/io_uring_socket_handle_impl.cc create mode 100644 source/common/network/io_uring_socket_handle_impl.h create mode 100644 test/common/network/io_uring_socket_handle_impl_integration_test.cc create mode 100644 test/common/network/io_uring_socket_handle_impl_test.cc diff --git a/source/common/io/io_uring_worker_impl.h b/source/common/io/io_uring_worker_impl.h index 1aa2222592d7..b2ebd1d38049 100644 --- a/source/common/io/io_uring_worker_impl.h +++ b/source/common/io/io_uring_worker_impl.h @@ -201,6 +201,8 @@ class IoUringServerSocket : public IoUringSocketEntry { void onShutdown(Request* req, int32_t result, bool injected) override; void onCancel(Request* req, int32_t result, bool injected) override; + Buffer::OwnedImpl& getReadBuffer() { return read_buf_; } + protected: // Since the write of IoUringSocket is async, there may have write request is on the fly when // close the socket. This timeout is setting for a time to wait the write request done. diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 53ca953779a4..4268d592f29c 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -250,25 +250,43 @@ envoy_cc_library( "io_socket_handle_impl.cc", "socket_interface_impl.cc", "win32_socket_handle_impl.cc", - ], + ] + select({ + "//bazel:linux": [ + "io_uring_socket_handle_impl.cc", + ], + "//conditions:default": [], + }), hdrs = [ "io_socket_handle_base_impl.h", "io_socket_handle_impl.h", "socket_interface_impl.h", "win32_socket_handle_impl.h", - ], + ] + select({ + "//bazel:linux": [ + "io_uring_socket_handle_impl.h", + ], + "//conditions:default": [], + }), deps = [ ":address_lib", ":io_socket_error_lib", ":socket_interface_lib", ":socket_lib", + "//envoy/common/io:io_uring_interface", "//envoy/event:dispatcher_interface", "//envoy/network:io_handle_interface", "//source/common/api:os_sys_calls_lib", "//source/common/buffer:buffer_lib", "//source/common/event:dispatcher_includes", "@envoy_api//envoy/extensions/network/socket_interface/v3:pkg_cc_proto", - ], + ] + select({ + "//bazel:linux": [ + "//source/common/io:io_uring_impl_lib", + "//source/common/io:io_uring_worker_factory_impl_lib", + "//source/common/io:io_uring_worker_lib", + ], + "//conditions:default": [], + }), alwayslink = LEGACY_ALWAYSLINK, ) diff --git a/source/common/network/io_uring_socket_handle_impl.cc b/source/common/network/io_uring_socket_handle_impl.cc new file mode 100644 index 000000000000..330a250edd61 --- /dev/null +++ b/source/common/network/io_uring_socket_handle_impl.cc @@ -0,0 +1,415 @@ +#include "source/common/network/io_uring_socket_handle_impl.h" + +#include "envoy/buffer/buffer.h" +#include "envoy/common/exception.h" +#include "envoy/event/dispatcher.h" + +#include "source/common/api/os_sys_calls_impl.h" +#include "source/common/common/assert.h" +#include "source/common/common/utility.h" +#include "source/common/io/io_uring_worker_impl.h" +#include "source/common/network/address_impl.h" +#include "source/common/network/io_socket_error_impl.h" +#include "source/common/network/io_socket_handle_impl.h" +#include "source/common/network/socket_interface_impl.h" + +namespace Envoy { +namespace Network { + +IoUringSocketHandleImpl::IoUringSocketHandleImpl(Io::IoUringWorkerFactory& io_uring_worker_factory, + os_fd_t fd, bool socket_v6only, + absl::optional domain, bool is_server_socket) + : IoSocketHandleBaseImpl(fd, socket_v6only, domain), + io_uring_worker_factory_(io_uring_worker_factory), + io_uring_socket_type_(is_server_socket ? IoUringSocketType::Server + : IoUringSocketType::Unknown) { + ENVOY_LOG(trace, "construct io uring socket handle, fd = {}, type = {}", fd_, + ioUringSocketTypeStr()); +} + +IoUringSocketHandleImpl::~IoUringSocketHandleImpl() { + ENVOY_LOG(trace, "~IoUringSocketHandleImpl, type = {}", ioUringSocketTypeStr()); + if (SOCKET_VALID(fd_)) { + // If the socket is owned by the main thread like a listener, it may outlive the IoUringWorker. + // We have to ensure that the current thread has been registered and the io_uring in the thread + // is still available. + // TODO(zhxie): for current usage of server socket and client socket, the check may be + // redundant. + if (io_uring_socket_type_ != IoUringSocketType::Unknown && + io_uring_socket_type_ != IoUringSocketType::Accept && + io_uring_worker_factory_.currentThreadRegistered() && io_uring_socket_.has_value()) { + if (io_uring_socket_->getStatus() != Io::IoUringSocketStatus::Closed) { + io_uring_socket_.ref().close(false); + } + } else { + // The TLS slot has been shut down by this moment with io_uring wiped out, thus better use the + // POSIX system call instead of IoUringSocketHandleImpl::close(). + ::close(fd_); + } + } +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::close() { + ENVOY_LOG(trace, "close, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + ASSERT(SOCKET_VALID(fd_)); + + if (io_uring_socket_type_ == IoUringSocketType::Unknown || + io_uring_socket_type_ == IoUringSocketType::Accept || !io_uring_socket_.has_value()) { + if (file_event_) { + file_event_.reset(); + } + ::close(fd_); + } else { + io_uring_socket_.ref().close(false); + io_uring_socket_.reset(); + } + SET_SOCKET_INVALID(fd_); + return Api::ioCallUint64ResultNoError(); +} + +Api::IoCallUint64Result +IoUringSocketHandleImpl::readv(uint64_t max_length, Buffer::RawSlice* slices, uint64_t num_slice) { + ENVOY_LOG(debug, "readv, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + Api::IoCallUint64Result result = copyOut(max_length, slices, num_slice); + if (result.ok()) { + // If the return value is 0, there should be a remote close. Return the value directly. + if (result.return_value_ != 0) { + io_uring_socket_->getReadParam()->buf_.drain(result.return_value_); + } + } + return result; +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::read(Buffer::Instance& buffer, + absl::optional max_length_opt) { + ENVOY_LOG(trace, "read, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + auto read_result = checkReadResult(); + if (read_result.has_value()) { + return std::move(*read_result); + } + + const OptRef& read_param = io_uring_socket_->getReadParam(); + uint64_t max_read_length = + std::min(max_length_opt.value_or(UINT64_MAX), read_param->buf_.length()); + buffer.move(read_param->buf_, max_read_length); + return {max_read_length, IoSocketError::none()}; +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::writev(const Buffer::RawSlice* slices, + uint64_t num_slice) { + ENVOY_LOG(trace, "writev, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + auto write_result = checkWriteResult(); + if (write_result.has_value()) { + return std::move(*write_result); + } + + uint64_t ret = io_uring_socket_->write(slices, num_slice); + return {ret, IoSocketError::none()}; +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::write(Buffer::Instance& buffer) { + ENVOY_LOG(trace, "write {}, fd = {}, type = {}", buffer.length(), fd_, ioUringSocketTypeStr()); + + auto write_result = checkWriteResult(); + if (write_result.has_value()) { + return std::move(*write_result); + } + + uint64_t buffer_size = buffer.length(); + io_uring_socket_->write(buffer); + return {buffer_size, IoSocketError::none()}; +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::sendmsg(const Buffer::RawSlice*, uint64_t, int, + const Address::Ip*, + const Address::Instance&) { + ENVOY_LOG(trace, "sendmsg, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + return Network::IoSocketError::ioResultSocketInvalidAddress(); +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::recvmsg(Buffer::RawSlice*, const uint64_t, + uint32_t, RecvMsgOutput&) { + ENVOY_LOG(trace, "recvmsg, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + return Network::IoSocketError::ioResultSocketInvalidAddress(); +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::recvmmsg(RawSliceArrays&, uint32_t, + RecvMsgOutput&) { + ENVOY_LOG(trace, "recvmmsg, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + return Network::IoSocketError::ioResultSocketInvalidAddress(); +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::recv(void* buffer, size_t length, int flags) { + ASSERT(io_uring_socket_.has_value()); + ENVOY_LOG(trace, "recv, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + // The only used flag in Envoy is MSG_PEEK for listener filters, including TLS inspectors. + ASSERT(flags == 0 || flags == MSG_PEEK); + Buffer::RawSlice slice; + slice.mem_ = buffer; + slice.len_ = length; + if (flags == 0) { + return readv(length, &slice, 1); + } + + return copyOut(length, &slice, 1); +} + +Api::SysCallIntResult IoUringSocketHandleImpl::bind(Address::InstanceConstSharedPtr address) { + ENVOY_LOG(trace, "bind {}, fd = {}, io_uring_socket_type = {}", address->asString(), fd_, + ioUringSocketTypeStr()); + return Api::OsSysCallsSingleton::get().bind(fd_, address->sockAddr(), address->sockAddrLen()); +} + +Api::SysCallIntResult IoUringSocketHandleImpl::listen(int backlog) { + ENVOY_LOG(trace, "listen, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + ASSERT(io_uring_socket_type_ == IoUringSocketType::Unknown); + + io_uring_socket_type_ = IoUringSocketType::Accept; + setBlocking(false); + return Api::OsSysCallsSingleton::get().listen(fd_, backlog); +} + +IoHandlePtr IoUringSocketHandleImpl::accept(struct sockaddr* addr, socklen_t* addrlen) { + ENVOY_LOG(trace, "accept, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + ASSERT(io_uring_socket_type_ == IoUringSocketType::Accept); + + auto result = Api::OsSysCallsSingleton::get().accept(fd_, addr, addrlen); + if (SOCKET_INVALID(result.return_value_)) { + return nullptr; + } + return std::make_unique(io_uring_worker_factory_, result.return_value_, + socket_v6only_, domain_, true); +} + +Api::SysCallIntResult IoUringSocketHandleImpl::connect(Address::InstanceConstSharedPtr address) { + ENVOY_LOG(trace, "connect, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + ASSERT(io_uring_socket_type_ == IoUringSocketType::Client); + + io_uring_socket_->connect(address); + return Api::SysCallIntResult{-1, EINPROGRESS}; +} + +Api::SysCallIntResult IoUringSocketHandleImpl::getOption(int level, int optname, void* optval, + socklen_t* optlen) { + // io_uring socket does not populate connect error in getsockopt. Instead, the connect error is + // returned in onConnect() handling. We will imitate the default socket behavior here for client + // socket with optname SO_ERROR, which is only used to check connect error. + if (io_uring_socket_type_ == IoUringSocketType::Client && optname == SO_ERROR && + io_uring_socket_.has_value()) { + int* intval = static_cast(optval); + *intval = -io_uring_socket_->getWriteParam()->result_; + *optlen = sizeof(int); + return {0, 0}; + } + + return IoSocketHandleBaseImpl::getOption(level, optname, optval, optlen); +} + +IoHandlePtr IoUringSocketHandleImpl::duplicate() { + ENVOY_LOG(trace, "duplicate, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + Api::SysCallSocketResult result = Api::OsSysCallsSingleton::get().duplicate(fd_); + RELEASE_ASSERT(result.return_value_ != -1, + fmt::format("duplicate failed for '{}': ({}) {}", fd_, result.errno_, + errorDetails(result.errno_))); + return SocketInterfaceImpl::makePlatformSpecificSocket(result.return_value_, socket_v6only_, + domain_, &io_uring_worker_factory_); +} + +void IoUringSocketHandleImpl::initializeFileEvent(Event::Dispatcher& dispatcher, + Event::FileReadyCb cb, + Event::FileTriggerType trigger, uint32_t events) { + ENVOY_LOG(trace, "initialize file event, fd = {}, type = {}, has socket = {}", fd_, + ioUringSocketTypeStr(), io_uring_socket_.has_value()); + + // The IoUringSocket has already been created. It usually happened after a resetFileEvents. + if (io_uring_socket_.has_value()) { + if (&io_uring_socket_->getIoUringWorker().dispatcher() == + &io_uring_worker_factory_.getIoUringWorker()->dispatcher()) { + io_uring_socket_->setFileReadyCb(std::move(cb)); + io_uring_socket_->enableRead(); + io_uring_socket_->enableCloseEvent(events & Event::FileReadyType::Closed); + } else { + ENVOY_LOG(trace, "initialize file event from another thread, fd = {}, type = {}", fd_, + ioUringSocketTypeStr()); + Thread::CondVar wait_cv; + Thread::MutexBasicLockable mutex; + Buffer::OwnedImpl buf; + os_fd_t fd = io_uring_socket_->fd(); + + { + Thread::LockGuard lock(mutex); + // Close the original socket in its running thread. + io_uring_socket_->getIoUringWorker().dispatcher().post( + [&origin_socket = io_uring_socket_, &wait_cv, &mutex, &buf]() { + // Move the data of original socket's read buffer to the temporary buf. + origin_socket->close(true, [&wait_cv, &mutex, &buf](Buffer::Instance& buffer) { + Thread::LockGuard lock(mutex); + buf.move(buffer); + wait_cv.notifyOne(); + }); + }); + wait_cv.wait(mutex); + } + + // Move the temporary buf to the newly created one. + io_uring_socket_ = io_uring_worker_factory_.getIoUringWorker()->addServerSocket( + fd, buf, std::move(cb), events & Event::FileReadyType::Closed); + } + return; + } + + switch (io_uring_socket_type_) { + case IoUringSocketType::Accept: + file_event_ = dispatcher.createFileEvent(fd_, cb, trigger, events); + break; + case IoUringSocketType::Server: + io_uring_socket_ = io_uring_worker_factory_.getIoUringWorker()->addServerSocket( + fd_, std::move(cb), events & Event::FileReadyType::Closed); + break; + case IoUringSocketType::Unknown: + case IoUringSocketType::Client: + io_uring_socket_type_ = IoUringSocketType::Client; + io_uring_socket_ = io_uring_worker_factory_.getIoUringWorker()->addClientSocket( + fd_, std::move(cb), events & Event::FileReadyType::Closed); + break; + } +} + +void IoUringSocketHandleImpl::activateFileEvents(uint32_t events) { + ENVOY_LOG(trace, "activate file events {}, fd = {}, type = {}", events, fd_, + ioUringSocketTypeStr()); + + if (io_uring_socket_type_ == IoUringSocketType::Accept) { + ASSERT(file_event_ != nullptr); + file_event_->activate(events); + return; + } + + if (events & Event::FileReadyType::Read) { + io_uring_socket_->injectCompletion(Io::Request::RequestType::Read); + } + if (events & Event::FileReadyType::Write) { + io_uring_socket_->injectCompletion(Io::Request::RequestType::Write); + } +} + +void IoUringSocketHandleImpl::enableFileEvents(uint32_t events) { + ENVOY_LOG(trace, "enable file events {}, fd = {}, type = {}", events, fd_, + ioUringSocketTypeStr()); + + if (io_uring_socket_type_ == IoUringSocketType::Accept) { + ASSERT(file_event_ != nullptr); + file_event_->setEnabled(events); + return; + } + + if (events & Event::FileReadyType::Read) { + io_uring_socket_->enableRead(); + } else { + io_uring_socket_->disableRead(); + } + io_uring_socket_->enableCloseEvent(events & Event::FileReadyType::Closed); +} + +void IoUringSocketHandleImpl::resetFileEvents() { + ENVOY_LOG(trace, "reset file events, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + if (io_uring_socket_type_ == IoUringSocketType::Accept) { + file_event_.reset(); + return; + } + + io_uring_socket_->disableRead(); + io_uring_socket_->enableCloseEvent(false); +} + +Api::SysCallIntResult IoUringSocketHandleImpl::shutdown(int how) { + ENVOY_LOG(trace, "shutdown, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + + ASSERT(io_uring_socket_type_ == IoUringSocketType::Server || + io_uring_socket_type_ == IoUringSocketType::Client); + + io_uring_socket_->shutdown(how); + return Api::SysCallIntResult{0, 0}; +} + +absl::optional IoUringSocketHandleImpl::checkReadResult() const { + ASSERT(io_uring_socket_.has_value()); + ASSERT(io_uring_socket_type_ == IoUringSocketType::Server || + io_uring_socket_type_ == IoUringSocketType::Client); + + const OptRef& read_param = io_uring_socket_->getReadParam(); + // A absl::nullopt read param means that there is no io_uring request which has been done. + if (read_param == absl::nullopt) { + if (io_uring_socket_->getStatus() != Io::IoUringSocketStatus::RemoteClosed) { + return Api::IoCallUint64Result{0, IoSocketError::getIoSocketEagainError()}; + } else { + ENVOY_LOG(trace, "read, fd = {}, type = {}, remote close", fd_, ioUringSocketTypeStr()); + return Api::ioCallUint64ResultNoError(); + } + } + + if (read_param->result_ == 0) { + ENVOY_LOG(trace, "read remote close, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); + return Api::ioCallUint64ResultNoError(); + } + + if (read_param->result_ < 0) { + ASSERT(read_param->buf_.length() == 0); + ENVOY_LOG(trace, "read error = {}, fd = {}, type = {}", -read_param->result_, fd_, + ioUringSocketTypeStr()); + if (read_param->result_ == -EAGAIN) { + return Api::IoCallUint64Result{0, IoSocketError::getIoSocketEagainError()}; + } + return Api::IoCallUint64Result{0, IoSocketError::create(-read_param->result_)}; + } + + // The buffer has been read in the previous call, return EAGAIN to tell the caller to wait for + // the next read event. + if (read_param->buf_.length() == 0) { + return Api::IoCallUint64Result{0, IoSocketError::getIoSocketEagainError()}; + } + return absl::nullopt; +} + +absl::optional IoUringSocketHandleImpl::checkWriteResult() const { + ASSERT(io_uring_socket_.has_value()); + ASSERT(io_uring_socket_type_ == IoUringSocketType::Server || + io_uring_socket_type_ == IoUringSocketType::Client); + + const OptRef& write_param = io_uring_socket_->getWriteParam(); + if (write_param != absl::nullopt) { + // EAGAIN indicates an injected write event to trigger IO handle write. Submit the new write to + // the io_uring. + if (write_param->result_ < 0 && write_param->result_ != -EAGAIN) { + return Api::IoCallUint64Result{0, IoSocketError::create(-write_param->result_)}; + } + } + return absl::nullopt; +} + +Api::IoCallUint64Result IoUringSocketHandleImpl::copyOut(uint64_t max_length, + Buffer::RawSlice* slices, + uint64_t num_slice) { + auto read_result = checkReadResult(); + if (read_result.has_value()) { + return std::move(*read_result); + } + + const OptRef& read_param = io_uring_socket_->getReadParam(); + const uint64_t max_read_length = std::min(max_length, static_cast(read_param->result_)); + uint64_t num_bytes_to_read = read_param->buf_.copyOutToSlices(max_read_length, slices, num_slice); + return {num_bytes_to_read, IoSocketError::none()}; +} + +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/io_uring_socket_handle_impl.h b/source/common/network/io_uring_socket_handle_impl.h new file mode 100644 index 000000000000..85e8469b11d1 --- /dev/null +++ b/source/common/network/io_uring_socket_handle_impl.h @@ -0,0 +1,94 @@ +#pragma once + +#include "envoy/buffer/buffer.h" +#include "envoy/common/io/io_uring.h" +#include "envoy/network/io_handle.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/logger.h" +#include "source/common/network/io_socket_handle_base_impl.h" + +namespace Envoy { + +namespace Network { + +class IoUringSocketHandleImpl; + +using IoUringSocketHandleImplOptRef = + absl::optional>; + +enum class IoUringSocketType { + Unknown, + Accept, + Server, + Client, +}; + +/** + * IoHandle derivative for sockets. + */ +class IoUringSocketHandleImpl : public IoSocketHandleBaseImpl { +public: + IoUringSocketHandleImpl(Io::IoUringWorkerFactory& io_uring_worker_factory, + os_fd_t fd = INVALID_SOCKET, bool socket_v6only = false, + absl::optional domain = absl::nullopt, + bool is_server_socket = false); + ~IoUringSocketHandleImpl() override; + + Api::IoCallUint64Result close() override; + Api::IoCallUint64Result readv(uint64_t max_length, Buffer::RawSlice* slices, + uint64_t num_slice) override; + Api::IoCallUint64Result read(Buffer::Instance& buffer, + absl::optional max_length_opt) override; + Api::IoCallUint64Result writev(const Buffer::RawSlice* slices, uint64_t num_slice) override; + Api::IoCallUint64Result write(Buffer::Instance& buffer) override; + Api::IoCallUint64Result sendmsg(const Buffer::RawSlice* slices, uint64_t num_slice, int flags, + const Address::Ip* self_ip, + const Address::Instance& peer_address) override; + Api::IoCallUint64Result recvmsg(Buffer::RawSlice* slices, const uint64_t num_slice, + uint32_t self_port, RecvMsgOutput& output) override; + Api::IoCallUint64Result recvmmsg(RawSliceArrays& slices, uint32_t self_port, + RecvMsgOutput& output) override; + Api::IoCallUint64Result recv(void* buffer, size_t length, int flags) override; + Api::SysCallIntResult bind(Address::InstanceConstSharedPtr address) override; + Api::SysCallIntResult listen(int backlog) override; + IoHandlePtr accept(struct sockaddr* addr, socklen_t* addrlen) override; + Api::SysCallIntResult connect(Address::InstanceConstSharedPtr address) override; + Api::SysCallIntResult getOption(int level, int optname, void* optval, socklen_t* optlen) override; + IoHandlePtr duplicate() override; + void initializeFileEvent(Event::Dispatcher& dispatcher, Event::FileReadyCb cb, + Event::FileTriggerType trigger, uint32_t events) override; + void activateFileEvents(uint32_t events) override; + void enableFileEvents(uint32_t events) override; + void resetFileEvents() override; + Api::SysCallIntResult shutdown(int how) override; + +protected: + std::string ioUringSocketTypeStr() const { + switch (io_uring_socket_type_) { + case IoUringSocketType::Unknown: + return "unknown"; + case IoUringSocketType::Accept: + return "accept"; + case IoUringSocketType::Client: + return "client"; + case IoUringSocketType::Server: + return "server"; + } + PANIC_DUE_TO_CORRUPT_ENUM; + } + + Io::IoUringWorkerFactory& io_uring_worker_factory_; + IoUringSocketType io_uring_socket_type_; + OptRef io_uring_socket_{absl::nullopt}; + + Event::FileEventPtr file_event_{nullptr}; + + absl::optional checkReadResult() const; + absl::optional checkWriteResult() const; + Api::IoCallUint64Result copyOut(uint64_t max_length, Buffer::RawSlice* slices, + uint64_t num_slice); +}; + +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/socket_interface_impl.cc b/source/common/network/socket_interface_impl.cc index aead17ecc3ab..a3dbec704417 100644 --- a/source/common/network/socket_interface_impl.cc +++ b/source/common/network/socket_interface_impl.cc @@ -10,15 +10,39 @@ #include "source/common/network/io_socket_handle_impl.h" #include "source/common/network/win32_socket_handle_impl.h" +#ifdef __linux__ +#include "source/common/network/io_uring_socket_handle_impl.h" +#endif + namespace Envoy { namespace Network { -IoHandlePtr SocketInterfaceImpl::makePlatformSpecificSocket(int socket_fd, bool socket_v6only, - absl::optional domain) { +namespace { +[[maybe_unused]] bool hasIoUringWorkerFactory(Io::IoUringWorkerFactory* io_uring_worker_factory) { + return io_uring_worker_factory != nullptr && io_uring_worker_factory->currentThreadRegistered() && + io_uring_worker_factory->getIoUringWorker() != absl::nullopt; +} +} // namespace + +IoHandlePtr SocketInterfaceImpl::makePlatformSpecificSocket( + int socket_fd, bool socket_v6only, absl::optional domain, + [[maybe_unused]] Io::IoUringWorkerFactory* io_uring_worker_factory) { if constexpr (Event::PlatformDefaultTriggerType == Event::FileTriggerType::EmulatedEdge) { return std::make_unique(socket_fd, socket_v6only, domain); } +#ifdef __linux__ + // Only create IoUringSocketHandleImpl when the IoUringWorkerFactory has been created and it has + // been registered in the TLS, initialized. There are cases that test may create threads before + // IoUringWorkerFactory has been added to the TLS and got initialized. + if (hasIoUringWorkerFactory(io_uring_worker_factory)) { + return std::make_unique(*io_uring_worker_factory, socket_fd, + socket_v6only, domain); + } else { + return std::make_unique(socket_fd, socket_v6only, domain); + } +#else return std::make_unique(socket_fd, socket_v6only, domain); +#endif } IoHandlePtr SocketInterfaceImpl::makeSocket(int socket_fd, bool socket_v6only, diff --git a/source/common/network/socket_interface_impl.h b/source/common/network/socket_interface_impl.h index 780c0dc97b71..6c9d8b1ac6b2 100644 --- a/source/common/network/socket_interface_impl.h +++ b/source/common/network/socket_interface_impl.h @@ -1,5 +1,6 @@ #pragma once +#include "envoy/common/io/io_uring.h" #include "envoy/network/socket.h" #include "source/common/network/socket_interface.h" @@ -26,8 +27,9 @@ class SocketInterfaceImpl : public SocketInterfaceBase { return "envoy.extensions.network.socket_interface.default_socket_interface"; }; - static IoHandlePtr makePlatformSpecificSocket(int socket_fd, bool socket_v6only, - absl::optional domain); + static IoHandlePtr + makePlatformSpecificSocket(int socket_fd, bool socket_v6only, absl::optional domain, + Io::IoUringWorkerFactory* io_uring_worker_factory = nullptr); protected: virtual IoHandlePtr makeSocket(int socket_fd, bool socket_v6only, diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 9e4c1c2e8205..ca7317da451c 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -433,6 +433,37 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "io_uring_socket_handle_impl_test", + srcs = select({ + "//bazel:linux": ["io_uring_socket_handle_impl_test.cc"], + "//conditions:default": [], + }), + deps = [ + "//test/mocks/api:api_mocks", + "//test/mocks/event:event_mocks", + "//test/mocks/io:io_mocks", + "//test/test_common:threadsafe_singleton_injector_lib", + ], +) + +envoy_cc_test( + name = "io_uring_socket_handle_impl_integration_test", + srcs = select({ + "//bazel:linux": ["io_uring_socket_handle_impl_integration_test.cc"], + "//conditions:default": [], + }), + deps = [ + "//source/common/network:default_socket_interface_lib", + "//source/common/thread_local:thread_local_lib", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + ] + select({ + "//bazel:linux": ["//source/common/io:io_uring_worker_factory_impl_lib"], + "//conditions:default": [], + }), +) + envoy_cc_test( name = "win32_socket_handle_impl_test", srcs = ["win32_socket_handle_impl_test.cc"], diff --git a/test/common/network/io_uring_socket_handle_impl_integration_test.cc b/test/common/network/io_uring_socket_handle_impl_integration_test.cc new file mode 100644 index 000000000000..50422910c848 --- /dev/null +++ b/test/common/network/io_uring_socket_handle_impl_integration_test.cc @@ -0,0 +1,973 @@ +#include + +#include "source/common/api/os_sys_calls_impl.h" +#include "source/common/io/io_uring_impl.h" +#include "source/common/io/io_uring_worker_factory_impl.h" +#include "source/common/network/address_impl.h" +#include "source/common/network/io_socket_handle_impl.h" +#include "source/common/network/io_uring_socket_handle_impl.h" +#include "source/common/thread_local/thread_local_impl.h" + +#include "test/test_common/test_time.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Network { +namespace { + +class IoUringSocketHandleImplIntegrationTest : public testing::Test { +public: + IoUringSocketHandleImplIntegrationTest() : should_skip_(!Io::isIoUringSupported()) {} + + void SetUp() override { + if (should_skip_) { + GTEST_SKIP(); + } + } + + void TearDown() override { + if (!thread_is_shutdown_) { + instance_.shutdownGlobalThreading(); + instance_.shutdownThread(); + } + } + + void initialize(bool create_second_thread = false) { + api_ = Api::createApiForTest(time_system_); + dispatcher_ = api_->allocateDispatcher("test_thread"); + instance_.registerThread(*dispatcher_, true); + + if (create_second_thread) { + second_dispatcher_ = api_->allocateDispatcher("test_second_thread"); + instance_.registerThread(*second_dispatcher_, false); + } + + io_uring_worker_factory_ = + std::make_unique(10, false, 8192, 1000, instance_); + io_uring_worker_factory_->onWorkerThreadInitialized(); + + // Create the thread after the io_uring worker has been initialized, otherwise the dispatcher + // will quit when there is no any remaining registered event. + if (create_second_thread) { + second_thread_ = api_->threadFactory().createThread( + [this]() -> void { second_dispatcher_->run(Event::Dispatcher::RunType::Block); }); + } + } + + void createAcceptConnection() { + // Create an io_uring handle with accept socket. + fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; + EXPECT_GE(fd_, 0); + io_uring_socket_handle_ = std::make_unique( + *io_uring_worker_factory_, fd_, false, absl::nullopt, false); + + // Listen within the io_uring handle. + auto local_addr = std::make_shared("127.0.0.1", 0); + io_uring_socket_handle_->bind(local_addr); + io_uring_socket_handle_->listen(1); + + // Create a socket handle. + os_fd_t fd = Api::OsSysCallsSingleton::get() + .socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP) + .return_value_; + EXPECT_GE(fd, 0); + io_socket_handle_ = std::make_unique(fd); + } + + void createServerConnection() { + // Create an io_uring handle with server socket. + fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; + EXPECT_GE(fd_, 0); + io_uring_socket_handle_ = std::make_unique( + *io_uring_worker_factory_, fd_, false, absl::nullopt, true); + } + + void createClientConnection() { + // Prepare the listener. + os_fd_t fd = Api::OsSysCallsSingleton::get() + .socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP) + .return_value_; + EXPECT_GE(fd, 0); + IoHandlePtr listener = std::make_unique(fd); + + // Listen within the listener. + auto local_addr = std::make_shared("127.0.0.1", 0); + listener->bind(local_addr); + listener->listen(1); + listener->initializeFileEvent( + *dispatcher_, + [this, &listener](uint32_t) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + io_socket_handle_ = listener->accept(&addr, &addrlen); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Create an io_uring handle with client socket. + fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; + EXPECT_GE(fd_, 0); + io_uring_socket_handle_ = std::make_unique( + *io_uring_worker_factory_, fd_, false, absl::nullopt, false); + + int error = -1; + socklen_t error_size = sizeof(error); + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &error, &error_size](uint32_t events) { + if (events & Event::FileReadyType::Write) { + io_uring_socket_handle_->getOption(SOL_SOCKET, SO_ERROR, &error, &error_size); + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Connect from io_uring handle. + io_uring_socket_handle_->connect(listener->localAddress()); + while (error == -1) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(error, 0); + } + + bool should_skip_{false}; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + Event::GlobalTimeSystem time_system_; + ThreadLocal::InstanceImpl instance_; + std::unique_ptr io_uring_worker_factory_; + os_fd_t fd_; + IoHandlePtr io_uring_socket_handle_; + IoHandlePtr io_socket_handle_; + Thread::ThreadPtr second_thread_; + Event::DispatcherPtr second_dispatcher_; + bool thread_is_shutdown_{false}; +}; + +TEST_F(IoUringSocketHandleImplIntegrationTest, Close) { + initialize(); + createServerConnection(); + + io_uring_socket_handle_->close(); + + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(errno, EBADF); +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, CancelAndClose) { + initialize(); + createServerConnection(); + + // Submit the read request. + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + io_uring_socket_handle_->close(); + + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(errno, EBADF); +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Accept) { + initialize(); + createAcceptConnection(); + + bool accepted = false; + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &accepted](uint32_t) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + auto handle = io_uring_socket_handle_->accept(&addr, &addrlen); + EXPECT_NE(handle, nullptr); + accepted = true; + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Connect from the socket handle. + io_socket_handle_->connect(io_uring_socket_handle_->localAddress()); + while (!accepted) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_TRUE(accepted); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(errno, EBADF); +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, AcceptError) { + initialize(); + createAcceptConnection(); + + bool accepted = false; + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &accepted](uint32_t) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + auto handle = io_uring_socket_handle_->accept(&addr, &addrlen); + EXPECT_EQ(handle, nullptr); + accepted = true; + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Accept nothing. + io_uring_socket_handle_->enableFileEvents(Event::FileReadyType::Read); + io_uring_socket_handle_->activateFileEvents(Event::FileReadyType::Read); + while (!accepted) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_TRUE(accepted); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(errno, EBADF); +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Connect) { + initialize(); + createClientConnection(); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, ConnectError) { + initialize(); + + // Create an io_uring handle with client socket. + fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; + EXPECT_GE(fd_, 0); + io_uring_socket_handle_ = std::make_unique( + *io_uring_worker_factory_, fd_, false, absl::nullopt, false); + + int original_error = -1; + socklen_t original_error_size = sizeof(original_error); + int error = -1; + socklen_t error_size = sizeof(error); + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &error, &error_size, &original_error, &original_error_size](uint32_t events) { + if (events & Event::FileReadyType::Write) { + // We cannot get error with getsockopt since the error has been read within io_uring. + getsockopt(io_uring_socket_handle_->fdDoNotUse(), SOL_SOCKET, SO_ERROR, &original_error, + &original_error_size); + // The read error will be transferred to the io_uring handle. + io_uring_socket_handle_->getOption(SOL_SOCKET, SO_ERROR, &error, &error_size); + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Connect from io_uring handle. + auto local_addr = std::make_shared("127.0.0.1", 9999); + io_uring_socket_handle_->connect(local_addr); + while (error == -1) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(original_error, 0); + EXPECT_EQ(error, ECONNREFUSED); + + // Close safely. + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Read) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.return_value_, data.size()); + + // Read again would expect the EAGAIN returned. + ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_TRUE(ret.wouldBlock()); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, ReadContinuity) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + bool first_read = true; + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &first_read](uint32_t event) { + if (first_read) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, 5); + EXPECT_EQ(ret.return_value_, 5); + } else { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data.substr(0, 5)); + + // Cleanup previous read. + first_read = false; + read_buffer.drain(read_buffer.length()); + EXPECT_EQ(write_buffer.length(), 0); + + // Write from the peer handle again to trigger the read event. + std::string data2 = " again"; + write_buffer.add(data2); + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data.substr(5) + data2); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, ReadActively) { + initialize(); + createClientConnection(); + + Buffer::OwnedImpl read_buffer; + + // Read actively. + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.wouldBlock(), true); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Readv) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + Buffer::Reservation reservation = read_buffer.reserveForRead(); + auto ret = + io_uring_socket_handle_->readv(11, reservation.slices(), reservation.numSlices()); + EXPECT_EQ(ret.return_value_, data.size()); + reservation.commit(ret.return_value_); + + // Read again would expect the EAGAIN returned. + Buffer::Reservation reservation2 = read_buffer.reserveForRead(); + ret = io_uring_socket_handle_->readv(11, reservation2.slices(), reservation2.numSlices()); + EXPECT_TRUE(ret.wouldBlock()); + reservation2.commit(0); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, ReadvContinuity) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + bool first_read = true; + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &first_read](uint32_t event) { + if (first_read) { + Buffer::Reservation reservation = read_buffer.reserveForRead(); + auto ret = + io_uring_socket_handle_->readv(5, reservation.slices(), reservation.numSlices()); + EXPECT_EQ(ret.return_value_, 5); + reservation.commit(ret.return_value_); + } else { + EXPECT_EQ(event, Event::FileReadyType::Read); + Buffer::Reservation reservation = read_buffer.reserveForRead(); + auto ret = + io_uring_socket_handle_->readv(1024, reservation.slices(), reservation.numSlices()); + reservation.commit(ret.return_value_); + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data.substr(0, 5)); + + // Cleanup previous read. + first_read = false; + read_buffer.drain(read_buffer.length()); + EXPECT_EQ(write_buffer.length(), 0); + + // Write from the peer handle again to trigger the read event. + std::string data2 = " again"; + write_buffer.add(data2); + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data.substr(5) + data2); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Write) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, + Event::FileReadyType::Write); + + io_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.return_value_, data.size()); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write with the io_uring handle. + auto ret = io_uring_socket_handle_->write(write_buffer).return_value_; + EXPECT_EQ(ret, data.size()); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Writev) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, + Event::FileReadyType::Write); + + io_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.return_value_, data.size()); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write with the io_uring handle. + auto ret = io_uring_socket_handle_->writev(&write_buffer.getRawSlices()[0], 1).return_value_; + EXPECT_EQ(ret, data.size()); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Recv) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl peek_buffer; + Buffer::OwnedImpl recv_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &peek_buffer, &recv_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + // Recv with MSG_PEEK will not drain the buffer. + Buffer::Reservation reservation = peek_buffer.reserveForRead(); + auto ret = io_uring_socket_handle_->recv(reservation.slices()->mem_, 5, MSG_PEEK); + EXPECT_EQ(ret.return_value_, 5); + reservation.commit(ret.return_value_); + + // Recv without flags behaves the same as readv. + Buffer::Reservation reservation2 = recv_buffer.reserveForRead(); + auto ret2 = + io_uring_socket_handle_->recv(reservation2.slices()->mem_, reservation2.length(), 0); + EXPECT_EQ(ret2.return_value_, data.size()); + reservation2.commit(ret2.return_value_); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (recv_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(peek_buffer.toString(), "Hello"); + EXPECT_EQ(recv_buffer.toString(), data); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Bind) { + initialize(); + createClientConnection(); + // Create an io_uring handle with client socket. + fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; + EXPECT_GE(fd_, 0); + io_uring_socket_handle_ = std::make_unique( + *io_uring_worker_factory_, fd_, false, absl::nullopt, false); + auto local_addr = std::make_shared("127.0.0.1", 0); + io_uring_socket_handle_->bind(local_addr); + + // Close safely. + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, GetOption) { + initialize(); + createServerConnection(); + + int optval = -1; + socklen_t optlen = sizeof(optval); + auto ret = io_uring_socket_handle_->getOption(SOL_SOCKET, SO_REUSEADDR, &optval, &optlen); + EXPECT_EQ(ret.return_value_, 0); + EXPECT_EQ(optval, 0); + + // Close safely. + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Duplicate) { + initialize(); + createClientConnection(); + + IoHandlePtr io_uring_socket_handle_2 = io_uring_socket_handle_->duplicate(); + + // Close safely. + io_uring_socket_handle_2->close(); + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, ActivateReadEvent) { + initialize(); + createServerConnection(); + + // Submit the read request. + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + Buffer::OwnedImpl read_buffer; + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_TRUE(ret.wouldBlock()); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + io_uring_socket_handle_->activateFileEvents(Event::FileReadyType::Read); + + // Close safely. + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, ActivateWriteEvent) { + initialize(); + createClientConnection(); + + Buffer::OwnedImpl write_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &write_buffer](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Write); + auto ret = io_uring_socket_handle_->write(write_buffer); + EXPECT_TRUE(ret.wouldBlock()); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Write); + + io_uring_socket_handle_->activateFileEvents(Event::FileReadyType::Write); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, Shutdown) { + initialize(); + createClientConnection(); + + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + io_uring_socket_handle_->shutdown(SHUT_WR); + auto ret = io_socket_handle_->read(read_buffer, absl::nullopt); + while (ret.wouldBlock()) { + ret = io_socket_handle_->read(read_buffer, absl::nullopt); + } + EXPECT_EQ(ret.return_value_, 0); + + // Close safely. + io_socket_handle_->close(); + io_uring_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +// Tests the case of a write event will be emitted after remote closed when the read is disabled and +// the write is listened only. +TEST_F(IoUringSocketHandleImplIntegrationTest, + RemoteCloseWithCloseEventDisabledAndReadEventDisabled) { + initialize(); + createClientConnection(); + + Buffer::OwnedImpl read_buffer; + bool got_write_event = false; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &got_write_event](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Write); + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_TRUE(ret.ok()); + EXPECT_EQ(0, ret.return_value_); + io_uring_socket_handle_->close(); + got_write_event = true; + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + io_uring_socket_handle_->enableFileEvents(Event::FileReadyType::Write); + + io_socket_handle_->close(); + while (!got_write_event) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.length(), 0); + + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, RemoteCloseWithCloseEventDisabled) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + if (ret.return_value_ > 0) { + EXPECT_EQ(ret.return_value_, data.size()); + + // Read again would expect the EAGAIN returned. + ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_TRUE(ret.wouldBlock()); + } else if (ret.return_value_ == 0) { + io_uring_socket_handle_->close(); + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + io_uring_socket_handle_->enableFileEvents(Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + io_socket_handle_->close(); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, RemoteCloseWithCloseEventEnabled) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + if (event & Event::FileReadyType::Read) { + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.return_value_, data.size()); + + // Read again would expect the EAGAIN returned. + ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_TRUE(ret.wouldBlock()); + } else if (event & Event::FileReadyType::Closed) { + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(0, ret.return_value_); + io_uring_socket_handle_->close(); + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read | Event::FileReadyType::Closed); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + io_socket_handle_->close(); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +// Ensures IoUringHandleImpl will close the socket on destruction. +TEST_F(IoUringSocketHandleImplIntegrationTest, CloseIoUringSocketOnDestruction) { + initialize(); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer, &data](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.return_value_, data.size()); + + // Read again would expect the EAGAIN returned. + ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + EXPECT_TRUE(ret.wouldBlock()); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data); + io_uring_socket_handle_.reset(); + while (io_socket_handle_->read(read_buffer, absl::nullopt).return_value_ != 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + // Close safely. + io_socket_handle_->close(); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } +} + +// Ensures IoUringHandleImpl can be released correctly when the IoUringWorker is released earlier. +TEST_F(IoUringSocketHandleImplIntegrationTest, IoUringWorkerEarlyRelease) { + initialize(); + createClientConnection(); + + thread_is_shutdown_ = true; + instance_.shutdownGlobalThreading(); + instance_.shutdownThread(); +} + +TEST_F(IoUringSocketHandleImplIntegrationTest, MigrateServerSocketBetweenThreads) { + initialize(true); + createClientConnection(); + + std::string data = "Hello world"; + Buffer::OwnedImpl write_buffer(data); + Buffer::OwnedImpl read_buffer; + + io_uring_socket_handle_->initializeFileEvent( + *dispatcher_, + [this, &read_buffer](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, 5); + EXPECT_EQ(ret.return_value_, 5); + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // Write from the peer handle. + io_socket_handle_->write(write_buffer); + while (read_buffer.length() == 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data.substr(0, 5)); + + io_uring_socket_handle_->resetFileEvents(); + read_buffer.drain(read_buffer.length()); + + // Migrate io_uring between threads. + std::atomic initialized_in_new_thread = false; + std::atomic read_in_new_thread = false; + second_dispatcher_->post([this, &second_dispatcher = second_dispatcher_, &read_buffer, &data, + &initialized_in_new_thread, &read_in_new_thread]() { + io_uring_socket_handle_->initializeFileEvent( + *second_dispatcher, + [this, &read_buffer, &data, &read_in_new_thread](uint32_t event) { + EXPECT_EQ(event, Event::FileReadyType::Read); + auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); + // For the next read. + if (read_buffer.length() > data.substr(5).length()) { + read_in_new_thread = true; + } + }, + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + initialized_in_new_thread = true; + }); + while (!initialized_in_new_thread) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + // Write from the peer handle again to trigger the read event. + std::string data2 = " again"; + write_buffer.add(data2); + io_socket_handle_->write(write_buffer); + while (!read_in_new_thread) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + EXPECT_EQ(read_buffer.toString(), data.substr(5) + data2); + + // Close safely. + io_socket_handle_->close(); + second_dispatcher_->post([this]() { io_uring_socket_handle_->close(); }); + while (fcntl(fd_, F_GETFD, 0) >= 0) { + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + second_dispatcher_->exit(); + second_thread_->join(); +} + +} // namespace +} // namespace Network +} // namespace Envoy diff --git a/test/common/network/io_uring_socket_handle_impl_test.cc b/test/common/network/io_uring_socket_handle_impl_test.cc new file mode 100644 index 000000000000..80031b200e8a --- /dev/null +++ b/test/common/network/io_uring_socket_handle_impl_test.cc @@ -0,0 +1,133 @@ +#include "source/common/network/address_impl.h" +#include "source/common/network/io_uring_socket_handle_impl.h" + +#include "test/mocks/api/mocks.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/io/mocks.h" +#include "test/test_common/threadsafe_singleton_injector.h" + +namespace Envoy { +namespace Network { +namespace { + +class IoUringSocketHandleTestImpl : public IoUringSocketHandleImpl { +public: + IoUringSocketHandleTestImpl(Io::IoUringWorkerFactory& factory, bool is_server_socket) + : IoUringSocketHandleImpl(factory, INVALID_SOCKET, false, absl::nullopt, is_server_socket) {} + IoUringSocketType ioUringSocketType() const { return io_uring_socket_type_; } +}; + +class IoUringSocketHandleTest : public ::testing::Test { +public: + Io::MockIoUringSocket socket_; + Io::MockIoUringWorker worker_; + Io::MockIoUringWorkerFactory factory_; + Event::MockDispatcher dispatcher_; +}; + +TEST_F(IoUringSocketHandleTest, CreateServerSocket) { + IoUringSocketHandleTestImpl impl(factory_, true); + EXPECT_EQ(IoUringSocketType::Server, impl.ioUringSocketType()); +} + +TEST_F(IoUringSocketHandleTest, CreateClientSocket) { + IoUringSocketHandleTestImpl impl(factory_, false); + EXPECT_EQ(IoUringSocketType::Unknown, impl.ioUringSocketType()); + EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); + EXPECT_CALL(factory_, getIoUringWorker()) + .WillOnce(testing::Return(OptRef(worker_))); + impl.initializeFileEvent( + dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + EXPECT_EQ(IoUringSocketType::Client, impl.ioUringSocketType()); +} + +TEST_F(IoUringSocketHandleTest, ReadError) { + IoUringSocketHandleTestImpl impl(factory_, false); + EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); + EXPECT_CALL(factory_, getIoUringWorker()) + .WillOnce(testing::Return(OptRef(worker_))); + impl.initializeFileEvent( + dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + // EAGAIN error. + Buffer::OwnedImpl read_buffer; + Io::ReadParam read_param{read_buffer, -EAGAIN}; + auto read_param_ref = OptRef(read_param); + EXPECT_CALL(socket_, getReadParam()).WillOnce(testing::ReturnRef(read_param_ref)); + auto ret = impl.read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::Again); + + // Non-EAGAIN error. + Io::ReadParam read_param_2{read_buffer, -EBADF}; + auto read_param_ref_2 = OptRef(read_param_2); + EXPECT_CALL(socket_, getReadParam()).WillOnce(testing::ReturnRef(read_param_ref_2)); + ret = impl.read(read_buffer, absl::nullopt); + EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::BadFd); +} + +TEST_F(IoUringSocketHandleTest, WriteError) { + IoUringSocketHandleTestImpl impl(factory_, false); + EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); + EXPECT_CALL(factory_, getIoUringWorker()) + .WillOnce(testing::Return(OptRef(worker_))); + impl.initializeFileEvent( + dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + Buffer::OwnedImpl write_buffer; + Io::WriteParam write_param{-EBADF}; + auto write_param_ref = OptRef(write_param); + EXPECT_CALL(socket_, getWriteParam()).WillOnce(testing::ReturnRef(write_param_ref)); + auto ret = impl.write(write_buffer); + EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::BadFd); +} + +TEST_F(IoUringSocketHandleTest, WritevError) { + IoUringSocketHandleTestImpl impl(factory_, false); + EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); + EXPECT_CALL(factory_, getIoUringWorker()) + .WillOnce(testing::Return(OptRef(worker_))); + impl.initializeFileEvent( + dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); + + Buffer::OwnedImpl write_buffer; + Io::WriteParam write_param{-EBADF}; + auto write_param_ref = OptRef(write_param); + EXPECT_CALL(socket_, getWriteParam()).WillOnce(testing::ReturnRef(write_param_ref)); + auto slice = write_buffer.frontSlice(); + auto ret = impl.writev(&slice, 1); + EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::BadFd); +} + +TEST_F(IoUringSocketHandleTest, SendmsgNotSupported) { + IoUringSocketHandleTestImpl impl(factory_, true); + + Buffer::OwnedImpl write_buffer; + auto slice = write_buffer.frontSlice(); + auto local_addr = std::make_shared("127.0.0.1", 0); + EXPECT_THAT(impl.sendmsg(&slice, 0, 0, nullptr, *local_addr).err_->getErrorCode(), + Api::IoError::IoErrorCode::NoSupport); +} + +TEST_F(IoUringSocketHandleTest, RecvmsgNotSupported) { + IoUringSocketHandleTestImpl impl(factory_, true); + + Buffer::OwnedImpl write_buffer; + auto slice = write_buffer.frontSlice(); + IoHandle::RecvMsgOutput output(0, nullptr); + EXPECT_THAT(impl.recvmsg(&slice, 0, 0, output).err_->getErrorCode(), + Api::IoError::IoErrorCode::NoSupport); +} + +TEST_F(IoUringSocketHandleTest, RecvmmsgNotSupported) { + IoUringSocketHandleTestImpl impl(factory_, true); + + Buffer::OwnedImpl write_buffer; + RawSliceArrays array(0, absl::FixedArray(0)); + IoHandle::RecvMsgOutput output(0, nullptr); + EXPECT_THAT(impl.recvmmsg(array, 0, output).err_->getErrorCode(), + Api::IoError::IoErrorCode::NoSupport); +} + +} // namespace +} // namespace Network +} // namespace Envoy diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 1a1eae3faa56..de722d444565 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1411,6 +1411,7 @@ upcasts upstreams uptime upvalue +uring urlencoded urls userdata From 89eeeceef1cfcc4f3b43f26847b9aca0ad809de1 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:14:37 +0000 Subject: [PATCH 16/36] deps/api: Bump `com_github_bufbuild_buf` -> 1.30.0 (#32933) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- api/bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 902923662e0d..d7853884cac6 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -131,11 +131,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "buf", project_desc = "A new way of working with Protocol Buffers.", # Used for breaking change detection in API protobufs project_url = "https://buf.build", - version = "1.29.0", - sha256 = "1033f26361e6fc30ffcfab9d4e4274ffd4af88d9c97de63d2e1721c4a07c1380", + version = "1.30.0", + sha256 = "219f48fb1bb190e0f761e35cac0821dfd9c1b0dfda80d7aaf522347755d829ab", strip_prefix = "buf", urls = ["https://github.com/bufbuild/buf/releases/download/v{version}/buf-Linux-x86_64.tar.gz"], - release_date = "2024-01-24", + release_date = "2024-03-07", use_category = ["api"], license = "Apache-2.0", license_url = "https://github.com/bufbuild/buf/blob/v{version}/LICENSE", From 213b757639b2b0c911250e73c6c57ea59cd30ead Mon Sep 17 00:00:00 2001 From: Tiago Quelhas Date: Fri, 15 Mar 2024 19:28:52 +0100 Subject: [PATCH 17/36] bazel: remove `output_to_genfiles = True` (#32931) Remove `output_to_genfiles = True`. This is a no-op, as Bazel already enables `--incompatible_merge_genfiles_directory` by default, which makes the distinction between genfiles and bin moot. Signed-off-by: Tiago Quelhas --- api/bazel/cc_proto_descriptor_library/builddefs.bzl | 1 - 1 file changed, 1 deletion(-) diff --git a/api/bazel/cc_proto_descriptor_library/builddefs.bzl b/api/bazel/cc_proto_descriptor_library/builddefs.bzl index 2da95d00a063..c38c20bd8a1a 100644 --- a/api/bazel/cc_proto_descriptor_library/builddefs.bzl +++ b/api/bazel/cc_proto_descriptor_library/builddefs.bzl @@ -335,7 +335,6 @@ cc_proto_descriptor_library_aspect = aspect( ) cc_proto_descriptor_library = rule( - output_to_genfiles = True, implementation = _cc_proto_descriptor_rule_impl, attrs = { "deps": attr.label_list( From 3a8fadb08e00568f7962f72193ea8c58b52fc15f Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Fri, 15 Mar 2024 11:54:03 -0700 Subject: [PATCH 18/36] Stringmatcher: factory context for jwt_authn, ext_proc, csrf filters (#32924) Signed-off-by: Greg Greenway --- source/extensions/filters/http/csrf/config.cc | 4 ++-- source/extensions/filters/http/csrf/csrf_filter.cc | 9 +++++---- source/extensions/filters/http/csrf/csrf_filter.h | 12 +++++++----- source/extensions/filters/http/ext_proc/config.cc | 5 ++--- source/extensions/filters/http/ext_proc/ext_proc.h | 9 +++++---- .../filters/http/ext_proc/matching_utils.cc | 8 +++++--- .../filters/http/ext_proc/matching_utils.h | 3 ++- .../extensions/filters/http/jwt_authn/jwks_cache.cc | 5 +++-- test/extensions/filters/http/csrf/BUILD | 1 + .../extensions/filters/http/csrf/csrf_filter_test.cc | 10 ++++++---- test/extensions/filters/http/ext_proc/BUILD | 5 ++--- test/extensions/filters/http/ext_proc/filter_test.cc | 6 +++--- .../filters/http/ext_proc/ordering_test.cc | 6 +++--- .../filters/http/ext_proc/unit_test_fuzz/BUILD | 2 +- .../unit_test_fuzz/ext_proc_unit_test_fuzz.cc | 6 +++--- 15 files changed, 50 insertions(+), 41 deletions(-) diff --git a/source/extensions/filters/http/csrf/config.cc b/source/extensions/filters/http/csrf/config.cc index a36139e17bb7..425bf3812693 100644 --- a/source/extensions/filters/http/csrf/config.cc +++ b/source/extensions/filters/http/csrf/config.cc @@ -15,7 +15,7 @@ Http::FilterFactoryCb CsrfFilterFactory::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::csrf::v3::CsrfPolicy& policy, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { CsrfFilterConfigSharedPtr config = std::make_shared( - policy, stats_prefix, context.scope(), context.serverFactoryContext().runtime()); + policy, stats_prefix, context.scope(), context.serverFactoryContext()); return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { callbacks.addStreamDecoderFilter(std::make_shared(config)); }; @@ -25,7 +25,7 @@ Router::RouteSpecificFilterConfigConstSharedPtr CsrfFilterFactory::createRouteSpecificFilterConfigTyped( const envoy::extensions::filters::http::csrf::v3::CsrfPolicy& policy, Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor&) { - return std::make_shared(policy, context.runtime()); + return std::make_shared(policy, context); } /** diff --git a/source/extensions/filters/http/csrf/csrf_filter.cc b/source/extensions/filters/http/csrf/csrf_filter.cc index ea8a521fd85e..c36c1ceb2816 100644 --- a/source/extensions/filters/http/csrf/csrf_filter.cc +++ b/source/extensions/filters/http/csrf/csrf_filter.cc @@ -76,15 +76,16 @@ static CsrfStats generateStats(const std::string& prefix, Stats::Scope& scope) { static CsrfPolicyPtr generatePolicy(const envoy::extensions::filters::http::csrf::v3::CsrfPolicy& policy, - Runtime::Loader& runtime) { - return std::make_unique(policy, runtime); + Server::Configuration::CommonFactoryContext& context) { + return std::make_unique(policy, context); } } // namespace CsrfFilterConfig::CsrfFilterConfig( const envoy::extensions::filters::http::csrf::v3::CsrfPolicy& policy, - const std::string& stats_prefix, Stats::Scope& scope, Runtime::Loader& runtime) - : stats_(generateStats(stats_prefix, scope)), policy_(generatePolicy(policy, runtime)) {} + const std::string& stats_prefix, Stats::Scope& scope, + Server::Configuration::CommonFactoryContext& context) + : stats_(generateStats(stats_prefix, scope)), policy_(generatePolicy(policy, context)) {} CsrfFilter::CsrfFilter(const CsrfFilterConfigSharedPtr config) : config_(config) {} diff --git a/source/extensions/filters/http/csrf/csrf_filter.h b/source/extensions/filters/http/csrf/csrf_filter.h index b52b9473a884..fe7bc78a1a29 100644 --- a/source/extensions/filters/http/csrf/csrf_filter.h +++ b/source/extensions/filters/http/csrf/csrf_filter.h @@ -35,12 +35,13 @@ struct CsrfStats { class CsrfPolicy : public Router::RouteSpecificFilterConfig { public: CsrfPolicy(const envoy::extensions::filters::http::csrf::v3::CsrfPolicy& policy, - Runtime::Loader& runtime) - : policy_(policy), runtime_(runtime) { + Server::Configuration::CommonFactoryContext& context) + : policy_(policy), runtime_(context.runtime()) { for (const auto& additional_origin : policy.additional_origins()) { additional_origins_.emplace_back( - std::make_unique>( - additional_origin)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + additional_origin, context)); } } @@ -78,7 +79,8 @@ using CsrfPolicyPtr = std::unique_ptr; class CsrfFilterConfig { public: CsrfFilterConfig(const envoy::extensions::filters::http::csrf::v3::CsrfPolicy& policy, - const std::string& stats_prefix, Stats::Scope& scope, Runtime::Loader& runtime); + const std::string& stats_prefix, Stats::Scope& scope, + Server::Configuration::CommonFactoryContext& context); CsrfStats& stats() { return stats_; } const CsrfPolicy* policy() { return policy_.get(); } diff --git a/source/extensions/filters/http/ext_proc/config.cc b/source/extensions/filters/http/ext_proc/config.cc index ee45fc6a73c4..cfbba66b4181 100644 --- a/source/extensions/filters/http/ext_proc/config.cc +++ b/source/extensions/filters/http/ext_proc/config.cc @@ -20,7 +20,7 @@ Http::FilterFactoryCb ExternalProcessingFilterConfig::createFilterFactoryFromPro proto_config, std::chrono::milliseconds(message_timeout_ms), max_message_timeout_ms, context.scope(), stats_prefix, Envoy::Extensions::Filters::Common::Expr::getBuilder(context.serverFactoryContext()), - context.serverFactoryContext().localInfo()); + context.serverFactoryContext()); return [filter_config, grpc_service = proto_config.grpc_service(), &context](Http::FilterChainFactoryCallbacks& callbacks) { @@ -50,8 +50,7 @@ ExternalProcessingFilterConfig::createFilterFactoryFromProtoWithServerContextTyp const auto filter_config = std::make_shared( proto_config, std::chrono::milliseconds(message_timeout_ms), max_message_timeout_ms, server_context.scope(), stats_prefix, - Envoy::Extensions::Filters::Common::Expr::getBuilder(server_context), - server_context.localInfo()); + Envoy::Extensions::Filters::Common::Expr::getBuilder(server_context), server_context); return [filter_config, grpc_service = proto_config.grpc_service(), &server_context](Http::FilterChainFactoryCallbacks& callbacks) { diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 689b1e322872..45fb789b6d7b 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -129,7 +129,7 @@ class FilterConfig { const uint32_t max_message_timeout_ms, Stats::Scope& scope, const std::string& stats_prefix, Extensions::Filters::Common::Expr::BuilderInstanceSharedPtr builder, - const LocalInfo::LocalInfo& local_info) + Server::Configuration::CommonFactoryContext& context) : failure_mode_allow_(config.failure_mode_allow()), disable_clear_route_cache_(config.disable_clear_route_cache()), message_timeout_(message_timeout), max_message_timeout_ms_(max_message_timeout_ms), @@ -138,8 +138,9 @@ class FilterConfig { filter_metadata_(config.filter_metadata()), allow_mode_override_(config.allow_mode_override()), disable_immediate_response_(config.disable_immediate_response()), - allowed_headers_(initHeaderMatchers(config.forward_rules().allowed_headers())), - disallowed_headers_(initHeaderMatchers(config.forward_rules().disallowed_headers())), + allowed_headers_(initHeaderMatchers(config.forward_rules().allowed_headers(), context)), + disallowed_headers_( + initHeaderMatchers(config.forward_rules().disallowed_headers(), context)), untyped_forwarding_namespaces_( config.metadata_options().forwarding_namespaces().untyped().begin(), config.metadata_options().forwarding_namespaces().untyped().end()), @@ -149,7 +150,7 @@ class FilterConfig { untyped_receiving_namespaces_( config.metadata_options().receiving_namespaces().untyped().begin(), config.metadata_options().receiving_namespaces().untyped().end()), - expression_manager_(builder, local_info, config.request_attributes(), + expression_manager_(builder, context.localInfo(), config.request_attributes(), config.response_attributes()) {} bool failureModeAllow() const { return failure_mode_allow_; } diff --git a/source/extensions/filters/http/ext_proc/matching_utils.cc b/source/extensions/filters/http/ext_proc/matching_utils.cc index e778be5ab945..e17c600da533 100644 --- a/source/extensions/filters/http/ext_proc/matching_utils.cc +++ b/source/extensions/filters/http/ext_proc/matching_utils.cc @@ -86,12 +86,14 @@ ExpressionManager::evaluateAttributes(const Filters::Common::Expr::Activation& a } std::vector -initHeaderMatchers(const envoy::type::matcher::v3::ListStringMatcher& header_list) { +initHeaderMatchers(const envoy::type::matcher::v3::ListStringMatcher& header_list, + Server::Configuration::CommonFactoryContext& context) { std::vector header_matchers; for (const auto& matcher : header_list.patterns()) { header_matchers.push_back( - std::make_unique>( - matcher)); + std::make_unique< + Matchers::StringMatcherImplWithContext>( + matcher, context)); } return header_matchers; } diff --git a/source/extensions/filters/http/ext_proc/matching_utils.h b/source/extensions/filters/http/ext_proc/matching_utils.h index f02c51ac2728..efd3a1f522f7 100644 --- a/source/extensions/filters/http/ext_proc/matching_utils.h +++ b/source/extensions/filters/http/ext_proc/matching_utils.h @@ -57,7 +57,8 @@ class ExpressionManager : public Logger::Loggable { }; std::vector -initHeaderMatchers(const envoy::type::matcher::v3::ListStringMatcher& header_list); +initHeaderMatchers(const envoy::type::matcher::v3::ListStringMatcher& header_list, + Server::Configuration::CommonFactoryContext& context); } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/source/extensions/filters/http/jwt_authn/jwks_cache.cc b/source/extensions/filters/http/jwt_authn/jwks_cache.cc index 37bad44e10e9..b5109b639ef6 100644 --- a/source/extensions/filters/http/jwt_authn/jwks_cache.cc +++ b/source/extensions/filters/http/jwt_authn/jwks_cache.cc @@ -63,7 +63,7 @@ class JwksDataImpl : public JwksCache::JwksData, public Logger::Loggable(audiences); if (jwt_provider_.has_subjects()) { - sub_matcher_.emplace(jwt_provider_.subjects()); + sub_matcher_.emplace(jwt_provider_.subjects(), context.serverFactoryContext()); } if (jwt_provider_.require_expiration()) { @@ -189,7 +189,8 @@ class JwksDataImpl : public JwksCache::JwksData, public Logger::Loggable tls_; // async fetcher JwksAsyncFetcherPtr async_fetcher_; - absl::optional> sub_matcher_; + absl::optional> + sub_matcher_; absl::optional max_exp_; }; diff --git a/test/extensions/filters/http/csrf/BUILD b/test/extensions/filters/http/csrf/BUILD index 913a61c7c447..5a958bce60e8 100644 --- a/test/extensions/filters/http/csrf/BUILD +++ b/test/extensions/filters/http/csrf/BUILD @@ -21,6 +21,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/csrf:csrf_filter_lib", "//test/mocks/buffer:buffer_mocks", "//test/mocks/http:http_mocks", + "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:upstream_mocks", "@envoy_api//envoy/extensions/filters/http/csrf/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/csrf/csrf_filter_test.cc b/test/extensions/filters/http/csrf/csrf_filter_test.cc index 85e2bb18a3bf..ba3b3bbe27ba 100644 --- a/test/extensions/filters/http/csrf/csrf_filter_test.cc +++ b/test/extensions/filters/http/csrf/csrf_filter_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/printers.h" @@ -43,7 +44,8 @@ class CsrfFilterTest : public testing::Test { const auto& add_regex_origin = policy.mutable_additional_origins()->Add(); add_regex_origin->MergeFrom(TestUtility::createRegexMatcher(R"(www\-[0-9]\.allow\.com)")); - return std::make_shared(policy, "test", *stats_.rootScope(), runtime_); + return std::make_shared(policy, "test", *stats_.rootScope(), + factory_context_); } CsrfFilterTest() : config_(setupConfig()), filter_(config_) {} @@ -69,14 +71,14 @@ class CsrfFilterTest : public testing::Test { } void setFilterEnabled(bool enabled) { - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("csrf.enabled", testing::Matcher(_))) .WillByDefault(Return(enabled)); } void setShadowEnabled(bool enabled) { - ON_CALL(runtime_.snapshot_, + ON_CALL(factory_context_.runtime_loader_.snapshot_, featureEnabled("csrf.shadow_enabled", testing::Matcher(_))) .WillByDefault(Return(enabled)); @@ -87,7 +89,7 @@ class CsrfFilterTest : public testing::Test { Buffer::OwnedImpl data_; Router::MockDirectResponseEntry direct_response_entry_; Stats::IsolatedStoreImpl stats_; - NiceMock runtime_; + NiceMock factory_context_; CsrfFilterConfigSharedPtr config_; CsrfFilter filter_; diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 13abe5f9969d..46ec34379f44 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -54,10 +54,9 @@ envoy_extension_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/http:stream_encoder_mock", "//test/mocks/http:stream_mock", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/server:overload_manager_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/proto:helloworld_proto_cc_proto", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -88,7 +87,7 @@ envoy_extension_cc_test( "//test/common/http:common_lib", "//test/mocks/event:event_mocks", "//test/mocks/local_info:local_info_mocks", - "//test/mocks/server:factory_context_mocks", + "//test/mocks/server:server_factory_context_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 85c742435fe7..b10e28c20ceb 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -25,7 +25,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/server/factory_context.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" @@ -136,7 +136,7 @@ class HttpFilterTest : public testing::Test { proto_config, 200ms, 10000, *stats_store_.rootScope(), "", std::make_shared( Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)), - local_info_); + factory_context_); filter_ = std::make_unique(config_, std::move(client_), proto_config.grpc_service()); filter_->setEncoderFilterCallbacks(encoder_callbacks_); EXPECT_CALL(encoder_callbacks_, encoderBufferLimit()).WillRepeatedly(Return(BufferSize)); @@ -579,7 +579,7 @@ class HttpFilterTest : public testing::Test { Envoy::Event::SimulatedTimeSystem* test_time_; envoy::config::core::v3::Metadata dynamic_metadata_; testing::NiceMock connection_; - testing::NiceMock local_info_; + NiceMock factory_context_; }; // Using the default configuration, test the filter with a processor that diff --git a/test/extensions/filters/http/ext_proc/ordering_test.cc b/test/extensions/filters/http/ext_proc/ordering_test.cc index 06d1e89e4aba..c6ecc82ea31a 100644 --- a/test/extensions/filters/http/ext_proc/ordering_test.cc +++ b/test/extensions/filters/http/ext_proc/ordering_test.cc @@ -7,9 +7,9 @@ #include "test/extensions/filters/http/ext_proc/mock_server.h" #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/router/mocks.h" +#include "test/mocks/server/server_factory_context.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/cluster_manager.h" @@ -75,7 +75,7 @@ class OrderingTest : public testing::Test { proto_config, kMessageTimeout, kMaxMessageTimeoutMs, *stats_store_.rootScope(), "", std::make_shared( Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)), - local_info_); + factory_context_); filter_ = std::make_unique(config_, std::move(client_), proto_config.grpc_service()); filter_->setEncoderFilterCallbacks(encoder_callbacks_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); @@ -216,7 +216,7 @@ class OrderingTest : public testing::Test { Http::TestResponseHeaderMapImpl response_headers_; Http::TestRequestTrailerMapImpl request_trailers_; Http::TestResponseTrailerMapImpl response_trailers_; - NiceMock local_info_; + NiceMock factory_context_; }; // A base class for tests that will check that gRPC streams fail while being created diff --git a/test/extensions/filters/http/ext_proc/unit_test_fuzz/BUILD b/test/extensions/filters/http/ext_proc/unit_test_fuzz/BUILD index 630a47ea12df..13649e730933 100644 --- a/test/extensions/filters/http/ext_proc/unit_test_fuzz/BUILD +++ b/test/extensions/filters/http/ext_proc/unit_test_fuzz/BUILD @@ -41,7 +41,7 @@ envoy_cc_fuzz_test( "//source/extensions/filters/http/ext_proc:config", "//test/extensions/filters/http/common/fuzz:http_filter_fuzzer_lib", "//test/mocks/http:http_mocks", - "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", + "//test/mocks/server:server_factory_context_mocks", ], ) diff --git a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc index 184c4be32365..2675fda20eea 100644 --- a/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc +++ b/test/extensions/filters/http/ext_proc/unit_test_fuzz/ext_proc_unit_test_fuzz.cc @@ -5,8 +5,8 @@ #include "test/extensions/filters/http/ext_proc/unit_test_fuzz/mocks.h" #include "test/fuzz/fuzz_runner.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/server/server_factory_context.h" using testing::Return; using testing::ReturnRef; @@ -47,7 +47,7 @@ class FuzzerMocks { NiceMock response_trailers_; NiceMock buffer_; NiceMock async_client_stream_info_; - NiceMock local_info_; + NiceMock factory_context_; }; DEFINE_PROTO_FUZZER( @@ -87,7 +87,7 @@ DEFINE_PROTO_FUZZER( proto_config, std::chrono::milliseconds(200), 200, *stats_store.rootScope(), "", std::make_shared( Envoy::Extensions::Filters::Common::Expr::createBuilder(nullptr)), - mocks.local_info_); + mocks.factory_context_); } catch (const EnvoyException& e) { ENVOY_LOG_MISC(debug, "EnvoyException during ext_proc filter config validation: {}", e.what()); return; From fa482d5efcdbd63a3ada92617a32f60dde8e23d2 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 15 Mar 2024 18:57:02 +0000 Subject: [PATCH 19/36] Revert "io_uring: add io_uring socket handle (#32265)" (#32936) This reverts commit e8ea33a86a0271ff410659ae3755dc513222322a. Signed-off-by: Ryan Northey --- source/common/io/io_uring_worker_impl.h | 2 - source/common/network/BUILD | 24 +- .../network/io_uring_socket_handle_impl.cc | 415 -------- .../network/io_uring_socket_handle_impl.h | 94 -- .../common/network/socket_interface_impl.cc | 28 +- source/common/network/socket_interface_impl.h | 6 +- test/common/network/BUILD | 31 - ...ing_socket_handle_impl_integration_test.cc | 973 ------------------ .../io_uring_socket_handle_impl_test.cc | 133 --- tools/spelling/spelling_dictionary.txt | 1 - 10 files changed, 7 insertions(+), 1700 deletions(-) delete mode 100644 source/common/network/io_uring_socket_handle_impl.cc delete mode 100644 source/common/network/io_uring_socket_handle_impl.h delete mode 100644 test/common/network/io_uring_socket_handle_impl_integration_test.cc delete mode 100644 test/common/network/io_uring_socket_handle_impl_test.cc diff --git a/source/common/io/io_uring_worker_impl.h b/source/common/io/io_uring_worker_impl.h index b2ebd1d38049..1aa2222592d7 100644 --- a/source/common/io/io_uring_worker_impl.h +++ b/source/common/io/io_uring_worker_impl.h @@ -201,8 +201,6 @@ class IoUringServerSocket : public IoUringSocketEntry { void onShutdown(Request* req, int32_t result, bool injected) override; void onCancel(Request* req, int32_t result, bool injected) override; - Buffer::OwnedImpl& getReadBuffer() { return read_buf_; } - protected: // Since the write of IoUringSocket is async, there may have write request is on the fly when // close the socket. This timeout is setting for a time to wait the write request done. diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 4268d592f29c..53ca953779a4 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -250,43 +250,25 @@ envoy_cc_library( "io_socket_handle_impl.cc", "socket_interface_impl.cc", "win32_socket_handle_impl.cc", - ] + select({ - "//bazel:linux": [ - "io_uring_socket_handle_impl.cc", - ], - "//conditions:default": [], - }), + ], hdrs = [ "io_socket_handle_base_impl.h", "io_socket_handle_impl.h", "socket_interface_impl.h", "win32_socket_handle_impl.h", - ] + select({ - "//bazel:linux": [ - "io_uring_socket_handle_impl.h", - ], - "//conditions:default": [], - }), + ], deps = [ ":address_lib", ":io_socket_error_lib", ":socket_interface_lib", ":socket_lib", - "//envoy/common/io:io_uring_interface", "//envoy/event:dispatcher_interface", "//envoy/network:io_handle_interface", "//source/common/api:os_sys_calls_lib", "//source/common/buffer:buffer_lib", "//source/common/event:dispatcher_includes", "@envoy_api//envoy/extensions/network/socket_interface/v3:pkg_cc_proto", - ] + select({ - "//bazel:linux": [ - "//source/common/io:io_uring_impl_lib", - "//source/common/io:io_uring_worker_factory_impl_lib", - "//source/common/io:io_uring_worker_lib", - ], - "//conditions:default": [], - }), + ], alwayslink = LEGACY_ALWAYSLINK, ) diff --git a/source/common/network/io_uring_socket_handle_impl.cc b/source/common/network/io_uring_socket_handle_impl.cc deleted file mode 100644 index 330a250edd61..000000000000 --- a/source/common/network/io_uring_socket_handle_impl.cc +++ /dev/null @@ -1,415 +0,0 @@ -#include "source/common/network/io_uring_socket_handle_impl.h" - -#include "envoy/buffer/buffer.h" -#include "envoy/common/exception.h" -#include "envoy/event/dispatcher.h" - -#include "source/common/api/os_sys_calls_impl.h" -#include "source/common/common/assert.h" -#include "source/common/common/utility.h" -#include "source/common/io/io_uring_worker_impl.h" -#include "source/common/network/address_impl.h" -#include "source/common/network/io_socket_error_impl.h" -#include "source/common/network/io_socket_handle_impl.h" -#include "source/common/network/socket_interface_impl.h" - -namespace Envoy { -namespace Network { - -IoUringSocketHandleImpl::IoUringSocketHandleImpl(Io::IoUringWorkerFactory& io_uring_worker_factory, - os_fd_t fd, bool socket_v6only, - absl::optional domain, bool is_server_socket) - : IoSocketHandleBaseImpl(fd, socket_v6only, domain), - io_uring_worker_factory_(io_uring_worker_factory), - io_uring_socket_type_(is_server_socket ? IoUringSocketType::Server - : IoUringSocketType::Unknown) { - ENVOY_LOG(trace, "construct io uring socket handle, fd = {}, type = {}", fd_, - ioUringSocketTypeStr()); -} - -IoUringSocketHandleImpl::~IoUringSocketHandleImpl() { - ENVOY_LOG(trace, "~IoUringSocketHandleImpl, type = {}", ioUringSocketTypeStr()); - if (SOCKET_VALID(fd_)) { - // If the socket is owned by the main thread like a listener, it may outlive the IoUringWorker. - // We have to ensure that the current thread has been registered and the io_uring in the thread - // is still available. - // TODO(zhxie): for current usage of server socket and client socket, the check may be - // redundant. - if (io_uring_socket_type_ != IoUringSocketType::Unknown && - io_uring_socket_type_ != IoUringSocketType::Accept && - io_uring_worker_factory_.currentThreadRegistered() && io_uring_socket_.has_value()) { - if (io_uring_socket_->getStatus() != Io::IoUringSocketStatus::Closed) { - io_uring_socket_.ref().close(false); - } - } else { - // The TLS slot has been shut down by this moment with io_uring wiped out, thus better use the - // POSIX system call instead of IoUringSocketHandleImpl::close(). - ::close(fd_); - } - } -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::close() { - ENVOY_LOG(trace, "close, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - ASSERT(SOCKET_VALID(fd_)); - - if (io_uring_socket_type_ == IoUringSocketType::Unknown || - io_uring_socket_type_ == IoUringSocketType::Accept || !io_uring_socket_.has_value()) { - if (file_event_) { - file_event_.reset(); - } - ::close(fd_); - } else { - io_uring_socket_.ref().close(false); - io_uring_socket_.reset(); - } - SET_SOCKET_INVALID(fd_); - return Api::ioCallUint64ResultNoError(); -} - -Api::IoCallUint64Result -IoUringSocketHandleImpl::readv(uint64_t max_length, Buffer::RawSlice* slices, uint64_t num_slice) { - ENVOY_LOG(debug, "readv, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - Api::IoCallUint64Result result = copyOut(max_length, slices, num_slice); - if (result.ok()) { - // If the return value is 0, there should be a remote close. Return the value directly. - if (result.return_value_ != 0) { - io_uring_socket_->getReadParam()->buf_.drain(result.return_value_); - } - } - return result; -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::read(Buffer::Instance& buffer, - absl::optional max_length_opt) { - ENVOY_LOG(trace, "read, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - auto read_result = checkReadResult(); - if (read_result.has_value()) { - return std::move(*read_result); - } - - const OptRef& read_param = io_uring_socket_->getReadParam(); - uint64_t max_read_length = - std::min(max_length_opt.value_or(UINT64_MAX), read_param->buf_.length()); - buffer.move(read_param->buf_, max_read_length); - return {max_read_length, IoSocketError::none()}; -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::writev(const Buffer::RawSlice* slices, - uint64_t num_slice) { - ENVOY_LOG(trace, "writev, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - auto write_result = checkWriteResult(); - if (write_result.has_value()) { - return std::move(*write_result); - } - - uint64_t ret = io_uring_socket_->write(slices, num_slice); - return {ret, IoSocketError::none()}; -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::write(Buffer::Instance& buffer) { - ENVOY_LOG(trace, "write {}, fd = {}, type = {}", buffer.length(), fd_, ioUringSocketTypeStr()); - - auto write_result = checkWriteResult(); - if (write_result.has_value()) { - return std::move(*write_result); - } - - uint64_t buffer_size = buffer.length(); - io_uring_socket_->write(buffer); - return {buffer_size, IoSocketError::none()}; -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::sendmsg(const Buffer::RawSlice*, uint64_t, int, - const Address::Ip*, - const Address::Instance&) { - ENVOY_LOG(trace, "sendmsg, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - return Network::IoSocketError::ioResultSocketInvalidAddress(); -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::recvmsg(Buffer::RawSlice*, const uint64_t, - uint32_t, RecvMsgOutput&) { - ENVOY_LOG(trace, "recvmsg, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - return Network::IoSocketError::ioResultSocketInvalidAddress(); -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::recvmmsg(RawSliceArrays&, uint32_t, - RecvMsgOutput&) { - ENVOY_LOG(trace, "recvmmsg, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - return Network::IoSocketError::ioResultSocketInvalidAddress(); -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::recv(void* buffer, size_t length, int flags) { - ASSERT(io_uring_socket_.has_value()); - ENVOY_LOG(trace, "recv, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - // The only used flag in Envoy is MSG_PEEK for listener filters, including TLS inspectors. - ASSERT(flags == 0 || flags == MSG_PEEK); - Buffer::RawSlice slice; - slice.mem_ = buffer; - slice.len_ = length; - if (flags == 0) { - return readv(length, &slice, 1); - } - - return copyOut(length, &slice, 1); -} - -Api::SysCallIntResult IoUringSocketHandleImpl::bind(Address::InstanceConstSharedPtr address) { - ENVOY_LOG(trace, "bind {}, fd = {}, io_uring_socket_type = {}", address->asString(), fd_, - ioUringSocketTypeStr()); - return Api::OsSysCallsSingleton::get().bind(fd_, address->sockAddr(), address->sockAddrLen()); -} - -Api::SysCallIntResult IoUringSocketHandleImpl::listen(int backlog) { - ENVOY_LOG(trace, "listen, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - ASSERT(io_uring_socket_type_ == IoUringSocketType::Unknown); - - io_uring_socket_type_ = IoUringSocketType::Accept; - setBlocking(false); - return Api::OsSysCallsSingleton::get().listen(fd_, backlog); -} - -IoHandlePtr IoUringSocketHandleImpl::accept(struct sockaddr* addr, socklen_t* addrlen) { - ENVOY_LOG(trace, "accept, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - ASSERT(io_uring_socket_type_ == IoUringSocketType::Accept); - - auto result = Api::OsSysCallsSingleton::get().accept(fd_, addr, addrlen); - if (SOCKET_INVALID(result.return_value_)) { - return nullptr; - } - return std::make_unique(io_uring_worker_factory_, result.return_value_, - socket_v6only_, domain_, true); -} - -Api::SysCallIntResult IoUringSocketHandleImpl::connect(Address::InstanceConstSharedPtr address) { - ENVOY_LOG(trace, "connect, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - ASSERT(io_uring_socket_type_ == IoUringSocketType::Client); - - io_uring_socket_->connect(address); - return Api::SysCallIntResult{-1, EINPROGRESS}; -} - -Api::SysCallIntResult IoUringSocketHandleImpl::getOption(int level, int optname, void* optval, - socklen_t* optlen) { - // io_uring socket does not populate connect error in getsockopt. Instead, the connect error is - // returned in onConnect() handling. We will imitate the default socket behavior here for client - // socket with optname SO_ERROR, which is only used to check connect error. - if (io_uring_socket_type_ == IoUringSocketType::Client && optname == SO_ERROR && - io_uring_socket_.has_value()) { - int* intval = static_cast(optval); - *intval = -io_uring_socket_->getWriteParam()->result_; - *optlen = sizeof(int); - return {0, 0}; - } - - return IoSocketHandleBaseImpl::getOption(level, optname, optval, optlen); -} - -IoHandlePtr IoUringSocketHandleImpl::duplicate() { - ENVOY_LOG(trace, "duplicate, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - Api::SysCallSocketResult result = Api::OsSysCallsSingleton::get().duplicate(fd_); - RELEASE_ASSERT(result.return_value_ != -1, - fmt::format("duplicate failed for '{}': ({}) {}", fd_, result.errno_, - errorDetails(result.errno_))); - return SocketInterfaceImpl::makePlatformSpecificSocket(result.return_value_, socket_v6only_, - domain_, &io_uring_worker_factory_); -} - -void IoUringSocketHandleImpl::initializeFileEvent(Event::Dispatcher& dispatcher, - Event::FileReadyCb cb, - Event::FileTriggerType trigger, uint32_t events) { - ENVOY_LOG(trace, "initialize file event, fd = {}, type = {}, has socket = {}", fd_, - ioUringSocketTypeStr(), io_uring_socket_.has_value()); - - // The IoUringSocket has already been created. It usually happened after a resetFileEvents. - if (io_uring_socket_.has_value()) { - if (&io_uring_socket_->getIoUringWorker().dispatcher() == - &io_uring_worker_factory_.getIoUringWorker()->dispatcher()) { - io_uring_socket_->setFileReadyCb(std::move(cb)); - io_uring_socket_->enableRead(); - io_uring_socket_->enableCloseEvent(events & Event::FileReadyType::Closed); - } else { - ENVOY_LOG(trace, "initialize file event from another thread, fd = {}, type = {}", fd_, - ioUringSocketTypeStr()); - Thread::CondVar wait_cv; - Thread::MutexBasicLockable mutex; - Buffer::OwnedImpl buf; - os_fd_t fd = io_uring_socket_->fd(); - - { - Thread::LockGuard lock(mutex); - // Close the original socket in its running thread. - io_uring_socket_->getIoUringWorker().dispatcher().post( - [&origin_socket = io_uring_socket_, &wait_cv, &mutex, &buf]() { - // Move the data of original socket's read buffer to the temporary buf. - origin_socket->close(true, [&wait_cv, &mutex, &buf](Buffer::Instance& buffer) { - Thread::LockGuard lock(mutex); - buf.move(buffer); - wait_cv.notifyOne(); - }); - }); - wait_cv.wait(mutex); - } - - // Move the temporary buf to the newly created one. - io_uring_socket_ = io_uring_worker_factory_.getIoUringWorker()->addServerSocket( - fd, buf, std::move(cb), events & Event::FileReadyType::Closed); - } - return; - } - - switch (io_uring_socket_type_) { - case IoUringSocketType::Accept: - file_event_ = dispatcher.createFileEvent(fd_, cb, trigger, events); - break; - case IoUringSocketType::Server: - io_uring_socket_ = io_uring_worker_factory_.getIoUringWorker()->addServerSocket( - fd_, std::move(cb), events & Event::FileReadyType::Closed); - break; - case IoUringSocketType::Unknown: - case IoUringSocketType::Client: - io_uring_socket_type_ = IoUringSocketType::Client; - io_uring_socket_ = io_uring_worker_factory_.getIoUringWorker()->addClientSocket( - fd_, std::move(cb), events & Event::FileReadyType::Closed); - break; - } -} - -void IoUringSocketHandleImpl::activateFileEvents(uint32_t events) { - ENVOY_LOG(trace, "activate file events {}, fd = {}, type = {}", events, fd_, - ioUringSocketTypeStr()); - - if (io_uring_socket_type_ == IoUringSocketType::Accept) { - ASSERT(file_event_ != nullptr); - file_event_->activate(events); - return; - } - - if (events & Event::FileReadyType::Read) { - io_uring_socket_->injectCompletion(Io::Request::RequestType::Read); - } - if (events & Event::FileReadyType::Write) { - io_uring_socket_->injectCompletion(Io::Request::RequestType::Write); - } -} - -void IoUringSocketHandleImpl::enableFileEvents(uint32_t events) { - ENVOY_LOG(trace, "enable file events {}, fd = {}, type = {}", events, fd_, - ioUringSocketTypeStr()); - - if (io_uring_socket_type_ == IoUringSocketType::Accept) { - ASSERT(file_event_ != nullptr); - file_event_->setEnabled(events); - return; - } - - if (events & Event::FileReadyType::Read) { - io_uring_socket_->enableRead(); - } else { - io_uring_socket_->disableRead(); - } - io_uring_socket_->enableCloseEvent(events & Event::FileReadyType::Closed); -} - -void IoUringSocketHandleImpl::resetFileEvents() { - ENVOY_LOG(trace, "reset file events, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - if (io_uring_socket_type_ == IoUringSocketType::Accept) { - file_event_.reset(); - return; - } - - io_uring_socket_->disableRead(); - io_uring_socket_->enableCloseEvent(false); -} - -Api::SysCallIntResult IoUringSocketHandleImpl::shutdown(int how) { - ENVOY_LOG(trace, "shutdown, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - - ASSERT(io_uring_socket_type_ == IoUringSocketType::Server || - io_uring_socket_type_ == IoUringSocketType::Client); - - io_uring_socket_->shutdown(how); - return Api::SysCallIntResult{0, 0}; -} - -absl::optional IoUringSocketHandleImpl::checkReadResult() const { - ASSERT(io_uring_socket_.has_value()); - ASSERT(io_uring_socket_type_ == IoUringSocketType::Server || - io_uring_socket_type_ == IoUringSocketType::Client); - - const OptRef& read_param = io_uring_socket_->getReadParam(); - // A absl::nullopt read param means that there is no io_uring request which has been done. - if (read_param == absl::nullopt) { - if (io_uring_socket_->getStatus() != Io::IoUringSocketStatus::RemoteClosed) { - return Api::IoCallUint64Result{0, IoSocketError::getIoSocketEagainError()}; - } else { - ENVOY_LOG(trace, "read, fd = {}, type = {}, remote close", fd_, ioUringSocketTypeStr()); - return Api::ioCallUint64ResultNoError(); - } - } - - if (read_param->result_ == 0) { - ENVOY_LOG(trace, "read remote close, fd = {}, type = {}", fd_, ioUringSocketTypeStr()); - return Api::ioCallUint64ResultNoError(); - } - - if (read_param->result_ < 0) { - ASSERT(read_param->buf_.length() == 0); - ENVOY_LOG(trace, "read error = {}, fd = {}, type = {}", -read_param->result_, fd_, - ioUringSocketTypeStr()); - if (read_param->result_ == -EAGAIN) { - return Api::IoCallUint64Result{0, IoSocketError::getIoSocketEagainError()}; - } - return Api::IoCallUint64Result{0, IoSocketError::create(-read_param->result_)}; - } - - // The buffer has been read in the previous call, return EAGAIN to tell the caller to wait for - // the next read event. - if (read_param->buf_.length() == 0) { - return Api::IoCallUint64Result{0, IoSocketError::getIoSocketEagainError()}; - } - return absl::nullopt; -} - -absl::optional IoUringSocketHandleImpl::checkWriteResult() const { - ASSERT(io_uring_socket_.has_value()); - ASSERT(io_uring_socket_type_ == IoUringSocketType::Server || - io_uring_socket_type_ == IoUringSocketType::Client); - - const OptRef& write_param = io_uring_socket_->getWriteParam(); - if (write_param != absl::nullopt) { - // EAGAIN indicates an injected write event to trigger IO handle write. Submit the new write to - // the io_uring. - if (write_param->result_ < 0 && write_param->result_ != -EAGAIN) { - return Api::IoCallUint64Result{0, IoSocketError::create(-write_param->result_)}; - } - } - return absl::nullopt; -} - -Api::IoCallUint64Result IoUringSocketHandleImpl::copyOut(uint64_t max_length, - Buffer::RawSlice* slices, - uint64_t num_slice) { - auto read_result = checkReadResult(); - if (read_result.has_value()) { - return std::move(*read_result); - } - - const OptRef& read_param = io_uring_socket_->getReadParam(); - const uint64_t max_read_length = std::min(max_length, static_cast(read_param->result_)); - uint64_t num_bytes_to_read = read_param->buf_.copyOutToSlices(max_read_length, slices, num_slice); - return {num_bytes_to_read, IoSocketError::none()}; -} - -} // namespace Network -} // namespace Envoy diff --git a/source/common/network/io_uring_socket_handle_impl.h b/source/common/network/io_uring_socket_handle_impl.h deleted file mode 100644 index 85e8469b11d1..000000000000 --- a/source/common/network/io_uring_socket_handle_impl.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include "envoy/buffer/buffer.h" -#include "envoy/common/io/io_uring.h" -#include "envoy/network/io_handle.h" - -#include "source/common/buffer/buffer_impl.h" -#include "source/common/common/logger.h" -#include "source/common/network/io_socket_handle_base_impl.h" - -namespace Envoy { - -namespace Network { - -class IoUringSocketHandleImpl; - -using IoUringSocketHandleImplOptRef = - absl::optional>; - -enum class IoUringSocketType { - Unknown, - Accept, - Server, - Client, -}; - -/** - * IoHandle derivative for sockets. - */ -class IoUringSocketHandleImpl : public IoSocketHandleBaseImpl { -public: - IoUringSocketHandleImpl(Io::IoUringWorkerFactory& io_uring_worker_factory, - os_fd_t fd = INVALID_SOCKET, bool socket_v6only = false, - absl::optional domain = absl::nullopt, - bool is_server_socket = false); - ~IoUringSocketHandleImpl() override; - - Api::IoCallUint64Result close() override; - Api::IoCallUint64Result readv(uint64_t max_length, Buffer::RawSlice* slices, - uint64_t num_slice) override; - Api::IoCallUint64Result read(Buffer::Instance& buffer, - absl::optional max_length_opt) override; - Api::IoCallUint64Result writev(const Buffer::RawSlice* slices, uint64_t num_slice) override; - Api::IoCallUint64Result write(Buffer::Instance& buffer) override; - Api::IoCallUint64Result sendmsg(const Buffer::RawSlice* slices, uint64_t num_slice, int flags, - const Address::Ip* self_ip, - const Address::Instance& peer_address) override; - Api::IoCallUint64Result recvmsg(Buffer::RawSlice* slices, const uint64_t num_slice, - uint32_t self_port, RecvMsgOutput& output) override; - Api::IoCallUint64Result recvmmsg(RawSliceArrays& slices, uint32_t self_port, - RecvMsgOutput& output) override; - Api::IoCallUint64Result recv(void* buffer, size_t length, int flags) override; - Api::SysCallIntResult bind(Address::InstanceConstSharedPtr address) override; - Api::SysCallIntResult listen(int backlog) override; - IoHandlePtr accept(struct sockaddr* addr, socklen_t* addrlen) override; - Api::SysCallIntResult connect(Address::InstanceConstSharedPtr address) override; - Api::SysCallIntResult getOption(int level, int optname, void* optval, socklen_t* optlen) override; - IoHandlePtr duplicate() override; - void initializeFileEvent(Event::Dispatcher& dispatcher, Event::FileReadyCb cb, - Event::FileTriggerType trigger, uint32_t events) override; - void activateFileEvents(uint32_t events) override; - void enableFileEvents(uint32_t events) override; - void resetFileEvents() override; - Api::SysCallIntResult shutdown(int how) override; - -protected: - std::string ioUringSocketTypeStr() const { - switch (io_uring_socket_type_) { - case IoUringSocketType::Unknown: - return "unknown"; - case IoUringSocketType::Accept: - return "accept"; - case IoUringSocketType::Client: - return "client"; - case IoUringSocketType::Server: - return "server"; - } - PANIC_DUE_TO_CORRUPT_ENUM; - } - - Io::IoUringWorkerFactory& io_uring_worker_factory_; - IoUringSocketType io_uring_socket_type_; - OptRef io_uring_socket_{absl::nullopt}; - - Event::FileEventPtr file_event_{nullptr}; - - absl::optional checkReadResult() const; - absl::optional checkWriteResult() const; - Api::IoCallUint64Result copyOut(uint64_t max_length, Buffer::RawSlice* slices, - uint64_t num_slice); -}; - -} // namespace Network -} // namespace Envoy diff --git a/source/common/network/socket_interface_impl.cc b/source/common/network/socket_interface_impl.cc index a3dbec704417..aead17ecc3ab 100644 --- a/source/common/network/socket_interface_impl.cc +++ b/source/common/network/socket_interface_impl.cc @@ -10,39 +10,15 @@ #include "source/common/network/io_socket_handle_impl.h" #include "source/common/network/win32_socket_handle_impl.h" -#ifdef __linux__ -#include "source/common/network/io_uring_socket_handle_impl.h" -#endif - namespace Envoy { namespace Network { -namespace { -[[maybe_unused]] bool hasIoUringWorkerFactory(Io::IoUringWorkerFactory* io_uring_worker_factory) { - return io_uring_worker_factory != nullptr && io_uring_worker_factory->currentThreadRegistered() && - io_uring_worker_factory->getIoUringWorker() != absl::nullopt; -} -} // namespace - -IoHandlePtr SocketInterfaceImpl::makePlatformSpecificSocket( - int socket_fd, bool socket_v6only, absl::optional domain, - [[maybe_unused]] Io::IoUringWorkerFactory* io_uring_worker_factory) { +IoHandlePtr SocketInterfaceImpl::makePlatformSpecificSocket(int socket_fd, bool socket_v6only, + absl::optional domain) { if constexpr (Event::PlatformDefaultTriggerType == Event::FileTriggerType::EmulatedEdge) { return std::make_unique(socket_fd, socket_v6only, domain); } -#ifdef __linux__ - // Only create IoUringSocketHandleImpl when the IoUringWorkerFactory has been created and it has - // been registered in the TLS, initialized. There are cases that test may create threads before - // IoUringWorkerFactory has been added to the TLS and got initialized. - if (hasIoUringWorkerFactory(io_uring_worker_factory)) { - return std::make_unique(*io_uring_worker_factory, socket_fd, - socket_v6only, domain); - } else { - return std::make_unique(socket_fd, socket_v6only, domain); - } -#else return std::make_unique(socket_fd, socket_v6only, domain); -#endif } IoHandlePtr SocketInterfaceImpl::makeSocket(int socket_fd, bool socket_v6only, diff --git a/source/common/network/socket_interface_impl.h b/source/common/network/socket_interface_impl.h index 6c9d8b1ac6b2..780c0dc97b71 100644 --- a/source/common/network/socket_interface_impl.h +++ b/source/common/network/socket_interface_impl.h @@ -1,6 +1,5 @@ #pragma once -#include "envoy/common/io/io_uring.h" #include "envoy/network/socket.h" #include "source/common/network/socket_interface.h" @@ -27,9 +26,8 @@ class SocketInterfaceImpl : public SocketInterfaceBase { return "envoy.extensions.network.socket_interface.default_socket_interface"; }; - static IoHandlePtr - makePlatformSpecificSocket(int socket_fd, bool socket_v6only, absl::optional domain, - Io::IoUringWorkerFactory* io_uring_worker_factory = nullptr); + static IoHandlePtr makePlatformSpecificSocket(int socket_fd, bool socket_v6only, + absl::optional domain); protected: virtual IoHandlePtr makeSocket(int socket_fd, bool socket_v6only, diff --git a/test/common/network/BUILD b/test/common/network/BUILD index ca7317da451c..9e4c1c2e8205 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -433,37 +433,6 @@ envoy_cc_test( ], ) -envoy_cc_test( - name = "io_uring_socket_handle_impl_test", - srcs = select({ - "//bazel:linux": ["io_uring_socket_handle_impl_test.cc"], - "//conditions:default": [], - }), - deps = [ - "//test/mocks/api:api_mocks", - "//test/mocks/event:event_mocks", - "//test/mocks/io:io_mocks", - "//test/test_common:threadsafe_singleton_injector_lib", - ], -) - -envoy_cc_test( - name = "io_uring_socket_handle_impl_integration_test", - srcs = select({ - "//bazel:linux": ["io_uring_socket_handle_impl_integration_test.cc"], - "//conditions:default": [], - }), - deps = [ - "//source/common/network:default_socket_interface_lib", - "//source/common/thread_local:thread_local_lib", - "//test/test_common:environment_lib", - "//test/test_common:utility_lib", - ] + select({ - "//bazel:linux": ["//source/common/io:io_uring_worker_factory_impl_lib"], - "//conditions:default": [], - }), -) - envoy_cc_test( name = "win32_socket_handle_impl_test", srcs = ["win32_socket_handle_impl_test.cc"], diff --git a/test/common/network/io_uring_socket_handle_impl_integration_test.cc b/test/common/network/io_uring_socket_handle_impl_integration_test.cc deleted file mode 100644 index 50422910c848..000000000000 --- a/test/common/network/io_uring_socket_handle_impl_integration_test.cc +++ /dev/null @@ -1,973 +0,0 @@ -#include - -#include "source/common/api/os_sys_calls_impl.h" -#include "source/common/io/io_uring_impl.h" -#include "source/common/io/io_uring_worker_factory_impl.h" -#include "source/common/network/address_impl.h" -#include "source/common/network/io_socket_handle_impl.h" -#include "source/common/network/io_uring_socket_handle_impl.h" -#include "source/common/thread_local/thread_local_impl.h" - -#include "test/test_common/test_time.h" -#include "test/test_common/utility.h" - -#include "gtest/gtest.h" - -namespace Envoy { -namespace Network { -namespace { - -class IoUringSocketHandleImplIntegrationTest : public testing::Test { -public: - IoUringSocketHandleImplIntegrationTest() : should_skip_(!Io::isIoUringSupported()) {} - - void SetUp() override { - if (should_skip_) { - GTEST_SKIP(); - } - } - - void TearDown() override { - if (!thread_is_shutdown_) { - instance_.shutdownGlobalThreading(); - instance_.shutdownThread(); - } - } - - void initialize(bool create_second_thread = false) { - api_ = Api::createApiForTest(time_system_); - dispatcher_ = api_->allocateDispatcher("test_thread"); - instance_.registerThread(*dispatcher_, true); - - if (create_second_thread) { - second_dispatcher_ = api_->allocateDispatcher("test_second_thread"); - instance_.registerThread(*second_dispatcher_, false); - } - - io_uring_worker_factory_ = - std::make_unique(10, false, 8192, 1000, instance_); - io_uring_worker_factory_->onWorkerThreadInitialized(); - - // Create the thread after the io_uring worker has been initialized, otherwise the dispatcher - // will quit when there is no any remaining registered event. - if (create_second_thread) { - second_thread_ = api_->threadFactory().createThread( - [this]() -> void { second_dispatcher_->run(Event::Dispatcher::RunType::Block); }); - } - } - - void createAcceptConnection() { - // Create an io_uring handle with accept socket. - fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; - EXPECT_GE(fd_, 0); - io_uring_socket_handle_ = std::make_unique( - *io_uring_worker_factory_, fd_, false, absl::nullopt, false); - - // Listen within the io_uring handle. - auto local_addr = std::make_shared("127.0.0.1", 0); - io_uring_socket_handle_->bind(local_addr); - io_uring_socket_handle_->listen(1); - - // Create a socket handle. - os_fd_t fd = Api::OsSysCallsSingleton::get() - .socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP) - .return_value_; - EXPECT_GE(fd, 0); - io_socket_handle_ = std::make_unique(fd); - } - - void createServerConnection() { - // Create an io_uring handle with server socket. - fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; - EXPECT_GE(fd_, 0); - io_uring_socket_handle_ = std::make_unique( - *io_uring_worker_factory_, fd_, false, absl::nullopt, true); - } - - void createClientConnection() { - // Prepare the listener. - os_fd_t fd = Api::OsSysCallsSingleton::get() - .socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP) - .return_value_; - EXPECT_GE(fd, 0); - IoHandlePtr listener = std::make_unique(fd); - - // Listen within the listener. - auto local_addr = std::make_shared("127.0.0.1", 0); - listener->bind(local_addr); - listener->listen(1); - listener->initializeFileEvent( - *dispatcher_, - [this, &listener](uint32_t) { - struct sockaddr addr; - socklen_t addrlen = sizeof(addr); - io_socket_handle_ = listener->accept(&addr, &addrlen); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Create an io_uring handle with client socket. - fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; - EXPECT_GE(fd_, 0); - io_uring_socket_handle_ = std::make_unique( - *io_uring_worker_factory_, fd_, false, absl::nullopt, false); - - int error = -1; - socklen_t error_size = sizeof(error); - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &error, &error_size](uint32_t events) { - if (events & Event::FileReadyType::Write) { - io_uring_socket_handle_->getOption(SOL_SOCKET, SO_ERROR, &error, &error_size); - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Connect from io_uring handle. - io_uring_socket_handle_->connect(listener->localAddress()); - while (error == -1) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(error, 0); - } - - bool should_skip_{false}; - Api::ApiPtr api_; - Event::DispatcherPtr dispatcher_; - Event::GlobalTimeSystem time_system_; - ThreadLocal::InstanceImpl instance_; - std::unique_ptr io_uring_worker_factory_; - os_fd_t fd_; - IoHandlePtr io_uring_socket_handle_; - IoHandlePtr io_socket_handle_; - Thread::ThreadPtr second_thread_; - Event::DispatcherPtr second_dispatcher_; - bool thread_is_shutdown_{false}; -}; - -TEST_F(IoUringSocketHandleImplIntegrationTest, Close) { - initialize(); - createServerConnection(); - - io_uring_socket_handle_->close(); - - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(errno, EBADF); -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, CancelAndClose) { - initialize(); - createServerConnection(); - - // Submit the read request. - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - io_uring_socket_handle_->close(); - - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(errno, EBADF); -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Accept) { - initialize(); - createAcceptConnection(); - - bool accepted = false; - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &accepted](uint32_t) { - struct sockaddr addr; - socklen_t addrlen = sizeof(addr); - auto handle = io_uring_socket_handle_->accept(&addr, &addrlen); - EXPECT_NE(handle, nullptr); - accepted = true; - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Connect from the socket handle. - io_socket_handle_->connect(io_uring_socket_handle_->localAddress()); - while (!accepted) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_TRUE(accepted); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(errno, EBADF); -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, AcceptError) { - initialize(); - createAcceptConnection(); - - bool accepted = false; - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &accepted](uint32_t) { - struct sockaddr addr; - socklen_t addrlen = sizeof(addr); - auto handle = io_uring_socket_handle_->accept(&addr, &addrlen); - EXPECT_EQ(handle, nullptr); - accepted = true; - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Accept nothing. - io_uring_socket_handle_->enableFileEvents(Event::FileReadyType::Read); - io_uring_socket_handle_->activateFileEvents(Event::FileReadyType::Read); - while (!accepted) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_TRUE(accepted); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(errno, EBADF); -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Connect) { - initialize(); - createClientConnection(); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, ConnectError) { - initialize(); - - // Create an io_uring handle with client socket. - fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; - EXPECT_GE(fd_, 0); - io_uring_socket_handle_ = std::make_unique( - *io_uring_worker_factory_, fd_, false, absl::nullopt, false); - - int original_error = -1; - socklen_t original_error_size = sizeof(original_error); - int error = -1; - socklen_t error_size = sizeof(error); - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &error, &error_size, &original_error, &original_error_size](uint32_t events) { - if (events & Event::FileReadyType::Write) { - // We cannot get error with getsockopt since the error has been read within io_uring. - getsockopt(io_uring_socket_handle_->fdDoNotUse(), SOL_SOCKET, SO_ERROR, &original_error, - &original_error_size); - // The read error will be transferred to the io_uring handle. - io_uring_socket_handle_->getOption(SOL_SOCKET, SO_ERROR, &error, &error_size); - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Connect from io_uring handle. - auto local_addr = std::make_shared("127.0.0.1", 9999); - io_uring_socket_handle_->connect(local_addr); - while (error == -1) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(original_error, 0); - EXPECT_EQ(error, ECONNREFUSED); - - // Close safely. - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Read) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.return_value_, data.size()); - - // Read again would expect the EAGAIN returned. - ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_TRUE(ret.wouldBlock()); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, ReadContinuity) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - bool first_read = true; - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &first_read](uint32_t event) { - if (first_read) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, 5); - EXPECT_EQ(ret.return_value_, 5); - } else { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data.substr(0, 5)); - - // Cleanup previous read. - first_read = false; - read_buffer.drain(read_buffer.length()); - EXPECT_EQ(write_buffer.length(), 0); - - // Write from the peer handle again to trigger the read event. - std::string data2 = " again"; - write_buffer.add(data2); - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data.substr(5) + data2); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, ReadActively) { - initialize(); - createClientConnection(); - - Buffer::OwnedImpl read_buffer; - - // Read actively. - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.wouldBlock(), true); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Readv) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - Buffer::Reservation reservation = read_buffer.reserveForRead(); - auto ret = - io_uring_socket_handle_->readv(11, reservation.slices(), reservation.numSlices()); - EXPECT_EQ(ret.return_value_, data.size()); - reservation.commit(ret.return_value_); - - // Read again would expect the EAGAIN returned. - Buffer::Reservation reservation2 = read_buffer.reserveForRead(); - ret = io_uring_socket_handle_->readv(11, reservation2.slices(), reservation2.numSlices()); - EXPECT_TRUE(ret.wouldBlock()); - reservation2.commit(0); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, ReadvContinuity) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - bool first_read = true; - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &first_read](uint32_t event) { - if (first_read) { - Buffer::Reservation reservation = read_buffer.reserveForRead(); - auto ret = - io_uring_socket_handle_->readv(5, reservation.slices(), reservation.numSlices()); - EXPECT_EQ(ret.return_value_, 5); - reservation.commit(ret.return_value_); - } else { - EXPECT_EQ(event, Event::FileReadyType::Read); - Buffer::Reservation reservation = read_buffer.reserveForRead(); - auto ret = - io_uring_socket_handle_->readv(1024, reservation.slices(), reservation.numSlices()); - reservation.commit(ret.return_value_); - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data.substr(0, 5)); - - // Cleanup previous read. - first_read = false; - read_buffer.drain(read_buffer.length()); - EXPECT_EQ(write_buffer.length(), 0); - - // Write from the peer handle again to trigger the read event. - std::string data2 = " again"; - write_buffer.add(data2); - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data.substr(5) + data2); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Write) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, - Event::FileReadyType::Write); - - io_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.return_value_, data.size()); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write with the io_uring handle. - auto ret = io_uring_socket_handle_->write(write_buffer).return_value_; - EXPECT_EQ(ret, data.size()); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Writev) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, - Event::FileReadyType::Write); - - io_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.return_value_, data.size()); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write with the io_uring handle. - auto ret = io_uring_socket_handle_->writev(&write_buffer.getRawSlices()[0], 1).return_value_; - EXPECT_EQ(ret, data.size()); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Recv) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl peek_buffer; - Buffer::OwnedImpl recv_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &peek_buffer, &recv_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - // Recv with MSG_PEEK will not drain the buffer. - Buffer::Reservation reservation = peek_buffer.reserveForRead(); - auto ret = io_uring_socket_handle_->recv(reservation.slices()->mem_, 5, MSG_PEEK); - EXPECT_EQ(ret.return_value_, 5); - reservation.commit(ret.return_value_); - - // Recv without flags behaves the same as readv. - Buffer::Reservation reservation2 = recv_buffer.reserveForRead(); - auto ret2 = - io_uring_socket_handle_->recv(reservation2.slices()->mem_, reservation2.length(), 0); - EXPECT_EQ(ret2.return_value_, data.size()); - reservation2.commit(ret2.return_value_); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (recv_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(peek_buffer.toString(), "Hello"); - EXPECT_EQ(recv_buffer.toString(), data); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Bind) { - initialize(); - createClientConnection(); - // Create an io_uring handle with client socket. - fd_ = Api::OsSysCallsSingleton::get().socket(AF_INET, SOCK_STREAM, IPPROTO_TCP).return_value_; - EXPECT_GE(fd_, 0); - io_uring_socket_handle_ = std::make_unique( - *io_uring_worker_factory_, fd_, false, absl::nullopt, false); - auto local_addr = std::make_shared("127.0.0.1", 0); - io_uring_socket_handle_->bind(local_addr); - - // Close safely. - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, GetOption) { - initialize(); - createServerConnection(); - - int optval = -1; - socklen_t optlen = sizeof(optval); - auto ret = io_uring_socket_handle_->getOption(SOL_SOCKET, SO_REUSEADDR, &optval, &optlen); - EXPECT_EQ(ret.return_value_, 0); - EXPECT_EQ(optval, 0); - - // Close safely. - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Duplicate) { - initialize(); - createClientConnection(); - - IoHandlePtr io_uring_socket_handle_2 = io_uring_socket_handle_->duplicate(); - - // Close safely. - io_uring_socket_handle_2->close(); - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, ActivateReadEvent) { - initialize(); - createServerConnection(); - - // Submit the read request. - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - Buffer::OwnedImpl read_buffer; - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_TRUE(ret.wouldBlock()); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - io_uring_socket_handle_->activateFileEvents(Event::FileReadyType::Read); - - // Close safely. - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, ActivateWriteEvent) { - initialize(); - createClientConnection(); - - Buffer::OwnedImpl write_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &write_buffer](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Write); - auto ret = io_uring_socket_handle_->write(write_buffer); - EXPECT_TRUE(ret.wouldBlock()); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Write); - - io_uring_socket_handle_->activateFileEvents(Event::FileReadyType::Write); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, Shutdown) { - initialize(); - createClientConnection(); - - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - io_uring_socket_handle_->shutdown(SHUT_WR); - auto ret = io_socket_handle_->read(read_buffer, absl::nullopt); - while (ret.wouldBlock()) { - ret = io_socket_handle_->read(read_buffer, absl::nullopt); - } - EXPECT_EQ(ret.return_value_, 0); - - // Close safely. - io_socket_handle_->close(); - io_uring_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -// Tests the case of a write event will be emitted after remote closed when the read is disabled and -// the write is listened only. -TEST_F(IoUringSocketHandleImplIntegrationTest, - RemoteCloseWithCloseEventDisabledAndReadEventDisabled) { - initialize(); - createClientConnection(); - - Buffer::OwnedImpl read_buffer; - bool got_write_event = false; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &got_write_event](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Write); - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_TRUE(ret.ok()); - EXPECT_EQ(0, ret.return_value_); - io_uring_socket_handle_->close(); - got_write_event = true; - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - io_uring_socket_handle_->enableFileEvents(Event::FileReadyType::Write); - - io_socket_handle_->close(); - while (!got_write_event) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.length(), 0); - - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, RemoteCloseWithCloseEventDisabled) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - if (ret.return_value_ > 0) { - EXPECT_EQ(ret.return_value_, data.size()); - - // Read again would expect the EAGAIN returned. - ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_TRUE(ret.wouldBlock()); - } else if (ret.return_value_ == 0) { - io_uring_socket_handle_->close(); - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - io_uring_socket_handle_->enableFileEvents(Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - io_socket_handle_->close(); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, RemoteCloseWithCloseEventEnabled) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - if (event & Event::FileReadyType::Read) { - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.return_value_, data.size()); - - // Read again would expect the EAGAIN returned. - ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_TRUE(ret.wouldBlock()); - } else if (event & Event::FileReadyType::Closed) { - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(0, ret.return_value_); - io_uring_socket_handle_->close(); - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read | Event::FileReadyType::Closed); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - io_socket_handle_->close(); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -// Ensures IoUringHandleImpl will close the socket on destruction. -TEST_F(IoUringSocketHandleImplIntegrationTest, CloseIoUringSocketOnDestruction) { - initialize(); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer, &data](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.return_value_, data.size()); - - // Read again would expect the EAGAIN returned. - ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - EXPECT_TRUE(ret.wouldBlock()); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data); - io_uring_socket_handle_.reset(); - while (io_socket_handle_->read(read_buffer, absl::nullopt).return_value_ != 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - - // Close safely. - io_socket_handle_->close(); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } -} - -// Ensures IoUringHandleImpl can be released correctly when the IoUringWorker is released earlier. -TEST_F(IoUringSocketHandleImplIntegrationTest, IoUringWorkerEarlyRelease) { - initialize(); - createClientConnection(); - - thread_is_shutdown_ = true; - instance_.shutdownGlobalThreading(); - instance_.shutdownThread(); -} - -TEST_F(IoUringSocketHandleImplIntegrationTest, MigrateServerSocketBetweenThreads) { - initialize(true); - createClientConnection(); - - std::string data = "Hello world"; - Buffer::OwnedImpl write_buffer(data); - Buffer::OwnedImpl read_buffer; - - io_uring_socket_handle_->initializeFileEvent( - *dispatcher_, - [this, &read_buffer](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, 5); - EXPECT_EQ(ret.return_value_, 5); - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // Write from the peer handle. - io_socket_handle_->write(write_buffer); - while (read_buffer.length() == 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data.substr(0, 5)); - - io_uring_socket_handle_->resetFileEvents(); - read_buffer.drain(read_buffer.length()); - - // Migrate io_uring between threads. - std::atomic initialized_in_new_thread = false; - std::atomic read_in_new_thread = false; - second_dispatcher_->post([this, &second_dispatcher = second_dispatcher_, &read_buffer, &data, - &initialized_in_new_thread, &read_in_new_thread]() { - io_uring_socket_handle_->initializeFileEvent( - *second_dispatcher, - [this, &read_buffer, &data, &read_in_new_thread](uint32_t event) { - EXPECT_EQ(event, Event::FileReadyType::Read); - auto ret = io_uring_socket_handle_->read(read_buffer, absl::nullopt); - // For the next read. - if (read_buffer.length() > data.substr(5).length()) { - read_in_new_thread = true; - } - }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - initialized_in_new_thread = true; - }); - while (!initialized_in_new_thread) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - - // Write from the peer handle again to trigger the read event. - std::string data2 = " again"; - write_buffer.add(data2); - io_socket_handle_->write(write_buffer); - while (!read_in_new_thread) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - EXPECT_EQ(read_buffer.toString(), data.substr(5) + data2); - - // Close safely. - io_socket_handle_->close(); - second_dispatcher_->post([this]() { io_uring_socket_handle_->close(); }); - while (fcntl(fd_, F_GETFD, 0) >= 0) { - dispatcher_->run(Event::Dispatcher::RunType::NonBlock); - } - second_dispatcher_->exit(); - second_thread_->join(); -} - -} // namespace -} // namespace Network -} // namespace Envoy diff --git a/test/common/network/io_uring_socket_handle_impl_test.cc b/test/common/network/io_uring_socket_handle_impl_test.cc deleted file mode 100644 index 80031b200e8a..000000000000 --- a/test/common/network/io_uring_socket_handle_impl_test.cc +++ /dev/null @@ -1,133 +0,0 @@ -#include "source/common/network/address_impl.h" -#include "source/common/network/io_uring_socket_handle_impl.h" - -#include "test/mocks/api/mocks.h" -#include "test/mocks/event/mocks.h" -#include "test/mocks/io/mocks.h" -#include "test/test_common/threadsafe_singleton_injector.h" - -namespace Envoy { -namespace Network { -namespace { - -class IoUringSocketHandleTestImpl : public IoUringSocketHandleImpl { -public: - IoUringSocketHandleTestImpl(Io::IoUringWorkerFactory& factory, bool is_server_socket) - : IoUringSocketHandleImpl(factory, INVALID_SOCKET, false, absl::nullopt, is_server_socket) {} - IoUringSocketType ioUringSocketType() const { return io_uring_socket_type_; } -}; - -class IoUringSocketHandleTest : public ::testing::Test { -public: - Io::MockIoUringSocket socket_; - Io::MockIoUringWorker worker_; - Io::MockIoUringWorkerFactory factory_; - Event::MockDispatcher dispatcher_; -}; - -TEST_F(IoUringSocketHandleTest, CreateServerSocket) { - IoUringSocketHandleTestImpl impl(factory_, true); - EXPECT_EQ(IoUringSocketType::Server, impl.ioUringSocketType()); -} - -TEST_F(IoUringSocketHandleTest, CreateClientSocket) { - IoUringSocketHandleTestImpl impl(factory_, false); - EXPECT_EQ(IoUringSocketType::Unknown, impl.ioUringSocketType()); - EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); - EXPECT_CALL(factory_, getIoUringWorker()) - .WillOnce(testing::Return(OptRef(worker_))); - impl.initializeFileEvent( - dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - EXPECT_EQ(IoUringSocketType::Client, impl.ioUringSocketType()); -} - -TEST_F(IoUringSocketHandleTest, ReadError) { - IoUringSocketHandleTestImpl impl(factory_, false); - EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); - EXPECT_CALL(factory_, getIoUringWorker()) - .WillOnce(testing::Return(OptRef(worker_))); - impl.initializeFileEvent( - dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - // EAGAIN error. - Buffer::OwnedImpl read_buffer; - Io::ReadParam read_param{read_buffer, -EAGAIN}; - auto read_param_ref = OptRef(read_param); - EXPECT_CALL(socket_, getReadParam()).WillOnce(testing::ReturnRef(read_param_ref)); - auto ret = impl.read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::Again); - - // Non-EAGAIN error. - Io::ReadParam read_param_2{read_buffer, -EBADF}; - auto read_param_ref_2 = OptRef(read_param_2); - EXPECT_CALL(socket_, getReadParam()).WillOnce(testing::ReturnRef(read_param_ref_2)); - ret = impl.read(read_buffer, absl::nullopt); - EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::BadFd); -} - -TEST_F(IoUringSocketHandleTest, WriteError) { - IoUringSocketHandleTestImpl impl(factory_, false); - EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); - EXPECT_CALL(factory_, getIoUringWorker()) - .WillOnce(testing::Return(OptRef(worker_))); - impl.initializeFileEvent( - dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - Buffer::OwnedImpl write_buffer; - Io::WriteParam write_param{-EBADF}; - auto write_param_ref = OptRef(write_param); - EXPECT_CALL(socket_, getWriteParam()).WillOnce(testing::ReturnRef(write_param_ref)); - auto ret = impl.write(write_buffer); - EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::BadFd); -} - -TEST_F(IoUringSocketHandleTest, WritevError) { - IoUringSocketHandleTestImpl impl(factory_, false); - EXPECT_CALL(worker_, addClientSocket(_, _, _)).WillOnce(testing::ReturnRef(socket_)); - EXPECT_CALL(factory_, getIoUringWorker()) - .WillOnce(testing::Return(OptRef(worker_))); - impl.initializeFileEvent( - dispatcher_, [](uint32_t) {}, Event::PlatformDefaultTriggerType, Event::FileReadyType::Read); - - Buffer::OwnedImpl write_buffer; - Io::WriteParam write_param{-EBADF}; - auto write_param_ref = OptRef(write_param); - EXPECT_CALL(socket_, getWriteParam()).WillOnce(testing::ReturnRef(write_param_ref)); - auto slice = write_buffer.frontSlice(); - auto ret = impl.writev(&slice, 1); - EXPECT_EQ(ret.err_->getErrorCode(), Api::IoError::IoErrorCode::BadFd); -} - -TEST_F(IoUringSocketHandleTest, SendmsgNotSupported) { - IoUringSocketHandleTestImpl impl(factory_, true); - - Buffer::OwnedImpl write_buffer; - auto slice = write_buffer.frontSlice(); - auto local_addr = std::make_shared("127.0.0.1", 0); - EXPECT_THAT(impl.sendmsg(&slice, 0, 0, nullptr, *local_addr).err_->getErrorCode(), - Api::IoError::IoErrorCode::NoSupport); -} - -TEST_F(IoUringSocketHandleTest, RecvmsgNotSupported) { - IoUringSocketHandleTestImpl impl(factory_, true); - - Buffer::OwnedImpl write_buffer; - auto slice = write_buffer.frontSlice(); - IoHandle::RecvMsgOutput output(0, nullptr); - EXPECT_THAT(impl.recvmsg(&slice, 0, 0, output).err_->getErrorCode(), - Api::IoError::IoErrorCode::NoSupport); -} - -TEST_F(IoUringSocketHandleTest, RecvmmsgNotSupported) { - IoUringSocketHandleTestImpl impl(factory_, true); - - Buffer::OwnedImpl write_buffer; - RawSliceArrays array(0, absl::FixedArray(0)); - IoHandle::RecvMsgOutput output(0, nullptr); - EXPECT_THAT(impl.recvmmsg(array, 0, output).err_->getErrorCode(), - Api::IoError::IoErrorCode::NoSupport); -} - -} // namespace -} // namespace Network -} // namespace Envoy diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index de722d444565..1a1eae3faa56 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1411,7 +1411,6 @@ upcasts upstreams uptime upvalue -uring urlencoded urls userdata From 7991d71d663108561e6e33fb46348a09d7a3b8ac Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 19:06:40 +0000 Subject: [PATCH 20/36] deps: Bump `com_github_luajit_luajit` -> d06beb0 (#32930) * deps: Bump `com_github_luajit_luajit` -> d06beb0 Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> * rm-win-patch Signed-off-by: Ryan Northey --------- Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Signed-off-by: Ryan Northey Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: Ryan Northey --- bazel/foreign_cc/luajit.patch | 84 ++-------------------------------- bazel/repository_locations.bzl | 6 +-- 2 files changed, 6 insertions(+), 84 deletions(-) diff --git a/bazel/foreign_cc/luajit.patch b/bazel/foreign_cc/luajit.patch index f7781067b4ab..ab525a950f69 100644 --- a/bazel/foreign_cc/luajit.patch +++ b/bazel/foreign_cc/luajit.patch @@ -39,96 +39,18 @@ index 30d64be2..ae7ec875 100644 TARGET_AR= $(CROSS)ar rcus -TARGET_STRIP= $(CROSS)strip +TARGET_STRIP?= $(CROSS)strip - + TARGET_LIBPATH= $(or $(PREFIX),/usr/local)/$(or $(MULTILIB),lib) TARGET_SONAME= libluajit-$(ABIVER).so.$(MAJVER) @@ -598,7 +598,7 @@ endif - + Q= @ E= @echo -#Q= +Q= #E= @: - + ############################################################################## -diff --git a/src/msvcbuild.bat b/src/msvcbuild.bat -index d323d8d4..2e08a3a1 100644 ---- a/src/msvcbuild.bat -+++ b/src/msvcbuild.bat -@@ -13,9 +13,7 @@ - @if not defined INCLUDE goto :FAIL - - @setlocal --@rem Add more debug flags here, e.g. DEBUGCFLAGS=/DLUA_USE_APICHECK --@set DEBUGCFLAGS= --@set LJCOMPILE=cl /nologo /c /O2 /W3 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_STDIO_INLINE=__declspec(dllexport)__inline -+@set LJCOMPILE=cl /nologo /c /W3 /D_CRT_SECURE_NO_DEPRECATE /D_CRT_STDIO_INLINE=__declspec(dllexport)__inline /DLUAJIT_ENABLE_LUA52COMPAT - @set LJLINK=link /nologo - @set LJMT=mt /nologo - @set LJLIB=lib /nologo /nodefaultlib -@@ -24,10 +22,9 @@ - @set DASC=vm_x64.dasc - @set LJDLLNAME=lua51.dll - @set LJLIBNAME=lua51.lib --@set BUILDTYPE=release - @set ALL_LIB=lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_buffer.c - --%LJCOMPILE% host\minilua.c -+%LJCOMPILE% /O2 host\minilua.c - @if errorlevel 1 goto :BAD - %LJLINK% /out:minilua.exe minilua.obj - @if errorlevel 1 goto :BAD -@@ -51,7 +48,7 @@ if exist minilua.exe.manifest^ - minilua %DASM% -LN %DASMFLAGS% -o host\buildvm_arch.h %DASC% - @if errorlevel 1 goto :BAD - --%LJCOMPILE% /I "." /I %DASMDIR% host\buildvm*.c -+%LJCOMPILE% /O2 /I "." /I %DASMDIR% host\buildvm*.c - @if errorlevel 1 goto :BAD - %LJLINK% /out:buildvm.exe buildvm*.obj - @if errorlevel 1 goto :BAD -@@ -75,26 +72,35 @@ buildvm -m folddef -o lj_folddef.h lj_opt_fold.c - - @if "%1" neq "debug" goto :NODEBUG - @shift --@set BUILDTYPE=debug --@set LJCOMPILE=%LJCOMPILE% /Zi %DEBUGCFLAGS% --@set LJLINK=%LJLINK% /opt:ref /opt:icf /incremental:no -+@set LJCOMPILE=%LJCOMPILE% /O0 /Z7 -+@set LJLINK=%LJLINK% /debug /opt:ref /opt:icf /incremental:no -+@set LJCRTDBG=d -+@goto :ENDDEBUG - :NODEBUG --@set LJLINK=%LJLINK% /%BUILDTYPE% -+@set LJCOMPILE=%LJCOMPILE% /O2 /Z7 -+@set LJLINK=%LJLINK% /release /incremental:no -+@set LJCRTDBG= -+:ENDDEBUG - @if "%1"=="amalg" goto :AMALGDLL - @if "%1"=="static" goto :STATIC --%LJCOMPILE% /MD /DLUA_BUILD_AS_DLL lj_*.c lib_*.c -+@set LJCOMPILE=%LJCOMPILE% /MD%LJCRTDBG% -+%LJCOMPILE% /DLUA_BUILD_AS_DLL lj_*.c lib_*.c - @if errorlevel 1 goto :BAD - %LJLINK% /DLL /out:%LJDLLNAME% lj_*.obj lib_*.obj - @if errorlevel 1 goto :BAD - @goto :MTDLL - :STATIC -+@shift -+@set LJCOMPILE=%LJCOMPILE% /MT%LJCRTDBG% - %LJCOMPILE% lj_*.c lib_*.c - @if errorlevel 1 goto :BAD - %LJLIB% /OUT:%LJLIBNAME% lj_*.obj lib_*.obj - @if errorlevel 1 goto :BAD - @goto :MTDLL - :AMALGDLL --%LJCOMPILE% /MD /DLUA_BUILD_AS_DLL ljamalg.c -+@shift -+@set LJCOMPILE=%LJCOMPILE% /MD%LJCRTDBG% -+%LJCOMPILE% /DLUA_BUILD_AS_DLL ljamalg.c - @if errorlevel 1 goto :BAD - %LJLINK% /DLL /out:%LJDLLNAME% ljamalg.obj lj_vm.obj - @if errorlevel 1 goto :BAD diff --git a/build.py b/build.py new file mode 100755 index 00000000..1201542c diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 5c15f4e87db3..b81d31b33a9d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -471,11 +471,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "Just-In-Time compiler for Lua", project_url = "https://luajit.org", # LuaJIT only provides rolling releases - version = "1c279127050e86e99970100e9c42e0f09cd54ab7", - sha256 = "c62f6e6d5bff89e4718709841cd6be71ad419ac9fa780c91abf1635cda923f8f", + version = "d06beb0480c5d1eb53b3343e78063950275aa281", + sha256 = "6abd146a1dfa240a965748f63221633446affa2a715e3eb03879136e3efb95f4", strip_prefix = "LuaJIT-{version}", urls = ["https://github.com/LuaJIT/LuaJIT/archive/{version}.tar.gz"], - release_date = "2023-04-16", + release_date = "2024-03-10", use_category = ["dataplane_ext"], extensions = [ "envoy.filters.http.lua", From 0dede366537171607da9e5a0ff40586e6017e827 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:48:20 -0400 Subject: [PATCH 21/36] fuzz: Extend ext_authz fuzzer coverage to grpc client (#32859) Signed-off-by: Antonio Leonti Signed-off-by: Antonio V. Leonti <53806445+antoniovleonti@users.noreply.github.com> --- test/extensions/filters/http/ext_authz/BUILD | 1 + .../http/ext_authz/ext_authz_fuzz_test.cc | 100 ++++++++++-------- 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/test/extensions/filters/http/ext_authz/BUILD b/test/extensions/filters/http/ext_authz/BUILD index 3ba58efb6997..dc83e80e281d 100644 --- a/test/extensions/filters/http/ext_authz/BUILD +++ b/test/extensions/filters/http/ext_authz/BUILD @@ -108,6 +108,7 @@ envoy_cc_fuzz_test( "//source/extensions/filters/http/ext_authz", "//test/extensions/filters/common/ext_authz:ext_authz_mocks", "//test/extensions/filters/http/common/fuzz:http_filter_fuzzer_lib", + "//test/mocks/grpc:grpc_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", diff --git a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc index 7745a37cb4d5..9b121b4e6626 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_test.cc @@ -8,6 +8,7 @@ #include "test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h" #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" +#include "test/mocks/grpc/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" @@ -23,30 +24,29 @@ namespace HttpFilters { namespace ExtAuthz { namespace { -Filters::Common::ExtAuthz::ResponsePtr -makeAuthzResponse(const Filters::Common::ExtAuthz::CheckStatus status) { - Filters::Common::ExtAuthz::ResponsePtr response = - std::make_unique(); - response->status = status; +std::unique_ptr +makeGrpcCheckResponse(const Grpc::Status::WellKnownGrpcStatus status) { + auto response = std::make_unique(); + response->mutable_status()->set_code(status); // TODO: We only add the response status. // Add fuzzed inputs for headers_to_(set/append/add), body, status_code to the Response. return response; } -Filters::Common::ExtAuthz::CheckStatus resultCaseToCheckStatus( +Grpc::Status::WellKnownGrpcStatus resultCaseToGrpcStatus( const envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase::AuthResult result) { - Filters::Common::ExtAuthz::CheckStatus check_status; + Grpc::Status::WellKnownGrpcStatus check_status; switch (result) { case envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase::OK: { - check_status = Filters::Common::ExtAuthz::CheckStatus::OK; + check_status = Grpc::Status::WellKnownGrpcStatus::Ok; break; } case envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase::ERROR: { - check_status = Filters::Common::ExtAuthz::CheckStatus::Error; + check_status = Grpc::Status::WellKnownGrpcStatus::Internal; break; } case envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase::DENIED: { - check_status = Filters::Common::ExtAuthz::CheckStatus::Denied; + check_status = Grpc::Status::WellKnownGrpcStatus::PermissionDenied; break; } default: { @@ -57,32 +57,22 @@ Filters::Common::ExtAuthz::CheckStatus resultCaseToCheckStatus( return check_status; } -class FuzzerMocks { +class StatelessFuzzerMocks { public: - FuzzerMocks() : addr_(std::make_shared("/test/test.sock")) { - - ON_CALL(decoder_callbacks_, connection()) - .WillByDefault(Return(OptRef{connection_})); + StatelessFuzzerMocks() + : addr_(std::make_shared("/test/test.sock")) { connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr_); connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr_); - - ON_CALL(decoder_callbacks_.stream_info_, dynamicMetadata()) - .WillByDefault( - Invoke([&]() -> envoy::config::core::v3::Metadata& { return filter_metadata_; })); - } - - void setFilterMetadata(const envoy::config::core::v3::Metadata& metadata) { - filter_metadata_.CopyFrom(metadata); } + // Only add mocks here that are stateless. I.e. if you need to call ON_CALL on a mock each fuzzer + // run, do not add the mock here, because it will leak memory. NiceMock factory_context_; - NiceMock decoder_callbacks_; NiceMock encoder_callbacks_; Network::Address::InstanceConstSharedPtr addr_; NiceMock connection_; - - // Returned by mock decoder_callbacks_.stream_info_.dynamicMetadata() - envoy::config::core::v3::Metadata filter_metadata_; + NiceMock async_request_; + Envoy::Tracing::MockSpan mock_span_; }; DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase& input) { @@ -93,7 +83,7 @@ DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::ext_authz::ExtAuthzT return; } - static FuzzerMocks mocks; + static StatelessFuzzerMocks mocks; NiceMock stats_store; static ScopedInjectableLoader engine(std::make_unique()); @@ -109,32 +99,52 @@ DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::ext_authz::ExtAuthzT return; } - Filters::Common::ExtAuthz::MockClient* client = new Filters::Common::ExtAuthz::MockClient(); - std::unique_ptr filter = - std::make_unique(config, Filters::Common::ExtAuthz::ClientPtr{client}); - filter->setDecoderFilterCallbacks(mocks.decoder_callbacks_); - filter->setEncoderFilterCallbacks(mocks.encoder_callbacks_); + auto internal_mock_client = std::make_shared>(); + auto grpc_client = new Filters::Common::ExtAuthz::GrpcClientImpl(internal_mock_client, + std::chrono::milliseconds(1000)); + auto filter = std::make_unique(config, Filters::Common::ExtAuthz::ClientPtr{grpc_client}); // Set metadata context. - mocks.setFilterMetadata(input.filter_metadata()); + NiceMock decoder_callbacks; + ON_CALL(decoder_callbacks, connection()) + .WillByDefault(Return(OptRef{mocks.connection_})); + envoy::config::core::v3::Metadata metadata = input.filter_metadata(); + ON_CALL(decoder_callbacks.stream_info_, dynamicMetadata()) + .WillByDefault(testing::ReturnRef(metadata)); + + filter->setDecoderFilterCallbacks(decoder_callbacks); + filter->setEncoderFilterCallbacks(mocks.encoder_callbacks_); // Set check result default action. - envoy::service::auth::v3::CheckRequest check_request; - ON_CALL(*client, check(_, _, _, _)) - .WillByDefault(Invoke([&](Filters::Common::ExtAuthz::RequestCallbacks& callbacks, - const envoy::service::auth::v3::CheckRequest& check_param, - Tracing::Span&, const StreamInfo::StreamInfo&) -> void { - check_request = check_param; - callbacks.onComplete(makeAuthzResponse(resultCaseToCheckStatus(input.result()))); - })); + ON_CALL(*internal_mock_client, sendRaw(_, _, _, _, _, _)) + .WillByDefault( + Invoke([&](absl::string_view, absl::string_view, Buffer::InstancePtr&& serialized_req, + Grpc::RawAsyncRequestCallbacks&, Tracing::Span&, + const Http::AsyncClient::RequestOptions&) -> Grpc::AsyncRequest* { + envoy::service::auth::v3::CheckRequest check_request; + EXPECT_TRUE(check_request.ParseFromString(serialized_req->toString())) + << "Could not parse serialized check request"; + + // TODO: Query the request header map in HttpFilterFuzzer to test + // headers_to_(add/remove/append). + // TODO: Test check request attributes against config + // and filter metadata. + ENVOY_LOG_MISC(trace, "Check Request attributes {}", + check_request.attributes().DebugString()); + + const Grpc::Status::WellKnownGrpcStatus status = resultCaseToGrpcStatus(input.result()); + if (status == Grpc::Status::WellKnownGrpcStatus::Ok) { + grpc_client->onSuccess(makeGrpcCheckResponse(status), mocks.mock_span_); + } else { + grpc_client->onFailure(status, "Fuzz input status was not ok!", mocks.mock_span_); + } + return &mocks.async_request_; + })); // TODO: Add response headers. Envoy::Extensions::HttpFilters::HttpFilterFuzzer fuzzer; fuzzer.runData(static_cast(filter.get()), input.request_data()); - // TODO: Query the request header map in HttpFilterFuzzer to test headers_to_(add/remove/append). - // TODO: Test check request attributes against config and filter metadata. - ENVOY_LOG_MISC(trace, "Check Request attributes {}", check_request.attributes().DebugString()); } } // namespace From 9e447bfaa2eaf29925bfb279b83e1ad202d1b514 Mon Sep 17 00:00:00 2001 From: GiantCroc Date: Sun, 17 Mar 2024 21:53:10 +0800 Subject: [PATCH 22/36] contrib: add qatzstd compressor (#32166) Signed-off-by: giantcroc --- api/BUILD | 1 + .../qatzstd/compressor/v3alpha/BUILD | 9 ++ .../qatzstd/compressor/v3alpha/qatzstd.proto | 69 +++++++++++ api/versioning/BUILD | 1 + bazel/foreign_cc/qatzstd.patch | 13 +++ bazel/repositories.bzl | 9 ++ bazel/repository_locations.bzl | 17 ++- changelogs/current.yaml | 3 + contrib/all_contrib_extensions.bzl | 2 + contrib/contrib_build_config.bzl | 1 + contrib/extensions_metadata.yaml | 5 + contrib/qat/BUILD | 6 +- .../qatzstd/compressor/source/BUILD | 69 +++++++++++ .../qatzstd/compressor/source/config.cc | 81 +++++++++++++ .../qatzstd/compressor/source/config.h | 83 +++++++++++++ .../source/qatzstd_compressor_impl.cc | 85 ++++++++++++++ .../source/qatzstd_compressor_impl.h | 51 ++++++++ .../compression/qatzstd/compressor/test/BUILD | 20 ++++ .../test/qatzstd_compressor_impl_test.cc | 110 ++++++++++++++++++ docs/BUILD | 1 + docs/root/api-v3/config/contrib/qat/qat.rst | 1 + .../other_features/_include/qatzstd.yaml | 63 ++++++++++ .../other_features/other_features.rst | 1 + .../configuration/other_features/qatzstd.rst | 34 ++++++ source/common/common/logger.h | 1 + source/common/compression/zstd/common/BUILD | 19 +++ .../compression/zstd/common/base.cc | 4 +- .../compression/zstd/common/base.h | 2 - .../common/compression/zstd/compressor/BUILD | 20 ++++ .../compressor/zstd_compressor_impl_base.cc | 59 ++++++++++ .../compressor/zstd_compressor_impl_base.h | 43 +++++++ .../extensions/compression/zstd/common/BUILD | 10 -- .../compression/zstd/compressor/BUILD | 3 +- .../zstd/compressor/zstd_compressor_impl.cc | 47 ++------ .../zstd/compressor/zstd_compressor_impl.h | 20 ++-- .../compression/zstd/decompressor/BUILD | 2 +- .../decompressor/zstd_decompressor_impl.cc | 2 +- .../decompressor/zstd_decompressor_impl.h | 4 +- test/test_common/utility.cc | 6 +- test/test_common/utility.h | 3 +- tools/spelling/spelling_dictionary.txt | 1 + 41 files changed, 909 insertions(+), 72 deletions(-) create mode 100644 api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/BUILD create mode 100644 api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/qatzstd.proto create mode 100644 bazel/foreign_cc/qatzstd.patch create mode 100644 contrib/qat/compression/qatzstd/compressor/source/BUILD create mode 100644 contrib/qat/compression/qatzstd/compressor/source/config.cc create mode 100644 contrib/qat/compression/qatzstd/compressor/source/config.h create mode 100644 contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc create mode 100644 contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h create mode 100644 contrib/qat/compression/qatzstd/compressor/test/BUILD create mode 100644 contrib/qat/compression/qatzstd/compressor/test/qatzstd_compressor_impl_test.cc create mode 100644 docs/root/configuration/other_features/_include/qatzstd.yaml create mode 100644 docs/root/configuration/other_features/qatzstd.rst create mode 100644 source/common/compression/zstd/common/BUILD rename source/{extensions => common}/compression/zstd/common/base.cc (86%) rename source/{extensions => common}/compression/zstd/common/base.h (92%) create mode 100644 source/common/compression/zstd/compressor/BUILD create mode 100644 source/common/compression/zstd/compressor/zstd_compressor_impl_base.cc create mode 100644 source/common/compression/zstd/compressor/zstd_compressor_impl_base.h diff --git a/api/BUILD b/api/BUILD index e0efeebce4f7..829715418693 100644 --- a/api/BUILD +++ b/api/BUILD @@ -73,6 +73,7 @@ proto_library( visibility = ["//visibility:public"], deps = [ "//contrib/envoy/extensions/compression/qatzip/compressor/v3alpha:pkg", + "//contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/checksum/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/dynamo/v3:pkg", "//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg", diff --git a/api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/BUILD b/api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/BUILD new file mode 100644 index 000000000000..29ebf0741406 --- /dev/null +++ b/api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/BUILD @@ -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_xds//udpa/annotations:pkg"], +) diff --git a/api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/qatzstd.proto b/api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/qatzstd.proto new file mode 100644 index 000000000000..182722d01e4f --- /dev/null +++ b/api/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/qatzstd.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package envoy.extensions.compression.qatzstd.compressor.v3alpha; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.compression.qatzstd.compressor.v3alpha"; +option java_outer_classname = "QatzstdProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Qatzstd Compressor] +// Qatzstd :ref:`configuration overview `. +// [#extension: envoy.compression.qatzstd.compressor] + +// [#next-free-field: 8] +message Qatzstd { + // Reference to http://facebook.github.io/zstd/zstd_manual.html + enum Strategy { + DEFAULT = 0; + FAST = 1; + DFAST = 2; + GREEDY = 3; + LAZY = 4; + LAZY2 = 5; + BTLAZY2 = 6; + BTOPT = 7; + BTULTRA = 8; + BTULTRA2 = 9; + } + + // Set compression parameters according to pre-defined compression level table. + // Note that exact compression parameters are dynamically determined, + // depending on both compression level and source content size (when known). + // Value 0 means default, and default level is 3. + // + // Setting a level does not automatically set all other compression parameters + // to default. Setting this will however eventually dynamically impact the compression + // parameters which have not been manually set. The manually set + // ones will 'stick'. + google.protobuf.UInt32Value compression_level = 1 [(validate.rules).uint32 = {lte: 22 gte: 1}]; + + // A 32-bits checksum of content is written at end of frame. If not set, defaults to false. + bool enable_checksum = 2; + + // The higher the value of selected strategy, the more complex it is, + // resulting in stronger and slower compression. + // + // Special: value 0 means "use default strategy". + Strategy strategy = 3 [(validate.rules).enum = {defined_only: true}]; + + // Value for compressor's next output buffer. If not set, defaults to 4096. + google.protobuf.UInt32Value chunk_size = 5 [(validate.rules).uint32 = {lte: 65536 gte: 4096}]; + + // Enable QAT to accelerate Zstd compression or not. If not set, defaults to false. + // + // This is useful in the case that users want to enable QAT for a period of time and disable QAT for another period of time, + // they don't have to change the config too much or prepare for another config that has software zstd compressor and just changing the value of this filed. + bool enable_qat_zstd = 6; + + // Fallback to software for Qatzstd when input size is less than this value. + // Valid only ``enable_qat_zstd`` is ``true``. 0 means no fallback at all. If not set, defaults to 4000. + google.protobuf.UInt32Value qat_zstd_fallback_threshold = 7 + [(validate.rules).uint32 = {lte: 65536 gte: 0}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index eb4569353709..b74e3df2a867 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -10,6 +10,7 @@ proto_library( visibility = ["//visibility:public"], deps = [ "//contrib/envoy/extensions/compression/qatzip/compressor/v3alpha:pkg", + "//contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha:pkg", "//contrib/envoy/extensions/config/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/checksum/v3alpha:pkg", "//contrib/envoy/extensions/filters/http/dynamo/v3:pkg", diff --git a/bazel/foreign_cc/qatzstd.patch b/bazel/foreign_cc/qatzstd.patch new file mode 100644 index 000000000000..84bd231db5e9 --- /dev/null +++ b/bazel/foreign_cc/qatzstd.patch @@ -0,0 +1,13 @@ +diff --git a/src/Makefile b/src/Makefile +index 1abf10d..c5fa3a6 100644 +--- a/src/Makefile ++++ b/src/Makefile +@@ -54,7 +54,7 @@ ifneq ($(ICP_ROOT), ) + endif + else + QATFLAGS = -DINTREE +- LDFLAGS = -lqat ++ LDFLAGS += -lqat + ifneq ($(ENABLE_USDM_DRV), 0) + QATFLAGS += -DENABLE_USDM_DRV + LDFLAGS += -lusdm diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index bec5940badf3..d231f276c7ab 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -313,6 +313,7 @@ def envoy_dependencies(skip_targets = []): _com_github_intel_ipp_crypto_crypto_mb_fips() _com_github_intel_qatlib() _com_github_intel_qatzip() + _com_github_qat_zstd() _com_github_lz4_lz4() _com_github_jbeder_yaml_cpp() _com_github_libevent_libevent() @@ -585,6 +586,14 @@ def _com_github_intel_qatzip(): build_file_content = BUILD_ALL_CONTENT, ) +def _com_github_qat_zstd(): + external_http_archive( + name = "com_github_qat_zstd", + build_file_content = BUILD_ALL_CONTENT, + patch_args = ["-p1"], + patches = ["@envoy//bazel/foreign_cc:qatzstd.patch"], + ) + def _com_github_lz4_lz4(): external_http_archive( name = "com_github_lz4_lz4", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index b81d31b33a9d..6cddbf98ad60 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -446,7 +446,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/intel/qatlib/archive/refs/tags/{version}.tar.gz"], use_category = ["dataplane_ext"], release_date = "2024-02-20", - extensions = ["envoy.tls.key_providers.qat", "envoy.compression.qatzip.compressor"], + extensions = ["envoy.tls.key_providers.qat", "envoy.compression.qatzip.compressor", "envoy.compression.qatzstd.compressor"], cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/intel/qatlib/blob/{version}/LICENSE", @@ -466,6 +466,21 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "BSD-3-Clause", license_url = "https://github.com/intel/QATzip/blob/v{version}/LICENSE", ), + com_github_qat_zstd = dict( + project_name = "QAT-ZSTD-Plugin", + project_desc = "Intel® QuickAssist Technology ZSTD Plugin (QAT ZSTD Plugin)", + project_url = "https://github.com/intel/QAT-ZSTD-Plugin/", + version = "0.1.0", + sha256 = "74c5bfbb3b0c6f1334e128ee0b43958d1d34751a4762e54e8f970c443e445f33", + strip_prefix = "QAT-ZSTD-Plugin-{version}", + urls = ["https://github.com/intel/QAT-ZSTD-Plugin/archive/refs/tags/v{version}.tar.gz"], + use_category = ["dataplane_ext"], + extensions = [ + "envoy.compression.qatzstd.compressor", + ], + release_date = "2023-09-08", + cpe = "N/A", + ), com_github_luajit_luajit = dict( project_name = "LuaJIT", project_desc = "Just-In-Time compiler for Lua", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4bb71b22796c..092048320fae 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -246,6 +246,9 @@ new_features: change: | added support for :ref:`%UPSTREAM_CONNECTION_ID% ` for the upstream connection identifier. +- area: compression + change: | + Added Qatzstd :ref:`compressor `. - area: aws_lambda change: | Added :ref:`host_rewrite ` config to be used diff --git a/contrib/all_contrib_extensions.bzl b/contrib/all_contrib_extensions.bzl index 6b7994a2623b..7caf371f6032 100644 --- a/contrib/all_contrib_extensions.bzl +++ b/contrib/all_contrib_extensions.bzl @@ -18,6 +18,7 @@ ARM64_SKIP_CONTRIB_TARGETS = [ "envoy.tls.key_providers.qat", "envoy.network.connection_balance.dlb", "envoy.compression.qatzip.compressor", + "envoy.compression.qatzstd.compressor", ] PPC_SKIP_CONTRIB_TARGETS = [ "envoy.tls.key_providers.cryptomb", @@ -26,6 +27,7 @@ PPC_SKIP_CONTRIB_TARGETS = [ "envoy.network.connection_balance.dlb", "envoy.regex_engines.hyperscan", "envoy.compression.qatzip.compressor", + "envoy.compression.qatzstd.compressor", ] FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS = [ diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index b8e7811a168c..49b1f7afde79 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -5,6 +5,7 @@ CONTRIB_EXTENSIONS = { # "envoy.compression.qatzip.compressor": "//contrib/qat/compression/qatzip/compressor/source:config", + "envoy.compression.qatzstd.compressor": "//contrib/qat/compression/qatzstd/compressor/source:config", # # HTTP filters diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index 8168b063da58..d8aee9d710fb 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -18,6 +18,11 @@ envoy.compression.qatzip.compressor: - envoy.compression.compressor security_posture: robust_to_untrusted_downstream_and_upstream status: alpha +envoy.compression.qatzstd.compressor: + categories: + - envoy.compression.compressor + security_posture: robust_to_untrusted_downstream_and_upstream + status: alpha envoy.filters.http.squash: categories: - envoy.filters.http diff --git a/contrib/qat/BUILD b/contrib/qat/BUILD index d435976e4953..08ddf04136c3 100644 --- a/contrib/qat/BUILD +++ b/contrib/qat/BUILD @@ -20,12 +20,16 @@ configure_make( configure_options = [ "--disable-fast-crc-in-assembler", "--disable-systemd", - "--disable-shared", "--with-pic", + "--enable-shared", "--enable-static", "--enable-samples=no", ], lib_source = "@com_github_intel_qatlib//:all", + out_shared_libs = [ + "libqat.so", + "libusdm.so", + ], out_static_libs = [ "libqat.a", "libusdm.a", diff --git a/contrib/qat/compression/qatzstd/compressor/source/BUILD b/contrib/qat/compression/qatzstd/compressor/source/BUILD new file mode 100644 index 000000000000..960fa2b9b55e --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/source/BUILD @@ -0,0 +1,69 @@ +load("@rules_foreign_cc//foreign_cc:defs.bzl", "make") +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_cc_library", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +make( + name = "qat-zstd", + build_data = ["@com_github_qat_zstd//:all"], + env = select({ + "//bazel:clang_build": { + "CFLAGS": "-Wno-error=unused-parameter -Wno-error=unused-command-line-argument -I$$EXT_BUILD_DEPS/qatlib/include -I$$EXT_BUILD_DEPS/zstd/include", + }, + "//conditions:default": { + "CFLAGS": "-I$$EXT_BUILD_DEPS/qatlib/include -I$$EXT_BUILD_DEPS/zstd/include", + }, + }), + includes = [], + lib_source = "@com_github_qat_zstd//:all", + out_static_libs = ["libqatseqprod.a"], + tags = ["skip_on_windows"], + target_compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + targets = [ + "ENABLE_USDM_DRV=1", + "install", + ], + deps = [ + "//contrib/qat:qatlib", + "//external:zstd", + ], +) + +envoy_cc_library( + name = "compressor_lib", + srcs = ["qatzstd_compressor_impl.cc"], + hdrs = ["qatzstd_compressor_impl.h"], + deps = [ + ":qat-zstd", + "//envoy/compression/compressor:compressor_interface", + "//envoy/server:factory_context_interface", + "//source/common/buffer:buffer_lib", + "//source/common/compression/zstd/common:zstd_base_lib", + "//source/common/compression/zstd/compressor:compressor_base", + ], +) + +envoy_cc_contrib_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":compressor_lib", + ":qat-zstd", + "//envoy/event:dispatcher_interface", + "//envoy/thread_local:thread_local_interface", + "//source/common/http:headers_lib", + "//source/extensions/compression/common/compressor:compressor_factory_base_lib", + "@envoy_api//contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha:pkg_cc_proto", + ], +) diff --git a/contrib/qat/compression/qatzstd/compressor/source/config.cc b/contrib/qat/compression/qatzstd/compressor/source/config.cc new file mode 100644 index 000000000000..67a24d530d1b --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/source/config.cc @@ -0,0 +1,81 @@ +#include "contrib/qat/compression/qatzstd/compressor/source/config.h" + +namespace Envoy { +namespace Extensions { +namespace Compression { +namespace Qatzstd { +namespace Compressor { + +QatzstdCompressorFactory::QatzstdCompressorFactory( + const envoy::extensions::compression::qatzstd::compressor::v3alpha::Qatzstd& qatzstd, + ThreadLocal::SlotAllocator& tls) + : compression_level_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(qatzstd, compression_level, ZSTD_CLEVEL_DEFAULT)), + enable_checksum_(qatzstd.enable_checksum()), strategy_(qatzstd.strategy()), + chunk_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(qatzstd, chunk_size, ZSTD_CStreamOutSize())), + enable_qat_zstd_(qatzstd.enable_qat_zstd()), + qat_zstd_fallback_threshold_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + qatzstd, qat_zstd_fallback_threshold, DefaultQatZstdFallbackThreshold)), + tls_slot_(nullptr) { + if (enable_qat_zstd_) { + tls_slot_ = ThreadLocal::TypedSlot::makeUnique(tls); + tls_slot_->set([](Event::Dispatcher&) { return std::make_shared(); }); + } +} + +QatzstdCompressorFactory::QatzstdThreadLocal::QatzstdThreadLocal() + : initialized_(false), sequenceProducerState_(nullptr) {} + +QatzstdCompressorFactory::QatzstdThreadLocal::~QatzstdThreadLocal() { + if (initialized_) { + /* Free sequence producer state */ + QZSTD_freeSeqProdState(sequenceProducerState_); + /* Stop QAT device, please call this function when + you won't use QAT anymore or before the process exits */ + QZSTD_stopQatDevice(); + } +} + +void* QatzstdCompressorFactory::QatzstdThreadLocal::GetQATSession() { + // The session must be initialized only once in every worker thread. + if (!initialized_) { + + int status = QZSTD_startQatDevice(); + // RELEASE_ASSERT(status == QZSTD_OK, "failed to initialize hardware"); + if (status != QZSTD_OK) { + ENVOY_LOG(warn, "Failed to initialize qat hardware"); + } else { + ENVOY_LOG(debug, "Initialize qat hardware successful"); + } + sequenceProducerState_ = QZSTD_createSeqProdState(); + initialized_ = true; + } + + return sequenceProducerState_; +} + +Envoy::Compression::Compressor::CompressorPtr QatzstdCompressorFactory::createCompressor() { + return std::make_unique( + compression_level_, enable_checksum_, strategy_, chunk_size_, enable_qat_zstd_, + qat_zstd_fallback_threshold_, enable_qat_zstd_ ? tls_slot_->get()->GetQATSession() : nullptr); +} + +Envoy::Compression::Compressor::CompressorFactoryPtr +QatzstdCompressorLibraryFactory::createCompressorFactoryFromProtoTyped( + const envoy::extensions::compression::qatzstd::compressor::v3alpha::Qatzstd& proto_config, + Server::Configuration::FactoryContext& context) { + return std::make_unique(proto_config, + context.serverFactoryContext().threadLocal()); +} + +/** + * Static registration for the zstd compressor library. @see NamedCompressorLibraryConfigFactory. + */ +REGISTER_FACTORY(QatzstdCompressorLibraryFactory, + Envoy::Compression::Compressor::NamedCompressorLibraryConfigFactory); + +} // namespace Compressor +} // namespace Qatzstd +} // namespace Compression +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/qat/compression/qatzstd/compressor/source/config.h b/contrib/qat/compression/qatzstd/compressor/source/config.h new file mode 100644 index 000000000000..8e78cd8123be --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/source/config.h @@ -0,0 +1,83 @@ +#pragma once + +#include "envoy/compression/compressor/factory.h" +#include "envoy/event/dispatcher.h" +#include "envoy/thread_local/thread_local.h" + +#include "source/common/common/logger.h" +#include "source/common/http/headers.h" +#include "source/extensions/compression/common/compressor/factory_base.h" + +#include "contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/qatzstd.pb.h" +#include "contrib/envoy/extensions/compression/qatzstd/compressor/v3alpha/qatzstd.pb.validate.h" +#include "contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h" +#include "qatseqprod.h" + +namespace Envoy { +namespace Extensions { +namespace Compression { +namespace Qatzstd { +namespace Compressor { + +// Default threshold for qat_zstd fallback to software. +const uint32_t DefaultQatZstdFallbackThreshold = 4000; + +namespace { + +const std::string& qatzstdStatsPrefix() { CONSTRUCT_ON_FIRST_USE(std::string, "qatzstd."); } +const std::string& qatzstdExtensionName() { + CONSTRUCT_ON_FIRST_USE(std::string, "envoy.compression.qatzstd.compressor"); +} + +} // namespace + +class QatzstdCompressorFactory : public Envoy::Compression::Compressor::CompressorFactory { +public: + QatzstdCompressorFactory( + const envoy::extensions::compression::qatzstd::compressor::v3alpha::Qatzstd& qatzstd, + ThreadLocal::SlotAllocator& tls); + + // Envoy::Compression::Compressor::CompressorFactory + Envoy::Compression::Compressor::CompressorPtr createCompressor() override; + const std::string& statsPrefix() const override { return qatzstdStatsPrefix(); } + const std::string& contentEncoding() const override { + return Http::CustomHeaders::get().ContentEncodingValues.Zstd; + } + +private: + struct QatzstdThreadLocal : public ThreadLocal::ThreadLocalObject, + public Logger::Loggable { + QatzstdThreadLocal(); + ~QatzstdThreadLocal() override; + void* GetQATSession(); + bool initialized_; + void* sequenceProducerState_; + }; + const uint32_t compression_level_; + const bool enable_checksum_; + const uint32_t strategy_; + const uint32_t chunk_size_; + const bool enable_qat_zstd_; + const uint32_t qat_zstd_fallback_threshold_; + ThreadLocal::TypedSlotPtr tls_slot_; +}; + +class QatzstdCompressorLibraryFactory + : public Compression::Common::Compressor::CompressorLibraryFactoryBase< + envoy::extensions::compression::qatzstd::compressor::v3alpha::Qatzstd> { +public: + QatzstdCompressorLibraryFactory() : CompressorLibraryFactoryBase(qatzstdExtensionName()) {} + +private: + Envoy::Compression::Compressor::CompressorFactoryPtr createCompressorFactoryFromProtoTyped( + const envoy::extensions::compression::qatzstd::compressor::v3alpha::Qatzstd& config, + Server::Configuration::FactoryContext& context) override; +}; + +DECLARE_FACTORY(QatzstdCompressorLibraryFactory); + +} // namespace Compressor +} // namespace Qatzstd +} // namespace Compression +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc new file mode 100644 index 000000000000..7b1cb85a8aae --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.cc @@ -0,0 +1,85 @@ +#include "contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h" + +namespace Envoy { +namespace Extensions { +namespace Compression { +namespace Qatzstd { +namespace Compressor { + +QatzstdCompressorImpl::QatzstdCompressorImpl(uint32_t compression_level, bool enable_checksum, + uint32_t strategy, uint32_t chunk_size, + bool enable_qat_zstd, + uint32_t qat_zstd_fallback_threshold, + void* sequenceProducerState) + : ZstdCompressorImplBase(compression_level, enable_checksum, strategy, chunk_size), + enable_qat_zstd_(enable_qat_zstd), qat_zstd_fallback_threshold_(qat_zstd_fallback_threshold), + sequenceProducerState_(sequenceProducerState), input_ptr_{std::make_unique( + chunk_size)}, + input_len_(0), chunk_size_(chunk_size) { + size_t result; + result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_compressionLevel, compression_level_); + RELEASE_ASSERT(!ZSTD_isError(result), ""); + + ENVOY_LOG(debug, + "zstd new ZstdCompressorImpl, compression_level: {}, strategy: {}, chunk_size: " + "{}, enable_qat_zstd: {}, qat_zstd_fallback_threshold: {}", + compression_level, strategy, chunk_size, enable_qat_zstd, qat_zstd_fallback_threshold); + if (enable_qat_zstd_) { + /* register qatSequenceProducer */ + ZSTD_registerSequenceProducer(cctx_.get(), sequenceProducerState_, qatSequenceProducer); + result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_enableSeqProducerFallback, 1); + RELEASE_ASSERT(!ZSTD_isError(result), ""); + } +} + +void QatzstdCompressorImpl::compressPreprocess(Buffer::Instance& buffer, + Envoy::Compression::Compressor::State state) { + ENVOY_LOG(debug, "zstd compress input size {}", buffer.length()); + if (enable_qat_zstd_ && state == Envoy::Compression::Compressor::State::Flush) { + // Fall back to software if input size less than threshold to achieve better performance. + if (buffer.length() < qat_zstd_fallback_threshold_) { + ENVOY_LOG(debug, "zstd compress fall back to software"); + ZSTD_registerSequenceProducer(cctx_.get(), nullptr, nullptr); + } + } +} + +void QatzstdCompressorImpl::setInput(const uint8_t* input, size_t size) { + input_.src = input; + input_.pos = 0; + input_.size = size; + input_len_ = 0; +} + +void QatzstdCompressorImpl::compressProcess(const Buffer::Instance& buffer, + const Buffer::RawSlice& slice, + Buffer::Instance& accumulation_buffer) { + if (slice.len_ == buffer.length() || slice.len_ > chunk_size_) { + if (input_len_ > 0) { + setInput(input_ptr_.get(), input_len_); + process(accumulation_buffer, ZSTD_e_continue); + } + setInput(static_cast(slice.mem_), slice.len_); + process(accumulation_buffer, ZSTD_e_continue); + } else { + if (input_len_ + slice.len_ > chunk_size_) { + setInput(input_ptr_.get(), input_len_); + process(accumulation_buffer, ZSTD_e_continue); + } + memcpy(input_ptr_.get() + input_len_, slice.mem_, slice.len_); // NOLINT(safe-memcpy) + input_len_ += slice.len_; + } +} + +void QatzstdCompressorImpl::compressPostprocess(Buffer::Instance& accumulation_buffer) { + if (input_len_ > 0) { + setInput(input_ptr_.get(), input_len_); + process(accumulation_buffer, ZSTD_e_continue); + } +} + +} // namespace Compressor +} // namespace Qatzstd +} // namespace Compression +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h new file mode 100644 index 000000000000..1960b13b7671 --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/source/qatzstd_compressor_impl.h @@ -0,0 +1,51 @@ +#pragma once + +#include "envoy/compression/compressor/compressor.h" +#include "envoy/server/factory_context.h" + +#include "source/common/common/logger.h" +#include "source/common/compression/zstd/common/base.h" +#include "source/common/compression/zstd/compressor/zstd_compressor_impl_base.h" + +#include "qatseqprod.h" + +namespace Envoy { +namespace Extensions { +namespace Compression { +namespace Qatzstd { +namespace Compressor { + +/** + * Implementation of compressor's interface. + */ +class QatzstdCompressorImpl : public Envoy::Compression::Zstd::Compressor::ZstdCompressorImplBase, + public Logger::Loggable { +public: + QatzstdCompressorImpl(uint32_t compression_level, bool enable_checksum, uint32_t strategy, + uint32_t chunk_size, bool enable_qat_zstd, + uint32_t qat_zstd_fallback_threshold, void* sequenceProducerState); + +private: + void compressPreprocess(Buffer::Instance& buffer, + Envoy::Compression::Compressor::State state) override; + + void compressProcess(const Buffer::Instance& buffer, const Buffer::RawSlice& slice, + Buffer::Instance& accumulation_buffer) override; + + void compressPostprocess(Buffer::Instance& accumulation_buffer) override; + + void setInput(const uint8_t* input, size_t size); + + bool enable_qat_zstd_; + const uint32_t qat_zstd_fallback_threshold_; + void* sequenceProducerState_; + std::unique_ptr input_ptr_; + uint64_t input_len_; + uint64_t chunk_size_; +}; + +} // namespace Compressor +} // namespace Qatzstd +} // namespace Compression +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/qat/compression/qatzstd/compressor/test/BUILD b/contrib/qat/compression/qatzstd/compressor/test/BUILD new file mode 100644 index 000000000000..85297caa141a --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/test/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "compressor_test", + srcs = ["qatzstd_compressor_impl_test.cc"], + deps = [ + "//contrib/qat/compression/qatzstd/compressor/source:config", + "//source/extensions/compression/zstd/decompressor:decompressor_lib", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:utility_lib", + ], +) diff --git a/contrib/qat/compression/qatzstd/compressor/test/qatzstd_compressor_impl_test.cc b/contrib/qat/compression/qatzstd/compressor/test/qatzstd_compressor_impl_test.cc new file mode 100644 index 000000000000..257a23061ed2 --- /dev/null +++ b/contrib/qat/compression/qatzstd/compressor/test/qatzstd_compressor_impl_test.cc @@ -0,0 +1,110 @@ +#include "source/common/buffer/buffer_impl.h" +#include "source/common/stats/isolated_store_impl.h" +#include "source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.h" + +#include "test/mocks/server/factory_context.h" +#include "test/test_common/utility.h" + +#include "contrib/qat/compression/qatzstd/compressor/source/config.h" +#include "gtest/gtest.h" +#include "qatseqprod.h" + +namespace Envoy { +namespace Extensions { +namespace Compression { +namespace Qatzstd { +namespace Compressor { +namespace { + +class QatzstdCompressorImplTest : public testing::Test { +protected: + void drainBuffer(Buffer::OwnedImpl& buffer) { + buffer.drain(buffer.length()); + ASSERT_EQ(0, buffer.length()); + } + + void verifyWithDecompressor(Envoy::Compression::Compressor::CompressorPtr compressor) { + Buffer::OwnedImpl buffer; + Buffer::OwnedImpl accumulation_buffer; + std::string original_text{}; + for (uint64_t i = 0; i < 10; i++) { + TestUtility::feedBufferWithRandomCharacters(buffer, default_input_size_ * i, i, i); + original_text.append(buffer.toString()); + ASSERT_EQ(default_input_size_ * i * i, buffer.length()); + compressor->compress(buffer, Envoy::Compression::Compressor::State::Flush); + accumulation_buffer.add(buffer); + drainBuffer(buffer); + } + + compressor->compress(buffer, Envoy::Compression::Compressor::State::Finish); + accumulation_buffer.add(buffer); + drainBuffer(buffer); + + Stats::IsolatedStoreImpl stats_store{}; + Zstd::Decompressor::ZstdDecompressorImpl decompressor{*stats_store.rootScope(), "test.", + default_ddict_manager_, 4096}; + + decompressor.decompress(accumulation_buffer, buffer); + std::string decompressed_text{buffer.toString()}; + + ASSERT_EQ(original_text.length(), decompressed_text.length()); + EXPECT_EQ(original_text, decompressed_text); + } + + Envoy::Compression::Compressor::CompressorFactoryPtr + createQatzstdCompressorFactoryFromConfig(const std::string& json) { + envoy::extensions::compression::qatzstd::compressor::v3alpha::Qatzstd qatzstd_config; + TestUtility::loadFromJson(json, qatzstd_config); + + return qatzstd_compressor_library_factory_.createCompressorFactoryFromProto(qatzstd_config, + context_); + } + + uint32_t default_input_size_{796}; + Zstd::Decompressor::ZstdDDictManagerPtr default_ddict_manager_{nullptr}; + QatzstdCompressorLibraryFactory qatzstd_compressor_library_factory_; + NiceMock context_; +}; + +class QatzstdConfigTest : public QatzstdCompressorImplTest, + public ::testing::WithParamInterface> {}; + +// These tests should pass even if required hardware or setup steps required for qatzstd are +// missing. Qatzstd uses a sofware fallback in this case. +INSTANTIATE_TEST_SUITE_P( + QatzstdConfigTestInstantiation, QatzstdConfigTest, + // First tuple has all default values. + ::testing::Values(std::make_tuple(1, 4096, true, 4096), std::make_tuple(2, 4096, true, 4096), + std::make_tuple(3, 65536, true, 4096), std::make_tuple(4, 4096, true, 4096), + std::make_tuple(5, 8192, true, 1024), std::make_tuple(6, 4096, false, 1024), + std::make_tuple(7, 4096, true, 1024), std::make_tuple(8, 8192, true, 4096), + std::make_tuple(9, 8192, true, 1024), std::make_tuple(10, 16384, true, 1024), + std::make_tuple(11, 8192, true, 8192), + std::make_tuple(12, 4096, true, 1024))); + +TEST_P(QatzstdConfigTest, LoadConfigAndVerifyWithDecompressor) { + std::tuple config_value_tuple = GetParam(); + std::string json{fmt::format(R"EOF({{ + "compression_level": {}, + "chunk_size": {}, + "enable_qat_zstd": {}, + "qat_zstd_fallback_threshold": {}, +}})EOF", + std::get<0>(config_value_tuple), std::get<1>(config_value_tuple), + std::get<2>(config_value_tuple), std::get<3>(config_value_tuple))}; + + Envoy::Compression::Compressor::CompressorFactoryPtr qatzstd_compressor_factory = + createQatzstdCompressorFactoryFromConfig(json); + + EXPECT_EQ("zstd", qatzstd_compressor_factory->contentEncoding()); + EXPECT_EQ("qatzstd.", qatzstd_compressor_factory->statsPrefix()); + + verifyWithDecompressor(qatzstd_compressor_factory->createCompressor()); +} + +} // namespace +} // namespace Compressor +} // namespace Qatzstd +} // namespace Compression +} // namespace Extensions +} // namespace Envoy diff --git a/docs/BUILD b/docs/BUILD index 16df02c3e5a6..3497d8d9f186 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -35,6 +35,7 @@ filegroup( "root/configuration/other_features/_include/hyperscan_matcher_multiple.yaml", "root/configuration/other_features/_include/hyperscan_regex_engine.yaml", "root/configuration/other_features/_include/qatzip.yaml", + "root/configuration/other_features/_include/qatzstd.yaml", "root/intro/arch_overview/security/_include/ssl.yaml", "root/configuration/listeners/network_filters/_include/generic_proxy_filter.yaml", "root/configuration/overview/_include/xds_api/oauth-sds-example.yaml", diff --git a/docs/root/api-v3/config/contrib/qat/qat.rst b/docs/root/api-v3/config/contrib/qat/qat.rst index 3e524d700b13..c03e01dd4397 100644 --- a/docs/root/api-v3/config/contrib/qat/qat.rst +++ b/docs/root/api-v3/config/contrib/qat/qat.rst @@ -4,3 +4,4 @@ ../../../extensions/private_key_providers/qat/v3alpha/* ../../../extensions/compression/qatzip/compressor/v3alpha/* + ../../../extensions/compression/qatzstd/compressor/v3alpha/* diff --git a/docs/root/configuration/other_features/_include/qatzstd.yaml b/docs/root/configuration/other_features/_include/qatzstd.yaml new file mode 100644 index 000000000000..4a33cfe65094 --- /dev/null +++ b/docs/root/configuration/other_features/_include/qatzstd.yaml @@ -0,0 +1,63 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.filters.http.compressor + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.compressor.v3.Compressor + response_direction_config: + common_config: + min_content_length: 100 + content_type: + - application/octet-stream + disable_on_etag_header: false + compressor_library: + name: text_optimized + typed_config: + "@type": type.googleapis.com/envoy.extensions.compression.qatzstd.compressor.v3alpha.Qatzstd + compression_level: 9 + enable_qat_zstd: true + chunk_size: 65536 + qat_zstd_fallback_threshold: 0 + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 1234 +admin: + address: + socket_address: + address: 0.0.0.0 + port_value: 9901 diff --git a/docs/root/configuration/other_features/other_features.rst b/docs/root/configuration/other_features/other_features.rst index bd54f4e7a129..81c5ca3046cc 100644 --- a/docs/root/configuration/other_features/other_features.rst +++ b/docs/root/configuration/other_features/other_features.rst @@ -12,4 +12,5 @@ Other features wasm wasm_service qatzip + qatzstd string_matcher diff --git a/docs/root/configuration/other_features/qatzstd.rst b/docs/root/configuration/other_features/qatzstd.rst new file mode 100644 index 000000000000..e963c862e6d7 --- /dev/null +++ b/docs/root/configuration/other_features/qatzstd.rst @@ -0,0 +1,34 @@ +.. _config_qatzstd: + +Qatzstd Compressor +======================= + +* :ref:`v3 API reference ` + + +Qatzstd compressor provides Envoy with faster hardware-accelerated zstd compression by integrating with `Intel® QuickAssist Technology (Intel® QAT) `_ through the qatlib and QAT-ZSTD-Plugin libraries. + +Example configuration +--------------------- + +An example for Qatzstd compressor configuration is: + +.. literalinclude:: _include/qatzstd.yaml + :language: yaml + + +How it works +------------ + +If enabled, the Qatzstd compressor will: + +- attach Qat hardware +- create Threadlocal Qatzstd context for each worker thread + +When new http package come, one worker thread will process it using its Qatzstd context and send the data needed to be compressed to +Qat hardware using standard zstd api. + +Installing and using QAT-ZSTD-Plugin +------------------------------------ + +For information on how to build/install and use QAT-ZSTD-Plugin see `introduction `_. diff --git a/source/common/common/logger.h b/source/common/common/logger.h index a90e43628e1e..4f0e7375f459 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -45,6 +45,7 @@ const static bool should_log = true; FUNCTION(config) \ FUNCTION(connection) \ FUNCTION(conn_handler) \ + FUNCTION(compression) \ FUNCTION(decompression) \ FUNCTION(dns) \ FUNCTION(dubbo) \ diff --git a/source/common/compression/zstd/common/BUILD b/source/common/compression/zstd/common/BUILD new file mode 100644 index 000000000000..e9ecaa5338b2 --- /dev/null +++ b/source/common/compression/zstd/common/BUILD @@ -0,0 +1,19 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "zstd_base_lib", + srcs = ["base.cc"], + hdrs = ["base.h"], + external_deps = ["zstd"], + deps = [ + "//source/common/buffer:buffer_lib", + ], +) diff --git a/source/extensions/compression/zstd/common/base.cc b/source/common/compression/zstd/common/base.cc similarity index 86% rename from source/extensions/compression/zstd/common/base.cc rename to source/common/compression/zstd/common/base.cc index 3682f3626934..89dc2941cb2d 100644 --- a/source/extensions/compression/zstd/common/base.cc +++ b/source/common/compression/zstd/common/base.cc @@ -1,7 +1,6 @@ -#include "source/extensions/compression/zstd/common/base.h" +#include "source/common/compression/zstd/common/base.h" namespace Envoy { -namespace Extensions { namespace Compression { namespace Zstd { namespace Common { @@ -27,5 +26,4 @@ void Base::getOutput(Buffer::Instance& output_buffer) { } // namespace Common } // namespace Zstd } // namespace Compression -} // namespace Extensions } // namespace Envoy diff --git a/source/extensions/compression/zstd/common/base.h b/source/common/compression/zstd/common/base.h similarity index 92% rename from source/extensions/compression/zstd/common/base.h rename to source/common/compression/zstd/common/base.h index 6a3c626a3701..8582fe3f86f3 100644 --- a/source/extensions/compression/zstd/common/base.h +++ b/source/common/compression/zstd/common/base.h @@ -7,7 +7,6 @@ #include "zstd.h" namespace Envoy { -namespace Extensions { namespace Compression { namespace Zstd { namespace Common { @@ -29,5 +28,4 @@ struct Base { } // namespace Common } // namespace Zstd } // namespace Compression -} // namespace Extensions } // namespace Envoy diff --git a/source/common/compression/zstd/compressor/BUILD b/source/common/compression/zstd/compressor/BUILD new file mode 100644 index 000000000000..16a3fbe97f1e --- /dev/null +++ b/source/common/compression/zstd/compressor/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "compressor_base", + srcs = ["zstd_compressor_impl_base.cc"], + hdrs = ["zstd_compressor_impl_base.h"], + deps = [ + "//envoy/compression/compressor:compressor_interface", + "//source/common/buffer:buffer_lib", + "//source/common/compression/zstd/common:zstd_base_lib", + ], +) diff --git a/source/common/compression/zstd/compressor/zstd_compressor_impl_base.cc b/source/common/compression/zstd/compressor/zstd_compressor_impl_base.cc new file mode 100644 index 000000000000..2a04f72b83c0 --- /dev/null +++ b/source/common/compression/zstd/compressor/zstd_compressor_impl_base.cc @@ -0,0 +1,59 @@ +#include "source/common/compression/zstd/compressor/zstd_compressor_impl_base.h" + +#include "source/common/buffer/buffer_impl.h" + +namespace Envoy { +namespace Compression { +namespace Zstd { +namespace Compressor { + +ZstdCompressorImplBase::ZstdCompressorImplBase(uint32_t compression_level, bool enable_checksum, + uint32_t strategy, uint32_t chunk_size) + : Common::Base(chunk_size), cctx_(ZSTD_createCCtx(), &ZSTD_freeCCtx), + compression_level_(compression_level) { + size_t result; + result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_checksumFlag, enable_checksum); + RELEASE_ASSERT(!ZSTD_isError(result), ""); + + result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_strategy, strategy); + RELEASE_ASSERT(!ZSTD_isError(result), ""); +} + +void ZstdCompressorImplBase::compress(Buffer::Instance& buffer, + Envoy::Compression::Compressor::State state) { + compressPreprocess(buffer, state); + + Buffer::OwnedImpl accumulation_buffer; + for (const Buffer::RawSlice& input_slice : buffer.getRawSlices()) { + if (input_slice.len_ > 0) { + compressProcess(buffer, input_slice, accumulation_buffer); + buffer.drain(input_slice.len_); + } + } + + compressPostprocess(accumulation_buffer); + + ASSERT(buffer.length() == 0); + buffer.move(accumulation_buffer); + + if (state == Envoy::Compression::Compressor::State::Finish) { + process(buffer, ZSTD_e_end); + } +} + +void ZstdCompressorImplBase::process(Buffer::Instance& output_buffer, ZSTD_EndDirective mode) { + bool finished; + do { + const size_t remaining = ZSTD_compressStream2(cctx_.get(), &output_, &input_, mode); + getOutput(output_buffer); + // If we're on the last chunk we're finished when zstd returns 0, + // which means its consumed all the input AND finished the frame. + // Otherwise, we're finished when we've consumed all the input. + finished = (ZSTD_e_end == mode) ? (remaining == 0) : (input_.pos == input_.size); + } while (!finished); +} + +} // namespace Compressor +} // namespace Zstd +} // namespace Compression +} // namespace Envoy diff --git a/source/common/compression/zstd/compressor/zstd_compressor_impl_base.h b/source/common/compression/zstd/compressor/zstd_compressor_impl_base.h new file mode 100644 index 000000000000..954b3aa17f9a --- /dev/null +++ b/source/common/compression/zstd/compressor/zstd_compressor_impl_base.h @@ -0,0 +1,43 @@ +#pragma once + +#include "envoy/compression/compressor/compressor.h" + +#include "source/common/compression/zstd/common/base.h" + +namespace Envoy { +namespace Compression { +namespace Zstd { +namespace Compressor { + +/** + * Implementation of compressor's interface. + */ +class ZstdCompressorImplBase : public Common::Base, + public Envoy::Compression::Compressor::Compressor, + NonCopyable { +public: + ZstdCompressorImplBase(uint32_t compression_level, bool enable_checksum, uint32_t strategy, + uint32_t chunk_size); + + // Compression::Compressor::Compressor + void compress(Buffer::Instance& buffer, Envoy::Compression::Compressor::State state) override; + + void process(Buffer::Instance& output_buffer, ZSTD_EndDirective mode); + +protected: + virtual void compressPreprocess(Buffer::Instance& buffer, + Envoy::Compression::Compressor::State state) PURE; + + virtual void compressProcess(const Buffer::Instance& buffer, const Buffer::RawSlice& input_slice, + Buffer::Instance& accumulation_buffer) PURE; + + virtual void compressPostprocess(Buffer::Instance& accumulation_buffer) PURE; + + std::unique_ptr cctx_; + const uint32_t compression_level_; +}; + +} // namespace Compressor +} // namespace Zstd +} // namespace Compression +} // namespace Envoy diff --git a/source/extensions/compression/zstd/common/BUILD b/source/extensions/compression/zstd/common/BUILD index 98b6a3abb62f..a0aa94556434 100644 --- a/source/extensions/compression/zstd/common/BUILD +++ b/source/extensions/compression/zstd/common/BUILD @@ -8,16 +8,6 @@ licenses(["notice"]) # Apache 2 envoy_extension_package() -envoy_cc_library( - name = "zstd_base_lib", - srcs = ["base.cc"], - hdrs = ["base.h"], - external_deps = ["zstd"], - deps = [ - "//source/common/buffer:buffer_lib", - ], -) - envoy_cc_library( name = "zstd_dictionary_manager_lib", hdrs = ["dictionary_manager.h"], diff --git a/source/extensions/compression/zstd/compressor/BUILD b/source/extensions/compression/zstd/compressor/BUILD index e6167ff1a1cb..76c3343a3202 100644 --- a/source/extensions/compression/zstd/compressor/BUILD +++ b/source/extensions/compression/zstd/compressor/BUILD @@ -16,7 +16,8 @@ envoy_cc_library( deps = [ "//envoy/compression/compressor:compressor_interface", "//source/common/buffer:buffer_lib", - "//source/extensions/compression/zstd/common:zstd_base_lib", + "//source/common/compression/zstd/common:zstd_base_lib", + "//source/common/compression/zstd/compressor:compressor_base", "//source/extensions/compression/zstd/common:zstd_dictionary_manager_lib", ], ) diff --git a/source/extensions/compression/zstd/compressor/zstd_compressor_impl.cc b/source/extensions/compression/zstd/compressor/zstd_compressor_impl.cc index 89d3ed754e65..55ece012d086 100644 --- a/source/extensions/compression/zstd/compressor/zstd_compressor_impl.cc +++ b/source/extensions/compression/zstd/compressor/zstd_compressor_impl.cc @@ -1,7 +1,5 @@ #include "source/extensions/compression/zstd/compressor/zstd_compressor_impl.h" -#include "source/common/buffer/buffer_impl.h" - namespace Envoy { namespace Extensions { namespace Compression { @@ -11,15 +9,9 @@ namespace Compressor { ZstdCompressorImpl::ZstdCompressorImpl(uint32_t compression_level, bool enable_checksum, uint32_t strategy, const ZstdCDictManagerPtr& cdict_manager, uint32_t chunk_size) - : Common::Base(chunk_size), cctx_(ZSTD_createCCtx(), &ZSTD_freeCCtx), - cdict_manager_(cdict_manager), compression_level_(compression_level) { + : ZstdCompressorImplBase(compression_level, enable_checksum, strategy, chunk_size), + cdict_manager_(cdict_manager) { size_t result; - result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_checksumFlag, enable_checksum); - RELEASE_ASSERT(!ZSTD_isError(result), ""); - - result = ZSTD_CCtx_setParameter(cctx_.get(), ZSTD_c_strategy, strategy); - RELEASE_ASSERT(!ZSTD_isError(result), ""); - if (cdict_manager_) { ZSTD_CDict* cdict = cdict_manager_->getFirstDictionary(); result = ZSTD_CCtx_refCDict(cctx_.get(), cdict); @@ -29,36 +21,17 @@ ZstdCompressorImpl::ZstdCompressorImpl(uint32_t compression_level, bool enable_c RELEASE_ASSERT(!ZSTD_isError(result), ""); } -void ZstdCompressorImpl::compress(Buffer::Instance& buffer, - Envoy::Compression::Compressor::State state) { - Buffer::OwnedImpl accumulation_buffer; - for (const Buffer::RawSlice& input_slice : buffer.getRawSlices()) { - if (input_slice.len_ > 0) { - setInput(input_slice); - process(accumulation_buffer, ZSTD_e_continue); - buffer.drain(input_slice.len_); - } - } - - ASSERT(buffer.length() == 0); - buffer.move(accumulation_buffer); +void ZstdCompressorImpl::compressPreprocess(Buffer::Instance&, + Envoy::Compression::Compressor::State) {} - if (state == Envoy::Compression::Compressor::State::Finish) { - process(buffer, ZSTD_e_end); - } +void ZstdCompressorImpl::compressProcess(const Buffer::Instance&, + const Buffer::RawSlice& input_slice, + Buffer::Instance& accumulation_buffer) { + setInput(input_slice); + process(accumulation_buffer, ZSTD_e_continue); } -void ZstdCompressorImpl::process(Buffer::Instance& output_buffer, ZSTD_EndDirective mode) { - bool finished; - do { - const size_t remaining = ZSTD_compressStream2(cctx_.get(), &output_, &input_, mode); - getOutput(output_buffer); - // If we're on the last chunk we're finished when zstd returns 0, - // which means its consumed all the input AND finished the frame. - // Otherwise, we're finished when we've consumed all the input. - finished = (ZSTD_e_end == mode) ? (remaining == 0) : (input_.pos == input_.size); - } while (!finished); -} +void ZstdCompressorImpl::compressPostprocess(Buffer::Instance&) {} } // namespace Compressor } // namespace Zstd diff --git a/source/extensions/compression/zstd/compressor/zstd_compressor_impl.h b/source/extensions/compression/zstd/compressor/zstd_compressor_impl.h index e0adaec7caaf..95da3d1a8e12 100644 --- a/source/extensions/compression/zstd/compressor/zstd_compressor_impl.h +++ b/source/extensions/compression/zstd/compressor/zstd_compressor_impl.h @@ -2,7 +2,8 @@ #include "envoy/compression/compressor/compressor.h" -#include "source/extensions/compression/zstd/common/base.h" +#include "source/common/compression/zstd/common/base.h" +#include "source/common/compression/zstd/compressor/zstd_compressor_impl_base.h" #include "source/extensions/compression/zstd/common/dictionary_manager.h" namespace Envoy { @@ -18,22 +19,21 @@ using ZstdCDictManagerPtr = std::unique_ptr; /** * Implementation of compressor's interface. */ -class ZstdCompressorImpl : public Common::Base, - public Envoy::Compression::Compressor::Compressor, - NonCopyable { +class ZstdCompressorImpl : public Envoy::Compression::Zstd::Compressor::ZstdCompressorImplBase { public: ZstdCompressorImpl(uint32_t compression_level, bool enable_checksum, uint32_t strategy, const ZstdCDictManagerPtr& cdict_manager, uint32_t chunk_size); - // Compression::Compressor::Compressor - void compress(Buffer::Instance& buffer, Envoy::Compression::Compressor::State state) override; - private: - void process(Buffer::Instance& output_buffer, ZSTD_EndDirective mode); + void compressPreprocess(Buffer::Instance& buffer, + Envoy::Compression::Compressor::State state) override; + + void compressProcess(const Buffer::Instance& buffer, const Buffer::RawSlice& input_slice, + Buffer::Instance& accumulation_buffer) override; + + void compressPostprocess(Buffer::Instance& accumulation_buffer) override; - std::unique_ptr cctx_; const ZstdCDictManagerPtr& cdict_manager_; - const uint32_t compression_level_; }; } // namespace Compressor diff --git a/source/extensions/compression/zstd/decompressor/BUILD b/source/extensions/compression/zstd/decompressor/BUILD index 9fc4383b939a..361a3b866f23 100644 --- a/source/extensions/compression/zstd/decompressor/BUILD +++ b/source/extensions/compression/zstd/decompressor/BUILD @@ -18,7 +18,7 @@ envoy_cc_library( "//envoy/stats:stats_interface", "//envoy/stats:stats_macros", "//source/common/buffer:buffer_lib", - "//source/extensions/compression/zstd/common:zstd_base_lib", + "//source/common/compression/zstd/common:zstd_base_lib", "//source/extensions/compression/zstd/common:zstd_dictionary_manager_lib", ], ) diff --git a/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.cc b/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.cc index 7a165da1973c..e2a00232c120 100644 --- a/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.cc +++ b/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.cc @@ -21,7 +21,7 @@ constexpr uint64_t MaxInflateRatio = 100; ZstdDecompressorImpl::ZstdDecompressorImpl(Stats::Scope& scope, const std::string& stats_prefix, const ZstdDDictManagerPtr& ddict_manager, uint32_t chunk_size) - : Common::Base(chunk_size), dctx_(ZSTD_createDCtx(), &ZSTD_freeDCtx), + : Envoy::Compression::Zstd::Common::Base(chunk_size), dctx_(ZSTD_createDCtx(), &ZSTD_freeDCtx), ddict_manager_(ddict_manager), stats_(generateStats(stats_prefix, scope)) {} void ZstdDecompressorImpl::decompress(const Buffer::Instance& input_buffer, diff --git a/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.h b/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.h index 597a8c72ad0e..5f7ed470e3e4 100644 --- a/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.h +++ b/source/extensions/compression/zstd/decompressor/zstd_decompressor_impl.h @@ -5,7 +5,7 @@ #include "envoy/stats/stats_macros.h" #include "source/common/common/logger.h" -#include "source/extensions/compression/zstd/common/base.h" +#include "source/common/compression/zstd/common/base.h" #include "source/extensions/compression/zstd/common/dictionary_manager.h" #include "zstd_errors.h" @@ -39,7 +39,7 @@ struct ZstdDecompressorStats { /** * Implementation of decompressor's interface. */ -class ZstdDecompressorImpl : public Common::Base, +class ZstdDecompressorImpl : public Envoy::Compression::Zstd::Common::Base, public Envoy::Compression::Decompressor::Decompressor, public Logger::Loggable, NonCopyable { diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 7f40c6e3efd3..8b1843373671 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -130,7 +130,7 @@ bool TestUtility::rawSlicesEqual(const Buffer::RawSlice* lhs, const Buffer::RawS } void TestUtility::feedBufferWithRandomCharacters(Buffer::Instance& buffer, uint64_t n_char, - uint64_t seed) { + uint64_t seed, uint64_t n_slice) { const std::string sample = "Neque porro quisquam est qui dolorem ipsum.."; std::mt19937 generate(seed); std::uniform_int_distribution<> distribute(1, sample.length() - 1); @@ -138,7 +138,9 @@ void TestUtility::feedBufferWithRandomCharacters(Buffer::Instance& buffer, uint6 for (uint64_t n = 0; n < n_char; ++n) { str += sample.at(distribute(generate)); } - buffer.add(str); + for (uint64_t n = 0; n < n_slice; ++n) { + buffer.add(str); + } } Stats::CounterSharedPtr TestUtility::findCounter(Stats::Store& store, const std::string& name) { diff --git a/test/test_common/utility.h b/test/test_common/utility.h index c365450e83a4..77e0785ea210 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -178,9 +178,10 @@ class TestUtility { * @param buffer supplies the buffer to be fed. * @param n_char number of characters that should be added to the supplied buffer. * @param seed seeds pseudo-random number generator (default = 0). + * @param n_slice number of slices (default = 1). */ static void feedBufferWithRandomCharacters(Buffer::Instance& buffer, uint64_t n_char, - uint64_t seed = 0); + uint64_t seed = 0, uint64_t n_slice = 1); /** * Finds a stat in a vector with the given name. diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 1a1eae3faa56..1edc5139573f 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -354,6 +354,7 @@ QUIC QoS qat qatzip +qatzstd RAII RANLUX RBAC From 3f248d591fcc499386d037b2f5ad562b0c829196 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:08:26 +0000 Subject: [PATCH 23/36] build(deps): bump redis from `e647cfe` to `3134997` in /examples/redis (#32926) Bumps redis from `e647cfe` to `3134997`. --- updated-dependencies: - dependency-name: redis dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/redis/Dockerfile-redis | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index c0098b611cf2..825ed0e86758 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:e647cfe134bf5e8e74e620f66346f93418acfc240b71dd85640325cb7cd01402 +FROM redis@sha256:3134997edb04277814aa51a4175a588d45eb4299272f8eff2307bbf8b39e4d43 From f4fda5aef258ae6ca53cfb60f7b39d47355b932e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:09:11 +0000 Subject: [PATCH 24/36] build(deps): bump setuptools from 69.1.1 to 69.2.0 in /tools/base (#32898) Bumps [setuptools](https://github.com/pypa/setuptools) from 69.1.1 to 69.2.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v69.1.1...v69.2.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 019da779c73f..542b6bd88dcd 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1604,7 +1604,7 @@ zstandard==0.22.0 \ # via envoy-base-utils # The following packages are considered to be unsafe in a requirements file: -setuptools==69.1.1 \ - --hash=sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56 \ - --hash=sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8 +setuptools==69.2.0 \ + --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ + --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c # via -r requirements.in From b9d8dec81d5b04e850b57bbb28df6187279898a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:09:44 +0000 Subject: [PATCH 25/36] build(deps): bump actions/checkout from 4.1.1 to 4.1.2 (#32865) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/b4ffde65f46336ab88eb53be808477a3936bae11...9bb56186c3b09b4f86b1c65136769dd318469633) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_precheck_deps.yml | 2 +- .github/workflows/codeql-daily.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/envoy-dependency.yml | 4 ++-- .github/workflows/mobile-release.yml | 2 +- .github/workflows/mobile-traffic_director.yml | 2 +- .github/workflows/pr_notifier.yml | 2 +- .github/workflows/scorecard.yml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/_precheck_deps.yml b/.github/workflows/_precheck_deps.yml index 8864b7beada4..241fdce64090 100644 --- a/.github/workflows/_precheck_deps.yml +++ b/.github/workflows/_precheck_deps.yml @@ -50,7 +50,7 @@ jobs: if: ${{ inputs.dependency-review }} steps: - name: Checkout Repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: ref: ${{ fromJSON(inputs.request).request.sha }} persist-credentials: false diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 0217ac0c4462..6f2a31997585 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Free disk space uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.27 diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 91cfb8b2df1f..06e1f29a7260 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -32,7 +32,7 @@ jobs: if: github.repository == 'envoyproxy/envoy' steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: fetch-depth: 2 diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index 1aae9d60d58d..286b9f248ea1 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -143,7 +143,7 @@ jobs: path: envoy fetch-depth: 0 token: ${{ steps.appauth.outputs.token }} - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 name: Checkout Envoy build tools repository with: repository: envoyproxy/envoy-build-tools @@ -235,7 +235,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Run dependency checker run: | TODAY_DATE=$(date -u -I"date") diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index d051821814f0..c0a0e3981b97 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -95,7 +95,7 @@ jobs: - output: envoy - output: envoy_xds steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: fetch-depth: 0 - name: Add safe directory diff --git a/.github/workflows/mobile-traffic_director.yml b/.github/workflows/mobile-traffic_director.yml index 4458469f0636..b4fa71cf30bd 100644 --- a/.github/workflows/mobile-traffic_director.yml +++ b/.github/workflows/mobile-traffic_director.yml @@ -30,7 +30,7 @@ jobs: timeout-minutes: 120 steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Add safe directory run: git config --global --add safe.directory /__w/envoy/envoy - name: 'Run GcpTrafficDirectorIntegrationTest' diff --git a/.github/workflows/pr_notifier.yml b/.github/workflows/pr_notifier.yml index 95083e527889..06e8ff695cc7 100644 --- a/.github/workflows/pr_notifier.yml +++ b/.github/workflows/pr_notifier.yml @@ -22,7 +22,7 @@ jobs: || !contains(github.actor, '[bot]')) }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Notify about PRs run: | ARGS=() diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8c4bb04b2ba0..589daf48cffe 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -21,7 +21,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 with: persist-credentials: false From dc21640dff119e1365aa8286b7c34a1f0585e147 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 10:11:43 +0000 Subject: [PATCH 26/36] deps: Bump `aspect_bazel_lib` -> 2.5.3 (#32932) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6cddbf98ad60..ae267e507a16 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -148,12 +148,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Aspect Bazel helpers", project_desc = "Base Starlark libraries and basic Bazel rules which are useful for constructing rulesets and BUILD files", project_url = "https://github.com/aspect-build/bazel-lib", - version = "2.4.2", - sha256 = "f75d03783588e054899eb0729a97fb5b8973c1a26f30373fafd485c90bf207d1", + version = "2.5.3", + sha256 = "6c25c59581041ede31e117693047f972cc4700c89acf913658dc89d04c338f8d", strip_prefix = "bazel-lib-{version}", urls = ["https://github.com/aspect-build/bazel-lib/archive/v{version}.tar.gz"], use_category = ["build"], - release_date = "2024-02-21", + release_date = "2024-03-08", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/aspect-build/bazel-lib/blob/v{version}/LICENSE", From 50e9108c22c2e46d4ad04a977d24d89201aa0c62 Mon Sep 17 00:00:00 2001 From: "Vikas Choudhary (vikasc)" Date: Mon, 18 Mar 2024 19:20:20 +0530 Subject: [PATCH 27/36] Support upstream http filters with tcp tunneling (#27183) As suggested in this comment, Router::UpstreamRequest is being initialized and owned by TcpProxy::CombinedUpstream, which is replacement for TcpProxy::HttpUpstream. Eventually TcpProxy::HttpUpstream will be removed. "Combined" in CombinedUpstream signifies that logic of this class,unlike current TcpProxy::HttpUpstream , does not depend on H1/H2/H3 Signed-off-by: Vikas Choudhary --- envoy/router/router.h | 11 + envoy/tcp/BUILD | 1 + envoy/tcp/upstream.h | 15 +- source/common/http/conn_manager_impl.h | 1 + source/common/http/filter_manager.cc | 12 +- source/common/http/filter_manager.h | 5 + source/common/router/router.cc | 10 +- source/common/router/upstream_request.cc | 12 +- source/common/router/upstream_request.h | 23 +- source/common/runtime/runtime_features.cc | 3 + source/common/runtime/runtime_features.h | 2 + source/common/tcp_proxy/BUILD | 7 + source/common/tcp_proxy/tcp_proxy.cc | 122 ++++++-- source/common/tcp_proxy/tcp_proxy.h | 118 +++++++- source/common/tcp_proxy/upstream.cc | 223 ++++++++++++++- source/common/tcp_proxy/upstream.h | 184 +++++++++++- .../upstreams/http/http/upstream_request.h | 1 + .../upstreams/http/tcp/upstream_request.h | 1 + .../upstreams/http/udp/upstream_request.h | 1 + source/extensions/upstreams/tcp/generic/BUILD | 1 + .../upstreams/tcp/generic/config.cc | 4 +- .../extensions/upstreams/tcp/generic/config.h | 2 + test/common/router/upstream_request_test.cc | 5 +- test/common/tcp_proxy/BUILD | 4 + test/common/tcp_proxy/tcp_proxy_test.cc | 193 +++++++++---- test/common/tcp_proxy/tcp_proxy_test_base.h | 5 +- test/common/tcp_proxy/upstream_test.cc | 264 ++++++++++++++++-- test/extensions/filters/http/cache/BUILD | 1 + test/extensions/filters/http/csrf/BUILD | 1 + .../filters/http/custom_response/BUILD | 2 +- test/extensions/filters/http/fault/BUILD | 1 + .../filters/http/health_check/BUILD | 1 + test/extensions/filters/http/jwt_authn/BUILD | 2 +- test/extensions/filters/http/rbac/BUILD | 2 +- .../http/tcp/upstream_request_test.cc | 2 +- .../http/udp/upstream_request_test.cc | 1 + test/extensions/upstreams/tcp/generic/BUILD | 1 + .../upstreams/tcp/generic/config_test.cc | 46 +-- test/integration/BUILD | 13 +- test/integration/base_integration_test.cc | 1 + test/integration/http_protocol_integration.cc | 24 +- test/integration/http_protocol_integration.h | 4 + test/mocks/http/mocks.cc | 1 + test/mocks/http/mocks.h | 2 + test/mocks/router/BUILD | 9 + test/mocks/router/upstream_request.cc | 13 + test/mocks/router/upstream_request.h | 21 ++ 47 files changed, 1185 insertions(+), 193 deletions(-) create mode 100644 test/mocks/router/upstream_request.cc create mode 100644 test/mocks/router/upstream_request.h diff --git a/envoy/router/router.h b/envoy/router/router.h index 299cb25c2960..8d559c280d33 100644 --- a/envoy/router/router.h +++ b/envoy/router/router.h @@ -1526,6 +1526,17 @@ class GenericUpstream { * @param trailers supplies the trailers to encode. */ virtual void encodeTrailers(const Http::RequestTrailerMap& trailers) PURE; + + // TODO(vikaschoudhary16): Remove this api. + // This api is only used to enable half-close semantics on the upstream connection. + // This ideally should be done via calling connection.enableHalfClose() but since TcpProxy + // does not have access to the upstream connection, it is done via this api for now. + /** + * Enable half-close semantics on the upstream connection. Reading a remote half-close + * will not fully close the connection. This is off by default. + * @param enabled Whether to set half-close semantics as enabled or disabled. + */ + virtual void enableHalfClose() PURE; /** * Enable/disable further data from this stream. */ diff --git a/envoy/tcp/BUILD b/envoy/tcp/BUILD index 86d70b1774c6..5c3bdca0d9ef 100644 --- a/envoy/tcp/BUILD +++ b/envoy/tcp/BUILD @@ -26,6 +26,7 @@ envoy_cc_library( "//envoy/http:header_evaluator", "//envoy/tcp:conn_pool_interface", "//envoy/upstream:upstream_interface", + "//source/common/router:router_lib", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], ) diff --git a/envoy/tcp/upstream.h b/envoy/tcp/upstream.h index 200ec7fc9ea7..f6191a27513b 100644 --- a/envoy/tcp/upstream.h +++ b/envoy/tcp/upstream.h @@ -2,11 +2,14 @@ #include "envoy/buffer/buffer.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" +#include "envoy/http/filter.h" #include "envoy/http/header_evaluator.h" #include "envoy/stream_info/stream_info.h" #include "envoy/tcp/conn_pool.h" #include "envoy/upstream/upstream.h" +#include "source/common/router/router.h" + namespace Envoy { namespace Upstream { @@ -48,14 +51,17 @@ class TunnelingConfigHelper { virtual void propagateResponseTrailers(Http::ResponseTrailerMapPtr&& trailers, const StreamInfo::FilterStateSharedPtr& filter_state) const PURE; + virtual const Envoy::Router::FilterConfig& routerFilterConfig() const PURE; + virtual Server::Configuration::ServerFactoryContext& serverFactoryContext() const PURE; }; using TunnelingConfigHelperOptConstRef = OptRef; // An API for wrapping either a TCP or an HTTP connection pool. -class GenericConnPool : public Logger::Loggable { +class GenericConnPool : public Event::DeferredDeletable, + public Logger::Loggable { public: - virtual ~GenericConnPool() = default; + ~GenericConnPool() override = default; /** * Called to create a TCP connection or HTTP stream for "CONNECT" streams. @@ -105,9 +111,9 @@ class GenericConnectionPoolCallbacks { // Interface for a generic Upstream, which can communicate with a TCP or HTTP // upstream. -class GenericUpstream { +class GenericUpstream : public Event::DeferredDeletable { public: - virtual ~GenericUpstream() = default; + ~GenericUpstream() override = default; /** * Enable/disable further data from this stream. @@ -175,6 +181,7 @@ class GenericConnPoolFactory : public Envoy::Config::TypedFactory { TunnelingConfigHelperOptConstRef config, Upstream::LoadBalancerContext* context, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, + Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, StreamInfo::StreamInfo& downstream_info) const PURE; }; diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 11151b157bf5..c8d731afdb3d 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -314,6 +314,7 @@ class ConnectionManagerImpl : Logger::Loggable, OptRef tracingConfig() const override; const ScopeTrackedObject& scope() override; OptRef downstreamCallbacks() override { return *this; } + bool isHalfCloseEnabled() override { return false; } // DownstreamStreamFilterCallbacks void setRoute(Router::RouteConstSharedPtr route) override; diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 187bb7b58561..070440572660 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -901,9 +901,17 @@ FilterManager::commonEncodePrefix(ActiveStreamEncoderFilter* filter, bool end_st FilterIterationStartState filter_iteration_start_state) { // Only do base state setting on the initial call. Subsequent calls for filtering do not touch // the base state. + ENVOY_STREAM_LOG(trace, "commonEncodePrefix end_stream: {}, isHalfCloseEnabled: {}", *this, + end_stream, filter_manager_callbacks_.isHalfCloseEnabled()); if (filter == nullptr) { - ASSERT(!state_.local_complete_); - state_.local_complete_ = end_stream; + // half close is enabled in case tcp proxying is done with http1 encoder. In this case, we + // should not set the local_complete_ flag to true when end_stream is true. + // setting local_complete_ to true will cause any data sent in the upstream direction to be + // dropped. + if (end_stream && !filter_manager_callbacks_.isHalfCloseEnabled()) { + ASSERT(!state_.local_complete_); + state_.local_complete_ = true; + } return encoder_filters_.begin(); } diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 6a671ab99e9b..da5ba20033bd 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -545,6 +545,11 @@ class FilterManagerCallbacks { * Returns the DownstreamStreamFilterCallbacks for downstream HTTP filters. */ virtual OptRef downstreamCallbacks() { return {}; } + /** + * Returns if close from the upstream is to be handled with half-close semantics. + * This is used for HTTP/1.1 codec. + */ + virtual bool isHalfCloseEnabled() PURE; }; /** diff --git a/source/common/router/router.cc b/source/common/router/router.cc index a41e1c3374dc..c3baed7b839e 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -744,8 +744,9 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, // will never transition from false to true. bool can_use_http3 = !transport_socket_options_ || !transport_socket_options_->http11ProxyInfo().has_value(); - UpstreamRequestPtr upstream_request = std::make_unique( - *this, std::move(generic_conn_pool), can_send_early_data, can_use_http3); + UpstreamRequestPtr upstream_request = + std::make_unique(*this, std::move(generic_conn_pool), can_send_early_data, + can_use_http3, false /*enable_half_close*/); LinkedList::moveIntoList(std::move(upstream_request), upstream_requests_); upstream_requests_.front()->acceptHeadersFromRouter(end_stream); if (streaming_shadows_) { @@ -1987,8 +1988,9 @@ void Filter::doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry cleanup(); return; } - UpstreamRequestPtr upstream_request = std::make_unique( - *this, std::move(generic_conn_pool), can_send_early_data, can_use_http3); + UpstreamRequestPtr upstream_request = + std::make_unique(*this, std::move(generic_conn_pool), can_send_early_data, + can_use_http3, false /*enable_tcp_tunneling*/); if (include_attempt_count_in_request_) { downstream_headers_->setEnvoyAttemptCount(attempt_count_); diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 7c9079e58a44..bb0803df3be5 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -80,7 +80,8 @@ class UpstreamFilterManager : public Http::FilterManager { UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, std::unique_ptr&& conn_pool, - bool can_send_early_data, bool can_use_http3) + bool can_send_early_data, bool can_use_http3, + bool enable_half_close) : parent_(parent), conn_pool_(std::move(conn_pool)), stream_info_(parent_.callbacks()->dispatcher().timeSource(), nullptr), start_time_(parent_.callbacks()->dispatcher().timeSource().monotonicTime()), @@ -93,7 +94,8 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, cleaned_up_(false), had_upstream_(false), stream_options_({can_send_early_data, can_use_http3}), grpc_rq_success_deferred_(false), upstream_wait_for_response_headers_before_disabling_read_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.upstream_wait_for_response_headers_before_disabling_read")) { + "envoy.reloadable_features.upstream_wait_for_response_headers_before_disabling_read")), + enable_half_close_(enable_half_close) { if (auto tracing_config = parent_.callbacks()->tracingConfig(); tracing_config.has_value()) { if (tracing_config->spawnUpstreamSpan() || parent_.config().start_child_span_) { span_ = parent_.callbacks()->activeSpan().spawnChild( @@ -258,7 +260,8 @@ void UpstreamRequest::decode1xxHeaders(Http::ResponseHeaderMapPtr&& headers) { // on to the router. void UpstreamRequest::decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) { ASSERT(headers.get()); - ENVOY_STREAM_LOG(trace, "upstream response headers:\n{}", *parent_.callbacks(), *headers); + ENVOY_STREAM_LOG(trace, "end_stream: {}, upstream response headers:\n{}", *parent_.callbacks(), + end_stream, *headers); ScopeTrackerScopeState scope(&parent_.callbacks()->scope(), parent_.callbacks()->dispatcher()); resetPerTryIdleTimer(); @@ -581,6 +584,9 @@ void UpstreamRequest::onPoolReady(std::unique_ptr&& upstream, had_upstream_ = true; // Have the upstream use the account of the downstream. upstream_->setAccount(parent_.callbacks()->account()); + if (enable_half_close_) { + upstream_->enableHalfClose(); + } host->outlierDetector().putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess); diff --git a/source/common/router/upstream_request.h b/source/common/router/upstream_request.h index 9e88bfefda31..c6e7a4ec4c94 100644 --- a/source/common/router/upstream_request.h +++ b/source/common/router/upstream_request.h @@ -62,29 +62,27 @@ class UpstreamCodecFilter; * UpstreamCodecFilter. This is accomplished via the UpstreamStreamFilterCallbacks * interface, with the UpstreamFilterManager acting as intermediary. * - * UpstreamRequest is marked as final because no subclasses are expected. - * This class is intended to be used as-is without any specialized inheritance. */ -class UpstreamRequest final : public Logger::Loggable, - public UpstreamToDownstream, - public LinkedObject, - public GenericConnectionPoolCallbacks, - public Event::DeferredDeletable { +class UpstreamRequest : public Logger::Loggable, + public UpstreamToDownstream, + public LinkedObject, + public GenericConnectionPoolCallbacks, + public Event::DeferredDeletable { public: UpstreamRequest(RouterFilterInterface& parent, std::unique_ptr&& conn_pool, - bool can_send_early_data, bool can_use_http3); + bool can_send_early_data, bool can_use_http3, bool enable_half_close); ~UpstreamRequest() override; void deleteIsPending() override { cleanUp(); } // To be called from the destructor, or prior to deferred delete. void cleanUp(); - void acceptHeadersFromRouter(bool end_stream); - void acceptDataFromRouter(Buffer::Instance& data, bool end_stream); + virtual void acceptHeadersFromRouter(bool end_stream); + virtual void acceptDataFromRouter(Buffer::Instance& data, bool end_stream); void acceptTrailersFromRouter(Http::RequestTrailerMap& trailers); void acceptMetadataFromRouter(Http::MetadataMapPtr&& metadata_map_ptr); - void resetStream(); + virtual void resetStream(); void setupPerTryTimeout(); void maybeEndDecode(bool end_stream); void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host, bool pool_success); @@ -258,6 +256,7 @@ class UpstreamRequest final : public Logger::Loggable, Http::ConnectionPool::Instance::StreamOptions stream_options_; bool grpc_rq_success_deferred_ : 1; bool upstream_wait_for_response_headers_before_disabling_read_ : 1; + bool enable_half_close_ : 1; }; class UpstreamRequestFilterManagerCallbacks : public Http::FilterManagerCallbacks, @@ -371,7 +370,7 @@ class UpstreamRequestFilterManagerCallbacks : public Http::FilterManagerCallback void setUpstreamToDownstream(UpstreamToDownstream& upstream_to_downstream_interface) override { upstream_request_.upstream_interface_ = upstream_to_downstream_interface; } - + bool isHalfCloseEnabled() override { return upstream_request_.enable_half_close_; } Http::RequestTrailerMapPtr trailers_; Http::ResponseHeaderMapPtr informational_headers_; Http::ResponseHeaderMapPtr response_headers_; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 40e526641425..ec9e7f6a40e3 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -130,6 +130,9 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_runtime_initialized); FALSE_RUNTIME_GUARD(envoy_reloadable_features_always_use_v6); // TODO(wbpcode) complete remove this feature is no one use it. FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); +// TODO(vikaschoudhary16) flip this to true only after all the +// TcpProxy::Filter::HttpStreamDecoderFilterCallbacks are implemented or commented as unnecessary +FALSE_RUNTIME_GUARD(envoy_restart_features_upstream_http_filters_with_tcp_proxy); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); // TODO(suniltheta): Once the newly added http async technique is stabilized move it under diff --git a/source/common/runtime/runtime_features.h b/source/common/runtime/runtime_features.h index 0c2fb2d9b9e7..e698064a5971 100644 --- a/source/common/runtime/runtime_features.h +++ b/source/common/runtime/runtime_features.h @@ -26,6 +26,8 @@ void maybeSetRuntimeGuard(absl::string_view name, bool value); void maybeSetDeprecatedInts(absl::string_view name, uint32_t value); constexpr absl::string_view defer_processing_backedup_streams = "envoy.reloadable_features.defer_processing_backedup_streams"; +constexpr absl::string_view upstream_http_filters_with_tcp_proxy = + "envoy.restart_features.upstream_http_filters_with_tcp_proxy"; } // namespace Runtime } // namespace Envoy diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index 966088ed4a08..c6883b43aec3 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -17,15 +17,22 @@ envoy_cc_library( "upstream.h", ], deps = [ + "//envoy/http:header_map_interface", + "//envoy/router:router_ratelimit_interface", "//envoy/tcp:conn_pool_interface", "//envoy/tcp:upstream_interface", "//envoy/upstream:cluster_manager_interface", "//envoy/upstream:load_balancer_interface", + "//source/common/http:async_client_lib", "//source/common/http:codec_client_lib", + "//source/common/http:hash_policy_lib", "//source/common/http:header_map_lib", "//source/common/http:headers_lib", "//source/common/http:utility_lib", + "//source/common/network:utility_lib", "//source/common/router:header_parser_lib", + "//source/common/router:router_lib", + "//source/common/router:shadow_writer_lib", ], ) diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index f60f31ec44db..21483aca5463 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -32,8 +32,10 @@ #include "source/common/network/upstream_server_name.h" #include "source/common/network/upstream_socket_options_filter_state.h" #include "source/common/router/metadatamatchcriteria_impl.h" +#include "source/common/router/shadow_writer_impl.h" #include "source/common/stream_info/stream_id_provider_impl.h" #include "source/common/stream_info/uint64_accessor_impl.h" +#include "source/common/tracing/http_tracer_impl.h" namespace Envoy { namespace TcpProxy { @@ -110,7 +112,7 @@ Config::SharedConfig::SharedConfig( } if (config.has_tunneling_config()) { tunneling_config_helper_ = - std::make_unique(config.tunneling_config(), context); + std::make_unique(*stats_scope_.get(), config, context); } if (config.has_max_downstream_connection_duration()) { const uint64_t connection_duration = @@ -230,8 +232,10 @@ UpstreamDrainManager& Config::drainManager() { } Filter::Filter(ConfigSharedPtr config, Upstream::ClusterManager& cluster_manager) - : config_(config), cluster_manager_(cluster_manager), downstream_callbacks_(*this), - upstream_callbacks_(new UpstreamCallbacks(this)) { + : tracing_config_(Tracing::EgressConfig::get()), config_(config), + cluster_manager_(cluster_manager), downstream_callbacks_(*this), + upstream_callbacks_(new UpstreamCallbacks(this)), + upstream_decoder_filter_callbacks_(HttpStreamDecoderFilterCallbacks(this)) { ASSERT(config != nullptr); } @@ -289,9 +293,11 @@ void Filter::onInitFailure(UpstreamFailureReason reason) { // not have started attempting to connect to an upstream and there is no // connection pool callback latency to record. if (initial_upstream_connection_start_time_.has_value()) { - getStreamInfo().upstreamInfo()->upstreamTiming().recordConnectionPoolCallbackLatency( - initial_upstream_connection_start_time_.value(), - read_callbacks_->connection().dispatcher().timeSource()); + if (!getStreamInfo().upstreamInfo()->upstreamTiming().connectionPoolCallbackLatency()) { + getStreamInfo().upstreamInfo()->upstreamTiming().recordConnectionPoolCallbackLatency( + initial_upstream_connection_start_time_.value(), + read_callbacks_->connection().dispatcher().timeSource()); + } } read_callbacks_->connection().close( Network::ConnectionCloseType::NoFlush, @@ -552,9 +558,16 @@ bool Filter::maybeTunnel(Upstream::ThreadLocalCluster& cluster) { if (!factory) { return false; } - - generic_conn_pool_ = factory->createGenericConnPool(cluster, config_->tunnelingConfigHelper(), - this, *upstream_callbacks_, getStreamInfo()); + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + // TODO(vikaschoudhary16): Initialize route_ once per cluster. + upstream_decoder_filter_callbacks_.route_ = std::make_shared( + cluster.info()->name(), + *std::unique_ptr{new Router::RetryPolicyImpl()}); + } + generic_conn_pool_ = factory->createGenericConnPool( + cluster, config_->tunnelingConfigHelper(), this, *upstream_callbacks_, + upstream_decoder_filter_callbacks_, getStreamInfo()); if (generic_conn_pool_) { connecting_ = true; connect_attempts_++; @@ -570,7 +583,19 @@ bool Filter::maybeTunnel(Upstream::ThreadLocalCluster& cluster) { void Filter::onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, absl::string_view failure_reason, Upstream::HostDescriptionConstSharedPtr host) { - generic_conn_pool_.reset(); + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + // generic_conn_pool_ is an instance of TcpProxy::HttpConnPool. + // generic_conn_pool_->newStream() is called in maybeTunnel() which initializes an instance of + // Router::UpstreamRequest. If Router::UpstreamRequest receives headers from the upstream which + // results in end_stream=true, then via callbacks passed to Router::UpstreamRequest, + // TcpProxy::Filter::onGenericPoolFailure() gets invoked. If we do not do deferredDelete here, + // then the same instance of UpstreamRequest which is under execution will go out of scope. + read_callbacks_->connection().dispatcher().deferredDelete(std::move(generic_conn_pool_)); + } else { + generic_conn_pool_.reset(); + } + read_callbacks_->upstreamHost(host); getStreamInfo().upstreamInfo()->setUpstreamHost(host); getStreamInfo().upstreamInfo()->setUpstreamTransportFailureReason(failure_reason); @@ -594,23 +619,30 @@ void Filter::onGenericPoolReady(StreamInfo::StreamInfo* info, Upstream::HostDescriptionConstSharedPtr& host, const Network::ConnectionInfoProvider& address_provider, Ssl::ConnectionInfoConstSharedPtr ssl_info) { + StreamInfo::UpstreamInfo& upstream_info = *getStreamInfo().upstreamInfo(); + if (!upstream_info.upstreamTiming().connectionPoolCallbackLatency()) { + upstream_info.upstreamTiming().recordConnectionPoolCallbackLatency( + initial_upstream_connection_start_time_.value(), + read_callbacks_->connection().dispatcher().timeSource()); + } upstream_ = std::move(upstream); generic_conn_pool_.reset(); read_callbacks_->upstreamHost(host); - StreamInfo::UpstreamInfo& upstream_info = *getStreamInfo().upstreamInfo(); - upstream_info.upstreamTiming().recordConnectionPoolCallbackLatency( - initial_upstream_connection_start_time_.value(), - read_callbacks_->connection().dispatcher().timeSource()); + // No need to set information using address_provider in case routing via Router::UpstreamRequest + // because in that case, information is already set by the + // Router::UpstreamRequest::onPoolReady() method before reaching here. + if (upstream_info.upstreamLocalAddress() == nullptr) { + upstream_info.setUpstreamLocalAddress(address_provider.localAddress()); + upstream_info.setUpstreamRemoteAddress(address_provider.remoteAddress()); + } upstream_info.setUpstreamHost(host); - upstream_info.setUpstreamLocalAddress(address_provider.localAddress()); - upstream_info.setUpstreamRemoteAddress(address_provider.remoteAddress()); upstream_info.setUpstreamSslConnection(ssl_info); onUpstreamConnection(); read_callbacks_->continueReading(); if (info) { upstream_info.setUpstreamFilterState(info->filterState()); } -} +} // namespace TcpProxy const Router::MetadataMatchCriteria* Filter::metadataMatchCriteria() { const Router::MetadataMatchCriteria* route_criteria = @@ -640,14 +672,30 @@ const std::string& TunnelResponseTrailers::key() { } TunnelingConfigHelperImpl::TunnelingConfigHelperImpl( - const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig& - config_message, + Stats::Scope& stats_scope, + const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config_message, Server::Configuration::FactoryContext& context) - : use_post_(config_message.use_post()), - header_parser_(Envoy::Router::HeaderParser::configure(config_message.headers_to_add())), - propagate_response_headers_(config_message.propagate_response_headers()), - propagate_response_trailers_(config_message.propagate_response_trailers()), - post_path_(config_message.post_path()) { + : use_post_(config_message.tunneling_config().use_post()), + header_parser_(Envoy::Router::HeaderParser::configure( + config_message.tunneling_config().headers_to_add())), + propagate_response_headers_(config_message.tunneling_config().propagate_response_headers()), + propagate_response_trailers_(config_message.tunneling_config().propagate_response_trailers()), + post_path_(config_message.tunneling_config().post_path()), + route_stat_name_storage_("tcpproxy_tunneling", context.scope().symbolTable()), + // TODO(vikaschoudhary16): figure out which of the following router_config_ members are + // not required by tcp_proxy and move them to a different class + router_config_(route_stat_name_storage_.statName(), + context.serverFactoryContext().localInfo(), stats_scope, + context.serverFactoryContext().clusterManager(), + context.serverFactoryContext().runtime(), + context.serverFactoryContext().api().randomGenerator(), + std::make_unique( + context.serverFactoryContext().clusterManager()), + true, false, false, false, false, false, {}, + context.serverFactoryContext().api().timeSource(), + context.serverFactoryContext().httpContext(), + context.serverFactoryContext().routerContext()), + server_factory_context_(context.serverFactoryContext()) { if (!post_path_.empty() && !use_post_) { throw EnvoyException("Can't set a post path when POST method isn't used"); } @@ -655,7 +703,7 @@ TunnelingConfigHelperImpl::TunnelingConfigHelperImpl( envoy::config::core::v3::SubstitutionFormatString substitution_format_config; substitution_format_config.mutable_text_format_source()->set_inline_string( - config_message.hostname()); + config_message.tunneling_config().hostname()); hostname_fmt_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( substitution_format_config, context); } @@ -697,8 +745,8 @@ void Filter::onConnectTimeout() { } Network::FilterStatus Filter::onData(Buffer::Instance& data, bool end_stream) { - ENVOY_CONN_LOG(trace, "downstream connection received {} bytes, end_stream={}", - read_callbacks_->connection(), data.length(), end_stream); + ENVOY_CONN_LOG(trace, "downstream connection received {} bytes, end_stream={}, has upstream {}", + read_callbacks_->connection(), data.length(), end_stream, upstream_ != nullptr); getStreamInfo().getDownstreamBytesMeter()->addWireBytesReceived(data.length()); if (upstream_) { getStreamInfo().getUpstreamBytesMeter()->addWireBytesSent(data.length()); @@ -799,14 +847,23 @@ void Filter::onUpstreamEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { - upstream_.reset(); + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + read_callbacks_->connection().dispatcher().deferredDelete(std::move(upstream_)); + } else { + upstream_.reset(); + } disableIdleTimer(); if (connecting) { if (event == Network::ConnectionEvent::RemoteClose) { - getStreamInfo().setResponseFlag(StreamInfo::CoreResponseFlag::UpstreamConnectionFailure); - read_callbacks_->upstreamHost()->outlierDetector().putResult( - Upstream::Outlier::Result::LocalOriginConnectFailed); + getStreamInfo().setResponseFlag(StreamInfo::UpstreamConnectionFailure); + // upstreamHost can be nullptr if we received a disconnect from the upstream before + // receiving any response + if (read_callbacks_->upstreamHost() != nullptr) { + read_callbacks_->upstreamHost()->outlierDetector().putResult( + Upstream::Outlier::Result::LocalOriginConnectFailed); + } } if (!downstream_closed_) { route_ = pickRoute(); @@ -930,6 +987,9 @@ void Filter::disableIdleTimer() { } } +Filter::HttpStreamDecoderFilterCallbacks::HttpStreamDecoderFilterCallbacks(Filter* parent) + : parent_(parent), request_trailer_map_(Http::RequestTrailerMapImpl::create()) {} + UpstreamDrainManager::~UpstreamDrainManager() { // If connections aren't closed before they are destructed an ASSERT fires, // so cancel all pending drains, which causes the connections to be closed. diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 45f34b901405..4019ea1211ef 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -10,6 +10,7 @@ #include "envoy/common/random_generator.h" #include "envoy/event/timer.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" +#include "envoy/http/codec.h" #include "envoy/http/header_evaluator.h" #include "envoy/network/connection.h" #include "envoy/network/filter.h" @@ -22,6 +23,7 @@ #include "envoy/upstream/cluster_manager.h" #include "envoy/upstream/upstream.h" +#include "source/common/common/assert.h" #include "source/common/common/logger.h" #include "source/common/formatter/substitution_format_string.h" #include "source/common/http/header_map_impl.h" @@ -143,24 +145,29 @@ class TunnelResponseTrailers : public Http::TunnelResponseHeadersOrTrailersImpl private: const Http::ResponseTrailerMapPtr response_trailers_; }; - +class Config; class TunnelingConfigHelperImpl : public TunnelingConfigHelper, protected Logger::Loggable { public: TunnelingConfigHelperImpl( - const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig& - config_message, + Stats::Scope& scope, + const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config_message, Server::Configuration::FactoryContext& context); std::string host(const StreamInfo::StreamInfo& stream_info) const override; bool usePost() const override { return use_post_; } const std::string& postPath() const override { return post_path_; } Envoy::Http::HeaderEvaluator& headerEvaluator() const override { return *header_parser_; } + + const Envoy::Router::FilterConfig& routerFilterConfig() const override { return router_config_; } void propagateResponseHeaders(Http::ResponseHeaderMapPtr&& headers, const StreamInfo::FilterStateSharedPtr& filter_state) const override; void propagateResponseTrailers(Http::ResponseTrailerMapPtr&& trailers, const StreamInfo::FilterStateSharedPtr& filter_state) const override; + Server::Configuration::ServerFactoryContext& serverFactoryContext() const override { + return server_factory_context_; + } private: const bool use_post_; @@ -169,6 +176,9 @@ class TunnelingConfigHelperImpl : public TunnelingConfigHelper, const bool propagate_response_headers_; const bool propagate_response_trailers_; std::string post_path_; + Stats::StatNameManagedStorage route_stat_name_storage_; + const Router::FilterConfig router_config_; + Server::Configuration::ServerFactoryContext& server_factory_context_; }; /** @@ -461,6 +471,107 @@ class Filter : public Network::ReadFilter, }; StreamInfo::StreamInfo& getStreamInfo(); + class HttpStreamDecoderFilterCallbacks : public Http::StreamDecoderFilterCallbacks, + public ScopeTrackedObject { + public: + HttpStreamDecoderFilterCallbacks(Filter* parent); + // Http::StreamDecoderFilterCallbacks + OptRef connection() override { + return parent_->read_callbacks_->connection(); + } + StreamInfo::StreamInfo& streamInfo() override { return parent_->getStreamInfo(); } + const ScopeTrackedObject& scope() override { return *this; } + Event::Dispatcher& dispatcher() override { + return parent_->read_callbacks_->connection().dispatcher(); + } + void resetStream(Http::StreamResetReason, absl::string_view) override { + IS_ENVOY_BUG("Not implemented. Unexpected call to resetStream()"); + }; + Router::RouteConstSharedPtr route() override { return route_; } + Upstream::ClusterInfoConstSharedPtr clusterInfo() override { + return parent_->cluster_manager_.getThreadLocalCluster(parent_->route_->clusterName()) + ->info(); + } + uint64_t streamId() const override { + auto sip = parent_->getStreamInfo().getStreamIdProvider(); + if (sip) { + return sip->toInteger().value(); + } + return 0; + } + Tracing::Span& activeSpan() override { return parent_->active_span_; } + OptRef tracingConfig() const override { + return makeOptRef(parent_->tracing_config_); + } + void continueDecoding() override {} + void addDecodedData(Buffer::Instance&, bool) override {} + void injectDecodedDataToFilterChain(Buffer::Instance&, bool) override {} + Http::RequestTrailerMap& addDecodedTrailers() override { return *request_trailer_map_; } + Http::MetadataMapVector& addDecodedMetadata() override { + static Http::MetadataMapVector metadata_map_vector; + return metadata_map_vector; + } + const Buffer::Instance* decodingBuffer() override { return nullptr; } + void modifyDecodingBuffer(std::function) override {} + void sendLocalReply(Http::Code, absl::string_view, + std::function, + const absl::optional, + absl::string_view) override {} + void encode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} + Http::ResponseHeaderMapOptRef informationalHeaders() override { return {}; } + void encodeHeaders(Http::ResponseHeaderMapPtr&&, bool, absl::string_view) override {} + Http::ResponseHeaderMapOptRef responseHeaders() override { return {}; } + void encodeData(Buffer::Instance&, bool) override {} + Http::RequestHeaderMapOptRef requestHeaders() override { return {}; } + Http::RequestTrailerMapOptRef requestTrailers() override { return {}; } + void encodeTrailers(Http::ResponseTrailerMapPtr&&) override {} + Http::ResponseTrailerMapOptRef responseTrailers() override { return {}; } + void encodeMetadata(Http::MetadataMapPtr&&) override {} + // TODO(vikaschoudhary16): Implement watermark callbacks and test through flow control e2es. + void onDecoderFilterAboveWriteBufferHighWatermark() override {} + void onDecoderFilterBelowWriteBufferLowWatermark() override {} + void addDownstreamWatermarkCallbacks(Http::DownstreamWatermarkCallbacks&) override {} + void removeDownstreamWatermarkCallbacks(Http::DownstreamWatermarkCallbacks&) override {} + void setDecoderBufferLimit(uint32_t) override {} + uint32_t decoderBufferLimit() override { return 0; } + bool recreateStream(const Http::ResponseHeaderMap*) override { return false; } + void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr&) override {} + Network::Socket::OptionsSharedPtr getUpstreamSocketOptions() const override { return nullptr; } + const Router::RouteSpecificFilterConfig* mostSpecificPerFilterConfig() const override { + return nullptr; + } + Buffer::BufferMemoryAccountSharedPtr account() const override { return nullptr; } + void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost) override {} + absl::optional + upstreamOverrideHost() const override { + return absl::nullopt; + } + void restoreContextOnContinue(ScopeTrackedObjectStack& tracked_object_stack) override { + tracked_object_stack.add(*this); + } + void traversePerFilterConfig( + std::function) const override {} + Http::Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override { return {}; } + OptRef downstreamCallbacks() override { return {}; } + OptRef upstreamCallbacks() override { return {}; } + void resetIdleTimer() override {} + // absl::optional upstreamOverrideHost() const override { + // return absl::nullopt; + // } + absl::string_view filterConfigName() const override { return ""; } + + // ScopeTrackedObject + void dumpState(std::ostream& os, int indent_level) const override { + const char* spaces = spacesForLevel(indent_level); + os << spaces << "TcpProxy " << this << DUMP_MEMBER(streamId()) << "\n"; + DUMP_DETAILS(parent_->getStreamInfo().upstreamInfo()); + } + Filter* parent_{}; + Http::RequestTrailerMapPtr request_trailer_map_; + std::shared_ptr route_; + }; + Tracing::NullSpan active_span_; + const Tracing::Config& tracing_config_; protected: struct DownstreamCallbacks : public Network::ConnectionCallbacks { @@ -545,6 +656,7 @@ class Filter : public Network::ReadFilter, uint32_t connect_attempts_{}; bool connecting_{}; bool downstream_closed_{}; + HttpStreamDecoderFilterCallbacks upstream_decoder_filter_callbacks_; }; // This class deals with an upstream connection that needs to finish flushing, when the downstream diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index 5e4eaa35338d..01e99426b9f6 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -1,16 +1,19 @@ #include "source/common/tcp_proxy/upstream.h" +#include "envoy/http/header_map.h" #include "envoy/upstream/cluster_manager.h" #include "source/common/http/codec_client.h" #include "source/common/http/codes.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/headers.h" +#include "source/common/http/null_route_impl.h" #include "source/common/http/utility.h" #include "source/common/runtime/runtime_features.h" namespace Envoy { namespace TcpProxy { + using TunnelingConfig = envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; @@ -194,6 +197,7 @@ void HttpUpstream::resetEncoder(Network::ConnectionEvent event, bool inform_down conn_pool_callbacks_->onFailure(); return; } + if (inform_downstream) { upstream_callbacks_.onEvent(event); } @@ -229,7 +233,7 @@ TcpConnPool::~TcpConnPool() { void TcpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { callbacks_ = &callbacks; - // Given this function is reentrant, make sure we only reset the upstream_handle_ if given a + // Given this function is re-entrant, make sure we only reset the upstream_handle_ if given a // valid connection handle. If newConnection fails inline it may result in attempting to // select a new host, and a recursive call to establishUpstreamConnection. In this case the // first call to newConnection will return null and the inner call will persist. @@ -270,29 +274,67 @@ HttpConnPool::HttpConnPool(Upstream::ThreadLocalCluster& thread_local_cluster, Upstream::LoadBalancerContext* context, const TunnelingConfigHelper& config, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, + Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, Http::CodecType type, StreamInfo::StreamInfo& downstream_info) - : config_(config), type_(type), upstream_callbacks_(upstream_callbacks), - downstream_info_(downstream_info) { + : config_(config), type_(type), decoder_filter_callbacks_(&stream_decoder_callbacks), + upstream_callbacks_(upstream_callbacks), downstream_info_(downstream_info) { absl::optional protocol; if (type_ == Http::CodecType::HTTP3) { protocol = Http::Protocol::Http3; } else if (type_ == Http::CodecType::HTTP2) { protocol = Http::Protocol::Http2; } + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + absl::optional upstream_protocol = protocol; + generic_conn_pool_ = createConnPool(thread_local_cluster, context, upstream_protocol); + return; + } conn_pool_data_ = thread_local_cluster.httpConnPool(Upstream::ResourcePriority::Default, protocol, context); } +std::unique_ptr +HttpConnPool::createConnPool(Upstream::ThreadLocalCluster& cluster, + Upstream::LoadBalancerContext* context, + absl::optional protocol) { + Router::GenericConnPoolFactory* factory = nullptr; + factory = Envoy::Config::Utility::getFactoryByName( + "envoy.filters.connection_pools.http.generic"); + if (!factory) { + return nullptr; + } + + return factory->createGenericConnPool( + cluster, Envoy::Router::GenericConnPoolFactory::UpstreamProtocol::HTTP, + decoder_filter_callbacks_->route()->routeEntry()->priority(), protocol, context); +} + HttpConnPool::~HttpConnPool() { if (upstream_handle_ != nullptr) { // Because HTTP connections are generally shorter lived and have a higher probability of use // before going idle, they are closed with Default rather than CloseExcess. upstream_handle_->cancel(ConnectionPool::CancelPolicy::Default); } + if (combined_upstream_ != nullptr) { + combined_upstream_->onDownstreamEvent(Network::ConnectionEvent::LocalClose); + } } void HttpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { callbacks_ = &callbacks; + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + combined_upstream_ = std::make_unique( + *this, upstream_callbacks_, *decoder_filter_callbacks_, config_, downstream_info_); + RouterUpstreamRequestPtr upstream_request = std::make_unique( + *combined_upstream_, std::move(generic_conn_pool_), /*can_send_early_data_=*/false, + /*can_use_http3_=*/true, true /*enable_tcp_tunneling*/); + combined_upstream_->setRouterUpstreamRequest(std::move(upstream_request)); + combined_upstream_->newStream(callbacks); + return; + } + upstream_ = std::make_unique(upstream_callbacks_, config_, downstream_info_, type_); Tcp::ConnectionPool::Cancellable* handle = conn_pool_data_.value().newStream(upstream_->responseDecoder(), *this, @@ -310,6 +352,15 @@ void HttpConnPool::onPoolFailure(ConnectionPool::PoolFailureReason reason, callbacks_->onGenericPoolFailure(reason, failure_reason, host); } +void HttpConnPool::onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host, + bool pool_success) { + if (!pool_success) { + return; + } + combined_upstream_->setConnPoolCallbacks(std::make_unique( + *this, host, downstream_info_.downstreamAddressProvider().sslConnection())); +} + void HttpConnPool::onPoolReady(Http::RequestEncoder& request_encoder, Upstream::HostDescriptionConstSharedPtr host, StreamInfo::StreamInfo& info, absl::optional) { @@ -332,8 +383,174 @@ void HttpConnPool::onPoolReady(Http::RequestEncoder& request_encoder, void HttpConnPool::onGenericPoolReady(Upstream::HostDescriptionConstSharedPtr& host, const Network::ConnectionInfoProvider& address_provider, Ssl::ConnectionInfoConstSharedPtr ssl_info) { + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + + callbacks_->onGenericPoolReady(nullptr, std::move(combined_upstream_), host, address_provider, + ssl_info); + return; + } callbacks_->onGenericPoolReady(nullptr, std::move(upstream_), host, address_provider, ssl_info); } +CombinedUpstream::CombinedUpstream(HttpConnPool& http_conn_pool, + Tcp::ConnectionPool::UpstreamCallbacks& callbacks, + Http::StreamDecoderFilterCallbacks& decoder_callbacks, + const TunnelingConfigHelper& config, + StreamInfo::StreamInfo& downstream_info) + : config_(config), downstream_info_(downstream_info), parent_(http_conn_pool), + decoder_filter_callbacks_(decoder_callbacks), response_decoder_(*this), + upstream_callbacks_(callbacks) { + auto is_ssl = downstream_info_.downstreamAddressProvider().sslConnection(); + downstream_headers_ = Http::createHeaderMap({ + {Http::Headers::get().Method, config_.usePost() ? "POST" : "CONNECT"}, + {Http::Headers::get().Host, config_.host(downstream_info_)}, + }); + + if (config_.usePost()) { + const std::string& scheme = + is_ssl ? Http::Headers::get().SchemeValues.Https : Http::Headers::get().SchemeValues.Http; + downstream_headers_->addReference(Http::Headers::get().Path, config_.postPath()); + downstream_headers_->addReference(Http::Headers::get().Scheme, scheme); + } + + config_.headerEvaluator().evaluateHeaders( + *downstream_headers_, {downstream_info_.getRequestHeaders()}, downstream_info_); +} + +void CombinedUpstream::setRouterUpstreamRequest( + Router::UpstreamRequestPtr router_upstream_request) { + LinkedList::moveIntoList(std::move(router_upstream_request), upstream_requests_); +} + +void CombinedUpstream::newStream(GenericConnectionPoolCallbacks&) { + upstream_requests_.front()->acceptHeadersFromRouter(false); +} + +void CombinedUpstream::encodeData(Buffer::Instance& data, bool end_stream) { + if (upstream_requests_.empty()) { + return; + } + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + if (end_stream) { + doneWriting(); + } +} + +bool CombinedUpstream::readDisable(bool disable) { + if (upstream_requests_.empty()) { + return false; + } + if (disable) { + upstream_requests_.front()->onAboveWriteBufferHighWatermark(); + } + return true; +} + +Tcp::ConnectionPool::ConnectionData* +CombinedUpstream::onDownstreamEvent(Network::ConnectionEvent event) { + if (upstream_requests_.empty()) { + return nullptr; + } + + if (event == Network::ConnectionEvent::LocalClose || + event == Network::ConnectionEvent::RemoteClose) { + upstream_requests_.front()->resetStream(); + } + return nullptr; +} + +bool CombinedUpstream::isValidResponse(const Http::ResponseHeaderMap& headers) { + switch (parent_.codecType()) { + case Http::CodecType::HTTP1: + // According to RFC7231 any 2xx response indicates that the connection is + // established. + // Any 'Content-Length' or 'Transfer-Encoding' header fields MUST be ignored. + // https://tools.ietf.org/html/rfc7231#section-4.3.6 + return Http::CodeUtility::is2xx(Http::Utility::getResponseStatus(headers)); + case Http::CodecType::HTTP2: + case Http::CodecType::HTTP3: + if (Http::Utility::getResponseStatus(headers) != 200) { + return false; + } + return true; + } + return true; +} + +void CombinedUpstream::onResetEncoder(Network::ConnectionEvent event, bool inform_downstream) { + if (event == Network::ConnectionEvent::LocalClose || + event == Network::ConnectionEvent::RemoteClose) { + if (!upstream_requests_.empty()) { + upstream_requests_.front()->resetStream(); + } + } + + // If we did not receive a valid CONNECT response yet we treat this as a pool + // failure, otherwise we forward the event downstream. + if (conn_pool_callbacks_ != nullptr) { + conn_pool_callbacks_->onFailure(); + return; + } + + if (inform_downstream) { + upstream_callbacks_.onEvent(event); + } +} + +// Router::RouterFilterInterface +void CombinedUpstream::onUpstreamHeaders([[maybe_unused]] uint64_t response_code, + Http::ResponseHeaderMapPtr&& headers, + [[maybe_unused]] UpstreamRequest& upstream_request, + bool end_stream) { + responseDecoder().decodeHeaders(std::move(headers), end_stream); +} + +void CombinedUpstream::onUpstreamData(Buffer::Instance& data, + [[maybe_unused]] UpstreamRequest& upstream_request, + bool end_stream) { + responseDecoder().decodeData(data, end_stream); +} + +void CombinedUpstream::onUpstreamTrailers(Http::ResponseTrailerMapPtr&& trailers, + UpstreamRequest&) { + responseDecoder().decodeTrailers(std::move(trailers)); +} + +Http::RequestHeaderMap* CombinedUpstream::downstreamHeaders() { return downstream_headers_.get(); } + +void CombinedUpstream::doneReading() { + read_half_closed_ = true; + if (write_half_closed_) { + onResetEncoder(Network::ConnectionEvent::LocalClose); + } +} + +void CombinedUpstream::onUpstreamReset(Http::StreamResetReason, absl::string_view, + UpstreamRequest&) { + upstream_callbacks_.onEvent(Network::ConnectionEvent::RemoteClose); +} + +void CombinedUpstream::doneWriting() { + write_half_closed_ = true; + if (read_half_closed_) { + onResetEncoder(Network::ConnectionEvent::LocalClose); + } +} + +void CombinedUpstream::onResetStream(Http::StreamResetReason, absl::string_view) { + read_half_closed_ = true; + write_half_closed_ = true; + onResetEncoder(Network::ConnectionEvent::LocalClose); +} + +void CombinedUpstream::onAboveWriteBufferHighWatermark() { + upstream_callbacks_.onAboveWriteBufferHighWatermark(); +} + +void CombinedUpstream::onBelowWriteBufferLowWatermark() { + upstream_callbacks_.onBelowWriteBufferLowWatermark(); +} + } // namespace TcpProxy } // namespace Envoy diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index d16126547cc5..23af532b37cb 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -1,7 +1,11 @@ #pragma once +#include + #include "envoy/http/conn_pool.h" +#include "envoy/http/header_map.h" #include "envoy/network/connection.h" +#include "envoy/router/router_ratelimit.h" #include "envoy/tcp/conn_pool.h" #include "envoy/tcp/upstream.h" #include "envoy/upstream/load_balancer.h" @@ -11,7 +15,13 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/dump_state_utils.h" #include "source/common/http/codec_client.h" +#include "source/common/http/hash_policy.h" +#include "source/common/http/null_route_impl.h" +#include "source/common/network/utility.h" +#include "source/common/router/config_impl.h" #include "source/common/router/header_parser.h" +#include "source/common/router/router.h" +#include "source/extensions/early_data/default_early_data_policy.h" namespace Envoy { namespace TcpProxy { @@ -47,16 +57,25 @@ class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callback }; class HttpUpstream; +class CombinedUpstream; class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callbacks { public: HttpConnPool(Upstream::ThreadLocalCluster& thread_local_cluster, Upstream::LoadBalancerContext* context, const TunnelingConfigHelper& config, - Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, Http::CodecType type, + Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, + Http::StreamDecoderFilterCallbacks&, Http::CodecType type, StreamInfo::StreamInfo& downstream_info); + + using RouterUpstreamRequest = Router::UpstreamRequest; + using RouterUpstreamRequestPtr = std::unique_ptr; ~HttpConnPool() override; - bool valid() const { return conn_pool_data_.has_value(); } + bool valid() const { return conn_pool_data_.has_value() || generic_conn_pool_; } + Http::CodecType codecType() const { return type_; } + std::unique_ptr createConnPool(Upstream::ThreadLocalCluster&, + Upstream::LoadBalancerContext* context, + absl::optional protocol); // GenericConnPool void newStream(GenericConnectionPoolCallbacks& callbacks) override; @@ -69,16 +88,32 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba Upstream::HostDescriptionConstSharedPtr host, StreamInfo::StreamInfo& info, absl::optional) override; + void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr, bool); + void onHttpPoolReady(Upstream::HostDescriptionConstSharedPtr& host, + Ssl::ConnectionInfoConstSharedPtr ssl_info); + class Callbacks { public: Callbacks(HttpConnPool& conn_pool, Upstream::HostDescriptionConstSharedPtr host, Ssl::ConnectionInfoConstSharedPtr ssl_info) : conn_pool_(&conn_pool), host_(host), ssl_info_(ssl_info) {} virtual ~Callbacks() = default; - virtual void onSuccess(Http::RequestEncoder& request_encoder) { + virtual void onSuccess(Http::RequestEncoder* request_encoder) { ASSERT(conn_pool_ != nullptr); - conn_pool_->onGenericPoolReady(host_, request_encoder.getStream().connectionInfoProvider(), - ssl_info_); + if (!Runtime::runtimeFeatureEnabled( + "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { + ASSERT(request_encoder != nullptr); + conn_pool_->onGenericPoolReady(host_, request_encoder->getStream().connectionInfoProvider(), + ssl_info_); + return; + } + + Network::ConnectionInfoProviderSharedPtr local_connection_info_provider( + std::make_shared( + Network::Utility::getCanonicalIpv4LoopbackAddress(), + Network::Utility::getCanonicalIpv4LoopbackAddress())); + + conn_pool_->onGenericPoolReady(host_, *local_connection_info_provider.get(), ssl_info_); } virtual void onFailure() { ASSERT(conn_pool_ != nullptr); @@ -104,9 +139,12 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba absl::optional conn_pool_data_{}; Http::ConnectionPool::Cancellable* upstream_handle_{}; GenericConnectionPoolCallbacks* callbacks_{}; + Http::StreamDecoderFilterCallbacks* decoder_filter_callbacks_; Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks_; std::unique_ptr upstream_; + std::unique_ptr combined_upstream_; StreamInfo::StreamInfo& downstream_info_; + std::unique_ptr generic_conn_pool_; }; class TcpUpstream : public GenericUpstream { @@ -130,6 +168,7 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { public: using TunnelingConfig = envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; + HttpUpstream(Tcp::ConnectionPool::UpstreamCallbacks& callbacks, const TunnelingConfigHelper& config, StreamInfo::StreamInfo& downstream_info, Http::CodecType type); @@ -155,7 +194,7 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { void onAboveWriteBufferHighWatermark() override; void onBelowWriteBufferLowWatermark() override; - void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); + virtual void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); void setConnPoolCallbacks(std::unique_ptr&& callbacks) { conn_pool_callbacks_ = std::move(callbacks); } @@ -170,12 +209,13 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { const TunnelingConfigHelper& config_; // The downstream info that is owned by the downstream connection. StreamInfo::StreamInfo& downstream_info_; + std::unique_ptr downstream_headers_; private: + Upstream::ClusterInfoConstSharedPtr cluster_; class DecoderShim : public Http::ResponseDecoder { public: DecoderShim(HttpUpstream& parent) : parent_(parent) {} - // Http::ResponseDecoder void decode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} void decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override { bool is_valid_response = parent_.isValidResponse(*headers); @@ -184,7 +224,7 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { if (!is_valid_response || end_stream) { parent_.resetEncoder(Network::ConnectionEvent::LocalClose); } else if (parent_.conn_pool_callbacks_ != nullptr) { - parent_.conn_pool_callbacks_->onSuccess(*parent_.request_encoder_); + parent_.conn_pool_callbacks_->onSuccess(parent_.request_encoder_); parent_.conn_pool_callbacks_.reset(); } } @@ -224,5 +264,133 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { std::unique_ptr conn_pool_callbacks_; }; +class CombinedUpstream : public GenericUpstream, + protected Http::StreamCallbacks, + public Envoy::Router::RouterFilterInterface { +public: + CombinedUpstream(HttpConnPool& http_conn_pool, Tcp::ConnectionPool::UpstreamCallbacks& callbacks, + Http::StreamDecoderFilterCallbacks& decoder_callbacks, + const TunnelingConfigHelper& config, StreamInfo::StreamInfo& downstream_info); + ~CombinedUpstream() override = default; + using UpstreamRequest = Router::UpstreamRequest; + Http::ResponseDecoder& responseDecoder() { return response_decoder_; } + void doneReading(); + void doneWriting(); + using UpstreamRequestPtr = std::unique_ptr; + void setRouterUpstreamRequest(UpstreamRequestPtr); + void newStream(GenericConnectionPoolCallbacks& callbacks); + void encodeData(Buffer::Instance& data, bool end_stream) override; + Tcp::ConnectionPool::ConnectionData* onDownstreamEvent(Network::ConnectionEvent event) override; + bool isValidResponse(const Http::ResponseHeaderMap&); + bool readDisable(bool disable) override; + void setConnPoolCallbacks(std::unique_ptr&& callbacks) { + conn_pool_callbacks_ = std::move(callbacks); + } + void addBytesSentCallback(Network::Connection::BytesSentCb) override{}; + // HTTP upstream must not implement converting upstream transport + // socket from non-secure to secure mode. + bool startUpstreamSecureTransport() override { return false; } + Ssl::ConnectionInfoConstSharedPtr getUpstreamConnectionSslInfo() override { return nullptr; } + + // Http::StreamCallbacks + void onResetStream(Http::StreamResetReason reason, + absl::string_view transport_failure_reason) override; + void onAboveWriteBufferHighWatermark() override; + void onBelowWriteBufferLowWatermark() override; + + // Router::RouterFilterInterface + void onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPtr&& headers, + UpstreamRequest& upstream_request, bool end_stream) override; + void onUpstreamData(Buffer::Instance& data, UpstreamRequest& upstream_request, + bool end_stream) override; + void onUpstream1xxHeaders(Http::ResponseHeaderMapPtr&&, UpstreamRequest&) override {} + void onUpstreamTrailers(Http::ResponseTrailerMapPtr&&, UpstreamRequest&) override; + void onUpstreamMetadata(Http::MetadataMapPtr&&) override {} + void onUpstreamReset(Http::StreamResetReason stream_reset_reason, + absl::string_view transport_failure_reason, UpstreamRequest&) override; + void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host, + bool pool_success) override { + parent_.onUpstreamHostSelected(host, pool_success); + } + void onPerTryTimeout(UpstreamRequest&) override {} + void onPerTryIdleTimeout(UpstreamRequest&) override {} + void onStreamMaxDurationReached(UpstreamRequest&) override {} + Http::StreamDecoderFilterCallbacks* callbacks() override { return &decoder_filter_callbacks_; } + Upstream::ClusterInfoConstSharedPtr cluster() override { + return decoder_filter_callbacks_.clusterInfo(); + } + Router::FilterConfig& config() override { + return const_cast(config_.routerFilterConfig()); + } + Router::TimeoutData timeout() override { return {}; } + absl::optional dynamicMaxStreamDuration() const override { + return absl::nullopt; + } + Http::RequestHeaderMap* downstreamHeaders() override; + Http::RequestTrailerMap* downstreamTrailers() override { return nullptr; } + bool downstreamResponseStarted() const override { return false; } + bool downstreamEndStream() const override { return false; } + uint32_t attemptCount() const override { return 0; } + +protected: + void onResetEncoder(Network::ConnectionEvent event, bool inform_downstream = true); + + // The config object that is owned by the downstream network filter chain factory. + const TunnelingConfigHelper& config_; + // The downstream info that is owned by the downstream connection. + StreamInfo::StreamInfo& downstream_info_; + std::list upstream_requests_; + std::unique_ptr downstream_headers_; + HttpConnPool& parent_; + +private: + Http::StreamDecoderFilterCallbacks& decoder_filter_callbacks_; + class DecoderShim : public Http::ResponseDecoder { + public: + DecoderShim(CombinedUpstream& parent) : parent_(parent) {} + // Http::ResponseDecoder + void decode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} + void decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override { + bool is_valid_response = parent_.isValidResponse(*headers); + parent_.config_.propagateResponseHeaders(std::move(headers), + parent_.downstream_info_.filterState()); + if (!is_valid_response || end_stream) { + parent_.onResetEncoder(Network::ConnectionEvent::LocalClose); + } else if (parent_.conn_pool_callbacks_ != nullptr) { + parent_.conn_pool_callbacks_->onSuccess(nullptr /*parent_.request_encoder_*/); + parent_.conn_pool_callbacks_.reset(); + } + } + void decodeData(Buffer::Instance& data, bool end_stream) override { + parent_.upstream_callbacks_.onUpstreamData(data, end_stream); + if (end_stream) { + parent_.doneReading(); + } + } + void decodeTrailers(Http::ResponseTrailerMapPtr&& trailers) override { + parent_.config_.propagateResponseTrailers(std::move(trailers), + parent_.downstream_info_.filterState()); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers")) { + Buffer::OwnedImpl data; + parent_.upstream_callbacks_.onUpstreamData(data, /* end_stream = */ true); + } + parent_.doneReading(); + } + void decodeMetadata(Http::MetadataMapPtr&&) override {} + void dumpState(std::ostream& os, int indent_level) const override { + DUMP_STATE_UNIMPLEMENTED(DecoderShim); + } + + private: + CombinedUpstream& parent_; + }; + DecoderShim response_decoder_; + Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks_; + std::unique_ptr conn_pool_callbacks_; + bool read_half_closed_{}; + bool write_half_closed_{}; +}; + } // namespace TcpProxy } // namespace Envoy diff --git a/source/extensions/upstreams/http/http/upstream_request.h b/source/extensions/upstreams/http/http/upstream_request.h index d7b29d7c8d53..db8adfc2cc14 100644 --- a/source/extensions/upstreams/http/http/upstream_request.h +++ b/source/extensions/upstreams/http/http/upstream_request.h @@ -73,6 +73,7 @@ class HttpUpstream : public Router::GenericUpstream, public Envoy::Http::StreamC void encodeTrailers(const Envoy::Http::RequestTrailerMap& trailers) override { request_encoder_->encodeTrailers(trailers); } + void enableHalfClose() override { request_encoder_->enableTcpTunneling(); } void readDisable(bool disable) override { request_encoder_->getStream().readDisable(disable); } diff --git a/source/extensions/upstreams/http/tcp/upstream_request.h b/source/extensions/upstreams/http/tcp/upstream_request.h index 87e9431fbd30..8c7431250c1e 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.h +++ b/source/extensions/upstreams/http/tcp/upstream_request.h @@ -72,6 +72,7 @@ class TcpUpstream : public Router::GenericUpstream, void encodeMetadata(const Envoy::Http::MetadataMapVector&) override {} Envoy::Http::Status encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) override; void encodeTrailers(const Envoy::Http::RequestTrailerMap&) override; + void enableHalfClose() override {} void readDisable(bool disable) override; void resetStream() override; void setAccount(Buffer::BufferMemoryAccountSharedPtr) override {} diff --git a/source/extensions/upstreams/http/udp/upstream_request.h b/source/extensions/upstreams/http/udp/upstream_request.h index 4d4a132411e1..b5f6e25e6540 100644 --- a/source/extensions/upstreams/http/udp/upstream_request.h +++ b/source/extensions/upstreams/http/udp/upstream_request.h @@ -77,6 +77,7 @@ class UdpUpstream : public Router::GenericUpstream, void encodeTrailers(const Envoy::Http::RequestTrailerMap&) override {} void readDisable(bool) override {} void resetStream() override; + void enableHalfClose() override {} void setAccount(Buffer::BufferMemoryAccountSharedPtr) override {} const StreamInfo::BytesMeterSharedPtr& bytesMeter() override { return bytes_meter_; } diff --git a/source/extensions/upstreams/tcp/generic/BUILD b/source/extensions/upstreams/tcp/generic/BUILD index a29fa7133934..ea02233167e3 100644 --- a/source/extensions/upstreams/tcp/generic/BUILD +++ b/source/extensions/upstreams/tcp/generic/BUILD @@ -18,6 +18,7 @@ envoy_cc_extension( ], visibility = ["//visibility:public"], deps = [ + "//envoy/http:filter_interface", "//envoy/stream_info:bool_accessor_interface", "//envoy/stream_info:filter_state_interface", "//source/common/http:codec_client_lib", diff --git a/source/extensions/upstreams/tcp/generic/config.cc b/source/extensions/upstreams/tcp/generic/config.cc index e688ab84a510..d3e33fdb83f8 100644 --- a/source/extensions/upstreams/tcp/generic/config.cc +++ b/source/extensions/upstreams/tcp/generic/config.cc @@ -18,6 +18,7 @@ TcpProxy::GenericConnPoolPtr GenericConnPoolFactory::createGenericConnPool( Upstream::ThreadLocalCluster& thread_local_cluster, TcpProxy::TunnelingConfigHelperOptConstRef config, Upstream::LoadBalancerContext* context, Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, + Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, StreamInfo::StreamInfo& downstream_info) const { if (config.has_value() && !disableTunnelingByFilterState(downstream_info)) { Http::CodecType pool_type; @@ -30,7 +31,8 @@ TcpProxy::GenericConnPoolPtr GenericConnPoolFactory::createGenericConnPool( pool_type = Http::CodecType::HTTP1; } auto ret = std::make_unique( - thread_local_cluster, context, *config, upstream_callbacks, pool_type, downstream_info); + thread_local_cluster, context, *config, upstream_callbacks, stream_decoder_callbacks, + pool_type, downstream_info); return (ret->valid() ? std::move(ret) : nullptr); } auto ret = std::make_unique(thread_local_cluster, context, diff --git a/source/extensions/upstreams/tcp/generic/config.h b/source/extensions/upstreams/tcp/generic/config.h index c4fee935ef96..e7e87a0145c3 100644 --- a/source/extensions/upstreams/tcp/generic/config.h +++ b/source/extensions/upstreams/tcp/generic/config.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.pb.h" +#include "envoy/http/filter.h" #include "envoy/registry/registry.h" #include "envoy/tcp/upstream.h" @@ -22,6 +23,7 @@ class GenericConnPoolFactory : public TcpProxy::GenericConnPoolFactory { TcpProxy::TunnelingConfigHelperOptConstRef config, Upstream::LoadBalancerContext* context, Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, + Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, StreamInfo::StreamInfo& downstream_info) const override; ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/test/common/router/upstream_request_test.cc b/test/common/router/upstream_request_test.cc index 67a6b003cd36..bb9a8185d4b4 100644 --- a/test/common/router/upstream_request_test.cc +++ b/test/common/router/upstream_request_test.cc @@ -33,8 +33,9 @@ class UpstreamRequestTest : public testing::Test { void initialize() { auto conn_pool = std::make_unique>(); conn_pool_ = conn_pool.get(); - upstream_request_ = std::make_unique(router_filter_interface_, - std::move(conn_pool), false, true); + upstream_request_ = + std::make_unique(router_filter_interface_, std::move(conn_pool), false, + true, false /*enable_tcp_tunneling*/); } Http::FilterFactoryCb createDecoderFilterFactoryCb(Http::StreamDecoderFilterSharedPtr filter) { return [filter](Http::FilterChainFactoryCallbacks& callbacks) { diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index 0630b106fd5c..d6c47b8a9cbc 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -79,9 +79,13 @@ envoy_cc_test( deps = [ "//source/common/tcp_proxy", "//test/mocks/http:http_mocks", + "//test/mocks/router:router_filter_interface", + "//test/mocks/router:upstream_request", "//test/mocks/server:factory_context_mocks", + "//test/mocks/stats:stats_mocks", "//test/mocks/tcp:tcp_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "//test/mocks/upstream:load_balancer_context_mock", "//test/test_common:test_runtime_lib", ], ) diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 41ad163dba41..144e50f2fbbc 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -171,7 +171,7 @@ class TcpProxyTest : public TcpProxyTestBase { std::shared_ptr> mock_access_logger_; }; -TEST_F(TcpProxyTest, ExplicitCluster) { +TEST_P(TcpProxyTest, ExplicitCluster) { configure(defaultConfig()); NiceMock connection; @@ -179,7 +179,7 @@ TEST_F(TcpProxyTest, ExplicitCluster) { } // Tests that half-closes are proxied and don't themselves cause any connection to be closed. -TEST_F(TcpProxyTest, HalfCloseProxy) { +TEST_P(TcpProxyTest, HalfCloseProxy) { setup(1); EXPECT_CALL(filter_callbacks_.connection_, close(_)).Times(0); @@ -200,7 +200,7 @@ TEST_F(TcpProxyTest, HalfCloseProxy) { } // Test with an explicitly configured upstream. -TEST_F(TcpProxyTest, ExplicitFactory) { +TEST_P(TcpProxyTest, ExplicitFactory) { // Explicitly configure an HTTP upstream, to test factory creation. auto& info = factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ .cluster_.info_; @@ -224,7 +224,7 @@ TEST_F(TcpProxyTest, ExplicitFactory) { } // Test nothing bad happens if an invalid factory is configured. -TEST_F(TcpProxyTest, BadFactory) { +TEST_P(TcpProxyTest, BadFactory) { auto& info = factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ .cluster_.info_; info->upstream_config_ = std::make_unique(); @@ -262,7 +262,7 @@ TEST_F(TcpProxyTest, BadFactory) { } // Test that downstream is closed after an upstream LocalClose. -TEST_F(TcpProxyTest, UpstreamLocalDisconnect) { +TEST_P(TcpProxyTest, UpstreamLocalDisconnect) { setup(1); raiseEventUpstreamConnected(0); @@ -280,7 +280,7 @@ TEST_F(TcpProxyTest, UpstreamLocalDisconnect) { } // Test that downstream is closed after an upstream RemoteClose. -TEST_F(TcpProxyTest, UpstreamRemoteDisconnect) { +TEST_P(TcpProxyTest, UpstreamRemoteDisconnect) { setup(1); timeSystem().advanceTimeWait(std::chrono::microseconds(20)); @@ -304,7 +304,7 @@ TEST_F(TcpProxyTest, UpstreamRemoteDisconnect) { } // Test that reconnect is attempted after a local connect failure -TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { +TEST_P(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -326,7 +326,7 @@ TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { } // Make sure that the tcp proxy code handles reentrant calls to onPoolFailure. -TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { +TEST_P(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -350,7 +350,7 @@ TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { } // Test that reconnect is attempted after a remote connect failure -TEST_F(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { +TEST_P(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -364,7 +364,7 @@ TEST_F(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { } // Test that reconnect is attempted after a connect timeout. -TEST_F(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { +TEST_P(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -378,7 +378,7 @@ TEST_F(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { } // Test that only the configured number of connect attempts occur -TEST_F(TcpProxyTest, ConnectAttemptsLimit) { +TEST_P(TcpProxyTest, ConnectAttemptsLimit) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = accessLogConfig("%RESPONSE_FLAGS%"); config.mutable_max_connect_attempts()->set_value(3); @@ -409,7 +409,7 @@ TEST_F(TcpProxyTest, ConnectAttemptsLimit) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, ConnectedNoOp) { +TEST_P(TcpProxyTest, ConnectedNoOp) { setup(1); raiseEventUpstreamConnected(0); @@ -419,7 +419,7 @@ TEST_F(TcpProxyTest, ConnectedNoOp) { } // Test that the tcp proxy sends the correct notifications to the outlier detector -TEST_F(TcpProxyTest, OutlierDetection) { +TEST_P(TcpProxyTest, OutlierDetection) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(3); setup(3, config); @@ -437,7 +437,7 @@ TEST_F(TcpProxyTest, OutlierDetection) { raiseEventUpstreamConnected(2); } -TEST_F(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { +TEST_P(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { setup(1); raiseEventUpstreamConnected(0); @@ -459,7 +459,7 @@ TEST_F(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { filter_callbacks_.connection_.runLowWatermarkCallbacks(); } -TEST_F(TcpProxyTest, DownstreamDisconnectRemote) { +TEST_P(TcpProxyTest, DownstreamDisconnectRemote) { setup(1); raiseEventUpstreamConnected(0); @@ -476,7 +476,7 @@ TEST_F(TcpProxyTest, DownstreamDisconnectRemote) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } -TEST_F(TcpProxyTest, DownstreamDisconnectLocal) { +TEST_P(TcpProxyTest, DownstreamDisconnectLocal) { setup(1); raiseEventUpstreamConnected(0); @@ -493,7 +493,7 @@ TEST_F(TcpProxyTest, DownstreamDisconnectLocal) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_F(TcpProxyTest, UpstreamConnectTimeout) { +TEST_P(TcpProxyTest, UpstreamConnectTimeout) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); @@ -503,7 +503,7 @@ TEST_F(TcpProxyTest, UpstreamConnectTimeout) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, UpstreamClusterNotFound) { +TEST_P(TcpProxyTest, UpstreamClusterNotFound) { setup(0, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) @@ -514,7 +514,7 @@ TEST_F(TcpProxyTest, UpstreamClusterNotFound) { EXPECT_EQ(access_log_data_.value(), "NC"); } -TEST_F(TcpProxyTest, NoHost) { +TEST_P(TcpProxyTest, NoHost) { EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); setup(0, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onNewConnection()); @@ -522,7 +522,78 @@ TEST_F(TcpProxyTest, NoHost) { EXPECT_EQ(access_log_data_, "UH"); } -TEST_F(TcpProxyTest, RouteWithMetadataMatch) { +// Tests StreamDecoderFilterCallbacks interface implementation +TEST_P(TcpProxyTest, StreamDecoderFilterCallbacks) { + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = + accessLogConfig("%RESPONSE_FLAGS%"); + config.mutable_tunneling_config()->set_hostname("www.example.com"); + configure(config); + NiceMock thread_local_cluster_; + auto cluster_info = std::make_shared>(); + // EXPECT_CALL(factory_context_.serverFactoryContext().clusterManager(), getThreadLocalCluster(_)) + // .WillRepeatedly(Return(&thread_local_cluster_)); + EXPECT_CALL(thread_local_cluster_, info()).WillRepeatedly(Return(cluster_info)); + filter_ = + std::make_unique(config_, factory_context_.serverFactoryContext().clusterManager()); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + auto stream_decoder_callbacks = Filter::HttpStreamDecoderFilterCallbacks(filter_.get()); + EXPECT_NO_THROW(stream_decoder_callbacks.streamId()); + EXPECT_NO_THROW(stream_decoder_callbacks.connection()); + EXPECT_NO_THROW(stream_decoder_callbacks.dispatcher()); + EXPECT_ENVOY_BUG( + { stream_decoder_callbacks.resetStream(Http::StreamResetReason::RemoteReset, ""); }, + "Not implemented"); + EXPECT_NO_THROW(stream_decoder_callbacks.streamInfo()); + EXPECT_NO_THROW(stream_decoder_callbacks.scope()); + EXPECT_NO_THROW(stream_decoder_callbacks.route()); + EXPECT_NO_THROW(stream_decoder_callbacks.continueDecoding()); + EXPECT_NO_THROW(stream_decoder_callbacks.requestHeaders()); + EXPECT_NO_THROW(stream_decoder_callbacks.requestTrailers()); + EXPECT_NO_THROW(stream_decoder_callbacks.responseHeaders()); + EXPECT_NO_THROW(stream_decoder_callbacks.responseTrailers()); + EXPECT_NO_THROW(stream_decoder_callbacks.encodeMetadata(nullptr)); + EXPECT_NO_THROW(stream_decoder_callbacks.onDecoderFilterAboveWriteBufferHighWatermark()); + EXPECT_NO_THROW(stream_decoder_callbacks.onDecoderFilterBelowWriteBufferLowWatermark()); + EXPECT_NO_THROW(stream_decoder_callbacks.setDecoderBufferLimit(uint32_t{0})); + EXPECT_NO_THROW(stream_decoder_callbacks.decoderBufferLimit()); + EXPECT_NO_THROW(stream_decoder_callbacks.recreateStream(nullptr)); + EXPECT_NO_THROW(stream_decoder_callbacks.getUpstreamSocketOptions()); + Network::Socket::OptionsSharedPtr sock_options = + Network::SocketOptionFactory::buildIpTransparentOptions(); + EXPECT_NO_THROW(stream_decoder_callbacks.addUpstreamSocketOptions(sock_options)); + EXPECT_NO_THROW(stream_decoder_callbacks.mostSpecificPerFilterConfig()); + EXPECT_NO_THROW(stream_decoder_callbacks.account()); + EXPECT_NO_THROW(stream_decoder_callbacks.setUpstreamOverrideHost( + Upstream::LoadBalancerContext::OverrideHost(std::make_pair("foo", true)))); + EXPECT_NO_THROW(stream_decoder_callbacks.http1StreamEncoderOptions()); + EXPECT_NO_THROW(stream_decoder_callbacks.downstreamCallbacks()); + EXPECT_NO_THROW(stream_decoder_callbacks.upstreamCallbacks()); + EXPECT_NO_THROW(stream_decoder_callbacks.upstreamOverrideHost()); + EXPECT_NO_THROW(stream_decoder_callbacks.resetIdleTimer()); + EXPECT_NO_THROW(stream_decoder_callbacks.filterConfigName()); + EXPECT_NO_THROW(stream_decoder_callbacks.activeSpan()); + EXPECT_NO_THROW(stream_decoder_callbacks.tracingConfig()); + Buffer::OwnedImpl inject_data; + EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedData(inject_data, false)); + EXPECT_NO_THROW(stream_decoder_callbacks.injectDecodedDataToFilterChain(inject_data, false)); + EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedData(inject_data, false)); + EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedTrailers()); + EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedMetadata()); + EXPECT_NO_THROW(stream_decoder_callbacks.decodingBuffer()); + auto func = [](Buffer::Instance&) {}; + EXPECT_NO_THROW(stream_decoder_callbacks.modifyDecodingBuffer(func)); + EXPECT_NO_THROW(stream_decoder_callbacks.encode1xxHeaders(nullptr)); + EXPECT_NO_THROW(stream_decoder_callbacks.informationalHeaders()); + EXPECT_NO_THROW(stream_decoder_callbacks.encodeHeaders(nullptr, false, "")); + EXPECT_NO_THROW(stream_decoder_callbacks.encodeData(inject_data, false)); + EXPECT_NO_THROW(stream_decoder_callbacks.encodeTrailers(nullptr)); + EXPECT_NO_THROW(stream_decoder_callbacks.setDecoderBufferLimit(0)); + std::array buffer; + OutputBufferStream ostream{buffer.data(), buffer.size()}; + EXPECT_NO_THROW(stream_decoder_callbacks.dumpState(ostream, 0)); +} + +TEST_P(TcpProxyTest, RouteWithMetadataMatch) { auto v1 = ProtobufWkt::Value(); v1.set_string_value("v1"); auto v2 = ProtobufWkt::Value(); @@ -562,7 +633,7 @@ TEST_F(TcpProxyTest, RouteWithMetadataMatch) { // Tests that the endpoint selector of a weighted cluster gets included into the // LoadBalancerContext. -TEST_F(TcpProxyTest, WeightedClusterWithMetadataMatch) { +TEST_P(TcpProxyTest, WeightedClusterWithMetadataMatch) { const std::string yaml = R"EOF( stat_prefix: name weighted_clusters: @@ -659,7 +730,7 @@ TEST_F(TcpProxyTest, WeightedClusterWithMetadataMatch) { } // Test that metadata match criteria provided on the StreamInfo is used. -TEST_F(TcpProxyTest, StreamInfoDynamicMetadata) { +TEST_P(TcpProxyTest, StreamInfoDynamicMetadata) { configure(defaultConfig()); ProtobufWkt::Value val; @@ -697,7 +768,7 @@ TEST_F(TcpProxyTest, StreamInfoDynamicMetadata) { // Test that if both streamInfo and configuration add metadata match criteria, they // are merged. -TEST_F(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { +TEST_P(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { const std::string yaml = R"EOF( stat_prefix: name weighted_clusters: @@ -758,7 +829,7 @@ TEST_F(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { EXPECT_EQ(hv2, effective_criterions[2]->value()); } -TEST_F(TcpProxyTest, DisconnectBeforeData) { +TEST_P(TcpProxyTest, DisconnectBeforeData) { configure(defaultConfig()); filter_ = std::make_unique(config_, factory_context_.server_factory_context_.cluster_manager_); @@ -769,7 +840,7 @@ TEST_F(TcpProxyTest, DisconnectBeforeData) { // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_F(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { +TEST_P(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -777,13 +848,13 @@ TEST_F(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_F(TcpProxyTest, LocalClosedBeforeUpstreamConnected) { +TEST_P(TcpProxyTest, LocalClosedBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_F(TcpProxyTest, UpstreamConnectFailure) { +TEST_P(TcpProxyTest, UpstreamConnectFailure) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); @@ -799,7 +870,7 @@ TEST_F(TcpProxyTest, UpstreamConnectFailure) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, UpstreamConnectionLimit) { +TEST_P(TcpProxyTest, UpstreamConnectionLimit) { configure(accessLogConfig("%RESPONSE_FLAGS%")); factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_ ->resetResourceManager(0, 0, 0, 0, 0); @@ -816,7 +887,7 @@ TEST_F(TcpProxyTest, UpstreamConnectionLimit) { EXPECT_EQ(access_log_data_, "UO"); } -TEST_F(TcpProxyTest, IdleTimeoutObjectFactory) { +TEST_P(TcpProxyTest, IdleTimeoutObjectFactory) { const std::string name = "envoy.tcp_proxy.per_connection_idle_timeout_ms"; auto* factory = Registry::FactoryRegistry::getFactory(name); @@ -828,7 +899,7 @@ TEST_F(TcpProxyTest, IdleTimeoutObjectFactory) { EXPECT_EQ(duration_in_milliseconds, object->serializeAsString()); } -TEST_F(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { +TEST_P(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { const std::string name = "envoy.tcp_proxy.per_connection_idle_timeout_ms"; auto* factory = Registry::FactoryRegistry::getFactory(name); @@ -837,7 +908,7 @@ TEST_F(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { ASSERT_EQ(nullptr, factory->createFromBytes("not_a_number")); } -TEST_F(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { +TEST_P(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -877,7 +948,7 @@ TEST_F(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { // Tests that the idle timer closes both connections, and gets updated when either // connection has activity. -TEST_F(TcpProxyTest, IdleTimeout) { +TEST_P(TcpProxyTest, IdleTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -907,7 +978,7 @@ TEST_F(TcpProxyTest, IdleTimeout) { } // Tests that the idle timer is disabled when the downstream connection is closed. -TEST_F(TcpProxyTest, IdleTimerDisabledDownstreamClose) { +TEST_P(TcpProxyTest, IdleTimerDisabledDownstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -921,7 +992,7 @@ TEST_F(TcpProxyTest, IdleTimerDisabledDownstreamClose) { } // Tests that the idle timer is disabled when the upstream connection is closed. -TEST_F(TcpProxyTest, IdleTimerDisabledUpstreamClose) { +TEST_P(TcpProxyTest, IdleTimerDisabledUpstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -935,7 +1006,7 @@ TEST_F(TcpProxyTest, IdleTimerDisabledUpstreamClose) { } // Tests that flushing data during an idle timeout doesn't cause problems. -TEST_F(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { +TEST_P(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -987,7 +1058,7 @@ TEST_F(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { // Checks that %UPSTREAM_WIRE_BYTES_SENT%, %UPSTREAM_WIRE_BYTES_RECEIVED%, // %DOWNSTREAM_WIRE_BYTES_SENT%, and %DOWNSTREAM_WIRE_BYTES_RECEIVED% are // correctly logged. -TEST_F(TcpProxyTest, AccessLogBytesMeterData) { +TEST_P(TcpProxyTest, AccessLogBytesMeterData) { setup(1, accessLogConfig("%UPSTREAM_WIRE_BYTES_SENT% %UPSTREAM_WIRE_BYTES_RECEIVED% " "%DOWNSTREAM_WIRE_BYTES_SENT% %DOWNSTREAM_WIRE_BYTES_RECEIVED%")); raiseEventUpstreamConnected(0); @@ -1005,7 +1076,7 @@ TEST_F(TcpProxyTest, AccessLogBytesMeterData) { // Test that access log fields %UPSTREAM_HOST% and %UPSTREAM_CLUSTER% are correctly logged with the // observability name. -TEST_F(TcpProxyTest, AccessLogUpstreamHost) { +TEST_P(TcpProxyTest, AccessLogUpstreamHost) { setup(1, accessLogConfig("%UPSTREAM_HOST% %UPSTREAM_CLUSTER%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1014,7 +1085,7 @@ TEST_F(TcpProxyTest, AccessLogUpstreamHost) { } // Test that access log field %UPSTREAM_LOCAL_ADDRESS% is correctly logged. -TEST_F(TcpProxyTest, AccessLogUpstreamLocalAddress) { +TEST_P(TcpProxyTest, AccessLogUpstreamLocalAddress) { setup(1, accessLogConfig("%UPSTREAM_LOCAL_ADDRESS%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1023,7 +1094,7 @@ TEST_F(TcpProxyTest, AccessLogUpstreamLocalAddress) { } // Test that access log fields %DOWNSTREAM_PEER_URI_SAN% is correctly logged. -TEST_F(TcpProxyTest, AccessLogPeerUriSan) { +TEST_P(TcpProxyTest, AccessLogPeerUriSan) { filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( Network::Utility::resolveUrl("tcp://1.1.1.2:20000")); filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -1041,7 +1112,7 @@ TEST_F(TcpProxyTest, AccessLogPeerUriSan) { } // Test that access log fields %DOWNSTREAM_TLS_SESSION_ID% is correctly logged. -TEST_F(TcpProxyTest, AccessLogTlsSessionId) { +TEST_P(TcpProxyTest, AccessLogTlsSessionId) { filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( Network::Utility::resolveUrl("tcp://1.1.1.2:20000")); filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -1061,7 +1132,7 @@ TEST_F(TcpProxyTest, AccessLogTlsSessionId) { // Test that access log fields %DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% and // %DOWNSTREAM_LOCAL_ADDRESS% are correctly logged. -TEST_F(TcpProxyTest, AccessLogDownstreamAddress) { +TEST_P(TcpProxyTest, AccessLogDownstreamAddress) { filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( Network::Utility::resolveUrl("tcp://1.1.1.2:20000")); filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -1073,7 +1144,7 @@ TEST_F(TcpProxyTest, AccessLogDownstreamAddress) { } // Test that intermediate log entry by field %ACCESS_LOG_TYPE%. -TEST_F(TcpProxyTest, IntermediateLogEntry) { +TEST_P(TcpProxyTest, IntermediateLogEntry) { auto config = accessLogConfig("%ACCESS_LOG_TYPE%"); config.mutable_access_log_options()->mutable_access_log_flush_interval()->set_seconds(1); config.mutable_idle_timeout()->set_seconds(0); @@ -1129,7 +1200,7 @@ TEST_F(TcpProxyTest, IntermediateLogEntry) { AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionEnd)); } -TEST_F(TcpProxyTest, TestAccessLogOnUpstreamConnected) { +TEST_P(TcpProxyTest, TestAccessLogOnUpstreamConnected) { auto config = accessLogConfig("%UPSTREAM_HOST% %ACCESS_LOG_TYPE%"); config.mutable_access_log_options()->set_flush_access_log_on_connected(true); @@ -1151,7 +1222,7 @@ TEST_F(TcpProxyTest, TestAccessLogOnUpstreamConnected) { AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionEnd))); } -TEST_F(TcpProxyTest, AccessLogUpstreamSSLConnection) { +TEST_P(TcpProxyTest, AccessLogUpstreamSSLConnection) { setup(1); NiceMock stream_info; @@ -1168,7 +1239,7 @@ TEST_F(TcpProxyTest, AccessLogUpstreamSSLConnection) { } // Tests that upstream flush works properly with no idle timeout configured. -TEST_F(TcpProxyTest, UpstreamFlushNoTimeout) { +TEST_P(TcpProxyTest, UpstreamFlushNoTimeout) { setup(1); raiseEventUpstreamConnected(0); @@ -1192,7 +1263,7 @@ TEST_F(TcpProxyTest, UpstreamFlushNoTimeout) { // Tests that upstream flush works with an idle timeout configured, but the connection // finishes draining before the timer expires. -TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { +TEST_P(TcpProxyTest, UpstreamFlushTimeoutConfigured) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1223,7 +1294,7 @@ TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { } // Tests that upstream flush closes the connection when the idle timeout fires. -TEST_F(TcpProxyTest, UpstreamFlushTimeoutExpired) { +TEST_P(TcpProxyTest, UpstreamFlushTimeoutExpired) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1251,7 +1322,7 @@ TEST_F(TcpProxyTest, UpstreamFlushTimeoutExpired) { // Tests that upstream flush will close a connection if it reads data from the upstream // connection after the downstream connection is closed (nowhere to send it). -TEST_F(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { +TEST_P(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { setup(1); raiseEventUpstreamConnected(0); @@ -1270,13 +1341,13 @@ TEST_F(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { upstream_callbacks_->onUpstreamData(buffer, false); } -TEST_F(TcpProxyTest, UpstreamSocketOptionsReturnedEmpty) { +TEST_P(TcpProxyTest, UpstreamSocketOptionsReturnedEmpty) { setup(1); auto options = filter_->upstreamSocketOptions(); EXPECT_EQ(options, nullptr); } -TEST_F(TcpProxyTest, TcpProxySetRedirectRecordsToUpstream) { +TEST_P(TcpProxyTest, TcpProxySetRedirectRecordsToUpstream) { setup(1, true); EXPECT_TRUE(filter_->upstreamSocketOptions()); auto iterator = std::find_if( @@ -1296,7 +1367,7 @@ TEST_F(TcpProxyTest, TcpProxySetRedirectRecordsToUpstream) { } // Tests that downstream connection can access upstream connections filter state. -TEST_F(TcpProxyTest, ShareFilterState) { +TEST_P(TcpProxyTest, ShareFilterState) { setup(1); upstream_connections_.at(0)->streamInfo().filterState()->setData( @@ -1312,7 +1383,7 @@ TEST_F(TcpProxyTest, ShareFilterState) { } // Tests that filter callback can access downstream and upstream address and ssl properties. -TEST_F(TcpProxyTest, AccessDownstreamAndUpstreamProperties) { +TEST_P(TcpProxyTest, AccessDownstreamAndUpstreamProperties) { setup(1); raiseEventUpstreamConnected(0); @@ -1325,7 +1396,7 @@ TEST_F(TcpProxyTest, AccessDownstreamAndUpstreamProperties) { upstream_connections_.at(0)->streamInfo().downstreamAddressProvider().sslConnection()); } -TEST_F(TcpProxyTest, PickClusterOnUpstreamFailure) { +TEST_P(TcpProxyTest, PickClusterOnUpstreamFailure) { auto config = defaultConfig(); set2Cluster(config); config.mutable_max_connect_attempts()->set_value(2); @@ -1354,7 +1425,7 @@ TEST_F(TcpProxyTest, PickClusterOnUpstreamFailure) { } // Verify that odcds callback does not re-pick cluster. Upstream connect failure does. -TEST_F(TcpProxyTest, OnDemandCallbackStickToTheSelectedCluster) { +TEST_P(TcpProxyTest, OnDemandCallbackStickToTheSelectedCluster) { auto config = onDemandConfig(); set2Cluster(config); config.mutable_max_connect_attempts()->set_value(2); @@ -1413,7 +1484,7 @@ TEST_F(TcpProxyTest, OnDemandCallbackStickToTheSelectedCluster) { } // Verify the on demand api is not invoked when the target thread local cluster is present. -TEST_F(TcpProxyTest, OdcdsIsIgnoredIfClusterExists) { +TEST_P(TcpProxyTest, OdcdsIsIgnoredIfClusterExists) { auto config = onDemandConfig(); setup(1, config); @@ -1432,7 +1503,7 @@ TEST_F(TcpProxyTest, OdcdsIsIgnoredIfClusterExists) { } // Verify the on demand request is cancelled if the tcp downstream connection is closed. -TEST_F(TcpProxyTest, OdcdsCancelIfConnectionClose) { +TEST_P(TcpProxyTest, OdcdsCancelIfConnectionClose) { auto config = onDemandConfig(); mock_odcds_api_handle_ = Upstream::MockOdCdsApiHandle::create().release(); @@ -1454,7 +1525,7 @@ TEST_F(TcpProxyTest, OdcdsCancelIfConnectionClose) { } // Verify a request can be served after a successful on demand cluster request. -TEST_F(TcpProxyTest, OdcdsBasicDownstreamLocalClose) { +TEST_P(TcpProxyTest, OdcdsBasicDownstreamLocalClose) { auto config = onDemandConfig(); mock_odcds_api_handle_ = Upstream::MockOdCdsApiHandle::create().release(); @@ -1499,7 +1570,7 @@ TEST_F(TcpProxyTest, OdcdsBasicDownstreamLocalClose) { } // Verify the connection is closed after the cluster missing callback is triggered. -TEST_F(TcpProxyTest, OdcdsClusterMissingCauseConnectionClose) { +TEST_P(TcpProxyTest, OdcdsClusterMissingCauseConnectionClose) { auto config = onDemandConfig(); mock_odcds_api_handle_ = Upstream::MockOdCdsApiHandle::create().release(); @@ -1528,7 +1599,7 @@ TEST_F(TcpProxyTest, OdcdsClusterMissingCauseConnectionClose) { } // Test that upstream transport failure message is reflected in access logs. -TEST_F(TcpProxyTest, UpstreamConnectFailureStreamInfoAccessLog) { +TEST_P(TcpProxyTest, UpstreamConnectFailureStreamInfoAccessLog) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); setup(1, accessLogConfig("%UPSTREAM_TRANSPORT_FAILURE_REASON%")); @@ -1545,7 +1616,7 @@ TEST_F(TcpProxyTest, UpstreamConnectFailureStreamInfoAccessLog) { // Test that call to tcp_proxy filter's startUpstreamSecureTransport results // in upstream's startUpstreamSecureTransport call. -TEST_F(TcpProxyTest, UpstreamStartSecureTransport) { +TEST_P(TcpProxyTest, UpstreamStartSecureTransport) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); setup(1, config); @@ -1554,6 +1625,8 @@ TEST_F(TcpProxyTest, UpstreamStartSecureTransport) { filter_->startUpstreamSecureTransport(); } +INSTANTIATE_TEST_SUITE_P(WithOrWithoutUpstream, TcpProxyTest, ::testing::Bool()); + TEST(PerConnectionCluster, ObjectFactory) { const std::string name = "envoy.tcp_proxy.cluster"; auto* factory = diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index 9816df3871d9..a7a7770d218a 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -57,9 +57,11 @@ inline Config constructConfigFromYaml(const std::string& yaml, return {tcp_proxy, context}; } -class TcpProxyTestBase : public testing::Test { +class TcpProxyTestBase : public testing::TestWithParam { public: TcpProxyTestBase() { + scoped_runtime_.mergeValues({{"envoy.restart_features.upstream_http_filters_with_tcp_proxy", + GetParam() ? "true" : "false"}}); ON_CALL(*factory_context_.server_factory_context_.access_log_manager_.file_, write(_)) .WillByDefault(SaveArg<0>(&access_log_data_)); ON_CALL(filter_callbacks_.connection_.stream_info_, setUpstreamClusterInfo(_)) @@ -175,6 +177,7 @@ class TcpProxyTestBase : public testing::Test { Upstream::HostDescriptionConstSharedPtr upstream_host_{}; Upstream::ClusterInfoConstSharedPtr upstream_cluster_{}; std::string redirect_records_data_ = "some data"; + TestScopedRuntime scoped_runtime_; }; } // namespace TcpProxy diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index d48d9690c602..feca11eb4a60 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -6,8 +6,11 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/stream_encoder.h" +#include "test/mocks/router/router_filter_interface.h" +#include "test/mocks/router/upstream_request.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/tcp/mocks.h" +#include "test/mocks/upstream/load_balancer_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/test_runtime.h" @@ -23,7 +26,7 @@ using testing::Return; namespace Envoy { namespace TcpProxy { namespace { -using envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; +using envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy; class HttpUpstreamTest : public testing::TestWithParam { public: @@ -37,11 +40,11 @@ class HttpUpstreamTest : public testing::TestWithParam { .WillByDefault(Return(Http::Http1StreamEncoderOptionsOptRef(stream_encoder_options_))); } EXPECT_CALL(stream_encoder_options_, enableHalfClose()).Times(AnyNumber()); - config_message_.set_hostname("default.host.com:443"); + tcp_proxy_.mutable_tunneling_config()->set_hostname("default.host.com:443"); } void setupUpstream() { - config_ = std::make_unique(config_message_, context_); + config_ = std::make_unique(scope_, tcp_proxy_, context_); upstream_ = std::make_unique(callbacks_, *this->config_, downstream_stream_info_, GetParam()); upstream_->setRequestEncoder(encoder_, true); @@ -51,7 +54,9 @@ class HttpUpstreamTest : public testing::TestWithParam { Http::MockRequestEncoder encoder_; Http::MockHttp1StreamEncoderOptions stream_encoder_options_; NiceMock callbacks_; - TcpProxy_TunnelingConfig config_message_; + TcpProxy tcp_proxy_; + NiceMock store_; + Stats::MockScope& scope_{store_.mockScope()}; std::unique_ptr config_; std::unique_ptr upstream_; NiceMock context_; @@ -146,7 +151,7 @@ TEST_P(HttpUpstreamTest, UpstreamWatermarks) { class MockHttpConnPoolCallbacks : public HttpConnPool::Callbacks { public: - MOCK_METHOD(void, onSuccess, (Http::RequestEncoder & request_encoder)); + MOCK_METHOD(void, onSuccess, (Http::RequestEncoder * request_encoder)); MOCK_METHOD(void, onFailure, ()); }; @@ -235,11 +240,11 @@ class HttpUpstreamRequestEncoderTest : public testing::TestWithParamset_hostname("default.host.com:443"); } void setupUpstream() { - config_ = std::make_unique(config_message_, context_); + config_ = std::make_unique(scope_, tcp_proxy_, context_); upstream_ = std::make_unique(callbacks_, *this->config_, this->downstream_stream_info_, GetParam()); } @@ -259,7 +264,9 @@ class HttpUpstreamRequestEncoderTest : public testing::TestWithParam context_; std::unique_ptr upstream_; - TcpProxy_TunnelingConfig config_message_; + TcpProxy tcp_proxy_; + NiceMock store_; + Stats::MockScope& scope_{store_.mockScope()}; std::unique_ptr config_; bool is_http2_ = true; }; @@ -281,7 +288,7 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoder) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePost) { - this->config_message_.set_use_post(true); + this->tcp_proxy_.mutable_tunneling_config()->set_use_post(true); this->setupUpstream(); std::unique_ptr expected_headers; expected_headers = Http::createHeaderMap({ @@ -300,8 +307,8 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePost) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePostWithCustomPath) { - this->config_message_.set_use_post(true); - this->config_message_.set_post_path("/test"); + this->tcp_proxy_.mutable_tunneling_config()->set_use_post(true); + this->tcp_proxy_.mutable_tunneling_config()->set_post_path("/test"); this->setupUpstream(); std::unique_ptr expected_headers; expected_headers = Http::createHeaderMap({ @@ -320,25 +327,25 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePostWithCustomPath) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderConnectWithCustomPath) { - this->config_message_.set_use_post(false); - this->config_message_.set_post_path("/test"); + this->tcp_proxy_.mutable_tunneling_config()->set_use_post(false); + this->tcp_proxy_.mutable_tunneling_config()->set_post_path("/test"); EXPECT_THROW_WITH_MESSAGE(this->setupUpstream(), EnvoyException, "Can't set a post path when POST method isn't used"); } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeaders) { - auto* header = this->config_message_.add_headers_to_add(); + auto* header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); auto* hdr = header->mutable_header(); hdr->set_key("header0"); hdr->set_value("value0"); - header = this->config_message_.add_headers_to_add(); + header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("header1"); hdr->set_value("value1"); header->set_append_action(envoy::config::core::v3::HeaderValueOption::APPEND_IF_EXISTS_OR_ADD); - header = this->config_message_.add_headers_to_add(); + header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("header1"); hdr->set_value("value2"); @@ -360,13 +367,13 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeaders) { } TEST_P(HttpUpstreamRequestEncoderTest, ConfigReuse) { - auto* header = this->config_message_.add_headers_to_add(); + auto* header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); auto* hdr = header->mutable_header(); hdr->set_key("key"); hdr->set_value("value1"); header->set_append_action(envoy::config::core::v3::HeaderValueOption::APPEND_IF_EXISTS_OR_ADD); - header = this->config_message_.add_headers_to_add(); + header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("key"); hdr->set_value("value2"); @@ -404,12 +411,12 @@ TEST_P(HttpUpstreamRequestEncoderTest, ConfigReuse) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamInfo) { - auto* header = this->config_message_.add_headers_to_add(); + auto* header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); auto* hdr = header->mutable_header(); hdr->set_key("header0"); hdr->set_value("value0"); - header = this->config_message_.add_headers_to_add(); + header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("downstream_local_port"); hdr->set_value("%DOWNSTREAM_LOCAL_PORT%"); @@ -438,7 +445,7 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamInfo) TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoRequestedServerName) { - this->config_message_.set_hostname("%REQUESTED_SERVER_NAME%:443"); + this->tcp_proxy_.mutable_tunneling_config()->set_hostname("%REQUESTED_SERVER_NAME%:443"); this->setupUpstream(); std::unique_ptr expected_headers; @@ -462,7 +469,8 @@ TEST_P(HttpUpstreamRequestEncoderTest, } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoDynamicMetadata) { - this->config_message_.set_hostname("%DYNAMIC_METADATA(tunnel:address)%:443"); + this->tcp_proxy_.mutable_tunneling_config()->set_hostname( + "%DYNAMIC_METADATA(tunnel:address)%:443"); this->setupUpstream(); std::unique_ptr expected_headers; @@ -482,6 +490,218 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoD EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false)); this->upstream_->setRequestEncoder(this->encoder_, false); } + +class CombinedUpstreamTest : public testing::Test { +public: + CombinedUpstreamTest() { + EXPECT_CALL(encoder_, getStream()).Times(AnyNumber()); + EXPECT_CALL(encoder_, http1StreamEncoderOptions()).Times(AnyNumber()); + EXPECT_CALL(encoder_, enableTcpTunneling()).Times(AnyNumber()); + EXPECT_CALL(stream_encoder_options_, enableHalfClose()).Times(AnyNumber()); + tcp_proxy_.mutable_tunneling_config()->set_hostname("default.host.com:443"); + } + + void setup() { + tunnel_config_ = std::make_unique(scope_, tcp_proxy_, context_); + conn_pool_ = std::make_unique(cluster_, &lb_context_, *tunnel_config_, callbacks_, + decoder_callbacks_, Http::CodecType::HTTP2, + downstream_stream_info_); + upstream_ = std::make_unique(*conn_pool_, callbacks_, decoder_callbacks_, + *tunnel_config_, downstream_stream_info_); + auto mock_conn_pool = std::make_unique>(); + std::unique_ptr generic_conn_pool = std::move(mock_conn_pool); + config_ = std::make_shared(tcp_proxy_, factory_context_); + filter_ = + std::make_unique(config_, factory_context_.serverFactoryContext().clusterManager()); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + auto mock_upst = std::make_unique>( + *upstream_, std::move(generic_conn_pool)); + mock_router_upstream_request_ = mock_upst.get(); + upstream_->setRouterUpstreamRequest(std::move(mock_upst)); + EXPECT_CALL(*mock_router_upstream_request_, acceptHeadersFromRouter(false)); + EXPECT_NO_THROW(tunnel_config_->serverFactoryContext()); + upstream_->newStream(*filter_); + } + + Router::MockUpstreamRequest* mock_router_upstream_request_{}; + NiceMock router_filter_interface_; + NiceMock factory_context_; + ConfigSharedPtr config_; + NiceMock filter_callbacks_; + std::unique_ptr filter_; + + NiceMock downstream_stream_info_; + Http::MockRequestEncoder encoder_; + Http::MockHttp1StreamEncoderOptions stream_encoder_options_; + NiceMock callbacks_; + TcpProxy tcp_proxy_; + NiceMock decoder_callbacks_; + NiceMock cluster_; + NiceMock lb_context_; + std::unique_ptr conn_pool_; + NiceMock store_; + Stats::MockScope& scope_{store_.mockScope()}; + std::unique_ptr tunnel_config_; + std::unique_ptr upstream_; + NiceMock context_; +}; +TEST_F(CombinedUpstreamTest, RouterFilterInterface) { + this->setup(); + EXPECT_EQ(this->upstream_->startUpstreamSecureTransport(), false); + EXPECT_EQ(this->upstream_->getUpstreamConnectionSslInfo(), nullptr); + auto mock_conn_pool = std::make_unique>(); + std::unique_ptr generic_conn_pool = std::move(mock_conn_pool); + auto mock_upst = std::make_unique>( + *this->upstream_, std::move(generic_conn_pool)); + EXPECT_NO_THROW(this->upstream_->onUpstream1xxHeaders(nullptr, *mock_upst.get())); + EXPECT_NO_THROW(this->upstream_->onUpstreamMetadata(nullptr)); + EXPECT_NO_THROW(this->upstream_->onPerTryTimeout(*mock_upst.get())); + EXPECT_NO_THROW(this->upstream_->onPerTryIdleTimeout(*mock_upst.get())); + EXPECT_NO_THROW(this->upstream_->onStreamMaxDurationReached(*mock_upst.get())); + EXPECT_EQ(this->upstream_->dynamicMaxStreamDuration(), absl::nullopt); + EXPECT_EQ(this->upstream_->downstreamTrailers(), nullptr); + EXPECT_EQ(this->upstream_->downstreamResponseStarted(), false); + EXPECT_EQ(this->upstream_->downstreamEndStream(), false); + EXPECT_EQ(this->upstream_->attemptCount(), 0); +} + +TEST_F(CombinedUpstreamTest, WriteUpstream) { + this->setup(); + EXPECT_CALL(*this->mock_router_upstream_request_, + acceptDataFromRouter(BufferStringEqual("foo"), false /*end_stream*/)); + Buffer::OwnedImpl buffer1("foo"); + this->upstream_->encodeData(buffer1, false); + + EXPECT_CALL(*this->mock_router_upstream_request_, + acceptDataFromRouter(BufferStringEqual("bar"), true /*end_stream*/)); + Buffer::OwnedImpl buffer2("bar"); + this->upstream_->encodeData(buffer2, true); + + // New upstream with no encoder. + this->upstream_ = std::make_unique(*conn_pool_, callbacks_, decoder_callbacks_, + *tunnel_config_, downstream_stream_info_); + this->upstream_->encodeData(buffer2, true); +} + +TEST_F(CombinedUpstreamTest, WriteDownstream) { + this->setup(); + EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("foo"), false)); + Buffer::OwnedImpl buffer1("foo"); + this->upstream_->responseDecoder().decodeData(buffer1, false); + + EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("bar"), true)); + Buffer::OwnedImpl buffer2("bar"); + this->upstream_->responseDecoder().decodeData(buffer2, true); +} + +TEST_F(CombinedUpstreamTest, InvalidUpgradeWithEarlyFin) { + this->setup(); + EXPECT_CALL(this->callbacks_, onEvent(_)); + Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "200"}}}; + this->upstream_->responseDecoder().decodeHeaders(std::move(headers), true); +} + +TEST_F(CombinedUpstreamTest, InvalidUpgradeWithNon200) { + this->setup(); + EXPECT_CALL(this->callbacks_, onEvent(_)); + Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "301"}}}; + this->upstream_->responseDecoder().decodeHeaders(std::move(headers), false); +} + +TEST_F(CombinedUpstreamTest, ReadDisable) { + this->setup(); + EXPECT_CALL(*this->mock_router_upstream_request_, onAboveWriteBufferHighWatermark()); + EXPECT_TRUE(this->upstream_->readDisable(true)); + + EXPECT_CALL(*this->mock_router_upstream_request_, onAboveWriteBufferHighWatermark()).Times(0); + EXPECT_TRUE(this->upstream_->readDisable(false)); + + // New upstream with no encoder. + this->upstream_ = std::make_unique(*conn_pool_, callbacks_, decoder_callbacks_, + *tunnel_config_, downstream_stream_info_); + EXPECT_FALSE(this->upstream_->readDisable(true)); +} + +TEST_F(CombinedUpstreamTest, AddBytesSentCallbackForCoverage) { + this->setup(); + this->upstream_->addBytesSentCallback([&](uint64_t) { return true; }); +} + +TEST_F(CombinedUpstreamTest, DownstreamDisconnect) { + this->setup(); + EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); + EXPECT_CALL(this->callbacks_, onEvent(_)).Times(0); + EXPECT_TRUE(this->upstream_->onDownstreamEvent(Network::ConnectionEvent::LocalClose) == nullptr); +} + +TEST_F(CombinedUpstreamTest, UpstreamReset) { + this->setup(); + EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); + EXPECT_CALL(this->callbacks_, onEvent(_)); + this->upstream_->onResetStream(Http::StreamResetReason::ConnectionTermination, ""); +} + +TEST_F(CombinedUpstreamTest, UpstreamWatermarks) { + this->setup(); + EXPECT_CALL(this->callbacks_, onAboveWriteBufferHighWatermark()); + this->upstream_->onAboveWriteBufferHighWatermark(); + + EXPECT_CALL(this->callbacks_, onBelowWriteBufferLowWatermark()); + this->upstream_->onBelowWriteBufferLowWatermark(); +} + +TEST_F(CombinedUpstreamTest, OnSuccessCalledOnValidResponse) { + this->setup(); + auto conn_pool_callbacks = std::make_unique(); + auto conn_pool_callbacks_raw = conn_pool_callbacks.get(); + this->upstream_->setConnPoolCallbacks(std::move(conn_pool_callbacks)); + EXPECT_CALL(*conn_pool_callbacks_raw, onFailure()).Times(0); + EXPECT_CALL(*conn_pool_callbacks_raw, onSuccess(_)); + Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "200"}}}; + this->upstream_->responseDecoder().decodeHeaders(std::move(headers), false); +} + +TEST_F(CombinedUpstreamTest, OnFailureCalledOnInvalidResponse) { + this->setup(); + auto conn_pool_callbacks = std::make_unique(); + auto conn_pool_callbacks_raw = conn_pool_callbacks.get(); + this->upstream_->setConnPoolCallbacks(std::move(conn_pool_callbacks)); + EXPECT_CALL(*conn_pool_callbacks_raw, onFailure()); + EXPECT_CALL(*conn_pool_callbacks_raw, onSuccess(_)).Times(0); + Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "404"}}}; + this->upstream_->responseDecoder().decodeHeaders(std::move(headers), false); +} + +TEST_F(CombinedUpstreamTest, DumpsResponseDecoderWithoutAllocatingMemory) { + std::array buffer; + OutputBufferStream ostream{buffer.data(), buffer.size()}; + this->setup(); + + Stats::TestUtil::MemoryTest memory_test; + this->upstream_->responseDecoder().dumpState(ostream, 1); + EXPECT_EQ(memory_test.consumedBytes(), 0); + EXPECT_THAT(ostream.contents(), EndsWith("has not implemented dumpState\n")); +} +TEST_F(CombinedUpstreamTest, UpstreamTrailersMarksDoneReading) { + this->setup(); + EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); + this->upstream_->doneWriting(); + Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; + this->upstream_->responseDecoder().decodeTrailers(std::move(trailers)); +} + +TEST_F(CombinedUpstreamTest, UpstreamTrailersDontPropagateFinDownstreamWhenFeatureDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers", + "false"}}); + this->setup(); + EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); + upstream_->doneWriting(); + EXPECT_CALL(callbacks_, onUpstreamData(_, _)).Times(0); + Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; + upstream_->responseDecoder().decodeTrailers(std::move(trailers)); +} } // namespace } // namespace TcpProxy } // namespace Envoy diff --git a/test/extensions/filters/http/cache/BUILD b/test/extensions/filters/http/cache/BUILD index 1d1f5f0ab270..ce6100368be6 100644 --- a/test/extensions/filters/http/cache/BUILD +++ b/test/extensions/filters/http/cache/BUILD @@ -124,6 +124,7 @@ envoy_extension_cc_test( "cache_filter_integration_test.cc", ], extension_names = ["envoy.filters.http.cache"], + shard_count = 2, deps = [ "//source/extensions/filters/http/cache:config", "//source/extensions/filters/http/cache:http_cache_lib", diff --git a/test/extensions/filters/http/csrf/BUILD b/test/extensions/filters/http/csrf/BUILD index 5a958bce60e8..2a3b5ef8c3a4 100644 --- a/test/extensions/filters/http/csrf/BUILD +++ b/test/extensions/filters/http/csrf/BUILD @@ -33,6 +33,7 @@ envoy_extension_cc_test( size = "large", srcs = ["csrf_filter_integration_test.cc"], extension_names = ["envoy.filters.http.csrf"], + shard_count = 2, deps = [ "//source/extensions/filters/http/csrf:config", "//test/config:utility_lib", diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index 704c20d197f4..01c7daba1d5c 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -81,7 +81,7 @@ envoy_extension_cc_test( "custom_response_integration_test.cc", ], extension_names = ["envoy.filters.http.custom_response"], - shard_count = 2, + shard_count = 4, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/fault/BUILD b/test/extensions/filters/http/fault/BUILD index a7413da5feaf..55c19ce038bf 100644 --- a/test/extensions/filters/http/fault/BUILD +++ b/test/extensions/filters/http/fault/BUILD @@ -55,6 +55,7 @@ envoy_extension_cc_test( size = "large", srcs = ["fault_filter_integration_test.cc"], extension_names = ["envoy.filters.http.fault"], + shard_count = 4, deps = [ "//source/extensions/filters/http/fault:config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/health_check/BUILD b/test/extensions/filters/http/health_check/BUILD index 8d7a622ebf1d..7023b7b02bf6 100644 --- a/test/extensions/filters/http/health_check/BUILD +++ b/test/extensions/filters/http/health_check/BUILD @@ -45,6 +45,7 @@ envoy_extension_cc_test( "health_check_integration_test.cc", ], extension_names = ["envoy.filters.http.health_check"], + shard_count = 2, deps = [ "//source/extensions/filters/http/buffer:config", "//source/extensions/filters/http/health_check:config", diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 9b84849c432f..321ebccf2c83 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -162,7 +162,7 @@ envoy_extension_cc_test( "envoy.filters.http.jwt_authn", "envoy.filters.http.set_filter_state", ], - shard_count = 4, + shard_count = 8, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 4c397d1e0389..33538419eebd 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -58,7 +58,7 @@ envoy_extension_cc_test( size = "large", srcs = ["rbac_filter_integration_test.cc"], extension_names = ["envoy.filters.http.rbac"], - shard_count = 3, + shard_count = 10, tags = ["skip_on_windows"], deps = [ "//source/extensions/clusters/dynamic_forward_proxy:cluster", diff --git a/test/extensions/upstreams/http/tcp/upstream_request_test.cc b/test/extensions/upstreams/http/tcp/upstream_request_test.cc index e48d6a4ad7e4..9bacca7d18af 100644 --- a/test/extensions/upstreams/http/tcp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/tcp/upstream_request_test.cc @@ -114,7 +114,7 @@ class TcpUpstreamTest : public ::testing::Test { EXPECT_CALL(mock_router_filter_, callbacks()).Times(AnyNumber()); upstream_request_ = std::make_unique( mock_router_filter_, std::make_unique>(), false, - false); + false, false /*enable_tcp_tunneling*/); auto data = std::make_unique>(); EXPECT_CALL(*data, connection()).Times(AnyNumber()).WillRepeatedly(ReturnRef(connection())); tcp_upstream_ = std::make_unique(upstream_request_.get(), std::move(data)); diff --git a/test/extensions/upstreams/http/udp/upstream_request_test.cc b/test/extensions/upstreams/http/udp/upstream_request_test.cc index 423b21589589..c6020febfea6 100644 --- a/test/extensions/upstreams/http/udp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/udp/upstream_request_test.cc @@ -46,6 +46,7 @@ class UdpUpstreamTest : public ::testing::Test { udp_upstream_ = std::make_unique(&mock_upstream_to_downstream_, std::move(mock_socket), std::move(mock_host), mock_dispatcher_); + EXPECT_NO_THROW(udp_upstream_->enableHalfClose()); } protected: diff --git a/test/extensions/upstreams/tcp/generic/BUILD b/test/extensions/upstreams/tcp/generic/BUILD index 2a9375294a53..57cc2e3fcc9a 100644 --- a/test/extensions/upstreams/tcp/generic/BUILD +++ b/test/extensions/upstreams/tcp/generic/BUILD @@ -17,5 +17,6 @@ envoy_cc_test( "//source/extensions/upstreams/tcp/generic:config", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:upstream_mocks", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc index f2c62b449174..08d2ff0ead46 100644 --- a/test/extensions/upstreams/tcp/generic/config_test.cc +++ b/test/extensions/upstreams/tcp/generic/config_test.cc @@ -1,7 +1,10 @@ +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" + #include "source/common/stream_info/bool_accessor_impl.h" #include "source/common/tcp_proxy/tcp_proxy.h" #include "source/extensions/upstreams/tcp/generic/config.h" +#include "test/mocks/http/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/cluster_manager.h" @@ -14,13 +17,13 @@ using testing::_; using testing::AnyNumber; using testing::NiceMock; using testing::Return; +using testing::ReturnRef; namespace Envoy { namespace Extensions { namespace Upstreams { namespace Tcp { namespace Generic { - class TcpConnPoolTest : public ::testing::Test { public: TcpConnPoolTest() { @@ -32,6 +35,10 @@ class TcpConnPoolTest : public ::testing::Test { NiceMock downstream_stream_info_; NiceMock connection_; Upstream::MockLoadBalancerContext lb_context_; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy tcp_proxy_; + NiceMock store_; + Stats::MockScope& scope_{store_.mockScope()}; + NiceMock decoder_callbacks_; NiceMock context_; }; @@ -39,13 +46,13 @@ TEST_F(TcpConnPoolTest, TestNoTunnelingConfig) { EXPECT_CALL(thread_local_cluster_, tcpConnPool(_, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(), - &lb_context_, callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, TestTunnelingDisabledByFilterState) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - config_proto.set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); + tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); downstream_stream_info_.filterState()->setData( TcpProxy::DisableTunnelingFilterStateKey, @@ -55,13 +62,13 @@ TEST_F(TcpConnPoolTest, TestTunnelingDisabledByFilterState) { EXPECT_CALL(thread_local_cluster_, tcpConnPool(_, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, TestTunnelingNotDisabledIfFilterStateHasFalseValue) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - config_proto.set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); + tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); downstream_stream_info_.filterState()->setData( TcpProxy::DisableTunnelingFilterStateKey, @@ -71,46 +78,47 @@ TEST_F(TcpConnPoolTest, TestTunnelingNotDisabledIfFilterStateHasFalseValue) { EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, TestNoConnPool) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - config_proto.set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); + tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, Http2Config) { auto info = std::make_shared(); - EXPECT_CALL(*info, features()).WillOnce(Return(Upstream::ClusterInfo::Features::HTTP2)); - EXPECT_CALL(thread_local_cluster_, info).WillOnce(Return(info)); + const std::string fake_cluster_name = "fake_cluster"; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - config_proto.set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); + tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, Http3Config) { auto info = std::make_shared(); + const std::string fake_cluster_name = "fake_cluster"; EXPECT_CALL(*info, features()) .Times(AnyNumber()) .WillRepeatedly(Return(Upstream::ClusterInfo::Features::HTTP3)); EXPECT_CALL(thread_local_cluster_, info).Times(AnyNumber()).WillRepeatedly(Return(info)); envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - config_proto.set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); + tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); } TEST(DisableTunnelingObjectFactory, CreateFromBytes) { diff --git a/test/integration/BUILD b/test/integration/BUILD index fce0e38d657d..72636c49d709 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -329,6 +329,7 @@ envoy_cc_test( srcs = envoy_select_admin_functionality([ "drain_close_integration_test.cc", ]), + shard_count = 6, tags = [ "cpu:3", ], @@ -614,6 +615,7 @@ envoy_cc_test( srcs = [ "buffer_accounting_integration_test.cc", ], + shard_count = 2, tags = [ "cpu:3", ], @@ -985,7 +987,7 @@ envoy_cc_test( srcs = ["idle_timeout_integration_test.cc"], # As this test has many pauses for idle timeouts, it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 4, + shard_count = 16, tags = [ "cpu:3", ], @@ -1355,7 +1357,7 @@ envoy_cc_test( srcs = [ "redirect_integration_test.cc", ], - shard_count = 2, + shard_count = 4, tags = [ "cpu:3", "nofips", @@ -1391,6 +1393,7 @@ envoy_cc_test( name = "websocket_integration_test", size = "large", srcs = ["websocket_integration_test.cc"], + shard_count = 2, tags = [ "cpu:3", ], @@ -1537,7 +1540,7 @@ envoy_cc_test( name = "overload_integration_test", size = "large", srcs = ["overload_integration_test.cc"], - shard_count = 4, + shard_count = 10, tags = [ "cpu:3", ], @@ -1811,7 +1814,7 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 8, + shard_count = 30, tags = [ "cpu:3", ], @@ -1837,6 +1840,7 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], + shard_count = 4, deps = [ ":http_integration_lib", ":http_protocol_integration_lib", @@ -2281,6 +2285,7 @@ envoy_cc_test( srcs = [ "local_reply_integration_test.cc", ], + shard_count = 2, tags = [ "cpu:2", ], diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 13295850af25..baab4dd3be91 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -427,6 +427,7 @@ void BaseIntegrationTest::registerTestServerPorts(const std::vector const auto admin_addr = test_server->server().admin()->socket().connectionInfoProvider().localAddress(); if (admin_addr->type() == Network::Address::Type::Ip) { + ENVOY_LOG(debug, "registered 'admin' as port {}.", admin_addr->ip()->port()); registerPort("admin", admin_addr->ip()->port()); } } diff --git a/test/integration/http_protocol_integration.cc b/test/integration/http_protocol_integration.cc index 50f79fdf6366..914fffefcd99 100644 --- a/test/integration/http_protocol_integration.cc +++ b/test/integration/http_protocol_integration.cc @@ -39,15 +39,17 @@ std::vector HttpProtocolIntegrationTest::getProtocolTest #else use_header_validator_values.push_back(false); #endif - for (Http1ParserImpl http1_implementation : http1_implementations) { - for (Http2Impl http2_implementation : http2_implementations) { - for (bool defer_processing : http2_bool_values) { - for (bool deprecate_callback_visitor : http2_bool_values) { - for (bool use_header_validator : use_header_validator_values) { - ret.push_back(HttpProtocolTestParams{ - ip_version, downstream_protocol, upstream_protocol, http1_implementation, - http2_implementation, defer_processing, use_header_validator, - deprecate_callback_visitor}); + for (const bool tunneling_with_upstream_filters : {false, true}) { + for (Http1ParserImpl http1_implementation : http1_implementations) { + for (Http2Impl http2_implementation : http2_implementations) { + for (bool defer_processing : http2_bool_values) { + for (bool deprecate_callback_visitor : http2_bool_values) { + for (bool use_header_validator : use_header_validator_values) { + ret.push_back(HttpProtocolTestParams{ + ip_version, downstream_protocol, upstream_protocol, http1_implementation, + http2_implementation, defer_processing, use_header_validator, + deprecate_callback_visitor, tunneling_with_upstream_filters}); + } } } } @@ -102,9 +104,11 @@ std::string HttpProtocolIntegrationTest::protocolTestParamsToString( http2ImplementationToString(params.param.http2_implementation), params.param.defer_processing_backedup_streams ? "WithDeferredProcessing" : "NoDeferredProcessing", + params.param.use_universal_header_validator ? "Uhv" : "Legacy", params.param.deprecate_callback_visitor ? "WithCallbackVisitor" : "NoCallbackVisitor", - params.param.use_universal_header_validator ? "Uhv" : "Legacy"); + params.param.tunneling_with_upstream_filters ? "WithUpstreamHttpFilters" + : "WithoutUpstreamHttpFilters"); } void HttpProtocolIntegrationTest::setUpstreamOverrideStreamErrorOnInvalidHttpMessage() { diff --git a/test/integration/http_protocol_integration.h b/test/integration/http_protocol_integration.h index 0c0dd6c1bbc0..1fa57f3d1992 100644 --- a/test/integration/http_protocol_integration.h +++ b/test/integration/http_protocol_integration.h @@ -15,6 +15,7 @@ struct HttpProtocolTestParams { Http2Impl http2_implementation; bool defer_processing_backedup_streams; bool use_universal_header_validator; + bool tunneling_with_upstream_filters; bool deprecate_callback_visitor; }; @@ -86,6 +87,9 @@ class HttpProtocolIntegrationTest : public testing::TestWithParam static void initializeMockStreamFilterCallbacks(T& callbacks) MockStreamDecoderFilterCallbacks::MockStreamDecoderFilterCallbacks() { initializeMockStreamFilterCallbacks(*this); + ON_CALL(*this, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); ON_CALL(*this, decodingBuffer()).WillByDefault(Invoke(&buffer_, &Buffer::InstancePtr::get)); ON_CALL(*this, addDownstreamWatermarkCallbacks(_)) diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 3ec3f7c5eb71..f7770ac37b96 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -109,6 +109,7 @@ class MockFilterManagerCallbacks : public FilterManagerCallbacks { MOCK_METHOD(OptRef, tracingConfig, (), (const)); MOCK_METHOD(const ScopeTrackedObject&, scope, ()); MOCK_METHOD(void, restoreContextOnContinue, (ScopeTrackedObjectStack&)); + MOCK_METHOD(bool, isHalfCloseEnabled, ()); ResponseHeaderMapPtr informational_headers_; ResponseHeaderMapPtr response_headers_; @@ -330,6 +331,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, bool is_grpc_request_{}; bool is_head_request_{false}; bool stream_destroyed_{}; + NiceMock dispatcher_; }; class MockStreamEncoderFilterCallbacks : public StreamEncoderFilterCallbacks, diff --git a/test/mocks/router/BUILD b/test/mocks/router/BUILD index 79a35cb2b25d..2d1364e40bc1 100644 --- a/test/mocks/router/BUILD +++ b/test/mocks/router/BUILD @@ -49,3 +49,12 @@ envoy_cc_mock( "//test/test_common:test_time_lib", ], ) + +envoy_cc_mock( + name = "upstream_request", + srcs = ["upstream_request.cc"], + hdrs = ["upstream_request.h"], + deps = [ + "//source/common/router:router_lib", + ], +) diff --git a/test/mocks/router/upstream_request.cc b/test/mocks/router/upstream_request.cc new file mode 100644 index 000000000000..aabf4baf8a05 --- /dev/null +++ b/test/mocks/router/upstream_request.cc @@ -0,0 +1,13 @@ +#include "test/mocks/router/upstream_request.h" + +namespace Envoy { +namespace Router { + +MockUpstreamRequest::MockUpstreamRequest(RouterFilterInterface& router_interface, + std::unique_ptr&& conn_pool) + : UpstreamRequest(router_interface, std::move(conn_pool), false, true, true) {} + +MockUpstreamRequest::~MockUpstreamRequest() = default; + +} // namespace Router +} // namespace Envoy diff --git a/test/mocks/router/upstream_request.h b/test/mocks/router/upstream_request.h new file mode 100644 index 000000000000..10688fdde5d1 --- /dev/null +++ b/test/mocks/router/upstream_request.h @@ -0,0 +1,21 @@ +#pragma once + +#include "source/common/router/upstream_request.h" + +#include "gmock/gmock.h" + +namespace Envoy { +namespace Router { + +class MockUpstreamRequest : public UpstreamRequest { +public: + MockUpstreamRequest(RouterFilterInterface& router_interface, std::unique_ptr&&); + ~MockUpstreamRequest() override; + MOCK_METHOD(void, acceptHeadersFromRouter, (bool end_stream), (override)); + MOCK_METHOD(void, acceptDataFromRouter, (Buffer::Instance & data, bool end_stream), (override)); + MOCK_METHOD(void, onAboveWriteBufferHighWatermark, (), (override)); + MOCK_METHOD(void, resetStream, (), (override)); +}; + +} // namespace Router +} // namespace Envoy From d94c8eb3e072d26016403c8bc92aa47c25f56ea2 Mon Sep 17 00:00:00 2001 From: danzh Date: Mon, 18 Mar 2024 11:49:54 -0400 Subject: [PATCH 28/36] quic: remove unnecessary type cast in integration test (#32937) Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- test/integration/http_integration.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 2064c9629c45..368054ba95a0 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -245,15 +245,13 @@ Network::ClientConnectionPtr HttpIntegrationTest::makeClientConnectionWithOption fmt::format("udp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); Network::Address::InstanceConstSharedPtr local_addr = Network::Test::getCanonicalLoopbackAddress(version_); - auto& quic_transport_socket_factory_ref = - dynamic_cast(*quic_transport_socket_factory_); return Quic::createQuicNetworkConnection( - *quic_connection_persistent_info_, quic_transport_socket_factory_ref.getCryptoConfig(), + *quic_connection_persistent_info_, quic_transport_socket_factory_->getCryptoConfig(), quic::QuicServerId( - quic_transport_socket_factory_ref.clientContextConfig()->serverNameIndication(), + quic_transport_socket_factory_->clientContextConfig()->serverNameIndication(), static_cast(port)), *dispatcher_, server_addr, local_addr, quic_stat_names_, {}, *stats_store_.rootScope(), - options, nullptr, connection_id_generator_, quic_transport_socket_factory_ref); + options, nullptr, connection_id_generator_, *quic_transport_socket_factory_); #else ASSERT(false, "running a QUIC integration test without compiling QUIC"); return nullptr; From 19726b78c218801069d74b796727d76b89e2b54c Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 18 Mar 2024 15:55:00 +0000 Subject: [PATCH 29/36] wasm: Remove `wavm` (#32872) Signed-off-by: Ryan Northey --- api/envoy/extensions/wasm/v3/wasm.proto | 7 +- bazel/BUILD | 6 - bazel/README.md | 1 - bazel/envoy_build_system.bzl | 2 - bazel/envoy_select.bzl | 9 - bazel/foreign_cc/BUILD | 157 ------------------ bazel/foreign_cc/llvm.patch | 25 --- bazel/repositories.bzl | 24 --- bazel/repository_locations.bzl | 35 ---- ci/do_ci.sh | 17 +- .../configuration/other_features/wasm.rst | 3 +- source/extensions/common/wasm/wasm_vm.cc | 3 +- source/extensions/common/wasm/wasm_vm.h | 3 +- source/extensions/extensions_build_config.bzl | 1 - source/extensions/extensions_metadata.yaml | 8 - source/extensions/wasm_runtime/wavm/BUILD | 21 --- source/extensions/wasm_runtime/wavm/config.cc | 26 --- .../bootstrap/wasm/wasm_speed_test.cc | 6 - test/extensions/common/wasm/BUILD | 1 - test/extensions/common/wasm/wasm_runtime.cc | 3 - test/extensions/filters/http/wasm/BUILD | 2 - .../filters/http/wasm/test_data/BUILD | 19 --- test/per_file_coverage.sh | 1 - tools/spelling/spelling_dictionary.txt | 1 - 24 files changed, 9 insertions(+), 372 deletions(-) delete mode 100644 bazel/foreign_cc/llvm.patch delete mode 100644 source/extensions/wasm_runtime/wavm/BUILD delete mode 100644 source/extensions/wasm_runtime/wavm/config.cc diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index 54ef437cbaf7..58b7b57c489d 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -50,7 +50,7 @@ message VmConfig { string vm_id = 1; // The Wasm runtime type, defaults to the first available Wasm engine used at Envoy build-time. - // The priority to search for the available engine is: v8 -> wasmtime -> wamr -> wavm. + // The priority to search for the available engine is: v8 -> wasmtime -> wamr. // Available Wasm runtime types are registered as extensions. The following runtimes are included // in Envoy code base: // @@ -68,11 +68,6 @@ message VmConfig { // **envoy.wasm.runtime.wamr**: `WAMR `_-based WebAssembly runtime. // This runtime is not enabled in the official build. // - // .. _extension_envoy.wasm.runtime.wavm: - // - // **envoy.wasm.runtime.wavm**: `WAVM `_-based WebAssembly runtime. - // This runtime is not enabled in the official build. - // // .. _extension_envoy.wasm.runtime.wasmtime: // // **envoy.wasm.runtime.wasmtime**: `Wasmtime `_-based WebAssembly runtime. diff --git a/bazel/BUILD b/bazel/BUILD index 19578ec3c59e..c4749d3140d3 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -497,12 +497,6 @@ config_setting( values = {"define": "zlib=ng"}, ) -# TODO: consider converting WAVM VM support to an extension (https://github.com/envoyproxy/envoy/issues/12574) -config_setting( - name = "wasm_wavm", - values = {"define": "wasm=wavm"}, -) - config_setting( name = "wasm_v8", values = {"define": "wasm=v8"}, diff --git a/bazel/README.md b/bazel/README.md index 03a8008bcf05..02ad261e62b6 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -738,7 +738,6 @@ To enable a specific WebAssembly (Wasm) engine, you'll need to pass `--define wa * `v8` (the default included engine) * `wamr` * `wasmtime` -* `wavm` If you're building from a custom build repository, the parameters need to prefixed with `@envoy`, for example `--@envoy//source/extensions/filters/http/kill_request:enabled`. diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index 90fd023733b2..604e9f86bc61 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -38,7 +38,6 @@ load( _envoy_select_wasm_v8 = "envoy_select_wasm_v8", _envoy_select_wasm_wamr = "envoy_select_wasm_wamr", _envoy_select_wasm_wasmtime = "envoy_select_wasm_wasmtime", - _envoy_select_wasm_wavm = "envoy_select_wasm_wavm", ) load( ":envoy_test.bzl", @@ -252,7 +251,6 @@ envoy_select_wasm_cpp_tests = _envoy_select_wasm_cpp_tests envoy_select_wasm_rust_tests = _envoy_select_wasm_rust_tests envoy_select_wasm_v8 = _envoy_select_wasm_v8 envoy_select_wasm_wamr = _envoy_select_wasm_wamr -envoy_select_wasm_wavm = _envoy_select_wasm_wavm envoy_select_wasm_wasmtime = _envoy_select_wasm_wasmtime envoy_select_linkstatic = _envoy_linkstatic diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 87d5c71eefa2..8caee534fab4 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -154,7 +154,6 @@ def envoy_select_wasm_v8(xs): "@envoy//bazel:wasm_v8": xs, "@envoy//bazel:wasm_wamr": [], "@envoy//bazel:wasm_wasmtime": [], - "@envoy//bazel:wasm_wavm": [], "@envoy//bazel:wasm_disabled": [], # TODO(phlax): re-enable once issues with llvm profiler are resolved # (see https://github.com/envoyproxy/envoy/issues/24164) @@ -168,7 +167,6 @@ def envoy_select_wasm_v8_bool(): "@envoy//bazel:wasm_v8": True, "@envoy//bazel:wasm_wamr": False, "@envoy//bazel:wasm_wasmtime": False, - "@envoy//bazel:wasm_wavm": False, "@envoy//bazel:wasm_disabled": False, # TODO(phlax): re-enable once issues with llvm profiler are resolved # (see https://github.com/envoyproxy/envoy/issues/24164) @@ -183,13 +181,6 @@ def envoy_select_wasm_wamr(xs): "//conditions:default": [], }) -# Selects the given values depending on the Wasm runtimes enabled in the current build. -def envoy_select_wasm_wavm(xs): - return select({ - "@envoy//bazel:wasm_wavm": xs, - "//conditions:default": [], - }) - # Selects the given values depending on the Wasm runtimes enabled in the current build. def envoy_select_wasm_wasmtime(xs): return select({ diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index d80b6b218293..b863192e58a7 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -349,133 +349,6 @@ envoy_cmake( }), ) -envoy_cmake( - name = "llvm", - cache_entries = { - # Disable both: BUILD and INCLUDE, since some of the INCLUDE - # targets build code instead of only generating build files. - "LLVM_BUILD_BENCHMARKS": "off", - "LLVM_INCLUDE_BENCHMARKS": "off", - "LLVM_BUILD_DOCS": "off", - "LLVM_INCLUDE_DOCS": "off", - "LLVM_BUILD_EXAMPLES": "off", - "LLVM_INCLUDE_EXAMPLES": "off", - "LLVM_BUILD_RUNTIME": "off", - "LLVM_BUILD_RUNTIMES": "off", - "LLVM_INCLUDE_RUNTIMES": "off", - "LLVM_BUILD_TESTS": "off", - "LLVM_INCLUDE_TESTS": "off", - "LLVM_BUILD_TOOLS": "off", - "LLVM_INCLUDE_TOOLS": "off", - "LLVM_BUILD_UTILS": "off", - "LLVM_INCLUDE_UTILS": "off", - "LLVM_ENABLE_IDE": "off", - "LLVM_ENABLE_LIBEDIT": "off", - "LLVM_ENABLE_LIBXML2": "off", - "LLVM_ENABLE_TERMINFO": "off", - "LLVM_ENABLE_ZLIB": "off", - "LLVM_TARGETS_TO_BUILD": "X86", - "CMAKE_CXX_COMPILER_FORCED": "on", - # Workaround for the issue with statically linked libstdc++ - # using -l:libstdc++.a. - "CMAKE_CXX_FLAGS": "-lstdc++", - }, - env = { - # Workaround for the -DDEBUG flag added in fastbuild on macOS, - # which conflicts with DEBUG macro used in LLVM. - "CFLAGS": "-UDEBUG", - "CXXFLAGS": "-UDEBUG", - "ASMFLAGS": "-UDEBUG", - }, - lib_source = "@org_llvm_llvm//:all", - out_static_libs = select({ - "//conditions:default": [ - # This list must be updated when the bazel llvm version is updated - # (in `bazel/repository_locations.bzl`) - # - # The list can be regenerated by compiling the correct/updated llvm version - # from sources and running: - # - # `llvm-config --libnames` - # - "libLLVMWindowsManifest.a", - "libLLVMXRay.a", - "libLLVMLibDriver.a", - "libLLVMDlltoolDriver.a", - "libLLVMCoverage.a", - "libLLVMLineEditor.a", - "libLLVMX86Disassembler.a", - "libLLVMX86AsmParser.a", - "libLLVMX86CodeGen.a", - "libLLVMX86Desc.a", - "libLLVMX86Info.a", - "libLLVMOrcJIT.a", - "libLLVMMCJIT.a", - "libLLVMJITLink.a", - "libLLVMOrcTargetProcess.a", - "libLLVMOrcShared.a", - "libLLVMInterpreter.a", - "libLLVMExecutionEngine.a", - "libLLVMRuntimeDyld.a", - "libLLVMSymbolize.a", - "libLLVMDebugInfoPDB.a", - "libLLVMDebugInfoGSYM.a", - "libLLVMOption.a", - "libLLVMObjectYAML.a", - "libLLVMMCA.a", - "libLLVMMCDisassembler.a", - "libLLVMLTO.a", - "libLLVMPasses.a", - "libLLVMCFGuard.a", - "libLLVMCoroutines.a", - "libLLVMObjCARCOpts.a", - "libLLVMHelloNew.a", - "libLLVMipo.a", - "libLLVMVectorize.a", - "libLLVMLinker.a", - "libLLVMInstrumentation.a", - "libLLVMFrontendOpenMP.a", - "libLLVMFrontendOpenACC.a", - "libLLVMExtensions.a", - "libLLVMDWARFLinker.a", - "libLLVMGlobalISel.a", - "libLLVMMIRParser.a", - "libLLVMAsmPrinter.a", - "libLLVMDebugInfoDWARF.a", - "libLLVMSelectionDAG.a", - "libLLVMCodeGen.a", - "libLLVMIRReader.a", - "libLLVMAsmParser.a", - "libLLVMInterfaceStub.a", - "libLLVMFileCheck.a", - "libLLVMFuzzMutate.a", - "libLLVMTarget.a", - "libLLVMScalarOpts.a", - "libLLVMInstCombine.a", - "libLLVMAggressiveInstCombine.a", - "libLLVMTransformUtils.a", - "libLLVMBitWriter.a", - "libLLVMAnalysis.a", - "libLLVMProfileData.a", - "libLLVMObject.a", - "libLLVMTextAPI.a", - "libLLVMMCParser.a", - "libLLVMMC.a", - "libLLVMDebugInfoCodeView.a", - "libLLVMDebugInfoMSF.a", - "libLLVMBitReader.a", - "libLLVMCore.a", - "libLLVMRemarks.a", - "libLLVMBitstreamReader.a", - "libLLVMBinaryFormat.a", - "libLLVMSupport.a", - "libLLVMDemangle.a", - ], - }), - tags = ["skip_on_windows"], - alwayslink = True, -) - envoy_cmake( name = "nghttp2", cache_entries = { @@ -522,36 +395,6 @@ envoy_cmake( tags = ["skip_on_windows"], ) -envoy_cmake( - name = "wavm", - cache_entries = { - "LLVM_DIR": "$EXT_BUILD_DEPS/copy_llvm/llvm/lib/cmake/llvm", - "WAVM_ENABLE_STATIC_LINKING": "on", - "WAVM_ENABLE_RELEASE_ASSERTS": "on", - "WAVM_ENABLE_UNWIND": "on", - # Workaround for the issue with statically linked libstdc++ - # using -l:libstdc++.a. - "CMAKE_CXX_FLAGS": "-lstdc++ -Wno-unused-command-line-argument", - }, - env = { - # Workaround for the -DDEBUG flag added in fastbuild on macOS, - # which conflicts with DEBUG macro used in LLVM. - "CFLAGS": "-UDEBUG", - "CXXFLAGS": "-UDEBUG -Wno-error=unused-but-set-variable", - "ASMFLAGS": "-UDEBUG", - }, - lib_source = "@com_github_wavm_wavm//:all", - out_binaries = ["wavm"], - out_static_libs = select({ - "//conditions:default": [ - "libWAVM.a", - "libWAVMUnwind.a", - ], - }), - tags = ["skip_on_windows"], - deps = [":llvm"], -) - envoy_cmake( name = "zlib", cache_entries = { diff --git a/bazel/foreign_cc/llvm.patch b/bazel/foreign_cc/llvm.patch deleted file mode 100644 index cd02f2842401..000000000000 --- a/bazel/foreign_cc/llvm.patch +++ /dev/null @@ -1,25 +0,0 @@ -# Workaround for Envoy's CMAKE_BUILD_TYPE=Bazel. ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -247,7 +247,7 @@ - string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) - - if (CMAKE_BUILD_TYPE AND -- NOT uppercase_CMAKE_BUILD_TYPE MATCHES "^(DEBUG|RELEASE|RELWITHDEBINFO|MINSIZEREL)$") -+ NOT uppercase_CMAKE_BUILD_TYPE MATCHES "^(DEBUG|RELEASE|RELWITHDEBINFO|MINSIZEREL|BAZEL)$") - message(FATAL_ERROR "Invalid value for CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") - endif() - -# Workaround for a missing -fuse-ld flag in CXXFLAGS, which results in -# different linkers being used during configure and compilation phases. ---- a/cmake/modules/HandleLLVMOptions.cmake -+++ b/cmake/modules/HandleLLVMOptions.cmake -@@ -718,8 +718,6 @@ endif() - if (UNIX AND CMAKE_GENERATOR STREQUAL "Ninja") - include(CheckLinkerFlag) - check_linker_flag("-Wl,--color-diagnostics" LINKER_SUPPORTS_COLOR_DIAGNOSTICS) -- append_if(LINKER_SUPPORTS_COLOR_DIAGNOSTICS "-Wl,--color-diagnostics" -- CMAKE_EXE_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS) - endif() - - # Add flags for add_dead_strip(). diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index d231f276c7ab..82922ec78b69 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -383,9 +383,7 @@ def envoy_dependencies(skip_targets = []): _rust_deps() _kafka_deps() - _org_llvm_llvm() _com_github_wamr() - _com_github_wavm_wavm() _com_github_wasmtime() _com_github_wasm_c_api() @@ -1368,18 +1366,6 @@ def _com_github_gperftools_gperftools(): actual = "@envoy//bazel/foreign_cc:gperftools", ) -def _org_llvm_llvm(): - external_http_archive( - name = "org_llvm_llvm", - build_file_content = BUILD_ALL_CONTENT, - patch_args = ["-p1"], - patches = ["@envoy//bazel/foreign_cc:llvm.patch"], - ) - native.bind( - name = "llvm", - actual = "@envoy//bazel/foreign_cc:llvm", - ) - def _com_github_wamr(): external_http_archive( name = "com_github_wamr", @@ -1390,16 +1376,6 @@ def _com_github_wamr(): actual = "@envoy//bazel/foreign_cc:wamr", ) -def _com_github_wavm_wavm(): - external_http_archive( - name = "com_github_wavm_wavm", - build_file_content = BUILD_ALL_CONTENT, - ) - native.bind( - name = "wavm", - actual = "@envoy//bazel/foreign_cc:wavm", - ) - def _com_github_wasmtime(): external_http_archive( name = "com_github_wasmtime", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index ae267e507a16..f4026bfe6818 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1074,26 +1074,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_pkg/blob/{version}/LICENSE", ), - org_llvm_llvm = dict( - # When changing this, you must re-generate the list of llvm libs - # see `bazel/foreign_cc/BUILD` for further information. - project_name = "LLVM", - project_desc = "LLVM Compiler Infrastructure", - project_url = "https://llvm.org", - version = "12.0.1", - sha256 = "7d9a8405f557cefc5a21bf5672af73903b64749d9bc3a50322239f56f34ffddf", - strip_prefix = "llvm-{version}.src", - urls = ["https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/llvm-{version}.src.tar.xz"], - release_date = "2021-07-09", - use_category = ["dataplane_ext"], - extensions = [ - "envoy.wasm.runtime.wamr", - "envoy.wasm.runtime.wavm", - ], - cpe = "cpe:2.3:a:llvm:*:*", - license = "Apache-2.0", - license_url = "https://github.com/llvm/llvm-project/blob/llvmorg-{version}/llvm/LICENSE.TXT", - ), com_github_wamr = dict( project_name = "Webassembly Micro Runtime", project_desc = "A standalone runtime with a small footprint for WebAssembly", @@ -1109,19 +1089,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "Apache-2.0", license_url = "https://github.com/bytecodealliance/wasm-micro-runtime/blob/{version}/LICENSE", ), - com_github_wavm_wavm = dict( - project_name = "WAVM", - project_desc = "WebAssembly Virtual Machine", - project_url = "https://wavm.github.io", - version = "3f9a150cac7faf28eab357a2c5b83d2ec740c7d9", - sha256 = "82e05ade03fdac60cf863972d3e7420a771ef4a18afad26ac442554ab0be1207", - strip_prefix = "WAVM-{version}", - urls = ["https://github.com/WAVM/WAVM/archive/{version}.tar.gz"], - release_date = "2022-05-14", - use_category = ["dataplane_ext"], - extensions = ["envoy.wasm.runtime.wavm"], - cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", - ), com_github_wasmtime = dict( project_name = "wasmtime", project_desc = "A standalone runtime for WebAssembly", @@ -1432,7 +1399,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.null", "envoy.wasm.runtime.v8", "envoy.wasm.runtime.wamr", - "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], release_date = "2023-05-01", @@ -1458,7 +1424,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.wasm.runtime.null", "envoy.wasm.runtime.v8", "envoy.wasm.runtime.wamr", - "envoy.wasm.runtime.wavm", "envoy.wasm.runtime.wasmtime", ], release_date = "2023-12-19", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 1c04c4250292..2cecbff3f793 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -427,21 +427,12 @@ case $CI_TARGET in "${TEST_TARGETS[@]}" \ --test_tag_filters=-nofips \ --build_tests_only - echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" - bazel_with_collection \ - test "${BAZEL_BUILD_OPTIONS[@]}" \ - --config=compile-time-options \ - --define wasm=wavm \ - -c fastbuild \ - "${TEST_TARGETS[@]}" \ - --test_tag_filters=-nofips \ - --build_tests_only # "--define log_debug_assert_in_release=enabled" must be tested with a release build, so run only # these tests under "-c opt" to save time in CI. bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ --config=compile-time-options \ - --define wasm=wavm \ + --define wasm=wasmtime \ -c opt \ @envoy//test/common/common:assert_test \ @envoy//test/server:server_test @@ -449,15 +440,15 @@ case $CI_TARGET in bazel_with_collection \ test "${BAZEL_BUILD_OPTIONS[@]}" \ --config=compile-time-options \ - --define wasm=wavm \ + --define wasm=wamtime \ -c opt \ @envoy//test/common/common:assert_test \ --define log_fast_debug_assert_in_release=enabled \ --define log_debug_assert_in_release=disabled - echo "Building binary with wasm=wavm... and logging disabled" + echo "Building binary with wasm=wasmtime... and logging disabled" bazel build "${BAZEL_BUILD_OPTIONS[@]}" \ --config=compile-time-options \ - --define wasm=wavm \ + --define wasm=wasmtime \ --define enable_logging=disabled \ -c fastbuild \ @envoy//source/exe:envoy-static \ diff --git a/docs/root/configuration/other_features/wasm.rst b/docs/root/configuration/other_features/wasm.rst index f34146e68138..25c23c7ccf6e 100644 --- a/docs/root/configuration/other_features/wasm.rst +++ b/docs/root/configuration/other_features/wasm.rst @@ -12,10 +12,9 @@ The following runtimes are supported by Envoy: envoy.wasm.runtime.v8, "`V8 `_-based runtime" envoy.wasm.runtime.wamr, "`WAMR `_ runtime" envoy.wasm.runtime.wasmtime, "`Wasmtime `_ runtime" - envoy.wasm.runtime.wavm, "`WAVM `_ runtime" envoy.wasm.runtime.null, "Compiled modules linked into Envoy" -WAMR(WASM-Micro-Runtime), Wasmtime and WAVM runtimes are not included in Envoy release image by default. +WAMR(WASM-Micro-Runtime), Wasmtime runtime is not included in Envoy release image by default. Wasm runtime emits the following statistics: diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index fc225eb3045b..aaf20e95569a 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -79,8 +79,7 @@ bool isWasmEngineAvailable(absl::string_view runtime) { absl::string_view getFirstAvailableWasmEngineName() { constexpr absl::string_view wasm_engines[] = { - "envoy.wasm.runtime.v8", "envoy.wasm.runtime.wasmtime", "envoy.wasm.runtime.wamr", - "envoy.wasm.runtime.wavm"}; + "envoy.wasm.runtime.v8", "envoy.wasm.runtime.wasmtime", "envoy.wasm.runtime.wamr"}; for (const auto wasm_engine : wasm_engines) { if (isWasmEngineAvailable(wasm_engine)) { return wasm_engine; diff --git a/source/extensions/common/wasm/wasm_vm.h b/source/extensions/common/wasm/wasm_vm.h index 139d06c87f69..e347428e1155 100644 --- a/source/extensions/common/wasm/wasm_vm.h +++ b/source/extensions/common/wasm/wasm_vm.h @@ -38,7 +38,8 @@ class WasmException : public EnvoyException { using WasmVmPtr = std::unique_ptr; -// Create a new low-level Wasm VM using runtime of the given type (e.g. "envoy.wasm.runtime.wavm"). +// Create a new low-level Wasm VM using runtime of the given type (e.g. +// "envoy.wasm.runtime.wasmtime"). WasmVmPtr createWasmVm(absl::string_view runtime); /** diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index a3fa5c9831e2..1425006ffaf4 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -348,7 +348,6 @@ EXTENSIONS = { "envoy.wasm.runtime.null": "//source/extensions/wasm_runtime/null:config", "envoy.wasm.runtime.v8": "//source/extensions/wasm_runtime/v8:config", "envoy.wasm.runtime.wamr": "//source/extensions/wasm_runtime/wamr:config", - "envoy.wasm.runtime.wavm": "//source/extensions/wasm_runtime/wavm:config", "envoy.wasm.runtime.wasmtime": "//source/extensions/wasm_runtime/wasmtime:config", # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 771ae0f999c3..88957f155d5e 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1354,14 +1354,6 @@ envoy.wasm.runtime.wasmtime: # is updated to capture additional Wasm runtimes". security_posture: unknown status: alpha -envoy.wasm.runtime.wavm: - categories: - - envoy.wasm.runtime - # "This may never change from unknown until the threat model at - # https://envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/threat_model#core-and-extensions - # is updated to capture additional Wasm runtimes". - security_posture: unknown - status: alpha envoy.watchdog.profile_action: categories: - envoy.guarddog_actions diff --git a/source/extensions/wasm_runtime/wavm/BUILD b/source/extensions/wasm_runtime/wavm/BUILD deleted file mode 100644 index fa65a029b17b..000000000000 --- a/source/extensions/wasm_runtime/wavm/BUILD +++ /dev/null @@ -1,21 +0,0 @@ -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_extension", - "envoy_extension_package", -) -load("//bazel:envoy_select.bzl", "envoy_select_wasm_wavm") - -licenses(["notice"]) # Apache 2 - -envoy_extension_package() - -envoy_cc_extension( - name = "config", - srcs = envoy_select_wasm_wavm(["config.cc"]), - deps = envoy_select_wasm_wavm([ - "//envoy/registry", - "//source/extensions/common/wasm:wasm_runtime_factory_interface", - "@proxy_wasm_cpp_host//:base_lib", - "@proxy_wasm_cpp_host//:wavm_lib", - ]), -) diff --git a/source/extensions/wasm_runtime/wavm/config.cc b/source/extensions/wasm_runtime/wavm/config.cc deleted file mode 100644 index c2b3d93a29e4..000000000000 --- a/source/extensions/wasm_runtime/wavm/config.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "envoy/registry/registry.h" - -#include "source/extensions/common/wasm/wasm_runtime_factory.h" - -#include "include/proxy-wasm/wavm.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Wasm { - -class WavmRuntimeFactory : public WasmRuntimeFactory { -public: - WasmVmPtr createWasmVm() override { return proxy_wasm::createWavmVm(); } - - std::string name() const override { return "envoy.wasm.runtime.wavm"; } -}; - -#if defined(PROXY_WASM_HAS_RUNTIME_WAVM) -REGISTER_FACTORY(WavmRuntimeFactory, WasmRuntimeFactory); -#endif - -} // namespace Wasm -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/test/extensions/bootstrap/wasm/wasm_speed_test.cc b/test/extensions/bootstrap/wasm/wasm_speed_test.cc index aaadd74ba6e2..2c1333fbb8a3 100644 --- a/test/extensions/bootstrap/wasm/wasm_speed_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_speed_test.cc @@ -91,12 +91,6 @@ static void bmWasmSimpleCallSpeedTest(benchmark::State& state, std::string test, std::string("null")); \ BENCHMARK_CAPTURE(bmWasmSimpleCallSpeedTest, WasmSpeedTest_##_t, std::string(#_t), \ std::string("wamr")); -#elif defined(PROXY_WASM_HAS_RUNTIME_WAVM) -#define B(_t) \ - BENCHMARK_CAPTURE(bmWasmSimpleCallSpeedTest, NullSpeedTest_##_t, std::string(#_t), \ - std::string("null")); \ - BENCHMARK_CAPTURE(bmWasmSimpleCallSpeedTest, WasmSpeedTest_##_t, std::string(#_t), \ - std::string("wavm")); #elif defined(PROXY_WASM_HAS_RUNTIME_WASMTIME) #define B(_t) \ BENCHMARK_CAPTURE(bmWasmSimpleCallSpeedTest, NullSpeedTest_##_t, std::string(#_t), \ diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 62daee5c4c16..cbd4bb7ade1e 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -100,7 +100,6 @@ envoy_cc_test_library( "//source/extensions/wasm_runtime/v8:config", "//source/extensions/wasm_runtime/wamr:config", "//source/extensions/wasm_runtime/wasmtime:config", - "//source/extensions/wasm_runtime/wavm:config", ], ) diff --git a/test/extensions/common/wasm/wasm_runtime.cc b/test/extensions/common/wasm/wasm_runtime.cc index db5ecc6e400b..e21f9835cea0 100644 --- a/test/extensions/common/wasm/wasm_runtime.cc +++ b/test/extensions/common/wasm/wasm_runtime.cc @@ -10,9 +10,6 @@ std::vector sandboxRuntimes() { #if defined(PROXY_WASM_HAS_RUNTIME_V8) runtimes.push_back("v8"); #endif -#if defined(PROXY_WASM_HAS_RUNTIME_WAVM) - runtimes.push_back("wavm"); -#endif #if defined(PROXY_WASM_HAS_RUNTIME_WAMR) runtimes.push_back("wamr"); #endif diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index 2b4f39eb4631..004163e9c65f 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -18,7 +18,6 @@ envoy_package() envoy_extension_cc_test( name = "wasm_filter_test", - size = "enormous", # For WAVM without precompilation. TODO: add precompilation. srcs = ["wasm_filter_test.cc"], data = envoy_select_wasm_cpp_tests([ "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", @@ -55,7 +54,6 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "config_test", - size = "enormous", # For WAVM without precompilation. TODO: add precompilation. srcs = ["config_test.cc"], data = envoy_select_wasm_cpp_tests([ "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index fe2816d95412..c9e28cd8de9d 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -178,22 +178,3 @@ cc_proto_library( name = "test_cc_proto", deps = [":test_proto"], ) - -# TODO: FIXME -# -#filegroup( -# name = "wavm_binary", -# srcs = ["//bazel/foreign_cc:wavm"], -# output_group = "wavm", -#) -# -#genrule( -# name = "test_cpp_wavm_compile", -# srcs = [":test_cpp.wasm"], -# outs = ["test_cpp.wavm_compiled.wasm"], -# cmd = "./$(location wavm_binary) compile $(location test_cpp.wasm) $(location test_cpp.wavm_compiled.wasm)", -# tools = [ -# ":test_cpp.wasm", -# ":wavm_binary", -# ], -#) diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index cd8604d486dc..d34afbad937e 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -56,7 +56,6 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/tls/private_key:88.9" "source/extensions/wasm_runtime/wamr:0.0" # Not enabled in coverage build "source/extensions/wasm_runtime/wasmtime:0.0" # Not enabled in coverage build -"source/extensions/wasm_runtime/wavm:0.0" # Not enabled in coverage build "source/extensions/watchdog:83.3" # Death tests within extensions "source/extensions/listener_managers:70.5" "source/extensions/listener_managers/validation_listener_manager:70.5" diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 1edc5139573f..2b4096b313e5 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -471,7 +471,6 @@ VM VPN WAITFORONE WASM -WAVM WIP WKT WRONGPASS From efd9bef80764663be31b115301812a5bb647fad5 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 18 Mar 2024 12:59:44 -0400 Subject: [PATCH 30/36] quic: lowering quic brokenness timer (#32910) turns out we always ran with roughly this override in production Testing: updated tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Alyssa Wilk --- .../common/http/http3_status_tracker_impl.cc | 12 ++-- .../http/http3_status_tracker_impl_test.cc | 68 +++++++------------ 2 files changed, 30 insertions(+), 50 deletions(-) diff --git a/source/common/http/http3_status_tracker_impl.cc b/source/common/http/http3_status_tracker_impl.cc index c065fe6e3202..4f0dc6db28b9 100644 --- a/source/common/http/http3_status_tracker_impl.cc +++ b/source/common/http/http3_status_tracker_impl.cc @@ -5,10 +5,10 @@ namespace Http { namespace { -// Initially, HTTP/3 is be marked broken for 5 minutes. -const std::chrono::minutes DefaultExpirationTime{5}; -// Cap the broken period at just under 1 day. -const int MaxConsecutiveBrokenCount = 8; +// Initially, HTTP/3 is marked broken for 1 second. +const std::chrono::seconds DefaultExpirationTime{1}; +// Cap the broken period around a day and a half. +const int MaxConsecutiveBrokenCount = 17; } // namespace Http3StatusTrackerImpl::Http3StatusTrackerImpl(Event::Dispatcher& dispatcher) @@ -25,10 +25,10 @@ bool Http3StatusTrackerImpl::hasHttp3FailedRecently() const { void Http3StatusTrackerImpl::markHttp3Broken() { state_ = State::Broken; if (!expiration_timer_->enabled()) { - std::chrono::minutes expiration_in_min = + std::chrono::seconds expiration_in_sec = DefaultExpirationTime * (1 << consecutive_broken_count_); expiration_timer_->enableTimer( - std::chrono::duration_cast(expiration_in_min)); + std::chrono::duration_cast(expiration_in_sec)); if (consecutive_broken_count_ < MaxConsecutiveBrokenCount) { ++consecutive_broken_count_; } diff --git a/test/common/http/http3_status_tracker_impl_test.cc b/test/common/http/http3_status_tracker_impl_test.cc index 8d6ce3b88d82..19c6a582f090 100644 --- a/test/common/http/http3_status_tracker_impl_test.cc +++ b/test/common/http/http3_status_tracker_impl_test.cc @@ -33,7 +33,7 @@ TEST_F(Http3StatusTrackerImplTest, Initialized) { TEST_F(Http3StatusTrackerImplTest, MarkBroken) { EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); EXPECT_TRUE(tracker_.isHttp3Broken()); EXPECT_FALSE(tracker_.isHttp3Confirmed()); @@ -42,7 +42,7 @@ TEST_F(Http3StatusTrackerImplTest, MarkBroken) { TEST_F(Http3StatusTrackerImplTest, MarkBrokenRepeatedly) { EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); EXPECT_TRUE(tracker_.isHttp3Broken()); EXPECT_FALSE(tracker_.isHttp3Confirmed()); @@ -55,7 +55,7 @@ TEST_F(Http3StatusTrackerImplTest, MarkBrokenRepeatedly) { TEST_F(Http3StatusTrackerImplTest, MarkBrokenThenExpires) { EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); @@ -67,19 +67,19 @@ TEST_F(Http3StatusTrackerImplTest, MarkBrokenThenExpires) { TEST_F(Http3StatusTrackerImplTest, MarkBrokenWithBackoff) { EXPECT_CALL(*timer_, enabled()).WillRepeatedly(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(10 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(2 * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); EXPECT_FALSE(tracker_.isHttp3Broken()); EXPECT_FALSE(tracker_.isHttp3Confirmed()); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(20 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(4 * 1000), nullptr)); tracker_.markHttp3Broken(); EXPECT_TRUE(tracker_.isHttp3Broken()); EXPECT_FALSE(tracker_.isHttp3Confirmed()); @@ -88,7 +88,7 @@ TEST_F(Http3StatusTrackerImplTest, MarkBrokenWithBackoff) { EXPECT_FALSE(tracker_.isHttp3Broken()); EXPECT_FALSE(tracker_.isHttp3Confirmed()); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(8 * 1000), nullptr)); tracker_.markHttp3Broken(); EXPECT_TRUE(tracker_.isHttp3Broken()); EXPECT_FALSE(tracker_.isHttp3Confirmed()); @@ -101,51 +101,31 @@ TEST_F(Http3StatusTrackerImplTest, MarkBrokenWithBackoff) { TEST_F(Http3StatusTrackerImplTest, MarkBrokenWithBackoffMax) { EXPECT_CALL(*timer_, enabled()).WillRepeatedly(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(10 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(20 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(80 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(160 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(320 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); - - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(640 * 60 * 1000), nullptr)); - tracker_.markHttp3Broken(); - timer_->invokeCallback(); + for (int i = 0; i < 17; ++i) { + EXPECT_CALL( + *timer_, + enableTimer(std::chrono::milliseconds(1 * static_cast(pow(2, i)) * 1000), nullptr)); + tracker_.markHttp3Broken(); + timer_->invokeCallback(); + } - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1280 * 60 * 1000), nullptr)); + EXPECT_CALL( + *timer_, + enableTimer(std::chrono::milliseconds(1 * static_cast(pow(2, 17)) * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); // Broken period no longer increases. - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1280 * 60 * 1000), nullptr)); + EXPECT_CALL( + *timer_, + enableTimer(std::chrono::milliseconds(1 * static_cast(pow(2, 17)) * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); } TEST_F(Http3StatusTrackerImplTest, MarkBrokenThenExpiresThenConfirmedThenBroken) { EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); @@ -157,7 +137,7 @@ TEST_F(Http3StatusTrackerImplTest, MarkBrokenThenExpiresThenConfirmedThenBroken) // markConfirmed will have reset the timeout back to the initial value. EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); EXPECT_TRUE(tracker_.isHttp3Broken()); @@ -167,7 +147,7 @@ TEST_F(Http3StatusTrackerImplTest, MarkBrokenThenExpiresThenConfirmedThenBroken) TEST_F(Http3StatusTrackerImplTest, MarkBrokenThenConfirmed) { EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); timer_->invokeCallback(); @@ -185,7 +165,7 @@ TEST_F(Http3StatusTrackerImplTest, MarkFailedRecentlyAndThenBroken) { EXPECT_FALSE(tracker_.isHttp3Confirmed()); EXPECT_CALL(*timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5 * 60 * 1000), nullptr)); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1 * 1000), nullptr)); tracker_.markHttp3Broken(); EXPECT_TRUE(tracker_.isHttp3Broken()); From 87655e546f7d4218959f29957abaeafd5110d4fc Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 18 Mar 2024 13:27:25 -0400 Subject: [PATCH 31/36] admin: add streaming variant of the admin API (#32346) Commit Message: In https://github.com/envoyproxy/envoy/pull/19693 a streaming stats implementation was introduced, dramatically reducing the amount of time and memory it takes to generate an admin http response. /config_dump (#32054) and /clusters_ (#32054) can also be improved in this way. However, in some environments we do not want to expose an HTTP port for admin requests, and we must use the C++ API. However that API is not streaming: it buffers the entire content. This PR adds a new streaming API. Mechanically this new functionality was easy to add as an externally callble C++ API as the Admin class already supported this model on behalf of /stats. However there's a fair amount of complexity managing potential races between active streaming requests, server exit, and explicit cancellation of an in-progress request. So much of the effort and complexity in this PR is due to making that thread-safe and testing it. Additional Description: ![Life of an AdminResponse (1)](https://github.com/envoyproxy/envoy/assets/1942589/28387991-406c-45d4-812e-ccd1be910c36) Note to reviewers: if this PR is too big it can be broken into 2. The significant additions to main_common.cc, main_common.h, and main_common_test.cc need to stay together in one PR, the rest of the changes are non-functional refactors which are needed for the larger changes to work; they could be reviewed and merged first. Risk Level: medium -- this is a new API but it does refactor some existing functionality. Using the new functionality will add some risk also as there is some careful thought required to believe we have protected against all possibilities of shutdown/cancel/admin-request races. Testing: //test/..., and lots of tsan repeated tests for the new streaming tests. Lots of iteration to ensure every new line of main_common.cc is hit by coverage tests. Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Fixes: #31755 Signed-off-by: Joshua Marantz --- envoy/server/admin.h | 5 + source/exe/BUILD | 16 +- source/exe/admin_response.cc | 191 ++++++++++ source/exe/admin_response.h | 188 ++++++++++ source/exe/main_common.cc | 39 ++- source/exe/main_common.h | 56 ++- source/server/admin/BUILD | 1 + source/server/admin/admin.cc | 4 +- source/server/admin/admin.h | 8 +- source/server/admin/admin_filter.cc | 6 +- source/server/admin/admin_filter.h | 10 +- source/server/config_validation/admin.h | 1 + test/exe/BUILD | 40 ++- test/exe/admin_response_test.cc | 351 +++++++++++++++++++ test/exe/main_common_test.cc | 150 +------- test/exe/main_common_test_base.cc | 117 +++++++ test/exe/main_common_test_base.h | 77 ++++ test/integration/admin_html/test_server.cc | 3 +- test/mocks/server/admin.h | 1 + test/per_file_coverage.sh | 2 +- test/server/admin/admin_filter_test.cc | 11 +- test/server/admin/admin_instance.cc | 2 +- test/server/config_validation/BUILD | 1 + test/server/config_validation/server_test.cc | 3 + 24 files changed, 1111 insertions(+), 172 deletions(-) create mode 100644 source/exe/admin_response.cc create mode 100644 source/exe/admin_response.h create mode 100644 test/exe/admin_response_test.cc create mode 100644 test/exe/main_common_test_base.cc create mode 100644 test/exe/main_common_test_base.h diff --git a/envoy/server/admin.h b/envoy/server/admin.h index 251b0cf47b44..48c1629fef44 100644 --- a/envoy/server/admin.h +++ b/envoy/server/admin.h @@ -281,6 +281,11 @@ class Admin { * Closes the listening socket for the admin. */ virtual void closeSocket() PURE; + + /** + * Creates a streaming request context from the url path in the admin stream. + */ + virtual RequestPtr makeRequest(AdminStream& admin_stream) const PURE; }; } // namespace Server diff --git a/source/exe/BUILD b/source/exe/BUILD index a4651aae9b4f..0f7947c1d07a 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -7,6 +7,7 @@ load( "envoy_cc_posix_without_linux_library", "envoy_cc_win32_library", "envoy_package", + "envoy_select_admin_functionality", "envoy_select_enable_http3", "envoy_select_signal_trace", ) @@ -102,7 +103,7 @@ envoy_cc_library( hdrs = [ "main_common.h", ], - deps = [ + deps = envoy_select_admin_functionality([":admin_response_lib"]) + [ ":platform_impl_lib", ":process_wide_lib", ":stripped_main_base_lib", @@ -118,6 +119,19 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "admin_response_lib", + srcs = ["admin_response.cc"], + hdrs = ["admin_response.h"], + deps = [ + "//source/common/buffer:buffer_lib", + "//source/common/http:header_map_lib", + "//source/server:server_lib", + "//source/server/admin:admin_lib", + "//source/server/admin:utils_lib", + ], +) + envoy_cc_library( name = "main_common_with_all_extensions_lib", deps = [ diff --git a/source/exe/admin_response.cc b/source/exe/admin_response.cc new file mode 100644 index 000000000000..0c1ab0958bec --- /dev/null +++ b/source/exe/admin_response.cc @@ -0,0 +1,191 @@ +#include "source/exe/admin_response.h" + +#include "envoy/server/admin.h" + +#include "source/server/admin/admin_filter.h" +#include "source/server/admin/utils.h" + +namespace Envoy { + +AdminResponse::AdminResponse(Server::Instance& server, absl::string_view path, + absl::string_view method, SharedPtrSet response_set) + : server_(server), opt_admin_(server.admin()), shared_response_set_(response_set) { + request_headers_->setMethod(method); + request_headers_->setPath(path); +} + +AdminResponse::~AdminResponse() { + cancel(); + shared_response_set_->detachResponse(this); +} + +void AdminResponse::getHeaders(HeadersFn fn) { + auto request_headers = [response = shared_from_this()]() { response->requestHeaders(); }; + + // First check for cancelling or termination. + { + absl::MutexLock lock(&mutex_); + ASSERT(headers_fn_ == nullptr); + if (cancelled_) { + return; + } + headers_fn_ = fn; + if (terminated_ || !opt_admin_) { + sendErrorLockHeld(); + return; + } + } + server_.dispatcher().post(request_headers); +} + +void AdminResponse::nextChunk(BodyFn fn) { + auto request_next_chunk = [response = shared_from_this()]() { response->requestNextChunk(); }; + + // Note the caller may race a call to nextChunk with the server being + // terminated. + { + absl::MutexLock lock(&mutex_); + ASSERT(body_fn_ == nullptr); + if (cancelled_) { + return; + } + body_fn_ = fn; + if (terminated_ || !opt_admin_) { + sendAbortChunkLockHeld(); + return; + } + } + + // Note that nextChunk may be called from any thread -- it's the callers choice, + // including the Envoy main thread, which would occur if the caller initiates + // the request of a chunk upon receipt of the previous chunk. + // + // In that case it may race against the AdminResponse object being deleted, + // in which case the callbacks, held in a shared_ptr, will be cancelled + // from the destructor. If that happens *before* we post to the main thread, + // we will just skip and never call fn. + server_.dispatcher().post(request_next_chunk); +} + +// Called by the user if it is not longer interested in the result of the +// admin request. After calling cancel() the caller must not call nextChunk or +// getHeaders. +void AdminResponse::cancel() { + absl::MutexLock lock(&mutex_); + cancelled_ = true; + headers_fn_ = nullptr; + body_fn_ = nullptr; +} + +bool AdminResponse::cancelled() const { + absl::MutexLock lock(&mutex_); + return cancelled_; +} + +// Called from terminateAdminRequests when the Envoy server +// terminates. After this is called, the caller may need to complete the +// admin response, and so calls to getHeader and nextChunk remain valid, +// resulting in 503 and an empty body. +void AdminResponse::terminate() { + ASSERT_IS_MAIN_OR_TEST_THREAD(); + absl::MutexLock lock(&mutex_); + if (!terminated_) { + terminated_ = true; + sendErrorLockHeld(); + sendAbortChunkLockHeld(); + } +} + +void AdminResponse::requestHeaders() { + ASSERT_IS_MAIN_OR_TEST_THREAD(); + { + absl::MutexLock lock(&mutex_); + if (cancelled_ || terminated_) { + return; + } + } + Server::AdminFilter filter(*opt_admin_); + filter.decodeHeaders(*request_headers_, false); + request_ = opt_admin_->makeRequest(filter); + code_ = request_->start(*response_headers_); + { + absl::MutexLock lock(&mutex_); + if (headers_fn_ == nullptr || cancelled_) { + return; + } + Server::Utility::populateFallbackResponseHeaders(code_, *response_headers_); + headers_fn_(code_, *response_headers_); + headers_fn_ = nullptr; + } +} + +void AdminResponse::requestNextChunk() { + ASSERT_IS_MAIN_OR_TEST_THREAD(); + { + absl::MutexLock lock(&mutex_); + if (cancelled_ || terminated_ || !more_data_) { + return; + } + } + ASSERT(response_.length() == 0); + more_data_ = request_->nextChunk(response_); + { + absl::MutexLock lock(&mutex_); + if (sent_end_stream_ || cancelled_) { + return; + } + sent_end_stream_ = !more_data_; + body_fn_(response_, more_data_); + ASSERT(response_.length() == 0); + body_fn_ = nullptr; + } +} + +void AdminResponse::sendAbortChunkLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (!sent_end_stream_ && body_fn_ != nullptr) { + response_.drain(response_.length()); + body_fn_(response_, false); + sent_end_stream_ = true; + } + body_fn_ = nullptr; +} + +void AdminResponse::sendErrorLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (headers_fn_ != nullptr) { + code_ = Http::Code::InternalServerError; + Server::Utility::populateFallbackResponseHeaders(code_, *response_headers_); + headers_fn_(code_, *response_headers_); + headers_fn_ = nullptr; + } +} + +void AdminResponse::PtrSet::terminateAdminRequests() { + ASSERT_IS_MAIN_OR_TEST_THREAD(); + + absl::MutexLock lock(&mutex_); + accepting_admin_requests_ = false; + for (AdminResponse* response : response_set_) { + // Consider the possibility of response being deleted due to its creator + // dropping its last reference right here. From its destructor it will call + // detachResponse(), which is mutex-ed against this loop, so before the + // memory becomes invalid, the call to terminate will complete. + response->terminate(); + } + response_set_.clear(); +} + +void AdminResponse::PtrSet::attachResponse(AdminResponse* response) { + absl::MutexLock lock(&mutex_); + if (accepting_admin_requests_) { + response_set_.insert(response); + } else { + response->terminate(); + } +} + +void AdminResponse::PtrSet::detachResponse(AdminResponse* response) { + absl::MutexLock lock(&mutex_); + response_set_.erase(response); +} + +} // namespace Envoy diff --git a/source/exe/admin_response.h b/source/exe/admin_response.h new file mode 100644 index 000000000000..b3683b5707c7 --- /dev/null +++ b/source/exe/admin_response.h @@ -0,0 +1,188 @@ +#pragma once + +#include + +#include "envoy/server/instance.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/http/header_map_impl.h" + +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" + +namespace Envoy { + +class AdminResponse; + +// Holds context for a streaming response from the admin system, enabling +// flow-control into another system. This is particularly important when the +// generated response is very large, such that holding it in memory may cause +// fragmentation or out-of-memory failures. It is possible to interleave xDS +// response handling, overload management, and other admin requests during the +// streaming of a long admin response. +// +// There can be be multiple AdminResponses at a time; each are separately +// managed. However they will obtain their data from Envoy functions that +// run on the main thread. +// +// Responses may still be active after the server has shut down, and is no +// longer running its main thread dispatcher. In this state, the callbacks +// will be called with appropriate error codes. +// +// Requests can also be cancelled explicitly by calling cancel(). After +// cancel() is called, no further callbacks will be called by the response. +// +// The lifecycle of an AdminResponse is rendered as a finite state machine +// bubble diagram: +// https://docs.google.com/drawings/d/1njUl1twApEMoxmjaG4b7optTh5fcb_YNcfSnkHbdfq0/view +class AdminResponse : public std::enable_shared_from_this { +public: + // AdminResponse can outlive MainCommonBase. But AdminResponse needs a + // reliable way of knowing whether MainCommonBase is alive, so we do this with + // PtrSet, which is held by MainCommonBase and all the active AdminResponses. + // via shared_ptr. This gives MainCommonBase a reliable way of notifying all + // active responses that it is being shut down, and thus all responses need to + // be terminated. And it gives a reliable way for AdminResponse to detach + // itself, whether or not MainCommonBase is already deleted. + // + // In summary: + // * MainCommonBase can outlive AdminResponse so we need detachResponse. + // * AdminResponse can outlive MainCommonBase, so we need shared_ptr. + class PtrSet { + public: + /** + * Called when an AdminResponse is created. When terminateAdminRequests is + * called, all outstanding response objects have their terminate() methods + * called. + * + * @param response the response pointer to be added to the set. + */ + void attachResponse(AdminResponse* response); + + /** + * Called when an AdminResponse is terminated, either by completing normally + * or having the caller call cancel on it. Either way it needs to be removed + * from the set that will be used by terminateAdminRequests below. + * + * @param response the response pointer to be removed from the set. + */ + void detachResponse(AdminResponse* response); + + /** + * Called after the server run-loop finishes; any outstanding streaming + * admin requests will otherwise hang as the main-thread dispatcher loop + * will no longer run. + */ + void terminateAdminRequests(); + + mutable absl::Mutex mutex_; + absl::flat_hash_set response_set_ ABSL_GUARDED_BY(mutex_); + bool accepting_admin_requests_ ABSL_GUARDED_BY(mutex_) = true; + }; + using SharedPtrSet = std::shared_ptr; + + AdminResponse(Server::Instance& server, absl::string_view path, absl::string_view method, + SharedPtrSet response_set); + ~AdminResponse(); + + /** + * Requests the headers for the response. This can be called from any + * thread, and HeaderFn may also be called from any thread. + * + * HeadersFn will not be called after cancel(). It is invalid to + * to call nextChunk from within HeadersFn -- the caller must trigger + * such a call on another thread, after HeadersFn returns. Calling + * nextChunk from HeadersFn may deadlock. + * + * If the server is shut down during the operation, headersFn may + * be called with a 503, if it has not already been called. + * + * @param fn The function to be called with the headers and status code. + */ + using HeadersFn = std::function; + void getHeaders(HeadersFn fn); + + /** + * Requests a new chunk. This can be called from any thread, and the BodyFn + * callback may also be called from any thread. BodyFn will be called in a + * loop until the Buffer passed to it is fully drained. When 'false' is + * passed as the second arg to BodyFn, that signifies the end of the + * response, and nextChunk must not be called again. + * + * BodyFn will not be called after cancel(). It is invalid to + * to call nextChunk from within BodyFn -- the caller must trigger + * such a call on another thread, after BodyFn returns. Calling + * nextChunk from BodyFn may deadlock. + * + * If the server is shut down during the operation, bodyFn will + * be called with an empty body and 'false' for more_data, if + * this has not already occurred. + * + * @param fn A function to be called on each chunk. + */ + using BodyFn = std::function; + void nextChunk(BodyFn fn); + + /** + * Requests that any outstanding callbacks be dropped. This can be called + * when the context in which the request is made is destroyed. This enables + * an application to implement a. The Response itself is held as a + * shared_ptr as that makes it much easier to manage cancellation across + * multiple threads. + */ + void cancel(); + + /** + * @return whether the request was cancelled. + */ + bool cancelled() const; + +private: + /** + * Called when the server is terminated. This calls any outstanding + * callbacks to be called. If nextChunk is called after termination, + * its callback is called false for the second arg, indicating + * end of stream. + */ + void terminate(); + + void requestHeaders(); + void requestNextChunk(); + void sendAbortChunkLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + void sendErrorLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + Server::Instance& server_; + OptRef opt_admin_; + Buffer::OwnedImpl response_; + Http::Code code_; + Server::Admin::RequestPtr request_; + Http::RequestHeaderMapPtr request_headers_{Http::RequestHeaderMapImpl::create()}; + Http::ResponseHeaderMapPtr response_headers_{Http::ResponseHeaderMapImpl::create()}; + bool more_data_ = true; + + // True if cancel() was explicitly called by the user; headers and body + // callbacks are never called after cancel(). + bool cancelled_ ABSL_GUARDED_BY(mutex_) = false; + + // True if the Envoy server has stopped running its main loop. Headers and + // body requests can be initiated and called back are called after terminate, + // so callers do not have to special case this -- the request will simply fail + // with an empty response. + bool terminated_ ABSL_GUARDED_BY(mutex_) = false; + + // Used to indicate whether the body function has been called with false + // as its second argument. That must always happen at most once, even + // if terminate races with the normal end-of-stream marker. more=false + // may never be sent if the request is cancelled, nor deleted prior to + // it being requested. + bool sent_end_stream_ ABSL_GUARDED_BY(mutex_) = false; + + HeadersFn headers_fn_ ABSL_GUARDED_BY(mutex_); + BodyFn body_fn_ ABSL_GUARDED_BY(mutex_); + mutable absl::Mutex mutex_; + + SharedPtrSet shared_response_set_; +}; +using AdminResponseSharedPtr = std::shared_ptr; + +} // namespace Envoy diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index d18a575f2dd0..87b3342f3335 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -10,6 +10,7 @@ #include "source/common/common/compiler_requirements.h" #include "source/common/common/logger.h" #include "source/common/common/perf_annotation.h" +#include "source/common/common/thread.h" #include "source/common/network/utility.h" #include "source/common/stats/thread_local_store.h" #include "source/exe/platform_impl.h" @@ -56,26 +57,46 @@ MainCommonBase::MainCommonBase(const Server::Options& options, Event::TimeSystem std::unique_ptr process_context) : StrippedMainBase(options, time_system, listener_hooks, component_factory, std::move(platform_impl), std::move(random_generator), - std::move(process_context), createFunction()) {} + std::move(process_context), createFunction()) +#ifdef ENVOY_ADMIN_FUNCTIONALITY + , + shared_response_set_(std::make_shared()) +#endif +{ +} bool MainCommonBase::run() { + // Avoid returning from inside switch cases to minimize uncovered lines + // while avoiding gcc warnings by hitting the final return. + bool ret = false; + switch (options_.mode()) { case Server::Mode::Serve: runServer(); - return true; +#ifdef ENVOY_ADMIN_FUNCTIONALITY + shared_response_set_->terminateAdminRequests(); +#endif + ret = true; + break; case Server::Mode::Validate: - return Server::validateConfig( + ret = Server::validateConfig( options_, Network::Utility::getLocalAddress(options_.localAddressIpVersion()), component_factory_, platform_impl_->threadFactory(), platform_impl_->fileSystem(), process_context_ ? ProcessContextOptRef(std::ref(*process_context_)) : absl::nullopt); + break; case Server::Mode::InitOnly: PERF_DUMP(); - return true; + ret = true; + break; } - return false; // for gcc. + return ret; } #ifdef ENVOY_ADMIN_FUNCTIONALITY + +// This request variant buffers the entire response in one string. New uses +// should opt for the streaming version below, where an AdminResponse object +// is created and used to stream data with flow-control. void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method, const AdminRequestFn& handler) { std::string path_and_query_buf = std::string(path_and_query); @@ -89,6 +110,14 @@ void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string handler(*response_headers, body); }); } + +AdminResponseSharedPtr MainCommonBase::adminRequest(absl::string_view path_and_query, + absl::string_view method) { + auto response = + std::make_shared(*server(), path_and_query, method, shared_response_set_); + shared_response_set_->attachResponse(response.get()); + return response; +} #endif MainCommon::MainCommon(const std::vector& args) diff --git a/source/exe/main_common.h b/source/exe/main_common.h index 349decdec0cc..500f293ce7f1 100644 --- a/source/exe/main_common.h +++ b/source/exe/main_common.h @@ -10,6 +10,10 @@ #include "source/common/stats/symbol_table.h" #include "source/common/stats/thread_local_store.h" #include "source/common/thread_local/thread_local_impl.h" + +#ifdef ENVOY_ADMIN_FUNCTIONALITY +#include "source/exe/admin_response.h" +#endif #include "source/exe/process_wide.h" #include "source/exe/stripped_main_base.h" #include "source/server/listener_hooks.h" @@ -37,20 +41,45 @@ class MainCommonBase : public StrippedMainBase { using AdminRequestFn = std::function; - // Makes an admin-console request by path, calling handler() when complete. - // The caller can initiate this from any thread, but it posts the request - // onto the main thread, so the handler is called asynchronously. - // - // This is designed to be called from downstream consoles, so they can access - // the admin console information stream without opening up a network port. - // - // This should only be called while run() is active; ensuring this is the - // responsibility of the caller. - // - // TODO(jmarantz): consider std::future for encapsulating this delayed request - // semantics, rather than a handler callback. + /** + * Makes an admin-console request by path, calling handler() when complete. + * The caller can initiate this from any thread, but it posts the request + * onto the main thread, so the handler is called asynchronously. + * + * This is designed to be called from downstream consoles, so they can access + * the admin console information stream without opening up a network port. + * + * This should only be called while run() is active; ensuring this is the + * responsibility of the caller. + * + * TODO(jmarantz): consider std::future for encapsulating this delayed request + * semantics, rather than a handler callback. + * + * Consider using the 2-arg version of adminRequest, below, which enables + * streaming of large responses one chunk at a time, without holding + * potentially huge response text in memory. + * + * @param path_and_query the URL to send to admin, including any query params. + * @param method the HTTP method: "GET" or "POST" + * @param handler an async callback that will be sent the serialized headers + * and response. + */ void adminRequest(absl::string_view path_and_query, absl::string_view method, const AdminRequestFn& handler); + + /** + * Initiates a streaming response to an admin request. The caller interacts + * with the returned AdminResponse object, and can thus control the pace of + * handling chunks of response text. + * + * @param path_and_query the URL to send to admin, including any query params. + * @param method the HTTP method: "GET" or "POST" + * @return AdminResponseSharedPtr the response object + */ + AdminResponseSharedPtr adminRequest(absl::string_view path_and_query, absl::string_view method); + +private: + AdminResponse::SharedPtrSet shared_response_set_; #endif }; @@ -82,6 +111,9 @@ class MainCommon { const MainCommonBase::AdminRequestFn& handler) { base_.adminRequest(path_and_query, method, handler); } + AdminResponseSharedPtr adminRequest(absl::string_view path_and_query, absl::string_view method) { + return base_.adminRequest(path_and_query, method); + } #endif static std::string hotRestartVersion(bool hot_restart_enabled); diff --git a/source/server/admin/BUILD b/source/server/admin/BUILD index 3913191068d4..1c453e712a8e 100644 --- a/source/server/admin/BUILD +++ b/source/server/admin/BUILD @@ -388,6 +388,7 @@ envoy_cc_library( name = "utils_lib", srcs = ["utils.cc"], hdrs = ["utils.h"], + visibility = ["//visibility:public"], deps = [ "//envoy/init:manager_interface", "//source/common/common:enum_to_int", diff --git a/source/server/admin/admin.cc b/source/server/admin/admin.cc index 07f3b271381a..0b21be7fef7f 100644 --- a/source/server/admin/admin.cc +++ b/source/server/admin/admin.cc @@ -293,7 +293,7 @@ bool AdminImpl::createNetworkFilterChain(Network::Connection& connection, bool AdminImpl::createFilterChain(Http::FilterChainManager& manager, bool, const Http::FilterChainOptions&) const { Http::FilterFactoryCb factory = [this](Http::FilterChainFactoryCallbacks& callbacks) { - callbacks.addStreamFilter(std::make_shared(createRequestFunction())); + callbacks.addStreamFilter(std::make_shared(*this)); }; manager.applyFilterFactoryCb({}, factory); return true; @@ -494,7 +494,7 @@ bool AdminImpl::removeHandler(const std::string& prefix) { Http::Code AdminImpl::request(absl::string_view path_and_query, absl::string_view method, Http::ResponseHeaderMap& response_headers, std::string& body) { - AdminFilter filter(createRequestFunction()); + AdminFilter filter(*this); auto request_headers = Http::RequestHeaderMapImpl::create(); request_headers->setMethod(method); diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index 86c6ab7f57d3..ef0cde79927b 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -216,9 +216,6 @@ class AdminImpl : public Admin, void closeSocket() override; void addListenerToHandler(Network::ConnectionHandler* handler) override; - GenRequestFn createRequestFunction() const { - return [this](AdminStream& admin_stream) -> RequestPtr { return makeRequest(admin_stream); }; - } uint64_t maxRequestsPerConnection() const override { return 0; } const HttpConnectionManagerProto::ProxyStatusConfig* proxyStatusConfig() const override { return proxy_status_config_.get(); @@ -246,10 +243,7 @@ class AdminImpl : public Admin, ::Envoy::Http::HeaderValidatorStats& getHeaderValidatorStats(Http::Protocol protocol); #endif - /** - * Creates a Request from the request in the admin stream. - */ - RequestPtr makeRequest(AdminStream& admin_stream) const; + RequestPtr makeRequest(AdminStream& admin_stream) const override; /** * Creates a UrlHandler structure from a non-chunked callback. diff --git a/source/server/admin/admin_filter.cc b/source/server/admin/admin_filter.cc index 7a11c251dc88..d9dee2576612 100644 --- a/source/server/admin/admin_filter.cc +++ b/source/server/admin/admin_filter.cc @@ -5,8 +5,7 @@ namespace Envoy { namespace Server { -AdminFilter::AdminFilter(Admin::GenRequestFn admin_handler_fn) - : admin_handler_fn_(admin_handler_fn) {} +AdminFilter::AdminFilter(const Admin& admin) : admin_(admin) {} Http::FilterHeadersStatus AdminFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { @@ -87,12 +86,13 @@ void AdminFilter::onComplete() { auto header_map = Http::ResponseHeaderMapImpl::create(); RELEASE_ASSERT(request_headers_, ""); - Admin::RequestPtr handler = admin_handler_fn_(*this); + Admin::RequestPtr handler = admin_.makeRequest(*this); Http::Code code = handler->start(*header_map); Utility::populateFallbackResponseHeaders(code, *header_map); decoder_callbacks_->encodeHeaders(std::move(header_map), false, StreamInfo::ResponseCodeDetails::get().AdminFilterResponse); + // TODO(#31087): use high/lower watermarks to apply flow-control to the admin http port. bool more_data; do { Buffer::OwnedImpl response; diff --git a/source/server/admin/admin_filter.h b/source/server/admin/admin_filter.h index e163029b2283..2dbe6d01df94 100644 --- a/source/server/admin/admin_filter.h +++ b/source/server/admin/admin_filter.h @@ -28,7 +28,13 @@ class AdminFilter : public Http::PassThroughFilter, absl::string_view path_and_query, Http::ResponseHeaderMap& response_headers, Buffer::OwnedImpl& response, AdminFilter& filter)>; - AdminFilter(Admin::GenRequestFn admin_handler_func); + /** + * Instantiates an AdminFilter. + * + * @param admin the admin context from which to create the filter. This is used + * to create a request object based on the path. + */ + AdminFilter(const Admin& admin); // Http::StreamFilterBase // Handlers relying on the reference should use addOnDestroyCallback() @@ -58,7 +64,7 @@ class AdminFilter : public Http::PassThroughFilter, * Called when an admin request has been completely received. */ void onComplete(); - Admin::GenRequestFn admin_handler_fn_; + const Admin& admin_; Http::RequestHeaderMap* request_headers_{}; std::list> on_destroy_callbacks_; bool end_stream_on_complete_ = true; diff --git a/source/server/config_validation/admin.h b/source/server/config_validation/admin.h index 173afb29d355..8e75607e3f8f 100644 --- a/source/server/config_validation/admin.h +++ b/source/server/config_validation/admin.h @@ -39,6 +39,7 @@ class ValidationAdmin : public Admin { void addListenerToHandler(Network::ConnectionHandler* handler) override; uint32_t concurrency() const override { return 1; } void closeSocket() override {} + RequestPtr makeRequest(AdminStream&) const override { return nullptr; } private: ConfigTrackerImpl config_tracker_; diff --git a/test/exe/BUILD b/test/exe/BUILD index 7be94770af65..065b80e97014 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -1,6 +1,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", + "envoy_cc_test_library", "envoy_package", "envoy_select_admin_functionality", "envoy_sh_test", @@ -60,13 +61,50 @@ envoy_sh_test( ], ) +envoy_cc_test_library( + name = "main_common_test_base_lib", + srcs = ["main_common_test_base.cc"], + hdrs = ["main_common_test_base.h"], + data = [ + "//test/config/integration:google_com_proxy_port_0", + ], + deps = [ + "//source/common/api:api_lib", + "//source/common/stats:isolated_store_lib", + "//source/exe:envoy_main_common_with_core_extensions_lib", + "//source/exe:platform_impl_lib", + "//source/extensions/clusters/logical_dns:logical_dns_cluster_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/test_common:contention_lib", + "//test/test_common:environment_lib", + "//test/test_common:thread_factory_for_test_lib", + ], +) + envoy_cc_test( name = "main_common_test", srcs = envoy_select_admin_functionality(["main_common_test.cc"]), data = [ "//test/config/integration:google_com_proxy_port_0", ], - deps = [ + deps = envoy_select_admin_functionality([":main_common_test_base_lib"]) + [ + "//source/common/api:api_lib", + "//source/exe:envoy_main_common_with_core_extensions_lib", + "//source/exe:platform_impl_lib", + "//source/extensions/clusters/logical_dns:logical_dns_cluster_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/test_common:contention_lib", + "//test/test_common:environment_lib", + ], +) + +envoy_cc_test( + name = "admin_response_test", + srcs = envoy_select_admin_functionality(["admin_response_test.cc"]), + data = [ + "//test/config/integration:google_com_proxy_port_0", + ], + deps = envoy_select_admin_functionality([":main_common_test_base_lib"]) + [ "//source/common/api:api_lib", "//source/exe:envoy_main_common_with_core_extensions_lib", "//source/exe:platform_impl_lib", diff --git a/test/exe/admin_response_test.cc b/test/exe/admin_response_test.cc new file mode 100644 index 000000000000..33a7f10248e5 --- /dev/null +++ b/test/exe/admin_response_test.cc @@ -0,0 +1,351 @@ +#include "test/exe/main_common_test_base.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +class AdminStreamingTest : public AdminRequestTestBase, public testing::Test { +protected: + static constexpr absl::string_view StreamingEndpoint = "/stream"; + + class StreamingAdminRequest : public Envoy::Server::Admin::Request { + public: + static constexpr uint64_t NumChunks = 10; + static constexpr uint64_t BytesPerChunk = 10000; + + StreamingAdminRequest(std::function& get_headers_hook, + std::function& next_chunk_hook) + : chunk_(BytesPerChunk, 'a'), get_headers_hook_(get_headers_hook), + next_chunk_hook_(next_chunk_hook) {} + Http::Code start(Http::ResponseHeaderMap&) override { + get_headers_hook_(); + return Http::Code::OK; + } + bool nextChunk(Buffer::Instance& response) override { + next_chunk_hook_(); + response.add(chunk_); + return --chunks_remaining_ > 0; + } + + private: + const std::string chunk_; + uint64_t chunks_remaining_{NumChunks}; + std::function& get_headers_hook_; + std::function& next_chunk_hook_; + }; + + AdminStreamingTest() : AdminRequestTestBase(Network::Address::IpVersion::v4) { + startEnvoy(); + started_.WaitForNotification(); + Server::Admin& admin = *main_common_->server()->admin(); + admin.addStreamingHandler( + std::string(StreamingEndpoint), "streaming api", + [this](Server::AdminStream&) -> Server::Admin::RequestPtr { + return std::make_unique(get_headers_hook_, next_chunk_hook_); + }, + true, false); + } + + struct ResponseData { + uint64_t num_chunks_{0}; + uint64_t num_bytes_{0}; + Http::Code code_; + std::string content_type_; + }; + + ResponseData runStreamingRequest(AdminResponseSharedPtr response, + std::function chunk_hook = nullptr) { + absl::Notification done; + std::vector out; + absl::Notification headers_notify; + ResponseData response_data; + response->getHeaders( + [&headers_notify, &response_data](Http::Code code, Http::ResponseHeaderMap& headers) { + response_data.code_ = code; + response_data.content_type_ = headers.getContentTypeValue(); + headers_notify.Notify(); + }); + headers_notify.WaitForNotification(); + bool cont = true; + while (cont && !response->cancelled()) { + absl::Notification chunk_notify; + response->nextChunk( + [&chunk_notify, &response_data, &cont](Buffer::Instance& chunk, bool more) { + cont = more; + response_data.num_bytes_ += chunk.length(); + chunk.drain(chunk.length()); + ++response_data.num_chunks_; + chunk_notify.Notify(); + }); + chunk_notify.WaitForNotification(); + if (chunk_hook != nullptr) { + chunk_hook(); + } + } + + return response_data; + } + + /** + * @return a streaming response to a GET of StreamingEndpoint. + */ + AdminResponseSharedPtr streamingResponse() { + return main_common_->adminRequest(StreamingEndpoint, "GET"); + } + + /** + * In order to trigger certain early-exit criteria in a test, we can exploit + * the fact that all the admin responses are delivered on the main thread. + * So we can pause those by blocking the main thread indefinitely. + * + * The provided lambda runs in the main thread, between two notifications + * controlled by this function. + * + * @param fn function to run in the main thread, before interlockMainThread returns. + */ + void interlockMainThread(std::function fn) { + main_common_->dispatcherForTest().post([this, fn] { + resume_.WaitForNotification(); + fn(); + pause_point_.Notify(); + }); + resume_.Notify(); + pause_point_.WaitForNotification(); + } + + /** + * Requests the headers and waits until the headers have been sent. + * + * @param response the response from which to get headers. + */ + void waitForHeaders(AdminResponseSharedPtr response) { + absl::Notification headers_notify; + response->getHeaders( + [&headers_notify](Http::Code, Http::ResponseHeaderMap&) { headers_notify.Notify(); }); + headers_notify.WaitForNotification(); + } + + /** + * Initiates a '/quitquitquit' call and requests the headers for that call, + * but does not wait for the call to complete. We avoid waiting in order to + * trigger a potential race to ensure that MainCommon handles it properly. + */ + void quitAndRequestHeaders() { + AdminResponseSharedPtr quit_response = main_common_->adminRequest("/quitquitquit", "POST"); + quit_response->getHeaders([](Http::Code, Http::ResponseHeaderMap&) {}); + } + + // This variable provides a hook to allow a test method to specify a hook to + // run when nextChunk() is called. This is currently used only for one test, + // CancelAfterAskingForChunk, that initiates a cancel() from within the chunk + // handler. + std::function get_headers_hook_ = []() {}; + std::function next_chunk_hook_ = []() {}; +}; + +TEST_F(AdminStreamingTest, RequestGetStatsAndQuit) { + AdminResponseSharedPtr response = streamingResponse(); + ResponseData response_data = runStreamingRequest(response); + EXPECT_EQ(StreamingAdminRequest::NumChunks, response_data.num_chunks_); + EXPECT_EQ(StreamingAdminRequest::NumChunks * StreamingAdminRequest::BytesPerChunk, + response_data.num_bytes_); + EXPECT_EQ(Http::Code::OK, response_data.code_); + EXPECT_EQ("text/plain; charset=UTF-8", response_data.content_type_); + EXPECT_TRUE(quitAndWait()); +} + +TEST_F(AdminStreamingTest, QuitDuringChunks) { + int quit_counter = 0; + static constexpr int chunks_to_send_before_quitting = 3; + AdminResponseSharedPtr response = streamingResponse(); + ResponseData response_data = runStreamingRequest(response, [&quit_counter, this]() { + if (++quit_counter == chunks_to_send_before_quitting) { + EXPECT_TRUE(quitAndWait()); + } + }); + EXPECT_EQ(4, response_data.num_chunks_); + EXPECT_EQ(chunks_to_send_before_quitting * StreamingAdminRequest::BytesPerChunk, + response_data.num_bytes_); + EXPECT_EQ(Http::Code::OK, response_data.code_); + EXPECT_EQ("text/plain; charset=UTF-8", response_data.content_type_); +} + +TEST_F(AdminStreamingTest, CancelDuringChunks) { + int quit_counter = 0; + static constexpr int chunks_to_send_before_quitting = 3; + AdminResponseSharedPtr response = streamingResponse(); + ResponseData response_data = runStreamingRequest(response, [response, &quit_counter]() { + if (++quit_counter == chunks_to_send_before_quitting) { + response->cancel(); + } + }); + EXPECT_EQ(3, response_data.num_chunks_); // no final call to the chunk handler after cancel. + EXPECT_EQ(chunks_to_send_before_quitting * StreamingAdminRequest::BytesPerChunk, + response_data.num_bytes_); + EXPECT_EQ(Http::Code::OK, response_data.code_); + EXPECT_EQ("text/plain; charset=UTF-8", response_data.content_type_); + EXPECT_TRUE(quitAndWait()); +} + +TEST_F(AdminStreamingTest, CancelBeforeAskingForHeader) { + AdminResponseSharedPtr response = streamingResponse(); + interlockMainThread([response]() { response->cancel(); }); + int header_calls = 0; + + // After 'cancel', the headers function will not be called. + response->getHeaders([&header_calls](Http::Code, Http::ResponseHeaderMap&) { ++header_calls; }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(0, header_calls); +} + +TEST_F(AdminStreamingTest, CancelAfterAskingForHeader1) { + int header_calls = 0; + AdminResponseSharedPtr response = streamingResponse(); + interlockMainThread([&header_calls, response]() { + response->getHeaders([&header_calls](Http::Code, Http::ResponseHeaderMap&) { ++header_calls; }); + response->cancel(); + }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(0, header_calls); +} + +TEST_F(AdminStreamingTest, CancelAfterAskingForHeader2) { + int header_calls = 0; + AdminResponseSharedPtr response = streamingResponse(); + get_headers_hook_ = [&response]() { response->cancel(); }; + response->getHeaders([&header_calls](Http::Code, Http::ResponseHeaderMap&) { ++header_calls; }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(0, header_calls); +} + +TEST_F(AdminStreamingTest, DeleteAfterAskingForHeader1) { + int header_calls = 0; + AdminResponseSharedPtr response = streamingResponse(); + interlockMainThread([&response, &header_calls]() { + response->getHeaders([&header_calls](Http::Code, Http::ResponseHeaderMap&) { ++header_calls; }); + response.reset(); + }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(1, header_calls); +} + +TEST_F(AdminStreamingTest, DeleteAfterAskingForHeader2) { + int header_calls = 0; + AdminResponseSharedPtr response = streamingResponse(); + get_headers_hook_ = [&response]() { response.reset(); }; + response->getHeaders([&header_calls](Http::Code, Http::ResponseHeaderMap&) { ++header_calls; }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(1, header_calls); +} + +TEST_F(AdminStreamingTest, CancelBeforeAskingForChunk1) { + AdminResponseSharedPtr response = streamingResponse(); + waitForHeaders(response); + response->cancel(); + int chunk_calls = 0; + response->nextChunk([&chunk_calls](Buffer::Instance&, bool) { ++chunk_calls; }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(0, chunk_calls); +} + +TEST_F(AdminStreamingTest, CancelBeforeAskingForChunk2) { + AdminResponseSharedPtr response = streamingResponse(); + waitForHeaders(response); + int chunk_calls = 0; + interlockMainThread([&response, &chunk_calls]() { + response->nextChunk([&chunk_calls](Buffer::Instance&, bool) { ++chunk_calls; }); + response->cancel(); + }); + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(0, chunk_calls); +} + +TEST_F(AdminStreamingTest, CancelAfterAskingForChunk) { + AdminResponseSharedPtr response = streamingResponse(); + waitForHeaders(response); + int chunk_calls = 0; + + // Cause the /streaming handler to pause while yielding the next chunk, to hit + // an early exit in requestNextChunk. + next_chunk_hook_ = [response]() { response->cancel(); }; + + interlockMainThread([&chunk_calls, response]() { + response->nextChunk([&chunk_calls](Buffer::Instance&, bool) { ++chunk_calls; }); + }); + + EXPECT_TRUE(quitAndWait()); + EXPECT_EQ(0, chunk_calls); +} + +TEST_F(AdminStreamingTest, QuitBeforeHeaders) { + AdminResponseSharedPtr response = streamingResponse(); + EXPECT_TRUE(quitAndWait()); + ResponseData response_data = runStreamingRequest(response); + EXPECT_EQ(1, response_data.num_chunks_); + EXPECT_EQ(0, response_data.num_bytes_); + EXPECT_EQ(Http::Code::InternalServerError, response_data.code_); + EXPECT_EQ("text/plain; charset=UTF-8", response_data.content_type_); +} + +TEST_F(AdminStreamingTest, QuitDeleteRace1) { + AdminResponseSharedPtr response = streamingResponse(); + // Initiates a streaming quit on the main thread, but do not wait for it. + quitAndRequestHeaders(); + response.reset(); // Races with the quitquitquit + EXPECT_TRUE(waitForEnvoyToExit()); +} + +TEST_F(AdminStreamingTest, QuitDeleteRace2) { + AdminResponseSharedPtr response = streamingResponse(); + adminRequest("/quitquitquit", "POST"); + response.reset(); + EXPECT_TRUE(waitForEnvoyToExit()); +} + +TEST_F(AdminStreamingTest, QuitCancelRace) { + AdminResponseSharedPtr response = streamingResponse(); + quitAndRequestHeaders(); + response->cancel(); // Races with the quitquitquit + EXPECT_TRUE(waitForEnvoyToExit()); +} + +TEST_F(AdminStreamingTest, QuitBeforeCreatingResponse) { + // Initiates a streaming quit on the main thread, and wait for headers, which + // will trigger the termination of the event loop, and subsequent nulling of + // main_common_. However we can pause the test infrastructure after the quit + // takes hold leaving main_common_ in tact, to reproduce a potential race. + pause_after_run_ = true; + adminRequest("/quitquitquit", "POST"); + pause_point_.WaitForNotification(); // run() finished, but main_common_ still exists. + AdminResponseSharedPtr response = streamingResponse(); + ResponseData response_data = runStreamingRequest(response); + EXPECT_EQ(1, response_data.num_chunks_); + EXPECT_EQ(0, response_data.num_bytes_); + EXPECT_EQ(Http::Code::InternalServerError, response_data.code_); + EXPECT_EQ("text/plain; charset=UTF-8", response_data.content_type_); + resume_.Notify(); + EXPECT_TRUE(waitForEnvoyToExit()); + response.reset(); +} + +TEST_F(AdminStreamingTest, TimeoutGettingResponse) { + absl::Notification got_headers; + AdminResponseSharedPtr response = streamingResponse(); + + // Mimics a slow admin response by adding a blocking notification in front + // of a call to initiate an admin request. + main_common_->dispatcherForTest().post([this, response, &got_headers] { + resume_.WaitForNotification(); + response->getHeaders( + [&got_headers](Http::Code, Http::ResponseHeaderMap&) { got_headers.Notify(); }); + pause_point_.Notify(); + }); + + ENVOY_LOG_MISC(info, "Blocking for 5 seconds to test timeout functionality..."); + ASSERT_FALSE(got_headers.WaitForNotificationWithTimeout(absl::Seconds(5))); + resume_.Notify(); + pause_point_.WaitForNotification(); + EXPECT_TRUE(quitAndWait()); +} + +} // namespace Envoy diff --git a/test/exe/main_common_test.cc b/test/exe/main_common_test.cc index 68d142e5b3b8..942a53e3e3b1 100644 --- a/test/exe/main_common_test.cc +++ b/test/exe/main_common_test.cc @@ -1,6 +1,5 @@ #include "envoy/common/platform.h" -#include "source/common/common/lock_guard.h" #include "source/common/common/mutex_tracer_impl.h" #include "source/common/common/random_generator.h" #include "source/common/common/thread.h" @@ -9,6 +8,7 @@ #include "source/exe/platform_impl.h" #include "source/server/options_impl.h" +#include "test/exe/main_common_test_base.h" #include "test/mocks/common.h" #include "test/test_common/contention.h" #include "test/test_common/environment.h" @@ -52,34 +52,10 @@ const std::string& outOfMemoryPattern() { * an argv array that is terminated with nullptr. Identifies the config * file relative to runfiles directory. */ -class MainCommonTest : public testing::TestWithParam { +class MainCommonTest : public MainCommonTestBase, + public testing::TestWithParam { protected: - MainCommonTest() - : config_file_(TestEnvironment::temporaryFileSubstitute( - "test/config/integration/google_com_proxy_port_0.yaml", TestEnvironment::ParamMap(), - TestEnvironment::PortMap(), GetParam())), - argv_({"envoy-static", "--use-dynamic-base-id", "-c", config_file_.c_str(), nullptr}) {} - - const char* const* argv() { return &argv_[0]; } - int argc() { return argv_.size() - 1; } - - // Adds an argument, assuring that argv remains null-terminated. - void addArg(const char* arg) { - ASSERT(!argv_.empty()); - const size_t last = argv_.size() - 1; - ASSERT(argv_[last] == nullptr); // invariant established in ctor, maintained below. - argv_[last] = arg; // guaranteed non-empty - argv_.push_back(nullptr); - } - - // Adds options to make Envoy exit immediately after initialization. - void initOnly() { - addArg("--mode"); - addArg("init_only"); - } - - std::string config_file_; - std::vector argv_; + MainCommonTest() : MainCommonTestBase(GetParam()) {} }; INSTANTIATE_TEST_SUITE_P(IpVersions, MainCommonTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), @@ -223,6 +199,16 @@ TEST_P(MainCommonTest, RetryDynamicBaseIdFails) { #endif } +// Verifies that the Logger::Registry is usable after constructing and +// destructing MainCommon. +TEST_P(MainCommonTest, ConstructDestructLogger) { + VERBOSE_EXPECT_NO_THROW(MainCommon main_common(argc(), argv())); + + const std::string logger_name = "logger"; + spdlog::details::log_msg log_msg(logger_name, spdlog::level::level_enum::err, "error"); + Logger::Registry::getSink()->log(log_msg); +} + // Test that std::set_new_handler() was called and the callback functions as expected. // This test fails under TSAN and ASAN, so don't run it in that build: // [ DEATH ] ==845==ERROR: ThreadSanitizer: requested allocation size 0x3e800000000 @@ -268,96 +254,10 @@ TEST_P(MainCommonDeathTest, OutOfMemoryHandler) { #endif } -class AdminRequestTest : public MainCommonTest { +class AdminRequestTest : public AdminRequestTestBase, + public testing::TestWithParam { protected: - AdminRequestTest() { addArg("--disable-hot-restart"); } - - // Runs an admin request specified in path, blocking until completion, and - // returning the response body. - std::string adminRequest(absl::string_view path, absl::string_view method) { - absl::Notification done; - std::string out; - main_common_->adminRequest( - path, method, - [&done, &out](const Http::HeaderMap& /*response_headers*/, absl::string_view body) { - out = std::string(body); - done.Notify(); - }); - done.WaitForNotification(); - return out; - } - - // Initiates Envoy running in its own thread. - void startEnvoy() { - envoy_thread_ = Thread::threadFactoryForTest().createThread([this]() { - // Note: main_common_ is accessed in the testing thread, but - // is race-free, as MainCommon::run() does not return until - // triggered with an adminRequest POST to /quitquitquit, which - // is done in the testing thread. - main_common_ = std::make_unique(argc(), argv()); - envoy_started_ = true; - started_.Notify(); - pauseResumeInterlock(pause_before_run_); - bool status = main_common_->run(); - pauseResumeInterlock(pause_after_run_); - main_common_.reset(); - envoy_finished_ = true; - envoy_return_ = status; - finished_.Notify(); - }); - } - - // Conditionally pauses at a critical point in the Envoy thread, waiting for - // the test thread to trigger something at that exact line. The test thread - // can then call resume_.Notify() to allow the Envoy thread to resume. - void pauseResumeInterlock(bool enable) { - if (enable) { - pause_point_.Notify(); - resume_.WaitForNotification(); - } - } - - // Wait until Envoy is inside the main server run loop proper. Before entering, Envoy runs any - // pending post callbacks, so it's not reliable to use adminRequest() or post() to do this. - // Generally, tests should not depend on this for correctness, but as a result of - // https://github.com/libevent/libevent/issues/779 we need to for TSAN. This is because the entry - // to event_base_loop() is where the signal base race occurs, but once we're in that loop in - // blocking mode, we're safe to take signals. - // TODO(htuch): Remove when https://github.com/libevent/libevent/issues/779 is fixed. - void waitForEnvoyRun() { - absl::Notification done; - main_common_->dispatcherForTest().post([this, &done] { - struct Sacrifice : Event::DeferredDeletable { - Sacrifice(absl::Notification& notify) : notify_(notify) {} - ~Sacrifice() override { notify_.Notify(); } - absl::Notification& notify_; - }; - auto sacrifice = std::make_unique(done); - // Wait for a deferred delete cleanup, this only happens in the main server run loop. - main_common_->dispatcherForTest().deferredDelete(std::move(sacrifice)); - }); - done.WaitForNotification(); - } - - // Having triggered Envoy to quit (via signal or /quitquitquit), this blocks until Envoy exits. - bool waitForEnvoyToExit() { - finished_.WaitForNotification(); - envoy_thread_->join(); - return envoy_return_; - } - - Stats::IsolatedStoreImpl stats_store_; - std::unique_ptr envoy_thread_; - std::unique_ptr main_common_; - absl::Notification started_; - absl::Notification finished_; - absl::Notification resume_; - absl::Notification pause_point_; - bool envoy_return_{false}; - bool envoy_started_{false}; - bool envoy_finished_{false}; - bool pause_before_run_{false}; - bool pause_after_run_{false}; + AdminRequestTest() : AdminRequestTestBase(GetParam()) {} }; INSTANTIATE_TEST_SUITE_P(IpVersions, AdminRequestTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), @@ -367,8 +267,7 @@ TEST_P(AdminRequestTest, AdminRequestGetStatsAndQuit) { startEnvoy(); started_.WaitForNotification(); EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("filesystem.reopen_failed")); - adminRequest("/quitquitquit", "POST"); - EXPECT_TRUE(waitForEnvoyToExit()); + quitAndWait(); } // no signals on Windows -- could probably make this work with GenerateConsoleCtrlEvent @@ -459,8 +358,7 @@ TEST_P(AdminRequestTest, AdminRequestBeforeRun) { // We don't get a notification when run(), so it's not safe to check whether the // admin handler is called until after we quit. - adminRequest("/quitquitquit", "POST"); - EXPECT_TRUE(waitForEnvoyToExit()); + quitAndWait(); EXPECT_TRUE(admin_handler_was_called); // This just checks that some stat output was reported. We could pick any stat. @@ -515,14 +413,4 @@ TEST_P(AdminRequestTest, AdminRequestAfterRun) { EXPECT_EQ(1, lambda_destroy_count); } -// Verifies that the Logger::Registry is usable after constructing and -// destructing MainCommon. -TEST_P(MainCommonTest, ConstructDestructLogger) { - VERBOSE_EXPECT_NO_THROW(MainCommon main_common(argc(), argv())); - - const std::string logger_name = "logger"; - spdlog::details::log_msg log_msg(logger_name, spdlog::level::level_enum::err, "error"); - Logger::Registry::getSink()->log(log_msg); -} - } // namespace Envoy diff --git a/test/exe/main_common_test_base.cc b/test/exe/main_common_test_base.cc new file mode 100644 index 000000000000..d0f3960b4148 --- /dev/null +++ b/test/exe/main_common_test_base.cc @@ -0,0 +1,117 @@ +#include "test/exe/main_common_test_base.h" + +#include "source/common/common/thread.h" + +#include "test/test_common/thread_factory_for_test.h" + +namespace Envoy { + +MainCommonTestBase::MainCommonTestBase(Network::Address::IpVersion version) + : config_file_(TestEnvironment::temporaryFileSubstitute( + "test/config/integration/google_com_proxy_port_0.yaml", TestEnvironment::ParamMap(), + TestEnvironment::PortMap(), version)), + argv_({"envoy-static", "--use-dynamic-base-id", "-c", config_file_.c_str(), nullptr}) {} + +const char* const* MainCommonTestBase::argv() { return &argv_[0]; } +int MainCommonTestBase::argc() { return argv_.size() - 1; } + +// Adds an argument, assuring that argv remains null-terminated. +void MainCommonTestBase::addArg(const char* arg) { + ASSERT(!argv_.empty()); + const size_t last = argv_.size() - 1; + ASSERT(argv_[last] == nullptr); // invariant established in ctor, maintained below. + argv_[last] = arg; // guaranteed non-empty + argv_.push_back(nullptr); +} + +// Adds options to make Envoy exit immediately after initialization. +void MainCommonTestBase::initOnly() { + addArg("--mode"); + addArg("init_only"); +} + +AdminRequestTestBase::AdminRequestTestBase(Network::Address::IpVersion version) + : MainCommonTestBase(version) { + addArg("--disable-hot-restart"); +} + +// Runs an admin request specified in path, blocking until completion, and +// returning the response body. +std::string AdminRequestTestBase::adminRequest(absl::string_view path, absl::string_view method) { + absl::Notification done; + std::string out; + main_common_->adminRequest( + path, method, + [&done, &out](const Http::HeaderMap& /*response_headers*/, absl::string_view body) { + out = std::string(body); + done.Notify(); + }); + done.WaitForNotification(); + return out; +} + +// Initiates Envoy running in its own thread. +void AdminRequestTestBase::startEnvoy() { + envoy_thread_ = Thread::threadFactoryForTest().createThread([this]() { + // Note: main_common_ is accessed in the testing thread, but + // is race-free, as MainCommon::run() does not return until + // triggered with an adminRequest POST to /quitquitquit, which + // is done in the testing thread. + main_common_ = std::make_unique(argc(), argv()); + envoy_started_ = true; + started_.Notify(); + pauseResumeInterlock(pause_before_run_); + bool status = main_common_->run(); + pauseResumeInterlock(pause_after_run_); + main_common_.reset(); + envoy_finished_ = true; + envoy_return_ = status; + finished_.Notify(); + }); +} + +// Conditionally pauses at a critical point in the Envoy thread, waiting for +// the test thread to trigger something at that exact line. The test thread +// can then call resume_.Notify() to allow the Envoy thread to resume. +void AdminRequestTestBase::pauseResumeInterlock(bool enable) { + if (enable) { + pause_point_.Notify(); + resume_.WaitForNotification(); + } +} + +// Wait until Envoy is inside the main server run loop proper. Before entering, Envoy runs any +// pending post callbacks, so it's not reliable to use adminRequest() or post() to do this. +// Generally, tests should not depend on this for correctness, but as a result of +// https://github.com/libevent/libevent/issues/779 we need to for TSAN. This is because the entry +// to event_base_loop() is where the signal base race occurs, but once we're in that loop in +// blocking mode, we're safe to take signals. +// TODO(htuch): Remove when https://github.com/libevent/libevent/issues/779 is fixed. +void AdminRequestTestBase::waitForEnvoyRun() { + absl::Notification done; + main_common_->dispatcherForTest().post([this, &done] { + struct Sacrifice : Event::DeferredDeletable { + Sacrifice(absl::Notification& notify) : notify_(notify) {} + ~Sacrifice() override { notify_.Notify(); } + absl::Notification& notify_; + }; + auto sacrifice = std::make_unique(done); + // Wait for a deferred delete cleanup, this only happens in the main server run loop. + main_common_->dispatcherForTest().deferredDelete(std::move(sacrifice)); + }); + done.WaitForNotification(); +} + +// Having triggered Envoy to quit (via signal or /quitquitquit), this blocks until Envoy exits. +bool AdminRequestTestBase::waitForEnvoyToExit() { + finished_.WaitForNotification(); + envoy_thread_->join(); + return envoy_return_; +} + +bool AdminRequestTestBase::quitAndWait() { + adminRequest("/quitquitquit", "POST"); + return waitForEnvoyToExit(); +} + +} // namespace Envoy diff --git a/test/exe/main_common_test_base.h b/test/exe/main_common_test_base.h new file mode 100644 index 000000000000..91ae895dd69f --- /dev/null +++ b/test/exe/main_common_test_base.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +#include "source/common/stats/isolated_store_impl.h" +#include "source/exe/main_common.h" + +#include "test/test_common/environment.h" + +#include "absl/synchronization/notification.h" + +namespace Envoy { + +class MainCommonTestBase { +protected: + MainCommonTestBase(Network::Address::IpVersion version); + const char* const* argv(); + int argc(); + + // Adds an argument, assuring that argv remains null-terminated. + void addArg(const char* arg); + + // Adds options to make Envoy exit immediately after initialization. + void initOnly(); + + std::string config_file_; + std::vector argv_; +}; + +class AdminRequestTestBase : public MainCommonTestBase { +protected: + AdminRequestTestBase(Network::Address::IpVersion version); + + // Runs an admin request specified in path, blocking until completion, and + // returning the response body. + std::string adminRequest(absl::string_view path, absl::string_view method); + + // Initiates Envoy running in its own thread. + void startEnvoy(); + + // Conditionally pauses at a critical point in the Envoy thread, waiting for + // the test thread to trigger something at that exact line. The test thread + // can then call resume_.Notify() to allow the Envoy thread to resume. + void pauseResumeInterlock(bool enable); + + // Wait until Envoy is inside the main server run loop proper. Before entering, Envoy runs any + // pending post callbacks, so it's not reliable to use adminRequest() or post() to do this. + // Generally, tests should not depend on this for correctness, but as a result of + // https://github.com/libevent/libevent/issues/779 we need to for TSAN. This is because the entry + // to event_base_loop() is where the signal base race occurs, but once we're in that loop in + // blocking mode, we're safe to take signals. + // TODO(htuch): Remove when https://github.com/libevent/libevent/issues/779 is fixed. + void waitForEnvoyRun(); + + // Having triggered Envoy to quit (via signal or /quitquitquit), this blocks until Envoy exits. + bool waitForEnvoyToExit(); + + // Sends a quit request to the server, and waits for Envoy to exit. Returns + // true if successful. + bool quitAndWait(); + + Stats::IsolatedStoreImpl stats_store_; + std::unique_ptr envoy_thread_; + std::unique_ptr main_common_; + absl::Notification started_; + absl::Notification finished_; + absl::Notification resume_; + absl::Notification pause_point_; + bool envoy_return_{false}; + bool envoy_started_{false}; + bool envoy_finished_{false}; + bool pause_before_run_{false}; + bool pause_after_run_{false}; +}; + +} // namespace Envoy diff --git a/test/integration/admin_html/test_server.cc b/test/integration/admin_html/test_server.cc index 4a53d7661bb9..d371d7b22108 100644 --- a/test/integration/admin_html/test_server.cc +++ b/test/integration/admin_html/test_server.cc @@ -14,7 +14,8 @@ namespace { * a query param but it could not be found. * * This test-server is only for testing; it potentially makes the - * entire file-system avail + * entire file-system available to HTTP clients, so this should not + * be used for production systems. */ Http::Code testCallback(Http::ResponseHeaderMap& response_headers, Buffer::Instance& response, Server::AdminStream& admin_stream) { diff --git a/test/mocks/server/admin.h b/test/mocks/server/admin.h index c7308d6a044e..a5be3ab379a0 100644 --- a/test/mocks/server/admin.h +++ b/test/mocks/server/admin.h @@ -40,6 +40,7 @@ class MockAdmin : public Admin { MOCK_METHOD(void, addListenerToHandler, (Network::ConnectionHandler * handler)); MOCK_METHOD(uint32_t, concurrency, (), (const)); MOCK_METHOD(void, closeSocket, ()); + MOCK_METHOD(RequestPtr, makeRequest, (AdminStream & admin_stream), (const)); NiceMock config_tracker_; NiceMock socket_; diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index d34afbad937e..5ee457fe70b3 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -21,7 +21,7 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/signal:87.2" # Death tests don't report LCOV "source/common/thread:0.0" # Death tests don't report LCOV "source/common/watchdog:58.6" # Death tests don't report LCOV -"source/exe:90.3" +"source/exe:94.0" # increased by #32346, need coverage for terminate_handler and hot restart failures "source/extensions/clusters/common:91.5" # This can be increased again once `#24903` lands "source/extensions/common:93.0" #flaky: be careful adjusting "source/extensions/common/proxy_protocol:93.8" # Adjusted for security patch diff --git a/test/server/admin/admin_filter_test.cc b/test/server/admin/admin_filter_test.cc index 9627cfd1ca31..fbf6c93cd72d 100644 --- a/test/server/admin/admin_filter_test.cc +++ b/test/server/admin/admin_filter_test.cc @@ -7,27 +7,28 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::ByMove; using testing::InSequence; using testing::NiceMock; +using testing::Return; namespace Envoy { namespace Server { class AdminFilterTest : public testing::TestWithParam { public: - AdminFilterTest() : filter_(adminHandlerCallback), request_headers_{{":path", "/"}} { + AdminFilterTest() : filter_(admin_), request_headers_{{":path", "/"}} { + EXPECT_CALL(admin_, makeRequest(_)).WillOnce(Return(ByMove(adminHandlerCallback()))); filter_.setDecoderFilterCallbacks(callbacks_); } - NiceMock server_; + NiceMock admin_; Stats::IsolatedStoreImpl listener_scope_; AdminFilter filter_; NiceMock callbacks_; Http::TestRequestHeaderMapImpl request_headers_; - static Admin::RequestPtr adminHandlerCallback(AdminStream& admin_stream) { - // silence compiler warnings for unused params - UNREFERENCED_PARAMETER(admin_stream); + static Admin::RequestPtr adminHandlerCallback() { return AdminImpl::makeStaticTextRequest("OK\n", Http::Code::OK); } }; diff --git a/test/server/admin/admin_instance.cc b/test/server/admin/admin_instance.cc index 7b18c448e18f..a2b4d2f15d17 100644 --- a/test/server/admin/admin_instance.cc +++ b/test/server/admin/admin_instance.cc @@ -10,7 +10,7 @@ namespace Server { AdminInstanceTest::AdminInstanceTest() : cpu_profile_path_(TestEnvironment::temporaryPath("envoy.prof")), admin_(cpu_profile_path_, server_, false), request_headers_{{":path", "/"}}, - admin_filter_(admin_.createRequestFunction()) { + admin_filter_(admin_) { std::list access_logs; Filesystem::FilePathAndType file_info{Filesystem::DestinationType::File, "/dev/null"}; access_logs.emplace_back(new Extensions::AccessLoggers::File::FileAccessLog( diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 6d838b121c91..ed55171c3d66 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -59,6 +59,7 @@ envoy_cc_test( "//source/extensions/filters/network/http_connection_manager:config", "//source/extensions/listener_managers/validation_listener_manager:validation_listener_manager_lib", "//source/extensions/transport_sockets/tls:config", + "//source/server/admin:admin_filter_lib", "//source/server/config_validation:server_lib", "//test/integration:integration_lib", "//test/mocks/network:network_mocks", diff --git a/test/server/config_validation/server_test.cc b/test/server/config_validation/server_test.cc index 8102b84b01d4..6462503b2cc8 100644 --- a/test/server/config_validation/server_test.cc +++ b/test/server/config_validation/server_test.cc @@ -4,6 +4,7 @@ #include "envoy/server/filter_config.h" #include "source/extensions/listener_managers/validation_listener_manager/validation_listener_manager.h" +#include "source/server/admin/admin_filter.h" #include "source/server/config_validation/server.h" #include "source/server/process_context_impl.h" @@ -207,6 +208,8 @@ TEST_P(ValidationServerTest, DummyMethodsTest) { server.admin()->addListenerToHandler(nullptr); server.admin()->closeSocket(); server.admin()->startHttpListener({}, nullptr, nullptr); + AdminFilter filter(*server.admin()); + EXPECT_TRUE(server.admin()->makeRequest(filter) == nullptr); Network::MockTcpListenerCallbacks listener_callbacks; Network::MockListenerConfig listener_config; From e6ba54383a5832232efadeb40b2c9c85bafcd83b Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 18 Mar 2024 15:51:00 -0400 Subject: [PATCH 32/36] tooling: envoy-ci ping (#32905) Signed-off-by: Alyssa Wilk --- tools/repo/notify.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/repo/notify.py b/tools/repo/notify.py index 275666b358d7..ec6495494446 100644 --- a/tools/repo/notify.py +++ b/tools/repo/notify.py @@ -300,6 +300,11 @@ async def post_to_oncall(self): text=( f"*{num_issues} Untriaged Issues* (please tag and cc area experts)\n<{ISSUE_LINK}|{ISSUE_LINK}>" )) + await self.send_message( + channel='#envoy-ci', + text=( + f"<@{oncall_handle}> please triage flakes per " + )) except SlackApiError as e: self.log.error(f"Unexpected error {e.response['error']}") From 6197741605052ce8eb60d42a94cf9b2cd146e8a1 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Mon, 18 Mar 2024 15:18:47 -0500 Subject: [PATCH 33/36] mobile: Remove jni_log (#32958) Signed-off-by: Fredy Wijaya --- mobile/library/jni/android_network_utility.cc | 3 - mobile/library/jni/java_jni_support.cc | 4 -- mobile/library/jni/jni_impl.cc | 59 +------------------ mobile/library/jni/jni_support.h | 4 -- mobile/library/jni/ndk_jni_support.cc | 12 ---- mobile/test/jni/test_jni_impl.cc | 12 ---- 6 files changed, 2 insertions(+), 92 deletions(-) diff --git a/mobile/library/jni/android_network_utility.cc b/mobile/library/jni/android_network_utility.cc index e8e2f71604cd..0a0fb87b2702 100644 --- a/mobile/library/jni/android_network_utility.cc +++ b/mobile/library/jni/android_network_utility.cc @@ -88,7 +88,6 @@ LocalRefUniquePtr callJvmVerifyX509CertChain(Envoy::JNI::JniHelper& jni const std::vector& cert_chain, std::string auth_type, absl::string_view hostname) { - jni_log("[Envoy]", "jvmVerifyX509CertChain"); LocalRefUniquePtr jcls_AndroidNetworkLibrary = findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); jmethodID jmid_verifyServerCertificates = jni_helper.getStaticMethodId( @@ -107,8 +106,6 @@ LocalRefUniquePtr callJvmVerifyX509CertChain(Envoy::JNI::JniHelper& jni envoy_cert_validation_result verifyX509CertChain(const std::vector& certs, absl::string_view hostname) { - jni_log("[Envoy]", "verifyX509CertChain"); - envoy_cert_verify_status_t result; bool is_issued_by_known_root; std::vector verified_chain; diff --git a/mobile/library/jni/java_jni_support.cc b/mobile/library/jni/java_jni_support.cc index 003cc31754b6..346e2cc1afbd 100644 --- a/mobile/library/jni/java_jni_support.cc +++ b/mobile/library/jni/java_jni_support.cc @@ -2,10 +2,6 @@ // NOLINT(namespace-envoy) -int jni_log_fmt(const char* /*tag*/, const char* /*fmt*/, void* /*value*/) { return 0; } - -int jni_log(const char* /*tag*/, const char* /*str*/) { return 0; } - jint attach_jvm(JavaVM* vm, JNIEnv** p_env, void* thr_args) { return vm->AttachCurrentThread(reinterpret_cast(p_env), thr_args); } diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index 78dd725fd581..a2ec47ba23f4 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -38,7 +38,6 @@ static void jvm_on_engine_running(void* context) { return; } - jni_log("[Envoy]", "jvm_on_engine_running"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); Envoy::JNI::LocalRefUniquePtr jcls_JvmonEngineRunningContext = @@ -72,7 +71,6 @@ static void jvm_on_log(envoy_log_level log_level, envoy_data data, const void* c } static void jvm_on_exit(void*) { - jni_log("[Envoy]", "library is exiting"); // Note that this is not dispatched because the thread that // needs to be detached is the engine thread. // This function is called from the context of the engine's @@ -81,7 +79,6 @@ static void jvm_on_exit(void*) { } static void jvm_on_track(envoy_map events, const void* context) { - jni_log("[Envoy]", "jvm_on_track"); if (context == nullptr) { return; } @@ -125,7 +122,6 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr // TODO(goaway): The retained_context leaks, but it's tied to the life of the engine. // This will need to be updated for https://github.com/envoyproxy/envoy-mobile/issues/332. jobject retained_context = env->NewGlobalRef(j_event_tracker); - jni_log_fmt("[Envoy]", "retained_context: %p", retained_context); event_tracker.track = jvm_on_track; event_tracker.context = retained_context; } @@ -180,7 +176,6 @@ extern "C" JNIEXPORT jstring JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_dumpStats(JNIEnv* env, jclass, // class jlong engine_handle) { - jni_log("[Envoy]", "dumpStats"); auto engine = reinterpret_cast(engine_handle); std::string stats = engine->dumpStats(); Envoy::JNI::JniHelper jni_helper(env); @@ -228,7 +223,6 @@ static void passHeaders(const char* method, const Envoy::Types::ManagedEnvoyHead static Envoy::JNI::LocalRefUniquePtr jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers, bool end_stream, envoy_stream_intel stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_headers"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); passHeaders("passHeader", headers, j_context); @@ -337,7 +331,6 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_data(const char* metho bool end_stream, envoy_stream_intel stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_data"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -432,18 +425,13 @@ static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data /*pending_headers*/ pending_headers}; } -static void jvm_on_metadata(envoy_headers metadata, envoy_stream_intel /*stream_intel*/, - void* /*context*/) { - jni_log("[Envoy]", "jvm_on_metadata"); - jni_log("[Envoy]", std::to_string(metadata.length).c_str()); -} +static void jvm_on_metadata(envoy_headers /* metadata */, envoy_stream_intel /*stream_intel*/, + void* /*context*/) {} static Envoy::JNI::LocalRefUniquePtr jvm_on_trailers(const char* method, envoy_headers trailers, envoy_stream_intel stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_trailers"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); passHeaders("passHeader", trailers, j_context); @@ -555,8 +543,6 @@ jvm_http_filter_on_response_trailers(envoy_headers trailers, envoy_stream_intel static void jvm_http_filter_set_request_callbacks(envoy_http_filter_callbacks callbacks, const void* context) { - jni_log("[Envoy]", "jvm_http_filter_set_request_callbacks"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(const_cast(context)); Envoy::JNI::LocalRefUniquePtr jcls_JvmCallbackContext = @@ -575,8 +561,6 @@ static void jvm_http_filter_set_request_callbacks(envoy_http_filter_callbacks ca static void jvm_http_filter_set_response_callbacks(envoy_http_filter_callbacks callbacks, const void* context) { - jni_log("[Envoy]", "jvm_http_filter_set_response_callbacks"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(const_cast(context)); Envoy::JNI::LocalRefUniquePtr jcls_JvmCallbackContext = @@ -596,8 +580,6 @@ static envoy_filter_resume_status jvm_http_filter_on_resume(const char* method, envoy_headers* headers, envoy_data* data, envoy_headers* trailers, bool end_stream, envoy_stream_intel stream_intel, const void* context) { - jni_log("[Envoy]", "jvm_on_resume"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(const_cast(context)); jlong headers_length = -1; @@ -666,8 +648,6 @@ jvm_http_filter_on_resume_response(envoy_headers* headers, envoy_data* data, static void call_jvm_on_complete(envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_complete"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -686,7 +666,6 @@ static void call_jvm_on_complete(envoy_stream_intel stream_intel, static void call_jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_error"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -717,8 +696,6 @@ static void jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, static void call_jvm_on_cancel(envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_cancel"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -761,8 +738,6 @@ static void jvm_http_filter_on_cancel(envoy_stream_intel stream_intel, } static void jvm_on_send_window_available(envoy_stream_intel stream_intel, void* context) { - jni_log("[Envoy]", "jvm_on_send_window_available"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -780,7 +755,6 @@ static void jvm_on_send_window_available(envoy_stream_intel stream_intel, void* // JvmKeyValueStoreContext static envoy_data jvm_kv_store_read(envoy_data key, const void* context) { - jni_log("[Envoy]", "jvm_kv_store_read"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(const_cast(context)); @@ -799,7 +773,6 @@ static envoy_data jvm_kv_store_read(envoy_data key, const void* context) { } static void jvm_kv_store_remove(envoy_data key, const void* context) { - jni_log("[Envoy]", "jvm_kv_store_remove"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(const_cast(context)); @@ -814,7 +787,6 @@ static void jvm_kv_store_remove(envoy_data key, const void* context) { } static void jvm_kv_store_save(envoy_data key, envoy_data value, const void* context) { - jni_log("[Envoy]", "jvm_kv_store_save"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(const_cast(context)); @@ -833,15 +805,11 @@ static void jvm_kv_store_save(envoy_data key, envoy_data value, const void* cont // JvmFilterFactoryContext static const void* jvm_http_filter_init(const void* context) { - jni_log("[Envoy]", "jvm_filter_init"); - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); envoy_http_filter* c_filter = static_cast(const_cast(context)); jobject j_context = static_cast(const_cast(c_filter->static_context)); - jni_log_fmt("[Envoy]", "j_context: %p", j_context); - Envoy::JNI::LocalRefUniquePtr jcls_JvmFilterFactoryContext = jni_helper.getObjectClass(j_context); jmethodID jmid_create = @@ -850,7 +818,6 @@ static const void* jvm_http_filter_init(const void* context) { Envoy::JNI::LocalRefUniquePtr j_filter = jni_helper.callObjectMethod(j_context, jmid_create); - jni_log_fmt("[Envoy]", "j_filter: %p", j_filter.get()); Envoy::JNI::GlobalRefUniquePtr retained_filter = jni_helper.newGlobalRef(j_filter.get()); return retained_filter.release(); @@ -913,10 +880,7 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_registerKeyValueStore(JNIEnv* e // TODO(goaway): The java context here leaks, but it's tied to the life of the engine. // This will need to be updated for https://github.com/envoyproxy/envoy-mobile/issues/332 - jni_log("[Envoy]", "registerKeyValueStore"); - jni_log_fmt("[Envoy]", "j_context: %p", j_context); jobject retained_context = env->NewGlobalRef(j_context); - jni_log_fmt("[Envoy]", "retained_context: %p", retained_context); envoy_kv_store* api = static_cast(safe_malloc(sizeof(envoy_kv_store))); api->save = jvm_kv_store_save; api->read = jvm_kv_store_read; @@ -938,10 +902,7 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_registerFilterFactory(JNIEnv* e // TODO(goaway): Everything here leaks, but it's all be tied to the life of the engine. // This will need to be updated for https://github.com/envoyproxy/envoy-mobile/issues/332 - jni_log("[Envoy]", "registerFilterFactory"); - jni_log_fmt("[Envoy]", "j_context: %p", j_context); jobject retained_context = env->NewGlobalRef(j_context); - jni_log_fmt("[Envoy]", "retained_context: %p", retained_context); envoy_http_filter* api = static_cast(safe_malloc(sizeof(envoy_http_filter))); api->init_filter = jvm_http_filter_init; api->on_request_headers = jvm_http_filter_on_request_headers; @@ -970,7 +931,6 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_registerFilterFactory(JNIEnv* e extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callResumeIteration( JNIEnv* env, jclass, jlong callback_handle, jobject j_context) { - jni_log("[Envoy]", "callResumeIteration"); // Context is only passed here to ensure it's not inadvertently gc'd during execution of this // function. To be extra safe, do an explicit retain with a GlobalRef. jobject retained_context = env->NewGlobalRef(j_context); @@ -983,7 +943,6 @@ Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callResumeIte extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callResetIdleTimer( JNIEnv* env, jclass, jlong callback_handle, jobject j_context) { - jni_log("[Envoy]", "callResetIdleTimer"); // Context is only passed here to ensure it's not inadvertently gc'd during execution of this // function. To be extra safe, do an explicit retain with a GlobalRef. jobject retained_context = env->NewGlobalRef(j_context); @@ -996,7 +955,6 @@ Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callResetIdle extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callReleaseCallbacks( JNIEnv* /*env*/, jclass, jlong callback_handle) { - jni_log("[Envoy]", "callReleaseCallbacks"); envoy_http_filter_callbacks* callbacks = reinterpret_cast(callback_handle); callbacks->release_callbacks(callbacks->callback_context); @@ -1017,9 +975,6 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra JNIEnv* env, jclass, jlong engine_handle, jlong stream_handle, jobject data, jint length, jboolean end_stream) { Envoy::JNI::JniHelper jni_helper(env); - if (end_stream) { - jni_log("[Envoy]", "jvm_send_data_end_stream"); - } return reinterpret_cast(engine_handle) ->sendData(static_cast(stream_handle), Envoy::JNI::javaByteBufferToEnvoyData(jni_helper, data, length), end_stream); @@ -1036,9 +991,6 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendDataByteArray(JNIEnv* env, jbyteArray data, jint length, jboolean end_stream) { Envoy::JNI::JniHelper jni_helper(env); - if (end_stream) { - jni_log("[Envoy]", "jvm_send_data_end_stream"); - } return reinterpret_cast(engine_handle) ->sendData(static_cast(stream_handle), Envoy::JNI::javaByteArrayToEnvoyData(jni_helper, data, length), end_stream); @@ -1057,7 +1009,6 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendTrailers( JNIEnv* env, jclass, jlong engine_handle, jlong stream_handle, jobjectArray trailers) { Envoy::JNI::JniHelper jni_helper(env); - jni_log("[Envoy]", "jvm_send_trailers"); return reinterpret_cast(engine_handle) ->sendTrailers(static_cast(stream_handle), Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, trailers)); @@ -1341,7 +1292,6 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_resetConnectivityState(JNIEnv* /*env*/, jclass, // class jlong engine) { - jni_log("[Envoy]", "resetConnectivityState"); return reinterpret_cast(engine)->resetConnectivityState(); } @@ -1349,7 +1299,6 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_setPreferredNetwork(JNIEnv* /*env*/, jclass, // class jlong engine, jint network) { - jni_log("[Envoy]", "setting preferred network"); return reinterpret_cast(engine)->setPreferredNetwork( static_cast(network)); } @@ -1358,8 +1307,6 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra JNIEnv* env, jclass, // class jlong engine, jstring host, jint port) { - jni_log("[Envoy]", "setProxySettings"); - Envoy::JNI::JniHelper jni_helper(env); Envoy::JNI::StringUtfUniquePtr java_host = jni_helper.getStringUtfChars(host, nullptr); const uint16_t native_port = static_cast(port); @@ -1371,7 +1318,6 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra } static void jvm_add_test_root_certificate(const uint8_t* cert, size_t len) { - jni_log("[Envoy]", "jvm_add_test_root_certificate"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); Envoy::JNI::LocalRefUniquePtr jcls_AndroidNetworkLibrary = Envoy::JNI::findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); @@ -1385,7 +1331,6 @@ static void jvm_add_test_root_certificate(const uint8_t* cert, size_t len) { } static void jvm_clear_test_root_certificate() { - jni_log("[Envoy]", "jvm_clear_test_root_certificate"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); Envoy::JNI::LocalRefUniquePtr jcls_AndroidNetworkLibrary = Envoy::JNI::findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); diff --git a/mobile/library/jni/jni_support.h b/mobile/library/jni/jni_support.h index 45fef67f3fa3..a66c87648710 100644 --- a/mobile/library/jni/jni_support.h +++ b/mobile/library/jni/jni_support.h @@ -4,8 +4,4 @@ // NOLINT(namespace-envoy) -extern "C" int jni_log_fmt(const char* tag, const char* fmt, void* value); - -extern "C" int jni_log(const char* tag, const char* str); - extern "C" jint attach_jvm(JavaVM* vm, JNIEnv** p_env, void* thr_args); diff --git a/mobile/library/jni/ndk_jni_support.cc b/mobile/library/jni/ndk_jni_support.cc index ecd4f2c98a82..98403edbbfef 100644 --- a/mobile/library/jni/ndk_jni_support.cc +++ b/mobile/library/jni/ndk_jni_support.cc @@ -1,19 +1,7 @@ -#include - #include "library/jni/jni_support.h" // NOLINT(namespace-envoy) -int jni_log_fmt(const char* /*tag*/, const char* /*fmt*/, void* /*value*/) { - // For debug logging, use __android_log_print(ANDROID_LOG_VERBOSE, tag, fmt, value); - return 0; -} - -int jni_log(const char* /*tag*/, const char* /*str*/) { - // For debug logging, use __android_log_write(ANDROID_LOG_VERBOSE, tag, str); - return 0; -} - jint attach_jvm(JavaVM* vm, JNIEnv** p_env, void* thr_args) { return vm->AttachCurrentThread(p_env, thr_args); } diff --git a/mobile/test/jni/test_jni_impl.cc b/mobile/test/jni/test_jni_impl.cc index 455665ac3b45..5e7477d47554 100644 --- a/mobile/test/jni/test_jni_impl.cc +++ b/mobile/test/jni/test_jni_impl.cc @@ -13,70 +13,60 @@ extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeStartHttpProxyTestServer(JNIEnv* env, jclass clazz) { - jni_log("[QTS]", "starting server"); start_server(Envoy::TestServerType::HTTP_PROXY); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeStartHttpsProxyTestServer( JNIEnv* env, jclass clazz) { - jni_log("[QTS]", "starting server"); start_server(Envoy::TestServerType::HTTPS_PROXY); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeStartHttp3TestServer(JNIEnv* env, jclass clazz) { - jni_log("[QTS]", "starting server"); start_server(Envoy::TestServerType::HTTP3); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeGetServerPort(JNIEnv* env, jclass clazz) { - jni_log("[QTS]", "getting server port"); return get_server_port(); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeStartHttp2TestServer(JNIEnv* env, jclass clazz) { - jni_log("[QTS]", "starting server"); start_server(Envoy::TestServerType::HTTP2_WITH_TLS); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeShutdownTestServer(JNIEnv* env, jclass clazz) { - jni_log("[QTS]", "shutting down server"); shutdown_server(); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeInitXdsTestServer(JNIEnv* env, jclass clazz) { - jni_log("[XTS]", "initializing xDS server"); initXdsServer(); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeStartXdsTestServer(JNIEnv* env, jclass clazz) { - jni_log("[XTS]", "starting xDS server"); startXdsServer(); } extern "C" JNIEXPORT jstring JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeGetXdsTestServerHost(JNIEnv* env, jclass clazz) { - jni_log("[XTS]", "getting xDS server host"); return env->NewStringUTF(getXdsServerHost()); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeGetXdsTestServerPort(JNIEnv* env, jclass clazz) { - jni_log("[XTS]", "getting xDS server port"); return getXdsServerPort(); } @@ -85,7 +75,6 @@ extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeSendDiscoveryResponse(JNIEnv* env, jclass clazz, jstring yaml) { - jni_log("[XTS]", "sending DiscoveryResponse from the xDS server"); const char* yaml_chars = env->GetStringUTFChars(yaml, /* isCopy= */ nullptr); // The yaml utilities have non-relevant thread asserts. Envoy::Thread::SkipAsserts skip; @@ -99,7 +88,6 @@ Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeSendDiscoveryRespons extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_testing_TestJni_nativeShutdownXdsTestServer(JNIEnv* env, jclass clazz) { - jni_log("[XTS]", "shutting down xDS server"); shutdownXdsServer(); } From eaa9d0aacb44f4c6e9ff3d4af20fb82cab8816c2 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 18 Mar 2024 19:43:24 -0400 Subject: [PATCH 34/36] Make the instance_interface dependency explicit (#32935) Signed-off-by: Yan Avlasov --- source/server/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/source/server/BUILD b/source/server/BUILD index c86be8637fc7..bca0cd626d7e 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -397,6 +397,7 @@ envoy_cc_library( hdrs = ["listener_manager_factory.h"], deps = [ "//envoy/server:factory_context_interface", + "//envoy/server:instance_interface", "//envoy/server:listener_manager_interface", "//envoy/server:worker_interface", "//source/common/quic:quic_stat_names_lib", From 307218e7ac4db4320ea0003952adcf2f93d4d8de Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 18 Mar 2024 19:49:26 -0400 Subject: [PATCH 35/36] Make macro work outside of the Envoy namespace (#32893) --------- Signed-off-by: Yan Avlasov --- envoy/common/exception.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/envoy/common/exception.h b/envoy/common/exception.h index 4d5d9fd45bc0..946e92788739 100644 --- a/envoy/common/exception.h +++ b/envoy/common/exception.h @@ -17,7 +17,7 @@ namespace Envoy { #define throwEnvoyExceptionOrPanic(x) PANIC(x) #define throwExceptionOrPanic(x, y) PANIC(y) #else -#define throwEnvoyExceptionOrPanic(x) throw EnvoyException(x) +#define throwEnvoyExceptionOrPanic(x) throw ::Envoy::EnvoyException(x) #define throwExceptionOrPanic(y, x) throw y(x) #endif From 7fec609a507371d7176c61aa4623f445543f294f Mon Sep 17 00:00:00 2001 From: Thomas <1607559+thomasvnoort@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:22:28 +0100 Subject: [PATCH 36/36] rbac: rules_stat_prefix to emit metrics with a prefix (#31835) This is akin to shadow_rules_stat_prefix but for non-shadowing rules. Since only shadow rules emit dynamic metadata, this prefix only applies to metrics. --------- Signed-off-by: Thomas van Noort --- .../filters/http/rbac/v3/rbac.proto | 7 +- changelogs/current.yaml | 4 + .../http/http_filters/rbac_filter.rst | 7 +- source/common/config/well_known_names.cc | 3 + source/common/config/well_known_names.h | 2 + .../extensions/filters/common/rbac/utility.cc | 11 ++- .../extensions/filters/common/rbac/utility.h | 6 +- .../filters/http/rbac/rbac_filter.cc | 2 +- .../filters/network/rbac/rbac_filter.cc | 2 +- test/common/stats/tag_extractor_impl_test.cc | 11 +++ .../filters/http/rbac/rbac_filter_test.cc | 81 +++++++++++------ .../filters/network/rbac/filter_test.cc | 88 ++++++++++++------- 12 files changed, 152 insertions(+), 72 deletions(-) diff --git a/api/envoy/extensions/filters/http/rbac/v3/rbac.proto b/api/envoy/extensions/filters/http/rbac/v3/rbac.proto index eeb505a17fb7..b14478b70e8f 100644 --- a/api/envoy/extensions/filters/http/rbac/v3/rbac.proto +++ b/api/envoy/extensions/filters/http/rbac/v3/rbac.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.filters.http.rbac] // RBAC filter config. -// [#next-free-field: 6] +// [#next-free-field: 7] message RBAC { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.rbac.v2.RBAC"; @@ -34,6 +34,11 @@ message RBAC { config.rbac.v3.RBAC rules = 1 [(udpa.annotations.field_migrate).oneof_promotion = "rules_specifier"]; + // If specified, rules will emit stats with the given prefix. + // This is useful to distinguish the stat when there are more than 1 RBAC filter configured with + // rules. + string rules_stat_prefix = 6; + // The match tree to use when resolving RBAC action for incoming requests. Requests do not // match any matcher will be denied. // If absent, no enforcing RBAC matcher will be applied. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 092048320fae..aa29fe080592 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -339,6 +339,10 @@ new_features: Update ``aws_request_signing`` filter to support optionally sending the aws signature in query parameters rather than headers, by specifying the :ref:`query_string ` configuration section. +- area: rbac + change: | + Added :ref:`rules_stat_prefix ` + to allow adding custom prefix to the stats emitted by rules. deprecated: - area: listener diff --git a/docs/root/configuration/http/http_filters/rbac_filter.rst b/docs/root/configuration/http/http_filters/rbac_filter.rst index db30b5123a01..4e2e5990392e 100644 --- a/docs/root/configuration/http/http_filters/rbac_filter.rst +++ b/docs/root/configuration/http/http_filters/rbac_filter.rst @@ -33,7 +33,12 @@ The RBAC filter outputs statistics in the ``http..rbac.`` namespace ` comes from the owning HTTP connection manager. -For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, the :ref:`shadow_rules_stat_prefix ` +For the rule statistics ``allowed`` and ``denied``, +the :ref:`rules_stat_prefix ` +can be used to add an extra prefix to output the statistics in the ``http..rbac..`` namespace. + +For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, +the :ref:`shadow_rules_stat_prefix ` can be used to add an extra prefix to output the statistics in the ``http..rbac..`` namespace. .. csv-table:: diff --git a/source/common/config/well_known_names.cc b/source/common/config/well_known_names.cc index 4e390568c31d..104b182bfb1a 100644 --- a/source/common/config/well_known_names.cc +++ b/source/common/config/well_known_names.cc @@ -207,6 +207,9 @@ TagNameValues::TagNameValues() { // (.).rbac.** addTokenized(RBAC_PREFIX, "$.rbac.**"); + + // http..rbac.(.)* + addTokenized(RBAC_HTTP_PREFIX, "http.*.rbac.$.**"); } void TagNameValues::addRe2(const std::string& name, const std::string& regex, diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index a0a40cc79496..8cf5fbd14715 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -123,6 +123,8 @@ class TagNameValues { const std::string CONNECTION_LIMIT_PREFIX = "envoy.connection_limit_prefix"; // Stats prefix for the RBAC network filter const std::string RBAC_PREFIX = "envoy.rbac_prefix"; + // Stats prefix for the RBAC http filter + const std::string RBAC_HTTP_PREFIX = "envoy.rbac_http_prefix"; // Stats prefix for the TCP Proxy network filter const std::string TCP_PREFIX = "envoy.tcp_prefix"; // Stats prefix for the UDP Proxy network filter diff --git a/source/extensions/filters/common/rbac/utility.cc b/source/extensions/filters/common/rbac/utility.cc index 710342d7b8fd..cc14523ee1fb 100644 --- a/source/extensions/filters/common/rbac/utility.cc +++ b/source/extensions/filters/common/rbac/utility.cc @@ -10,11 +10,14 @@ namespace Filters { namespace Common { namespace RBAC { -RoleBasedAccessControlFilterStats -generateStats(const std::string& prefix, const std::string& shadow_prefix, Stats::Scope& scope) { +RoleBasedAccessControlFilterStats generateStats(const std::string& prefix, + const std::string& rules_prefix, + const std::string& shadow_rules_prefix, + Stats::Scope& scope) { const std::string final_prefix = Envoy::statPrefixJoin(prefix, "rbac."); - return {ENFORCE_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix)) - SHADOW_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix + shadow_prefix))}; + return { + ENFORCE_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix + rules_prefix)) + SHADOW_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix + shadow_rules_prefix))}; } std::string responseDetail(const std::string& policy_id) { diff --git a/source/extensions/filters/common/rbac/utility.h b/source/extensions/filters/common/rbac/utility.h index 79cd8be7d2bf..fe17def3c6e1 100644 --- a/source/extensions/filters/common/rbac/utility.h +++ b/source/extensions/filters/common/rbac/utility.h @@ -34,8 +34,10 @@ struct RoleBasedAccessControlFilterStats { SHADOW_RBAC_FILTER_STATS(GENERATE_COUNTER_STRUCT) }; -RoleBasedAccessControlFilterStats -generateStats(const std::string& prefix, const std::string& shadow_prefix, Stats::Scope& scope); +RoleBasedAccessControlFilterStats generateStats(const std::string& prefix, + const std::string& rules_prefix, + const std::string& shadow_rules_prefix, + Stats::Scope& scope); template std::unique_ptr diff --git a/source/extensions/filters/http/rbac/rbac_filter.cc b/source/extensions/filters/http/rbac/rbac_filter.cc index bb6950942611..f1ee12283824 100644 --- a/source/extensions/filters/http/rbac/rbac_filter.cc +++ b/source/extensions/filters/http/rbac/rbac_filter.cc @@ -61,7 +61,7 @@ RoleBasedAccessControlFilterConfig::RoleBasedAccessControlFilterConfig( const std::string& stats_prefix, Stats::Scope& scope, Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor& validation_visitor) - : stats_(Filters::Common::RBAC::generateStats(stats_prefix, + : stats_(Filters::Common::RBAC::generateStats(stats_prefix, proto_config.rules_stat_prefix(), proto_config.shadow_rules_stat_prefix(), scope)), shadow_rules_stat_prefix_(proto_config.shadow_rules_stat_prefix()), engine_(Filters::Common::RBAC::createEngine(proto_config, context, validation_visitor, diff --git a/source/extensions/filters/network/rbac/rbac_filter.cc b/source/extensions/filters/network/rbac/rbac_filter.cc index 500a0c2f617e..7d07de0f6d18 100644 --- a/source/extensions/filters/network/rbac/rbac_filter.cc +++ b/source/extensions/filters/network/rbac/rbac_filter.cc @@ -56,7 +56,7 @@ RoleBasedAccessControlFilterConfig::RoleBasedAccessControlFilterConfig( const envoy::extensions::filters::network::rbac::v3::RBAC& proto_config, Stats::Scope& scope, Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor& validation_visitor) - : stats_(Filters::Common::RBAC::generateStats(proto_config.stat_prefix(), + : stats_(Filters::Common::RBAC::generateStats(proto_config.stat_prefix(), "", proto_config.shadow_rules_stat_prefix(), scope)), shadow_rules_stat_prefix_(proto_config.shadow_rules_stat_prefix()), engine_(Filters::Common::RBAC::createEngine(proto_config, context, validation_visitor, diff --git a/test/common/stats/tag_extractor_impl_test.cc b/test/common/stats/tag_extractor_impl_test.cc index 43e74080a685..747f9daca0c1 100644 --- a/test/common/stats/tag_extractor_impl_test.cc +++ b/test/common/stats/tag_extractor_impl_test.cc @@ -450,6 +450,17 @@ TEST(TagExtractorTest, DefaultTagExtractors) { rbac_prefix.name_ = tag_names.RBAC_PREFIX; rbac_prefix.value_ = "my_rbac_prefix"; regex_tester.testRegex("my_rbac_prefix.rbac.allowed", "rbac.allowed", {rbac_prefix}); + + // RBAC HTTP Filter Prefix + Tag rbac_http_hcm_prefix; + rbac_http_hcm_prefix.name_ = tag_names.HTTP_CONN_MANAGER_PREFIX; + rbac_http_hcm_prefix.value_ = "hcm_prefix"; + + Tag rbac_http_prefix; + rbac_http_prefix.name_ = tag_names.RBAC_HTTP_PREFIX; + rbac_http_prefix.value_ = "prefix"; + regex_tester.testRegex("http.hcm_prefix.rbac.prefix.allowed", "http.rbac.allowed", + {rbac_http_hcm_prefix, rbac_http_prefix}); } TEST(TagExtractorTest, ExtAuthzTagExtractors) { diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index c8578f2e2622..93cacd425885 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -35,7 +35,8 @@ enum class LogResult { Yes, No, Undecided }; class RoleBasedAccessControlFilterTest : public testing::Test { public: - void setupPolicy(envoy::config::rbac::v3::RBAC::Action action) { + void setupPolicy(envoy::config::rbac::v3::RBAC::Action action, + std::string rules_stat_prefix = "") { envoy::extensions::filters::http::rbac::v3::RBAC config; envoy::config::rbac::v3::Policy policy; @@ -47,6 +48,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { policy.add_principals()->set_any(true); config.mutable_rules()->set_action(action); (*config.mutable_rules()->mutable_policies())["foo"] = policy; + config.set_rules_stat_prefix(rules_stat_prefix); envoy::config::rbac::v3::Policy shadow_policy; auto shadow_policy_rules = shadow_policy.add_permissions()->mutable_or_rules(); @@ -55,7 +57,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { shadow_policy.add_principals()->set_any(true); config.mutable_shadow_rules()->set_action(action); (*config.mutable_shadow_rules()->mutable_policies())["bar"] = shadow_policy; - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); setupConfig(std::make_shared( config, "test", *store_.rootScope(), context_, @@ -156,7 +158,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { TestUtility::loadFromYaml(fmt::format(shadow_matcher_yaml, action, on_no_match_action), shadow_matcher); *config.mutable_shadow_matcher() = shadow_matcher; - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); setupConfig(std::make_shared( config, "test", *store_.rootScope(), context_, @@ -238,7 +240,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { *config.mutable_matcher() = matcher; *config.mutable_shadow_matcher() = matcher; - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); setupConfig(std::make_shared( config, "test", *store_.rootScope(), context_, @@ -335,8 +337,9 @@ TEST_F(RoleBasedAccessControlFilterTest, Allowed) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -359,8 +362,9 @@ TEST_F(RoleBasedAccessControlFilterTest, RequestedServerName) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -404,12 +408,16 @@ TEST_F(RoleBasedAccessControlFilterTest, Denied) { EXPECT_EQ(1U, config_->stats().shadow_allowed_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = req_info_.dynamicMetadata().filter_metadata().at("envoy.filters.http.rbac"); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); EXPECT_EQ("rbac_access_denied_matched_policy[none]", callbacks_.details()); checkAccessLogMetadata(LogResult::Undecided); } @@ -496,8 +504,9 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherAllowed) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -520,8 +529,9 @@ TEST_F(RoleBasedAccessControlFilterTest, RequestedServerNameMatcher) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -565,12 +575,16 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherDenied) { EXPECT_EQ(1U, config_->stats().shadow_allowed_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = req_info_.dynamicMetadata().filter_metadata().at("envoy.filters.http.rbac"); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); EXPECT_EQ("rbac_access_denied_matched_policy[none]", callbacks_.details()); checkAccessLogMetadata(LogResult::Undecided); } @@ -618,8 +632,9 @@ TEST_F(RoleBasedAccessControlFilterTest, ShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -639,8 +654,9 @@ TEST_F(RoleBasedAccessControlFilterTest, ShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -660,8 +676,9 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -681,8 +698,9 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -691,6 +709,13 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherShouldNotLog) { checkAccessLogMetadata(LogResult::No); } +TEST_F(RoleBasedAccessControlFilterTest, RulesStatPrefix) { + setupPolicy(envoy::config::rbac::v3::RBAC::ALLOW, "rules_prefix_"); + + EXPECT_EQ("test.rbac.rules_prefix_.allowed", config_->stats().allowed_.name()); + EXPECT_EQ("test.rbac.rules_prefix_.denied", config_->stats().denied_.name()); +} + // Upstream Ip and Port matcher tests. class UpstreamIpPortMatcherTests : public RoleBasedAccessControlFilterTest { public: diff --git a/test/extensions/filters/network/rbac/filter_test.cc b/test/extensions/filters/network/rbac/filter_test.cc index df60598f379d..78b4d05d32ea 100644 --- a/test/extensions/filters/network/rbac/filter_test.cc +++ b/test/extensions/filters/network/rbac/filter_test.cc @@ -33,7 +33,7 @@ class RoleBasedAccessControlNetworkFilterTest : public testing::Test { envoy::extensions::filters::network::rbac::v3::RBAC config; config.set_stat_prefix("tcp."); - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); if (with_policy) { envoy::config::rbac::v3::Policy policy; @@ -69,7 +69,7 @@ class RoleBasedAccessControlNetworkFilterTest : public testing::Test { std::string on_no_match_action = "DENY") { envoy::extensions::filters::network::rbac::v3::RBAC config; config.set_stat_prefix("tcp."); - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); if (with_matcher) { constexpr absl::string_view matcher_yaml = R"EOF( @@ -250,8 +250,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithOneTimeEnforcement) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithContinuousEnforcement) { @@ -270,8 +271,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithContinuousEnforcement EXPECT_EQ(2U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerName) { @@ -291,8 +293,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerName) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoPolicy) { @@ -309,8 +312,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoPolicy) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, Denied) { @@ -330,13 +334,17 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, Denied) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = stream_info_.dynamicMetadata().filter_metadata().at(NetworkFilterNames::get().Rbac); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithOneTimeEnforcement) { @@ -355,8 +363,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithOneTimeEnforce EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithContinuousEnforcement) { @@ -375,8 +384,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithContinuousEnfo EXPECT_EQ(2U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerNameMatcher) { @@ -396,8 +406,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerNameMatcher) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoMatcher) { @@ -414,8 +425,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoMatcher) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherDenied) { @@ -435,13 +447,17 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherDenied) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = stream_info_.dynamicMetadata().filter_metadata().at(NetworkFilterNames::get().Rbac); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); } // Log Tests @@ -456,8 +472,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, ShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(true); } @@ -473,8 +490,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, ShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(false); } @@ -504,8 +522,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(true); } @@ -521,8 +540,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(false); }