Files
nym/wasm/smolmix/README.md
T
mfahampshire 43a1bd38e8 Max/smolmix wasm (#6784)
* Mod gitignore + license trimming + comment trimming

* Big rewrite

* SURB inputs + DNS button in internal-dev

* Make ipr addr optional

* Accidentatly omitted files from rewrite commit

* Makefile + readme

* Comment rewrite

* Optimisation comment

* Replace manual waker map with
      smoltcp built-ins + adaptive poll

* Comments

* Extract socket creation helpers into stream.rs

* Cleanup comments

* Comment

* Comment notes and restrict ciphersuites wrt rustls-rustcrypto

* Dep. hack fix for demo + add clearnet fetch() for contrast

* Stripped down devtester

* Fix Clippy arg (fatfingered deletion)

* CodeRabbit catches

* Cargofmt

* Review nits: bridge logs, fetch early-return, static port counter, copyright years, README + Cargo + headless.js tidying

* PHONY + taskset override, switch internal-dev/tests to pnpm, fix wasm-pack out-dir

* Gate codec tests behind the codec feature for no-default-features builds

* IPv6 addr/route on smoltcp iface + configurable DNS resolvers via TunnelOpts

* DNS GUI inputs, close stale WS on reconnect, worker init guards + ws-send warning, Playwright listener cleanup, pnpm-lock in internal-dev

* Fix lp -> lp-data after rebase

* Revert nym-lp/nym-lp-data feature-gating left over from rebase

* Lift getrandom wasm_js cfg to workspace .cargo/config.toml so cargo check -p smolmix-wasm works from any CWD

* temp will amend git message

* Auto-discover IPR when none specified + 'Use random IPR' checkbox in internal-dev

* smolmix_tracker + State machine + ready_tunnel gate + getTunnelState JS surface

* Mirror red display() entries to console.error

* Add left out package-lock

* Reactor clock + yield_now + atomic seq + gateway-storage errors

* setupMixTunnel gate + MTU 1980 + http::Uri cleanup

* Review pass + fix test + clippy

* restore axum 0.8 bump from borked earlier merge

* Feature gating (dns/fetch/socket) + TunnelOptsBuilder + pnpm bypass

* Cont. with review comments

* tokio Nofity reactor wakes + cancellation + setup polishing

* Notify wakes + inner pattern + close_notify + util

* Tunable tunnelopts

* Fix tired commit

* CI prep

* Lint + Clippy

* coderabbit u32 fix

* nits + runtime debugging + expose in internal-dev

* remove redudant default-features

* Remove more redundant default-features
2026-05-28 15:57:10 +00:00

8.0 KiB

smolmix-wasm

Drop-in browser networking over the Nym mixnet. Routes HTTP and WebSocket traffic through a mixnet tunnel, giving web applications network-level privacy without changing application code.

Public API

Three WASM exports that mirror the browser's native networking surface:

Browser API smolmix export Description
fetch() mixFetch(url, init) HTTP/HTTPS request-response
new WebSocket() mixSocket(url, protocols, onEvent) WebSocket (WS/WSS)
(no direct browser equivalent) mixResolve(hostname) DNS-only hostname lookup over UDP/IPR (no TCP/TLS)

Arch

                          WasmTunnel
              +---------- tunnel.rs -----------+
              |                                |
              |  Owns: smoltcp stack, Nym      |
              |  client, connection pool,      |
              |  DNS cache, origin locks       |
              +--------------------------------+
                     |            |
              TCP/UDP sockets    |
              (futures::io)      |
                     |           |
                     v           v
              +-----------+  +-----------+  +-----------+
              |  Reactor  |  |  Bridge   |  | Nym Client|
              | reactor.rs|  | bridge.rs |  | (base     |
              |           |  |           |  |  client)  |
              +-----------+  +-----------+  +-----------+
                     |           |               |
                     v           v               |
              +-----------+  +-------+           |
              |  smoltcp  |  |  IPR  |           |
              | Interface |  |ipr.rs |           |
              +-----------+  +-------+           |
                     |           |               |
                     v           |               |
              +-----------+     |                |
              |  Device   |<----+                |
              | device.rs |     |                |
              | (virtual  |     v                |
              |   NIC)    |  LP frames           |
              +-----------+  + SURBs             |
              rx[] / tx[]       |                |
                                +--------->------+
                                     mixnet

Component walkthrough

  • Device (device.rs) - the virtual network interface card
  • Reactor (reactor.rs) - the smoltcp poll loop
  • Bridge (bridge.rs) - shuttles packets between the device and the mixnet
  • IPR (ipr.rs) - IP Packet Router protocol layer
  • WasmTcpStream / WasmUdpSocket / PooledConn (stream.rs) - futures::io::AsyncRead + AsyncWrite adapters over smoltcp sockets
  • WASM exports (lib.rs, mixfetch.rs, mixsocket.rs) - the surface JS calls into

Tuning

The JS setupMixTunnel(opts) shape accepts the following optional fields for timeouts, buffer sizes, and protocol limits. All have sensible defaults; only override when you have a concrete reason.

Field Default Notes
connectTimeoutMs 60000 IPR connect handshake timeout
dnsTimeoutMs 30000 DNS query timeout (per primary/fallback attempt)
tcpKeepaliveMs 10000 TCP keepalive probe interval
tcpBufferSize 65535 Per-TCP-stream RX/TX buffer; capped at u16::MAX
maxRedirects 5 mixFetch redirect chain depth before bail

On the Rust side these live in TunnelOpts::tuning: TuningOpts. The builder exposes them flat (.connect_timeout(d), .tcp_buffer_size(n), etc.) so callers don't see the grouping.

Feature flags

The crate is split into three user-facing cargo features matching the JS entry points. Default builds enable all three; downstream TS SDK packages can opt into a subset to drop the corresponding implementation + native deps from the wasm binary.

Feature JS export Pulls
dns mixResolve (nothing extra; DNS resolver is always compiled)
fetch mixFetch rustls TLS stack + hyper HTTP/1.1 client
socket mixSocket rustls TLS stack + async-tungstenite

Build a dns-only client:

cargo build --target wasm32-unknown-unknown --no-default-features --features dns

Build a fetch-only client (no WebSocket, no mixResolve JS export):

cargo build --target wasm32-unknown-unknown --no-default-features --features fetch

fetch and socket share the TLS stack (rustls + rustls-rustcrypto + webpki-roots); enabling both is roughly the same wasm size as either alone plus the hyper + async-tungstenite specifics.

Debug logging

debug_log! and debug_error! (in util.rs) wrap nym_wasm_utils::console_log! / console_error! behind the debug cargo feature. Tunnel start/shutdown and the IPR connect handshake stay unconditional; everything else is silent in release.

make build-debug enables the feature automatically (it builds with --features debug). make build-release-opt leaves it off, so release artefacts ship no verbose logging.

Cryptography

TLS terminates inside the WASM client, so we need a pure-Rust rustls crypto provider. rustls-rustcrypto as the only viable option: the underlying RustCrypto AEADs were audited by NCC Group in 2020 with no findings, while the rustls integration glue is 0.0.2-alpha. src/tls.rs restricts negotiation to AEAD-only suites with forward-secret key exchange.

Build

make build              # plain release wasm-pack build
make build-debug        # dev profile, verbose console logs on
make build-release-opt  # release + wasm-opt -Oz
make dev                # build-debug then start internal-dev webpack

Summary diagram

              JS caller
                 |
       +---------+---------+--------------+
       v                   v              v
  mixFetch            mixSocket       mixResolve
  (mixfetch.rs)      (mixsocket.rs)   (mixdns.rs)
       |                   |              |
       v                   v              v
  fetch::fetch       fetch::new_      dns::resolve
                     connection +     (dns.rs)
                     async_tungst.
       \                   |              /
        \                  v             /
         '-> WasmTcpStream / WasmUdpSocket  (stream.rs)
                            |
                            v  smoltcp socket buffer
                  +-------- smoltcp::Interface::poll() (reactor.rs)
                  |
                  v IP packet
            WasmDevice.tx_queue  (device.rs)
                  |
                  v drained 5ms
            bridge::start_bridge  (bridge.rs)
                  |
                  v
            ipr::send_ip_packet  (ipr.rs)
                  |
                  v  LP-framed DataRequest
            ClientInput::send  (upstream, nym-wasm-client-core)
                  |
                  v  Sphinx-packed
            JSWebsocket::new  -> WebSocket::open -> web_sys::WebSocket::new
                  (common/wasm/utils/src/websocket/mod.rs:58)
                  |
                  v
            Single wss:// to chosen gateway

  (Separately, at startup + on TopologyRefresher tick:)
            nym_http_api_client::ClientBuilder
              -> reqwest -> web_sys::fetch
              (common/client-core/src/init/helpers.rs:155)
              |
              v
            HTTPS GET https://validator.nymtech.net/...

Everything else (TLS handshakes, HTTP/1.1 requests, WebSocket frames in mixSocket) is content travelling inside that single gateway WSS as Sphinx-packed bytes.