libp2p-wasi-sockets 0.1.0

WASI 0.2 sockets transport for rust-libp2p
Documentation

libp2p-wasi-sockets

CI Crates.io docs.rs License: MIT OR Apache-2.0

A WASI 0.2 sockets transport for rust-libp2p.

Implements libp2p_core::Transport over wasi:sockets/tcp, enabling rust-libp2p applications to run as Wasm Components on any WASI 0.2 host — Wasmtime, Spin, jco, Wasmer — without any modification to the rest of the libp2p stack.


Why does this exist?

Every major rust-libp2p sub-crate (libp2p-core, libp2p-swarm, libp2p-noise, libp2p-yamux, libp2p-gossipsub, …) compiles cleanly for wasm32-wasip2. The only missing piece was a transport: libp2p-tcp is gated out for all wasm targets, and no wasi:sockets-based alternative existed.

This crate closes that gap.

What you can do with it

  • Run libp2p-based protocols as sandboxed Wasm Components in Wasmtime or Spin.
  • Drop P2P functionality into plugin systems that already embed a WASI 0.2 runtime.
  • Leverage the WASI network capability model — the host explicitly grants (or denies) network access per component, giving stronger isolation than containers.

What is deliberately out of scope

  • UDP / QUICwasi:sockets/udp doesn't expose the socket controls quinn needs.
  • DNS multiaddrs/dns4, /dns6, /dnsaddr planned for v0.2.
  • Browser — already covered by libp2p-websocket-websys / libp2p-webtransport-websys.
  • NAT traversal — AutoNAT, circuit-relay, DCUtR are not supported by the wasi-sockets API.

Status

v0.1.0 — all milestones complete; full Noise XX + Yamux interop verified against native rust-libp2p.

Milestone Description Status
M0 Scaffold, Cargo.toml, CI, multiaddr utils, unit tests
M1 WasiTcpStream AsyncRead/AsyncWrite bridge + echo integration test
M2 WasiTcpTransport listen_on + dial integration test
M3 Multi-listener, AddressExpired / ListenerClosed lifecycle events
M4 Noise XX + Yamux upgrade over WasiTcpTransport
M5 Interop test: WASM component ↔ native rust-libp2p (tokio)
M6 Docs polish, examples, 0.1.0 crates.io release

Quick start

# Cargo.toml — do NOT enable the `tcp` feature on the umbrella `libp2p` crate
[dependencies]
libp2p-wasi-sockets = "0.1"
libp2p-swarm        = "0.47"
libp2p-noise        = "0.46"
libp2p-yamux        = "0.47"
libp2p-ping         = "0.47"
libp2p-identity     = { version = "0.2", features = ["ed25519", "rand"] }
wstd                = "0.6"
use libp2p_swarm::{SwarmBuilder, SwarmEvent};
use libp2p_wasi_sockets::WasiTcpTransport;
use std::time::Duration;

#[wstd::main]
async fn main() {
    let keypair = libp2p_identity::Keypair::generate_ed25519();
    let mut swarm = SwarmBuilder::with_existing_identity(keypair)
        .with_other_transport(|_| WasiTcpTransport::default()).unwrap()
        .with_behaviour(|_| libp2p_ping::Behaviour::default()).unwrap()
        .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60)))
        .build();

    swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()).unwrap();

    loop {
        match swarm.select_next_some().await {
            SwarmEvent::NewListenAddr { address, .. } => eprintln!("listening on {address}"),
            SwarmEvent::Behaviour(event) => eprintln!("{event:?}"),
            _ => {}
        }
    }
}

Build and run:

rustup target add wasm32-wasip2

cargo build --release --target wasm32-wasip2

# -S inherit-network grants the component access to the host network.
# Without it, all dials return Error::AccessDenied.
wasmtime run -S inherit-network ./target/wasm32-wasip2/release/my_app.wasm

Supported multiaddrs

Pattern Status
/ip4/<addr>/tcp/<port>
/ip6/<addr>/tcp/<port>
…/p2p/<peer-id> suffix ✅ stripped, not dialled
/ip4/0.0.0.0/tcp/0 (ephemeral port)
/ip6/::/tcp/0 (ephemeral port)
/dns4, /dns6, /dnsaddr ❌ planned for v0.2
/quic-v1, /ws, /wss, /webrtc ❌ out of scope

Architecture

┌─────────────────────────────────────────┐
│  libp2p Swarm (user code)               │
│  Behaviour: ping / gossipsub / kad / …  │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────┴──────────────────────┐
│  Yamux (muxer) · Noise (security)       │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────┴──────────────────────┐
│  libp2p-wasi-sockets  (this crate)      │
│                                         │
│  WasiTcpTransport  impl Transport       │
│  WasiTcpStream     impl AsyncRead       │
│                         AsyncWrite      │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────┴──────────────────────┐
│  wstd 0.6  (wasi:sockets/tcp wrapper)   │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────┴──────────────────────┐
│  WASI 0.2 host (Wasmtime / Spin / …)    │
└─────────────────────────────────────────┘

Async bridge

wstd's TcpStream::read / write are async fns; futures::io::AsyncRead / AsyncWrite are poll-based. The bridge works by boxing each in-flight wstd operation as a Pin<Box<dyn Future>> and re-polling it on subsequent poll_* calls. On wasm32-wasip2 there are no threads, so the Send bound is satisfied via unsafe impl — safe because WASI resource handles are plain integers.


Pitfalls

Do not pull in libp2p-tcp

The umbrella libp2p crate gates libp2p-tcp off for all wasm targets, but if you pull it in manually the build will fail. Depend on sub-crates directly and verify:

cargo tree --target wasm32-wasip2 | grep libp2p-tcp
# must produce no output

Wasmtime network permissions

Wasmtime denies all network access by default:

# Grant full network access (development)
wasmtime run -S inherit-network my_app.wasm

# Grant only specific addresses (production)
wasmtime run --wasi tcp=127.0.0.1:4001 my_app.wasm

Requirements

  • Rust 1.83+
  • Target: wasm32-wasip2
  • WASI 0.2 host: Wasmtime ≥ 44, Spin ≥ 3.5, or any WASI 0.2.1-compatible runtime

License

Licensed under either of:

at your option. This matches the license of rust-libp2p.