---
title: "smolmix: TCP/UDP Over the Nym Mixnet"
description: "A userspace IP tunnel that provides standard TcpStream and UdpSocket types over the Nym mixnet. Compatible with the async tokio Rust ecosystem."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-04-29"
---

# smolmix

import { Callout } from 'nextra/components'
import { RUST_MSRV } from '../../components/versions'

`smolmix` is a TCP/UDP tunnel over the Nym mixnet. It uses a userspace network stack [`smoltcp`](https://docs.rs/smoltcp/latest/smoltcp/) to provide `TcpStream` and `UdpSocket` types that work with the async [`tokio`](https://docs.rs/tokio) Rust ecosystem e.g. [`tokio-rustls`](https://docs.rs/tokio-rustls), [`hyper`](https://docs.rs/hyper), [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite), etc.

The `TcpStream` type implements tokio's `AsyncRead`/`AsyncWrite` traits and `UdpSocket` provides `send_to`/`recv_from` for datagrams.

```text
┌──────────────────────────────────────────────────────────────┐
│  Your application (TLS, HTTP, WebSocket, DNS, etc.)          │
│       └─ smolmix::TcpStream / UdpSocket                      │
│            └─ smoltcp (userspace TCP/IP)                     │
│                 └─ Nym mixnet → IPR exit gateway → internet  │
└──────────────────────────────────────────────────────────────┘
```

Traffic exits the mixnet at an [IPR (Internet Packet Router)](/network/infrastructure/exit-services#ip-packet-router) exit gateway. The exit IP is the gateway's, not yours.

## Runtime and platform support
### Other runtimes
`smolmix` currently requires `tokio`. The internal pipeline is tokio-based: the bridge task that shuttles packets to the mixnet, the Nym SDK's `MixnetClient`, and the [`tokio-smoltcp`](https://docs.rs/tokio-smoltcp) reactor that drives the userspace TCP/IP stack all run on the tokio runtime.

This means `smolmix` is not directly compatible with alternative async runtimes like [`smol`](https://docs.rs/smol) or [`async-std`](https://docs.rs/async-std). If you need to use `smolmix` from another runtime, the [`async-compat`](https://docs.rs/async-compat) crate can bridge the gap.

### The WebAssembly build (browsers and WebViews)
The WebAssembly build of `smolmix` powers the mix-* packages [`mix-tunnel`](/developers/mix-tunnel), [`mix-fetch`](/developers/mix-fetch), [`mix-dns`](/developers/mix-dns), and [`mix-websocket`](/developers/mix-websocket). The wasm is inlined into `mix-tunnel`, so apps install one of those packages rather than building the crate themselves.

It runs in any JavaScript runtime that provides `WebSocket`, Web Workers, and WebAssembly. That means desktop browsers and mobile WebViews alike, so hybrid app shells (Capacitor, Ionic, Cordova, `react-native-webview`) are in scope, not just web pages. It does not run in a bare Node.js or React Native Hermes/JSC runtime, which lack Web Workers (and, for Hermes, WebAssembly). For native mobile or server targets, compile the Rust crate itself rather than using the wasm packages.

## Installation

Add `smolmix` to your `Cargo.toml`:

```toml
[dependencies]
smolmix = "1.21.1"
nym-bin-common = { version = "1.21.1", features = ["basic_tracing"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

`tokio` is a transitive dependency of smolmix, but you need to enable `rt-multi-thread` (the default runtime spun up by `#[tokio::main]`) and `macros` (for the `#[tokio::main]` attribute itself).

`nym-bin-common` is optional but recommended: it sets up [`tracing`](https://docs.rs/tracing) logging so you can see mixnet connection progress.

**Minimum Rust version:** {RUST_MSRV}+

`smolmix`'s API may still change between minor releases. Pin a version rather than tracking the latest, and read the changelog before bumping.

### From Git

For unreleased changes, import directly from the repository:

```toml
smolmix = { git = "https://github.com/nymtech/nym", branch = "develop" }
```

## When to use smolmix

| | smolmix | Stream module | mixFetch | SOCKS client |
|---|---|---|---|---|
| **Layer** | Transport (TCP/UDP) | Message (multiplexed streams) | HTTP | TCP (SOCKS proxy) |
| **Controls both sides?** | No (proxy mode) | Yes (E2E) | No (proxy mode) | No (proxy mode) |
| **API** | `TcpStream`, `UdpSocket` | `AsyncRead + AsyncWrite` | `fetch()` replacement | SOCKS4/5 protocol |
| **Composability** | Full: TLS, HTTP, WebSocket, DNS, etc. stack on top | Byte streams only | HTTP(S) only | Application-dependent |
| **Best for** | Reaching external services from Rust with standard networking | Peer-to-peer / E2E protocols between Nym clients | Browser HTTP requests | Legacy apps with SOCKS support |

## Security model

<Callout type="warning">
Traffic is Sphinx-encrypted inside the mixnet. Past the Exit Gateway, it travels over the public internet to the destination, the same as any other server-initiated connection. Protect the payload at the application layer with TLS ([`rustls`](https://docs.rs/rustls)), Noise Protocol ([`snow`](https://docs.rs/snow)), or equivalent, as you would on a direct connection.
</Callout>

The mixnet hides your identity from the destination and your destination from the network, but the IPR exit gateway sees the destination IP and port, and sees your payload only if you didn't encrypt it. If you connect with TLS (as in the [TCP example](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs)), the IPR only sees ciphertext addressed to the destination, not its contents. Plaintext HTTP is fully readable at the exit.

The full model (what each hop sees, trust boundaries, and a comparison with Tor and VPNs) is on [Exit security](/developers/concepts/exit-security).

## Examples

Runnable examples in [`smolmix/core/examples/`](https://github.com/nymtech/nym/tree/develop/smolmix/core/examples). Each is self-contained; read the `//!` doc comments at the top of each file for a walkthrough.

```sh
cargo run -p smolmix --example <name>
```

All examples accept `--ipr <ADDRESS>` to target a specific exit node (pass a `Recipient` address to `Tunnel::builder().ipr_address()`).

| Example | Source | What it demonstrates |
|---|---|---|
| UDP | [`udp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/udp.rs) | DNS lookup via [`hickory-proto`](https://docs.rs/hickory-proto), sending a raw UDP query to `1.1.1.1:53` through the mixnet. Runs a clearnet [`hickory-resolver`](https://docs.rs/hickory-resolver) lookup alongside it to compare resolved IPs and latency |
| TCP | [`tcp.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp.rs) | HTTPS request via [`hyper`](https://docs.rs/hyper) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Fetches Cloudflare's `/cdn-cgi/trace` to show that the exit IP differs from clearnet |
| WebSocket | [`websocket.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/websocket.rs) | WebSocket echo against `ws.postman-echo.com` via [`tokio-tungstenite`](https://docs.rs/tokio-tungstenite) + [`tokio-rustls`](https://docs.rs/tokio-rustls). Runs the same stack over clearnet first, so the only thing that changes between runs is the underlying TCP stream |
| TCP download | [`tcp_download.rs`](https://github.com/nymtech/nym/blob/develop/smolmix/core/examples/tcp_download.rs) | DNS-over-mixnet + multi-request HTTP/1.1 download over a single keep-alive connection, the full real-world pattern |

## Architecture

The internal stack (smoltcp reactor, device adapter, bridge task, packet flow) is documented in [`ARCHITECTURE.md`](https://github.com/nymtech/nym/blob/develop/smolmix/core/src/ARCHITECTURE.md). This is also the crate-level documentation on docs.rs.

## API reference

Full API documentation is available on [docs.rs/smolmix](https://docs.rs/smolmix).
