11# vmod-wasm
22
3- A Varnish VMOD that executes WebAssembly (Wasm) modules for request/response processing at the edge.
3+ A Varnish VMOD that executes WebAssembly modules for HTTP request processing at the edge.
44
5- [ ![ License: BSD-2-Clause ] ( https://img.shields.io/badge/license-BSD--2--Clause-blue .svg )] ( LICENSE )
5+ [ ![ License: CC BY-NC 4.0 ] ( https://img.shields.io/badge/license-CC%20BY--NC%204.0-lightgrey .svg )] ( LICENSE )
66[ ![ CI] ( https://github.com/RamazanKara/vmod-wasm/actions/workflows/ci.yml/badge.svg )] ( https://github.com/RamazanKara/vmod-wasm/actions )
77
88## Overview
99
10- vmod-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 .
10+ vmod-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.
1111
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.
13-
14- ## Status
15-
16- ** v0.1.0** — Feature-complete for HTTP request filtering. All 12 VTC tests pass.
17-
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 |
12+ Includes a [ Proxy-Wasm ABI] ( https://github.com/proxy-wasm/spec ) compatibility layer for running standard Wasm filters on Varnish.
2413
2514## Features
2615
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
16+ - Load ` .wasm ` modules at VCL init time
17+ - Call exported Wasm functions from VCL
18+ - Host functions for request inspection (headers, URL, method, client IP)
19+ - Set response headers and log to VSL from Wasm
20+ - Fuel-based execution limits
21+ - Memory limits (default 16 MiB)
22+ - Proxy-Wasm ABI support (header maps, local response, properties)
3423
3524## Quick Start
3625
37- ### Simple Wasm execution
38-
3926``` vcl
4027import wasm;
4128
4229sub vcl_init {
4330 wasm.load("my_filter", "/etc/varnish/wasm/filter.wasm");
44- wasm.set_fuel(1000000); # instruction limit
31+ wasm.set_fuel(1000000);
4532 wasm.set_memory_limit(8388608); # 8 MiB
4633}
4734
4835sub vcl_recv {
49- # Call an exported function — returns its i32 result as a string
5036 if (wasm.execute("my_filter", "on_request") == 403) {
5137 return (synth(403, "Blocked"));
5238 }
5339}
5440```
5541
56- ### Proxy-Wasm filter
42+ ### Proxy-Wasm
5743
5844``` 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-
6645sub 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
6946 set req.http.X-Wasm-Result = wasm.proxy_wasm_on_request("waf");
7047 if (req.http.X-Wasm-Result != "0") {
71- return (synth(403, "Blocked by WAF "));
48+ return (synth(403, "Blocked"));
7249 }
7350}
7451```
7552
7653## VCL Functions
7754
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-
9555| Function | Description |
9656| ----------| -------------|
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) |
57+ | ` wasm.load(name, path) ` | Load a ` .wasm ` module |
58+ | ` wasm.execute(module, func) ` | Call an exported function, returns i32 as string |
59+ | ` wasm.proxy_wasm_on_request(module) ` | Run Proxy-Wasm filter lifecycle (0=allow, >0=status, -1=error) |
60+ | ` wasm.set_fuel(fuel) ` | Set instruction limit per execution |
61+ | ` wasm.set_memory_limit(bytes) ` | Set max linear memory |
62+ | ` wasm.get_fuel() ` | Return current fuel limit |
63+ | ` wasm.get_memory_limit() ` | Return current memory limit |
64+ | ` wasm.version() ` | Return VMOD version |
10365
104- ### Proxy-Wasm host functions (Phase 4)
66+ ## Host Functions
10567
106- Also under ` env ` namespace, following the [ Proxy- Wasm ABI spec ] ( https://github.com/proxy-wasm/spec ) :
68+ Registered under the ` env ` namespace, available to all Wasm modules :
10769
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) |
70+ ** Request inspection:** ` get_request_header ` , ` get_request_url ` , ` get_request_method ` , ` get_client_ip ` , ` set_response_header ` , ` log_msg `
12071
121- ## Writing Wasm Modules
72+ ** Proxy-Wasm ABI: ** ` proxy_log ` , ` proxy_get_header_map_value ` , ` proxy_add_header_map_value ` , ` proxy_replace_header_map_value ` , ` proxy_remove_header_map_value ` , ` proxy_get_property ` , ` proxy_send_local_response ` , ` proxy_get_current_time_nanoseconds ` , ` proxy_set_effective_context ` , ` proxy_get_buffer_bytes `
12273
123- ### Simple filter (Rust)
74+ ## Writing Wasm Modules
12475
12576``` rust
12677// Compile with: cargo build --target wasm32-unknown-unknown --release
12778
12879extern " C" {
12980 fn get_request_header (name_ptr : * const u8 , name_len : i32 ,
13081 buf_ptr : * mut u8 , buf_len : i32 ) -> i32 ;
131- fn log_msg (level : i32 , msg_ptr : * const u8 , msg_len : i32 );
13282}
13383
13484#[no_mangle]
13585pub 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
86+ // Return 0 to allow, 403 to block
14387 0
14488}
14589```
14690
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
91+ See [ ` examples/rust/src/lib.rs ` ] ( examples/rust/src/lib.rs ) for a complete example including Proxy-Wasm ABI usage.
15392
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-
172- ## Building from Source
93+ ## Building
17394
17495### Prerequisites
17596
176- - Varnish Cache 7.5+ (with development headers)
177- - Wasmtime C API v25+ ([ releases] ( https://github.com/bytecodealliance/wasmtime/releases ) )
178- - autotools (automake, autoconf, libtool)
179- - pkg-config
180- - C compiler (gcc or clang)
97+ - Varnish Cache 7.5+ (with dev headers)
98+ - Wasmtime C API v25+
99+ - autotools, pkg-config, C compiler
181100
182101### Build
183102
184103``` bash
185104./autogen.sh
186105./configure
187106make
188- make check # runs 12 VTC tests
107+ make check
189108make install
190109```
191110
192- ### Docker (recommended for development)
111+ ### Docker
193112
194113``` bash
195114docker build -t vmod-wasm-dev .
@@ -199,65 +118,15 @@ docker run --rm vmod-wasm-dev make check
199118## Architecture
200119
201120```
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- └─────────────────┘
121+ VCL → vmod_wasm.c → wasm_engine.c → Wasmtime C API → .wasm module
122+ ↕
123+ host_functions.c / proxy_wasm.c
236124```
237125
238- ## Tests
239-
240- 12 VTC tests covering all functionality:
241-
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 ` |
256-
257126## License
258127
259- BSD-2-Clause — same as Varnish Cache itself .
128+ CC BY-NC 4.0 — see [ LICENSE ] ( LICENSE ) . Non-commercial use only .
260129
261130## Contributing
262131
263- Contributions welcome! See the [ implementation plan ] ( plan/feature-vmod-wasm-1.md ) for background .
132+ Contributions welcome.
0 commit comments