To build a WATM in Go, please refer to watm for examples and helper libraries interfacing Pluggable Transports-like interfaces. Official Go compiler is currently not supported until further notice.
You can contact one of developers personally via [email protected], or simply opening an issue.
The Rust implementation of the runtime library and information about writing, building, and using WebAssembly Transport Modules(WATM) from Rust can be found in water-rs.
If you quoted or used our work in your own project/paper/research, please cite our paper Just add WATER: WebAssembly-based Circumvention Transports, which is published in the proceedings of Free and Open Communications on the Internet (FOCI) in 2024 issue 1, pages 22-28.
BibTeX
@inproceedings{water-foci24,
author = {Erik Chi and Gaukas Wang and J. Alex Halderman and Eric Wustrow and Jack Wampler},
title = {Just add {WATER}: {WebAssembly}-based Circumvention Transports},
booktitle = {Free and Open Communications on the Internet},
publisher = {},
year = {2024},
url = {https://www.petsymposium.org/foci/2024/foci-2024-0003.pdf},
}
Empty your mind, be formless, shapeless, like water. If you put water into a cup, it becomes the cup. You put water into a bottle and it becomes the bottle. You put it in a teapot, it becomes the teapot. Now, water can flow or it can crash. Be water, my friend.
-- Bruce Lee
This project is licensed under Apache 2.0 license.
This repo contains a Go package water
, which implements the runtime library used to interact with .wasm
WebAssembly Transport Modules(WATM).
Based on WASI Snapshot Preview 1 (wasip1), currently W.A.T.E.R. provides a set of net
-like APIs via Dialer
, Listener
and Relay
.
W.A.T.E.R. is designed to be future-proof with the automated multi-version WebAssembly Transport Module(WATM) support. In order to minimize the size of compiled application binaries importing water
, the support for each WATM version is implemented in separate sub-packages and by default none will be enabled. The developer MUST manually enable each version to be supported by importing the corresponding package:
import (
// ...
_ "github.com/refraction-networking/water/transport/v0"
// ...
)
Otherwise, it is possible that the W.A.T.E.R. runtime cannot determine the version of the WATM and therefore fail to select the corresponding runtime:
panic: failed to listen: water: listener version not found
TODO: add documentations for customizable WATM version.
A Dialer
connects to a remote address and returns a net.Conn
to the caller if the connection can
be successfully established. The net.Conn
then provides tunnelled read and write to the remote
endpoint with the WebAssembly module wrapping / encrypting / transforming the traffic.
Dialer
is used to encapsulate the caller's connection into an outbound, transport-wrapped
connection.
wasm, _ := os.ReadFile("./examples/v0/plain/plain.wasm")
config := &water.Config{
TransportModuleBin: wasm,
}
dialer, _ := water.NewDialerWithContext(context.Background(), config)
conn, _ := dialer.DialContext(context.Background(),"tcp", remoteAddr)
// ...
A Listener
listens on a local address for incoming connections which it Accept()
s, returning
a net.Conn
for the caller to handle. The WebAssembly Module is responsible for the initial
accpt allowing it to implement both the server side handshake as well as any unwrap / decrypt
/reform operations required to validate and re-assemble the stream. The net.Conn
returned provides
the normalized stream, and allows the caller to write back to the client with the WebAssembly module
encoding / encrypting / transforming traffic to be obfuscated on the wire on the way to the remote
client.
Listener
is used to decapsulate incoming transport-wrapped connections for the caller to handle,
managing the tunnel obfuscation once a connection is established.
wasm, _ := os.ReadFile("./examples/v0/plain/plain.wasm")
config := &water.Config{
TransportModuleBin: wasm,
}
lis, _ := config.ListenContext(context.Background(), "tcp", localAddr)
defer lis.Close()
log.Printf("Listening on %s", lis.Addr().String())
for {
conn, err := lis.Accept()
handleConn(conn)
}
// ...
A Relay
combines the role of Dialer
and Listener
. It listens on a local address Accept()
-ing
incoming connections and Dial()
-ing the remote endpoints on establishment. The connecions are
tunneled through the WebAssembly Transport Module allowing the module to handshake, encode,
transform, pad, etc. without any caller interaction. The internal Relay
manages the incoming
connections as well as the associated outgoing connectons.
Relay
is a managed many-to-many handler for incoming connections that uses the WebAssemble module
to tunnel traffic.
wasm, _ := os.ReadFile("./examples/v0/plain/plain.wasm")
config := &water.Config{
TransportModuleBin: wasm,
}
relay, _ := water.NewRelayWithContext(context.Background(), config)
relay.ListenAndRelayTo("tcp", localAddr, "tcp", remoteAddr) // blocking
See examples for example usecase of W.A.T.E.R. API, including Dialer
, Listener
and Relay
.
watm
has its own licensing policy, please refer to watm for more information.
W.A.T.E.R. is designed to be cross-platform (and cross-architecture). Currently, it supports the following platforms:
Target | Compiles? | Tests Pass? |
---|---|---|
linux/amd64 | ✅ | ✅ |
linux/arm64 | ✅ | ✅ |
linux/riscv64 | ✅ | ✅ |
macos/amd64 | ✅ | ✅ |
macos/arm64 | ✅ | ✅ |
windows/amd64 | ✅ | ✅ |
windows/arm64 | ✅ | ❓ |
others | ❓ | ❓ |
-
We thank GitHub.com for providing GitHub Actions runners for all targets below:
linux/amd64
on Ubuntu Latestlinux/arm64
via docker/setup-qemu-actionlinux/riscv64
via docker/setup-qemu-actionmacos/amd64
on macOS 12macos/arm64
on macOS 14windows/amd64
on Windows Latest
-
We thank FlyCI.net for providing GitHub Actions runners on
macos/arm64
(Apple M1) in the past. (We switched to GitHub'smacos-14
runner as of Jan 31 2024)
We are currently actively looking for a CI provider for more target platforms. Please reach out and let us know if you would like to help.