f648349e82
* Diatixisify! * First pass at Typedoc generation for TS SDK * Remove overview pages * Fix typos and remove codebase references from docs Fix typos across network and developer docs: Quorum, available, cryptosystem, transaction, proportional, Standalone. Remove TODO placeholder from dVPN protocol page. Strip GitHub source links from network docs to decouple documentation from repo structure. * Expand thin landing pages across network and developer docs - Add intro content to network overview, infrastructure, and reference landing pages - Expand developer index with "where to start" guide - Add usage instructions and explanations to all five TS playground pages - Expand WebSocket client page with setup and message format examples * Restructure Rust SDK developer docs - Delete redundant mixnet example, message-helpers, and message-types subpages - Delete client-pool architecture and example subpages (content folded into landing) - Delete tcpproxy troubleshooting (folded into landing page) - Add deprecation notices to TcpProxy pages, pointing to Stream module - Add stream module docs: landing page, architecture, tutorial, and 4 example pages - Add mixnet and client-pool tutorials - Add SDK tour page - Update navigation and landing pages with docs.rs links * Restructure TS SDK developer docs - Merge overview, installation, and getting started into TS SDK landing page - Fold FAQ content into bundling/troubleshooting section - Delete redundant overview, installation, start, and FAQ pages - Update internal links in browsers.mdx and native.mdx - Update navigation and example page imports * Flatten and expand APIs section - Collapse nested API subpages into single pages with inline Redoc embeds - Rewrite introduction as landing page with decision table - Add endpoint categories, quick curl examples to each API page - Mark Explorer API as deprecated - Move NS API deployment guide to operators/performance-and-testing - Fix dangling /apis/nym-api/mainnet link in network-components - Remove sandbox endpoints from all API pages * Add redirects for moved and deleted pages - Add 25 redirects covering TS SDK, Rust SDK, APIs, and network sections - Fix dangling /developers/typescript/start link in operators changelog * Replace individual example doc pages with GitHub-linked tables, expand tutorials - replace individual example doc pages with GitHub-linked tables - expand mixnet tutorial with persistent identity and split_sender sections - add tcpproxy tutorial - rename "API Reference" to "TypeDoc Reference" in TS SDK sidebar - rename "Misc" to "Extras" in developer sidebar, move VPN CLI up - remove echo server from tools - update message-queue callout to reference actual modules - fix mixnet/examples redirect collision * Add SEO frontmatter, validate encryption standards, clean up URLs - add title/description/schemaType/section/lastUpdated frontmatter to 48 pages across developers, network, and APIs sections - remove network/.archive/ directory (compare against develop instead) - update nymtech.net → nym.com for website/blog links (keep infra URLs) - add native proxy "in progress" callout for Rust/C/Go * API-scraper update (#6598) * read nodes and locations * update python-prebuild.sh * Address PR #6494 review feedback - Use "mode" consistently instead of "role" on nym-nodes page - Replace "staking" with "bonding" for NYM token collateral - Wire up auto-scraped node counts via TimeNow + nodes-count.json - Fix broken licensing images: download CC icons locally, replace inline HTML - Fix 9 stale redirects pointing through deleted /network/architecture path * Fix linkcheck errors - Fix stale cross-links: /network/concepts/ → /network/mixnet-mode/ - Replace README.md references with globals.md in TypeDoc output - Add entryFileName: globals to typedoc.json configs to prevent recurrence * Fix remaining stale /network/architecture links - zk-nym-overview: architecture/nyx#nym-api → /network/infrastructure/nyx#nym-api - setup: network/architecture → /network/overview * Remove accidentally re-included architecture.md file from rebase * Standardize tutorials, document examples, add llms.txt, apply tone fixes - Expand Rust SDK tutorials with step-by-step structure; document all SDK examples across mixnet, client-pool, and tcpproxy pages - Add llms.txt generation script, wire into build and CI workflows - Apply tone/style fixes: deduplicate callouts, vary sentence structure, standardize voice consistency across changed pages * Consolidate redundant network overview docs * Trim dev docs: git-first imports, stream notice, collapse TcpProxy * Update tutorial * Refresh auto-generated API and command outputs * Update network section docs * Update developer and API docs: reusable components, stream protocol, conventions, tutorial fixes * Fix Rust SDK tutorial bugs: setup_env, port conflicts, logging, open_stream race condition * Update stream.mdx * Remove docs.rs link from Stream overview for the moment * add llms.txt and llms-full.txt note to readme --------- Co-authored-by: import this <97586125+serinko@users.noreply.github.com>
256 lines
9.3 KiB
Plaintext
256 lines
9.3 KiB
Plaintext
---
|
||
title: "Nym TcpProxy: Route TCP via the Mixnet"
|
||
description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Covers architecture, single and multi-connection patterns, and troubleshooting."
|
||
schemaType: "TechArticle"
|
||
section: "Developers"
|
||
lastUpdated: "2026-03-27"
|
||
---
|
||
|
||
# TcpProxy Module
|
||
import { Callout } from 'nextra/components';
|
||
import { CodeVerified } from '../../../components/code-verified'
|
||
|
||
<Callout type="error">
|
||
**This module is unmaintained.** The TcpProxy is no longer actively developed in favour of the [Stream module](./stream), which provides `AsyncRead + AsyncWrite` streams directly over the Mixnet without the TCP socket overhead. Existing users should plan to migrate to streams when possible. The TcpProxy will continue to work but will not receive new features or bug fixes.
|
||
</Callout>
|
||
|
||
The Stream module offers the same key benefit (familiar I/O patterns on top of the Mixnet) with a simpler API. Streams multiplex connections on a single client, eliminate the localhost socket overhead, and now include sequence-based message reordering. There is no remaining reason to choose TcpProxy over Streams for new projects.
|
||
|
||
---
|
||
|
||
`NymProxyClient` and `NymProxyServer` proxy TCP traffic through the Mixnet. Both run in a background thread and expose a configurable `localhost` socket that you read and write to like any other TCP connection.
|
||
|
||
> Non-Rust/Go developers who want to experiment with this module can start with the [standalone binaries](../tools/standalone-tcpproxy).
|
||
|
||
## Examples
|
||
|
||
| Example | Source |
|
||
|---|---|
|
||
| Single connection | [`tcp_proxy_single_connection.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_single_connection.rs) |
|
||
| Multiple connections | [`tcp_proxy_multistream.rs`](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/tcp_proxy_multistream.rs) |
|
||
|
||
```bash
|
||
cargo run --example tcp_proxy_single_connection
|
||
cargo run --example tcp_proxy_multistream
|
||
```
|
||
|
||
## API reference
|
||
|
||
- [API reference on docs.rs](https://docs.rs/nym-sdk/latest/nym_sdk/tcp_proxy/): architecture overview, client/server examples, and type documentation
|
||
|
||
## Tutorial
|
||
|
||
<CodeVerified />
|
||
|
||
Set up the project:
|
||
|
||
```sh
|
||
cargo init nym-tcp-proxy
|
||
cd nym-tcp-proxy
|
||
rm src/main.rs
|
||
```
|
||
|
||
Add dependencies to `Cargo.toml`:
|
||
|
||
```toml
|
||
[dependencies]
|
||
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "4077717" }
|
||
nym-network-defaults = { git = "https://github.com/nymtech/nym", rev = "4077717" }
|
||
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "4077717", features = ["basic_tracing"] }
|
||
tokio = { version = "1", features = ["full"] }
|
||
anyhow = "1"
|
||
blake3 = "=1.7.0" # required pin — see https://nymtech.net/developers/rust/importing
|
||
|
||
[[bin]]
|
||
name = "proxy_server"
|
||
path = "src/bin/proxy_server.rs"
|
||
|
||
[[bin]]
|
||
name = "proxy_client"
|
||
path = "src/bin/proxy_client.rs"
|
||
```
|
||
|
||
### Server
|
||
|
||
The server connects to the Mixnet and forwards incoming traffic to a local TCP service (e.g. a web server on port 8000).
|
||
|
||
```rust
|
||
use nym_sdk::tcp_proxy::NymProxyServer;
|
||
|
||
#[tokio::main]
|
||
async fn main() -> anyhow::Result<()> {
|
||
nym_bin_common::logging::setup_tracing_logger();
|
||
|
||
let mut server = NymProxyServer::new(
|
||
"127.0.0.1:8000", // upstream address (host:port)
|
||
"./proxy-server-config", // config directory for persistent keys
|
||
None, // env file (None = mainnet)
|
||
None, // gateway (None = auto-select)
|
||
).await?;
|
||
|
||
println!("Proxy server address: {}", server.nym_address());
|
||
server.run_with_shutdown().await?;
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
### Client
|
||
|
||
The client opens a localhost TCP socket and tunnels all traffic through the Mixnet to the server.
|
||
|
||
```rust
|
||
use nym_sdk::tcp_proxy::NymProxyClient;
|
||
use nym_sdk::mixnet::Recipient;
|
||
use nym_network_defaults::setup_env;
|
||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||
use tokio::net::TcpStream;
|
||
|
||
#[tokio::main]
|
||
async fn main() -> anyhow::Result<()> {
|
||
nym_bin_common::logging::setup_tracing_logger();
|
||
// Load mainnet network defaults into env vars (required by NymProxyClient's internal ClientPool)
|
||
setup_env(None::<String>);
|
||
|
||
let server_addr: Recipient = std::env::args()
|
||
.nth(1).expect("Usage: proxy_client <SERVER_NYM_ADDRESS>")
|
||
.parse()?;
|
||
|
||
let client = NymProxyClient::new(
|
||
server_addr,
|
||
"127.0.0.1", // listen host
|
||
"8070", // listen port
|
||
60, // close timeout (seconds)
|
||
None, // env file (None = mainnet)
|
||
1, // client pool size
|
||
).await?;
|
||
|
||
let proxy = tokio::spawn(async move { client.run().await });
|
||
|
||
// Wait for the pool to create a client and the proxy to be ready.
|
||
// The first startup takes ~10-15s while the client connects to the Mixnet.
|
||
println!("Waiting for proxy to be ready...");
|
||
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
|
||
|
||
let mut stream = TcpStream::connect("127.0.0.1:8070").await?;
|
||
stream.write_all(b"GET / HTTP/1.0\r\nHost: localhost\r\n\r\n").await?;
|
||
|
||
let mut response = Vec::new();
|
||
stream.read_to_end(&mut response).await?;
|
||
println!("Response:\n{}", String::from_utf8_lossy(&response));
|
||
|
||
drop(stream);
|
||
proxy.abort();
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
### Run it
|
||
|
||
Start an upstream TCP service (e.g. a simple HTTP server):
|
||
|
||
```sh
|
||
python3 -m http.server 8000
|
||
```
|
||
|
||
In a second terminal, start the proxy server:
|
||
|
||
```sh
|
||
RUST_LOG=info cargo run --bin proxy_server
|
||
```
|
||
|
||
Copy the Nym address it prints, then in a third terminal:
|
||
|
||
```sh
|
||
RUST_LOG=info cargo run --bin proxy_client -- <SERVER_NYM_ADDRESS>
|
||
```
|
||
|
||
The response will take 30–60 seconds to arrive as it traverses the Mixnet in both directions.
|
||
|
||
## Architecture
|
||
|
||
Each sub-module handles Nym clients differently:
|
||
- **`NymProxyClient`** relies on the [Client Pool](./client-pool) to create clients and keep a reserve. If incoming TCP connections outpace the pool, it creates an ephemeral client per connection. One client maps to one TCP connection.
|
||
- **`NymProxyServer`** has a single Nym client with a persistent identity.
|
||
|
||
### Sessions & message ordering
|
||
|
||
Messages are wrapped in a session ID per connection, with individual messages given an incrementing message ID. Once all messages are sent, the client sends a `Close` message to notify the server that there are no more outbound messages for this session.
|
||
|
||
> Session management and message IDs are necessary since *the Mixnet guarantees message delivery but not message ordering*: in the case of trying to e.g. send gRPC protobuf through the Mixnet, ordering is required so that a buffer is not split across Sphinx packet payloads, and that the 2nd half of the frame is not passed upstream to the parser before the 1st half.
|
||
|
||
The key data structure:
|
||
|
||
```rust
|
||
pub struct ProxiedMessage {
|
||
message: Payload,
|
||
session_id: Uuid,
|
||
message_id: u16,
|
||
}
|
||
```
|
||
|
||
### Full request/response flow
|
||
|
||
```mermaid
|
||
---
|
||
config:
|
||
theme: neo-dark
|
||
layout: elk
|
||
---
|
||
sequenceDiagram
|
||
box Local Machine
|
||
participant Client Process
|
||
participant NymProxyClient
|
||
end
|
||
Client Process->>NymProxyClient: Request bytes
|
||
NymProxyClient->>NymProxyClient: New session
|
||
NymProxyClient->>Entry Gateway: Sphinx Packets: Message 1
|
||
Entry Gateway-->>NymProxyClient: Acks
|
||
NymProxyClient->>Entry Gateway: Sphinx Packets: Message 2
|
||
Entry Gateway-->>NymProxyClient: Acks
|
||
NymProxyClient->>Entry Gateway: Sphinx Packets: Close Message
|
||
Entry Gateway-->>NymProxyClient: Acks
|
||
|
||
Entry Gateway-->>Mix Nodes: All Packets, Acks, etc
|
||
Note right of Mix Nodes: We are omitting the 3 hops etc for brevity here
|
||
Mix Nodes-->> Exit Gateway: All Packets, Acks, etc
|
||
|
||
Exit Gateway->>NymProxyServer: Sphinx Packets: Message 2
|
||
NymProxyServer-->>Exit Gateway: Acks
|
||
loop Message Buffer
|
||
NymProxyServer->>NymProxyServer: Wait for Message 1
|
||
Exit Gateway->>NymProxyServer: Sphinx Packets: Message 1
|
||
NymProxyServer-->>Exit Gateway: Acks
|
||
NymProxyServer->>NymProxyServer: Message Received: trigger upstream send
|
||
end
|
||
Note right of NymProxyServer: Note this happens **per session**
|
||
NymProxyServer->>Upstream Process: Reconstructed request bytes
|
||
Upstream Process->>Upstream Process: Do something with request
|
||
Exit Gateway->>NymProxyServer: Sphinx Packets: Close Message
|
||
NymProxyServer-->>Exit Gateway: Acks
|
||
NymProxyServer->>NymProxyServer: Trigger Client timeout start for session
|
||
Upstream Process->>NymProxyServer: Response bytes
|
||
NymProxyServer->>NymProxyServer: Write to provided SURB payloads
|
||
NymProxyServer->>Exit Gateway: Anonymous replies
|
||
box Remote Host
|
||
participant NymProxyServer
|
||
participant Upstream Process
|
||
end
|
||
|
||
Entry Gateway->>NymProxyClient: Sphinx Packets: Reply Message 2
|
||
NymProxyClient-->Entry Gateway: Ack
|
||
Loop Message Buffer:
|
||
NymProxyClient->>NymProxyClient: Wait for Message 1
|
||
Entry Gateway->>NymProxyClient: Sphinx Packets: Message 1
|
||
NymProxyClient-->>Entry Gateway: Acks
|
||
NymProxyClient->>NymProxyClient: Message Received: trigger send
|
||
NymProxyClient->>Client Process: Response bytes
|
||
end
|
||
Note right of NymProxyClient: Note this happens **per session**
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### Lots of `duplicate fragment received` messages
|
||
|
||
`WARN` level logs about duplicate fragments are caused by Mixnet-level packet retransmission, where both the original and the retransmitted copy arrive at the destination. This is expected behaviour, not a bug in the client or TcpProxy module.
|