Skip to content

Commit c471130

Browse files
committed
docs: Phase 5 — comprehensive README and CHANGELOG
- Rewrite README with complete documentation for all 4 phases - Add status table, VCL function reference, host function tables - Add Proxy-Wasm usage examples (simple filter + Proxy-Wasm) - Add ASCII architecture diagram showing all components - Add test matrix listing all 12 VTC tests - Create CHANGELOG.md with detailed v0.1.0 release notes - Update status from 'early development' to 'v0.1.0 feature-complete'
1 parent 6a6d328 commit c471130

2 files changed

Lines changed: 248 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Changelog
2+
3+
All notable changes to vmod-wasm will be documented in this file.
4+
5+
## [0.1.0] - 2026-04-29
6+
7+
### Added
8+
9+
#### Phase 1 — Basic Wasm Execution
10+
- `wasm.load(name, path)` — Load `.wasm` modules at VCL init
11+
- `wasm.execute(module, func)` — Call exported Wasm functions from VCL
12+
- `wasm.version()` — Return VMOD version string
13+
- Wasmtime C API v25 integration with engine/linker/store lifecycle
14+
- Thread-safe module registry (read-write lock, up to 64 modules)
15+
- 4 VTC tests: basic_load, allow_decision, block_decision, error_handling
16+
17+
#### Phase 2 — Host Functions
18+
- 6 host functions under `env` namespace:
19+
- `get_request_header` — Read any request header
20+
- `get_request_url` — Read the request URL
21+
- `get_request_method` — Read the HTTP method
22+
- `get_client_ip` — Read the client IP address
23+
- `set_response_header` — Set a response header
24+
- `log_msg` — Log messages to Varnish Shared Log (VSL)
25+
- 3 VTC tests: host_header, host_url_method, host_block_bot
26+
27+
#### Phase 3 — Execution Safety
28+
- `wasm.set_fuel(fuel)` — Configurable fuel (instruction) limits
29+
- `wasm.set_memory_limit(bytes)` — Configurable memory limits
30+
- `wasm.get_fuel()` / `wasm.get_memory_limit()` — Query current limits
31+
- Trap message extraction and logging to VSL
32+
- 3 VTC tests: fuel_exhaustion, memory_limit, resource_config
33+
34+
#### Phase 4 — Proxy-Wasm ABI Compatibility
35+
- `wasm.proxy_wasm_on_request(module)` — Execute Proxy-Wasm filter lifecycle
36+
- 10 Proxy-Wasm host functions:
37+
- `proxy_log` — Log with Proxy-Wasm log levels
38+
- `proxy_get_header_map_value` — Read headers by name
39+
- `proxy_add_header_map_value` — Add/set headers
40+
- `proxy_replace_header_map_value` — Replace header values
41+
- `proxy_remove_header_map_value` — Remove headers
42+
- `proxy_get_property` — Read request properties (path, method, protocol)
43+
- `proxy_send_local_response` — Send immediate responses (e.g. 403)
44+
- `proxy_get_current_time_nanoseconds` — Current time
45+
- `proxy_set_effective_context` — Context switching (stub)
46+
- `proxy_get_buffer_bytes` — Buffer access (stub)
47+
- Full Proxy-Wasm lifecycle: context_create → vm_start → configure → request_headers
48+
- Memory allocator protocol via `proxy_on_memory_allocate` export
49+
- 2 VTC tests: proxy_wasm_basic, proxy_wasm_block
50+
51+
### Infrastructure
52+
- Dockerfile with Debian bookworm-slim, Varnish 7.5, Wasmtime 25, Rust
53+
- GitHub Actions CI pipeline
54+
- autotools build system (automake/autoconf/libtool)
55+
- Rust test module (wasm32-unknown-unknown) with functions for all phases

README.md

Lines changed: 193 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,171 @@ A Varnish VMOD that executes WebAssembly (Wasm) modules for request/response pro
99

1010
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.
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
3240
import wasm;
3341
3442
sub 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
3848
sub 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
60187
make
61-
make check # runs VTC tests
188+
make check # runs 12 VTC tests
62189
make install
63190
```
64191

65192
### Docker (recommended for development)
66193

67194
```bash
68195
docker 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

101259
BSD-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

Comments
 (0)