# wasmnet
[](https://crates.io/crates/wasmnet)
[](https://docs.rs/wasmnet)
[](https://crates.io/crates/wasmnet)
[](LICENSE)
Networking proxy for browser WASM — bridges WASI socket APIs to real TCP via WebSocket.
Browser WASM cannot do raw TCP/UDP. wasmnet runs server-side and provides real network I/O on behalf of browser WASM programs, with policy controls.
```sh
Browser WASM -> WebSocket -> wasmnet server -> TCP -> Internet
```
## Install
### Server (Rust)
```bash
cargo install wasmnet
```
### Browser Client (npm)
```bash
npm install @anistark/wasmnet-client
```
## Quick Start
### Standalone Server
```bash
# Default policy (blocks private IPs, allows public)
wasmnet-server
# Custom port
wasmnet-server --port 8420
# Custom policy file
wasmnet-server --policy policy.toml
# No restrictions
wasmnet-server --no-policy
```
### Browser Client
[](https://www.npmjs.com/package/@anistark/wasmnet-client)
[](https://www.npmjs.com/package/@anistark/wasmnet-client)
```javascript
import { WasmnetClient } from '@anistark/wasmnet-client';
const client = new WasmnetClient('ws://localhost:9000');
await client.ready();
// Outbound TCP
const id = await client.connect('api.example.com', 443);
client.onData(id, (data) => console.log('received:', data));
client.send(id, 'GET / HTTP/1.1\r\nHost: api.example.com\r\n\r\n');
// Inbound TCP (bind a port)
const listener = await client.bind('0.0.0.0', 3000);
client.onAccept(listener.id, (connId, remote) => {
client.onData(connId, (data) => client.send(connId, data));
});
```
### Library (Embed in Rust)
```rust
use wasmnet::Server;
// Builder API
let server = Server::builder()
.host("0.0.0.0")
.port(9000)
.policy_file("policy.toml")?
.build()?;
server.listen().await?;
// Or with graceful shutdown
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let server = Server::builder().no_policy().build()?;
server.listen_with_shutdown(shutdown_rx).await?;
// Direct construction
use wasmnet::policy::PolicyConfig;
let addr = "0.0.0.0:9000".parse().unwrap();
let server = Server::from_config(PolicyConfig::default(), addr);
// Upgrade a single TCP stream (for embedding in existing servers)
use wasmnet::{handle_ws_upgrade, policy::Policy};
use std::sync::Arc;
let policy = Arc::new(Policy::allow_all());
handle_ws_upgrade(tcp_stream, policy).await;
```
## Protocol
JSON messages over a single WebSocket connection.
### Requests (browser → server)
| `connect` | `id`, `addr`, `port` | Outbound TCP connection |
| `bind` | `id`, `addr`, `port` | Bind a local TCP port |
| `listen` | `id`, `backlog?` | Start accepting connections |
| `send` | `id`, `data` (base64) | Send data on a socket |
| `close` | `id` | Close a socket or listener |
### Events (server → browser)
| `connected` | `id` | Connection established |
| `data` | `id`, `data` (base64) | Data received |
| `listening` | `id`, `port` | Listener bound |
| `accepted` | `id`, `conn_id`, `remote` | New inbound connection |
| `closed` | `id` | Socket closed |
| `error` | `id`, `msg` | Error occurred |
| `denied` | `id`, `msg` | Blocked by policy |
## Policy
Default policy blocks private IP ranges and allows all public addresses. Customize via TOML:
```toml
[network]
deny = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"]
allow = ["*"]
bind_ports = "3000-9999"
max_connections = 50
connection_timeout_secs = 30
```
### Deny-by-default mode
```toml
[network]
deny = ["*"]
allow = ["api.example.com:443", "*.github.com:443"]
max_connections = 5
```
See [`policy.example.toml`](policy.example.toml) for a full example.
## Architecture
```sh
┌─ Browser WASM ──────────────────────────────────┐
│ WASI socket call → serialize → WebSocket.send │
└──────────────────────┬──────────────────────────┘
│ WebSocket
┌──────────────────────▼──────────────────────────┐
│ wasmnet server │
│ 1. Policy check (allow/deny, rate limits) │
│ 2. Real TCP connect/bind │
│ 3. Bidirectional proxy: WS frames ↔ TCP bytes │
└─────────────────────────────────────────────────┘
```