If you hadn’t noticed, I have started looking into WebAssembly and WASI.
Last time I was looking into the LLVM toolchain. In the last few days, I have been reading about Envoy and its WASM filters. While there are a number of tutorials on how to use the Proxy WASM SDK using higher-level languages such as C++, Go, Rust, Zing and AssemblyScript, I could not find any resource on how to write a WASM extension from scratch using the canonical text format WAT and the lower-level tool wat2wasm
.
To be fair, the proxy-wasm ABI spec is very well-written and it can be easily used as a reference. However, for a newcomer like me, it is not clear by trying examples that employ higher-level language SDKs what is the minimal amount of code that has to be provided in a WASM filter in order to successfully initialize and boot Envoy.
So here is my attempt. From trial and error, it looks like Envoy will happily boot up if you implement at least two functions:
proxy_abi_version_0_2_0
proxy_on_memory_allocate
This will make for a pretty lousy Envoy filter, but it’s a good way to get started.
Create tiny.wat
:
(module
(func $proxy_abi_version_0_2_0
nop)
(func $proxy_on_memory_allocate (param $memory_size i32) (result i32)
i32.const 0 ;; do not allocate any memory
return)
(export "proxy_abi_version_0_2_0" (func $proxy_abi_version_0_2_0))
(export "proxy_on_memory_allocate" (func $proxy_on_memory_allocate))
)
then build tiny.wasm
wat2wasm tiny.wat
and then paste this into a tiny.yaml
that I lifted pretty much verbatim from Tetrate’s excellent workshop.
static_resources:
listeners:
- name: main
address:
socket_address:
address: 0.0.0.0
port_value: 18000
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains:
- "*"
routes:
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "hello world\n"
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
vm_config:
vm_id: "my_vm"
runtime: "envoy.wasm.runtime.v8"
code:
local:
filename: "tiny.wasm"
- name: envoy.filters.http.router
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
Then start your Envoy proxy using func-e
(again, courtesy of Tetrate’s Workshop. No, seriously, watch it).
$ func-e run -c envoy.yaml &
$ curl localhost:18000
hello world
Congratulations, your extensions does nothing. No really, the "hello world\n"
is configured in tiny.yaml
. So, yeah, all your extension does is loading without crashing the VM. Aren’t you proud?
Don’t you believe me? Why, let’s try implementing another callback in the spec; for instance proxy_on_vm_start
. This function takes two parameters (which we are not going to use) and returns an i32
representing a boolean value: 0
means false
, i.e. a failure to initialize.
(module
(func $proxy_abi_version_0_2_0
nop)
(func $proxy_on_memory_allocate (param $memory_size i32) (result i32)
i32.const 0 ;; do not allocate any memory
return)
(func $proxy_on_vm_start (param $root_context_id i32) (param $vm_configuration_size i32) (result i32)
i32.const 0
return)
(export "proxy_abi_version_0_2_0" (func $proxy_abi_version_0_2_0))
(export "proxy_on_memory_allocate" (func $proxy_on_memory_allocate))
(export "proxy_on_vm_start" (func $proxy_on_vm_start))
)
Let’s rebuild the wat
file with wasm2wat
and reload using func-e
and you’ll see that your Envoy proxy indeed will fail to start while attempting to load your WASM filter:
[2022-04-23 17:17:55.382][6666463][error][wasm] [source/extensions/common/wasm/wasm.cc:109] Wasm VM failed Failed to start base Wasm
[2022-04-23 17:17:55.383][6666463][critical][wasm] [source/extensions/common/wasm/wasm.cc:471] Plugin configured to fail closed failed to load
[2022-04-23 17:17:55.383][6666463][critical][main] [source/server/server.cc:117] error initializing configuration 'tiny.yaml': Unable to create Wasm HTTP filter
[2022-04-23 17:17:55.384][6666463][info][main] [source/server/server.cc:925] exiting
Unable to create Wasm HTTP filter
error: envoy exited with status: 1
but turn the boolean result into a 1
:
...
(func $proxy_on_vm_start (param $root_context_id i32) (param $vm_configuration_size i32) (result i32)
i32.const 1
return)
...
and rejoyce! Your WASM filter is back to load successfully, although it still cannot do anything useful. Well, we all have to start somewhere.