WATER
WebAssembly Transports & Executable Runtime (WATER) is a project that seeks to provide easily iterable and transferrable pluggable censorship circumvention transports. This involves a library supporting the design of transport modules (compiled to WASM) and client runtimes for running those modules in any language supporting current WASM standards (at the moment we have implemented support for Golang and Rust).
WATER
To build a WATM in Go, please refer to refraction-networking/watm for examples and helper libraries interfacing Pluggable Transports-like interfaces. Official Go compiler is currently not supported until further notice.
The Rust implementation of the runtime library and information about writing, building, and using WebAssembly Transport Modules(WATM) from Rust can be found in refraction-networking/water-rs.
Be Water
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
Usage
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
.
Versioning
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
Components (in Golang)
Dialer
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("https://github.com/refraction-networking/water/tree/master/examples/v0/plain/plain.wasm")
config := &water.Config{
TransportModuleBin: wasm,
}
dialer, _ := water.NewDialerWithContext(context.Background(), config)
conn, _ := dialer.DialContext(context.Background(),"tcp", remoteAddr)
// ...
Listener
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
accept 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("https://github.com/refraction-networking/water/tree/master/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)
}
// ...
Relay
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 connections 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 connections.
Relay
is a managed many-to-many handler for incoming connections that uses the WebAssemble module
to tunnel traffic.
wasm, _ := os.ReadFile("https://github.com/refraction-networking/water/tree/master/examples/v0/plain/plain.wasm")
config := &water.Config{
TransportModuleBin: wasm,
}
relay, _ := water.NewRelayWithContext(context.Background(), config)
relay.ListenAndRelayTo("tcp", localAddr, "tcp", remoteAddr) // blocking
Example
See refraction-networking/water/examples for
example use cases of the W.A.T.E.R. API, including Dialer
, Listener
and Relay
.
Submodules
watm
has its own licensing policy, please refer to refraction-networking/watm for more information.
Cross-platform Support
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 | ❓ | ❓ |
Cite our work
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.
@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},
}