diff --git a/.bazelversion b/.bazelversion index 91e4a9f26224..f22d756da39d 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.3.2 +6.5.0 diff --git a/.gitattributes b/.gitattributes index 8bd5b8b56f71..0dd1d568f0b3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ +* text=auto eol=lf + /generated_api_shadow/envoy/** linguist-generated=true /generated_api_shadow/bazel/** linguist-generated=true *.svg binary diff --git a/.github/config.yml b/.github/config.yml index ec426e498fcc..b3bdcd3985c7 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -254,6 +254,7 @@ run: mobile-core: paths: - "**/*" + - "*" mobile-format: paths: - .bazelrc diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 9eba30588dac..2f8fe09ad17c 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -8,7 +8,7 @@ on: - cron: '0 12 * * 4' concurrency: - group: ${{ github.head_ref-github.workflow || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} cancel-in-progress: true @@ -29,6 +29,12 @@ jobs: - name: Checkout repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Free disk space + uses: envoyproxy/toolshed/gh-actions/diskspace@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + with: + to_remove: | + /usr/local/lib/android + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # codeql-bundle-v2.13.4 diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index a0a1040c89b3..856f6aafdf48 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -13,7 +13,7 @@ on: pull_request: concurrency: - group: ${{ github.head_ref-github.workflow || github.run_id }} + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} cancel-in-progress: true env: diff --git a/OWNERS.md b/OWNERS.md index a8137725f596..772b07e8bbfd 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -40,7 +40,7 @@ routing PRs, questions, etc. to the right place. * Stats, abseil, scalability, and performance. * Adi Peleg ([adisuissa](https://github.com/adisuissa)) (adip@google.com) * xDS APIs, configuration, control plane, fuzzing. -* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (kbaichoo@google.com) +* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (envoy@kevinbaichoo.com) * Data plane, overload management, flow control. * Keith Smiley ([keith](https://github.com/keith)) (keithbsmiley@gmail.com) * Bazel, CI, compilers, linkers, general build issues, etc. @@ -80,7 +80,7 @@ without further review. * Pradeep Rao ([pradeepcrao](https://github.com/pradeepcrao)) (pcrao@google.com) * Kateryna Nezdolii ([nezdolik](https://github.com/nezdolik)) (kateryna.nezdolii@gmail.com) * Boteng Yao ([botengyao](https://github.com/botengyao)) (boteng@google.com) -* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (kbaichoo@google.com) +* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (envoy@kevinbaichoo.com) * Tianyu Xia ([tyxia](https://github.com/tyxia)) (tyxia@google.com) # Emeritus maintainers diff --git a/api/BUILD b/api/BUILD index 2eadff1f606f..45ad652f544b 100644 --- a/api/BUILD +++ b/api/BUILD @@ -292,6 +292,8 @@ proto_library( "//envoy/extensions/network/dns_resolver/cares/v3:pkg", "//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/consecutive_errors/v3:pkg", "//envoy/extensions/path/match/uri_template/v3:pkg", "//envoy/extensions/path/rewrite/uri_template/v3:pkg", "//envoy/extensions/quic/connection_id_generator/v3:pkg", diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index b5f36f273bcc..5e0cab225582 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // ` for more detail. // Bootstrap :ref:`configuration overview `. -// [#next-free-field: 41] +// [#next-free-field: 42] message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.Bootstrap"; @@ -411,6 +411,9 @@ message Bootstrap { // Optional gRPC async manager config. GrpcAsyncClientManagerConfig grpc_async_client_manager_config = 40; + + // Optional configuration for memory allocation manager. + MemoryAllocatorManager memory_allocator_manager = 41; } // Administration interface :ref:`operations documentation @@ -734,3 +737,14 @@ message CustomInlineHeader { // The type of the header that is expected to be set as the inline header. InlineHeaderType inline_header_type = 2 [(validate.rules).enum = {defined_only: true}]; } + +message MemoryAllocatorManager { + // Configures tcmalloc to perform background release of free memory in amount of bytes per ``memory_release_interval`` interval. + // If equals to ``0``, no memory release will occur. Defaults to ``0``. + uint64 bytes_to_release = 1 [(validate.rules).uint64 = {gte: 1}]; + + // Interval in milliseconds for memory releasing. If specified, during every + // interval Envoy will try to ``release bytes_to_release`` of free memory back to operating system for reuse. + // Defaults to 1000 milliseconds. + google.protobuf.Duration memory_release_interval = 2; +} diff --git a/api/envoy/config/cluster/v3/outlier_detection.proto b/api/envoy/config/cluster/v3/outlier_detection.proto index 11289e26b4f4..622486e41cfe 100644 --- a/api/envoy/config/cluster/v3/outlier_detection.proto +++ b/api/envoy/config/cluster/v3/outlier_detection.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.config.cluster.v3; +import "envoy/config/core/v3/extension.proto"; + import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; @@ -19,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // See the :ref:`architecture overview ` for // more information on outlier detection. -// [#next-free-field: 24] +// [#next-free-field: 25] message OutlierDetection { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.cluster.OutlierDetection"; @@ -167,4 +169,8 @@ message OutlierDetection { // To change this default behavior set this config to ``false`` where active health checking will not uneject the host. // Defaults to true. google.protobuf.BoolValue successful_active_health_check_uneject_host = 23; + + // Set of host's passive monitors. + // [#not-implemented-hide:] + repeated core.v3.TypedExtensionConfig monitors = 24; } diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto index 68841c3d1706..8a4e292af508 100644 --- a/api/envoy/config/core/v3/protocol.proto +++ b/api/envoy/config/core/v3/protocol.proto @@ -56,7 +56,7 @@ message QuicKeepAliveSettings { } // QUIC protocol options which apply to both downstream and upstream connections. -// [#next-free-field: 8] +// [#next-free-field: 9] message QuicProtocolOptions { // Maximum number of streams that the client can negotiate per connection. 100 // if not specified. @@ -102,6 +102,15 @@ message QuicProtocolOptions { // A comma-separated list of strings representing QUIC client connection options defined in // `QUICHE `_ and to be sent by upstream connections. string client_connection_options = 7; + + // The duration that a QUIC connection stays idle before it closes itself. If this field is not present, QUICHE + // default 600s will be applied. + // For internal corporate network, a long timeout is often fine. + // But for client facing network, 30s is usually a good choice. + google.protobuf.Duration idle_network_timeout = 8 [(validate.rules).duration = { + lte {seconds: 600} + gte {seconds: 1} + }]; } message UpstreamHttpProtocolOptions { diff --git a/api/envoy/config/listener/v3/quic_config.proto b/api/envoy/config/listener/v3/quic_config.proto index 8e7d8048d6a9..bd6327d4106a 100644 --- a/api/envoy/config/listener/v3/quic_config.proto +++ b/api/envoy/config/listener/v3/quic_config.proto @@ -72,7 +72,7 @@ message QuicProtocolOptions { core.v3.TypedExtensionConfig connection_id_generator_config = 8; // Configure the server's preferred address to advertise so that client can migrate to it. See :ref:`example ` which configures a pair of v4 and v6 preferred addresses. - // The current QUICHE implementation will advertise only one of the preferred IPv4 and IPv6 addresses based on the address family the client initially connects with, and only if the client is also QUICHE-based. + // The current QUICHE implementation will advertise only one of the preferred IPv4 and IPv6 addresses based on the address family the client initially connects with. // If not specified, Envoy will not advertise any server's preferred address. // [#extension-category: envoy.quic.server_preferred_address] core.v3.TypedExtensionConfig server_preferred_address_config = 9 diff --git a/api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD b/api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD new file mode 100644 index 000000000000..ef19132f9180 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD @@ -0,0 +1,12 @@ +# 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 = [ + "//envoy/type/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto b/api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto new file mode 100644 index 000000000000..7ea14dde2839 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package envoy.extensions.outlier_detection_monitors.common.v3; + +import "envoy/type/v3/range.proto"; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.outlier_detection_monitors.common.v3"; +option java_outer_classname = "ErrorTypesProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/outlier_detection_monitors/common/v3;commonv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Outlier detection error buckets] +// Error bucket for HTTP codes. +// [#not-implemented-hide:] +message HttpErrors { + type.v3.Int32Range range = 1; +} + +// Error bucket for locally originated errors. +// [#not-implemented-hide:] +message LocalOriginErrors { +} + +// Error bucket for database errors. +// Sub-parameters may be added later, like malformed response, error on write, etc. +// [#not-implemented-hide:] +message DatabaseErrors { +} + +// Union of possible error buckets. +// [#not-implemented-hide:] +message ErrorBuckets { + // List of buckets "catching" HTTP codes. + repeated HttpErrors http_errors = 1; + + // List of buckets "catching" locally originated errors. + repeated LocalOriginErrors local_origin_errors = 2; + + // List of buckets "catching" database errors. + repeated DatabaseErrors database_errors = 3; +} diff --git a/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD new file mode 100644 index 000000000000..60022c6b3a07 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD @@ -0,0 +1,12 @@ +# 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 = [ + "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto new file mode 100644 index 000000000000..8c74f6b574c7 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package envoy.extensions.outlier_detection_monitors.consecutive_errors.v3; + +import "envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.outlier_detection_monitors.consecutive_errors.v3"; +option java_outer_classname = "ConsecutiveErrorsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3;consecutive_errorsv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// Monitor which counts consecutive errors. +// If number of consecutive errors exceeds the threshold, monitor will report that the host +// is unhealthy. +// [#not-implemented-hide:] +message ConsecutiveErrors { + // Monitor name. + string name = 1; + + // The number of consecutive errors before ejection occurs. + google.protobuf.UInt32Value threshold = 2 [(validate.rules).uint32 = {lte: 100}]; + + // The % chance that a host is actually ejected. Defaults to 100. + google.protobuf.UInt32Value enforcing = 3 [(validate.rules).uint32 = {lte: 100}]; + + // Error buckets. + common.v3.ErrorBuckets error_buckets = 4; +} diff --git a/api/envoy/service/ext_proc/v3/BUILD b/api/envoy/service/ext_proc/v3/BUILD index 0e337d5c3ed1..37704a324955 100644 --- a/api/envoy/service/ext_proc/v3/BUILD +++ b/api/envoy/service/ext_proc/v3/BUILD @@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( has_services = True, deps = [ + "//envoy/annotations:pkg", "//envoy/config/core/v3:pkg", "//envoy/extensions/filters/http/ext_proc/v3:pkg", "//envoy/type/v3:pkg", diff --git a/api/envoy/service/ext_proc/v3/external_processor.proto b/api/envoy/service/ext_proc/v3/external_processor.proto index aa62ef74226d..21ce87a0cf50 100644 --- a/api/envoy/service/ext_proc/v3/external_processor.proto +++ b/api/envoy/service/ext_proc/v3/external_processor.proto @@ -9,6 +9,7 @@ import "envoy/type/v3/http_status.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; +import "envoy/annotations/deprecation.proto"; import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -56,7 +57,7 @@ service ExternalProcessor { // This represents the different types of messages that Envoy can send // to an external processing server. -// [#next-free-field: 9] +// [#next-free-field: 10] message ProcessingRequest { // Specify whether the filter that sent this request is running in synchronous // or asynchronous mode. The choice of synchronous or asynchronous mode @@ -112,6 +113,12 @@ message ProcessingRequest { // Dynamic metadata associated with the request. config.core.v3.Metadata metadata_context = 8; + + // The values of properties selected by the ``request_attributes`` + // or ``response_attributes`` list in the configuration. Each entry + // in the list is populated from the standard + // :ref:`attributes ` supported across Envoy. + map attributes = 9; } // For every ProcessingRequest received by the server with the ``async_mode`` field @@ -204,12 +211,11 @@ message HttpHeaders { config.core.v3.HeaderMap headers = 1; // [#not-implemented-hide:] - // The values of properties selected by the ``request_attributes`` - // or ``response_attributes`` list in the configuration. Each entry - // in the list is populated - // from the standard :ref:`attributes ` - // supported across Envoy. - map attributes = 2; + // This field is deprecated and not implemented. Attributes will be sent in + // the top-level :ref:`attributes attributes = 2 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // If true, then there is no message body associated with this // request or response. diff --git a/api/envoy/type/http/v3/cookie.proto b/api/envoy/type/http/v3/cookie.proto index 4fe0c2f69df6..0ceda999dfd3 100644 --- a/api/envoy/type/http/v3/cookie.proto +++ b/api/envoy/type/http/v3/cookie.proto @@ -22,7 +22,7 @@ message Cookie { string name = 1 [(validate.rules).string = {min_len: 1}]; // Duration of cookie. This will be used to set the expiry time of a new cookie when it is - // generated. Set this to 0 to use a session cookie. + // generated. Set this to 0s to use a session cookie and disable cookie expiration. google.protobuf.Duration ttl = 2 [(validate.rules).duration = {gte {}}]; // Path of cookie. This will be used to set the path of a new cookie when it is generated. diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 706d7d6f9d96..edb89aa6fe28 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -231,6 +231,8 @@ proto_library( "//envoy/extensions/network/dns_resolver/cares/v3:pkg", "//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/consecutive_errors/v3:pkg", "//envoy/extensions/path/match/uri_template/v3:pkg", "//envoy/extensions/path/rewrite/uri_template/v3:pkg", "//envoy/extensions/quic/connection_id_generator/v3:pkg", diff --git a/bazel/BUILD b/bazel/BUILD index 775add0417bc..19578ec3c59e 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -481,6 +481,14 @@ config_setting( values = {"define": "boringssl=disabled"}, ) +selects.config_setting_group( + name = "boringssl_fips_x86", + match_all = [ + ":boringssl_fips", + "@platforms//cpu:x86_64", + ], +) + config_setting( name = "zlib_ng", constraint_values = [ diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 012009edb517..1aaa11ebc889 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.3.0", - sha256 = "bda4a69fa50411b5feef473b423719d88992514d259dadba7d8218a1d02c7883", + version = "2.4.1", + sha256 = "38c5bf333ae70d1bb3a18da6053084ce5f475f0ed0a8f04eed415186d5a7b04b", strip_prefix = "bazel-lib-{version}", urls = ["https://github.com/aspect-build/bazel-lib/archive/v{version}.tar.gz"], use_category = ["build"], - release_date = "2024-01-10", + release_date = "2024-02-06", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/aspect-build/bazel-lib/blob/v{version}/LICENSE", @@ -988,9 +988,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Python rules for Bazel", project_desc = "Bazel rules for the Python language", project_url = "https://github.com/bazelbuild/rules_python", - version = "0.29.0", - sha256 = "d71d2c67e0bce986e1c5a7731b4693226867c45bfe0b7c5e0067228a536fc580", - release_date = "2024-01-22", + version = "0.31.0", + sha256 = "c68bdc4fbec25de5b5493b8819cfc877c4ea299c0dcb15c244c5a00208cde311", + release_date = "2024-02-13", strip_prefix = "rules_python-{version}", urls = ["https://github.com/bazelbuild/rules_python/archive/{version}.tar.gz"], use_category = ["build"], @@ -1175,12 +1175,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "60a22a631bdf944e26407d32a767b4aba953bc39", - sha256 = "70213d0e4016ce79db9f3dfc5e94b4e707c54b69e00fae7ea2593a08e9dfd11e", + version = "2c1f10f46ce16d42fcf692d324799d859e8478cc", + sha256 = "8754391ff9d75aa1ff3dddbb57c73830c63932f01cfb30d4ca158720b2eadeba", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2024-02-05", + release_date = "2024-02-13", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", diff --git a/changelogs/1.26.7.yaml b/changelogs/1.26.7.yaml new file mode 100644 index 000000000000..48e27387880c --- /dev/null +++ b/changelogs/1.26.7.yaml @@ -0,0 +1,28 @@ +date: February 9, 2024 + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/1.27.3.yaml b/changelogs/1.27.3.yaml new file mode 100644 index 000000000000..a67d0b6cabb2 --- /dev/null +++ b/changelogs/1.27.3.yaml @@ -0,0 +1,52 @@ +date: February 9, 2024 + +minor_behavior_changes: +- area: access_log + change: | + When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried + to find the keys configured in filter_state_objects_to_log. + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. +- area: tracing + change: | + Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/1.28.1.yaml b/changelogs/1.28.1.yaml new file mode 100644 index 000000000000..626bbf687b29 --- /dev/null +++ b/changelogs/1.28.1.yaml @@ -0,0 +1,53 @@ +date: February 9, 2024 + +behavior_changes: +- area: listener + change: | + undeprecated runtime key ``overload.global_downstream_max_connections`` until :ref:`downstream connections monitor + ` extension becomes stable. + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. +- area: tracing + change: | + Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. +- area: quic + change: | + Fixed a bug in QUIC and HCM interaction which could cause use-after-free during asynchronous certificates retrieval. + The fix is guarded by runtime ``envoy.reloadable_features.quic_fix_filter_manager_uaf``. +- area: proxy protocol + change: | + Fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/1.29.1.yaml b/changelogs/1.29.1.yaml new file mode 100644 index 000000000000..31831f89848b --- /dev/null +++ b/changelogs/1.29.1.yaml @@ -0,0 +1,35 @@ +date: February 9, 2024 + +bug_fixes: +- area: tracing + change: | + Added support for configuring resource detectors on the OpenTelemetry tracer. +- area: proxy protocol + change: | + Fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. + +removed_config_or_runtime: +- area: postgres proxy + change: | + Fix a race condition that may result from upstream servers refusing to switch to TLS/SSL. + This fix first appeared in ``v1.29.0`` release. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cb7d52d312a7..d980de92bc1f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -2,12 +2,26 @@ date: Pending behavior_changes: # *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +- area: http + change: | + Remove the hop by hop TE header from downstream request headers if it's not set to ``trailers``, else keep it. This change can be + temporarily reverted by setting ``envoy.reloadable_features.sanitize_te`` to false. +- area: stats + change: | + The runtime flag ``envoy.reloadable_features.enable_include_histograms`` is now enabled by default. + This causes the ``includeHistogram()`` method on ``Stats::SinkPredicates`` to filter histograms to + be flushed to stat sinks. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* - area: adaptive concurrency filter stats change: | Multiply the gradient value stat by 1000 to make it more granular (values will range between 500 and 2000). +- area: quic + change: | + :ref:`Server preferred address ` is + now sent to non-quiche quic clients when configured. This behavior can be disabled with runtime flag + ``envoy.reloadable_features.quic_send_server_preferred_address_to_all_clients``. - area: dns change: | Allowing ` to go as low as 1s. @@ -35,6 +49,9 @@ bug_fixes: - area: tracers change: | use unary RPC calls for OpenTelemetry trace exports, rather than client-side streaming connections. +- area: stateful_session + change: | + Support 0 TTL for proto-encoded cookies, which disables cookie expiration by Envoy. - area: load balancing change: | Added randomization in locality load-balancing initialization. This helps desynchronizing Envoys across @@ -45,6 +62,10 @@ bug_fixes: fixed a bug where second HTTP response headers received would cause Envoy to crash in cases where ``propagate_response_headers`` and retry configurations are enabled at the same time, and an upstream request is retried multiple times. +- area: xds + change: | + Reject xDS configurations where the rate-limit's :ref:`fill_rate ` + is set to Infinity or NaN. - area: tracing change: | Prevent Envoy from crashing at start up when the OpenTelemetry environment resource detector cannot detect any attributes. @@ -71,6 +92,19 @@ bug_fixes: Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: tcp_proxy + change: | + When tunneling TCP over HTTP, closed the downstream connection (for writing only) when upstream trailers are read + to support half close semantics during TCP tunneling. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers`` to false. +- area: jwt_authn + change: | + Fixed JWT extractor, which concatenated headers with a comma, resultig in invalid tokens. +- area: router + change: | + Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is + controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` @@ -158,5 +192,12 @@ new_features: Envoy will select the host with the fewest active requests from the entire host set rather than :ref:`choice_count ` random choices. +- area: redis + change: | + Added support for the ``ECHO`` command. deprecated: +- area: listener + change: | + deprecated runtime key ``overload.global_downstream_max_connections`` in favor of :ref:`downstream connections monitor + `. diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 06148a388eb0..2de63d92e29a 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:51ab103bb161fdf8fee4c6311a2d41f484effc409d4f4c58342ab68b2da7ccc2 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:49edf7003af1015b0841f34a04197e8b1c5f1d0c91e897c97749c78ee38b8ec2 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] diff --git a/contrib/all_contrib_extensions.bzl b/contrib/all_contrib_extensions.bzl index 19c605b700bb..6b7994a2623b 100644 --- a/contrib/all_contrib_extensions.bzl +++ b/contrib/all_contrib_extensions.bzl @@ -28,7 +28,7 @@ PPC_SKIP_CONTRIB_TARGETS = [ "envoy.compression.qatzip.compressor", ] -FIPS_SKIP_CONTRIB_TARGETS = [ +FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS = [ "envoy.compression.qatzip.compressor", ] diff --git a/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc b/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc index 313f93099b90..7ed6246805ee 100644 --- a/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc +++ b/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc @@ -657,7 +657,8 @@ CryptoMbPrivateKeyMethodProvider::CryptoMbPrivateKeyMethodProvider( std::chrono::milliseconds poll_delay = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(conf, poll_delay, 200)); - std::string private_key = Config::DataSource::read(conf.private_key(), false, api_); + std::string private_key = + THROW_OR_RETURN_VALUE(Config::DataSource::read(conf.private_key(), false, api_), std::string); bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); diff --git a/contrib/cryptomb/private_key_providers/test/fake_factory.cc b/contrib/cryptomb/private_key_providers/test/fake_factory.cc index b5a1a0cdae0d..eaa64bf4cc8e 100644 --- a/contrib/cryptomb/private_key_providers/test/fake_factory.cc +++ b/contrib/cryptomb/private_key_providers/test/fake_factory.cc @@ -168,8 +168,10 @@ FakeCryptoMbPrivateKeyMethodFactory::createPrivateKeyMethodProviderInstance( std::make_shared(supported_instruction_set_); // We need to get more RSA key params in order to be able to use BoringSSL signing functions. - std::string private_key = Config::DataSource::read( - conf.private_key(), false, private_key_provider_context.serverFactoryContext().api()); + std::string private_key = THROW_OR_RETURN_VALUE( + Config::DataSource::read(conf.private_key(), false, + private_key_provider_context.serverFactoryContext().api()), + std::string); bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); diff --git a/contrib/exe/BUILD b/contrib/exe/BUILD index 1210422f895d..6c085c76ba80 100644 --- a/contrib/exe/BUILD +++ b/contrib/exe/BUILD @@ -7,7 +7,7 @@ load( load( "//contrib:all_contrib_extensions.bzl", "ARM64_SKIP_CONTRIB_TARGETS", - "FIPS_SKIP_CONTRIB_TARGETS", + "FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS", "PPC_SKIP_CONTRIB_TARGETS", "envoy_all_contrib_extensions", ) @@ -24,7 +24,7 @@ alias( SELECTED_CONTRIB_EXTENSIONS = select({ "//bazel:linux_aarch64": envoy_all_contrib_extensions(ARM64_SKIP_CONTRIB_TARGETS), "//bazel:linux_ppc": envoy_all_contrib_extensions(PPC_SKIP_CONTRIB_TARGETS), - "//bazel:boringssl_fips": envoy_all_contrib_extensions(FIPS_SKIP_CONTRIB_TARGETS), + "//bazel:boringssl_fips_x86": envoy_all_contrib_extensions(FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS), "//conditions:default": envoy_all_contrib_extensions(), }) diff --git a/contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go b/contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go deleted file mode 100644 index e9047b95b970..000000000000 --- a/contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build go1.22 - -package network - -/* -// This is a performance optimization. -// The following noescape and nocallback directives are used to -// prevent the Go compiler from allocating function parameters on the heap. - -#cgo noescape envoyGoFilterDownstreamWrite -#cgo nocallback envoyGoFilterDownstreamWrite -#cgo noescape envoyGoFilterDownstreamInfo -#cgo nocallback envoyGoFilterDownstreamInfo -#cgo noescape envoyGoFilterUpstreamConnect -#cgo nocallback envoyGoFilterUpstreamConnect -#cgo noescape envoyGoFilterUpstreamWrite -#cgo nocallback envoyGoFilterUpstreamWrite -#cgo noescape envoyGoFilterUpstreamInfo -#cgo nocallback envoyGoFilterUpstreamInfo -#cgo noescape envoyGoFilterSetFilterState -#cgo nocallback envoyGoFilterSetFilterState -#cgo noescape envoyGoFilterGetFilterState -#cgo nocallback envoyGoFilterGetFilterState - -*/ -import "C" diff --git a/contrib/qat/private_key_providers/source/qat_private_key_provider.cc b/contrib/qat/private_key_providers/source/qat_private_key_provider.cc index b6a4b0b51658..f90615efcf25 100644 --- a/contrib/qat/private_key_providers/source/qat_private_key_provider.cc +++ b/contrib/qat/private_key_providers/source/qat_private_key_provider.cc @@ -363,7 +363,8 @@ QatPrivateKeyMethodProvider::QatPrivateKeyMethodProvider( std::chrono::milliseconds poll_delay = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(conf, poll_delay, 5)); - std::string private_key = Config::DataSource::read(conf.private_key(), false, api_); + std::string private_key = + THROW_OR_RETURN_VALUE(Config::DataSource::read(conf.private_key(), false, api_), std::string); bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); diff --git a/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc b/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc index 86c68a071668..95b77c071d5a 100644 --- a/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc +++ b/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc @@ -200,12 +200,13 @@ ClientPtr traClient(Event::Dispatcher& dispatcher, Server::Configuration::Factor const std::chrono::milliseconds timeout) { // TODO(ramaraochavali): register client to singleton when GrpcClientImpl supports concurrent // requests. + auto client_or_error = context.serverFactoryContext() + .clusterManager() + .grpcAsyncClientManager() + .getOrCreateRawAsyncClient(grpc_service, context.scope(), true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); return std::make_unique( - context.serverFactoryContext() - .clusterManager() - .grpcAsyncClientManager() - .getOrCreateRawAsyncClient(grpc_service, context.scope(), true), - dispatcher, timeout); + client_or_error.value(), dispatcher, timeout); } } // namespace TrafficRoutingAssistant diff --git a/contrib/sxg/filters/http/source/filter_config.h b/contrib/sxg/filters/http/source/filter_config.h index 11532d2e2145..ca3cbfdb5300 100644 --- a/contrib/sxg/filters/http/source/filter_config.h +++ b/contrib/sxg/filters/http/source/filter_config.h @@ -52,13 +52,15 @@ class SDSSecretReader : public SecretReader { Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = + THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), std::string); } return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), + std::string); } }); } diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index 477894d1f6bc..26b06951df94 100644 Binary files a/docs/inventories/v1.26/objects.inv and b/docs/inventories/v1.26/objects.inv differ diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index 0b0acf5c7b42..4eee783c0fd4 100644 Binary files a/docs/inventories/v1.27/objects.inv and b/docs/inventories/v1.27/objects.inv differ diff --git a/docs/inventories/v1.28/objects.inv b/docs/inventories/v1.28/objects.inv index c454862b2315..19b5b89f7d8e 100644 Binary files a/docs/inventories/v1.28/objects.inv and b/docs/inventories/v1.28/objects.inv differ diff --git a/docs/inventories/v1.29/objects.inv b/docs/inventories/v1.29/objects.inv new file mode 100644 index 000000000000..70feb4b0ae53 Binary files /dev/null and b/docs/inventories/v1.29/objects.inv differ diff --git a/docs/root/intro/arch_overview/other_protocols/redis.rst b/docs/root/intro/arch_overview/other_protocols/redis.rst index 1b38fd8c887a..d7c4480528e6 100644 --- a/docs/root/intro/arch_overview/other_protocols/redis.rst +++ b/docs/root/intro/arch_overview/other_protocols/redis.rst @@ -127,13 +127,14 @@ Supported commands At the protocol level, pipelines are supported. Use pipelining wherever possible for the best performance. -At the command level, Envoy only supports commands that can be reliably hashed to a server. AUTH and PING +At the command level, Envoy only supports commands that can be reliably hashed to a server. AUTH, PING and ECHO are the only exceptions. AUTH is processed locally by Envoy if a downstream password has been configured, and no other commands will be processed until authentication is successful when a password has been configured. Envoy will transparently issue AUTH commands upon connecting to upstream servers, if upstream authentication passwords are configured for the cluster. Envoy responds to PING immediately with PONG. -Arguments to PING are not allowed. All other supported commands must contain a key. Supported commands are -functionally identical to the original Redis command except possibly in failure scenarios. +Arguments to PING are not allowed. Envoy responds to ECHO immediately with the command argument. +All other supported commands must contain a key. Supported commands are functionally identical to the +original Redis command except possibly in failure scenarios. For details on each command's usage see the official `Redis command reference `_. @@ -143,6 +144,7 @@ For details on each command's usage see the official :widths: 1, 1 AUTH, Authentication + ECHO, Connection PING, Connection QUIT, Connection DEL, Generic diff --git a/docs/versions.yaml b/docs/versions.yaml index 06b7cca3c7d4..43dfa10ac3cb 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -19,6 +19,7 @@ "1.23": 1.23.12 "1.24": 1.24.12 "1.25": 1.25.11 -"1.26": 1.26.6 -"1.27": 1.27.2 -"1.28": 1.28.0 +"1.26": 1.26.7 +"1.27": 1.27.3 +"1.28": 1.28.1 +"1.29": 1.29.1 diff --git a/envoy/common/exception.h b/envoy/common/exception.h index 48240a1d232b..4d5d9fd45bc0 100644 --- a/envoy/common/exception.h +++ b/envoy/common/exception.h @@ -60,4 +60,12 @@ class EnvoyException : public std::runtime_error { if (!variable.ok()) { \ return variable; \ } + +template Type returnOrThrow(absl::StatusOr type_or_error) { + THROW_IF_STATUS_NOT_OK(type_or_error, throw); + return type_or_error.value(); +} + +#define THROW_OR_RETURN_VALUE(expression, type) returnOrThrow(expression) + } // namespace Envoy diff --git a/envoy/common/optref.h b/envoy/common/optref.h index 758ea66521b3..63ff2bff154c 100644 --- a/envoy/common/optref.h +++ b/envoy/common/optref.h @@ -41,7 +41,9 @@ template struct OptRef { * * @return a ref to the converted object. */ - template operator OptRef() { return OptRef(*ptr_); } + template operator OptRef() { + return ptr_ == nullptr ? absl::nullopt : OptRef(*ptr_); + } /** * Helper to call a const method on T. The caller is responsible for ensuring diff --git a/envoy/grpc/async_client_manager.h b/envoy/grpc/async_client_manager.h index aa99f2c23a6e..91c13d7d57ee 100644 --- a/envoy/grpc/async_client_manager.h +++ b/envoy/grpc/async_client_manager.h @@ -81,10 +81,9 @@ class AsyncClientManager { * @param skip_cluster_check if set to true skips checks for cluster presence and being statically * configured. * @param cache_option always use cache or use cache when runtime is enabled. - * @return RawAsyncClientPtr a grpc async client. - * @throws EnvoyException when grpc_service validation fails. + * @return RawAsyncClientPtr a grpc async client or an invalid status. */ - virtual RawAsyncClientSharedPtr + virtual absl::StatusOr getOrCreateRawAsyncClient(const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; @@ -100,7 +99,7 @@ class AsyncClientManager { * @return RawAsyncClientPtr a grpc async client. * @throws EnvoyException when grpc_service validation fails. */ - virtual RawAsyncClientSharedPtr + virtual absl::StatusOr getOrCreateRawAsyncClientWithHashKey(const GrpcServiceConfigWithHashKey& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; @@ -111,10 +110,9 @@ class AsyncClientManager { * @param scope stats scope. * @param skip_cluster_check if set to true skips checks for cluster presence and being statically * configured. - * @return AsyncClientFactoryPtr factory for grpc_service. - * @throws EnvoyException when grpc_service validation fails. + * @return AsyncClientFactoryPtr factory for grpc_service or an error status. */ - virtual AsyncClientFactoryPtr + virtual absl::StatusOr factoryForGrpcService(const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; }; diff --git a/envoy/stats/stats.h b/envoy/stats/stats.h index ea4596f64aa5..10af2c396620 100644 --- a/envoy/stats/stats.h +++ b/envoy/stats/stats.h @@ -38,10 +38,7 @@ class Metric : public RefcountInterface { * Returns the full name of the Metric. This is intended for most uses, such * as streaming out the name to a stats sink or admin request, or comparing * against it in a test. Independent of the evolution of the data - * representation for the name, this method will be available. For storing the - * name as a map key, however, nameCStr() is a better choice, albeit one that - * might change in the future to return a symbolized representation of the - * elaborated string. + * representation for the name, this method will be available. */ virtual std::string name() const PURE; diff --git a/envoy/tcp/async_tcp_client.h b/envoy/tcp/async_tcp_client.h index cc369bb221ca..df80fd0c3866 100644 --- a/envoy/tcp/async_tcp_client.h +++ b/envoy/tcp/async_tcp_client.h @@ -40,7 +40,8 @@ class AsyncTcpClient { /** * Connect to a remote host. Errors or connection events are reported via the - * event callback registered via setAsyncTcpClientCallbacks(). + * event callback registered via setAsyncTcpClientCallbacks(). We need to set the + * callbacks again to call connect() after the connection is disconnected. */ virtual bool connect() PURE; diff --git a/examples/lua/envoy.yaml b/examples/lua/envoy.yaml index bf5202f5f876..0a8bf2d98c81 100644 --- a/examples/lua/envoy.yaml +++ b/examples/lua/envoy.yaml @@ -40,7 +40,7 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua default_source_code: - inline_string: + inline_string: | local mylibrary = require("lib.mylibrary") function envoy_on_request(request_handle) diff --git a/examples/mysql/Dockerfile-mysql b/examples/mysql/Dockerfile-mysql index 67fcb457f4e4..46078228d2a4 100644 --- a/examples/mysql/Dockerfile-mysql +++ b/examples/mysql/Dockerfile-mysql @@ -1 +1 @@ -FROM mysql:8.3.0@sha256:053b32141c56632d75405ccdb89a38d04b8f964ec69721fb86f7fa734551cf9c +FROM mysql:8.3.0@sha256:2a9ef1075ff30c65bbcf4f96b25a03ea3b3f492c284e6c4a612c269ce4c5bb19 diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index 35ea973949ff..fb3547febddf 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -1,9 +1,9 @@ -FROM debian:bookworm-slim@sha256:7802002798b0e351323ed2357ae6dc5a8c4d0a05a57e7f4d8f97136151d3d603 as os-base +FROM debian:bookworm-slim@sha256:d02c76d82364cedca16ba3ed6f9102406fa9fa8833076a609cabf14270f43dfc 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 golang:1.21.6-bookworm@sha256:3efef61ff1d99c8a90845100e2a7e934b4a5d11b639075dc605ff53c141044fc as golang-base +FROM golang:1.22.0-bookworm@sha256:874c2677e43be36a429823f2742af85844772664f273c1c8c8235f10aba51cd3 as golang-base FROM golang-base as golang-control-plane-builder diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index 8035e9b80278..fbd63382cd9a 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:21.6-bookworm-slim@sha256:5792ef22332ab69f8149ca3f1b50519c97e220d8b0d97ca4d424963f34593fc4 as node-base +FROM node:21.6-bookworm-slim@sha256:f2eadc7a5287fc42ca6c4e9177b8120ec4321c9f2d75506210b948857a22b825 as node-base FROM node-base as node-http-auth diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index 812094642497..0d01aa6f10ba 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:09f23e02d76670d3b346a3c00aa33a27cf57aab8341eedfcdaed41459d14f5c4 +FROM postgres:latest@sha256:2881f973a604ea35d149c129fb98a676945d00ea7cb281ef1ee7ab2d9e22b309 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] diff --git a/examples/shared/websocket/Dockerfile b/examples/shared/websocket/Dockerfile index f57af91de78f..79ab5716f451 100644 --- a/examples/shared/websocket/Dockerfile +++ b/examples/shared/websocket/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim@sha256:7802002798b0e351323ed2357ae6dc5a8c4d0a05a57e7f4d8f97136151d3d603 as websocket-base +FROM debian:bookworm-slim@sha256:d02c76d82364cedca16ba3ed6f9102406fa9fa8833076a609cabf14270f43dfc 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 \ diff --git a/mobile/examples/swift/hello_world/ViewController.swift b/mobile/examples/swift/hello_world/ViewController.swift index 40445047ddc4..30c9e447ef5e 100644 --- a/mobile/examples/swift/hello_world/ViewController.swift +++ b/mobile/examples/swift/hello_world/ViewController.swift @@ -22,6 +22,7 @@ final class ViewController: UITableViewController { .addPlatformFilter(DemoFilter.init) .addPlatformFilter(BufferDemoFilter.init) .addPlatformFilter(AsyncDemoFilter.init) + .respectSystemProxySettings(true) .addNativeFilter( name: "envoy.filters.http.buffer", typedConfig: """ diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index b968cb68087f..9406fc2b9cc3 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -762,6 +762,12 @@ std::unique_ptr EngineBuilder::generate ->set_value(4); } + alpn_options.mutable_auto_config() + ->mutable_http3_protocol_options() + ->mutable_quic_protocol_options() + ->mutable_idle_network_timeout() + ->set_seconds(30); + base_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(h3_proxy_socket); (*base_cluster->mutable_typed_extension_protocol_options()) ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] @@ -769,7 +775,6 @@ std::unique_ptr EngineBuilder::generate // Set the upstream connections socket receive buffer size. The operating system defaults are // usually too small for QUIC. - // TODO(32304): We can remove this once core Envoy has better receive buffer defaults. // NOTE: An H3 cluster can also establish H2 connections (for example, if the H3 connection is // marked as broken in the ConnectivityGrid). This option would apply to all connections in the // cluster, meaning H2 TCP connections buffer size would also be set to 1MB. On the platforms diff --git a/mobile/library/common/apple/BUILD b/mobile/library/common/apple/BUILD new file mode 100644 index 000000000000..6b3cbcd92fa2 --- /dev/null +++ b/mobile/library/common/apple/BUILD @@ -0,0 +1,28 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_mobile_package") + +licenses(["notice"]) # Apache 2 + +envoy_mobile_package() + +envoy_cc_library( + name = "utility_lib", + srcs = select({ + "@envoy//bazel:apple": [ + "utility.cc", + ], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": [ + "utility.h", + ], + "//conditions:default": [], + }), + repository = "@envoy", + deps = select({ + "@envoy//bazel:apple": [ + "@envoy//source/common/common:assert_lib", + ], + "//conditions:default": [], + }), +) diff --git a/mobile/library/common/apple/utility.cc b/mobile/library/common/apple/utility.cc new file mode 100644 index 000000000000..bcbdfc2a3d3a --- /dev/null +++ b/mobile/library/common/apple/utility.cc @@ -0,0 +1,46 @@ +#include "library/common/apple/utility.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace Apple { + +std::string toString(CFStringRef cf_string) { + // A pointer to a C string or NULL if the internal storage of string + // does not allow this to be returned efficiently. + // CFStringGetCStringPtr will return a pointer to the string with no memory allocation and in + // constant time, if it can; otherwise, it will return null. + const char* efficient_c_str = CFStringGetCStringPtr(cf_string, kCFStringEncodingUTF8); + if (efficient_c_str) { + return std::string(efficient_c_str); + } + + CFIndex length = CFStringGetLength(cf_string); + // Adding 1 to accomodate the `\0` null delimiter in a C string. + CFIndex size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; + char* c_str = static_cast(malloc(size)); + // Use less efficient method of getting c string if CFStringGetCStringPtr failed. + const bool ret = CFStringGetCString(cf_string, c_str, size, kCFStringEncodingUTF8); + ENVOY_BUG(ret, "CFStringGetCString failed to convert CFStringRef to C string."); + + std::string ret_str; + ret_str = std::string(c_str); + + free(c_str); + return ret_str; +} + +int toInt(CFNumberRef number) { + if (number == nullptr) { + return 0; + } + + int value = 0; + const bool ret = CFNumberGetValue(number, kCFNumberSInt32Type, &value); + ENVOY_BUG(ret, "CFNumberGetValue failed to convert CFNumberRef to int."); + + return value; +} + +} // namespace Apple +} // namespace Envoy diff --git a/mobile/library/common/apple/utility.h b/mobile/library/common/apple/utility.h new file mode 100644 index 000000000000..a6ef2943f807 --- /dev/null +++ b/mobile/library/common/apple/utility.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +namespace Envoy { +namespace Apple { + +// Converts a CStringRef from Objective-C into a C++ std::string in the most effecient way feasible. +std::string toString(CFStringRef); + +// Converts a CFNumberRef (an Objective-C number representation) into a C++ int value. +int toInt(CFNumberRef); + +} // namespace Apple +} // namespace Envoy diff --git a/mobile/library/common/extensions/filters/http/network_configuration/BUILD b/mobile/library/common/extensions/filters/http/network_configuration/BUILD index ad0cce1697a7..2360408a9028 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/BUILD +++ b/mobile/library/common/extensions/filters/http/network_configuration/BUILD @@ -21,9 +21,13 @@ envoy_cc_extension( repository = "@envoy", deps = [ ":filter_cc_proto", + "//library/common/api:external_api_lib", "//library/common/http:header_utility_lib", "//library/common/http:internal_headers_lib", "//library/common/network:connectivity_manager_lib", + "//library/common/network:proxy_api_lib", + "//library/common/network:proxy_resolver_interface_lib", + "//library/common/network:proxy_settings_lib", "//library/common/stream_info:extra_stream_info_lib", "//library/common/types:c_types_lib", "@envoy//envoy/http:codes_interface", diff --git a/mobile/library/common/extensions/filters/http/network_configuration/filter.cc b/mobile/library/common/extensions/filters/http/network_configuration/filter.cc index 0502df10dca6..4aa7579f931d 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/filter.cc +++ b/mobile/library/common/extensions/filters/http/network_configuration/filter.cc @@ -2,9 +2,14 @@ #include "envoy/server/filter_config.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" #include "source/common/network/filter_state_proxy_info.h" +#include "library/common/api/external.h" +#include "library/common/data/utility.h" #include "library/common/http/header_utility.h" +#include "library/common/types/c_types.h" namespace Envoy { namespace Extensions { @@ -55,6 +60,17 @@ bool NetworkConfigurationFilter::onAddressResolved( return false; } +void NetworkConfigurationFilter::onProxyResolutionComplete( + Network::ProxySettingsConstSharedPtr proxy_settings) { + if (continueWithProxySettings(proxy_settings) == Http::FilterHeadersStatus::Continue) { + // PAC URL resolution happens via Apple APIs on a separate thread (Apple's run loop thread), so + // we schedule the continuation callback on the engine's dispatcher. + continue_decoding_callback_ = decoder_callbacks_->dispatcher().createSchedulableCallback( + [this]() { decoder_callbacks_->continueDecoding(); }); + continue_decoding_callback_->scheduleCallbackNextIteration(); + } +} + Http::FilterHeadersStatus NetworkConfigurationFilter::decodeHeaders(Http::RequestHeaderMap& request_headers, bool) { ENVOY_LOG(trace, "NetworkConfigurationFilter::decodeHeaders", request_headers); @@ -64,19 +80,76 @@ NetworkConfigurationFilter::decodeHeaders(Http::RequestHeaderMap& request_header return Http::FilterHeadersStatus::Continue; } - // If there is no proxy configured, continue. + // For iOS, we use the `envoy_proxy_resolver` API, which reads proxy settings asynchronously, + // and callbacks are invoked in the filter when proxy settings are updated. + auto* proxy_resolver = static_cast( + Api::External::retrieveApi("envoy_proxy_resolver", /*allow_absent=*/true)); + if (proxy_resolver != nullptr) { + return resolveProxy(request_headers, proxy_resolver); + } + + // For Android, proxy settings are pushed to the ConnectivityManager and synchronously handled + // here. const auto proxy_settings = connectivity_manager_->getProxySettings(); + return continueWithProxySettings(proxy_settings); +} + +Http::FilterHeadersStatus +NetworkConfigurationFilter::resolveProxy(Http::RequestHeaderMap& request_headers, + Network::ProxyResolverApi* proxy_resolver) { + ASSERT(proxy_resolver != nullptr, "proxy_resolver must not be null."); + + const std::string target_url = Http::Utility::buildOriginalUri(request_headers, absl::nullopt); + + std::weak_ptr weak_self = weak_from_this(); + Network::ProxyResolutionResult proxy_resolution_result = proxy_resolver->resolver->resolveProxy( + target_url, proxy_settings_, + [&weak_self](const std::vector& proxies) { + // This is the callback invoked from the Apple APIs resolving the PAC file URL, which + // happens on a separate Apple run loop thread. We keep a weak_ptr to this filter instance + // so that, if the stream is canceled and the filter chain is torn down in the meantime, + // we will fail to aquire the weak_ptr lock and won't execute any callbacks on the resolved + // proxies. + if (auto caller_ptr = weak_self.lock()) { + Network::ProxySettingsConstSharedPtr proxy_settings = + Network::ProxySettings::create(proxies); + // We are currently in the main thread, so post the proxy resolution callback to the + // worker thread's (i.e. the Engine thread) dispatcher. + caller_ptr->decoder_callbacks_->dispatcher().post([&weak_self, proxy_settings]() { + // Again, the stream may have been canceled in the interim, so try aquiring the + // filter through its weak_ptr before proceding with the proxy resolution callback. + if (auto filter_ptr = weak_self.lock()) { + filter_ptr->onProxyResolutionComplete(proxy_settings); + } + }); + } + }); + + switch (proxy_resolution_result) { + case Network::ProxyResolutionResult::NO_PROXY_CONFIGURED: + return Http::FilterHeadersStatus::Continue; + case Network::ProxyResolutionResult::RESULT_COMPLETED: + return continueWithProxySettings(Network::ProxySettings::create(proxy_settings_)); + case Network::ProxyResolutionResult::RESULT_IN_PROGRESS: + // `onProxyResolutionComplete` method will be called once the proxy resolution completes + return Http::FilterHeadersStatus::StopAllIterationAndWatermark; + } +} + +Http::FilterHeadersStatus NetworkConfigurationFilter::continueWithProxySettings( + Network::ProxySettingsConstSharedPtr proxy_settings) { + // If there is no proxy configured, continue. if (proxy_settings == nullptr) { return Http::FilterHeadersStatus::Continue; } - ENVOY_LOG(trace, "netconf_filter_processing_proxy_for_request", proxy_settings->asString()); + ENVOY_LOG(trace, "netconf_filter_processing_proxy_for_request proxy_settings={}", + proxy_settings->asString()); // If there is a proxy with a raw address, set the information, and continue. const auto proxy_address = proxy_settings->address(); if (proxy_address != nullptr) { - const auto authorityHeader = request_headers.get(AuthorityHeaderName); - - setInfo(request_headers.getHostValue(), proxy_address); + const auto host_value = decoder_callbacks_->streamInfo().getRequestHeaders()->getHostValue(); + setInfo(host_value, proxy_address); return Http::FilterHeadersStatus::Continue; } diff --git a/mobile/library/common/extensions/filters/http/network_configuration/filter.h b/mobile/library/common/extensions/filters/http/network_configuration/filter.h index 7fbe37583ffb..f17ccc38ec8c 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/filter.h +++ b/mobile/library/common/extensions/filters/http/network_configuration/filter.h @@ -7,6 +7,9 @@ #include "library/common/extensions/filters/http/network_configuration/filter.pb.h" #include "library/common/network/connectivity_manager.h" +#include "library/common/network/proxy_api.h" +#include "library/common/network/proxy_resolver_interface.h" +#include "library/common/network/proxy_settings.h" #include "library/common/stream_info/extra_stream_info.h" #include "library/common/types/c_types.h" @@ -21,7 +24,8 @@ namespace NetworkConfiguration { class NetworkConfigurationFilter final : public Http::PassThroughFilter, public Logger::Loggable, - public Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryCallbacks { + public Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryCallbacks, + public std::enable_shared_from_this { public: NetworkConfigurationFilter(Network::ConnectivityManagerSharedPtr connectivity_manager, bool enable_drain_post_dns_refresh, bool enable_interface_binding) @@ -43,11 +47,16 @@ class NetworkConfigurationFilter final const Extensions::Common::DynamicForwardProxy::DnsHostInfoSharedPtr& host_info) override; void onDestroy() override; + void onProxyResolutionComplete(Network::ProxySettingsConstSharedPtr proxy_settings); private: void setInfo(absl::string_view authority, Network::Address::InstanceConstSharedPtr address); bool onAddressResolved(const Extensions::Common::DynamicForwardProxy::DnsHostInfoSharedPtr& host_info); + Http::FilterHeadersStatus resolveProxy(Http::RequestHeaderMap& request_headers, + Network::ProxyResolverApi* resolver); + Http::FilterHeadersStatus + continueWithProxySettings(Network::ProxySettingsConstSharedPtr proxy_settings); // This is only present if there is an active proxy DNS lookup in progress. std::unique_ptr @@ -57,6 +66,8 @@ class NetworkConfigurationFilter final bool enable_drain_post_dns_refresh_; bool enable_interface_binding_; Event::SchedulableCallbackPtr continue_decoding_callback_; + // The lifetime of proxy settings must be attached to the lifetime of the filter. + std::vector proxy_settings_; }; } // namespace NetworkConfiguration diff --git a/mobile/library/common/network/BUILD b/mobile/library/common/network/BUILD index 7a119bf0bd52..d7f3ae1745be 100644 --- a/mobile/library/common/network/BUILD +++ b/mobile/library/common/network/BUILD @@ -11,17 +11,16 @@ envoy_cc_library( ], hdrs = [ "connectivity_manager.h", - "proxy_settings.h", ], repository = "@envoy", deps = [ + ":proxy_settings_lib", "//library/common/network:src_addr_socket_option_lib", "//library/common/types:c_types_lib", "@envoy//envoy/network:socket_interface", "@envoy//envoy/singleton:manager_interface", "@envoy//source/common/common:assert_lib", "@envoy//source/common/common:scalar_to_byte_vector_lib", - "@envoy//source/common/common:utility_lib", "@envoy//source/common/network:addr_family_aware_socket_option_lib", "@envoy//source/common/network:socket_option_lib", "@envoy//source/extensions/common/dynamic_forward_proxy:dns_cache_manager_impl", @@ -87,3 +86,70 @@ envoy_cc_library( "//conditions:default": [], }), ) + +envoy_cc_library( + name = "proxy_settings_lib", + srcs = [ + "proxy_settings.cc", + ], + hdrs = [ + "proxy_settings.h", + ], + repository = "@envoy", + deps = [ + "@envoy//source/common/network:utility_lib", + ], +) + +envoy_cc_library( + name = "proxy_resolver_interface_lib", + hdrs = ["proxy_resolver_interface.h"], + repository = "@envoy", + deps = [ + ":proxy_settings_lib", + ], +) + +envoy_cc_library( + name = "proxy_api_lib", + hdrs = ["proxy_api.h"], + repository = "@envoy", + deps = [ + ":proxy_resolver_interface_lib", + ], +) + +envoy_cc_library( + name = "apple_proxy_resolution_lib", + srcs = select({ + "@envoy//bazel:apple": [ + "apple_pac_proxy_resolver.cc", + "apple_proxy_resolution.cc", + "apple_proxy_resolver.cc", + "apple_system_proxy_settings_monitor.cc", + ], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": [ + "apple_pac_proxy_resolver.h", + "apple_proxy_resolution.h", + "apple_proxy_resolver.h", + "apple_system_proxy_settings_monitor.h", + ], + "//conditions:default": [], + }), + repository = "@envoy", + deps = select({ + "@envoy//bazel:apple": [ + ":proxy_api_lib", + ":proxy_resolver_interface_lib", + ":proxy_settings_lib", + "//library/common/api:external_api_lib", + "//library/common/apple:utility_lib", + "//library/common/extensions/cert_validator/platform_bridge:c_types_lib", + "//library/common/types:c_types_lib", + ], + "//conditions:default": [], + }), +) diff --git a/mobile/library/common/network/apple_pac_proxy_resolver.cc b/mobile/library/common/network/apple_pac_proxy_resolver.cc new file mode 100644 index 000000000000..9a07a00ba607 --- /dev/null +++ b/mobile/library/common/network/apple_pac_proxy_resolver.cc @@ -0,0 +1,109 @@ +#include "library/common/network/apple_pac_proxy_resolver.h" + +#include +#include +#include + +#include "source/common/common/assert.h" + +#include "library/common/apple/utility.h" + +namespace Envoy { +namespace Network { + +namespace { + +// Creates a CFURLRef from a C++ string URL. +CFURLRef createCFURL(const std::string& url_string) { + auto cf_url_string = + CFStringCreateWithCString(kCFAllocatorDefault, url_string.c_str(), kCFStringEncodingUTF8); + auto cf_url = CFURLCreateWithString(kCFAllocatorDefault, cf_url_string, /*baseURL=*/nullptr); + CFRelease(cf_url_string); + return cf_url; +} + +} // namespace + +void proxyAutoConfigurationResultCallback(void* ptr, CFArrayRef cf_proxies, CFErrorRef cf_error) { + // `ptr` contains the unowned pointer to the ProxySettingsResolvedCallback. We extract it from the + // void* and wrap it in a unique_ptr so the memory gets reclaimed at the end of the function when + // `completion_callback` goes out of scope. + std::unique_ptr completion_callback( + static_cast(ptr)); + + std::vector proxies; + if (cf_error != nullptr || cf_proxies == nullptr) { + ENVOY_BUG(cf_error != nullptr, Apple::toString(CFErrorCopyDescription(cf_error))); + // Treat the error case as if no proxy was configured. Seems to be consistent with what iOS + // system (URLSession) is doing. + (*completion_callback)(proxies); + return; + } + + for (int i = 0; i < CFArrayGetCount(cf_proxies); i++) { + CFDictionaryRef cf_dictionary = + static_cast(CFArrayGetValueAtIndex(cf_proxies, i)); + CFStringRef cf_proxy_type = + static_cast(CFDictionaryGetValue(cf_dictionary, kCFProxyTypeKey)); + bool is_http_proxy = CFStringCompare(cf_proxy_type, kCFProxyTypeHTTP, 0) == kCFCompareEqualTo; + bool is_https_proxy = CFStringCompare(cf_proxy_type, kCFProxyTypeHTTPS, 0) == kCFCompareEqualTo; + bool is_direct_proxy = CFStringCompare(cf_proxy_type, kCFProxyTypeNone, 0) == kCFCompareEqualTo; + + if (is_http_proxy || is_https_proxy) { + CFStringRef cf_host = + static_cast(CFDictionaryGetValue(cf_dictionary, kCFProxyHostNameKey)); + CFNumberRef cf_port = + static_cast(CFDictionaryGetValue(cf_dictionary, kCFProxyPortNumberKey)); + proxies.emplace_back(ProxySettings(Apple::toString(cf_host), Apple::toInt(cf_port))); + } else if (is_direct_proxy) { + proxies.push_back(ProxySettings::direct()); + } + } + + (*completion_callback)(proxies); +} + +CFRunLoopSourceRef +ApplePacProxyResolver::createPacUrlResolverSource(CFURLRef cf_proxy_autoconfiguration_file_url, + CFURLRef cf_target_url, + CFStreamClientContext* context) { + // Even though neither the name of the method nor Apple's documentation mentions that, manual + // testing shows that `CFNetworkExecuteProxyAutoConfigurationURL` method does caching of fetched + // PAC file and does not fetch it on every proxy resolution request. + return CFNetworkExecuteProxyAutoConfigurationURL(cf_proxy_autoconfiguration_file_url, + cf_target_url, + proxyAutoConfigurationResultCallback, context); +} + +void ApplePacProxyResolver::resolveProxies( + const std::string& target_url, const std::string& proxy_autoconfiguration_file_url, + ProxySettingsResolvedCallback proxy_resolution_completed) { + CFURLRef cf_target_url = createCFURL(target_url); + CFURLRef cf_proxy_autoconfiguration_file_url = createCFURL(proxy_autoconfiguration_file_url); + + std::unique_ptr completion_callback = + std::make_unique(std::move(proxy_resolution_completed)); + // According to https://developer.apple.com/documentation/corefoundation/cfstreamclientcontext, + // the version must be 0. + auto context = std::make_unique( + CFStreamClientContext{/*version=*/0, + /*info=*/completion_callback.release(), + /*retain=*/nullptr, + /*release=*/nullptr, + /*copyDescription=*/nullptr}); + + // Ownership of the context gets released to the CFRunLoopSourceRef. When + // `proxyAutoConfigurationResultCallback` gets invoked, the pointer is passed in and is + // responsible for releasing the memory. + CFRunLoopSourceRef run_loop_source = createPacUrlResolverSource( + cf_proxy_autoconfiguration_file_url, cf_target_url, context.release()); + + CFRunLoopAddSource(CFRunLoopGetMain(), run_loop_source, kCFRunLoopDefaultMode); + + CFRelease(cf_target_url); + CFRelease(cf_proxy_autoconfiguration_file_url); + CFRelease(run_loop_source); +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_pac_proxy_resolver.h b/mobile/library/common/network/apple_pac_proxy_resolver.h new file mode 100644 index 000000000000..24d349056506 --- /dev/null +++ b/mobile/library/common/network/apple_pac_proxy_resolver.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include +#include + +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +// The callback function for when the PAC file URL has been resolved and the configured proxies +// are available, if any. +// `ptr` contains the unowned pointer to the ProxySettingsResolvedCallback. +// `cf_proxies` is an array of the resolved proxies. +// `cf_error` is the error, if any, when trying to resolve the PAC file URL. +void proxyAutoConfigurationResultCallback(void* ptr, CFArrayRef cf_proxies, CFErrorRef cf_error); + +/** + * Resolves auto configuration (PAC) proxies. + */ +class ApplePacProxyResolver { +public: + virtual ~ApplePacProxyResolver() = default; + /** + * Resolves proxy for a given URL using proxy auto configuration file that's hosted at a given + * URL. + * @param target_url A request URL to resolve the proxy for. + * @param proxy_autoconfiguration_file_url A URL at which a proxy configuration file is hosted. + * @param proxy_resolution_completed A function that's called with result proxies as its + * arguments when proxy resolution completes. + */ + void resolveProxies(const std::string& target_url, + const std::string& proxy_autoconfiguration_file_url, + ProxySettingsResolvedCallback proxy_resolution_completed); + +protected: + // Creates a CFRunLoopSourceRef for resolving the PAC file URL that the main CFRunLoop will run. + // Implemented as a separate function and made virtual so we can overload in tests. + virtual CFRunLoopSourceRef + createPacUrlResolverSource(CFURLRef cf_proxy_autoconfiguration_file_url, CFURLRef cf_target_url, + CFStreamClientContext* context); +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_platform_cert_verifier.h b/mobile/library/common/network/apple_platform_cert_verifier.h index b559d41a4978..4ed6604c4a83 100644 --- a/mobile/library/common/network/apple_platform_cert_verifier.h +++ b/mobile/library/common/network/apple_platform_cert_verifier.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "absl/strings/string_view.h" diff --git a/mobile/library/common/network/apple_proxy_resolution.cc b/mobile/library/common/network/apple_proxy_resolution.cc new file mode 100644 index 000000000000..5af6f62711ca --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolution.cc @@ -0,0 +1,27 @@ +#include "library/common/network/apple_proxy_resolution.h" + +#include + +#include "library/common/api/external.h" +#include "library/common/network/apple_proxy_resolver.h" +#include "library/common/network/proxy_api.h" + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +void registerAppleProxyResolver() { + auto resolver = std::make_unique(); + resolver->start(); + + auto api = std::make_unique(); + api->resolver = std::move(resolver); + + Envoy::Api::External::registerApi("envoy_proxy_resolver", api.release()); +} + +#ifdef __cplusplus +} +#endif diff --git a/mobile/library/common/network/apple_proxy_resolution.h b/mobile/library/common/network/apple_proxy_resolution.h new file mode 100644 index 000000000000..068bf8fceda8 --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolution.h @@ -0,0 +1,16 @@ +#pragma once + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Registers the Apple proxy resolver. + */ +void registerAppleProxyResolver(); + +#ifdef __cplusplus +} +#endif diff --git a/mobile/library/common/network/apple_proxy_resolver.cc b/mobile/library/common/network/apple_proxy_resolver.cc new file mode 100644 index 000000000000..8329b1d853b2 --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolver.cc @@ -0,0 +1,67 @@ +#include "library/common/network/apple_proxy_resolver.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace Network { + +AppleProxyResolver::AppleProxyResolver() + : proxy_settings_monitor_( + std::make_unique(proxySettingsUpdater())), + pac_proxy_resolver_(std::make_unique()) {} + +SystemProxySettingsReadCallback AppleProxyResolver::proxySettingsUpdater() { + return SystemProxySettingsReadCallback( + [this](absl::optional proxy_settings) { + absl::MutexLock l(&mutex_); + proxy_settings_ = std::move(proxy_settings); + }); +} + +void AppleProxyResolver::start() { + proxy_settings_monitor_->start(); + started_ = true; +} + +ProxyResolutionResult +AppleProxyResolver::resolveProxy(const std::string& target_url_string, + std::vector& proxies, + ProxySettingsResolvedCallback proxy_resolution_completed) { + ASSERT(started_, "AppleProxyResolver not started."); + + std::string pac_file_url; + { + absl::MutexLock l(&mutex_); + if (!proxy_settings_.has_value()) { + return ProxyResolutionResult::NO_PROXY_CONFIGURED; + } + + const auto proxy_settings = proxy_settings_.value(); + if (!proxy_settings.isPacEnabled()) { + ProxySettings settings(proxy_settings.hostname(), proxy_settings.port()); + if (settings.isDirect()) { + return ProxyResolutionResult::NO_PROXY_CONFIGURED; + } + proxies.emplace_back(std::move(settings)); + return ProxyResolutionResult::RESULT_COMPLETED; + } + + pac_file_url = proxy_settings.pacFileUrl(); + } + + // TODO(abeyad): As stated in ApplePacProxyResolver's comments, + // CFNetworkExecuteProxyAutoConfigurationURL caches PAC file resolution, so it's not fetched on + // every request. However, it would be good to not depend on Apple's undocumented behavior and + // implement a caching layer with TTLs for PAC file URL resolution within AppleProxyResolver + // itself. + ASSERT(!pac_file_url.empty(), "PAC file URL must not be empty if PAC is enabled."); + pac_proxy_resolver_->resolveProxies( + pac_file_url, target_url_string, + [proxy_resolution_completed](const std::vector& proxies) { + proxy_resolution_completed(proxies); + }); + return ProxyResolutionResult::RESULT_IN_PROGRESS; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_proxy_resolver.h b/mobile/library/common/network/apple_proxy_resolver.h new file mode 100644 index 000000000000..7a39eed82fbe --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolver.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include +#include + +#include "library/common/network/apple_pac_proxy_resolver.h" +#include "library/common/network/apple_system_proxy_settings_monitor.h" +#include "library/common/network/proxy_resolver_interface.h" +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +/** + * Resolves proxies on Apple platforms. + */ +class AppleProxyResolver : public ProxyResolver { +public: + AppleProxyResolver(); + virtual ~AppleProxyResolver() = default; + + /** + * Starts proxy resolver. It needs to be called prior to any proxy resolution attempt. + */ + void start(); + + /** + * Resolves the proxy settings for the target URL. The result of proxy resolution is returned in + * the ProxyResolutionResult enum. If proxy resolution returns RESULT_COMPLETED, the `proxies` + * vector gets populated with the resolved proxy setting. If proxy resolution returns + * RESULT_IN_PROGRESS, the `proxy_resolution_completed` function gets invoked upon successful + * resolution of the proxy settings. + */ + virtual ProxyResolutionResult + resolveProxy(const std::string& target_url_string, std::vector& proxies, + ProxySettingsResolvedCallback proxy_resolution_completed) override; + + /* + * Supplies a function that updates this instance's proxy settings. + */ + SystemProxySettingsReadCallback proxySettingsUpdater(); + +private: + friend class TestAppleProxyResolver; + + std::unique_ptr proxy_settings_monitor_; + std::unique_ptr pac_proxy_resolver_; + absl::optional proxy_settings_; + absl::Mutex mutex_; + bool started_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_system_proxy_settings_monitor.cc b/mobile/library/common/network/apple_system_proxy_settings_monitor.cc new file mode 100644 index 000000000000..b5f5ef4b1b0d --- /dev/null +++ b/mobile/library/common/network/apple_system_proxy_settings_monitor.cc @@ -0,0 +1,99 @@ +#include "library/common/network/apple_system_proxy_settings_monitor.h" + +#include +#include +#include + +#include "library/common/apple/utility.h" + +namespace Envoy { +namespace Network { + +// The interval at which system proxy settings should be polled at. +CFTimeInterval kProxySettingsRefreshRateSeconds = 7; + +AppleSystemProxySettingsMonitor::AppleSystemProxySettingsMonitor( + SystemProxySettingsReadCallback proxy_settings_read_callback) + : proxy_settings_read_callback_(proxy_settings_read_callback) {} + +void AppleSystemProxySettingsMonitor::start() { + if (started_) { + return; + } + + started_ = true; + + dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, DISPATCH_QUEUE_PRIORITY_DEFAULT); + queue_ = dispatch_queue_create("io.envoyproxy.envoymobile.AppleSystemProxySettingsMonitor", + attributes); + + __block absl::optional proxy_settings; + dispatch_sync(queue_, ^{ + proxy_settings = readSystemProxySettings(); + proxy_settings_read_callback_(std::move(proxy_settings)); + }); + + source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue_); + dispatch_source_set_timer(source_, dispatch_time(DISPATCH_TIME_NOW, 0), + kProxySettingsRefreshRateSeconds * NSEC_PER_SEC, 0); + dispatch_source_set_event_handler(source_, ^{ + const auto new_proxy_settings = readSystemProxySettings(); + if (new_proxy_settings != proxy_settings) { + proxy_settings = new_proxy_settings; + proxy_settings_read_callback_(std::move(new_proxy_settings)); + } + }); + dispatch_resume(source_); +} + +AppleSystemProxySettingsMonitor::~AppleSystemProxySettingsMonitor() { + if (source_ != nullptr) { + dispatch_suspend(source_); + } + if (queue_ != nullptr) { + dispatch_release(queue_); + } +} + +CFDictionaryRef AppleSystemProxySettingsMonitor::getSystemProxySettings() const { + return CFNetworkCopySystemProxySettings(); +} + +absl::optional +AppleSystemProxySettingsMonitor::readSystemProxySettings() const { + CFDictionaryRef proxy_settings = getSystemProxySettings(); + if (proxy_settings == nullptr) { + return absl::nullopt; + } + + // iOS system settings allow users to enter an arbitrary big integer number i.e. 88888888 as a + // port number. That being said, testing using iOS 16 shows that Apple's APIs return + // `is_http_proxy_enabled` equal to false unless entered port number is within [0, 65535] range. + CFNumberRef cf_is_http_proxy_enabled = + static_cast(CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesHTTPEnable)); + CFNumberRef cf_is_auto_config_proxy_enabled = static_cast( + CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesProxyAutoConfigEnable)); + const bool is_http_proxy_enabled = Apple::toInt(cf_is_http_proxy_enabled) > 0; + const bool is_auto_config_proxy_enabled = Apple::toInt(cf_is_auto_config_proxy_enabled) > 0; + + absl::optional settings; + if (is_http_proxy_enabled) { + CFStringRef cf_hostname = + static_cast(CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesHTTPProxy)); + CFNumberRef cf_port = + static_cast(CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesHTTPPort)); + settings = absl::make_optional(Apple::toString(cf_hostname), + Apple::toInt(cf_port)); + } else if (is_auto_config_proxy_enabled) { + CFStringRef cf_pac_file_url_string = static_cast( + CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesProxyAutoConfigURLString)); + settings = absl::make_optional(Apple::toString(cf_pac_file_url_string)); + } + + CFRelease(proxy_settings); + return settings; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_system_proxy_settings_monitor.h b/mobile/library/common/network/apple_system_proxy_settings_monitor.h new file mode 100644 index 000000000000..bbf898bb5333 --- /dev/null +++ b/mobile/library/common/network/apple_system_proxy_settings_monitor.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include + +#include "absl/types/optional.h" +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +/** + * Monitors Apple system proxy settings changes. Optimized for iOS platform. + */ +class AppleSystemProxySettingsMonitor { +public: + /** + * Creates a new instance of the receiver. Calls provided function every time it detects a + * change of system proxy settings. Treats invalid PAC URLs as a lack of proxy configuration. + * + * @param proxy_settings_read_callback The closure to call every time system proxy settings + * change. The closure is called on a non-main queue. + */ + AppleSystemProxySettingsMonitor(SystemProxySettingsReadCallback proxy_settings_read_callback); + virtual ~AppleSystemProxySettingsMonitor(); + + /** + * Starts monitoring system proxy settings. + */ + void start(); + +protected: + // Protected and virtual so they can be overridden by tests to provide mocked system proxy + // settings. + virtual CFDictionaryRef getSystemProxySettings() const; + +private: + absl::optional readSystemProxySettings() const; + + dispatch_source_t source_; + dispatch_queue_t queue_; + bool started_; + SystemProxySettingsReadCallback proxy_settings_read_callback_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_api.h b/mobile/library/common/network/proxy_api.h new file mode 100644 index 000000000000..83f9f8d47fbf --- /dev/null +++ b/mobile/library/common/network/proxy_api.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "library/common/network/proxy_resolver_interface.h" + +namespace Envoy { +namespace Network { + +struct ProxyResolverApi { + std::unique_ptr resolver; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_resolver_interface.h b/mobile/library/common/network/proxy_resolver_interface.h new file mode 100644 index 000000000000..dd9213566819 --- /dev/null +++ b/mobile/library/common/network/proxy_resolver_interface.h @@ -0,0 +1,34 @@ +#pragma once + +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +enum class ProxyResolutionResult { + NO_PROXY_CONFIGURED = 0, + RESULT_COMPLETED = 1, + RESULT_IN_PROGRESS = 2, +}; + +// An interface for resolving the system's proxy settings. +class ProxyResolver { +public: + virtual ~ProxyResolver() = default; + + /** + * Resolves proxy for a given url. Depending on the type current system proxy settings the method + * may return results in synchronous or asynchronous way. + * @param proxies Result proxies for when the proxy resolution is performed synchronously. + * @param proxy_resolution_completed A function that's called with result proxies as its + * arguments for when the proxy resolution is performed asynchronously. + * @return Whether there is a proxy or no and whether proxy resolution was performed synchronously + * or whether it's still running. + */ + virtual ProxyResolutionResult + resolveProxy(const std::string& target_url_string, std::vector& proxies, + ProxySettingsResolvedCallback proxy_resolution_completed) PURE; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_settings.cc b/mobile/library/common/network/proxy_settings.cc new file mode 100644 index 000000000000..6350b67c3e23 --- /dev/null +++ b/mobile/library/common/network/proxy_settings.cc @@ -0,0 +1,91 @@ +#include "library/common/network/proxy_settings.h" + +#include + +namespace Envoy { +namespace Network { + +ProxySettings::ProxySettings(const std::string& host, const uint16_t port) + : address_(Envoy::Network::Utility::parseInternetAddressNoThrow(host, port)), hostname_(host), + port_(port) {} + +/*static*/ +ProxySettingsConstSharedPtr ProxySettings::parseHostAndPort(const std::string& host, + const uint16_t port) { + if (host == "" && port == 0) { + return nullptr; + } + return std::make_shared(host, port); +} + +/*static*/ +ProxySettings ProxySettings::direct() { return ProxySettings("", 0); } + +/*static*/ +ProxySettingsConstSharedPtr ProxySettings::create(const std::vector& settings_list) { + if (settings_list.empty()) { + return nullptr; + } + + for (const auto& proxy_settings : settings_list) { + if (!proxy_settings.isDirect()) { + // We'll use the first non-direct ProxySettings. + return std::make_shared(proxy_settings.hostname(), proxy_settings.port()); + } + } + + // No non-direct ProxySettings was found. + return nullptr; +} + +bool ProxySettings::isDirect() const { return hostname_ == "" && port_ == 0; } + +const Envoy::Network::Address::InstanceConstSharedPtr& ProxySettings::address() const { + return address_; +} + +const std::string& ProxySettings::hostname() const { return hostname_; } + +uint16_t ProxySettings::port() const { return port_; } + +const std::string ProxySettings::asString() const { + if (address_ != nullptr) { + return address_->asString(); + } + if (!hostname_.empty()) { + return absl::StrCat(hostname_, ":", port_); + } + return "no_proxy_configured"; +} + +bool ProxySettings::operator==(ProxySettings const& rhs) const { + // Even if the hostnames are IP addresses, they'll be stored in hostname_ + return hostname() == rhs.hostname() && port() == rhs.port(); +} + +bool ProxySettings::operator!=(ProxySettings const& rhs) const { return !(*this == rhs); } + +SystemProxySettings::SystemProxySettings(std::string&& hostname, int port) + : hostname_(std::move(hostname)), port_(port) {} + +SystemProxySettings::SystemProxySettings(std::string&& pac_file_url) + : port_(-1), pac_file_url_(std::move(pac_file_url)) {} + +const std::string& SystemProxySettings::hostname() const { return hostname_; } + +int SystemProxySettings::port() const { return port_; } + +const std::string& SystemProxySettings::pacFileUrl() const { return pac_file_url_; } + +bool SystemProxySettings::isPacEnabled() const { return !pac_file_url_.empty(); } + +bool SystemProxySettings::operator==(SystemProxySettings const& rhs) const { + return hostname() == rhs.hostname() && port() == rhs.port() && pacFileUrl() == rhs.pacFileUrl(); +} + +bool SystemProxySettings::operator!=(SystemProxySettings const& rhs) const { + return !(*this == rhs); +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_settings.h b/mobile/library/common/network/proxy_settings.h index c99aa8bc3066..19a7e4b28ffc 100644 --- a/mobile/library/common/network/proxy_settings.h +++ b/mobile/library/common/network/proxy_settings.h @@ -1,34 +1,47 @@ #pragma once +#include +#include +#include + #include "source/common/network/utility.h" +#include "absl/types/optional.h" + namespace Envoy { namespace Network { -struct ProxySettings; +class ProxySettings; +class SystemProxySettings; using ProxySettingsConstSharedPtr = std::shared_ptr; +using ProxySettingsResolvedCallback = std::function&)>; +using SystemProxySettingsReadCallback = std::function)>; /** * Proxy settings coming from platform specific APIs, i.e. ConnectivityManager in * the case of Android platform. * + * ProxySettings represents already-resolved proxy settings. For example, if the proxy + * settings are obtained from a PAC URL, then this object represents the resolved proxy + * settings after reading and parsing the PAC URL. + * + * TODO(abeyad): rename this class to ProxySetting (the plural in the name is confusing). */ -struct ProxySettings { +class ProxySettings { +public: /** - * @brief Construct a new Proxy Settings object. + * Construct a new Proxy Settings object. * * @param host The proxy host defined as a hostname or an IP address. Some platforms * (i.e., Android) allow users to specify proxy using either one of these. * @param port The proxy port. */ - ProxySettings(const std::string& host, const uint16_t port) - : address_(Envoy::Network::Utility::parseInternetAddressNoThrow(host, port)), hostname_(host), - port_(port) {} + ProxySettings(const std::string& host, const uint16_t port); /** - * @brief Parses given host and domain and creates proxy settings. Returns nullptr - * for an empty host and a port equal to 0 as they are passed to c++ native layer - * as a synonym of the lack of proxy settings configured on a device. + * Parses given host and domain and creates proxy settings. Returns nullptr for an empty host + * and a port equal to 0, as they are passed to the C++ native layer to represent the lack of + * proxy settings configured on a device. * * @param host The proxy host defined as a hostname or an IP address. Some platforms * (i.e., Android) allow users to specify proxy using either one of these. @@ -36,59 +49,76 @@ struct ProxySettings { * @return The created proxy settings, nullptr if the passed host is an empty string and * port is equal to 0. */ - static const ProxySettingsConstSharedPtr parseHostAndPort(const std::string& host, - const uint16_t port) { - if (host == "" && port == 0) { - return nullptr; - } - return std::make_shared(host, port); - } + static ProxySettingsConstSharedPtr parseHostAndPort(const std::string& host, const uint16_t port); + + /** + * Creates a ProxySettings instance to represent a direct connection (i.e. no proxy at all). + * + * @return The ProxySettings object. Calling isDirect() on it returns true. + */ + static ProxySettings direct(); + + /** + * Creates a shared pointer instance of the ProxySettings to use, from the list of resolved + * system proxy settings. + * + * @param settings_list A list of ProxySettings provided by the system. + * + * @return A shared pointer to an instance of the ProxySettings that was chosen from the proxy + * settings list. + */ + static ProxySettingsConstSharedPtr create(const std::vector& settings_list); /** - * @brief Returns an address of a proxy. This method returns nullptr for proxy settings - * that are initialized with anything other than an IP address. + * @return true if the instance represents a direct connection (i.e. no proxy), false otherwise. + */ + bool isDirect() const; + + /** + * Returns an address of a proxy. This method returns nullptr for proxy settings that are + * initialized with anything other than an IP address. * * @return Address of a proxy or nullptr if proxy address is incorrect or host is * defined using a hostname and not an IP address. */ - const Envoy::Network::Address::InstanceConstSharedPtr& address() const { return address_; } + const Envoy::Network::Address::InstanceConstSharedPtr& address() const; /** - * @brief Returns the hostname of a proxy. + * Returns a reference to the hostname of the proxy. * - * @return Hostname of a proxy or the empty string if there is no hostname. + * @return Hostname of the proxy (if the string is empty, there is no hostname). */ - const std::string& hostname() const { return hostname_; } + const std::string& hostname() const; /** - * @brief Returns the port of the proxy. + * Returns the port of the proxy. * * @return Port of the proxy. */ - uint16_t port() const { return port_; } + uint16_t port() const; /** - * @brief Returns a human readable representation of the proxy settings represented - * by the receiver + * Returns a human readable representation of the proxy settings represented by the receiver. * * @return const A human readable representation of the receiver. */ - const std::string asString() const { - if (address_ != nullptr) { - return address_->asString(); - } - if (!hostname_.empty()) { - return absl::StrCat(hostname_, ":", port_); - } - return "no_proxy_configured"; - } + const std::string asString() const; - bool operator==(ProxySettings const& rhs) const { - // Even if the hostnames are IP addresses, they'll be stored in hostname_ - return hostname() == rhs.hostname() && port() == rhs.port(); - } + /** + * Equals operator overload. + * @param rhs another ProxySettings object. + * @return returns true if the two ProxySettings objects are equivalent (represents the + * same configuration); false otherwise. + */ + bool operator==(ProxySettings const& rhs) const; - bool operator!=(ProxySettings const& rhs) const { return !(*this == rhs); } + /** + * Not equals operator overload. + * @param rhs another ProxySettings object. + * @return returns true if the two ProxySettings objects are not equivalent (represents + * different proxy configuration); false otherwise. + */ + bool operator!=(ProxySettings const& rhs) const; private: Envoy::Network::Address::InstanceConstSharedPtr address_; @@ -96,5 +126,67 @@ struct ProxySettings { uint16_t port_; }; +/** + * System proxy settings. There are 2 ways to specify desired proxy settings. Either provide proxy's + * hostname or provide a URL to a Proxy Auto-Configuration (PAC) file. + */ +class SystemProxySettings { +public: + /** + * Creates proxy settings using provided hostname and port. + * @param hostname A domain name or an IP address. + * @param port A valid internet port from within [0-65535] range. + */ + SystemProxySettings(std::string&& hostname, int port); + + /** + * Creates proxy settings using provided URL to a Proxy Auto-Configuration (PAC) file. + * @param pac_file_url A URL to a Proxy Auto-Configuration (PAC) file. + */ + SystemProxySettings(std::string&& pac_file_url); + + /** + * @return a reference to the hostname (which will be an empty string if not configured). + */ + const std::string& hostname() const; + + /* + * @return the port number, or -1 if a PAC file is configured. + */ + int port() const; + + /** + * @return a reference to the PAC file URL (which will be an empty string if not configured). + */ + const std::string& pacFileUrl() const; + + /** + * @return returns true if this object represents a PAC file URL configured proxy, + * returns false if this object represents a host/port configured proxy. + */ + bool isPacEnabled() const; + + /** + * Equals operator overload. + * @param rhs another SystemProxySettings object. + * @return returns true if the two SystemProxySettings objects are equivalent (represents the + * same configuration); false otherwise. + */ + bool operator==(SystemProxySettings const& rhs) const; + + /** + * Not equals operator overload. + * @param rhs another SystemProxySettings object. + * @return returns true if the two SystemProxySettings objects are not equivalent (represents + * different proxy configuration); false otherwise. + */ + bool operator!=(SystemProxySettings const& rhs) const; + +private: + std::string hostname_; + int port_; + std::string pac_file_url_; +}; + } // namespace Network } // namespace Envoy diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java index 6aff90ddd823..96102da6debc 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java @@ -81,7 +81,7 @@ private ProxyInfo extractProxyInfo(final Intent intent) { // proxy is configured. // // See https://github.com/envoyproxy/envoy-mobile/issues/2531 for more details. - if (info.getPacFileUrl() != null && info.getPacFileUrl() != Uri.EMPTY) { + if (!Uri.EMPTY.equals(info.getPacFileUrl())) { if (intent == null) { // PAC proxies are supported only when Intent is present return null; diff --git a/mobile/library/objective-c/BUILD b/mobile/library/objective-c/BUILD index 00d6435a4939..ee5c61619d10 100644 --- a/mobile/library/objective-c/BUILD +++ b/mobile/library/objective-c/BUILD @@ -60,6 +60,7 @@ envoy_objc_library( "//library/common:internal_engine_lib", "//library/common/api:c_types", "//library/common/network:apple_platform_cert_verifier", + "//library/common/network:apple_proxy_resolution_lib", ], ) diff --git a/mobile/library/objective-c/EnvoyEngine.h b/mobile/library/objective-c/EnvoyEngine.h index bddcd0775507..bb765e0a470a 100644 --- a/mobile/library/objective-c/EnvoyEngine.h +++ b/mobile/library/objective-c/EnvoyEngine.h @@ -33,11 +33,14 @@ NS_ASSUME_NONNULL_BEGIN @param logger Logging interface. @param eventTracker Event tracking interface. @param networkMonitoringMode Configure how the engines observe network reachability. + @param respectSystemProxySettings Whether to respect system proxy settings when performing + network requests. */ - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning logger:(nullable void (^)(NSInteger, NSString *))logger eventTracker:(nullable void (^)(EnvoyEvent *))eventTracker - networkMonitoringMode:(int)networkMonitoringMode; + networkMonitoringMode:(int)networkMonitoringMode + respectSystemProxySettings:(BOOL)respectSystemProxySettings; /** Run the Envoy engine with the provided configuration and log level. diff --git a/mobile/library/objective-c/EnvoyEngineImpl.mm b/mobile/library/objective-c/EnvoyEngineImpl.mm index adac27f8ed0a..0395f1b8641b 100644 --- a/mobile/library/objective-c/EnvoyEngineImpl.mm +++ b/mobile/library/objective-c/EnvoyEngineImpl.mm @@ -10,6 +10,8 @@ #import "library/cc/engine_builder.h" #import "library/common/internal_engine.h" +#include "library/common/network/apple_proxy_resolution.h" + #if TARGET_OS_IPHONE #import #endif @@ -405,7 +407,8 @@ @implementation EnvoyEngineImpl { - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning logger:(nullable void (^)(NSInteger, NSString *))logger eventTracker:(nullable void (^)(EnvoyEvent *))eventTracker - networkMonitoringMode:(int)networkMonitoringMode { + networkMonitoringMode:(int)networkMonitoringMode + respectSystemProxySettings:(BOOL)respectSystemProxySettings { self = [super init]; if (!self) { return nil; @@ -436,6 +439,10 @@ - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning _engine = new Envoy::InternalEngine(native_callbacks, native_logger, native_event_tracker); _engineHandle = reinterpret_cast(_engine); + if (respectSystemProxySettings) { + registerAppleProxyResolver(); + } + if (networkMonitoringMode == 1) { [_networkMonitor startReachability]; } else if (networkMonitoringMode == 2) { diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index 2969e21bc3ea..7a754d517c8a 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -154,6 +154,7 @@ open class EngineBuilder: NSObject { private var quicHints: [String: Int] = [:] private var quicCanonicalSuffixes: [String] = [] private var enableInterfaceBinding: Bool = false + private var respectSystemProxySettings: Bool = false private var enforceTrustChainVerification: Bool = true private var enablePlatformCertificateValidation: Bool = false private var enableDrainPostDnsRefresh: Bool = false @@ -366,6 +367,25 @@ open class EngineBuilder: NSObject { return self } + /// + /// Specify whether system proxy settings should be respected. If yes, Envoy Mobile will + /// use iOS APIs to query iOS Proxy settings configured on a device and will + /// respect these settings when establishing connections with remote services. + /// + /// The method is introduced for experimentation purposes and as a safety guard against + /// critical issues in the implementation of the proxying feature. It's intended to be removed + /// after it's confirmed that proxies on iOS work as expected. + /// + /// - parameter respectSystemProxySettings: whether to use the system's proxy settings for + /// outbound connections. + /// + /// - returns: This builder. + @discardableResult + public func respectSystemProxySettings(_ respectSystemProxySettings: Bool) -> Self { + self.respectSystemProxySettings = respectSystemProxySettings + return self + } + /// Specify whether to drain connections after the resolution of a soft DNS refresh. /// A refresh may be triggered directly via the Engine API, or as a result of a network /// status update provided by the OS. Draining connections does not interrupt existing @@ -697,7 +717,8 @@ open class EngineBuilder: NSObject { } }, eventTracker: self.eventTracker, - networkMonitoringMode: Int32(self.monitoringMode.rawValue)) + networkMonitoringMode: Int32(self.monitoringMode.rawValue), + respectSystemProxySettings: self.respectSystemProxySettings) let config = self.makeConfig() #if canImport(EnvoyCxxSwiftInterop) if self.enableSwiftBootstrap { diff --git a/mobile/library/swift/mocks/MockEnvoyEngine.swift b/mobile/library/swift/mocks/MockEnvoyEngine.swift index 057699ef05a0..f02a3cf8d6c2 100644 --- a/mobile/library/swift/mocks/MockEnvoyEngine.swift +++ b/mobile/library/swift/mocks/MockEnvoyEngine.swift @@ -5,7 +5,8 @@ import Foundation final class MockEnvoyEngine: NSObject { init(runningCallback onEngineRunning: (() -> Void)? = nil, logger: ((Int, String) -> Void)? = nil, - eventTracker: (([String: String]) -> Void)? = nil, networkMonitoringMode: Int32 = 0) {} + eventTracker: (([String: String]) -> Void)? = nil, networkMonitoringMode: Int32 = 0, + respectSystemProxySettings: Bool = false) {} /// Closure called when `run(withConfig:)` is called. static var onRunWithConfig: ((_ config: EnvoyConfiguration, _ logLevel: String?) -> Void)? diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index 71afc3b85a3d..020d6e97559a 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -94,6 +94,7 @@ TEST(TestConfig, ConfigIsApplied) { "canonical_suffixes: \".opq.com\"", "canonical_suffixes: \".xyz.com\"", "num_timeouts_to_trigger_port_migration { value: 4 }", + "idle_network_timeout { seconds: 30 }", #endif "key: \"dns_persistent_cache\" save_interval { seconds: 101 }", "key: \"always_use_v6\" value { bool_value: true }", diff --git a/mobile/test/common/extensions/filters/http/network_configuration/BUILD b/mobile/test/common/extensions/filters/http/network_configuration/BUILD index 9630ee6652bb..bac5601177cb 100644 --- a/mobile/test/common/extensions/filters/http/network_configuration/BUILD +++ b/mobile/test/common/extensions/filters/http/network_configuration/BUILD @@ -18,6 +18,9 @@ envoy_extension_cc_test( "//library/common/data:utility_lib", "//library/common/extensions/filters/http/network_configuration:config", "//library/common/extensions/filters/http/network_configuration:filter_cc_proto", + "//library/common/network:proxy_api_lib", + "//library/common/network:proxy_resolver_interface_lib", + "//library/common/network:proxy_settings_lib", "@envoy//test/extensions/common/dynamic_forward_proxy:mocks", "@envoy//test/mocks/event:event_mocks", "@envoy//test/mocks/http:http_mocks", diff --git a/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc b/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc index 813c4b7fdcd4..05a272da5bc0 100644 --- a/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc +++ b/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc @@ -11,6 +11,8 @@ #include "library/common/data/utility.h" #include "library/common/extensions/filters/http/network_configuration/filter.h" #include "library/common/extensions/filters/http/network_configuration/filter.pb.h" +#include "library/common/network/proxy_api.h" +#include "library/common/network/proxy_resolver_interface.h" #include "library/common/network/proxy_settings.h" using Envoy::Extensions::Common::DynamicForwardProxy::DnsCache; @@ -60,8 +62,8 @@ class MockConnectivityManager : public Network::ConnectivityManager { class NetworkConfigurationFilterTest : public testing::Test { public: NetworkConfigurationFilterTest() - : connectivity_manager_(new NiceMock), - proxy_settings_(new Network::ProxySettings("127.0.0.1", 82)), + : connectivity_manager_(std::make_shared>()), + proxy_settings_(std::make_shared("127.0.0.1", 82)), filter_(connectivity_manager_, false, false) { filter_.setDecoderFilterCallbacks(decoder_callbacks_); ON_CALL(decoder_callbacks_.stream_info_, getRequestHeaders()) @@ -184,6 +186,104 @@ TEST_F(NetworkConfigurationFilterTest, AsyncDnsLookupSuccess) { filter_.onLoadDnsCacheComplete(host_info_); } +class MockProxyResolver : public Network::ProxyResolver { +public: + MOCK_METHOD(Network::ProxyResolutionResult, resolveProxy, + (const std::string& target_url_string, std::vector& proxies, + Network::ProxySettingsResolvedCallback proxy_resolution_completed)); +}; + +class NetworkConfigurationFilterProxyResolverApiTest : public NetworkConfigurationFilterTest { +public: + NetworkConfigurationFilterProxyResolverApiTest() : NetworkConfigurationFilterTest() {} + + void SetUp() override { + auto proxy_resolver_api = std::make_unique(); + proxy_resolver_api->resolver = std::make_unique>(); + Api::External::registerApi("envoy_proxy_resolver", proxy_resolver_api.release()); + } + + void TearDown() override { + std::unique_ptr wrapped_api(getResolverApi()); + // Safe deletion of the envoy_proxy_resolver API. + wrapped_api.reset(); + } + + Network::ProxyResolverApi* getResolverApi() { + return static_cast( + Api::External::retrieveApi("envoy_proxy_resolver")); + } + + MockProxyResolver& getMockProxyResolver() { + return *static_cast(getResolverApi()->resolver.get()); + } +}; + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, NoProxyConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce(Return(Network::ProxyResolutionResult::NO_PROXY_CONFIGURED)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, EmptyProxyConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce(Return(Network::ProxyResolutionResult::RESULT_COMPLETED)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, DirectProxyConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce([](const std::string& /*target_url_string*/, + std::vector& proxies, + Network::ProxySettingsResolvedCallback /*proxy_resolution_completed*/) { + proxies.emplace_back(Network::ProxySettings::direct()); + return Network::ProxyResolutionResult::RESULT_COMPLETED; + }); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, ProxyWithIpAddressConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce([](const std::string& /*target_url_string*/, + std::vector& proxies, + Network::ProxySettingsResolvedCallback /*proxy_resolution_completed*/) { + proxies.emplace_back(Network::ProxySettings("127.0.0.1", 8080)); + return Network::ProxyResolutionResult::RESULT_COMPLETED; + }); + EXPECT_CALL(decoder_callbacks_.stream_info_, filterState()); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, ProxyWithHostConfigured) { + createCache(); + + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce([](const std::string& /*target_url_string*/, + std::vector& proxies, + Network::ProxySettingsResolvedCallback /*proxy_resolution_completed*/) { + proxies.emplace_back(Network::ProxySettings("foo.com", 8080)); + return Network::ProxyResolutionResult::RESULT_COMPLETED; + }); + EXPECT_CALL(decoder_callbacks_.stream_info_, filterState()); + EXPECT_CALL(*dns_cache_, loadDnsCacheEntry_(Eq("foo.com"), 8080, false, _)) + .WillOnce( + Invoke([&](absl::string_view, uint16_t, bool, DnsCache::LoadDnsCacheEntryCallbacks&) { + return MockDnsCache::MockLoadDnsCacheEntryResult{ + DnsCache::LoadDnsCacheEntryStatus::InCache, nullptr, host_info_}; + })); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + } // namespace } // namespace NetworkConfiguration } // namespace HttpFilters diff --git a/mobile/test/common/network/proxy_settings_test.cc b/mobile/test/common/network/proxy_settings_test.cc index db815c633551..bf2f538bd2db 100644 --- a/mobile/test/common/network/proxy_settings_test.cc +++ b/mobile/test/common/network/proxy_settings_test.cc @@ -1,3 +1,5 @@ +#include + #include "gtest/gtest.h" #include "library/common/network/proxy_settings.h" @@ -49,5 +51,39 @@ TEST_F(ProxySettingsTest, Hostname) { EXPECT_EQ(ProxySettings("foo.com", 80).asString(), "foo.com:80"); } +TEST_F(ProxySettingsTest, Direct) { + const ProxySettings direct = ProxySettings::direct(); + EXPECT_TRUE(direct.isDirect()); + EXPECT_EQ(direct.hostname(), ""); + EXPECT_EQ(direct.port(), 0); + EXPECT_EQ(direct.address(), nullptr); + EXPECT_EQ(direct.asString(), "no_proxy_configured"); + + const ProxySettings non_direct = ProxySettings("foo.com", 80); + EXPECT_FALSE(non_direct.isDirect()); +} + +TEST_F(ProxySettingsTest, Create) { + std::vector settings_list; + + // Empty list, so nullptr is returned. + EXPECT_EQ(ProxySettings::create(settings_list), nullptr); + + // 2nd setting in the list is non-direct, so it should be chosen to create the shared_ptr from. + settings_list.emplace_back(ProxySettings::direct()); + settings_list.emplace_back(ProxySettings("foo.com", 80)); + settings_list.emplace_back(ProxySettings("bar.com", 8080)); + ProxySettingsConstSharedPtr settings = ProxySettings::create(settings_list); + EXPECT_EQ(settings->hostname(), "foo.com"); + EXPECT_EQ(settings->port(), 80); + EXPECT_FALSE(settings->isDirect()); + + // All ProxySettings in the list are direct, so nullptr is returned. + settings_list.clear(); + settings_list.emplace_back(ProxySettings::direct()); + settings_list.emplace_back(ProxySettings::direct()); + EXPECT_EQ(ProxySettings::create(settings_list), nullptr); +} + } // namespace Network } // namespace Envoy diff --git a/mobile/test/common/proxy/BUILD b/mobile/test/common/proxy/BUILD new file mode 100644 index 000000000000..6c05ec3608b8 --- /dev/null +++ b/mobile/test/common/proxy/BUILD @@ -0,0 +1,38 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test_library", "envoy_mobile_package") + +licenses(["notice"]) # Apache 2 + +envoy_mobile_package() + +envoy_cc_test_library( + name = "test_apple_proxy_settings_lib", + srcs = select({ + "@envoy//bazel:apple": [ + "test_apple_api_registration.cc", + "test_apple_pac_proxy_resolver.cc", + "test_apple_proxy_resolver.cc", + "test_apple_proxy_settings_monitor.cc", + ], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": [ + "test_apple_api_registration.h", + "test_apple_pac_proxy_resolver.h", + "test_apple_proxy_resolver.h", + "test_apple_proxy_settings_monitor.h", + ], + "//conditions:default": [], + }), + repository = "@envoy", + deps = select({ + "@envoy//bazel:apple": [ + "//library/common/api:external_api_lib", + "//library/common/network:apple_proxy_resolution_lib", + "//library/common/network:proxy_api_lib", + "//library/common/network:proxy_settings_lib", + "//library/common/types:c_types_lib", + ], + "//conditions:default": [], + }), +) diff --git a/mobile/test/common/proxy/test_apple_api_registration.cc b/mobile/test/common/proxy/test_apple_api_registration.cc new file mode 100644 index 000000000000..5a8ea1ff6f9c --- /dev/null +++ b/mobile/test/common/proxy/test_apple_api_registration.cc @@ -0,0 +1,46 @@ +#include "test/common/proxy/test_apple_api_registration.h" + +#include "test/common/proxy/test_apple_pac_proxy_resolver.h" +#include "test/common/proxy/test_apple_proxy_resolver.h" +#include "test/common/proxy/test_apple_proxy_settings_monitor.h" + +#include "library/common/api/external.h" +#include "library/common/network/apple_proxy_resolution.h" +#include "library/common/network/apple_proxy_resolver.h" +#include "library/common/network/proxy_api.h" +#include "library/common/network/proxy_resolver_interface.h" +#include "library/common/types/c_types.h" + +// NOLINT(namespace-envoy) + +void registerTestAppleProxyResolver(absl::string_view host, int port, const bool use_pac_resolver) { + // Fetch the existing registered envoy_proxy_resolver API, if it exists. + void* existing_proxy_resolver = + Envoy::Api::External::retrieveApi("envoy_proxy_resolver", /*allow_absent=*/true); + if (existing_proxy_resolver != nullptr) { + std::unique_ptr wrapped( + static_cast(existing_proxy_resolver)); + // Delete the existing ProxyResolverApi. + wrapped.reset(); + } + + // Create a new test proxy resolver. + auto test_resolver = std::make_unique(); + // Create a TestAppleSystemProxySettingsMonitor and set the test resolver to use it. + test_resolver->setSettingsMonitorForTest( + std::make_unique( + std::string(host), port, use_pac_resolver, test_resolver->proxySettingsUpdater())); + if (use_pac_resolver) { + // Create a TestApplePacProxyResolver and set the test resolver to use it. + test_resolver->setPacResolverForTest( + std::make_unique(std::string(host), port)); + } + // Start the resolver, as we do when registering the envoy_proxy_resolver API. + test_resolver->start(); + // Create a new test ProxyResolverApi. + auto proxy_resolver = std::make_unique(); + // Set the API to use the test AppleProxyResolver. + proxy_resolver->resolver = std::move(test_resolver); + // Register the new test ProxyResolverApi. The Api registry takes over the pointer. + Envoy::Api::External::registerApi("envoy_proxy_resolver", proxy_resolver.release()); +} diff --git a/mobile/test/common/proxy/test_apple_api_registration.h b/mobile/test/common/proxy/test_apple_api_registration.h new file mode 100644 index 000000000000..4edc2f2cafa9 --- /dev/null +++ b/mobile/test/common/proxy/test_apple_api_registration.h @@ -0,0 +1,23 @@ +#pragma once + +#include "absl/strings/string_view.h" + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Registers the test Apple proxy resolver. If the `envoy_proxy_resolver` API already exists, it + * gets removed and deleted first. The test proxy resolver then gets registered as the + * `envoy_proxy_resolver` API. + * + * @param host The hostname of the test proxy. + * @param port The port of the test proxy. + */ +void registerTestAppleProxyResolver(absl::string_view host, int port, bool use_pac_resolver); + +#ifdef __cplusplus +} +#endif diff --git a/mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc new file mode 100644 index 000000000000..9c46434ce4c7 --- /dev/null +++ b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc @@ -0,0 +1,92 @@ +#include "test/common/proxy/test_apple_pac_proxy_resolver.h" + +#include +#include + +#include + +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +namespace { + +// Contains state that is passed into the run loop callback. +struct RunLoopSourceInfo { + RunLoopSourceInfo(CFStreamClientContext* _context, const std::string& _host, const int _port) + : context(_context), host(_host), port(_port) {} + + CFStreamClientContext* context; + const std::string& host; + const int port; +}; + +void runLoopCallback(void* info) { + // Wrap in unique_ptr so we release the memory at the end of the function execution. + std::unique_ptr run_loop_info(static_cast(info)); + + const void* keys[] = {kCFProxyTypeKey, kCFProxyHostNameKey, kCFProxyPortNumberKey}; + const void* values[] = { + kCFProxyTypeHTTPS, + CFStringCreateWithCString(kCFAllocatorDefault, run_loop_info->host.c_str(), + kCFStringEncodingUTF8), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &run_loop_info->port)}; + const int num_pairs = sizeof(keys) / sizeof(CFStringRef); + + CFDictionaryRef settings_dict = CFDictionaryCreate( + kCFAllocatorDefault, static_cast(keys), static_cast(values), + num_pairs, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + const void* objects[] = {settings_dict}; + CFArrayRef proxies = CFArrayCreate(kCFAllocatorDefault, static_cast(objects), + /*numValues=*/1, &kCFTypeArrayCallBacks); + + // Wrap in unique_ptr so we release the memory at the end of the function execution. + std::unique_ptr client_context(run_loop_info->context); + + // Invoke the proxy resolution callback that the ApplePacProxyResolver invokes. + Network::proxyAutoConfigurationResultCallback(/*ptr=*/client_context->info, proxies, + /*cf_error=*/nullptr); + + // Release the memory allocated above. + CFRelease(proxies); + CFRelease(settings_dict); + // We don't want to release the first value, since it's a global constant. + for (int i = 1; i < num_pairs; ++i) { + CFRelease(values[i]); + } +} + +} // namespace + +TestApplePacProxyResolver::TestApplePacProxyResolver(const std::string& host, const int port) + : host_(host), port_(port) {} + +CFRunLoopSourceRef TestApplePacProxyResolver::createPacUrlResolverSource( + CFURLRef /*cf_proxy_autoconfiguration_file_url*/, CFURLRef /*cf_target_url*/, + CFStreamClientContext* context) { + auto run_loop_info = std::make_unique(context, host_, port_); + run_loop_context_ = std::make_unique( + CFRunLoopSourceContext{/*version=*/0, + /*info=*/run_loop_info.release(), + /*retain=*/nullptr, + /*release=*/nullptr, + /*copyDescription=*/nullptr, + /*equal=*/nullptr, + /*hash=*/nullptr, + /*schedule=*/nullptr, + /*cancel=*/nullptr, + /*perform=*/runLoopCallback}); + + auto run_loop_source = + CFRunLoopSourceCreate(kCFAllocatorDefault, /*order=*/0, run_loop_context_.get()); + // There are no network events that get triggered to notify this test run loop source that + // it should process a result, so we have to signal it manually. + CFRunLoopSourceSignal(run_loop_source); + + return run_loop_source; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_pac_proxy_resolver.h b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.h new file mode 100644 index 000000000000..d1985c967c03 --- /dev/null +++ b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "library/common/network/apple_pac_proxy_resolver.h" + +namespace Envoy { +namespace Network { + +class TestApplePacProxyResolver : public Network::ApplePacProxyResolver { +public: + TestApplePacProxyResolver(const std::string& host, const int port); + virtual ~TestApplePacProxyResolver() = default; + +protected: + CFRunLoopSourceRef createPacUrlResolverSource(CFURLRef cf_proxy_autoconfiguration_file_url, + CFURLRef cf_target_url, + CFStreamClientContext* context) override; + + const std::string host_; + const int port_; + std::unique_ptr run_loop_context_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_resolver.cc b/mobile/test/common/proxy/test_apple_proxy_resolver.cc new file mode 100644 index 000000000000..283518fef8bc --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_resolver.cc @@ -0,0 +1,17 @@ +#include "test/common/proxy/test_apple_proxy_resolver.h" + +namespace Envoy { +namespace Network { + +void TestAppleProxyResolver::setSettingsMonitorForTest( + std::unique_ptr&& monitor) { + proxy_settings_monitor_ = std::move(monitor); +} + +void TestAppleProxyResolver::setPacResolverForTest( + std::unique_ptr&& resolver) { + pac_proxy_resolver_ = std::move(resolver); +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_resolver.h b/mobile/test/common/proxy/test_apple_proxy_resolver.h new file mode 100644 index 000000000000..f5543685cb2a --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_resolver.h @@ -0,0 +1,27 @@ +#pragma once + +#include "library/common/network/apple_pac_proxy_resolver.h" +#include "library/common/network/apple_proxy_resolver.h" +#include "library/common/network/apple_system_proxy_settings_monitor.h" + +namespace Envoy { +namespace Network { + +class TestAppleProxyResolver : public Network::AppleProxyResolver { +public: + /** + * Resets the proxy settings monitor to the supplied monitor instance. + * For tests only. + */ + void + setSettingsMonitorForTest(std::unique_ptr&& monitor); + + /** + * Resets the PAC URL resolver to the supplied instance. + * For tests only. + */ + void setPacResolverForTest(std::unique_ptr&& resolver); +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc new file mode 100644 index 000000000000..7c6ff214dc9d --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc @@ -0,0 +1,68 @@ +#include "test/common/proxy/test_apple_proxy_settings_monitor.h" + +#include +#include + +namespace Envoy { +namespace Network { + +TestAppleSystemProxySettingsMonitor::TestAppleSystemProxySettingsMonitor( + const std::string& host, const int port, const bool use_pac_resolver, + Network::SystemProxySettingsReadCallback proxy_settings_read_callback) + : AppleSystemProxySettingsMonitor(std::move(proxy_settings_read_callback)), host_(host), + port_(port), use_pac_resolver_(use_pac_resolver) {} + +CFDictionaryRef TestAppleSystemProxySettingsMonitor::getSystemProxySettings() const { + if (use_pac_resolver_) { + return getSystemProxySettingsWithPac(); + } + return getSystemProxySettingsWithoutPac(); +} + +CFDictionaryRef TestAppleSystemProxySettingsMonitor::getSystemProxySettingsWithoutPac() const { + const void* keys[] = {kCFNetworkProxiesHTTPEnable, kCFNetworkProxiesHTTPProxy, + kCFNetworkProxiesHTTPPort}; + + const void* values[] = { + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &one_), + CFStringCreateWithCString(kCFAllocatorDefault, host_.c_str(), kCFStringEncodingUTF8), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &port_)}; + + int num_pairs = sizeof(keys) / sizeof(CFStringRef); + + CFDictionaryRef settings_dict = CFDictionaryCreate( + kCFAllocatorDefault, static_cast(keys), static_cast(values), + num_pairs, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + for (int i = 0; i < num_pairs; ++i) { + CFRelease(values[i]); + } + return settings_dict; +} + +CFDictionaryRef TestAppleSystemProxySettingsMonitor::getSystemProxySettingsWithPac() const { + const void* keys[] = {kCFNetworkProxiesProxyAutoConfigEnable, + kCFNetworkProxiesProxyAutoConfigURLString}; + + const void* values[] = { + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &one_), + // The PAC file URL doesn't matter, we don't use it in the tests; we just need it to exist to + // exercise the PAC file URL code path in the tests. + CFStringCreateWithCString(kCFAllocatorDefault, + "http://randomproxysettingsserver.com/random.pac", + kCFStringEncodingUTF8)}; + + int num_pairs = sizeof(keys) / sizeof(CFStringRef); + + CFDictionaryRef settings_dict = CFDictionaryCreate( + kCFAllocatorDefault, static_cast(keys), static_cast(values), + num_pairs, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + for (int i = 0; i < num_pairs; ++i) { + CFRelease(values[i]); + } + return settings_dict; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_settings_monitor.h b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.h new file mode 100644 index 000000000000..1255b6c609df --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "library/common/network/apple_system_proxy_settings_monitor.h" +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +class TestAppleSystemProxySettingsMonitor : public Network::AppleSystemProxySettingsMonitor { +public: + TestAppleSystemProxySettingsMonitor( + const std::string& host, const int port, bool use_pac_resolver, + Network::SystemProxySettingsReadCallback proxy_settings_read_callback); + virtual ~TestAppleSystemProxySettingsMonitor() = default; + +protected: + CFDictionaryRef getSystemProxySettings() const override; + // Gets mock system proxy settings without a PAC file. + CFDictionaryRef getSystemProxySettingsWithoutPac() const; + // Gets mock system proxy settings with a PAC file. + CFDictionaryRef getSystemProxySettingsWithPac() const; + + const std::string host_; + const int port_; + const bool use_pac_resolver_; + // Used to pass into the Apple APIs to represent the number 1. We pass in pointers to the Apple + // APIs, so the value needs to outline the passed-in pointer. + const int one_{1}; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/objective-c/BUILD b/mobile/test/objective-c/BUILD index 1d977756c4ca..2460ad8698cd 100644 --- a/mobile/test/objective-c/BUILD +++ b/mobile/test/objective-c/BUILD @@ -11,7 +11,6 @@ envoy_mobile_objc_test( "EnvoyBridgeUtilityTest.m", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_objc_bridge_lib", @@ -24,7 +23,6 @@ envoy_mobile_objc_test( "EnvoyKeyValueStoreBridgeImplTest.m", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_key_value_store_bridge_impl_lib", @@ -45,3 +43,17 @@ envoy_objc_library( "//test/common/integration:test_server_interface_lib", ], ) + +envoy_objc_library( + name = "envoy_test_api", + testonly = True, + srcs = [ + "EnvoyTestApi.mm", + ], + hdrs = ["EnvoyTestApi.h"], + module_name = "EnvoyTestApi", + visibility = ["//visibility:public"], + deps = [ + "//test/common/proxy:test_apple_proxy_settings_lib", + ], +) diff --git a/mobile/test/objective-c/EnvoyTestApi.h b/mobile/test/objective-c/EnvoyTestApi.h new file mode 100644 index 000000000000..6f75e418728a --- /dev/null +++ b/mobile/test/objective-c/EnvoyTestApi.h @@ -0,0 +1,13 @@ +#pragma once + +#import + +// Interface for dealing with custom API registration for test classes. +@interface EnvoyTestApi : NSObject + +// Registers a test Proxy Resolver API. ++ (void)registerTestProxyResolver:(NSString *)host + port:(NSInteger)port + usePacResolver:(BOOL)usePacResolver; + +@end diff --git a/mobile/test/objective-c/EnvoyTestApi.mm b/mobile/test/objective-c/EnvoyTestApi.mm new file mode 100644 index 000000000000..3458aa5774c5 --- /dev/null +++ b/mobile/test/objective-c/EnvoyTestApi.mm @@ -0,0 +1,13 @@ +#import "test/objective-c/EnvoyTestApi.h" + +#import "test/common/proxy/test_apple_api_registration.h" + +@implementation EnvoyTestApi + ++ (void)registerTestProxyResolver:(NSString *)host + port:(NSInteger)port + usePacResolver:(BOOL)usePacResolver { + registerTestAppleProxyResolver([host UTF8String], port, usePacResolver); +} + +@end diff --git a/mobile/test/objective-c/EnvoyTestServer.h b/mobile/test/objective-c/EnvoyTestServer.h index df61cc4bf06b..13b0ea1f48bf 100644 --- a/mobile/test/objective-c/EnvoyTestServer.h +++ b/mobile/test/objective-c/EnvoyTestServer.h @@ -3,12 +3,20 @@ #import // Interface for starting and managing a test server. Calls into to test_server.cc +// +// NB: Any test that utilizes this class must have a `no-remote-exec` tag in its BUILD target. +// EnvoyTestServer binds to a listening socket on the machine it runs on, and on CI, this +// operation is not permitted in remote execution environments. @interface EnvoyTestServer : NSObject // Get the port of the upstream server. + (NSInteger)getEnvoyPort; // Starts a server with HTTP1 and no TLS. + (void)startHttp1PlaintextServer; +// Starts a server as a HTTP proxy. ++ (void)startHttpProxyServer; +// Starts a server as a HTTPS proxy. ++ (void)startHttpsProxyServer; // Shut down and clean up server. + (void)shutdownTestServer; // Add response data to the upstream. diff --git a/mobile/test/objective-c/EnvoyTestServer.mm b/mobile/test/objective-c/EnvoyTestServer.mm index 808863fb67cd..e1d64714fd52 100644 --- a/mobile/test/objective-c/EnvoyTestServer.mm +++ b/mobile/test/objective-c/EnvoyTestServer.mm @@ -11,6 +11,14 @@ + (void)startHttp1PlaintextServer { start_server(Envoy::TestServerType::HTTP1_WITHOUT_TLS); } ++ (void)startHttpProxyServer { + start_server(Envoy::TestServerType::HTTP_PROXY); +} + ++ (void)startHttpsProxyServer { + start_server(Envoy::TestServerType::HTTPS_PROXY); +} + + (void)shutdownTestServer { shutdown_server(); } diff --git a/mobile/test/swift/BUILD b/mobile/test/swift/BUILD index 83b7b81a1a75..090b92a24be2 100644 --- a/mobile/test/swift/BUILD +++ b/mobile/test/swift/BUILD @@ -21,7 +21,6 @@ envoy_mobile_swift_test( "RetryPolicyTests.swift", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_engine_objc_lib", diff --git a/mobile/test/swift/integration/BUILD b/mobile/test/swift/integration/BUILD index 4830157ab739..3fb55499ee22 100644 --- a/mobile/test/swift/integration/BUILD +++ b/mobile/test/swift/integration/BUILD @@ -5,13 +5,13 @@ licenses(["notice"]) # Apache 2 envoy_mobile_package() -# TODO(jpsim): Fix remote execution for all the tests in this file. - envoy_mobile_swift_test( name = "end_to_end_networking_test", srcs = [ "EndToEndNetworkingTest.swift", ], + # The test starts an Envoy server, which binds to a local socket. Binding to a local socket is + # not permitted on remote execution hosts, so we have to add the `no-remote-exec` tag. tags = [ "no-remote-exec", ], @@ -28,9 +28,6 @@ envoy_mobile_swift_test( srcs = [ "CancelStreamTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -43,9 +40,6 @@ envoy_mobile_swift_test( srcs = [ "EngineApiTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -58,9 +52,6 @@ envoy_mobile_swift_test( srcs = [ "FilterResetIdleTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -73,9 +64,6 @@ envoy_mobile_swift_test( srcs = [ "GRPCReceiveErrorTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -88,6 +76,8 @@ envoy_mobile_swift_test( srcs = [ "IdleTimeoutTest.swift", ], + # The test starts an Envoy server, which binds to a local socket. Binding to a local socket is + # not permitted on remote execution hosts, so we have to add the `no-remote-exec` tag. tags = [ "no-remote-exec", ], @@ -104,9 +94,6 @@ envoy_mobile_swift_test( srcs = [ "KeyValueStoreTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -119,9 +106,6 @@ envoy_mobile_swift_test( srcs = [ "ReceiveDataTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -134,9 +118,6 @@ envoy_mobile_swift_test( srcs = [ "ReceiveErrorTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -149,9 +130,6 @@ envoy_mobile_swift_test( srcs = [ "ResetConnectivityStateTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -164,9 +142,6 @@ envoy_mobile_swift_test( srcs = [ "SendDataTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -179,9 +154,6 @@ envoy_mobile_swift_test( srcs = [ "SendHeadersTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -194,9 +166,6 @@ envoy_mobile_swift_test( srcs = [ "SendTrailersTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -209,9 +178,6 @@ envoy_mobile_swift_test( srcs = [ "SetEventTrackerTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -224,9 +190,6 @@ envoy_mobile_swift_test( srcs = [ "SetEventTrackerTestNoTracker.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -239,9 +202,6 @@ envoy_mobile_swift_test( srcs = [ "SetLoggerTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -254,9 +214,6 @@ envoy_mobile_swift_test( srcs = [ "CancelGRPCStreamTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -270,6 +227,7 @@ objc_library( "TestExtensions.h", ], module_name = "TestExtensions", + visibility = ["//visibility:public"], deps = [ "@envoy_build_config//:test_extensions_no_autoregister", ], diff --git a/mobile/test/swift/integration/proxying/BUILD b/mobile/test/swift/integration/proxying/BUILD new file mode 100644 index 000000000000..d15f5a6f6526 --- /dev/null +++ b/mobile/test/swift/integration/proxying/BUILD @@ -0,0 +1,24 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_mobile_package") +load("@envoy_mobile//bazel:apple.bzl", "envoy_mobile_swift_test") + +licenses(["notice"]) # Apache 2 + +envoy_mobile_package() + +envoy_mobile_swift_test( + name = "http_request_using_proxy_test", + srcs = [ + "HTTPRequestUsingProxyTest.swift", + ], + # The test starts an Envoy test server, which requires the `no-remote-exec` tag. + tags = [ + "no-remote-exec", + ], + visibility = ["//visibility:public"], + deps = [ + "//library/objective-c:envoy_engine_objc_lib", + "//test/objective-c:envoy_test_api", + "//test/objective-c:envoy_test_server", + "//test/swift/integration:test_extensions", + ], +) diff --git a/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift new file mode 100644 index 000000000000..424159b9e6e4 --- /dev/null +++ b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift @@ -0,0 +1,220 @@ +import Envoy +import EnvoyEngine +import EnvoyTestApi +import EnvoyTestServer +import Foundation +import TestExtensions +import XCTest + +final class HTTPRequestUsingProxyTest: XCTestCase { + override static func setUp() { + super.setUp() + register_test_extensions() + } + + func testHTTPRequestUsingProxy() throws { + EnvoyTestServer.startHttpProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + let responseHeadersExpectation = + self.expectation(description: "Successful response headers received") + let responseTrailersExpectation = + self.expectation(description: "Successful response trailers received") + + let engine = EngineBuilder() + .addLogLevel(.trace) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: false) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "http", + authority: "neverssl.com", path: "/") + .build() + + var responseBuffer = Data() + engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders { responseHeaders, _, _ in + XCTAssertEqual(200, responseHeaders.httpStatus) + responseHeadersExpectation.fulfill() + } + .setOnResponseData { data, _, _ in + responseBuffer.append(contentsOf: data) + } + .setOnResponseTrailers { _, _ in + responseTrailersExpectation.fulfill() + } + .start() + .sendHeaders(requestHeaders, endStream: true) + + let expectations = [responseHeadersExpectation, responseTrailersExpectation] + XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 10), .completed) + + if let responseBody = String(data: responseBuffer, encoding: .utf8) { + XCTAssertGreaterThanOrEqual(responseBody.utf8.count, 3900) + } + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + func testHTTPSRequestUsingProxy() throws { + EnvoyTestServer.startHttpsProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + let responseHeadersExpectation = + self.expectation(description: "Successful response headers received") + let responseBodyExpectation = + self.expectation(description: "Successful response trailers received") + + let engine = EngineBuilder() + .addLogLevel(.debug) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: false) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "https", + authority: "cloud.google.com", path: "/") + .build() + + var responseBuffer = Data() + engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders { responseHeaders, _, _ in + XCTAssertEqual(200, responseHeaders.httpStatus) + responseHeadersExpectation.fulfill() + } + .setOnResponseData { data, endStream, _ in + responseBuffer.append(contentsOf: data) + if endStream { + responseBodyExpectation.fulfill() + } + } + .setOnResponseTrailers { _, _ in + } + .start() + .sendHeaders(requestHeaders, endStream: true) + + let expectations = [responseHeadersExpectation, responseBodyExpectation] + XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 10), .completed) + + if let responseBody = String(data: responseBuffer, encoding: .utf8) { + XCTAssertGreaterThanOrEqual(responseBody.utf8.count, 3900) + } + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + func testHTTPSRequestUsingPacFileUrlResolver() throws { + EnvoyTestServer.startHttpsProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + let responseHeadersExpectation = + self.expectation(description: "Successful response headers received") + let responseBodyExpectation = + self.expectation(description: "Successful response trailers received") + + let engine = EngineBuilder() + .addLogLevel(.debug) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: true) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "https", + authority: "cloud.google.com", path: "/") + .build() + + var responseBuffer = Data() + engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders { responseHeaders, _, _ in + XCTAssertEqual(200, responseHeaders.httpStatus) + responseHeadersExpectation.fulfill() + } + .setOnResponseData { data, endStream, _ in + responseBuffer.append(contentsOf: data) + if endStream { + responseBodyExpectation.fulfill() + } + } + .setOnResponseTrailers { _, _ in + } + .start() + .sendHeaders(requestHeaders, endStream: true) + + let expectations = [responseHeadersExpectation, responseBodyExpectation] + XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 10), .completed) + + if let responseBody = String(data: responseBuffer, encoding: .utf8) { + XCTAssertGreaterThanOrEqual(responseBody.utf8.count, 3900) + } + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + func testHTTPRequestUsingProxyCancelStream() throws { + EnvoyTestServer.startHttpProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + + let engine = EngineBuilder() + .addLogLevel(.trace) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: false) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "http", + authority: "neverssl.com", path: "/") + .build() + + let cancelExpectation = self.expectation(description: "Stream run with expected cancellation") + + engine.streamClient() + .newStreamPrototype() + .setOnCancel { _ in + // Handle stream cancellation, which is expected since we immediately + // cancel the stream after sending headers on it. + cancelExpectation.fulfill() + } + .start() + .sendHeaders(requestHeaders, endStream: true) + .cancel() + + XCTAssertEqual(XCTWaiter.wait(for: [cancelExpectation], timeout: 10), .completed) + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + // TODO(abeyad): Add test for proxy system settings updated. +} diff --git a/mobile/test/swift/stats/BUILD b/mobile/test/swift/stats/BUILD index dbc34bd9c78a..2d77e215c8a3 100644 --- a/mobile/test/swift/stats/BUILD +++ b/mobile/test/swift/stats/BUILD @@ -13,7 +13,6 @@ envoy_mobile_swift_test( "TagsBuilderTests.swift", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_engine_objc_lib", diff --git a/source/common/config/datasource.cc b/source/common/config/datasource.cc index 1935600c8a43..4c3765b3b7f8 100644 --- a/source/common/config/datasource.cc +++ b/source/common/config/datasource.cc @@ -15,28 +15,28 @@ static constexpr uint32_t RetryInitialDelayMilliseconds = 1000; static constexpr uint32_t RetryMaxDelayMilliseconds = 10 * 1000; static constexpr uint32_t RetryCount = 1; -std::string read(const envoy::config::core::v3::DataSource& source, bool allow_empty, Api::Api& api, - uint64_t max_size) { +absl::StatusOr read(const envoy::config::core::v3::DataSource& source, + bool allow_empty, Api::Api& api, uint64_t max_size) { std::string data; absl::StatusOr file_or_error; switch (source.specifier_case()) { case envoy::config::core::v3::DataSource::SpecifierCase::kFilename: if (max_size > 0) { if (!api.fileSystem().fileExists(source.filename())) { - throwEnvoyExceptionOrPanic(fmt::format("file {} does not exist", source.filename())); + return absl::InvalidArgumentError(fmt::format("file {} does not exist", source.filename())); } const ssize_t size = api.fileSystem().fileSize(source.filename()); if (size < 0) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( absl::StrCat("cannot determine size of file ", source.filename())); } if (static_cast(size) > max_size) { - throwEnvoyExceptionOrPanic(fmt::format("file {} size is {} bytes; maximum is {}", - source.filename(), size, max_size)); + return absl::InvalidArgumentError(fmt::format("file {} size is {} bytes; maximum is {}", + source.filename(), size, max_size)); } } file_or_error = api.fileSystem().fileReadToEnd(source.filename()); - THROW_IF_STATUS_NOT_OK(file_or_error, throw); + RETURN_IF_STATUS_NOT_OK(file_or_error); data = file_or_error.value(); break; case envoy::config::core::v3::DataSource::SpecifierCase::kInlineBytes: @@ -48,7 +48,7 @@ std::string read(const envoy::config::core::v3::DataSource& source, bool allow_e case envoy::config::core::v3::DataSource::SpecifierCase::kEnvironmentVariable: { const char* environment_variable = std::getenv(source.environment_variable().c_str()); if (environment_variable == nullptr) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("Environment variable doesn't exist: {}", source.environment_variable())); } data = environment_variable; @@ -56,12 +56,12 @@ std::string read(const envoy::config::core::v3::DataSource& source, bool allow_e } default: if (!allow_empty) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("Unexpected DataSource::specifier_case(): {}", source.specifier_case())); } } if (!allow_empty && data.empty()) { - throwEnvoyExceptionOrPanic("DataSource cannot be empty"); + return absl::InvalidArgumentError("DataSource cannot be empty"); } return data; } diff --git a/source/common/config/datasource.h b/source/common/config/datasource.h index 326c45a3d149..888794623eea 100644 --- a/source/common/config/datasource.h +++ b/source/common/config/datasource.h @@ -26,11 +26,11 @@ namespace DataSource { * @param api reference to the Api object * @param max_size max size limit of file to read, default 0 means no limit, and if the file data * would exceed the limit, it will throw a EnvoyException. - * @return std::string with DataSource contents. - * @throw EnvoyException if no DataSource case is specified and !allow_empty. + * @return std::string with DataSource contents. or an error status if no DataSource case is + * specified and !allow_empty. */ -std::string read(const envoy::config::core::v3::DataSource& source, bool allow_empty, Api::Api& api, - uint64_t max_size = 0); +absl::StatusOr read(const envoy::config::core::v3::DataSource& source, + bool allow_empty, Api::Api& api, uint64_t max_size = 0); /** * @param source data source. @@ -43,25 +43,6 @@ absl::optional getPath(const envoy::config::core::v3::DataSource& s */ using AsyncDataSourceCb = std::function; -class LocalAsyncDataProvider { -public: - LocalAsyncDataProvider(Init::Manager& manager, const envoy::config::core::v3::DataSource& source, - bool allow_empty, Api::Api& api, AsyncDataSourceCb&& callback) - : init_target_("LocalAsyncDataProvider", [this, &source, allow_empty, &api, callback]() { - callback(DataSource::read(source, allow_empty, api)); - init_target_.ready(); - }) { - manager.add(init_target_); - } - - ~LocalAsyncDataProvider() { init_target_.ready(); } - -private: - Init::TargetImpl init_target_; -}; - -using LocalAsyncDataProviderPtr = std::unique_ptr; - class RemoteAsyncDataProvider : public Event::DeferredDeletable, public Config::DataFetcher::RemoteDataFetcherCallback, public Logger::Loggable { diff --git a/source/common/config/utility.cc b/source/common/config/utility.cc index eff142670f25..7728019c228e 100644 --- a/source/common/config/utility.cc +++ b/source/common/config/utility.cc @@ -183,7 +183,7 @@ std::chrono::milliseconds Utility::configSourceInitialFetchTimeout( PROTOBUF_GET_MS_OR_DEFAULT(config_source, initial_fetch_timeout, 15000)); } -RateLimitSettings +absl::StatusOr Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source) { RateLimitSettings rate_limit_settings; if (api_config_source.has_rate_limit_settings()) { @@ -194,6 +194,12 @@ Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& rate_limit_settings.fill_rate_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), fill_rate, Envoy::Config::RateLimitSettings::DefaultFillRate); + // Reject the NaN and Inf values. + if (std::isnan(rate_limit_settings.fill_rate_) || std::isinf(rate_limit_settings.fill_rate_)) { + return absl::InvalidArgumentError( + fmt::format("The value of fill_rate in RateLimitSettings ({}) must not be NaN nor Inf", + rate_limit_settings.fill_rate_)); + } } return rate_limit_settings; } diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 5dfa959b1f5e..27971e06ac85 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -178,9 +178,10 @@ class Utility { * Parses RateLimit configuration from envoy::config::core::v3::ApiConfigSource to * RateLimitSettings. * @param api_config_source ApiConfigSource. - * @return RateLimitSettings. + * @return absl::StatusOr - returns an error when an + * invalid RateLimit config settings are provided. */ - static RateLimitSettings + static absl::StatusOr parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source); /** @@ -490,7 +491,6 @@ class Utility { const envoy::config::core::v3::ApiConfigSource& api_config_source, Random::RandomGenerator& random, const uint32_t default_base_interval_ms, absl::optional default_max_interval_ms) { - auto& grpc_services = api_config_source.grpc_services(); if (!grpc_services.empty() && grpc_services[0].has_envoy_grpc()) { return prepareJitteredExponentialBackOffStrategy( diff --git a/source/common/formatter/substitution_format_string.h b/source/common/formatter/substitution_format_string.h index b929fef5d7b3..3cdd032a9eee 100644 --- a/source/common/formatter/substitution_format_string.h +++ b/source/common/formatter/substitution_format_string.h @@ -58,8 +58,9 @@ class SubstitutionFormatStringUtils { commands); case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kTextFormatSource: return std::make_unique>( - Config::DataSource::read(config.text_format_source(), true, - context.serverFactoryContext().api()), + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.text_format_source(), true, + context.serverFactoryContext().api()), + std::string), config.omit_empty_values(), commands); case envoy::config::core::v3::SubstitutionFormatString::FormatCase::FORMAT_NOT_SET: PANIC_DUE_TO_PROTO_UNSET; diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index 73595f9ea0c1..f2875e30466e 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -45,12 +45,14 @@ bool validateGrpcCompatibleAsciiHeaderValue(absl::string_view h_value) { AsyncClientFactoryImpl::AsyncClientFactoryImpl(Upstream::ClusterManager& cm, const envoy::config::core::v3::GrpcService& config, - bool skip_cluster_check, TimeSource& time_source) + bool skip_cluster_check, TimeSource& time_source, + absl::Status& creation_status) : cm_(cm), config_(config), time_source_(time_source) { if (skip_cluster_check) { - return; + creation_status = absl::OkStatus(); + } else { + creation_status = cm_.checkActiveStaticCluster(config.envoy_grpc().cluster_name()); } - THROW_IF_NOT_OK(cm_.checkActiveStaticCluster(config.envoy_grpc().cluster_name())); } AsyncClientManagerImpl::AsyncClientManagerImpl( @@ -81,11 +83,11 @@ RawAsyncClientPtr AsyncClientFactoryImpl::createUncachedRawAsyncClient() { GoogleAsyncClientFactoryImpl::GoogleAsyncClientFactoryImpl( ThreadLocal::Instance& tls, ThreadLocal::Slot* google_tls_slot, Stats::Scope& scope, - const envoy::config::core::v3::GrpcService& config, Api::Api& api, const StatNames& stat_names) + const envoy::config::core::v3::GrpcService& config, Api::Api& api, const StatNames& stat_names, + absl::Status& creation_status) : tls_(tls), google_tls_slot_(google_tls_slot), scope_(scope.createScope(fmt::format("grpc.{}.", config.google_grpc().stat_prefix()))), config_(config), api_(api), stat_names_(stat_names) { - #ifndef ENVOY_GOOGLE_GRPC UNREFERENCED_PARAMETER(tls_); UNREFERENCED_PARAMETER(google_tls_slot_); @@ -93,26 +95,30 @@ GoogleAsyncClientFactoryImpl::GoogleAsyncClientFactoryImpl( UNREFERENCED_PARAMETER(config_); UNREFERENCED_PARAMETER(api_); UNREFERENCED_PARAMETER(stat_names_); - throwEnvoyExceptionOrPanic("Google C++ gRPC client is not linked"); + creation_status = absl::InvalidArgumentError("Google C++ gRPC client is not linked"); + return; #else ASSERT(google_tls_slot_ != nullptr); #endif + creation_status = absl::OkStatus(); // Check metadata for gRPC API compliance. Uppercase characters are lowered in the HeaderParser. // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md for (const auto& header : config.initial_metadata()) { // Validate key if (!validateGrpcHeaderChars(header.key())) { - throwEnvoyExceptionOrPanic( + creation_status = absl::InvalidArgumentError( fmt::format("Illegal characters in gRPC initial metadata header key: {}.", header.key())); + return; } // Validate value // Binary base64 encoded - handled by the GRPC library if (!::absl::EndsWith(header.key(), "-bin") && !validateGrpcCompatibleAsciiHeaderValue(header.value())) { - throwEnvoyExceptionOrPanic(fmt::format( + creation_status = absl::InvalidArgumentError(fmt::format( "Illegal ASCII value for gRPC initial metadata header key: {}.", header.key())); + return; } } } @@ -128,22 +134,30 @@ RawAsyncClientPtr GoogleAsyncClientFactoryImpl::createUncachedRawAsyncClient() { #endif } -AsyncClientFactoryPtr +absl::StatusOr AsyncClientManagerImpl::factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) { + absl::Status creation_status = absl::OkStatus(); + AsyncClientFactoryPtr factory; switch (config.target_specifier_case()) { case envoy::config::core::v3::GrpcService::TargetSpecifierCase::kEnvoyGrpc: - return std::make_unique(cm_, config, skip_cluster_check, time_source_); + factory = std::make_unique(cm_, config, skip_cluster_check, + time_source_, creation_status); + break; case envoy::config::core::v3::GrpcService::TargetSpecifierCase::kGoogleGrpc: - return std::make_unique(tls_, google_tls_slot_.get(), scope, - config, api_, stat_names_); + factory = std::make_unique( + tls_, google_tls_slot_.get(), scope, config, api_, stat_names_, creation_status); + break; case envoy::config::core::v3::GrpcService::TargetSpecifierCase::TARGET_SPECIFIER_NOT_SET: PANIC_DUE_TO_PROTO_UNSET; } - return nullptr; + if (!creation_status.ok()) { + return creation_status; + } + return factory; } -RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClient( +absl::StatusOr AsyncClientManagerImpl::getOrCreateRawAsyncClient( const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) { const GrpcServiceConfigWithHashKey config_with_hash_key = GrpcServiceConfigWithHashKey(config); @@ -151,21 +165,26 @@ RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClient( if (client != nullptr) { return client; } - client = factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check) - ->createUncachedRawAsyncClient(); + auto factory_or_error = + factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check); + RETURN_IF_STATUS_NOT_OK(factory_or_error); + client = factory_or_error.value()->createUncachedRawAsyncClient(); raw_async_client_cache_->setCache(config_with_hash_key, client); return client; } -RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClientWithHashKey( +absl::StatusOr +AsyncClientManagerImpl::getOrCreateRawAsyncClientWithHashKey( const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, bool skip_cluster_check) { RawAsyncClientSharedPtr client = raw_async_client_cache_->getCache(config_with_hash_key); if (client != nullptr) { return client; } - client = factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check) - ->createUncachedRawAsyncClient(); + auto factory_or_error = + factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check); + RETURN_IF_STATUS_NOT_OK(factory_or_error); + client = factory_or_error.value()->createUncachedRawAsyncClient(); raw_async_client_cache_->setCache(config_with_hash_key, client); return client; } diff --git a/source/common/grpc/async_client_manager_impl.h b/source/common/grpc/async_client_manager_impl.h index a01f02af41a8..54c3cf6b2b00 100644 --- a/source/common/grpc/async_client_manager_impl.h +++ b/source/common/grpc/async_client_manager_impl.h @@ -19,7 +19,8 @@ class AsyncClientFactoryImpl : public AsyncClientFactory { public: AsyncClientFactoryImpl(Upstream::ClusterManager& cm, const envoy::config::core::v3::GrpcService& config, - bool skip_cluster_check, TimeSource& time_source); + bool skip_cluster_check, TimeSource& time_source, + absl::Status& creation_status); RawAsyncClientPtr createUncachedRawAsyncClient() override; private: @@ -33,7 +34,7 @@ class GoogleAsyncClientFactoryImpl : public AsyncClientFactory { GoogleAsyncClientFactoryImpl(ThreadLocal::Instance& tls, ThreadLocal::Slot* google_tls_slot, Stats::Scope& scope, const envoy::config::core::v3::GrpcService& config, Api::Api& api, - const StatNames& stat_names); + const StatNames& stat_names, absl::Status& creation_status); RawAsyncClientPtr createUncachedRawAsyncClient() override; private: @@ -51,17 +52,17 @@ class AsyncClientManagerImpl : public AsyncClientManager { Upstream::ClusterManager& cm, ThreadLocal::Instance& tls, TimeSource& time_source, Api::Api& api, const StatNames& stat_names, const envoy::config::bootstrap::v3::Bootstrap::GrpcAsyncClientManagerConfig& config); - RawAsyncClientSharedPtr + absl::StatusOr getOrCreateRawAsyncClient(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) override; - RawAsyncClientSharedPtr + absl::StatusOr getOrCreateRawAsyncClientWithHashKey(const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, bool skip_cluster_check) override; - AsyncClientFactoryPtr factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, - Stats::Scope& scope, - bool skip_cluster_check) override; + absl::StatusOr + factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, + bool skip_cluster_check) override; class RawAsyncClientCache : public ThreadLocal::ThreadLocalObject { public: explicit RawAsyncClientCache(Event::Dispatcher& dispatcher, diff --git a/source/common/grpc/google_grpc_creds_impl.cc b/source/common/grpc/google_grpc_creds_impl.cc index ae49d3257a7f..d185db79d16d 100644 --- a/source/common/grpc/google_grpc_creds_impl.cc +++ b/source/common/grpc/google_grpc_creds_impl.cc @@ -16,9 +16,12 @@ std::shared_ptr CredsUtility::getChannelCredentials( CredentialSpecifierCase::kSslCredentials: { const auto& ssl_credentials = google_grpc.channel_credentials().ssl_credentials(); const grpc::SslCredentialsOptions ssl_credentials_options = { - Config::DataSource::read(ssl_credentials.root_certs(), true, api), - Config::DataSource::read(ssl_credentials.private_key(), true, api), - Config::DataSource::read(ssl_credentials.cert_chain(), true, api), + THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.root_certs(), true, api), + std::string), + THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.private_key(), true, api), + std::string), + THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.cert_chain(), true, api), + std::string), }; return grpc::SslCredentials(ssl_credentials_options); } diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 19a7e72adaba..b79c34431ab9 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -314,6 +314,7 @@ void AsyncOngoingRequestImpl::initialize() { } void AsyncRequestSharedImpl::onComplete() { + complete_ = true; callbacks_.onBeforeFinalizeUpstreamSpan(*child_span_, &response_->headers()); Tracing::HttpTracerUtility::finalizeUpstreamSpan(*child_span_, streamInfo(), @@ -335,6 +336,11 @@ void AsyncRequestSharedImpl::onTrailers(ResponseTrailerMapPtr&& trailers) { } void AsyncRequestSharedImpl::onReset() { + if (complete_) { + // This request has already been completed; a reset should be ignored. + return; + } + if (!cancelled_) { // Set "error reason" tag related to reset. The tagging for "error true" is done inside the // Tracing::HttpTracerUtility::finalizeUpstreamSpan. diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 645633adbdf2..7e6354bd931b 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -151,6 +151,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, absl::optional destructor_callback_; // Callback to listen for low/high/overflow watermark events. absl::optional> watermark_callbacks_; + bool complete_{}; private: void cleanup(); diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 47e7631c1309..b6725038eddd 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1735,7 +1735,7 @@ void ConnectionManagerImpl::ActiveStream::encode1xxHeaders(ResponseHeaderMap& re // Count both the 1xx and follow-up response code in stats. chargeStats(response_headers); - ENVOY_STREAM_LOG(debug, "encoding 100 continue headers via codec:\n{}", *this, response_headers); + ENVOY_STREAM_LOG(debug, "encoding 1xx continue headers via codec:\n{}", *this, response_headers); // Now actually encode via the codec. response_encoder_->encode1xxHeaders(response_headers); diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index b9fb637485db..08827b267127 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -92,9 +92,8 @@ ConnectionManagerUtility::MutateRequestHeadersResult ConnectionManagerUtility::m if (!Utility::isUpgrade(request_headers)) { request_headers.removeConnection(); request_headers.removeUpgrade(); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.sanitize_te")) { - request_headers.removeTE(); - } + + sanitizeTEHeader(request_headers); } // Clean proxy headers. @@ -292,6 +291,32 @@ ConnectionManagerUtility::MutateRequestHeadersResult ConnectionManagerUtility::m return {final_remote_address, absl::nullopt}; } +void ConnectionManagerUtility::sanitizeTEHeader(RequestHeaderMap& request_headers) { + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.sanitize_te")) { + return; + } + + absl::string_view te_header = request_headers.getTEValue(); + if (te_header.empty()) { + return; + } + + // If the TE header contains the "trailers" value, set the TE header to "trailers" only. + std::vector te_values = absl::StrSplit(te_header, ','); + for (const absl::string_view& te_value : te_values) { + bool is_trailers = + absl::StripAsciiWhitespace(te_value) == Http::Headers::get().TEValues.Trailers; + + if (is_trailers) { + request_headers.setTE(Http::Headers::get().TEValues.Trailers); + return; + } + } + + // If the TE header does not contain the "trailers" value, remove the TE header. + request_headers.removeTE(); +} + void ConnectionManagerUtility::cleanInternalHeaders( RequestHeaderMap& request_headers, bool edge_request, const std::list& internal_only_headers) { diff --git a/source/common/http/conn_manager_utility.h b/source/common/http/conn_manager_utility.h index a93d53706914..934b08132437 100644 --- a/source/common/http/conn_manager_utility.h +++ b/source/common/http/conn_manager_utility.h @@ -141,6 +141,7 @@ class ConnectionManagerUtility { static void mutateXfccRequestHeader(RequestHeaderMap& request_headers, Network::Connection& connection, ConnectionManagerConfig& config); + static void sanitizeTEHeader(RequestHeaderMap& request_headers); static void cleanInternalHeaders(RequestHeaderMap& request_headers, bool edge_request, const std::list& internal_only_headers); }; diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index ba51e7eee7e1..1ae84bc56da5 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -96,12 +96,17 @@ void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptFailed( // If this point is reached, all pools have been tried. Pass the pool failure up to the // original caller, if the caller hasn't already been notified. + signalFailureAndDeleteSelf(reason, transport_failure_reason, host); +} + +void ConnectivityGrid::WrapperCallbacks::signalFailureAndDeleteSelf( + ConnectionPool::PoolFailureReason reason, absl::string_view transport_failure_reason, + Upstream::HostDescriptionConstSharedPtr host) { ConnectionPool::Callbacks* callbacks = inner_callbacks_; inner_callbacks_ = nullptr; deleteThis(); if (callbacks != nullptr) { - ENVOY_LOG(trace, "Passing pool failure up to caller.", describePool(attempt->pool()), - host->hostname()); + ENVOY_LOG(trace, "Passing pool failure up to caller."); callbacks->onPoolFailure(reason, transport_failure_reason, host); } } @@ -191,6 +196,9 @@ void ConnectivityGrid::WrapperCallbacks::cancelAllPendingAttempts( absl::optional ConnectivityGrid::WrapperCallbacks::tryAnotherConnection() { + if (grid_.destroying_) { + return {}; + } absl::optional next_pool = grid_.nextPool(current_); if (!next_pool.has_value()) { // If there are no other pools to try, return an empty optional. @@ -236,9 +244,13 @@ ConnectivityGrid::ConnectivityGrid( ConnectivityGrid::~ConnectivityGrid() { // Ignore idle callbacks while the pools are destroyed below. destroying_ = true; - // Callbacks might have pending streams registered with the pools, so cancel and delete - // the callback before deleting the pools. - wrapped_callbacks_.clear(); + while (!wrapped_callbacks_.empty()) { + // Before tearing down the callbacks, make sure they pass up pool failure to + // the caller. We do not call onPoolFailure because it does up-calls to the + // (delete-in-process) grid. + wrapped_callbacks_.front()->signalFailureAndDeleteSelf( + ConnectionPool::PoolFailureReason::LocalConnectionFailure, "grid teardown", host_); + } pools_.clear(); } @@ -344,11 +356,6 @@ void ConnectivityGrid::addIdleCallback(IdleCb cb) { } void ConnectivityGrid::drainConnections(Envoy::ConnectionPool::DrainBehavior drain_behavior) { - if (draining_) { - // A drain callback has already been set, and only needs to happen once. - return; - } - if (drain_behavior == Envoy::ConnectionPool::DrainBehavior::DrainAndDelete) { // Note that no new pools can be created from this point on // as createNextPool fast-fails if `draining_` is true. diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index b5b9bb059662..cfc15dc6af72 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -96,6 +96,12 @@ class ConnectivityGrid : public ConnectionPool::Instance, StreamInfo::StreamInfo& info, absl::optional protocol); + // Called by onConnectionAttemptFailed and on grid deletion destruction to let wrapper + // callback subscribers know the connect attempt failed. + void signalFailureAndDeleteSelf(ConnectionPool::PoolFailureReason reason, + absl::string_view transport_failure_reason, + Upstream::HostDescriptionConstSharedPtr host); + private: // Removes this from the owning list, deleting it. void deleteThis(); diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index bfeb546128c9..b744fe6c5c61 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -532,7 +532,6 @@ void ConnectionImpl::ClientStreamImpl::decodeHeaders() { !CodeUtility::is1xx(status) || status == enumToInt(Http::Code::SwitchingProtocols); if (HeaderUtility::isSpecial1xx(*headers)) { - ASSERT(!remote_end_stream_); response_decoder_.decode1xxHeaders(std::move(headers)); } else { response_decoder_.decodeHeaders(std::move(headers), sendEndStream()); @@ -1107,6 +1106,9 @@ enum GoAwayErrorCode ngHttp2ErrorCodeToErrorCode(uint32_t code) noexcept { } Status ConnectionImpl::onPing(uint64_t opaque_data, bool is_ack) { + ENVOY_CONN_LOG(trace, "recv frame type=PING", connection_); + ASSERT(connection_.state() == Network::Connection::State::Open); + if (is_ack) { ENVOY_CONN_LOG(trace, "recv PING ACK {}", connection_, opaque_data); @@ -1115,9 +1117,10 @@ Status ConnectionImpl::onPing(uint64_t opaque_data, bool is_ack) { return okStatus(); } -Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t type, uint8_t flags, +Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t flags, size_t padding) { - RETURN_IF_ERROR(trackInboundFrames(stream_id, length, type, flags, padding)); + ENVOY_CONN_LOG(trace, "recv frame type=DATA stream_id={}", connection_, stream_id); + RETURN_IF_ERROR(trackInboundFrames(stream_id, length, NGHTTP2_DATA, flags, padding)); StreamImpl* stream = getStreamUnchecked(stream_id); if (!stream) { @@ -1133,6 +1136,7 @@ Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t typ } Status ConnectionImpl::onGoAway(uint32_t error_code) { + ENVOY_CONN_LOG(trace, "recv frame type=GOAWAY", connection_); // Only raise GOAWAY once, since we don't currently expose stream information. Shutdown // notifications are the same as a normal GOAWAY. // TODO: handle multiple GOAWAY frames. @@ -1191,11 +1195,12 @@ Status ConnectionImpl::onHeaders(int32_t stream_id, size_t length, uint8_t flags } Status ConnectionImpl::onRstStream(int32_t stream_id, uint32_t error_code) { - ENVOY_CONN_LOG(trace, "remote reset: {} {}", connection_, stream_id, error_code); + ENVOY_CONN_LOG(trace, "recv frame type=RST_STREAM stream_id={}", connection_, stream_id); StreamImpl* stream = getStreamUnchecked(stream_id); if (!stream) { return okStatus(); } + ENVOY_CONN_LOG(trace, "remote reset: {} {}", connection_, stream_id, error_code); // Track bytes received. stream->bytes_meter_->addWireBytesReceived(/*frame_length=*/4 + H2_FRAME_HEADER_SIZE); stream->remote_rst_ = true; @@ -1221,8 +1226,7 @@ Status ConnectionImpl::onFrameReceived(const nghttp2_frame* frame) { return onPing(data, frame->ping.hd.flags & NGHTTP2_FLAG_ACK); } if (frame->hd.type == NGHTTP2_DATA) { - return onBeginData(frame->hd.stream_id, frame->hd.length, frame->hd.type, frame->hd.flags, - frame->data.padlen); + return onBeginData(frame->hd.stream_id, frame->hd.length, frame->hd.flags, frame->data.padlen); } if (frame->hd.type == NGHTTP2_GOAWAY) { ASSERT(frame->hd.stream_id == 0); diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 5e7572970378..50e9411eaad7 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -708,7 +708,7 @@ class ConnectionImpl : public virtual Connection, int onData(int32_t stream_id, const uint8_t* data, size_t len); Status onBeforeFrameReceived(int32_t stream_id, size_t length, uint8_t type, uint8_t flags); Status onPing(uint64_t opaque_data, bool is_ack); - Status onBeginData(int32_t stream_id, size_t length, uint8_t type, uint8_t flags, size_t padding); + Status onBeginData(int32_t stream_id, size_t length, uint8_t flags, size_t padding); Status onGoAway(uint32_t error_code); Status onHeaders(int32_t stream_id, size_t length, uint8_t flags); Status onRstStream(int32_t stream_id, uint32_t error_code); diff --git a/source/common/local_reply/local_reply.cc b/source/common/local_reply/local_reply.cc index 1f745e69a8d1..ae1efcc6385b 100644 --- a/source/common/local_reply/local_reply.cc +++ b/source/common/local_reply/local_reply.cc @@ -61,7 +61,9 @@ class ResponseMapper { status_code_ = static_cast(config.status_code().value()); } if (config.has_body()) { - body_ = Config::DataSource::read(config.body(), true, context.serverFactoryContext().api()); + body_ = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.body(), true, context.serverFactoryContext().api()), + std::string); } if (config.has_body_format_override()) { diff --git a/source/common/memory/BUILD b/source/common/memory/BUILD index e6e528c96000..efd001d8d3da 100644 --- a/source/common/memory/BUILD +++ b/source/common/memory/BUILD @@ -14,7 +14,12 @@ envoy_cc_library( hdrs = ["stats.h"], tcmalloc_dep = 1, deps = [ + "//envoy/stats:stats_macros", + "//source/common/common:assert_lib", "//source/common/common:logger_lib", + "//source/common/common:thread_lib", + "//source/common/protobuf:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], ) diff --git a/source/common/memory/stats.cc b/source/common/memory/stats.cc index 3c3abb00aae4..c306210b727c 100644 --- a/source/common/memory/stats.cc +++ b/source/common/memory/stats.cc @@ -2,121 +2,155 @@ #include +#include "source/common/common/assert.h" #include "source/common/common/logger.h" #if defined(TCMALLOC) - #include "tcmalloc/malloc_extension.h" +#elif defined(GPERFTOOLS_TCMALLOC) +#include "gperftools/malloc_extension.h" +#endif namespace Envoy { namespace Memory { uint64_t Stats::totalCurrentlyAllocated() { +#if defined(TCMALLOC) return tcmalloc::MallocExtension::GetNumericProperty("generic.current_allocated_bytes") .value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) + size_t value = 0; + MallocExtension::instance()->GetNumericProperty("generic.current_allocated_bytes", &value); + return value; +#else + return 0; +#endif } uint64_t Stats::totalCurrentlyReserved() { +#if defined(TCMALLOC) // In Google's tcmalloc the semantics of generic.heap_size has // changed: it doesn't include unmapped bytes. return tcmalloc::MallocExtension::GetNumericProperty("generic.heap_size").value_or(0) + tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") .value_or(0); -} - -uint64_t Stats::totalThreadCacheBytes() { - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.current_total_thread_cache_bytes") - .value_or(0); -} - -uint64_t Stats::totalPageHeapFree() { - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_free_bytes").value_or(0); -} - -uint64_t Stats::totalPageHeapUnmapped() { - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") - .value_or(0); -} - -uint64_t Stats::totalPhysicalBytes() { - return tcmalloc::MallocExtension::GetProperties()["generic.physical_memory_used"].value; -} - -void Stats::dumpStatsToLog() { - ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", tcmalloc::MallocExtension::GetStats()); -} - -} // namespace Memory -} // namespace Envoy - #elif defined(GPERFTOOLS_TCMALLOC) - -#include "gperftools/malloc_extension.h" - -namespace Envoy { -namespace Memory { - -uint64_t Stats::totalCurrentlyAllocated() { - size_t value = 0; - MallocExtension::instance()->GetNumericProperty("generic.current_allocated_bytes", &value); - return value; -} - -uint64_t Stats::totalCurrentlyReserved() { size_t value = 0; MallocExtension::instance()->GetNumericProperty("generic.heap_size", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalThreadCacheBytes() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.current_total_thread_cache_bytes") + .value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.current_total_thread_cache_bytes", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalPageHeapFree() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_free_bytes").value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_free_bytes", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalPageHeapUnmapped() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") + .value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_unmapped_bytes", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalPhysicalBytes() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetProperties()["generic.physical_memory_used"].value; +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("generic.total_physical_bytes", &value); return value; +#else + return 0; +#endif } void Stats::dumpStatsToLog() { +#if defined(TCMALLOC) + ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", tcmalloc::MallocExtension::GetStats()); +#elif defined(GPERFTOOLS_TCMALLOC) constexpr int buffer_size = 100000; auto buffer = std::make_unique(buffer_size); MallocExtension::instance()->GetStats(buffer.get(), buffer_size); ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", buffer.get()); +#else + return; +#endif } -} // namespace Memory -} // namespace Envoy - +void AllocatorManager::tcmallocRelease() { +#if defined(TCMALLOC) + tcmalloc::MallocExtension::ReleaseMemoryToSystem(bytes_to_release_); +#elif defined(GPERFTOOLS_TCMALLOC) + MallocExtension::instance()->ReleaseToSystem(bytes_to_release_); #else + return; +#endif +} -namespace Envoy { -namespace Memory { - -uint64_t Stats::totalCurrentlyAllocated() { return 0; } -uint64_t Stats::totalThreadCacheBytes() { return 0; } -uint64_t Stats::totalCurrentlyReserved() { return 0; } -uint64_t Stats::totalPageHeapUnmapped() { return 0; } -uint64_t Stats::totalPageHeapFree() { return 0; } -uint64_t Stats::totalPhysicalBytes() { return 0; } -void Stats::dumpStatsToLog() {} +/** + * Configures tcmalloc release rate from the page heap. If `bytes_to_release_` + * has been initialized to `0`, no heap memory will be released in background. + */ +void AllocatorManager::configureBackgroundMemoryRelease(Api::Api& api) { + RELEASE_ASSERT(!tcmalloc_thread_, "Invalid state, tcmalloc has already been initialised"); + if (bytes_to_release_ > 0) { + tcmalloc_routine_dispatcher_ = api.allocateDispatcher(std::string(TCMALLOC_ROUTINE_THREAD_ID)); + memory_release_timer_ = tcmalloc_routine_dispatcher_->createTimer([this]() -> void { + const uint64_t unmapped_bytes_before_release = Stats::totalPageHeapUnmapped(); + tcmallocRelease(); + const uint64_t unmapped_bytes_after_release = Stats::totalPageHeapUnmapped(); + if (unmapped_bytes_after_release > unmapped_bytes_before_release) { + // Only increment stats if memory was actually released. As tcmalloc releases memory on a + // span granularity, during some release rounds there may be no memory released, if during + // past round too much memory was released. + // https://github.com/google/tcmalloc/blob/master/tcmalloc/tcmalloc.cc#L298 + allocator_manager_stats_.released_by_timer_.inc(); + } + memory_release_timer_->enableTimer(memory_release_interval_msec_); + }); + tcmalloc_thread_ = api.threadFactory().createThread( + [this]() -> void { + ENVOY_LOG_MISC(debug, "Started {}", TCMALLOC_ROUTINE_THREAD_ID); + memory_release_timer_->enableTimer(memory_release_interval_msec_); + tcmalloc_routine_dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + }, + Thread::Options{std::string(TCMALLOC_ROUTINE_THREAD_ID)}); + ENVOY_LOG_MISC( + info, fmt::format( + "Configured tcmalloc with background release rate: {} bytes per {} milliseconds", + bytes_to_release_, memory_release_interval_msec_.count())); + } +} } // namespace Memory } // namespace Envoy - -#endif // #if defined(TCMALLOC) diff --git a/source/common/memory/stats.h b/source/common/memory/stats.h index 71bc261ae175..03c0b0a0dba0 100644 --- a/source/common/memory/stats.h +++ b/source/common/memory/stats.h @@ -2,9 +2,24 @@ #include +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/stats/store.h" + +#include "source/common/common/thread.h" +#include "source/common/protobuf/utility.h" + namespace Envoy { + +#define MEMORY_ALLOCATOR_MANAGER_STATS(COUNTER) COUNTER(released_by_timer) + +struct MemoryAllocatorManagerStats { + MEMORY_ALLOCATOR_MANAGER_STATS(GENERATE_COUNTER_STRUCT) +}; + namespace Memory { +constexpr absl::string_view TCMALLOC_ROUTINE_THREAD_ID = "TcmallocProcessBackgroundActions"; + /** * Runtime stats for process memory usage. */ @@ -51,5 +66,40 @@ class Stats { static void dumpStatsToLog(); }; +class AllocatorManager { +public: + AllocatorManager(Api::Api& api, Envoy::Stats::Scope& scope, + const envoy::config::bootstrap::v3::MemoryAllocatorManager& config) + : bytes_to_release_(config.bytes_to_release()), + memory_release_interval_msec_(std::chrono::milliseconds( + PROTOBUF_GET_MS_OR_DEFAULT(config, memory_release_interval, 1000))), + allocator_manager_stats_(MemoryAllocatorManagerStats{ + MEMORY_ALLOCATOR_MANAGER_STATS(POOL_COUNTER_PREFIX(scope, "tcmalloc."))}) { + configureBackgroundMemoryRelease(api); + }; + + ~AllocatorManager() { + if (tcmalloc_routine_dispatcher_) { + tcmalloc_routine_dispatcher_->exit(); + } + if (tcmalloc_thread_) { + tcmalloc_thread_->join(); + tcmalloc_thread_.reset(); + } + } + +private: + const uint64_t bytes_to_release_; + const std::chrono::milliseconds memory_release_interval_msec_; + MemoryAllocatorManagerStats allocator_manager_stats_; + Thread::ThreadPtr tcmalloc_thread_; + Event::DispatcherPtr tcmalloc_routine_dispatcher_; + Event::TimerPtr memory_release_timer_; + void configureBackgroundMemoryRelease(Api::Api& api); + void tcmallocRelease(); + // Used for testing. + friend class AllocatorManagerPeer; +}; + } // namespace Memory } // namespace Envoy diff --git a/source/common/network/tcp_listener_impl.cc b/source/common/network/tcp_listener_impl.cc index 29b100928521..5df320f7938d 100644 --- a/source/common/network/tcp_listener_impl.cc +++ b/source/common/network/tcp_listener_impl.cc @@ -31,7 +31,7 @@ bool TcpListenerImpl::rejectCxOverGlobalLimit() const { if (runtime_.threadsafeSnapshot()->get(Runtime::Keys::GlobalMaxCxRuntimeKey)) { ENVOY_LOG_ONCE_MISC( warn, - "Global downstream connections limits is configured via runtime key {} and in " + "Global downstream connections limits is configured via deprecated runtime key {} and in " "{}. Using overload manager config.", Runtime::Keys::GlobalMaxCxRuntimeKey, Server::OverloadProactiveResources::get().GlobalDownstreamMaxConnections); diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index c4511a02ee53..9f9043650ac0 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -427,6 +427,7 @@ envoy_cc_library( "//source/common/network:address_lib", "//source/common/network:connection_socket_lib", "//source/common/network:socket_option_factory_lib", + "//source/common/protobuf:utility_lib", "//source/common/quic:quic_io_handle_wrapper_lib", "@com_github_google_quiche//:quic_core_config_lib", "@com_github_google_quiche//:quic_core_http_header_list_lib", diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index b036737992b9..0a90b562b04a 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -8,6 +8,7 @@ #include "source/common/http/utility.h" #include "source/common/network/socket_option_factory.h" #include "source/common/network/utility.h" +#include "source/common/protobuf/utility.h" namespace Envoy { namespace Quic { @@ -253,6 +254,10 @@ void convertQuicConfig(const envoy::config::core::v3::QuicProtocolOptions& confi quic_config.SetConnectionOptionsToSend(quic::ParseQuicTagVector(config.connection_options())); quic_config.SetClientConnectionOptions( quic::ParseQuicTagVector(config.client_connection_options())); + if (config.has_idle_network_timeout()) { + quic_config.SetIdleNetworkTimeout(quic::QuicTimeDelta::FromSeconds( + DurationUtil::durationToSeconds(config.idle_network_timeout()))); + } } void configQuicInitialFlowControlWindow(const envoy::config::core::v3::QuicProtocolOptions& config, diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index e9d19e841830..aeeb9bb0143a 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -38,6 +38,10 @@ template <> constexpr bool maybeOverride(absl::string_view name, bool val) // Do not include 32-byte per-entry overhead while counting header size. return false; } + if (name == "quic_always_support_server_preferred_address") { + // Envoy should send server preferred address without a client option by default. + return true; + } return val; } diff --git a/source/common/router/config_utility.cc b/source/common/router/config_utility.cc index f70bcc217a8f..b261f4a353a0 100644 --- a/source/common/router/config_utility.cc +++ b/source/common/router/config_utility.cc @@ -114,8 +114,9 @@ ConfigUtility::parseDirectResponseBody(const envoy::config::route::v3::Route& ro } const auto& body = route.direct_response().body(); - const std::string string_body = - Envoy::Config::DataSource::read(body, true, api, max_body_size_bytes); + auto body_or_error = Envoy::Config::DataSource::read(body, true, api, max_body_size_bytes); + RETURN_IF_STATUS_NOT_OK(body_or_error); + const std::string& string_body = body_or_error.value(); if (string_body.length() > max_body_size_bytes) { return absl::InvalidArgumentError(fmt::format("response body size is {} bytes; maximum is {}", string_body.length(), max_body_size_bytes)); diff --git a/source/common/router/router.cc b/source/common/router/router.cc index c4172d2d0906..a41e1c3374dc 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -873,10 +873,6 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea } } - // If we aren't buffering and there is no active request, an abort should have occurred - // already. - ASSERT(buffering || !upstream_requests_.empty()); - for (auto* shadow_stream : shadow_streams_) { if (end_stream) { shadow_stream->removeDestructorCallback(); @@ -902,7 +898,23 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea // this stack for whether `data` is the same buffer as already buffered data. callbacks_->addDecodedData(data, true); } else { - upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request")) { + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + } else { + if (!upstream_requests_.empty()) { + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + } else { + // not buffering any data for retry, shadow, and internal redirect, and there will be + // no more upstream request, abort the request and clean up. + cleanup(); + callbacks_->sendLocalReply( + Http::Code::ServiceUnavailable, + "upstream is closed prematurely during decoding data from downstream", modify_headers_, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().EarlyUpstreamReset); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + } } if (end_stream) { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index d5b3e34306d0..dc560deed2c8 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -42,6 +42,7 @@ RUNTIME_GUARD(envoy_reloadable_features_dns_cache_set_first_resolve_complete); RUNTIME_GUARD(envoy_reloadable_features_edf_lb_locality_scheduler_init_fix); RUNTIME_GUARD(envoy_reloadable_features_enable_compression_bomb_protection); RUNTIME_GUARD(envoy_reloadable_features_enable_connect_udp_support); +RUNTIME_GUARD(envoy_reloadable_features_enable_include_histograms); RUNTIME_GUARD(envoy_reloadable_features_enable_intermediate_ca); RUNTIME_GUARD(envoy_reloadable_features_enable_zone_routing_different_zone_counts); RUNTIME_GUARD(envoy_reloadable_features_exclude_host_in_eds_status_draining); @@ -76,12 +77,17 @@ RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); RUNTIME_GUARD(envoy_reloadable_features_proxy_status_upstream_request_timeout); RUNTIME_GUARD(envoy_reloadable_features_quic_fix_filter_manager_uaf); +// Ignore the automated "remove this flag" issue: we should keep this for 1 year. Confirm with +// @danzh2010 or @RyanTheOptimist before removing. +RUNTIME_GUARD(envoy_reloadable_features_quic_send_server_preferred_address_to_all_clients); RUNTIME_GUARD(envoy_reloadable_features_sanitize_te); RUNTIME_GUARD(envoy_reloadable_features_send_header_raw_value); +RUNTIME_GUARD(envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request); RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests); RUNTIME_GUARD(envoy_reloadable_features_ssl_transport_failure_reason_format); RUNTIME_GUARD(envoy_reloadable_features_stateful_session_encode_ttl_in_cookie); RUNTIME_GUARD(envoy_reloadable_features_stop_decode_metadata_on_local_reply); +RUNTIME_GUARD(envoy_reloadable_features_tcp_tunneling_send_downstream_fin_on_upstream_trailers); RUNTIME_GUARD(envoy_reloadable_features_test_feature_true); RUNTIME_GUARD(envoy_reloadable_features_thrift_allow_negative_field_ids); RUNTIME_GUARD(envoy_reloadable_features_thrift_connection_draining); @@ -114,8 +120,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_runtime_initialized); // TODO(mattklein123): Also unit test this if this sticks and this becomes the default for Apple & // Android. FALSE_RUNTIME_GUARD(envoy_reloadable_features_always_use_v6); -// TODO(pradeepcrao) reset this to true after 2 releases (1.27) -FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_include_histograms); // TODO(wbpcode) complete remove this feature is no one use it. FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index 5e5ca22fe27e..c40bcd8a463d 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -30,6 +30,7 @@ #ifdef ENVOY_ENABLE_QUIC #include "quiche_platform_impl/quiche_flags_impl.h" +#include "quiche/common/platform/api/quiche_flags.h" #endif namespace Envoy { @@ -59,6 +60,13 @@ void refreshReloadableFlags(const Snapshot::EntryMap& flag_map) { } #ifdef ENVOY_ENABLE_QUIC quiche::FlagRegistry::getInstance().updateReloadableFlags(quiche_flags_override); + + // Because this is a QUICHE protocol flag, this behavior can't be flipped with the above + // code, so it needs its own runtime flag and code to set it. + SetQuicheFlag(quic_always_support_server_preferred_address, + Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.quic_send_server_preferred_address_to_all_clients")); + #endif // Make sure ints are parsed after the flag allowing deprecated ints is parsed. for (const auto& it : flag_map) { diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index b1249969f8cf..4f030296efcd 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -20,10 +20,15 @@ static const std::string INLINE_STRING = ""; CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl( const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config, Api::Api& api) - : ca_cert_(Config::DataSource::read(config.trusted_ca(), true, api)), + : ca_cert_(THROW_OR_RETURN_VALUE( + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.trusted_ca(), true, api), + std::string), + std::string)), ca_cert_path_(Config::DataSource::getPath(config.trusted_ca()) .value_or(ca_cert_.empty() ? EMPTY_STRING : INLINE_STRING)), - certificate_revocation_list_(Config::DataSource::read(config.crl(), true, api)), + certificate_revocation_list_(THROW_OR_RETURN_VALUE( + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.crl(), true, api), std::string), + std::string)), certificate_revocation_list_path_( Config::DataSource::getPath(config.crl()) .value_or(certificate_revocation_list_.empty() ? EMPTY_STRING : INLINE_STRING)), diff --git a/source/common/ssl/tls_certificate_config_impl.cc b/source/common/ssl/tls_certificate_config_impl.cc index 0b131494b5da..bf5855797f75 100644 --- a/source/common/ssl/tls_certificate_config_impl.cc +++ b/source/common/ssl/tls_certificate_config_impl.cc @@ -14,7 +14,8 @@ namespace Ssl { namespace { std::vector readOcspStaple(const envoy::config::core::v3::DataSource& source, Api::Api& api) { - std::string staple = Config::DataSource::read(source, true, api); + std::string staple = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, true, api), std::string); if (source.specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kInlineString) { throwEnvoyExceptionOrPanic("OCSP staple cannot be provided via inline_string"); @@ -29,17 +30,21 @@ static const std::string INLINE_STRING = ""; TlsCertificateConfigImpl::TlsCertificateConfigImpl( const envoy::extensions::transport_sockets::tls::v3::TlsCertificate& config, Server::Configuration::TransportSocketFactoryContext& factory_context, Api::Api& api) - : certificate_chain_(Config::DataSource::read(config.certificate_chain(), true, api)), + : certificate_chain_(THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.certificate_chain(), true, api), std::string)), certificate_chain_path_( Config::DataSource::getPath(config.certificate_chain()) .value_or(certificate_chain_.empty() ? EMPTY_STRING : INLINE_STRING)), - private_key_(Config::DataSource::read(config.private_key(), true, api)), + private_key_(THROW_OR_RETURN_VALUE(Config::DataSource::read(config.private_key(), true, api), + std::string)), private_key_path_(Config::DataSource::getPath(config.private_key()) .value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)), - pkcs12_(Config::DataSource::read(config.pkcs12(), true, api)), + pkcs12_( + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.pkcs12(), true, api), std::string)), pkcs12_path_(Config::DataSource::getPath(config.pkcs12()) .value_or(pkcs12_.empty() ? EMPTY_STRING : INLINE_STRING)), - password_(Config::DataSource::read(config.password(), true, api)), + password_(THROW_OR_RETURN_VALUE(Config::DataSource::read(config.password(), true, api), + std::string)), password_path_(Config::DataSource::getPath(config.password()) .value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)), ocsp_staple_(readOcspStaple(config.ocsp_staple(), api)), diff --git a/source/common/tcp/async_tcp_client_impl.cc b/source/common/tcp/async_tcp_client_impl.cc index 2a8ac582174b..6bcfa9a21500 100644 --- a/source/common/tcp/async_tcp_client_impl.cc +++ b/source/common/tcp/async_tcp_client_impl.cc @@ -23,7 +23,6 @@ AsyncTcpClientImpl::AsyncTcpClientImpl(Event::Dispatcher& dispatcher, cluster_info_(thread_local_cluster_.info()), context_(context), connect_timer_(dispatcher.createTimer([this]() { onConnectTimeout(); })), enable_half_close_(enable_half_close) { - connect_timer_->enableTimer(cluster_info_->connectTimeout()); cluster_info_->trafficStats()->upstream_cx_active_.inc(); cluster_info_->trafficStats()->upstream_cx_total_.inc(); } @@ -43,6 +42,10 @@ bool AsyncTcpClientImpl::connect() { conn_connect_ms_ = std::make_unique( cluster_info_->trafficStats()->upstream_cx_connect_ms_, dispatcher_.timeSource()); + if (!connect_timer_) { + connect_timer_ = dispatcher_.createTimer([this]() { onConnectTimeout(); }); + } + connect_timer_->enableTimer(cluster_info_->connectTimeout()); connection_->setConnectionStats({cluster_info_->trafficStats()->upstream_cx_rx_bytes_total_, cluster_info_->trafficStats()->upstream_cx_rx_bytes_buffered_, diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index d115bc440cc2..d16126547cc5 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -8,6 +8,7 @@ #include "envoy/upstream/thread_local_cluster.h" #include "envoy/upstream/upstream.h" +#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/router/header_parser.h" @@ -196,6 +197,12 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { 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 {} diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc index dc2ef0129428..1f74bb653da8 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc @@ -55,10 +55,12 @@ GrpcAccessLoggerImpl::SharedPtr GrpcAccessLoggerCacheImpl::createLogger( // exceptions in worker threads. Call sites of this getOrCreateLogger must check the cluster // availability via ClusterManager::checkActiveStaticCluster beforehand, and throw exceptions in // the main thread if necessary. - auto client = async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true) - ->createUncachedRawAsyncClient(); - return std::make_shared(std::move(client), config, dispatcher, local_info_, - scope_); + auto factory_or_error = + async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + return std::make_shared( + factory_or_error.value()->createUncachedRawAsyncClient(), config, dispatcher, local_info_, + scope_); } } // namespace GrpcCommon diff --git a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc index 9c4a25d58367..6901c2c344d6 100644 --- a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc @@ -88,10 +88,11 @@ GrpcAccessLoggerImpl::SharedPtr GrpcAccessLoggerCacheImpl::createLogger( // We pass skip_cluster_check=true to factoryForGrpcService in order to avoid throwing // exceptions in worker threads. Call sites of this getOrCreateLogger must check the cluster // availability via ClusterManager::checkActiveStaticCluster beforehand, and throw exceptions in - // the main thread if necessary. - auto client = async_client_manager_ - .factoryForGrpcService(config.common_config().grpc_service(), scope_, true) - ->createUncachedRawAsyncClient(); + // the main thread if necessary to ensure it does not throw here. + auto factory_or_error = async_client_manager_.factoryForGrpcService( + config.common_config().grpc_service(), scope_, true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + auto client = factory_or_error.value()->createUncachedRawAsyncClient(); return std::make_shared(std::move(client), config, dispatcher, local_info_, scope_); } diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index c1c9bfd2868e..2b13ca94937d 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -995,8 +995,12 @@ WasmResult Context::grpcCall(std::string_view grpc_service, std::string_view ser auto& handler = grpc_call_request_[token]; handler.context_ = this; handler.token_ = token; - auto grpc_client = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + auto client_or_error = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( service_proto, *wasm()->scope_, true /* skip_cluster_check */); + if (!client_or_error.status().ok()) { + return WasmResult::BadArgument; + } + auto grpc_client = client_or_error.value(); grpc_initial_metadata_ = buildRequestHeaderMapFromPairs(initial_metadata); // set default hash policy to be based on :authority to enable consistent hash @@ -1040,8 +1044,12 @@ WasmResult Context::grpcStream(std::string_view grpc_service, std::string_view s auto& handler = grpc_stream_[token]; handler.context_ = this; handler.token_ = token; - auto grpc_client = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + auto client_or_error = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( service_proto, *wasm()->scope_, true /* skip_cluster_check */); + if (!client_or_error.status().ok()) { + return WasmResult::BadArgument; + } + auto grpc_client = client_or_error.value(); grpc_initial_metadata_ = buildRequestHeaderMapFromPairs(initial_metadata); // set default hash policy to be based on :authority to enable consistent hash diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index c8e969f0793b..d2de6db4d3c8 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -374,7 +374,8 @@ bool createWasm(const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scop stats_handler.onEvent(WasmEvent::RemoteLoadCacheMiss); } } else if (vm_config.code().has_local()) { - code = Config::DataSource::read(vm_config.code().local(), true, api); + code = THROW_OR_RETURN_VALUE(Config::DataSource::read(vm_config.code().local(), true, api), + std::string); source = Config::DataSource::getPath(vm_config.code().local()) .value_or(code.empty() ? EMPTY_STRING : INLINE_STRING); } diff --git a/source/extensions/compression/zstd/common/dictionary_manager.h b/source/extensions/compression/zstd/common/dictionary_manager.h index c70f2aba25b0..45501345e5ec 100644 --- a/source/extensions/compression/zstd/common/dictionary_manager.h +++ b/source/extensions/compression/zstd/common/dictionary_manager.h @@ -33,7 +33,8 @@ template class dictionary_map->reserve(dictionaries.size()); for (const auto& source : dictionaries) { - const auto data = Config::DataSource::read(source, false, api); + const auto data = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, false, api), std::string); auto dictionary = DictionarySharedPtr(builder_(data.data(), data.length())); auto id = getDictId(dictionary.get()); // If id == 0, the dictionary is not conform to Zstd specification, or empty. diff --git a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc index ca0266efa642..a356e7a8f6c3 100644 --- a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc @@ -26,12 +26,15 @@ SubscriptionPtr DeltaGrpcCollectionConfigSubscriptionFactory::create( auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(api_config_source); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ factory_or_error.value()->createUncachedRawAsyncClient(), /*dispatcher_=*/data.dispatcher_, /*service_method_=*/deltaGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/data.scope_, /*config_validators_=*/std::move(custom_config_validators), /*xds_resources_delegate_=*/{}, diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc index 3c9473dd5105..910dab4006e0 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc @@ -571,6 +571,9 @@ class GrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef xds_resources_delegate, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -578,7 +581,7 @@ class GrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/xds_resources_delegate, diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc index aea80ca7b8f1..20efc1d418b4 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc @@ -29,12 +29,15 @@ GrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::SubscriptionDat auto factory_or_error = Utility::factoryForGrpcApiConfigSource( data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(api_config_source); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/factory_or_error.value()->createUncachedRawAsyncClient(), /*dispatcher_=*/data.dispatcher_, /*service_method_=*/sotwGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/data.scope_, /*config_validators_=*/std::move(custom_config_validators), /*xds_resources_delegate_=*/data.xds_resources_delegate_, @@ -74,12 +77,15 @@ DeltaGrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::Subscripti auto factory_or_error = Utility::factoryForGrpcApiConfigSource( data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(api_config_source); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/factory_or_error.value()->createUncachedRawAsyncClient(), /*dispatcher_=*/data.dispatcher_, /*service_method_=*/deltaGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/data.scope_, /*config_validators_=*/std::move(custom_config_validators), /*xds_resources_delegate_=*/{}, diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc index e0f73f807f0b..571699f2c4c4 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc @@ -357,6 +357,9 @@ class NewGrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, OptRef, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -364,7 +367,7 @@ class NewGrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/absl::nullopt, diff --git a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc index 7f15728b26ef..52f1eedbe909 100644 --- a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc @@ -415,6 +415,9 @@ class DeltaGrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -422,7 +425,7 @@ class DeltaGrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/absl::nullopt, @@ -450,6 +453,9 @@ class SotwGrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -457,7 +463,7 @@ class SotwGrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/absl::nullopt, diff --git a/source/extensions/filters/common/ratelimit/ratelimit_impl.cc b/source/extensions/filters/common/ratelimit/ratelimit_impl.cc index 3097f1b62e5e..a72fa1b81661 100644 --- a/source/extensions/filters/common/ratelimit/ratelimit_impl.cc +++ b/source/extensions/filters/common/ratelimit/ratelimit_impl.cc @@ -127,12 +127,14 @@ ClientPtr rateLimitClient(Server::Configuration::FactoryContext& context, const std::chrono::milliseconds timeout) { // TODO(ramaraochavali): register client to singleton when GrpcClientImpl supports concurrent // requests. - return std::make_unique( + auto client_or_error = context.serverFactoryContext() .clusterManager() .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true), - timeout); + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + return std::make_unique(client_or_error.value(), + timeout); } } // namespace RateLimit diff --git a/source/extensions/filters/http/basic_auth/config.cc b/source/extensions/filters/http/basic_auth/config.cc index d7064730bccb..0b0332d8ec99 100644 --- a/source/extensions/filters/http/basic_auth/config.cc +++ b/source/extensions/filters/http/basic_auth/config.cc @@ -63,8 +63,9 @@ UserMap readHtpasswd(const std::string& htpasswd) { Http::FilterFactoryCb BasicAuthFilterFactory::createFilterFactoryFromProtoTyped( const BasicAuth& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { - UserMap users = readHtpasswd( - Config::DataSource::read(proto_config.users(), false, context.serverFactoryContext().api())); + UserMap users = readHtpasswd(THROW_OR_RETURN_VALUE( + Config::DataSource::read(proto_config.users(), false, context.serverFactoryContext().api()), + std::string)); FilterConfigConstSharedPtr config = std::make_unique(std::move(users), stats_prefix, context.scope()); return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index bbb99f63cc82..881609481e96 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -55,12 +55,14 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( Envoy::Grpc::GrpcServiceConfigWithHashKey(proto_config.grpc_service()); callback = [&context, filter_config, timeout_ms, config_with_hash_key](Http::FilterChainFactoryCallbacks& callbacks) { - auto client = std::make_unique( + auto client_or_error = context.serverFactoryContext() .clusterManager() .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true), - std::chrono::milliseconds(timeout_ms)); + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + auto client = std::make_unique( + client_or_error.value(), std::chrono::milliseconds(timeout_ms)); callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); }; } diff --git a/source/extensions/filters/http/ext_proc/client_impl.cc b/source/extensions/filters/http/ext_proc/client_impl.cc index a75f4aa88cc0..3c658244e72a 100644 --- a/source/extensions/filters/http/ext_proc/client_impl.cc +++ b/source/extensions/filters/http/ext_proc/client_impl.cc @@ -16,8 +16,10 @@ ExternalProcessorStreamPtr ExternalProcessorClientImpl::start( const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, const StreamInfo::StreamInfo& stream_info, const absl::optional& retry_policy) { - Grpc::AsyncClient grpcClient( - client_manager_.getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, scope_, true)); + auto client_or_error = + client_manager_.getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, scope_, true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + Grpc::AsyncClient grpcClient(client_or_error.value()); return ExternalProcessorStreamImpl::create(std::move(grpcClient), callbacks, stream_info, retry_policy); } diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 83be047c14f5..692cc5ad3b6f 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -1,5 +1,7 @@ #include "source/extensions/filters/http/ext_proc/ext_proc.h" +#include + #include "envoy/config/common/mutation_rules/v3/mutation_rules.pb.h" #include "envoy/config/core/v3/grpc_service.pb.h" #include "envoy/extensions/filters/http/ext_proc/v3/processing_mode.pb.h" @@ -277,8 +279,7 @@ void Filter::onDestroy() { } FilterHeadersStatus Filter::onHeaders(ProcessorState& state, - Http::RequestOrResponseHeaderMap& headers, bool end_stream, - ProtobufWkt::Struct* proto) { + Http::RequestOrResponseHeaderMap& headers, bool end_stream) { switch (openStream()) { case StreamOpenState::Error: return FilterHeadersStatus::StopIteration; @@ -292,14 +293,12 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, state.setHeaders(&headers); state.setHasNoBody(end_stream); ProcessingRequest req; + addAttributes(state, req); addDynamicMetadata(state, req); auto* headers_req = state.mutableHeaders(req); MutationUtils::headersToProto(headers, config_->allowedHeaders(), config_->disallowedHeaders(), *headers_req->mutable_headers()); headers_req->set_end_of_stream(end_stream); - if (proto != nullptr) { - (*headers_req->mutable_attributes())[FilterName] = *proto; - } state.onStartProcessorCall(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout(), ProcessorState::CallbackState::HeadersCallback); ENVOY_LOG(debug, "Sending headers message"); @@ -316,19 +315,14 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st decoding_state_.setCompleteBodyAvailable(true); } + // Set the request headers on decoding and encoding state in case they are + // needed later. + decoding_state_.setRequestHeaders(&headers); + encoding_state_.setRequestHeaders(&headers); + FilterHeadersStatus status = FilterHeadersStatus::Continue; if (decoding_state_.sendHeaders()) { - ProtobufWkt::Struct proto; - - if (config_->expressionManager().hasRequestExpr()) { - auto activation_ptr = Filters::Common::Expr::createActivation( - &config_->expressionManager().localInfo(), decoding_state_.callbacks()->streamInfo(), - &headers, nullptr, nullptr); - proto = config_->expressionManager().evaluateRequestAttributes(*activation_ptr); - } - - status = onHeaders(decoding_state_, headers, end_stream, - config_->expressionManager().hasRequestExpr() ? &proto : nullptr); + status = onHeaders(decoding_state_, headers, end_stream); ENVOY_LOG(trace, "onHeaders returning {}", static_cast(status)); } else { ENVOY_LOG(trace, "decodeHeaders: Skipped header processing"); @@ -591,7 +585,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& FilterTrailersStatus Filter::decodeTrailers(RequestTrailerMap& trailers) { ENVOY_LOG(trace, "decodeTrailers"); const auto status = onTrailers(decoding_state_, trailers); - ENVOY_LOG(trace, "encodeTrailers returning {}", static_cast(status)); + ENVOY_LOG(trace, "decodeTrailers returning {}", static_cast(status)); return status; } @@ -606,17 +600,7 @@ FilterHeadersStatus Filter::encodeHeaders(ResponseHeaderMap& headers, bool end_s FilterHeadersStatus status = FilterHeadersStatus::Continue; if (!processing_complete_ && encoding_state_.sendHeaders()) { - ProtobufWkt::Struct proto; - - if (config_->expressionManager().hasResponseExpr()) { - auto activation_ptr = Filters::Common::Expr::createActivation( - &config_->expressionManager().localInfo(), encoding_state_.callbacks()->streamInfo(), - nullptr, &headers, nullptr); - proto = config_->expressionManager().evaluateResponseAttributes(*activation_ptr); - } - - status = onHeaders(encoding_state_, headers, end_stream, - config_->expressionManager().hasResponseExpr() ? &proto : nullptr); + status = onHeaders(encoding_state_, headers, end_stream); ENVOY_LOG(trace, "onHeaders returns {}", static_cast(status)); } else { ENVOY_LOG(trace, "encodeHeaders: Skipped header processing"); @@ -651,6 +635,7 @@ ProcessingRequest Filter::setupBodyChunk(ProcessorState& state, const Buffer::In bool end_stream) { ENVOY_LOG(debug, "Sending a body chunk of {} bytes, end_stream {}", data.length(), end_stream); ProcessingRequest req; + addAttributes(state, req); addDynamicMetadata(state, req); auto* body_req = state.mutableBody(req); body_req->set_end_of_stream(end_stream); @@ -668,6 +653,7 @@ void Filter::sendBodyChunk(ProcessorState& state, ProcessorState::CallbackState void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers) { ProcessingRequest req; + addAttributes(state, req); addDynamicMetadata(state, req); auto* trailers_req = state.mutableTrailers(req); MutationUtils::headersToProto(trailers, config_->allowedHeaders(), config_->disallowedHeaders(), @@ -772,6 +758,21 @@ void Filter::addDynamicMetadata(const ProcessorState& state, ProcessingRequest& *req.mutable_metadata_context() = forwarding_metadata; } +void Filter::addAttributes(ProcessorState& state, ProcessingRequest& req) { + if (!state.sendAttributes(config_->expressionManager())) { + return; + } + + auto activation_ptr = Filters::Common::Expr::createActivation( + &config_->expressionManager().localInfo(), state.callbacks()->streamInfo(), + state.requestHeaders(), dynamic_cast(state.responseHeaders()), + dynamic_cast(state.responseTrailers())); + auto attributes = state.evaluateAttributes(config_->expressionManager(), *activation_ptr); + + state.setSentAttributes(true); + (*req.mutable_attributes())[FilterName] = attributes; +} + void Filter::setDynamicMetadata(Http::StreamFilterCallbacks* cb, const ProcessorState& state, const ProcessingResponse& response) { if (state.untypedReceivingMetadataNamespaces().empty() || !response.has_dynamic_metadata()) { diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 56c53c766744..93da3ebabd5d 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -384,8 +384,7 @@ class Filter : public Logger::Loggable, void sendImmediateResponse(const envoy::service::ext_proc::v3::ImmediateResponse& response); Http::FilterHeadersStatus onHeaders(ProcessorState& state, - Http::RequestOrResponseHeaderMap& headers, bool end_stream, - ProtobufWkt::Struct* proto); + Http::RequestOrResponseHeaderMap& headers, bool end_stream); // Return a pair of whether to terminate returning the current result. std::pair sendStreamChunk(ProcessorState& state); @@ -397,6 +396,7 @@ class Filter : public Logger::Loggable, void setDecoderDynamicMetadata(const envoy::service::ext_proc::v3::ProcessingResponse& response); void addDynamicMetadata(const ProcessorState& state, envoy::service::ext_proc::v3::ProcessingRequest& req); + void addAttributes(ProcessorState& state, envoy::service::ext_proc::v3::ProcessingRequest& req); const FilterConfigSharedPtr config_; const ExternalProcessorClientPtr client_; diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index a3ebe27617f0..01e9bc57ae59 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -15,6 +15,7 @@ #include "source/common/common/logger.h" #include "absl/status/status.h" +#include "matching_utils.h" namespace Envoy { namespace Extensions { @@ -134,8 +135,12 @@ class ProcessorState : public Logger::Loggable { return body_mode_; } + void setRequestHeaders(Http::RequestHeaderMap* headers) { request_headers_ = headers; } void setHeaders(Http::RequestOrResponseHeaderMap* headers) { headers_ = headers; } void setTrailers(Http::HeaderMap* trailers) { trailers_ = trailers; } + const Http::RequestHeaderMap* requestHeaders() const { return request_headers_; }; + virtual const Http::RequestOrResponseHeaderMap* responseHeaders() const PURE; + const Http::HeaderMap* responseTrailers() const { return trailers_; } void onStartProcessorCall(Event::TimerCb cb, std::chrono::milliseconds timeout, CallbackState callback_state); @@ -202,6 +207,14 @@ class ProcessorState : public Logger::Loggable { virtual Http::StreamFilterCallbacks* callbacks() const PURE; + virtual bool sendAttributes(const ExpressionManager& mgr) const PURE; + + void setSentAttributes(bool sent) { attributes_sent_ = sent; } + + virtual ProtobufWkt::Struct + evaluateAttributes(const ExpressionManager& mgr, + const Filters::Common::Expr::Activation& activation) const PURE; + protected: void setBodyMode( envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode); @@ -236,6 +249,10 @@ class ProcessorState : public Logger::Loggable { // The specific mode for body handling envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode_; + // The request_headers_ field is guaranteed to hold the pointer to the request + // headers as set in decodeHeaders. This allows both decoding and encoding states + // to have access to the request headers map. + Http::RequestHeaderMap* request_headers_ = nullptr; Http::RequestOrResponseHeaderMap* headers_ = nullptr; Http::HeaderMap* trailers_ = nullptr; Event::TimerPtr message_timer_; @@ -250,6 +267,9 @@ class ProcessorState : public Logger::Loggable { const std::vector* typed_forwarding_namespaces_{}; const std::vector* untyped_receiving_namespaces_{}; + // If true, the attributes for this processing state have already been sent. + bool attributes_sent_{}; + private: virtual void clearRouteCache(const envoy::service::ext_proc::v3::CommonResponse&) {} }; @@ -324,6 +344,17 @@ class DecodingProcessorState : public ProcessorState { Http::StreamFilterCallbacks* callbacks() const override { return decoder_callbacks_; } + bool sendAttributes(const ExpressionManager& mgr) const override { + return !attributes_sent_ && mgr.hasRequestExpr(); + } + + const Http::RequestOrResponseHeaderMap* responseHeaders() const override { return nullptr; } + ProtobufWkt::Struct + evaluateAttributes(const ExpressionManager& mgr, + const Filters::Common::Expr::Activation& activation) const override { + return mgr.evaluateRequestAttributes(activation); + } + private: void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); @@ -404,6 +435,18 @@ class EncodingProcessorState : public ProcessorState { Http::StreamFilterCallbacks* callbacks() const override { return encoder_callbacks_; } + bool sendAttributes(const ExpressionManager& mgr) const override { + return !attributes_sent_ && mgr.hasResponseExpr(); + } + + const Http::RequestOrResponseHeaderMap* responseHeaders() const override { return headers_; } + + ProtobufWkt::Struct + evaluateAttributes(const ExpressionManager& mgr, + const Filters::Common::Expr::Activation& activation) const override { + return mgr.evaluateResponseAttributes(activation); + } + private: void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); diff --git a/source/extensions/filters/http/jwt_authn/extractor.cc b/source/extensions/filters/http/jwt_authn/extractor.cc index 805af897a612..da5083026b82 100644 --- a/source/extensions/filters/http/jwt_authn/extractor.cc +++ b/source/extensions/filters/http/jwt_authn/extractor.cc @@ -253,20 +253,21 @@ ExtractorImpl::extract(const Http::RequestHeaderMap& headers) const { for (const auto& location_it : header_locations_) { const auto& location_spec = location_it.second; ENVOY_LOG(debug, "extract {}", location_it.first); - const auto result = - Http::HeaderUtility::getAllOfHeaderAsString(headers, location_spec->header_); - if (result.result().has_value()) { - auto value_str = result.result().value(); - if (!location_spec->value_prefix_.empty()) { - const auto pos = value_str.find(location_spec->value_prefix_); - if (pos == absl::string_view::npos) { - // value_prefix not found anywhere in value_str, so skip - continue; + auto header_value = headers.get(location_spec->header_); + if (!header_value.empty()) { + for (size_t i = 0; i < header_value.size(); i++) { + auto value_str = header_value[i]->value().getStringView(); + if (!location_spec->value_prefix_.empty()) { + const auto pos = value_str.find(location_spec->value_prefix_); + if (pos == absl::string_view::npos) { + // value_prefix not found anywhere in value_str, so skip + continue; + } + value_str = extractJWT(value_str, pos + location_spec->value_prefix_.length()); } - value_str = extractJWT(value_str, pos + location_spec->value_prefix_.length()); + tokens.push_back(std::make_unique( + std::string(value_str), location_spec->issuer_checker_, location_spec->header_)); } - tokens.push_back(std::make_unique( - std::string(value_str), location_spec->issuer_checker_, location_spec->header_)); } } diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.cc b/source/extensions/filters/http/jwt_authn/filter_factory.cc index 5bb39e47477e..abbf4ac34d21 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.cc +++ b/source/extensions/filters/http/jwt_authn/filter_factory.cc @@ -24,7 +24,8 @@ namespace { */ void validateJwtConfig(const JwtAuthentication& proto_config, Api::Api& api) { for (const auto& [name, provider] : proto_config.providers()) { - const auto inline_jwks = Config::DataSource::read(provider.local_jwks(), true, api); + const auto inline_jwks = THROW_OR_RETURN_VALUE( + Config::DataSource::read(provider.local_jwks(), true, api), std::string); if (!inline_jwks.empty()) { auto jwks_obj = Jwks::createFrom(inline_jwks, Jwks::JWKS); if (jwks_obj->getStatus() != Status::Ok) { diff --git a/source/extensions/filters/http/jwt_authn/jwks_cache.cc b/source/extensions/filters/http/jwt_authn/jwks_cache.cc index 818108701001..924e62cfc935 100644 --- a/source/extensions/filters/http/jwt_authn/jwks_cache.cc +++ b/source/extensions/filters/http/jwt_authn/jwks_cache.cc @@ -62,8 +62,10 @@ class JwksDataImpl : public JwksCache::JwksData, public Logger::Loggable(enable_jwt_cache, config, dispatcher.timeSource()); }); - const auto inline_jwks = Config::DataSource::read(jwt_provider_.local_jwks(), true, - context.serverFactoryContext().api()); + const auto inline_jwks = + THROW_OR_RETURN_VALUE(Config::DataSource::read(jwt_provider_.local_jwks(), true, + context.serverFactoryContext().api()), + std::string); if (!inline_jwks.empty()) { auto jwks = ::google::jwt_verify::Jwks::createFrom(inline_jwks, ::google::jwt_verify::Jwks::JWKS); diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 0f4b53f22934..a941b889a505 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -808,15 +808,16 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua& "for the Lua filter."); } - const std::string code = - Config::DataSource::read(proto_config.default_source_code(), true, api); + const std::string code = THROW_OR_RETURN_VALUE( + Config::DataSource::read(proto_config.default_source_code(), true, api), std::string); default_lua_code_setup_ = std::make_unique(code, tls); } else if (!proto_config.inline_code().empty()) { default_lua_code_setup_ = std::make_unique(proto_config.inline_code(), tls); } for (const auto& source : proto_config.source_codes()) { - const std::string code = Config::DataSource::read(source.second, true, api); + const std::string code = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source.second, true, api), std::string); auto per_lua_code_setup_ptr = std::make_unique(code, tls); if (!per_lua_code_setup_ptr) { continue; @@ -834,7 +835,8 @@ FilterConfigPerRoute::FilterConfigPerRoute( return; } // Read and parse the inline Lua code defined in the route configuration. - const std::string code_str = Config::DataSource::read(config.source_code(), true, context.api()); + const std::string code_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.source_code(), true, context.api()), std::string); per_lua_code_setup_ptr_ = std::make_unique(code_str, context.threadLocal()); } diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index 3e86351fc98b..d874f9f1bf66 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -60,13 +60,15 @@ class SDSSecretReader : public SecretReader { Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = + THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), std::string); } return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), + std::string); } }); } diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.cc b/source/extensions/filters/http/rate_limit_quota/client_impl.cc index 36af6a35b4fb..62154c371de0 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.cc +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.cc @@ -7,6 +7,25 @@ namespace Extensions { namespace HttpFilters { namespace RateLimitQuota { +Grpc::RawAsyncClientSharedPtr +getOrThrow(absl::StatusOr client_or_error) { + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + return client_or_error.value(); +} + +RateLimitClientImpl::RateLimitClientImpl( + const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, + Server::Configuration::FactoryContext& context, absl::string_view domain_name, + RateLimitQuotaCallbacks* callbacks, BucketsCache& quota_buckets) + : domain_name_(domain_name), + aync_client_(getOrThrow( + context.serverFactoryContext() + .clusterManager() + .grpcAsyncClientManager() + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true))), + rlqs_callback_(callbacks), quota_buckets_(quota_buckets), + time_source_(context.serverFactoryContext().mainThreadDispatcher().timeSource()) {} + RateLimitQuotaUsageReports RateLimitClientImpl::buildReport(absl::optional bucket_id) { RateLimitQuotaUsageReports report; // Build the report from quota bucket cache. diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.h b/source/extensions/filters/http/rate_limit_quota/client_impl.h index eb7c0f6a6685..4999ef62f9ca 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.h +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.h @@ -34,15 +34,7 @@ class RateLimitClientImpl : public RateLimitClient, public: RateLimitClientImpl(const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, Server::Configuration::FactoryContext& context, absl::string_view domain_name, - RateLimitQuotaCallbacks* callbacks, BucketsCache& quota_buckets) - : domain_name_(domain_name), - aync_client_( - context.serverFactoryContext() - .clusterManager() - .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true)), - rlqs_callback_(callbacks), quota_buckets_(quota_buckets), - time_source_(context.serverFactoryContext().mainThreadDispatcher().timeSource()) {} + RateLimitQuotaCallbacks* callbacks, BucketsCache& quota_buckets); void onReceiveMessage(RateLimitQuotaResponsePtr&& response) override; diff --git a/source/extensions/filters/network/common/redis/supported_commands.h b/source/extensions/filters/network/common/redis/supported_commands.h index 560689eba715..907eb2d6e238 100644 --- a/source/extensions/filters/network/common/redis/supported_commands.h +++ b/source/extensions/filters/network/common/redis/supported_commands.h @@ -61,6 +61,11 @@ struct SupportedCommands { */ static const std::string& auth() { CONSTRUCT_ON_FIRST_USE(std::string, "auth"); } + /** + * @return auth command + */ + static const std::string& echo() { CONSTRUCT_ON_FIRST_USE(std::string, "echo"); } + /** * @return mget command */ diff --git a/source/extensions/filters/network/direct_response/config.cc b/source/extensions/filters/network/direct_response/config.cc index 5d39492f590a..37f08ce2687b 100644 --- a/source/extensions/filters/network/direct_response/config.cc +++ b/source/extensions/filters/network/direct_response/config.cc @@ -25,8 +25,9 @@ class DirectResponseConfigFactory Network::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::network::direct_response::v3::Config& config, Server::Configuration::FactoryContext& context) override { - auto content = - Config::DataSource::read(config.response(), true, context.serverFactoryContext().api()); + auto content = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.response(), true, context.serverFactoryContext().api()), + std::string); return [content](Network::FilterManager& filter_manager) -> void { filter_manager.addReadFilter(std::make_shared(content)); diff --git a/source/extensions/filters/network/ext_authz/config.cc b/source/extensions/filters/network/ext_authz/config.cc index fe385b77c24a..28efb8fb9605 100644 --- a/source/extensions/filters/network/ext_authz/config.cc +++ b/source/extensions/filters/network/ext_authz/config.cc @@ -30,13 +30,13 @@ Network::FilterFactoryCb ExtAuthzConfigFactory::createFilterFactoryFromProtoType THROW_IF_NOT_OK(Envoy::Config::Utility::checkTransportVersion(proto_config)); return [grpc_service = proto_config.grpc_service(), &context, ext_authz_config, timeout_ms](Network::FilterManager& filter_manager) -> void { - auto async_client_factory = context.serverFactoryContext() - .clusterManager() - .grpcAsyncClientManager() - .factoryForGrpcService(grpc_service, context.scope(), true); - + auto factory_or_error = context.serverFactoryContext() + .clusterManager() + .grpcAsyncClientManager() + .factoryForGrpcService(grpc_service, context.scope(), true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); auto client = std::make_unique( - async_client_factory->createUncachedRawAsyncClient(), + factory_or_error.value()->createUncachedRawAsyncClient(), std::chrono::milliseconds(timeout_ms)); filter_manager.addReadFilter(Network::ReadFilterSharedPtr{ std::make_shared(ext_authz_config, std::move(client))}); diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index 692b5e073243..606d7c9ec202 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -629,6 +629,19 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, return nullptr; } + if (command_name == Common::Redis::SupportedCommands::echo()) { + // Respond to ECHO locally. + if (request->asArray().size() != 2) { + onInvalidRequest(callbacks); + return nullptr; + } + Common::Redis::RespValuePtr echo_resp(new Common::Redis::RespValue()); + echo_resp->type(Common::Redis::RespType::BulkString); + echo_resp->asString() = request->asArray()[1].asString(); + callbacks.onResponse(std::move(echo_resp)); + return nullptr; + } + if (command_name == Common::Redis::SupportedCommands::time()) { // Respond to TIME locally. Common::Redis::RespValuePtr time_resp(new Common::Redis::RespValue()); diff --git a/source/extensions/filters/network/redis_proxy/config.h b/source/extensions/filters/network/redis_proxy/config.h index fc32de5163bc..ac2a873ed951 100644 --- a/source/extensions/filters/network/redis_proxy/config.h +++ b/source/extensions/filters/network/redis_proxy/config.h @@ -27,11 +27,11 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig { } std::string authUsername(Api::Api& api) const { - return Config::DataSource::read(auth_username_, true, api); + return THROW_OR_RETURN_VALUE(Config::DataSource::read(auth_username_, true, api), std::string); } std::string authPassword(Api::Api& api) const { - return Config::DataSource::read(auth_password_, true, api); + return THROW_OR_RETURN_VALUE(Config::DataSource::read(auth_password_, true, api), std::string); } static const std::string authUsername(const Upstream::ClusterInfoConstSharedPtr info, diff --git a/source/extensions/filters/network/redis_proxy/proxy_filter.cc b/source/extensions/filters/network/redis_proxy/proxy_filter.cc index eff64deae428..3426239991cd 100644 --- a/source/extensions/filters/network/redis_proxy/proxy_filter.cc +++ b/source/extensions/filters/network/redis_proxy/proxy_filter.cc @@ -24,8 +24,8 @@ ProxyFilterConfig::ProxyFilterConfig( : drain_decision_(drain_decision), runtime_(runtime), stat_prefix_(fmt::format("redis.{}.", config.stat_prefix())), stats_(generateStats(stat_prefix_, scope)), - downstream_auth_username_( - Config::DataSource::read(config.downstream_auth_username(), true, api)), + downstream_auth_username_(THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.downstream_auth_username(), true, api), std::string)), dns_cache_manager_(cache_manager_factory.get()), dns_cache_(getCache(config)) { if (config.settings().enable_redirection() && !config.settings().has_dns_cache_config()) { @@ -33,8 +33,8 @@ ProxyFilterConfig::ProxyFilterConfig( "dns_cache_config field within the connection pool settings to avoid them"); } - auto downstream_auth_password = - Config::DataSource::read(config.downstream_auth_password(), true, api); + auto downstream_auth_password = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.downstream_auth_password(), true, api), std::string); if (!downstream_auth_password.empty()) { downstream_auth_passwords_.emplace_back(downstream_auth_password); } @@ -43,7 +43,8 @@ ProxyFilterConfig::ProxyFilterConfig( downstream_auth_passwords_.reserve(downstream_auth_passwords_.size() + config.downstream_auth_passwords().size()); for (const auto& source : config.downstream_auth_passwords()) { - const auto p = Config::DataSource::read(source, true, api); + const auto p = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, true, api), std::string); if (!p.empty()) { downstream_auth_passwords_.emplace_back(p); } diff --git a/source/extensions/grpc_credentials/file_based_metadata/config.cc b/source/extensions/grpc_credentials/file_based_metadata/config.cc index 1fa300a2f711..266adbf96443 100644 --- a/source/extensions/grpc_credentials/file_based_metadata/config.cc +++ b/source/extensions/grpc_credentials/file_based_metadata/config.cc @@ -72,7 +72,8 @@ FileBasedMetadataAuthenticator::GetMetadata(grpc::string_ref, grpc::string_ref, // TODO(#14320): avoid using an exception here or find some way of doing this // in the main thread. TRY_NEEDS_AUDIT { - std::string header_value = Envoy::Config::DataSource::read(config_.secret_data(), true, api_); + std::string header_value = THROW_OR_RETURN_VALUE( + Envoy::Config::DataSource::read(config_.secret_data(), true, api_), std::string); metadata->insert(std::make_pair(header_key, header_prefix + header_value)); } END_TRY diff --git a/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc b/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc index 80c98d28b2a3..8f94f6373fe0 100644 --- a/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc +++ b/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc @@ -21,9 +21,11 @@ LocalResponsePolicy::LocalResponsePolicy( const envoy::extensions::http::custom_response::local_response_policy::v3::LocalResponsePolicy& config, Server::Configuration::ServerFactoryContext& context) - : local_body_{config.has_body() ? absl::optional(Config::DataSource::read( - config.body(), true, context.api())) - : absl::optional{}}, + : local_body_{config.has_body() + ? absl::optional(THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.body(), true, context.api()), + std::string)) + : absl::optional{}}, status_code_{config.has_status_code() ? absl::optional( static_cast(config.status_code().value())) diff --git a/source/extensions/http/stateful_session/cookie/cookie.cc b/source/extensions/http/stateful_session/cookie/cookie.cc index 631249b763ea..21a508e7370e 100644 --- a/source/extensions/http/stateful_session/cookie/cookie.cc +++ b/source/extensions/http/stateful_session/cookie/cookie.cc @@ -16,12 +16,15 @@ void CookieBasedSessionStateFactory::SessionStateImpl::onUpdate( if (!upstream_address_.has_value() || host_address != upstream_address_.value()) { if (Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.stateful_session_encode_ttl_in_cookie")) { - auto expiry_time = std::chrono::duration_cast( - (time_source_.monotonicTime() + std::chrono::seconds(factory_.ttl_)).time_since_epoch()); // Build proto message envoy::Cookie cookie; cookie.set_address(std::string(host_address)); - cookie.set_expires(expiry_time.count()); + if (factory_.ttl_ != std::chrono::seconds::zero()) { + const auto expiry_time = std::chrono::duration_cast( + (time_source_.monotonicTime() + std::chrono::seconds(factory_.ttl_)) + .time_since_epoch()); + cookie.set_expires(expiry_time.count()); + } std::string proto_string; cookie.SerializeToString(&proto_string); diff --git a/source/extensions/http/stateful_session/cookie/cookie.h b/source/extensions/http/stateful_session/cookie/cookie.h index dc580395ae00..0ba898bf4c5b 100644 --- a/source/extensions/http/stateful_session/cookie/cookie.h +++ b/source/extensions/http/stateful_session/cookie/cookie.h @@ -69,17 +69,19 @@ class CookieBasedSessionStateFactory : public Envoy::Http::SessionStateFactory { envoy::Cookie cookie; if (cookie.ParseFromString(decoded_value)) { address = cookie.address(); - if (address.empty() || (cookie.expires() == 0)) { + if (address.empty()) { return absl::nullopt; } - std::chrono::seconds expiry_time(cookie.expires()); - auto now = std::chrono::duration_cast( - (time_source_.monotonicTime()).time_since_epoch()); - if (now > expiry_time) { - // Ignore the address extracted from the cookie. This will cause - // upstream cluster to select a new host and new cookie will be generated. - return absl::nullopt; + if (cookie.expires() != 0) { + const std::chrono::seconds expiry_time(cookie.expires()); + const auto now = std::chrono::duration_cast( + (time_source_.monotonicTime()).time_since_epoch()); + if (now > expiry_time) { + // Ignore the address extracted from the cookie. This will cause + // upstream cluster to select a new host and new cookie will be generated. + return absl::nullopt; + } } } else { ENVOY_LOG_ONCE_MISC( diff --git a/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc b/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc index c226a78c4a0a..5c33df45813b 100644 --- a/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc +++ b/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc @@ -48,7 +48,8 @@ LuaClusterSpecifierConfig::LuaClusterSpecifierConfig( Server::Configuration::CommonFactoryContext& context) : main_thread_dispatcher_(context.mainThreadDispatcher()), default_cluster_(config.default_cluster()) { - const std::string code_str = Config::DataSource::read(config.source_code(), true, context.api()); + const std::string code_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.source_code(), true, context.api()), std::string); per_lua_code_setup_ptr_ = std::make_unique(code_str, context.threadLocal()); } diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index db823131e0cd..791f81508f31 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -28,12 +28,13 @@ MetricsServiceSinkFactory::createStatsSink(const Protobuf::Message& config, THROW_IF_NOT_OK(Config::Utility::checkTransportVersion(sink_config)); ENVOY_LOG(debug, "Metrics Service gRPC service configuration: {}", grpc_service.DebugString()); + auto client_or_error = server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + grpc_service, server.scope(), false); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); std::shared_ptr> - grpc_metrics_streamer = std::make_shared( - server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, server.scope(), false), - server.localInfo()); + grpc_metrics_streamer = + std::make_shared(client_or_error.value(), server.localInfo()); return std::make_unique>( diff --git a/source/extensions/stat_sinks/open_telemetry/config.cc b/source/extensions/stat_sinks/open_telemetry/config.cc index f7e1487c5ea3..2dc66d339aef 100644 --- a/source/extensions/stat_sinks/open_telemetry/config.cc +++ b/source/extensions/stat_sinks/open_telemetry/config.cc @@ -26,11 +26,13 @@ OpenTelemetrySinkFactory::createStatsSink(const Protobuf::Message& config, case SinkConfig::ProtocolSpecifierCase::kGrpcService: { const auto& grpc_service = sink_config.grpc_service(); + auto client_or_error = + server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + grpc_service, server.scope(), false); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); std::shared_ptr grpc_metrics_exporter = - std::make_shared( - otlp_options, - server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, server.scope(), false)); + std::make_shared(otlp_options, + client_or_error.value()); return std::make_unique(otlp_metrics_flusher, grpc_metrics_exporter); } diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index 46ce3c35ae77..d3fe8624c65d 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -89,9 +89,11 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr sampler](Event::Dispatcher& dispatcher) { OpenTelemetryTraceExporterPtr exporter; if (opentelemetry_config.has_grpc_service()) { - Grpc::AsyncClientFactoryPtr&& factory = + auto factory_or_error = factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( opentelemetry_config.grpc_service(), factory_context.scope(), true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + Grpc::AsyncClientFactoryPtr&& factory = std::move(factory_or_error.value()); const Grpc::RawAsyncClientSharedPtr& async_client_shared_ptr = factory->createUncachedRawAsyncClient(); exporter = std::make_unique(async_client_shared_ptr); diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc index 0a5592df94f6..70ddb8defd92 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc @@ -30,7 +30,8 @@ Resource EnvironmentResourceDetector::detect() { std::string attributes_str = ""; TRY_NEEDS_AUDIT { - attributes_str = Config::DataSource::read(ds, true, context_.serverFactoryContext().api()); + attributes_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(ds, true, context_.serverFactoryContext().api()), std::string); } END_TRY catch (const EnvoyException& e) { ENVOY_LOG(warn, "Failed to detect resource attributes from the environment: {}.", e.what()); diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc index 33c9f67051e3..aa8b6d2d0cee 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -35,11 +35,13 @@ Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, tracing_context_factory_ = std::make_unique(config_); auto& factory_context = context.serverFactoryContext(); tls_slot_ptr_->set([proto_config, &factory_context, this](Event::Dispatcher& dispatcher) { - TracerPtr tracer = std::make_unique(std::make_unique( + auto factory_or_error = factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( - proto_config.grpc_service(), factory_context.scope(), true), - dispatcher, factory_context.api().randomGenerator(), tracing_stats_, - config_.delayed_buffer_size(), config_.token())); + proto_config.grpc_service(), factory_context.scope(), true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + TracerPtr tracer = std::make_unique(std::make_unique( + std::move(factory_or_error.value()), dispatcher, factory_context.api().randomGenerator(), + tracing_stats_, config_.delayed_buffer_size(), config_.token())); return std::make_shared(std::move(tracer)); }); } diff --git a/source/extensions/tracers/xray/config.cc b/source/extensions/tracers/xray/config.cc index 9421eb0678e0..805a900a1f3d 100644 --- a/source/extensions/tracers/xray/config.cc +++ b/source/extensions/tracers/xray/config.cc @@ -23,8 +23,10 @@ XRayTracerFactory::createTracerDriverTyped(const envoy::config::trace::v3::XRayC Server::Configuration::TracerFactoryContext& context) { std::string sampling_rules_json; TRY_NEEDS_AUDIT { - sampling_rules_json = Config::DataSource::read(proto_config.sampling_rule_manifest(), true, - context.serverFactoryContext().api()); + sampling_rules_json = + THROW_OR_RETURN_VALUE(Config::DataSource::read(proto_config.sampling_rule_manifest(), true, + context.serverFactoryContext().api()), + std::string); } END_TRY catch (EnvoyException& e) { ENVOY_LOG(error, "Failed to read sampling rules manifest because of {}.", e.what()); diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc index a5c67993712c..5ae1ebde5c55 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc @@ -61,7 +61,8 @@ SPIFFEValidator::SPIFFEValidator(const Envoy::Ssl::CertificateValidationContextC "Multiple trust bundles are given for one trust domain for ", domain.name())); } - auto cert = Config::DataSource::read(domain.trust_bundle(), true, config->api()); + auto cert = THROW_OR_RETURN_VALUE( + Config::DataSource::read(domain.trust_bundle(), true, config->api()), std::string); bssl::UniquePtr bio(BIO_new_mem_buf(const_cast(cert.data()), cert.size())); RELEASE_ASSERT(bio != nullptr, ""); bssl::UniquePtr list( diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 6322e594871f..b71736a12153 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -460,7 +460,8 @@ ServerContextConfigImpl::getSessionTicketKeys( const envoy::extensions::transport_sockets::tls::v3::TlsSessionTicketKeys& keys) { std::vector result; for (const auto& datasource : keys.keys()) { - result.emplace_back(getSessionTicketKey(Config::DataSource::read(datasource, false, api_))); + result.emplace_back(getSessionTicketKey( + THROW_OR_RETURN_VALUE(Config::DataSource::read(datasource, false, api_), std::string))); } return result; } diff --git a/source/server/server.cc b/source/server/server.cc index ce41536f6563..cbcd3ed80b80 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -35,7 +35,6 @@ #include "source/common/http/codes.h" #include "source/common/http/headers.h" #include "source/common/local_info/local_info_impl.h" -#include "source/common/memory/stats.h" #include "source/common/network/address_impl.h" #include "source/common/network/dns_resolver/dns_factory_util.h" #include "source/common/network/socket_interface.h" @@ -522,6 +521,9 @@ void InstanceBase::initializeOrThrow(Network::Address::InstanceConstSharedPtr lo server_stats_->dynamic_unknown_fields_, server_stats_->wip_protos_); + memory_allocator_ = std::make_unique( + *api_, *stats_store_.rootScope(), bootstrap_.memory_allocator_manager()); + initialization_timer_ = std::make_unique( server_stats_->initialization_time_ms_, timeSource()); server_stats_->concurrency_.set(options_.concurrency()); @@ -823,6 +825,15 @@ void InstanceBase::onRuntimeReady() { shutdown(); }); } + + // TODO (nezdolik): Fully deprecate this runtime key in the next release. + if (runtime().snapshot().get(Runtime::Keys::GlobalMaxCxRuntimeKey)) { + ENVOY_LOG(warn, + "Usage of the deprecated runtime key {}, consider switching to " + "`envoy.resource_monitors.downstream_connections` instead." + "This runtime key will be removed in future.", + Runtime::Keys::GlobalMaxCxRuntimeKey); + } } void InstanceBase::startWorkers() { @@ -908,8 +919,7 @@ RunHelper::RunHelper(Instance& instance, const Options& options, Event::Dispatch // If there is no global limit to the number of active connections, warn on startup. if (!overload_manager.getThreadLocalOverloadState().isResourceMonitorEnabled( - Server::OverloadProactiveResourceName::GlobalDownstreamMaxConnections) && - !instance.runtime().snapshot().get(Runtime::Keys::GlobalMaxCxRuntimeKey)) { + Server::OverloadProactiveResourceName::GlobalDownstreamMaxConnections)) { ENVOY_LOG(warn, "There is no configured limit to the number of allowed active downstream " "connections. Configure a " "limit in `envoy.resource_monitors.downstream_connections` resource monitor."); diff --git a/source/server/server.h b/source/server/server.h index dcfd5be7e5a9..48a56e1171a7 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -32,6 +32,7 @@ #include "source/common/grpc/context_impl.h" #include "source/common/http/context_impl.h" #include "source/common/init/manager_impl.h" +#include "source/common/memory/stats.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/quic/quic_stat_names.h" #include "source/common/router/context_impl.h" @@ -336,7 +337,6 @@ class InstanceBase : Logger::Loggable, Stage stage, std::function completion_cb = [] {}); void onRuntimeReady(); void onClusterManagerPrimaryInitializationComplete(); - using LifecycleNotifierCallbacks = std::list; using LifecycleNotifierCompletionCallbacks = std::list; @@ -414,8 +414,8 @@ class InstanceBase : Logger::Loggable, ServerFactoryContextImpl server_contexts_; bool enable_reuse_port_default_{false}; Regex::EnginePtr regex_engine_; - bool stats_flush_in_progress_ : 1; + std::unique_ptr memory_allocator_; template class LifecycleCallbackHandle : public ServerLifecycleNotifier::Handle, RaiiListElement { diff --git a/test/common/common/optref_test.cc b/test/common/common/optref_test.cc index e7356dc18c2b..b328dff0f757 100644 --- a/test/common/common/optref_test.cc +++ b/test/common/common/optref_test.cc @@ -98,6 +98,11 @@ TEST(OptRefTest, Conversion) { // Ref conversion on construction from non-const to const. OptRef const_ref_to_bar = bar_ref; EXPECT_EQ(&(*const_ref_to_bar), &bar); + + // Ref conversion on construction from derived class of null value. + OptRef bar_null_ref(absl::nullopt); + OptRef foo_ref_to_bar_null = bar_null_ref; + EXPECT_EQ(foo_ref_to_bar_null, absl::nullopt); } TEST(OptRefTest, Size) { diff --git a/test/common/config/datasource_test.cc b/test/common/config/datasource_test.cc index 070ff1631024..b58e5e0228d3 100644 --- a/test/common/config/datasource_test.cc +++ b/test/common/config/datasource_test.cc @@ -37,7 +37,6 @@ class AsyncDataSourceTest : public testing::Test { Event::TimerCb retry_timer_cb_; NiceMock request_{&cm_.thread_local_cluster_.async_client_}; - Config::DataSource::LocalAsyncDataProviderPtr local_data_provider_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; using AsyncClientSendFunc = std::function( - init_manager_, config.local(), true, *api_, [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, "xxxxxx"); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - - init_target_handle_->initialize(init_watcher_); - EXPECT_EQ(async_data, "xxxxxx"); -} - -TEST_F(AsyncDataSourceTest, LoadLocalEmptyDataSource) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - local: - inline_string: "" - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_local()); - - std::string async_data; - - EXPECT_CALL(init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { - init_target_handle_ = target.createHandle("test"); - })); - - local_data_provider_ = std::make_unique( - init_manager_, config.local(), true, *api_, [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, ""); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - - init_target_handle_->initialize(init_watcher_); - EXPECT_EQ(async_data, ""); -} - TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceNoCluster) { AsyncDataSourcePb config; @@ -601,7 +539,7 @@ TEST(DataSourceTest, WellKnownEnvironmentVariableTest) { config.specifier_case()); EXPECT_EQ(config.environment_variable(), "PATH"); Api::ApiPtr api = Api::createApiForTest(); - const auto path_data = DataSource::read(config, false, *api); + const auto path_data = DataSource::read(config, false, *api).value(); EXPECT_FALSE(path_data.empty()); } @@ -618,10 +556,10 @@ TEST(DataSourceTest, MissingEnvironmentVariableTest) { config.specifier_case()); EXPECT_EQ(config.environment_variable(), "ThisVariableDoesntExist"); Api::ApiPtr api = Api::createApiForTest(); - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, false, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableDoesntExist"); - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, true, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableDoesntExist"); + EXPECT_EQ(DataSource::read(config, false, *api).status().message(), + "Environment variable doesn't exist: ThisVariableDoesntExist"); + EXPECT_EQ(DataSource::read(config, true, *api).status().message(), + "Environment variable doesn't exist: ThisVariableDoesntExist"); } TEST(DataSourceTest, EmptyEnvironmentVariableTest) { @@ -641,14 +579,13 @@ TEST(DataSourceTest, EmptyEnvironmentVariableTest) { Api::ApiPtr api = Api::createApiForTest(); #ifdef WIN32 // Windows doesn't support empty environment variables. - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, false, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableIsEmpty"); - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, true, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableIsEmpty"); + EXPECT_EQ(DataSource::read(config, false, *api).status().message(), + "Environment variable doesn't exist: ThisVariableIsEmpty"); + EXPECT_EQ(DataSource::read(config, true, *api).status().message(), + "Environment variable doesn't exist: ThisVariableIsEmpty"); #else - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, false, *api), EnvoyException, - "DataSource cannot be empty"); - const auto environment_variable = DataSource::read(config, true, *api); + EXPECT_EQ(DataSource::read(config, false, *api).status().message(), "DataSource cannot be empty"); + const auto environment_variable = DataSource::read(config, true, *api).value(); EXPECT_TRUE(environment_variable.empty()); #endif } diff --git a/test/common/config/utility_test.cc b/test/common/config/utility_test.cc index b4f13d5195c8..767c0e7db1a9 100644 --- a/test/common/config/utility_test.cc +++ b/test/common/config/utility_test.cc @@ -86,19 +86,23 @@ TEST(UtilityTest, CheckFilesystemSubscriptionBackingPath) { TEST(UtilityTest, ParseDefaultRateLimitSettings) { envoy::config::core::v3::ApiConfigSource api_config_source; - const RateLimitSettings& rate_limit_settings = Utility::parseRateLimitSettings(api_config_source); - EXPECT_EQ(false, rate_limit_settings.enabled_); - EXPECT_EQ(100, rate_limit_settings.max_tokens_); - EXPECT_EQ(10, rate_limit_settings.fill_rate_); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_TRUE(rate_limit_settings.ok()); + EXPECT_EQ(false, rate_limit_settings->enabled_); + EXPECT_EQ(100, rate_limit_settings->max_tokens_); + EXPECT_EQ(10, rate_limit_settings->fill_rate_); } TEST(UtilityTest, ParseEmptyRateLimitSettings) { envoy::config::core::v3::ApiConfigSource api_config_source; api_config_source.mutable_rate_limit_settings(); - const RateLimitSettings& rate_limit_settings = Utility::parseRateLimitSettings(api_config_source); - EXPECT_EQ(true, rate_limit_settings.enabled_); - EXPECT_EQ(100, rate_limit_settings.max_tokens_); - EXPECT_EQ(10, rate_limit_settings.fill_rate_); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_TRUE(rate_limit_settings.ok()); + EXPECT_EQ(true, rate_limit_settings->enabled_); + EXPECT_EQ(100, rate_limit_settings->max_tokens_); + EXPECT_EQ(10, rate_limit_settings->fill_rate_); } TEST(UtilityTest, ParseRateLimitSettings) { @@ -107,10 +111,38 @@ TEST(UtilityTest, ParseRateLimitSettings) { api_config_source.mutable_rate_limit_settings(); rate_limits->mutable_max_tokens()->set_value(500); rate_limits->mutable_fill_rate()->set_value(4); - const RateLimitSettings& rate_limit_settings = Utility::parseRateLimitSettings(api_config_source); - EXPECT_EQ(true, rate_limit_settings.enabled_); - EXPECT_EQ(500, rate_limit_settings.max_tokens_); - EXPECT_EQ(4, rate_limit_settings.fill_rate_); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_TRUE(rate_limit_settings.ok()); + EXPECT_EQ(true, rate_limit_settings->enabled_); + EXPECT_EQ(500, rate_limit_settings->max_tokens_); + EXPECT_EQ(4, rate_limit_settings->fill_rate_); +} + +TEST(UtilityTest, ParseNanFillRateLimitSettings) { + envoy::config::core::v3::ApiConfigSource api_config_source; + envoy::config::core::v3::RateLimitSettings* rate_limits = + api_config_source.mutable_rate_limit_settings(); + rate_limits->mutable_max_tokens()->set_value(500); + rate_limits->mutable_fill_rate()->set_value(std::numeric_limits::quiet_NaN()); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_FALSE(rate_limit_settings.ok()); + EXPECT_EQ(rate_limit_settings.status().message(), + "The value of fill_rate in RateLimitSettings (nan) must not be NaN nor Inf"); +} + +TEST(UtilityTest, ParseInfiniteFillRateLimitSettings) { + envoy::config::core::v3::ApiConfigSource api_config_source; + envoy::config::core::v3::RateLimitSettings* rate_limits = + api_config_source.mutable_rate_limit_settings(); + rate_limits->mutable_max_tokens()->set_value(500); + rate_limits->mutable_fill_rate()->set_value(std::numeric_limits::infinity()); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_FALSE(rate_limit_settings.ok()); + EXPECT_EQ(rate_limit_settings.status().message(), + "The value of fill_rate in RateLimitSettings (inf) must not be NaN nor Inf"); } // TEST(UtilityTest, FactoryForGrpcApiConfigSource) should catch misconfigured diff --git a/test/common/grpc/async_client_manager_benchmark.cc b/test/common/grpc/async_client_manager_benchmark.cc index ca316d777cdf..5b62f6c270b5 100644 --- a/test/common/grpc/async_client_manager_benchmark.cc +++ b/test/common/grpc/async_client_manager_benchmark.cc @@ -53,8 +53,9 @@ void testGetOrCreateAsyncClientWithConfig(::benchmark::State& state) { UNREFERENCED_PARAMETER(_); for (int i = 0; i < 1000; i++) { RawAsyncClientSharedPtr foo_client0 = - async_client_man_test.async_client_manager_.getOrCreateRawAsyncClient( - grpc_service, async_client_man_test.scope_, true); + async_client_man_test.async_client_manager_ + .getOrCreateRawAsyncClient(grpc_service, async_client_man_test.scope_, true) + .value(); } } } @@ -70,8 +71,10 @@ void testGetOrCreateAsyncClientWithHashConfig(::benchmark::State& state) { UNREFERENCED_PARAMETER(_); for (int i = 0; i < 1000; i++) { RawAsyncClientSharedPtr foo_client0 = - async_client_man_test.async_client_manager_.getOrCreateRawAsyncClientWithHashKey( - config_with_hash_key_a, async_client_man_test.scope_, true); + async_client_man_test.async_client_manager_ + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key_a, + async_client_man_test.scope_, true) + .value(); } } } diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 4e89ac6f296b..8cbcd82c5c5d 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -231,7 +231,7 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcOk) { envoy::config::core::v3::GrpcService grpc_service; grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); EXPECT_CALL(cm_, checkActiveStaticCluster("foo")).WillOnce(Return(absl::OkStatus())); - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false); + ASSERT_TRUE(async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).ok()); } TEST_F(AsyncClientManagerImplTest, GrpcServiceConfigWithHashKeyTest) { @@ -266,16 +266,16 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithSecondsConfig) { initialize(aync_manager_config); RawAsyncClientSharedPtr foo_client_0 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); RawAsyncClientSharedPtr foo_client_1 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_0.get(), foo_client_1.get()); time_system_.advanceTimeAndRun(std::chrono::seconds(19), *dispatcher_, Event::Dispatcher::RunType::NonBlock); RawAsyncClientSharedPtr foo_client_2 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_1.get(), foo_client_2.get()); // Here we want to test behavior with a specific sequence of events, where each timer @@ -286,7 +286,7 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithSecondsConfig) { } RawAsyncClientSharedPtr foo_client_3 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_NE(foo_client_2.get(), foo_client_3.get()); } @@ -302,16 +302,16 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithMilliConfig) { initialize(aync_manager_config); RawAsyncClientSharedPtr foo_client_0 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); RawAsyncClientSharedPtr foo_client_1 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_0.get(), foo_client_1.get()); time_system_.advanceTimeAndRun(std::chrono::milliseconds(29999), *dispatcher_, Event::Dispatcher::RunType::NonBlock); RawAsyncClientSharedPtr foo_client_2 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_1.get(), foo_client_2.get()); // Here we want to test behavior with a specific sequence of events, where each timer @@ -322,7 +322,7 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithMilliConfig) { } RawAsyncClientSharedPtr foo_client_3 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_NE(foo_client_2.get(), foo_client_3.get()); } @@ -333,15 +333,15 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCache) { // Use cache when runtime is enabled. RawAsyncClientSharedPtr foo_client0 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); RawAsyncClientSharedPtr foo_client1 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client0.get(), foo_client1.get()); // Get a different raw async client with different cluster config. grpc_service.mutable_envoy_grpc()->set_cluster_name("bar"); RawAsyncClientSharedPtr bar_client = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_NE(foo_client1.get(), bar_client.get()); } @@ -352,8 +352,8 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcInvalid) { EXPECT_CALL(cm_, checkActiveStaticCluster("foo")).WillOnce(Invoke([](const std::string&) { return absl::InvalidArgumentError("failure"); })); - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "failure"); } @@ -364,10 +364,11 @@ TEST_F(AsyncClientManagerImplTest, GoogleGrpc) { grpc_service.mutable_google_grpc()->set_stat_prefix("foo"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_NE(nullptr, async_client_manager_->factoryForGrpcService(grpc_service, scope_, false)); + EXPECT_NE(nullptr, + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).value()); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -383,12 +384,12 @@ TEST_F(AsyncClientManagerImplTest, GoogleGrpcIllegalCharsInKey) { metadata.set_value("value"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Illegal characters in gRPC initial metadata header key: illegalcharacter;."); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -404,10 +405,11 @@ TEST_F(AsyncClientManagerImplTest, LegalGoogleGrpcChar) { metadata.set_value("value"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_NE(nullptr, async_client_manager_->factoryForGrpcService(grpc_service, scope_, false)); + EXPECT_NE(nullptr, + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).value()); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -423,12 +425,12 @@ TEST_F(AsyncClientManagerImplTest, GoogleGrpcIllegalCharsInValue) { metadata.set_value("NonAsciValue.भारत"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Illegal ASCII value for gRPC initial metadata header key: legal-key."); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -439,7 +441,8 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcUnknownSkipClusterCheck) { grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); EXPECT_CALL(cm_, checkActiveStaticCluster(_)).Times(0); - ASSERT_NO_THROW(async_client_manager_->factoryForGrpcService(grpc_service, scope_, true)); + ASSERT_TRUE( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, true).status().ok()); } } // namespace diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index 1d706dbe24aa..ea7327da21a6 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -369,6 +369,50 @@ TEST_F(AsyncClientImplTest, OngoingRequestWithWatermarkingAndReset) { stream_encoder_.getStream().resetStream(StreamResetReason::RemoteReset); } +TEST_F(AsyncClientImplTest, OngoingRequestWithResetAfterCompletion) { + auto headers = std::make_unique(); + HttpTestUtility::addDefaultHeaders(*headers); + TestRequestHeaderMapImpl headers_copy = *headers; + + Buffer::OwnedImpl data("test data"); + const Buffer::OwnedImpl data_copy(data.toString()); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](ResponseDecoder& decoder, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + headers_copy.addCopy("x-envoy-internal", "true"); + headers_copy.addCopy("x-forwarded-for", "127.0.0.1"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&headers_copy), false)); + EXPECT_CALL(stream_encoder_, encodeData(BufferEqual(&data_copy), true)); + + AsyncClient::OngoingRequest* request = + client_.startRequest(std::move(headers), callbacks_, AsyncClient::RequestOptions()); + EXPECT_NE(request, nullptr); + + request->sendData(data, true); + + expectSuccess(request, 200); + + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), true); + + request->reset(); + EXPECT_EQ( + 1UL, + cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("upstream_rq_200").value()); + EXPECT_EQ(1UL, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("internal.upstream_rq_200") + .value()); +} + TEST_F(AsyncClientImplTracingTest, Basic) { Tracing::MockSpan* child_span{new Tracing::MockSpan()}; message_->body().add("test body"); @@ -1444,6 +1488,50 @@ TEST_F(AsyncClientImplTracingTest, CancelRequest) { request->cancel(); } +TEST_F(AsyncClientImplTracingTest, CancelRequestAfterComplete) { + Tracing::MockSpan* child_span{new Tracing::MockSpan()}; + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](StreamDecoder&, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + return nullptr; + })); + + EXPECT_CALL(parent_span_, spawnChild_(_, "async fake_cluster egress", _)) + .WillOnce(Return(child_span)); + + AsyncClient::RequestOptions options = AsyncClient::RequestOptions().setParentSpan(parent_span_); + EXPECT_CALL(*child_span, setSampled(true)); + EXPECT_CALL(*child_span, injectContext(_, _)); + EXPECT_CALL(callbacks_, onBeforeFinalizeUpstreamSpan(_, _)) + .WillOnce(Invoke([](Tracing::Span& span, const Http::ResponseHeaderMap* response_headers) { + span.setTag("onBeforeFinalizeUpstreamSpan", "called"); + // Since this is a failure, we expect no response headers. + ASSERT_EQ(nullptr, response_headers); + })); + AsyncClient::Request* request = client_.send(std::move(message_), callbacks_, options); + + EXPECT_CALL(*child_span, setTag(Eq("onBeforeFinalizeUpstreamSpan"), Eq("called"))); + EXPECT_CALL(*child_span, + setTag(Eq(Tracing::Tags::get().Component), Eq(Tracing::Tags::get().Proxy))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/1.1"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().UpstreamAddress), Eq("10.0.0.1:443"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerAddress), Eq("10.0.0.1:443"))); + + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().UpstreamCluster), Eq("fake_cluster"))); + EXPECT_CALL(*child_span, + setTag(Eq(Tracing::Tags::get().UpstreamClusterName), Eq("observability_name"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("0"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().ResponseFlags), Eq("-"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*child_span, + setTag(Eq(Tracing::Tags::get().Canceled), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*child_span, finishSpan()); + request->cancel(); +} + TEST_F(AsyncClientImplTest, DestroyWithActiveStream) { EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) .WillOnce(Invoke( diff --git a/test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 b/test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 new file mode 100644 index 000000000000..6d064ff30a6b --- /dev/null +++ b/test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 @@ -0,0 +1,37 @@ +actions { + new_stream { + request_headers { + headers { + key: ":path" + value: "/" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: ":path" + value: "/" + } + headers { + key: "upgrade" + value: "connect-udp" + } + } + } +} +actions { + stream_action { + stream_id: 128 + response { + continue_headers { + } + } + } +} diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index d8ce2ffbfa45..81693fbb0cc3 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -94,7 +94,7 @@ class FuzzConfig : public ConnectionManagerConfig { callbacks.streamInfo().setResponseCodeDetails(""); })); EXPECT_CALL(*encoder_filter_, setEncoderFilterCallbacks(_)); - EXPECT_CALL(filter_factory_, createUpgradeFilterChain("WebSocket", _, _)) + EXPECT_CALL(filter_factory_, createUpgradeFilterChain(_, _, _)) .WillRepeatedly(Invoke([&](absl::string_view, const Http::FilterChainFactory::UpgradeMap*, FilterChainManager& manager) -> bool { return filter_factory_.createFilterChain(manager); @@ -341,6 +341,11 @@ class FuzzStream { response_state_ = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; })); + ON_CALL(encoder_, encodeData(_, true)) + .WillByDefault(Invoke([this](const Buffer::Instance&, bool end_stream) -> void { + response_state_ = + end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; + })); decoder_->decodeHeaders(std::move(headers), end_stream); return Http::okStatus(); })); diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 6633650dd204..e4ffd9d7c9b5 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -2237,5 +2237,42 @@ TEST_F(ConnectionManagerUtilityTest, DoNotOverwriteXForwardedPortFromUntrustedHo EXPECT_EQ("80", headers.getForwardedPortValue()); } +// Verify when TE header is present, the value should be preserved only if it's equal to "trailers". +TEST_F(ConnectionManagerUtilityTest, KeepTrailersTEHeaderSimple) { + TestRequestHeaderMapImpl headers{{"te", "trailers"}}; + callMutateRequestHeaders(headers, Protocol::Http2); + + EXPECT_EQ("trailers", headers.getTEValue()); +} + +// Verify when TE header is present, the value should be preserved only if it contains "trailers". +TEST_F(ConnectionManagerUtilityTest, KeepTrailersTEHeaderMultipleValuesAndWeigthted) { + TestRequestHeaderMapImpl headers{{"te", "chunked;q=0.8 , trailers ,deflate "}}; + callMutateRequestHeaders(headers, Protocol::Http2); + + EXPECT_EQ("trailers", headers.getTEValue()); +} + +// Verify when TE header is present, the value should be discarded if it doesn't contains +// "trailers". +TEST_F(ConnectionManagerUtilityTest, DiscardTEHeaderWithoutTrailers) { + TestRequestHeaderMapImpl headers{{"te", "gzip"}}; + callMutateRequestHeaders(headers, Protocol::Http2); + + EXPECT_EQ("", headers.getTEValue()); +} + +// Verify when TE header is present, the value should be kept if the reloadable feature +// "sanitize_te" is enabled. +TEST_F(ConnectionManagerUtilityTest, KeepTrailersTEHeaderReloadableFeatureDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.sanitize_te", "false"}}); + + TestRequestHeaderMapImpl headers{{"te", "gzip"}}; + callMutateRequestHeaders(headers, Protocol::Http2); + + EXPECT_EQ("gzip", headers.getTEValue()); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index afb1fb2c9a70..807fe5d32edc 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -560,6 +560,22 @@ TEST_F(ConnectivityGridTest, TestCancel) { cancel->cancel(Envoy::ConnectionPool::CancelPolicy::CloseExcess); } +// Test tearing down the grid with active connections. +TEST_F(ConnectivityGridTest, TestTeardown) { + initialize(); + addHttp3AlternateProtocol(); + EXPECT_EQ(grid_->first(), nullptr); + + grid_->newStream(decoder_, callbacks_, + {/*can_send_early_data_=*/false, + /*can_use_http3_=*/true}); + EXPECT_NE(grid_->first(), nullptr); + + // When the grid is reset, pool failure should be called. + EXPECT_CALL(callbacks_.pool_failure_, ready()); + grid_.reset(); +} + // Make sure drains get sent to all active pools. TEST_F(ConnectivityGridTest, Drain) { initialize(); @@ -599,20 +615,18 @@ TEST_F(ConnectivityGridTest, DrainCallbacks) { // The first time a drain is started, both pools should start draining. { EXPECT_CALL(*grid_->first(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections)); EXPECT_CALL(*grid_->second(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); - grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections)); + grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections); } - // The second time, the pools will not see any change. + // The second time a drain is started, both pools should still be notified. { EXPECT_CALL(*grid_->first(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)) - .Times(0); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); EXPECT_CALL(*grid_->second(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)) - .Times(0); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete); } { diff --git a/test/common/http/http2/response_header_corpus/clusterfuzz-testcase-minimized-response_header_fuzz_test-6305229339426816 b/test/common/http/http2/response_header_corpus/clusterfuzz-testcase-minimized-response_header_fuzz_test-6305229339426816 new file mode 100644 index 000000000000..f139d0ad01ed Binary files /dev/null and b/test/common/http/http2/response_header_corpus/clusterfuzz-testcase-minimized-response_header_fuzz_test-6305229339426816 differ diff --git a/test/common/memory/BUILD b/test/common/memory/BUILD index 3123d827b949..368cd14bf420 100644 --- a/test/common/memory/BUILD +++ b/test/common/memory/BUILD @@ -14,6 +14,18 @@ envoy_cc_test( deps = ["//source/common/memory:stats_lib"], ) +envoy_cc_test( + name = "memory_release_test", + srcs = ["memory_release_test.cc"], + deps = [ + "//source/common/event:dispatcher_lib", + "//source/common/memory:stats_lib", + "//test/common/stats:stat_test_utility_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "heap_shrinker_test", srcs = ["heap_shrinker_test.cc"], diff --git a/test/common/memory/memory_release_test.cc b/test/common/memory/memory_release_test.cc new file mode 100644 index 000000000000..041233baebd1 --- /dev/null +++ b/test/common/memory/memory_release_test.cc @@ -0,0 +1,139 @@ +#include "source/common/event/dispatcher_impl.h" +#include "source/common/memory/stats.h" + +#include "test/common/stats/stat_test_utility.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Memory { + +class AllocatorManagerPeer { +public: + static std::chrono::milliseconds + memoryReleaseInterval(const AllocatorManager& allocator_manager) { + return allocator_manager.memory_release_interval_msec_; + } + static uint64_t bytesToRelease(const AllocatorManager& allocator_manager) { + return allocator_manager.bytes_to_release_; + } +}; + +namespace { + +static const int MB = 1048576; + +class MemoryReleaseTest : public testing::Test { +protected: + MemoryReleaseTest() + : api_(Api::createApiForTest(stats_, time_system_)), + dispatcher_("test_thread", *api_, time_system_), scope_("memory_release_test.", stats_) {} + + void initialiseAllocatorManager(uint64_t bytes_to_release, float release_interval_s) { + const std::string yaml_config = (release_interval_s > 0) + ? fmt::format(R"EOF( + bytes_to_release: {} + memory_release_interval: {}s +)EOF", + bytes_to_release, release_interval_s) + : fmt::format(R"EOF( + bytes_to_release: {} +)EOF", + bytes_to_release); + const auto proto_config = + TestUtility::parseYaml(yaml_config); + allocator_manager_ = std::make_unique(*api_, scope_, proto_config); + } + + void step(const std::chrono::milliseconds& step) { time_system_.advanceTimeWait(step); } + + Envoy::Stats::TestUtil::TestStore stats_; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + Event::DispatcherImpl dispatcher_; + Envoy::Stats::TestUtil::TestScope scope_; + std::unique_ptr allocator_manager_; +}; + +TEST_F(MemoryReleaseTest, ReleaseRateAboveZeroDefaultIntervalMemoryReleased) { + size_t initial_allocated_bytes = Stats::totalCurrentlyAllocated(); + auto a = std::make_unique(MB); + auto b = std::make_unique(MB); + if (Stats::totalCurrentlyAllocated() <= initial_allocated_bytes) { + GTEST_SKIP() << "Skipping test, cannot measure memory usage precisely on this platform."; + } + auto initial_unmapped_bytes = Stats::totalPageHeapUnmapped(); + EXPECT_LOG_CONTAINS( + "info", + "Configured tcmalloc with background release rate: 1048576 bytes per 1000 milliseconds", + initialiseAllocatorManager(MB /*bytes per second*/, 0)); + EXPECT_EQ(MB, AllocatorManagerPeer::bytesToRelease(*allocator_manager_)); + EXPECT_EQ(std::chrono::milliseconds(1000), + AllocatorManagerPeer::memoryReleaseInterval(*allocator_manager_)); + a.reset(); + // Release interval was configured to default value (1 second). + step(std::chrono::milliseconds(1000)); + TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 1UL, + time_system_); + auto released_bytes_before_next_run = Stats::totalPageHeapUnmapped(); + b.reset(); + step(std::chrono::milliseconds(1000)); + TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 2UL, + time_system_); + auto final_released_bytes = Stats::totalPageHeapUnmapped(); + +#if defined(TCMALLOC) || defined(GPERFTOOLS_TCMALLOC) + EXPECT_LT(released_bytes_before_next_run, final_released_bytes); + EXPECT_LT(initial_unmapped_bytes, final_released_bytes); +#else + EXPECT_LE(released_bytes_before_next_run, final_released_bytes); + EXPECT_LE(initial_unmapped_bytes, final_released_bytes); +#endif +} + +TEST_F(MemoryReleaseTest, ReleaseRateZeroNoRelease) { + auto a = std::make_unique(MB); + EXPECT_LOG_NOT_CONTAINS( + "info", "Configured tcmalloc with background release rate: 0 bytes 1000 milliseconds", + initialiseAllocatorManager(0 /*bytes per second*/, 0)); + a.reset(); + // Release interval was configured to default value (1 second). + step(std::chrono::milliseconds(3000)); + EXPECT_EQ(0UL, stats_.counter("memory_release_test.tcmalloc.released_by_timer").value()); +} + +TEST_F(MemoryReleaseTest, ReleaseRateAboveZeroCustomIntervalMemoryReleased) { + size_t initial_allocated_bytes = Stats::totalCurrentlyAllocated(); + auto a = std::make_unique(40 * MB); + auto b = std::make_unique(40 * MB); + if (Stats::totalCurrentlyAllocated() <= initial_allocated_bytes) { + GTEST_SKIP() << "Skipping test, cannot measure memory usage precisely on this platform."; + } + auto initial_unmapped_bytes = Stats::totalPageHeapUnmapped(); + EXPECT_LOG_CONTAINS( + "info", + "Configured tcmalloc with background release rate: 16777216 bytes per 2000 milliseconds", + initialiseAllocatorManager(16 * MB /*bytes per second*/, 2)); + EXPECT_EQ(16 * MB, AllocatorManagerPeer::bytesToRelease(*allocator_manager_)); + EXPECT_EQ(std::chrono::milliseconds(2000), + AllocatorManagerPeer::memoryReleaseInterval(*allocator_manager_)); + a.reset(); + step(std::chrono::milliseconds(2000)); + b.reset(); + step(std::chrono::milliseconds(2000)); + TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 2UL, + time_system_); + auto final_released_bytes = Stats::totalPageHeapUnmapped(); +#if defined(TCMALLOC) || defined(GPERFTOOLS_TCMALLOC) + EXPECT_LT(initial_unmapped_bytes, final_released_bytes); +#else + EXPECT_LE(initial_unmapped_bytes, final_released_bytes); +#endif +} + +} // namespace +} // namespace Memory +} // namespace Envoy diff --git a/test/common/quic/envoy_quic_utils_test.cc b/test/common/quic/envoy_quic_utils_test.cc index 6950b09777a6..b1192d1aee7a 100644 --- a/test/common/quic/envoy_quic_utils_test.cc +++ b/test/common/quic/envoy_quic_utils_test.cc @@ -191,6 +191,8 @@ TEST(EnvoyQuicUtilsTest, ConvertQuicConfig) { EXPECT_EQ(25165824, quic_config.GetInitialSessionFlowControlWindowToSend()); EXPECT_TRUE(quic_config.SendConnectionOptions().empty()); EXPECT_TRUE(quic_config.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT).empty()); + EXPECT_EQ(quic::QuicTime::Delta::FromSeconds(quic::kMaximumIdleTimeoutSecs), + quic_config.IdleNetworkTimeout()); // Test converting values. config.mutable_max_concurrent_streams()->set_value(2); @@ -198,10 +200,12 @@ TEST(EnvoyQuicUtilsTest, ConvertQuicConfig) { config.mutable_initial_connection_window_size()->set_value(50); config.set_connection_options("5RTO,ACKD"); config.set_client_connection_options("6RTO,AKD4"); + config.mutable_idle_network_timeout()->set_seconds(30); convertQuicConfig(config, quic_config); EXPECT_EQ(2, quic_config.GetMaxBidirectionalStreamsToSend()); EXPECT_EQ(2, quic_config.GetMaxUnidirectionalStreamsToSend()); EXPECT_EQ(3, quic_config.GetInitialMaxStreamDataBytesIncomingBidirectionalToSend()); + EXPECT_EQ(quic::QuicTime::Delta::FromSeconds(30), quic_config.IdleNetworkTimeout()); EXPECT_EQ(2, quic_config.SendConnectionOptions().size()); EXPECT_EQ(2, quic_config.ClientRequestedIndependentOptions(quic::Perspective::IS_CLIENT).size()); std::string quic_copts = ""; diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 23037518759d..3a5d4e8ec5c8 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -276,6 +276,8 @@ envoy_cc_fuzz_test( size = "large", srcs = ["route_fuzz_test.cc"], corpus = ":route_corpus", + # The :config_impl_test_static target does not build with coverage + tags = ["nocoverage"], deps = [ ":route_fuzz_proto_cc_proto", "//source/common/router:config_lib", diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 32485d71500c..e867650b01e9 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -7570,9 +7570,10 @@ TEST_F(ConfigUtilityTest, ParseDirectResponseBody) { ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes).value()); route.mutable_direct_response()->mutable_body()->set_filename("missing_file"); - EXPECT_THROW_WITH_MESSAGE( - ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes).IgnoreError(), - EnvoyException, "file missing_file does not exist"); + EXPECT_EQ(ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes) + .status() + .message(), + "file missing_file does not exist"); // The default max body size in bytes is 4096 (MaxResponseBodySizeBytes). const std::string body(MaxResponseBodySizeBytes + 1, '*'); @@ -7587,10 +7588,10 @@ TEST_F(ConfigUtilityTest, ParseDirectResponseBody) { auto filename = TestEnvironment::writeStringToFileForTest("body", body); route.mutable_direct_response()->mutable_body()->set_filename(filename); expected_message = "file " + filename + " size is 4097 bytes; maximum is 2048"; - EXPECT_THROW_WITH_MESSAGE( - ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes / 2) - .IgnoreError(), - EnvoyException, expected_message); + EXPECT_EQ(ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes / 2) + .status() + .message(), + expected_message); // Update the max body size to 4098 bytes (MaxResponseBodySizeBytes + 2), hence the parsing is // successful. diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 1fce4783c62a..841e0380d39e 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -2737,6 +2737,69 @@ TEST_F(RouterTest, RetryRequestDuringBodyDataBetweenAttemptsNotEndStream) { EXPECT_TRUE(verifyHostUpstreamStats(1, 1)); } +// Test when the upstream request gets reset while the client is sending the body +// with more data arriving but not buffering any data. +TEST_F(RouterTest, UpstreamResetDuringBodyDataTransferNotBufferingNotEndStream) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request", "true"}}); + + Buffer::OwnedImpl decoding_buffer; + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(&decoding_buffer)); + EXPECT_CALL(callbacks_, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) { decoding_buffer.move(data); })); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-internal", "true"}, {"myheader", "present"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, false); + const std::string body1("body1"); + Buffer::OwnedImpl buf1(body1); + + // Send data while the upstream request is reset, should not have any failure. + encoder1.stream_.resetStream(Http::StreamResetReason::RemoteReset); + router_->decodeData(buf1, false); + + EXPECT_EQ(callbacks_.details(), "upstream_reset_before_response_started"); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + +// Test the original branch when local_reply_when_no_buffer_and_upstream_request runtime is false. +TEST_F(RouterTest, NormalPathUpstreamResetDuringBodyDataTransferNotBuffering) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request", + "false"}}); + + Buffer::OwnedImpl decoding_buffer; + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(&decoding_buffer)); + EXPECT_CALL(callbacks_, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) { decoding_buffer.move(data); })); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-internal", "true"}, {"myheader", "present"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, false); + + const std::string body1("body1"); + Buffer::OwnedImpl buf1(body1); + router_->decodeData(buf1, true); + EXPECT_EQ(1U, + callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); + + Http::ResponseHeaderMapPtr response_headers( + new Http::TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder->decodeHeaders(std::move(response_headers), true); + + EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); +} + // Test retrying a request, when the first attempt fails while the client // is sending the body, with the rest of the request arriving in between upstream // request attempts. diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 7ea7109e68e9..2cb819a4c02a 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -793,7 +793,7 @@ name: "encryption_key" const std::string secret_path = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/aes_128_key"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(secret_path)), - Config::DataSource::read(generic_secret.secret(), true, *api_)); + Config::DataSource::read(generic_secret.secret(), true, *api_).value()); } // Validate that SdsApi throws exception if an empty secret is passed to onConfigUpdate(). diff --git a/test/common/tcp/async_tcp_client_impl_test.cc b/test/common/tcp/async_tcp_client_impl_test.cc index 20b6a8d7abbd..a08a1e7576df 100644 --- a/test/common/tcp/async_tcp_client_impl_test.cc +++ b/test/common/tcp/async_tcp_client_impl_test.cc @@ -215,6 +215,19 @@ TEST_F(AsyncTcpClientImplTest, TestFailStats) { ->upstream_cx_connect_fail_.value()); } +TEST_F(AsyncTcpClientImplTest, TestFailWithReconnect) { + setUpClient(); + expectCreateConnection(false); + connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); + ASSERT_FALSE(client_->connected()); + connect_timer_ = new NiceMock(&dispatcher_); + EXPECT_EQ(1UL, cluster_manager_.thread_local_cluster_.cluster_.info_->traffic_stats_ + ->upstream_cx_connect_fail_.value()); + // Reconnect should success without the timer failure. + client_->setAsyncTcpClientCallbacks(callbacks_); + expectCreateConnection(true); +} + TEST_F(AsyncTcpClientImplTest, TestCxDestroyRemoteClose) { setUpClient(); expectCreateConnection(); diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index 7b27fecdf266..d48d9690c602 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -201,6 +201,28 @@ TEST_P(HttpUpstreamTest, UpstreamTrailersMarksDoneReading) { this->upstream_->responseDecoder().decodeTrailers(std::move(trailers)); } +TEST_P(HttpUpstreamTest, UpstreamTrailersPropagateFinDownstream) { + setupUpstream(); + EXPECT_CALL(encoder_.stream_, resetStream(_)).Times(0); + upstream_->doneWriting(); + EXPECT_CALL(callbacks_, onUpstreamData(BufferStringEqual(""), true)); + Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; + upstream_->responseDecoder().decodeTrailers(std::move(trailers)); +} + +TEST_P(HttpUpstreamTest, UpstreamTrailersDontPropagateFinDownstreamWhenFeatureDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers", + "false"}}); + setupUpstream(); + EXPECT_CALL(encoder_.stream_, resetStream(_)).Times(0); + upstream_->doneWriting(); + EXPECT_CALL(callbacks_, onUpstreamData(_, _)).Times(0); + Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; + upstream_->responseDecoder().decodeTrailers(std::move(trailers)); +} + class HttpUpstreamRequestEncoderTest : public testing::TestWithParam { public: HttpUpstreamRequestEncoderTest() { diff --git a/test/extensions/access_loggers/common/grpc_access_logger_test.cc b/test/extensions/access_loggers/common/grpc_access_logger_test.cc index ad9f890b76ad..0c151f01192d 100644 --- a/test/extensions/access_loggers/common/grpc_access_logger_test.cc +++ b/test/extensions/access_loggers/common/grpc_access_logger_test.cc @@ -484,6 +484,7 @@ class MockGrpcAccessLoggerCache createLogger(const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, Event::Dispatcher& dispatcher) override { auto client = async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true) + .value() ->createUncachedRawAsyncClient(); return std::make_shared(std::move(client), config, dispatcher, scope_, "mock_access_log_prefix.", diff --git a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc index 0dec36a101b0..65e31acc9947 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc @@ -1386,6 +1386,24 @@ TEST_F(NullGrpcMuxImplTest, OnDiscoveryResponseImplemented) { EXPECT_NO_THROW(null_mux_.onDiscoveryResponse(std::move(response), cp_stats)); } +TEST(GrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = + Config::Utility::getFactoryByName("envoy.config_mux.grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc index 9888a26b6e45..fe04f30f7a8a 100644 --- a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc @@ -1,5 +1,6 @@ #include +#include "envoy/common/exception.h" #include "envoy/config/endpoint/v3/endpoint.pb.h" #include "envoy/config/endpoint/v3/endpoint.pb.validate.h" #include "envoy/config/xds_config_tracker.h" @@ -23,6 +24,7 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/runtime/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/resources.h" #include "test/test_common/simulated_time_system.h" @@ -763,6 +765,24 @@ TEST_P(NewGrpcMuxImplTest, AddRemoveSubscriptions) { } } +TEST(NewGrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = Config::Utility::getFactoryByName( + "envoy.config_mux.new_grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc index 819c55381c20..ec78e9d4bed4 100644 --- a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc @@ -1300,6 +1300,42 @@ TEST_F(NullGrpcMuxImplTest, AddWatchRaisesException) { TEST_F(NullGrpcMuxImplTest, NoEdsResourcesCache) { EXPECT_EQ({}, null_mux_->edsResourcesCache()); } +TEST(UnifiedSotwGrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = Config::Utility::getFactoryByName( + "envoy.config_mux.sotw_grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + +TEST(UnifiedDeltaGrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = Config::Utility::getFactoryByName( + "envoy.config_mux.delta_grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + } // namespace } // namespace XdsMux } // namespace Config diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index c9d716c2dab2..62909863a5ec 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -35,8 +35,8 @@ class TestAsyncClientManagerImpl : public Grpc::AsyncClientManagerImpl { const Grpc::StatNames& stat_names, const Bootstrap::GrpcAsyncClientManagerConfig& config) : Grpc::AsyncClientManagerImpl(cm, tls, time_source, api, stat_names, config) {} - Grpc::AsyncClientFactoryPtr factoryForGrpcService(const envoy::config::core::v3::GrpcService&, - Stats::Scope&, bool) override { + absl::StatusOr + factoryForGrpcService(const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) override { return std::make_unique>(); } }; @@ -211,8 +211,9 @@ class ExtAuthzFilterGrpcTest : public ExtAuthzFilterTest { Envoy::Grpc::GrpcServiceConfigWithHashKey config_with_hash_key = Envoy::Grpc::GrpcServiceConfigWithHashKey(ext_authz_config.grpc_service()); Grpc::RawAsyncClientSharedPtr async_client = - async_client_manager_->getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, - context_.scope(), false); + async_client_manager_ + ->getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context_.scope(), false) + .value(); Grpc::MockAsyncClient* mock_async_client = dynamic_cast(async_client.get()); EXPECT_NE(mock_async_client, nullptr); diff --git a/test/extensions/filters/http/ext_proc/BUILD b/test/extensions/filters/http/ext_proc/BUILD index 5aa0cc570d4c..48cbd6ab6112 100644 --- a/test/extensions/filters/http/ext_proc/BUILD +++ b/test/extensions/filters/http/ext_proc/BUILD @@ -160,6 +160,7 @@ envoy_extension_cc_test( "//test/proto:helloworld_proto_cc_proto", "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/set_metadata/v3:pkg_cc_proto", "@envoy_api//envoy/service/ext_proc/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 6b295a0d0399..ea9e8337beeb 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -1,6 +1,7 @@ #include #include +#include "envoy/config/core/v3/base.pb.h" #include "envoy/extensions/filters/http/ext_proc/v3/ext_proc.pb.h" #include "envoy/extensions/filters/http/set_metadata/v3/set_metadata.pb.h" #include "envoy/network/address.h" @@ -314,7 +315,6 @@ class ExtProcIntegrationTest : public HttpIntegrationTest, ASSERT_TRUE(processor_connection_->waitForNewStream(*dispatcher_, processor_stream_)); } ASSERT_TRUE(processor_stream_->waitForGrpcMessage(*dispatcher_, request)); - ASSERT_TRUE(request.has_request_headers()); if (first_message) { processor_stream_->startGrpcStream(); } @@ -3452,45 +3452,82 @@ TEST_P(ExtProcIntegrationTest, SendAndReceiveDynamicMetadata) { } #if defined(USE_CEL_PARSER) -// Test the filter using the default configuration by connecting to -// an ext_proc server that responds to the request_headers message -// by requesting to modify the request headers. -TEST_P(ExtProcIntegrationTest, GetAndSetRequestResponseAttributes) { +TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SEND); proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); proto_config_.mutable_request_attributes()->Add("request.path"); proto_config_.mutable_request_attributes()->Add("request.method"); proto_config_.mutable_request_attributes()->Add("request.scheme"); proto_config_.mutable_request_attributes()->Add("connection.mtls"); + proto_config_.mutable_request_attributes()->Add("response.code"); proto_config_.mutable_response_attributes()->Add("response.code"); proto_config_.mutable_response_attributes()->Add("response.code_details"); initializeConfig(); HttpIntegrationTest::initialize(); auto response = sendDownstreamRequest(absl::nullopt); - processRequestHeadersMessage( - *grpc_upstreams_[0], true, [](const HttpHeaders& req, HeadersResponse&) { + + // Handle request headers message. + processGenericMessage( + *grpc_upstreams_[0], true, [](const ProcessingRequest& req, ProcessingResponse& resp) { + // Add something to the response so the message isn't seen as spurious + envoy::service::ext_proc::v3::HeadersResponse headers_resp; + *(resp.mutable_request_headers()) = headers_resp; + + EXPECT_TRUE(req.has_request_headers()); EXPECT_EQ(req.attributes().size(), 1); auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); EXPECT_EQ(proto_struct.fields().at("request.path").string_value(), "/"); EXPECT_EQ(proto_struct.fields().at("request.method").string_value(), "GET"); EXPECT_EQ(proto_struct.fields().at("request.scheme").string_value(), "http"); EXPECT_EQ(proto_struct.fields().at("connection.mtls").bool_value(), false); + // Make sure we did not include the attribute which was not yet available. + EXPECT_EQ(proto_struct.fields().size(), 4); + EXPECT_FALSE(proto_struct.fields().contains("response.code")); + + // Make sure we are not including any data in the deprecated HttpHeaders.attributes. + EXPECT_TRUE(req.request_headers().attributes().empty()); return true; }); - handleUpstreamRequest(); + handleUpstreamRequestWithTrailer(); - processResponseHeadersMessage( - *grpc_upstreams_[0], false, [](const HttpHeaders& req, HeadersResponse&) { + // Handle response headers message. + processGenericMessage( + *grpc_upstreams_[0], false, [](const ProcessingRequest& req, ProcessingResponse& resp) { + // Add something to the response so the message isn't seen as spurious + envoy::service::ext_proc::v3::HeadersResponse headers_resp; + *(resp.mutable_response_headers()) = headers_resp; + + EXPECT_TRUE(req.has_response_headers()); EXPECT_EQ(req.attributes().size(), 1); auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); EXPECT_EQ(proto_struct.fields().at("response.code").string_value(), "200"); EXPECT_EQ(proto_struct.fields().at("response.code_details").string_value(), StreamInfo::ResponseCodeDetails::get().ViaUpstream); + + // Make sure we didn't include request attributes in the response-path processing request. + EXPECT_FALSE(proto_struct.fields().contains("request.method")); + + // Make sure we are not including any data in the deprecated HttpHeaders.attributes. + EXPECT_TRUE(req.response_headers().attributes().empty()); return true; }); + // Handle response trailers message, making sure we did not send request or response attributes + // again. + processGenericMessage(*grpc_upstreams_[0], false, + [](const ProcessingRequest& req, ProcessingResponse& resp) { + // Add something to the response so the message isn't seen as spurious + envoy::service::ext_proc::v3::TrailersResponse trailer_resp; + *(resp.mutable_response_trailers()) = trailer_resp; + + EXPECT_TRUE(req.has_response_trailers()); + EXPECT_TRUE(req.attributes().empty()); + return true; + }); + verifyDownstreamResponse(*response, 200); } #endif diff --git a/test/extensions/filters/http/jwt_authn/extractor_test.cc b/test/extensions/filters/http/jwt_authn/extractor_test.cc index b639a0701619..3cad76fe54a9 100644 --- a/test/extensions/filters/http/jwt_authn/extractor_test.cc +++ b/test/extensions/filters/http/jwt_authn/extractor_test.cc @@ -143,6 +143,30 @@ TEST_F(ExtractorTest, TestDefaultHeaderLocationWithValidJWT) { EXPECT_TRUE(tokens[0]->isIssuerAllowed("issuer1")); } +TEST_F(ExtractorTest, TestDuplicatedHeadersWithDuplicatedTokenPrefixes) { + auto headers = TestRequestHeaderMapImpl{{"Authorization", absl::StrCat("Bearer ", GoodToken)}, + {"Authorization", absl::StrCat("Bearer ", GoodToken)}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 2); + + // Only the issue1 is using default header location. + EXPECT_EQ(tokens[0]->token(), GoodToken); + EXPECT_TRUE(tokens[0]->isIssuerAllowed("issuer1")); + EXPECT_EQ(tokens[1]->token(), GoodToken); + EXPECT_TRUE(tokens[1]->isIssuerAllowed("issuer1")); +} + +TEST_F(ExtractorTest, TestDuplicatedHeadersWithUniqueTokenPrefixes) { + auto headers = TestRequestHeaderMapImpl{{"Authorization", absl::StrCat("Bearer ", GoodToken)}, + {"Authorization", absl::StrCat("Basic ", "basic")}}; + auto tokens = extractor_->extract(headers); + EXPECT_EQ(tokens.size(), 1); + + // Only the issue1 is using default header location. + EXPECT_EQ(tokens[0]->token(), GoodToken); + EXPECT_TRUE(tokens[0]->isIssuerAllowed("issuer1")); +} + // Test extracting JWT as Bearer token from the default header location: "Authorization" - // using an actual (correctly-formatted) JWT but token is invalid, like: GoodToken + // chars_after_space expected to get all token include characters after the space: @@ -203,12 +227,13 @@ TEST_F(ExtractorTest, TestCustomHeaderToken) { EXPECT_FALSE(headers.has(Http::LowerCaseString("token-header"))); } -// Make sure a double custom header concatenates the token +// Make sure a double custom header does not concatenate the token TEST_F(ExtractorTest, TestDoubleCustomHeaderToken) { auto headers = TestRequestHeaderMapImpl{{"token-header", "jwt_token"}, {"token-header", "foo"}}; auto tokens = extractor_->extract(headers); - EXPECT_EQ(tokens.size(), 1); - EXPECT_EQ(tokens[0]->token(), "jwt_token,foo"); + EXPECT_EQ(tokens.size(), 2); + EXPECT_EQ(tokens[0]->token(), "jwt_token"); + EXPECT_EQ(tokens[1]->token(), "foo"); } // Test extracting token from the custom header: "prefix-header" diff --git a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc index b433468d170a..7a24ed7416ba 100644 --- a/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_splitter_impl_test.cc @@ -406,6 +406,22 @@ TEST_F(RedisSingleServerRequestTest, PingSuccess) { EXPECT_EQ(nullptr, handle_); }; +TEST_F(RedisSingleServerRequestTest, EchoSuccess) { + InSequence s; + + Common::Redis::RespValuePtr request{new Common::Redis::RespValue()}; + makeBulkStringArray(*request, {"echo", "foobar"}); + + Common::Redis::RespValue response; + response.type(Common::Redis::RespType::BulkString); + response.asString() = "foobar"; + + EXPECT_CALL(callbacks_, connectionAllowed()).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, onResponse_(PointeesEq(&response))); + handle_ = splitter_.makeRequest(std::move(request), callbacks_, dispatcher_, stream_info_); + EXPECT_EQ(nullptr, handle_); +}; + TEST_F(RedisSingleServerRequestTest, Time) { InSequence s; diff --git a/test/extensions/http/stateful_session/cookie/cookie_test.cc b/test/extensions/http/stateful_session/cookie/cookie_test.cc index 866edd4fd84a..34f3a04b3ca0 100644 --- a/test/extensions/http/stateful_session/cookie/cookie_test.cc +++ b/test/extensions/http/stateful_session/cookie/cookie_test.cc @@ -52,7 +52,7 @@ TEST(CookieBasedSessionStateFactoryTest, SessionStateTest) { if (use_proto) { envoy::Cookie cookie; cookie.set_address("1.2.3.4:80"); - cookie.set_expires(1000); + // The expiration field is not set in the cookie because TTL is 0 in the config. cookie.SerializeToString(&cookie_content); } else { cookie_content = "1.2.3.4:80"; @@ -176,13 +176,13 @@ TEST(CookieBasedSessionStateFactoryTest, SessionStateProtoCookie) { EXPECT_EQ(absl::nullopt, session_state->upstreamAddress()); // PROTO format - no "expired field" - cookie.set_expires(0); + cookie.clear_expires(); cookie.SerializeToString(&cookie_content); request_headers = {{":path", "/path"}, {"cookie", "override_host=" + Envoy::Base64::encode(cookie_content.c_str(), cookie_content.length())}}; session_state = factory.create(request_headers); - EXPECT_EQ(absl::nullopt, session_state->upstreamAddress()); + EXPECT_EQ("2.3.4.5:80", session_state->upstreamAddress().value()); // PROTO format - pass incorrect format. // The content should be treated as "old" style encoding. diff --git a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc index 7242c9f89cd6..b2fa4afe9659 100644 --- a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc +++ b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc @@ -104,7 +104,7 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, GlobalLimitSetViaRuntimeKeyAndOve config_helper_.addRuntimeOverride("overload.global_downstream_max_connections", "3"); initializeOverloadManager(2); const std::string log_line = - "Global downstream connections limits is configured via runtime key " + "Global downstream connections limits is configured via deprecated runtime key " "overload.global_downstream_max_connections and in " "envoy.resource_monitors.global_downstream_max_connections. Using overload manager " "config."; diff --git a/test/integration/cx_limit_integration_test.cc b/test/integration/cx_limit_integration_test.cc index c998727d01b9..ccf2b19dc1c6 100644 --- a/test/integration/cx_limit_integration_test.cc +++ b/test/integration/cx_limit_integration_test.cc @@ -136,6 +136,18 @@ TEST_P(ConnectionLimitIntegrationTest, TestListenerLimit) { doTest(init_func, "downstream_cx_overflow"); } +TEST_P(ConnectionLimitIntegrationTest, TestDeprecationWarningForGlobalCxRuntimeLimit) { + std::function init_func = [this]() { + setGlobalLimit(4); + initialize(); + }; + const std::string log_line = + "Usage of the deprecated runtime key overload.global_downstream_max_connections, " + "consider switching to `envoy.resource_monitors.downstream_connections` instead." + "This runtime key will be removed in future."; + EXPECT_LOG_CONTAINS("warn", log_line, { init_func(); }); +} + // TODO (nezdolik) move this test to overload manager test suite, once runtime key is fully // deprecated. TEST_P(ConnectionLimitIntegrationTest, TestEmptyGlobalCxRuntimeLimit) { diff --git a/test/integration/filters/server_factory_context_filter.cc b/test/integration/filters/server_factory_context_filter.cc index cea3f7ee5457..610df7941993 100644 --- a/test/integration/filters/server_factory_context_filter.cc +++ b/test/integration/filters/server_factory_context_filter.cc @@ -29,8 +29,10 @@ class TestGrpcClient : public Grpc::AsyncStreamCallbacks public: TestGrpcClient(Server::Configuration::ServerFactoryContext& context, const envoy::config::core::v3::GrpcService& grpc_service) - : client_(context.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, context.scope(), true)), + : client_(context.clusterManager() + .grpcAsyncClientManager() + .getOrCreateRawAsyncClient(grpc_service, context.scope(), true) + .value()), method_descriptor_(helloworld::Greeter::descriptor()->FindMethodByName("SayHello")) {} // AsyncStreamCallbacks diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index fdd978ebde06..d83e2e395ef2 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1945,6 +1945,42 @@ class Http2FrameIntegrationTest : public testing::TestWithParam void { hcm.set_proxy_100_continue(true); }); + beginSession(); + FakeRawConnectionPtr fake_upstream_connection; + + // Start a request and wait for it to reach the upstream. + sendFrame(Http2Frame::makeRequest(1, "host", "/path/to/long/url")); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); + ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); + + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + + // A malformed frame is translated to 103 header with END_STREAM by the underlying codec. + // Typically we should get a protocol error, but this should not crash Envoy. + // PAYLOAD_LENGTH: \x05 + // FRAME_TYPE: \x01 + // FLAGS: \x32 + // STREAM_ID: \x01 + // ASCII: \x31, \x30, \x33 for 1, 0, 3 respectively + const std::vector header_frame = { + 0x00, 0x00, 0x05, 0x01, 0x32, 0x00, 0x00, 0x00, 0x01, 0x2d, 0xfe, 0xff, 0x01, 0x10, + 0x00, 0x00, 0x05, 0x09, 0x0d, 0x00, 0x00, 0x00, 0x01, 0x09, 0x03, 0x31, 0x30, 0x33}; + const std::string header_frame_str(reinterpret_cast(header_frame.data()), + header_frame.size()); + ASSERT_TRUE(fake_upstream_connection->write(header_frame_str)); + + const Http2Frame response = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, response.type()); + + tcp_client_->close(); + test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); +} + TEST_P(Http2FrameIntegrationTest, MaxConcurrentStreamsIsRespected) { const int kTotalRequests = 101; config_helper_.addConfigModifier( diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 03bc51ff801d..f4e305689202 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -809,6 +809,52 @@ TEST_P(DownstreamProtocolIntegrationTest, TeSanitization) { EXPECT_EQ("", upstream_headers->getTEValue()); } +TEST_P(DownstreamProtocolIntegrationTest, TeSanitizationTrailers) { + if (downstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("envoy.reloadable_features.sanitize_te", "true"); + + default_request_headers_.setTE("trailers"); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + auto upstream_headers = + reinterpret_cast(fake_upstreams_[0].get())->lastRequestHeaders(); + EXPECT_TRUE(upstream_headers != nullptr); + EXPECT_EQ("trailers", upstream_headers->getTEValue()); +} + +TEST_P(DownstreamProtocolIntegrationTest, TeSanitizationTrailersMultipleValuesAndWeigthted) { + if (downstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride("envoy.reloadable_features.sanitize_te", "true"); + + default_request_headers_.setTE("chunked;q=0.8 , trailers ,deflate "); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + auto upstream_headers = + reinterpret_cast(fake_upstreams_[0].get())->lastRequestHeaders(); + EXPECT_TRUE(upstream_headers != nullptr); + EXPECT_EQ("trailers", upstream_headers->getTEValue()); +} + // Regression test for https://github.com/envoyproxy/envoy/issues/10270 TEST_P(ProtocolIntegrationTest, LongHeaderValueWithSpaces) { // Header with at least 20kb of spaces surrounded by non-whitespace characters to ensure that diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 21cdffc5657a..a4460d7f6add 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -1838,9 +1838,6 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddress) { }); initialize(); - quic::QuicTagVector connection_options{quic::kSPAD}; - dynamic_cast(*quic_connection_persistent_info_) - .quic_config_.SetConnectionOptionsToSend(connection_options); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); EnvoyQuicClientSession* quic_session = static_cast(codec_client_->connection()); @@ -1871,6 +1868,65 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddress) { } } +TEST_P(QuicHttpIntegrationTest, PreferredAddressRuntimeFlag) { + autonomous_upstream_ = true; + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.quic_send_server_preferred_address_to_all_clients", "false"); + config_helper_.addConfigModifier([=](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listen_address = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_address() + ->mutable_socket_address(); + // Change listening address to Any. + listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); + auto* preferred_address_config = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_server_preferred_address_config(); + // Configure a loopback interface as the server's preferred address. + preferred_address_config->set_name("quic.server_preferred_address.fixed"); + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig + server_preferred_address; + server_preferred_address.set_ipv4_address("127.0.0.2"); + server_preferred_address.set_ipv6_address("::2"); + preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); + + // Configure a test listener filter which is incompatible with any server preferred addresses + // but with any matcher, which effectively disables the filter. + auto* listener_filter = + bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); + listener_filter->set_name("dumb_filter"); + auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); + configuration.set_added_value("foo"); + configuration.set_allow_server_migration(false); + configuration.set_allow_client_migration(false); + listener_filter->mutable_typed_config()->PackFrom(configuration); + listener_filter->mutable_filter_disabled()->set_any_match(true); + }); + + initialize(); + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + EnvoyQuicClientSession* quic_session = + static_cast(codec_client_->connection()); + EXPECT_EQ(Network::Test::getLoopbackAddressString(version_), + quic_connection_->peer_address().host().ToString()); + EXPECT_TRUE(!quic_session->config()->HasReceivedIPv4AlternateServerAddress() && + !quic_session->config()->HasReceivedIPv6AlternateServerAddress()); + ASSERT_TRUE(quic_connection_->waitForHandshakeDone()); + EXPECT_FALSE(quic_connection_->IsValidatingServerPreferredAddress()); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/test/long/url"}, + {":authority", "sni.lyft.com"}, + {":scheme", "http"}, + {AutonomousStream::RESPONSE_SIZE_BYTES, std::to_string(1024 * 1024)}}; + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(default_request_headers_); + EXPECT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); +} + TEST_P(QuicHttpIntegrationTest, UsesPreferredAddressDualStack) { if (!(TestEnvironment::shouldRunTestForIpVersion(Network::Address::IpVersion::v6) && version_ == Network::Address::IpVersion::v4)) { @@ -1901,9 +1957,6 @@ TEST_P(QuicHttpIntegrationTest, UsesPreferredAddressDualStack) { }); initialize(); - quic::QuicTagVector connection_options{quic::kSPAD}; - dynamic_cast(*quic_connection_persistent_info_) - .quic_config_.SetConnectionOptionsToSend(connection_options); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); EnvoyQuicClientSession* quic_session = static_cast(codec_client_->connection()); @@ -1971,9 +2024,6 @@ TEST_P(QuicHttpIntegrationTest, PreferredAddressDroppedByIncompatibleListenerFil }); initialize(); - quic::QuicTagVector connection_options{quic::kSPAD}; - dynamic_cast(*quic_connection_persistent_info_) - .quic_config_.SetConnectionOptionsToSend(connection_options); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); EnvoyQuicClientSession* quic_session = static_cast(codec_client_->connection()); diff --git a/test/integration/sds_generic_secret_integration_test.cc b/test/integration/sds_generic_secret_integration_test.cc index 2d1b3aed08a9..0bd26ff6c3d2 100644 --- a/test/integration/sds_generic_secret_integration_test.cc +++ b/test/integration/sds_generic_secret_integration_test.cc @@ -31,8 +31,9 @@ class SdsGenericSecretTestFilter : public Http::StreamDecoderFilter { // Http::StreamDecoderFilter Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override { - headers.addCopy(Http::LowerCaseString("secret"), - Config::DataSource::read(config_provider_->secret()->secret(), true, api_)); + headers.addCopy( + Http::LowerCaseString("secret"), + Config::DataSource::read(config_provider_->secret()->secret(), true, api_).value()); return Http::FilterHeadersStatus::Continue; } diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index 5e0a5a37d664..023121aeef5f 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -1313,6 +1313,34 @@ TEST_P(TcpTunnelingIntegrationTest, CopyResponseTrailers) { EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr(trailer_value)); } +TEST_P(TcpTunnelingIntegrationTest, DownstreamFinOnUpstreamTrailers) { + if (upstreamProtocol() == Http::CodecType::HTTP1) { + return; + } + + initialize(); + + tcp_client_ = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + + upstream_request_->encodeHeaders(default_response_headers_, false); + sendBidiData(fake_upstream_connection_); + + // Send trailers + const std::string trailer_value = "trailer-value"; + Http::TestResponseTrailerMapImpl response_trailers{{"test-trailer-name", trailer_value}}; + upstream_request_->encodeTrailers(response_trailers); + + // Upstream trailers should close the downstream connection for writing. + tcp_client_->waitForHalfClose(); + + // Close Connection + tcp_client_->close(); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); +} + TEST_P(TcpTunnelingIntegrationTest, CloseUpstreamFirst) { initialize(); diff --git a/test/mocks/grpc/mocks.cc b/test/mocks/grpc/mocks.cc index 122563cf3340..c375a51226c7 100644 --- a/test/mocks/grpc/mocks.cc +++ b/test/mocks/grpc/mocks.cc @@ -39,6 +39,7 @@ MockAsyncClientFactory::MockAsyncClientFactory() { MockAsyncClientFactory::~MockAsyncClientFactory() = default; MockAsyncClientManager::MockAsyncClientManager() { + ON_CALL(*this, getOrCreateRawAsyncClient(_, _, _)).WillByDefault(Return(nullptr)); ON_CALL(*this, factoryForGrpcService(_, _, _)) .WillByDefault(Invoke([](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { return std::make_unique>(); diff --git a/test/mocks/grpc/mocks.h b/test/mocks/grpc/mocks.h index 920ccb7aa7d7..d0126823e671 100644 --- a/test/mocks/grpc/mocks.h +++ b/test/mocks/grpc/mocks.h @@ -111,15 +111,15 @@ class MockAsyncClientManager : public AsyncClientManager { MockAsyncClientManager(); ~MockAsyncClientManager() override; - MOCK_METHOD(AsyncClientFactoryPtr, factoryForGrpcService, + MOCK_METHOD(absl::StatusOr, factoryForGrpcService, (const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check)); - MOCK_METHOD(RawAsyncClientSharedPtr, getOrCreateRawAsyncClient, + MOCK_METHOD(absl::StatusOr, getOrCreateRawAsyncClient, (const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check)); - MOCK_METHOD(RawAsyncClientSharedPtr, getOrCreateRawAsyncClientWithHashKey, + MOCK_METHOD(absl::StatusOr, getOrCreateRawAsyncClientWithHashKey, (const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, bool skip_cluster_check)); }; diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index 0b166849e32a..c3c8518ac152 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -20,6 +20,10 @@ class Matcher : public internal::MatcherBase { public: explicit Matcher() = default; + + template ::type::is_gtest_matcher> + Matcher(M&& m) : internal::MatcherBase(std::forward(m)) {} + Matcher(Envoy::StreamInfo::ResponseFlag value) { *this = Eq(value); } Matcher(Envoy::StreamInfo::CoreResponseFlag value) { *this = Eq(Envoy::StreamInfo::ResponseFlag(value)); diff --git a/test/server/server_corpus/rate_limit_nan b/test/server/server_corpus/rate_limit_nan new file mode 100644 index 000000000000..4c9b4bb59ec5 --- /dev/null +++ b/test/server/server_corpus/rate_limit_nan @@ -0,0 +1,44 @@ +node { + id: "\016\000\317@" + cluster: "@" +} +dynamic_resources { + ads_config { + api_type: DELTA_GRPC + grpc_services { + google_grpc { + target_uri: "7.0z1" + stat_prefix: "Ny" + } + } + rate_limit_settings { + max_tokens { + } + fill_rate { + value: nan + } + } + transport_api_version: V3 + } +} +layered_runtime { + layers { + name: "0" + rtds_layer { + name: "1=" + rtds_config { + ads { + } + initial_fetch_timeout { + seconds: 9472 + } + } + } + } + layers { + name: "q" + static_layer { + } + } +} +default_socket_interface: "#" diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 268f131abcaf..9176cb67f791 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -419,39 +419,39 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==42.0.2 \ - --hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \ - --hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \ - --hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \ - --hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \ - --hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \ - --hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \ - --hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \ - --hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \ - --hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \ - --hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \ - --hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \ - --hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \ - --hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \ - --hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \ - --hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \ - --hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \ - --hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \ - --hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \ - --hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \ - --hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \ - --hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \ - --hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \ - --hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \ - --hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \ - --hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \ - --hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \ - --hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \ - --hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \ - --hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \ - --hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \ - --hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \ - --hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f +cryptography==42.0.3 \ + --hash=sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129 \ + --hash=sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe \ + --hash=sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20 \ + --hash=sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec \ + --hash=sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3 \ + --hash=sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd \ + --hash=sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5 \ + --hash=sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b \ + --hash=sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46 \ + --hash=sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504 \ + --hash=sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306 \ + --hash=sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead \ + --hash=sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e \ + --hash=sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938 \ + --hash=sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a \ + --hash=sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b \ + --hash=sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a \ + --hash=sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd \ + --hash=sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54 \ + --hash=sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c \ + --hash=sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857 \ + --hash=sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f \ + --hash=sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f \ + --hash=sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef \ + --hash=sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c \ + --hash=sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548 \ + --hash=sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65 \ + --hash=sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4 \ + --hash=sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4 \ + --hash=sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a \ + --hash=sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151 \ + --hash=sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396 # via # -r requirements.in # aioquic @@ -689,9 +689,9 @@ gitdb==4.0.11 \ --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b # via gitpython -gitpython==3.1.41 \ - --hash=sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c \ - --hash=sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048 +gitpython==3.1.42 \ + --hash=sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd \ + --hash=sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb # via -r requirements.in google-apitools==0.5.32 \ --hash=sha256:b78f74116558e0476e19501b5b4b2ac7c93261a69c5449c861ea95cbc853c688 \ @@ -700,7 +700,9 @@ google-apitools==0.5.32 \ google-auth[aiohttp]==2.27.0 \ --hash=sha256:8e4bad367015430ff253fe49d500fdc3396c1a434db5740828c728e45bcce245 \ --hash=sha256:e863a56ccc2d8efa83df7a80272601e43487fa9a728a376205c86c26aaefa821 - # via gsutil + # via + # google-auth + # gsutil google-reauth==0.1.1 \ --hash=sha256:cb39074488d74c8853074dde47368bbf8f739d4a4338b89aab696c895b6d8368 \ --hash=sha256:f9f6852a55c2c5453d581cd01f3d1278e86147c03d008409800390a834235892 @@ -919,57 +921,57 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.13 \ - --hash=sha256:031df1026c7ea8303332d78711f180231e3ae8b564271fb748a03926587c5546 \ - --hash=sha256:0d3ba9d88e20765335260d7b25547d7c571eee2b698200f97afa7d8c7cd668fc \ - --hash=sha256:0d691c44604941945b00e0a13b19a7d9c1a19511abadf0080f373e98fdeb6b31 \ - --hash=sha256:0fd9a2101d04e85086ea6198786a3f016e45475f800712e6833e14bf9ce2832f \ - --hash=sha256:16946d095212a3dec552572c5d9bca7afa40f3116ad49695a397be07d529f1fa \ - --hash=sha256:1ab9dbdec3f13f3ea6f937564ce21651844cfbf2725099f2f490426acf683c23 \ - --hash=sha256:23f21faf072ed3b60b5954686f98157e073f6a8068eaa58dbde83e87212eda84 \ - --hash=sha256:266e55c83f81248f63cc93d11c5e3a53df49a5d2598fa9e9db5f99837a802d5d \ - --hash=sha256:2cc03a35bfc71c8ebf96ce49b82c2a7be6af4b3cd3ac34166fdb42ac510bbfff \ - --hash=sha256:2f37f0cdd026ef777a4336e599d8194c8357fc14760c2a5ddcfdf1965d45504b \ - --hash=sha256:31372ba3a9fe8ad118e7d22fba46bbc18e89039e3bfa89db7bc8c18ee722dca8 \ - --hash=sha256:31fb66b41fb2c4c817d9610f0bc7d31345728d7b5295ac78b63603407432a2b2 \ - --hash=sha256:3869d65561f10071d3e7f35ae58fd377056f67d7aaed5222f318390c3ad30339 \ - --hash=sha256:3deadd8dc0e9ff844b5b656fa30a48dbee1c3b332d8278302dd9637f6b09f627 \ - --hash=sha256:43fd6036b16bb6742d03dae62f7bdf8214d06dea47e4353cde7e2bd1358d186f \ - --hash=sha256:446d9ad04204e79229ae19502daeea56479e55cbc32634655d886f5a39e91b44 \ - --hash=sha256:4584e8eb727bc431baaf1bf97e35a1d8a0109c924ec847395673dfd5f4ef6d6f \ - --hash=sha256:49b7e3fe861cb246361825d1a238f2584ed8ea21e714bf6bb17cebb86772e61c \ - --hash=sha256:5b98cd948372f0eb219bc309dee4633db1278687161e3280d9e693b6076951d2 \ - --hash=sha256:5ef58869f3399acbbe013518d8b374ee9558659eef14bca0984f67cb1fbd3c37 \ - --hash=sha256:60da7316131185d0110a1848e9ad15311e6c8938ee0b5be8cbd7261e1d80ee8f \ - --hash=sha256:62e9a99879c4d5a04926ac2518a992134bfa00d546ea5a4cae4b9be454d35a22 \ - --hash=sha256:63ef57a53bfc2091a7cd50a640d9ae866bd7d92a5225a1bab6baa60ef62583f2 \ - --hash=sha256:6e47153db080f5e87e8ba638f1a8b18995eede6b0abb93964d58cf11bcea362f \ - --hash=sha256:730385fdb99a21fce9bb84bb7fcbda72c88626facd74956bda712834b480729d \ - --hash=sha256:7ccd5bd222e5041069ad9d9868ab59e6dbc53ecde8d8c82b919954fbba43b46b \ - --hash=sha256:7e8e4a571d958910272af8d53a9cbe6599f9f5fd496a1bc51211183bb2072cbd \ - --hash=sha256:811ac076855e33e931549340288e0761873baf29276ad00f221709933c644330 \ - --hash=sha256:828c502bb261588f7de897e06cb23c4b122997cb039d2014cb78e7dabe92ef0c \ - --hash=sha256:838b898e8c1f26eb6b8d81b180981273f6f5110c76c22c384979aca854194f1b \ - --hash=sha256:860d0f5b42d0c0afd73fa4177709f6e1b966ba691fcd72175affa902052a81d6 \ - --hash=sha256:8a730bf07feacb0863974e67b206b7c503a62199de1cece2eb0d4c233ec29c11 \ - --hash=sha256:9156b96afa38db71344522f5517077eaedf62fcd2c9148392ff93d801128809c \ - --hash=sha256:9171e8e1a1f221953e38e84ae0abffe8759002fd8968106ee379febbb5358b33 \ - --hash=sha256:978117122ca4cc59b28af5322253017f6c5fc03dbdda78c7f4b94ae984c8dd43 \ - --hash=sha256:9b1b5adc5adf596c59dca57156b71ad301d73956f5bab4039b0e34dbf50b9fa0 \ - --hash=sha256:9bcf56efdb83244cde070e82a69c0f03c47c235f0a5cb6c81d9da23af7fbaae4 \ - --hash=sha256:a8c83718346de08d68b3cb1105c5d91e5fc39885d8610fdda16613d4e3941459 \ - --hash=sha256:ae77275a28667d9c82d4522b681504642055efa0368d73108511647c6499b31c \ - --hash=sha256:b57c0954a9fdd2b05b9cec0f5a12a0bdce5bf021a5b3b09323041613972481ab \ - --hash=sha256:b812417199eeb169c25f67815cfb66fd8de7ff098bf57d065e8c1943a7ba5c8f \ - --hash=sha256:cfad553a36548262e7da0f3a7464270e13900b898800fb571a5d4b298c3f8356 \ - --hash=sha256:d3222db9df629ef3c3673124f2e05fb72bc4a320c117e953fec0d69dde82e36d \ - --hash=sha256:d714595d81efab11b42bccd119977d94b25d12d3a806851ff6bfd286a4bce960 \ - --hash=sha256:d92a3e835a5100f1d5b566fff79217eab92223ca31900dba733902a182a35ab0 \ - --hash=sha256:ddc089315d030c54f0f03fb38286e2667c05009a78d659f108a8efcfbdf2e585 \ - --hash=sha256:e3b0c4da61f39899561e08e571f54472a09fa71717d9797928af558175ae5243 \ - --hash=sha256:eaaf80957c38e9d3f796f355a80fad945e72cd745e6b64c210e635b7043b673e \ - --hash=sha256:fa6b67f8bef277c2a4aadd548d58796854e7d760964126c3209b19bccc6a74f1 \ - --hash=sha256:fc6bc65b0cf524ee042e0bc2912b9206ef242edfba7426cf95763e4af01f527a +orjson==3.9.14 \ + --hash=sha256:0572f174f50b673b7df78680fb52cd0087a8585a6d06d295a5f790568e1064c6 \ + --hash=sha256:06fb40f8e49088ecaa02f1162581d39e2cf3fd9dbbfe411eb2284147c99bad79 \ + --hash=sha256:08e722a8d06b13b67a51f247a24938d1a94b4b3862e40e0eef3b2e98c99cd04c \ + --hash=sha256:135d518f73787ce323b1a5e21fb854fe22258d7a8ae562b81a49d6c7f826f2a3 \ + --hash=sha256:19cdea0664aec0b7f385be84986d4defd3334e9c3c799407686ee1c26f7b8251 \ + --hash=sha256:1f7b6f3ef10ae8e3558abb729873d033dbb5843507c66b1c0767e32502ba96bb \ + --hash=sha256:20837e10835c98973673406d6798e10f821e7744520633811a5a3d809762d8cc \ + --hash=sha256:236230433a9a4968ab895140514c308fdf9f607cb8bee178a04372b771123860 \ + --hash=sha256:23d1528db3c7554f9d6eeb09df23cb80dd5177ec56eeb55cc5318826928de506 \ + --hash=sha256:26280a7fcb62d8257f634c16acebc3bec626454f9ab13558bbf7883b9140760e \ + --hash=sha256:29512eb925b620e5da2fd7585814485c67cc6ba4fe739a0a700c50467a8a8065 \ + --hash=sha256:2eefc41ba42e75ed88bc396d8fe997beb20477f3e7efa000cd7a47eda452fbb2 \ + --hash=sha256:3014ccbda9be0b1b5f8ea895121df7e6524496b3908f4397ff02e923bcd8f6dd \ + --hash=sha256:449bf090b2aa4e019371d7511a6ea8a5a248139205c27d1834bb4b1e3c44d936 \ + --hash=sha256:4dc1c132259b38d12c6587d190cd09cd76e3b5273ce71fe1372437b4cbc65f6f \ + --hash=sha256:58b36f54da759602d8e2f7dad958752d453dfe2c7122767bc7f765e17dc59959 \ + --hash=sha256:5bf597530544db27a8d76aced49cfc817ee9503e0a4ebf0109cd70331e7bbe0c \ + --hash=sha256:6f39a10408478f4c05736a74da63727a1ae0e83e3533d07b19443400fe8591ca \ + --hash=sha256:6f52ac2eb49e99e7373f62e2a68428c6946cda52ce89aa8fe9f890c7278e2d3a \ + --hash=sha256:7183cc68ee2113b19b0b8714221e5e3b07b3ba10ca2bb108d78fd49cefaae101 \ + --hash=sha256:751250a31fef2bac05a2da2449aae7142075ea26139271f169af60456d8ad27a \ + --hash=sha256:75fc593cf836f631153d0e21beaeb8d26e144445c73645889335c2247fcd71a0 \ + --hash=sha256:7913079b029e1b3501854c9a78ad938ed40d61fe09bebab3c93e60ff1301b189 \ + --hash=sha256:793f6c9448ab6eb7d4974b4dde3f230345c08ca6c7995330fbceeb43a5c8aa5e \ + --hash=sha256:814f288c011efdf8f115c5ebcc1ab94b11da64b207722917e0ceb42f52ef30a3 \ + --hash=sha256:90903d2908158a2c9077a06f11e27545de610af690fb178fd3ba6b32492d4d1c \ + --hash=sha256:917311d6a64d1c327c0dfda1e41f3966a7fb72b11ca7aa2e7a68fcccc7db35d9 \ + --hash=sha256:95c03137b0cf66517c8baa65770507a756d3a89489d8ecf864ea92348e1beabe \ + --hash=sha256:978f416bbff9da8d2091e3cf011c92da68b13f2c453dcc2e8109099b2a19d234 \ + --hash=sha256:9a1af21160a38ee8be3f4fcf24ee4b99e6184cadc7f915d599f073f478a94d2c \ + --hash=sha256:a2591faa0c031cf3f57e5bce1461cfbd6160f3f66b5a72609a130924917cb07d \ + --hash=sha256:a603161318ff699784943e71f53899983b7dee571b4dd07c336437c9c5a272b0 \ + --hash=sha256:a6bc7928d161840096adc956703494b5c0193ede887346f028216cac0af87500 \ + --hash=sha256:a88cafb100af68af3b9b29b5ccd09fdf7a48c63327916c8c923a94c336d38dd3 \ + --hash=sha256:ab90c02cb264250b8a58cedcc72ed78a4a257d956c8d3c8bebe9751b818dfad8 \ + --hash=sha256:abcda41ecdc950399c05eff761c3de91485d9a70d8227cb599ad3a66afe93bcc \ + --hash=sha256:ac0c7eae7ad3a223bde690565442f8a3d620056bd01196f191af8be58a5248e1 \ + --hash=sha256:ac650d49366fa41fe702e054cb560171a8634e2865537e91f09a8d05ea5b1d37 \ + --hash=sha256:b7c11667421df2d8b18b021223505dcc3ee51be518d54e4dc49161ac88ac2b87 \ + --hash=sha256:ba3518b999f88882ade6686f1b71e207b52e23546e180499be5bbb63a2f9c6e6 \ + --hash=sha256:c19009ff37f033c70acd04b636380379499dac2cba27ae7dfc24f304deabbc81 \ + --hash=sha256:ce6f095eef0026eae76fc212f20f786011ecf482fc7df2f4c272a8ae6dd7b1ef \ + --hash=sha256:d2cf1d0557c61c75e18cf7d69fb689b77896e95553e212c0cc64cf2087944b84 \ + --hash=sha256:d450a8e0656efb5d0fcb062157b918ab02dcca73278975b4ee9ea49e2fcf5bd5 \ + --hash=sha256:df3266d54246cb56b8bb17fa908660d2a0f2e3f63fbc32451ffc1b1505051d07 \ + --hash=sha256:df76ecd17b1b3627bddfd689faaf206380a1a38cc9f6c4075bd884eaedcf46c2 \ + --hash=sha256:e2450d87dd7b4f277f4c5598faa8b49a0c197b91186c47a2c0b88e15531e4e3e \ + --hash=sha256:ea890e6dc1711aeec0a33b8520e395c2f3d59ead5b4351a788e06bf95fc7ba81 \ + --hash=sha256:f75823cc1674a840a151e999a7dfa0d86c911150dd6f951d0736ee9d383bf415 \ + --hash=sha256:fca33fdd0b38839b01912c57546d4f412ba7bfa0faf9bf7453432219aec2df07 # via # -r requirements.in # envoy-base-utils @@ -1061,6 +1063,7 @@ pyjwt[crypto]==2.8.0 \ # via # gidgethub # pygithub + # pyjwt pylsqpack==0.3.18 \ --hash=sha256:005ddce84bdcbf5c3cf99f764504208e1aa0a91a8331bf47108f2708f2a315e6 \ --hash=sha256:06e1bbe47514b83cd03158e5558ef8cc44f578169c1820098be9f3cc4137f16a \ @@ -1217,9 +1220,9 @@ six==1.16.0 \ # pyu2f # sphinxcontrib-httpdomain # thrift -slack-sdk==3.26.2 \ - --hash=sha256:a10e8ee69ca17d274989d0c2bbecb875f19898da3052d8d57de0898a00b1ab52 \ - --hash=sha256:bcdac5e688fa50e9357ecd00b803b6a8bad766aa614d35d8dc0636f40adc48bf +slack-sdk==3.27.0 \ + --hash=sha256:811472ce598db855ab3c02f098fa430323ccb253cfe17ba20c7b05ab206d984d \ + --hash=sha256:a901c68cb5547d5459cdefd81343d116db56d65f6b33f4081ddf1cdd243bf07e # via -r requirements.in smmap==5.0.1 \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 9cd42aba6c74..bab4737b0ece 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -129,7 +129,6 @@ paths: - source/common/protobuf/message_validator_impl.cc - source/common/quic/quic_server_transport_socket_factory.cc - source/common/secret/secret_manager_impl.cc - - source/common/grpc/async_client_manager_impl.cc - source/common/grpc/google_grpc_utils.cc - source/common/tcp_proxy/tcp_proxy.cc - source/common/config/subscription_factory_impl.cc @@ -169,6 +168,9 @@ paths: - source/common/common/logger_delegates.cc - source/common/upstream/health_checker_event_logger.h - source/common/upstream/outlier_detection_impl.h + - source/common/ssl/certificate_validation_context_config_impl.cc + - source/common/grpc/google_grpc_creds_impl.cc + - source/common/local_reply/local_reply.cc # Only one C++ file should instantiate grpc_init grpc_init: diff --git a/tools/repo/notify.py b/tools/repo/notify.py index d5a45ec2a37a..18a139f7e0c0 100644 --- a/tools/repo/notify.py +++ b/tools/repo/notify.py @@ -261,10 +261,11 @@ async def post_to_oncall(self): await self.send_message( channel='#envoy-maintainer-oncall', text=(f"*Stalled PRs* (PRs with review out-SLO, please address)\n{stalled}")) + num_issues = await self.track_open_issues() await self.send_message( channel='#envoy-maintainer-oncall', text=( - f"*Untriaged Issues* (please tag and cc area experts)\n<{ISSUE_LINK}|{ISSUE_LINK}>" + f"*{num_issues} Untriaged Issues* (please tag and cc area experts)\n<{ISSUE_LINK}|{ISSUE_LINK}>" )) except SlackApiError as e: self.log.error(f"Unexpected error {e.response['error']}") @@ -278,6 +279,11 @@ def pr_message(self, age, pull): f"<{pull['html_url']}|{html.escape(pull['title'])}> has been waiting " f"{markup}{days} days {hours} hours{markup}") + async def track_open_issues(self): + response = await self.session.get( + "https://api.github.com/repos/envoyproxy/envoy/issues?labels=triage") + return len(await response.json()) + async def run(self): if not self.github_token: self.log.error("Missing GITHUB_TOKEN: please check github workflow configuration")