wsproxy 0.1.3

WebSocket proxy for TCP connections
Documentation

wsproxy

A WebSocket proxy for TCP connections. Forward TCP traffic through WebSocket tunnels.

Architecture

TCP Client <---> ProxyClient <--WebSocket--> ProxyServer <---> TCP Server
  • ProxyServer: Listens for WebSocket connections and forwards data to TCP targets. Supports routing different URL paths to different backends.
  • ProxyClient: Listens for TCP connections and forwards data through WebSocket to the server.

Installation

cargo install --path .

Usage

Command Line

Server with a single default target:

wsproxy server --listen 0.0.0.0:8080 --default-target 127.0.0.1:22

Server with multiple routes:

wsproxy server --listen 0.0.0.0:8080 \
  --route /ssh=127.0.0.1:22 \
  --route /db=127.0.0.1:5432 \
  --route /redis=127.0.0.1:6379

Server with TLS (WSS):

wsproxy server --listen 0.0.0.0:8443 \
  --default-target 127.0.0.1:22 \
  --tls-cert cert.pem \
  --tls-key key.pem

Server with auto-generated self-signed certificate:

wsproxy server --listen 0.0.0.0:8443 \
  --default-target 127.0.0.1:22 \
  --tls-self-signed

Client:

wsproxy client --listen 127.0.0.1:2222 --server ws://proxy-server:8080/ssh

Client connecting to WSS server:

wsproxy client --listen 127.0.0.1:2222 --server wss://proxy-server:8443/ssh

Client with self-signed certificate (insecure mode):

wsproxy client --listen 127.0.0.1:2222 --server wss://proxy-server:8443/ssh --insecure

Client with custom CA certificate:

wsproxy client --listen 127.0.0.1:2222 --server wss://proxy-server:8443/ssh \
  --tls-ca-cert /path/to/ca.pem

SSH ProxyCommand (Tunnel Mode)

The tunnel command connects stdin/stdout directly to a WebSocket server, making it perfect for SSH's ProxyCommand option:

Direct tunnel:

wsproxy tunnel --server ws://proxy-server:8080/ssh

SSH config example:

Host myserver
    ProxyCommand wsproxy tunnel --server wss://proxy:8443/ssh
    User myuser

With self-signed certificate:

Host myserver
    ProxyCommand wsproxy tunnel --server wss://proxy:8443/ssh --insecure
    User myuser

Now ssh myserver will tunnel through the WebSocket proxy.

Daemon Mode

Run the server or client as a background daemon with automatic restart on failure:

Start a server daemon:

wsproxy daemon server --listen 0.0.0.0:8080 --default-target 127.0.0.1:22

Start a server daemon with TLS:

wsproxy daemon server --listen 0.0.0.0:8443 --default-target 127.0.0.1:22 \
  --tls-cert cert.pem --tls-key key.pem

Start a client daemon:

wsproxy daemon client --listen 127.0.0.1:2222 --server ws://proxy-server:8080/ssh

List running daemons:

wsproxy daemon list

Output:

ID   PID      ARGUMENTS
--------------------------------------------------
1    12345    server --listen 0.0.0.0:8080 --default-target 127.0.0.1:22
2    12346    client --listen 127.0.0.1:2222 --server ws://proxy-server:8080/ssh

Kill a daemon:

wsproxy daemon kill 1

Daemons automatically restart with exponential backoff (1ms to 5 minutes) if the underlying process crashes.

Configuration File

Instead of command-line arguments, you can use a TOML configuration file with hot-reload support. When the config file changes, the server automatically picks up the new configuration without dropping active connections.

Start server with config file:

wsproxy server --config server.toml

Example configuration file (server.toml):

listen = "0.0.0.0:8080"
default_target = "localhost:22"

[routes]
"/ssh" = "ssh-server.internal:22"
"/db" = "db.example.com:5432"
"/redis" = "127.0.0.1:6379"

[tls]
cert = "cert.pem"
key = "key.pem"
# Or use: self_signed = true

Hostnames are resolved using DNS when each connection is established, allowing DNS changes to take effect without restarting the server.

Hot-reload behavior:

  • Routes and default_target changes: Applied instantly. Existing connections continue uninterrupted; new connections use the updated routing.
  • Listen address or TLS changes: Server automatically restarts. Existing connections continue until they complete naturally.
  • Invalid configuration: If the config file has syntax errors or invalid values, the error is logged and the server continues running with the previous valid configuration. Existing connections are not affected.

Daemon mode with config:

wsproxy daemon server --config server.toml

Note: The --config flag cannot be combined with other server options (--listen, --route, --default-target, --tls-*).

Library

use wsproxy::{ProxyServer, ProxyClient, TlsOptions};

#[tokio::main]
async fn main() -> wsproxy::Result<()> {
    // Server with multiple routes
    let server = ProxyServer::builder()
        .route("/ssh", "127.0.0.1:22")?
        .route("/db", "127.0.0.1:5432")?
        .bind("0.0.0.0:8080")?;

    // Server with TLS (WSS)
    let secure_server = ProxyServer::builder()
        .default_target("127.0.0.1:22")?
        .tls("cert.pem", "key.pem")
        .bind("0.0.0.0:8443")?;

    // Server with auto-generated self-signed certificate
    let dev_server = ProxyServer::builder()
        .default_target("127.0.0.1:22")?
        .tls_self_signed()
        .bind("0.0.0.0:8443")?;

    // Client (supports both ws:// and wss://)
    let client = ProxyClient::bind(
        "127.0.0.1:2222",
        "wss://proxy-server:8443/ssh",
        TlsOptions::default(),
    )?;

    // Client with custom CA certificate (for self-signed servers)
    let client_custom_ca = ProxyClient::bind(
        "127.0.0.1:2222",
        "wss://proxy-server:8443/ssh",
        TlsOptions {
            insecure: false,
            ca_cert_path: Some("/path/to/ca.pem".to_string()),
        },
    )?;

    // Run server with config file (supports hot-reload)
    // wsproxy::server::run_with_config("server.toml").await?;

    // Run (typically in separate processes)
    // server.run().await?;
    // client.run().await?;
    Ok(())
}

Example: Simple Chat with netcat

This example demonstrates a simple chat through the WebSocket proxy using nc (netcat).

Terminal 1 - Start a TCP listener (the "chat server"):

nc -l 9000

Terminal 2 - Start the proxy server and client as daemons:

wsproxy daemon server --listen 127.0.0.1:8080 --default-target 127.0.0.1:9000
wsproxy daemon client --listen 127.0.0.1:2222 --server ws://127.0.0.1:8080

Terminal 2 - Connect to the proxy:

nc 127.0.0.1 2222

Now you can type messages in Terminal 2 and they will appear in Terminal 1 (and vice versa). The data flows:

Terminal 2 (nc) -> ProxyClient (:2222) -> WebSocket -> ProxyServer (:8080) -> Terminal 1 (nc :9000)

Clean up - kill the daemons:

wsproxy daemon list   # See running daemons
wsproxy daemon kill 1 # Kill server
wsproxy daemon kill 2 # Kill client

License

MIT License - see LICENSE for details.