@@ -9,44 +9,171 @@ A Varnish VMOD that executes WebAssembly (Wasm) modules for request/response pro
99
1010vmod-wasm embeds the [ Wasmtime] ( https://wasmtime.dev/ ) runtime into Varnish Cache, allowing you to write edge logic in ** Rust** , ** Go** , or ** AssemblyScript** , compile to WebAssembly, and execute it during request processing — without writing C or modifying VCL beyond a few function calls.
1111
12- The end goal is full [ Proxy-Wasm ABI] ( https://github.com/proxy-wasm/spec ) compatibility, so existing Wasm filters written for Envoy or nginx work on Varnish unchanged .
12+ It includes a [ Proxy-Wasm ABI] ( https://github.com/proxy-wasm/spec ) compatibility layer , so Wasm filters that follow the Proxy-Wasm standard can run on Varnish.
1313
1414## Status
1515
16- 🚧 ** Early development ** — Phase 1 (minimal Wasm execution) is in progress .
16+ ** v0.1.0 ** — Feature-complete for HTTP request filtering. All 12 VTC tests pass .
1717
18- ## Features (Planned)
18+ | Phase | Description | Status |
19+ | -------| -------------| --------|
20+ | 1 — Basic execution | Load & call Wasm functions from VCL | ✅ Done |
21+ | 2 — Host functions | Read headers/URL/method/IP, set response headers, log to VSL | ✅ Done |
22+ | 3 — Execution safety | Fuel limits, memory limits, trap/error reporting | ✅ Done |
23+ | 4 — Proxy-Wasm ABI | Proxy-Wasm lifecycle, header maps, send_local_response | ✅ Done |
1924
20- - Load ` .wasm ` modules at VCL init time (compiled once, instantiated per-request)
21- - Call exported Wasm functions from VCL
22- - Host functions: read request headers, URL, method, client IP
23- - Host functions: set response headers, log to VSL
24- - Fuel-based execution limits (prevent runaway modules)
25- - Memory limits (cap linear memory growth)
26- - Instance pooling for performance
27- - Proxy-Wasm ABI v0.2.1 compatibility
25+ ## Features
26+
27+ - ** Load ` .wasm ` modules** at VCL init (compiled once, instantiated per-request)
28+ - ** Call exported Wasm functions** from VCL and use the return value
29+ - ** 6 host functions** for request inspection: ` get_request_header ` , ` get_request_url ` , ` get_request_method ` , ` get_client_ip ` , ` set_response_header ` , ` log_msg `
30+ - ** Fuel-based execution limits** — prevent runaway or infinite-loop modules
31+ - ** Memory limits** — cap linear memory growth (default 16 MiB)
32+ - ** Trap & error reporting** — Wasm trap messages logged to VSL
33+ - ** Proxy-Wasm ABI** — 10 host functions implementing the core Proxy-Wasm lifecycle for HTTP request filtering
2834
2935## Quick Start
3036
37+ ### Simple Wasm execution
38+
3139``` vcl
3240import wasm;
3341
3442sub vcl_init {
3543 wasm.load("my_filter", "/etc/varnish/wasm/filter.wasm");
44+ wasm.set_fuel(1000000); # instruction limit
45+ wasm.set_memory_limit(8388608); # 8 MiB
3646}
3747
3848sub vcl_recv {
49+ # Call an exported function — returns its i32 result as a string
3950 if (wasm.execute("my_filter", "on_request") == 403) {
4051 return (synth(403, "Blocked"));
4152 }
4253}
4354```
4455
56+ ### Proxy-Wasm filter
57+
58+ ``` vcl
59+ import wasm;
60+
61+ sub vcl_init {
62+ wasm.load("waf", "/etc/varnish/wasm/waf_filter.wasm");
63+ wasm.set_fuel(10000000);
64+ }
65+
66+ sub vcl_recv {
67+ # Runs the full Proxy-Wasm lifecycle (context_create → vm_start →
68+ # configure → request_headers). Returns 0=allow, >0=HTTP status, -1=error
69+ set req.http.X-Wasm-Result = wasm.proxy_wasm_on_request("waf");
70+ if (req.http.X-Wasm-Result != "0") {
71+ return (synth(403, "Blocked by WAF"));
72+ }
73+ }
74+ ```
75+
76+ ## VCL Functions
77+
78+ | Function | Context | Description |
79+ | ----------| ---------| -------------|
80+ | ` wasm.load(STRING name, STRING path) ` | ` vcl_init ` | Load a ` .wasm ` module and register it by name |
81+ | ` wasm.execute(STRING module, STRING func) ` | any | Call an exported function, return its i32 result as a string |
82+ | ` wasm.proxy_wasm_on_request(STRING module) ` | ` vcl_recv ` | Execute a Proxy-Wasm filter lifecycle for the current request |
83+ | ` wasm.set_fuel(INT fuel) ` | ` vcl_init ` | Set the maximum fuel (instruction count) per execution |
84+ | ` wasm.set_memory_limit(INT bytes) ` | ` vcl_init ` | Set the maximum linear memory size in bytes |
85+ | ` wasm.get_fuel() ` | any | Return the current fuel limit |
86+ | ` wasm.get_memory_limit() ` | any | Return the current memory limit |
87+ | ` wasm.version() ` | any | Return the VMOD version string |
88+
89+ ## Host Functions (available to Wasm modules)
90+
91+ ### Basic host functions (Phase 2)
92+
93+ Registered under the ` env ` namespace:
94+
95+ | Function | Description |
96+ | ----------| -------------|
97+ | ` get_request_header(name_ptr, name_len, buf_ptr, buf_len) → i32 ` | Read a request header into the buffer, returns length |
98+ | ` get_request_url(buf_ptr, buf_len) → i32 ` | Read the request URL |
99+ | ` get_request_method(buf_ptr, buf_len) → i32 ` | Read the request method (GET, POST, etc.) |
100+ | ` get_client_ip(buf_ptr, buf_len) → i32 ` | Read the client IP address |
101+ | ` set_response_header(name_ptr, name_len, val_ptr, val_len) → i32 ` | Set a response header |
102+ | ` log_msg(level, msg_ptr, msg_len) ` | Log a message to VSL (0=debug, 1=info, 2=warn) |
103+
104+ ### Proxy-Wasm host functions (Phase 4)
105+
106+ Also under ` env ` namespace, following the [ Proxy-Wasm ABI spec] ( https://github.com/proxy-wasm/spec ) :
107+
108+ | Function | Description |
109+ | ----------| -------------|
110+ | ` proxy_log(level, msg_data, msg_size) ` | Log to VSL with Proxy-Wasm log levels |
111+ | ` proxy_get_header_map_value(map, key_data, key_size, ret_data, ret_size) ` | Read a header by name |
112+ | ` proxy_add_header_map_value(map, key_data, key_size, val_data, val_size) ` | Add/set a header |
113+ | ` proxy_replace_header_map_value(...) ` | Replace a header value |
114+ | ` proxy_remove_header_map_value(map, key_data, key_size) ` | Remove a header |
115+ | ` proxy_get_property(path_data, path_size, ret_data, ret_size) ` | Get request properties (path, method, protocol) |
116+ | ` proxy_send_local_response(status, ...) ` | Send an immediate response (e.g. 403) |
117+ | ` proxy_get_current_time_nanoseconds(ret_time) ` | Current time in nanoseconds |
118+ | ` proxy_set_effective_context(context_id) ` | Switch context (stub) |
119+ | ` proxy_get_buffer_bytes(buffer_type, start, max, ret_data, ret_size) ` | Read buffer data (stub) |
120+
121+ ## Writing Wasm Modules
122+
123+ ### Simple filter (Rust)
124+
125+ ``` rust
126+ // Compile with: cargo build --target wasm32-unknown-unknown --release
127+
128+ extern " C" {
129+ fn get_request_header (name_ptr : * const u8 , name_len : i32 ,
130+ buf_ptr : * mut u8 , buf_len : i32 ) -> i32 ;
131+ fn log_msg (level : i32 , msg_ptr : * const u8 , msg_len : i32 );
132+ }
133+
134+ #[no_mangle]
135+ pub extern " C" fn on_request () -> i32 {
136+ let name = b " User-Agent" ;
137+ let mut buf = [0u8 ; 512 ];
138+ let len = unsafe {
139+ get_request_header (name . as_ptr (), name . len () as i32 ,
140+ buf . as_mut_ptr (), buf . len () as i32 )
141+ };
142+ // Check for bad bots, return 403 to block or 0 to allow
143+ 0
144+ }
145+ ```
146+
147+ ### Proxy-Wasm filter (raw ABI)
148+
149+ ``` rust
150+ // Compile with: cargo build --target wasm32-unknown-unknown --release
151+ // Module must export: proxy_on_context_create, proxy_on_vm_start,
152+ // proxy_on_configure, proxy_on_request_headers, proxy_on_memory_allocate
153+
154+ extern " C" {
155+ fn proxy_log (level : i32 , msg_data : i32 , msg_size : i32 ) -> i32 ;
156+ fn proxy_get_header_map_value (map : i32 , key_data : i32 , key_size : i32 ,
157+ ret_data : i32 , ret_size : i32 ) -> i32 ;
158+ fn proxy_send_local_response (status : i32 , details_data : i32 ,
159+ details_size : i32 , body_data : i32 , body_size : i32 ,
160+ headers_data : i32 , headers_size : i32 , grpc_status : i32 ) -> i32 ;
161+ }
162+
163+ #[no_mangle]
164+ pub extern " C" fn proxy_on_request_headers (_ctx : i32 , _n : i32 , _eos : i32 ) -> i32 {
165+ // Inspect headers, block bad requests, add headers, etc.
166+ 0 // CONTINUE
167+ }
168+ ```
169+
170+ See [ ` examples/rust/src/lib.rs ` ] ( examples/rust/src/lib.rs ) for a complete working example.
171+
45172## Building from Source
46173
47174### Prerequisites
48175
49- - Varnish Cache 7.4 + (with development headers)
176+ - Varnish Cache 7.5 + (with development headers)
50177- Wasmtime C API v25+ ([ releases] ( https://github.com/bytecodealliance/wasmtime/releases ) )
51178- autotools (automake, autoconf, libtool)
52179- pkg-config
@@ -58,48 +185,79 @@ sub vcl_recv {
58185./autogen.sh
59186./configure
60187make
61- make check # runs VTC tests
188+ make check # runs 12 VTC tests
62189make install
63190```
64191
65192### Docker (recommended for development)
66193
67194``` bash
68195docker build -t vmod-wasm-dev .
69- docker run --rm -v $( pwd ) :/src vmod-wasm-dev make check
196+ docker run --rm vmod-wasm-dev make check
70197```
71198
72- ## Writing Wasm Modules
73-
74- Example in Rust (using the vmod-wasm guest SDK):
199+ ## Architecture
75200
76- ``` rust
77- #[no_mangle]
78- pub extern " C" fn on_request () -> i32 {
79- // Return 0 = allow, 403 = block
80- 0
81- }
82201```
83-
84- Compile with:
85- ``` bash
86- cargo build --target wasm32-wasi --release
202+ ┌─────────────────────────────┐
203+ │ VCL layer │
204+ │ wasm.load / wasm.execute / │
205+ │ wasm.proxy_wasm_on_request │
206+ └──────────────┬──────────────┘
207+ │
208+ ┌──────────────▼──────────────┐
209+ │ vmod_wasm.c │
210+ │ VCL function dispatch │
211+ └──────────────┬──────────────┘
212+ │
213+ ┌──────────────▼──────────────┐
214+ │ wasm_engine.c │
215+ │ Wasmtime lifecycle mgmt │
216+ │ • engine_call (Phase 1-3) │
217+ │ • proxy_wasm_call (Phase 4) │
218+ └──────┬──────────────┬───────┘
219+ │ │
220+ ┌──────────────▼──┐ ┌───────▼──────────────┐
221+ │ host_functions.c│ │ proxy_wasm.c │
222+ │ 6 basic host │ │ 10 Proxy-Wasm host │
223+ │ functions │ │ functions │
224+ └────────┬────────┘ └───────┬──────────────┘
225+ │ │
226+ └───────┬────────────┘
227+ │
228+ ┌────────▼────────┐
229+ │ Wasmtime C API │
230+ │ (libwasmtime) │
231+ └────────┬────────┘
232+ │
233+ ┌────────▼────────┐
234+ │ .wasm module │
235+ └─────────────────┘
87236```
88237
89- ## Architecture
238+ ## Tests
90239
91- ```
92- VCL → vmod_wasm.c → Wasmtime C API → Wasm Module
93- ↕
94- host_functions.c (request context bridge)
95- ```
240+ 12 VTC tests covering all functionality:
96241
97- See [ plan/feature-vmod-wasm-1.md] ( plan/feature-vmod-wasm-1.md ) for the full implementation plan.
242+ | Test | Phase | Description |
243+ | ------| -------| -------------|
244+ | ` basic_load ` | 1 | Load module, call ` get_constant ` and ` add_numbers ` |
245+ | ` allow_decision ` | 1 | ` on_request_allow ` returns 0 |
246+ | ` block_decision ` | 1 | ` on_request_block ` returns 403 |
247+ | ` error_handling ` | 1 | Missing module/function handling |
248+ | ` host_header ` | 2 | Read User-Agent via ` get_request_header ` |
249+ | ` host_url_method ` | 2 | Read URL and method via host functions |
250+ | ` host_block_bot ` | 2 | Block BadBot via host function header inspection |
251+ | ` fuel_exhaustion ` | 3 | Infinite loop stopped by fuel limit |
252+ | ` memory_limit ` | 3 | Memory growth capped by limit |
253+ | ` resource_config ` | 3 | ` set_fuel ` /` set_memory_limit ` /` get_* ` round-trip |
254+ | ` proxy_wasm_basic ` | 4 | Proxy-Wasm lifecycle with header addition |
255+ | ` proxy_wasm_block ` | 4 | Proxy-Wasm BadBot blocking via ` send_local_response ` |
98256
99257## License
100258
101259BSD-2-Clause — same as Varnish Cache itself.
102260
103261## Contributing
104262
105- Contributions welcome! See the implementation plan for current status and open tasks .
263+ Contributions welcome! See the [ implementation plan] ( plan/feature-vmod-wasm-1.md ) for background .
0 commit comments