Skip to content

Commit 274d68f

Browse files
authored
wasm: add config setting to control StopIteration behavior (#40213)
Add PluginConfig.allow_on_headers_stop_iteration config field to control the behavior of WasmFilter when plugin onRequestHeaders and onResponseHeaders callbacks return a value of FilterHeadersStatus::StopIteration. If allow_on_headers_stop_iteration is false (the default), then in cases where a Wasm plugin onRequestHeaders or onResponseHeaders callback returns FilterHeadersStatus::StopIteration, WasmFilter will maintain its current behavior of translating that response code to FilterHeadersStatus::StopAllIterationAndWatermark. If allow_on_headers_stop_iteration is true, then WasmFilter will avoid any translation of FilterHeadersStatus::StopIteration, and will pass it through to HCM unmodified, which allows Wasm plugins to inspect request or response body data before deciding how to handle/transform request or response headers. For details, see [Envoy Wasm / Proxy-Wasm support for FilterHeadersStatus::StopIteration](https://docs.google.com/document/d/1Whd1C0k-H2NHrPOmlAqqauFz6ObSTP017juJIYyciB0/edit?usp=sharing). This PR implements [Option B: WasmFilter config knob](https://docs.google.com/document/d/1Whd1C0k-H2NHrPOmlAqqauFz6ObSTP017juJIYyciB0/edit?tab=t.0#bookmark=id.5wxldlapsp54), building on proxy-wasm/proxy-wasm-cpp-host#434. Note that this config knob is a stopgap until upcoming v0.3.0 of the Proxy-Wasm ABI adds more comprehensive support for StopIteration and body buffering, per proxy-wasm/spec#63. Additional Description: This change updates the proxy-wasm-cpp-host dependency since it depends on proxy-wasm/proxy-wasm-cpp-host#434, which was recently merged there. Signed-off-by: Michael Warres <mpw@google.com>
1 parent 08ae12a commit 274d68f

File tree

6 files changed

+57
-5
lines changed

6 files changed

+57
-5
lines changed

api/envoy/extensions/wasm/v3/wasm.proto

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "envoy/config/core/v3/backoff.proto";
66
import "envoy/config/core/v3/base.proto";
77

88
import "google/protobuf/any.proto";
9+
import "google/protobuf/wrappers.proto";
910

1011
import "envoy/annotations/deprecation.proto";
1112
import "udpa/annotations/status.proto";
@@ -148,7 +149,7 @@ message EnvironmentVariables {
148149
}
149150

150151
// Base Configuration for Wasm Plugins e.g. filters and services.
151-
// [#next-free-field: 9]
152+
// [#next-free-field: 10]
152153
message PluginConfig {
153154
// A unique name for a filters/services in a VM for use in identifying the filter/service if
154155
// multiple filters/services are handled by the same ``vm_id`` and ``root_id`` and for
@@ -192,6 +193,10 @@ message PluginConfig {
192193

193194
// Configuration for restricting Proxy-Wasm capabilities available to modules.
194195
CapabilityRestrictionConfig capability_restriction_config = 6;
196+
197+
// Whether or not to allow plugin onRequestHeaders and onResponseHeaders callbacks to return
198+
// FilterHeadersStatus::StopIteration.
199+
google.protobuf.BoolValue allow_on_headers_stop_iteration = 9;
195200
}
196201

197202
// WasmService is configured as a built-in ``envoy.wasm_service`` :ref:`WasmService

bazel/repository_locations.bzl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,8 +1404,8 @@ REPOSITORY_LOCATIONS_SPEC = dict(
14041404
project_name = "WebAssembly for Proxies (C++ host implementation)",
14051405
project_desc = "WebAssembly for Proxies (C++ host implementation)",
14061406
project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host",
1407-
version = "c4d7bb0fda912e24c64daf2aa749ec54cec99412",
1408-
sha256 = "3ea005e85d2b37685149c794c6876fd6de7f632f0ad49dc2b3cd580e7e7a5525",
1407+
version = "65bb78fbf8beb6d3670701d35711e691c0c4c4ce",
1408+
sha256 = "a73bb64561d631097d4c1fb1c3c64f41f107b6186544565db70e74358b5f3ec3",
14091409
strip_prefix = "proxy-wasm-cpp-host-{version}",
14101410
urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"],
14111411
use_category = ["dataplane_ext"],
@@ -1420,7 +1420,7 @@ REPOSITORY_LOCATIONS_SPEC = dict(
14201420
"envoy.wasm.runtime.wamr",
14211421
"envoy.wasm.runtime.wasmtime",
14221422
],
1423-
release_date = "2024-12-19",
1423+
release_date = "2025-07-13",
14241424
cpe = "N/A",
14251425
license = "Apache-2.0",
14261426
license_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host/blob/{version}/LICENSE",

changelogs/current.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,5 +375,10 @@ new_features:
375375
- area: alts
376376
change: |
377377
Added environment variable-protected gRPC keepalive params to the ALTS handshaker client.
378+
- area: wasm
379+
change: |
380+
Added support for returning ``StopIteration`` from plugin ``onRequestHeader`` and ``onResponseHeader`` callbacks. See
381+
:ref:`allow_on_headers_stop_iteration <envoy_v3_api_field_extensions.wasm.v3.PluginConfig.allow_on_headers_stop_iteration>`
382+
for more details. By default, current behavior is maintained.
378383
379384
deprecated:

source/extensions/common/wasm/context.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "source/common/http/header_map_impl.h"
2727
#include "source/common/http/message_impl.h"
2828
#include "source/common/http/utility.h"
29+
#include "source/common/protobuf/utility.h"
2930
#include "source/common/tracing/http_tracer_impl.h"
3031
#include "source/extensions/common/wasm/plugin.h"
3132
#include "source/extensions/common/wasm/wasm.h"
@@ -70,6 +71,11 @@ namespace {
7071
// FilterState prefix for CelState values.
7172
constexpr absl::string_view CelStateKeyPrefix = "wasm.";
7273

74+
// Default behavior for Proxy-Wasm 0.2.* ABI is to not support StopIteration as
75+
// a return value from onRequestHeaders() or onResponseHeaders() plugin
76+
// callbacks.
77+
constexpr bool DefaultAllowOnHeadersStopIteration = false;
78+
7379
using HashPolicy = envoy::config::route::v3::RouteAction::HashPolicy;
7480
using CelState = Filters::Common::Expr::CelState;
7581
using CelStatePrototype = Filters::Common::Expr::CelStatePrototype;
@@ -162,13 +168,19 @@ Context::Context(Wasm* wasm, const PluginSharedPtr& plugin) : ContextBase(wasm,
162168
if (wasm != nullptr) {
163169
abi_version_ = wasm->abi_version_;
164170
}
165-
root_local_info_ = &std::static_pointer_cast<Plugin>(plugin)->localInfo();
171+
root_local_info_ = &this->plugin()->localInfo();
172+
allow_on_headers_stop_iteration_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(
173+
this->plugin()->wasmConfig().config(), allow_on_headers_stop_iteration,
174+
DefaultAllowOnHeadersStopIteration);
166175
}
167176
Context::Context(Wasm* wasm, uint32_t root_context_id, PluginHandleSharedPtr plugin_handle)
168177
: ContextBase(wasm, root_context_id, plugin_handle), plugin_handle_(plugin_handle) {
169178
if (wasm != nullptr) {
170179
abi_version_ = wasm->abi_version_;
171180
}
181+
allow_on_headers_stop_iteration_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(
182+
plugin()->wasmConfig().config(), allow_on_headers_stop_iteration,
183+
DefaultAllowOnHeadersStopIteration);
172184
}
173185

174186
Wasm* Context::wasm() const { return static_cast<Wasm*>(wasm_); }

test/extensions/filters/http/wasm/wasm_filter_test.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,29 @@ TEST_P(WasmHttpFilterTest, HeadersStopAndContinue) {
291291
filter().onDestroy();
292292
}
293293

294+
TEST_P(WasmHttpFilterTest, HeadersStopAndContinueAllowStopIteration) {
295+
if (std::get<1>(GetParam()) == "rust") {
296+
// TODO(PiotrSikora): This hand off is not currently possible in the Rust SDK.
297+
return;
298+
}
299+
setAllowOnHeadersStopIteration(true);
300+
setupTest("", "headers");
301+
setupFilter();
302+
EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_));
303+
EXPECT_CALL(filter(),
304+
log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders 2 headers"))));
305+
EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /"))));
306+
EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2"))));
307+
Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}};
308+
EXPECT_EQ(Http::FilterHeadersStatus::StopIteration,
309+
filter().decodeHeaders(request_headers, true));
310+
root_context_->onTick(0);
311+
filter().clearRouteCache();
312+
EXPECT_THAT(request_headers.get_("newheader"), Eq("newheadervalue"));
313+
EXPECT_THAT(request_headers.get_("server"), Eq("envoy-wasm-continue"));
314+
filter().onDestroy();
315+
}
316+
294317
#if 0
295318
TEST_P(WasmHttpFilterTest, HeadersStopAndEndStream) {
296319
if (std::get<1>(GetParam()) == "rust") {

test/test_common/wasm_base.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "test/test_common/printers.h"
2424
#include "test/test_common/utility.h"
2525

26+
#include "absl/types/optional.h"
2627
#include "gmock/gmock.h"
2728
#include "gtest/gtest.h"
2829

@@ -65,6 +66,10 @@ template <typename Base = testing::Test> class WasmTestBase : public Base {
6566
*plugin_config.mutable_root_id() = root_id_;
6667
*plugin_config.mutable_name() = "plugin_name";
6768
plugin_config.set_fail_open(fail_open_);
69+
if (allow_on_headers_stop_iteration_.has_value()) {
70+
plugin_config.mutable_allow_on_headers_stop_iteration()->set_value(
71+
*allow_on_headers_stop_iteration_);
72+
}
6873
plugin_config.mutable_configuration()->set_value(plugin_configuration_);
6974
*plugin_config.mutable_vm_config()->mutable_environment_variables() = envs_;
7075

@@ -122,6 +127,7 @@ template <typename Base = testing::Test> class WasmTestBase : public Base {
122127
plugin_configuration_ = plugin_configuration;
123128
}
124129
void setFailOpen(bool fail_open) { fail_open_ = fail_open; }
130+
void setAllowOnHeadersStopIteration(bool allow) { allow_on_headers_stop_iteration_ = allow; }
125131
void setAllowedCapabilities(proxy_wasm::AllowedCapabilitiesMap allowed_capabilities) {
126132
allowed_capabilities_ = allowed_capabilities;
127133
}
@@ -131,6 +137,7 @@ template <typename Base = testing::Test> class WasmTestBase : public Base {
131137
std::string root_id_ = "";
132138
std::string vm_configuration_ = "";
133139
bool fail_open_ = false;
140+
absl::optional<bool> allow_on_headers_stop_iteration_ = absl::nullopt;
134141
std::string plugin_configuration_ = "";
135142
proxy_wasm::AllowedCapabilitiesMap allowed_capabilities_ = {};
136143
envoy::extensions::wasm::v3::EnvironmentVariables envs_ = {};

0 commit comments

Comments
 (0)