Transports
Pluggable transport options for relay hops
Each relay hop can independently use a different transport. The transport is configured per-node in the chain definition and communicated via handshake metadata.
Available Transports
| Transport | Description | Use Case |
|---|---|---|
tls | TLS 1.2/1.3 encryption | Public internet hops |
plain | Raw TCP (no encryption) | Trusted networks, localhost, WireGuard tunnels |
Configuring Transports
In Chain Nodes (Entry Node)
[chains.example]
nodes = [
# First hop: TLS with custom SNI
{ addr = "relay-1.example.com:443", password = "pw1", transport = "tls", sni = "cdn.example.com" },
# Second hop: plain TCP (trusted network)
{ addr = "relay-2.internal:8080", password = "pw2", transport = "plain" },
]In Relay Node (Inbound)
[relay]
listen = "0.0.0.0:443"
transport = "tls" # What this relay acceptsTLS Transport
- Auto-generates self-signed ECDSA P-256 certificates at startup
- Certificate verification is skipped between relay nodes
- SNI can be configured per-hop to disguise traffic
- Optionally load certificates from files:
[relay.tls]
cert = "/path/to/cert.pem"
key = "/path/to/key.pem"Plain TCP Transport
- Zero overhead, no encryption
- Use only on trusted networks (localhost, VPN, WireGuard)
- The last hop to the exit server always uses plain TCP (client handles end-to-end TLS)
Transport Selection Logic
The entry node determines the transport for each hop:
- First hop: Uses the transport specified in
chain.nodes[0].transport - Intermediate hops: The handshake metadata tells the relay which transport to use for the outbound connection
- Last hop (to exit server): Always plain TCP
A ──TLS──▶ B1 ──plain──▶ B2 ──plain──▶ C(trojan-server)
▲
│
Client ────────────── end-to-end TLS ────┘