Move any traffic through a single WebSocket.
TunnelVision is a high-performance tunneling engine written in Go. It hides arbitrary TCP, UDP, SOCKS5, and HTTP-proxy traffic inside ordinary WebSocket or HTTP/2 streams — the kind of traffic every firewall, corporate proxy, and DPI box already lets through. If a network permits HTTPS, it permits TunnelVision.
It ships as a single static binary, runs as either end of a tunnel, and drops in as a Go library when you want to build tunneling into your own software.
- Goes where plain sockets can't. Restrictive gateways see nothing but a long-lived WebSocket or an HTTP/2 POST. Your SSH session, database connection, game traffic, or WireGuard handshake rides inside it.
- Speaks a lot of protocols. TCP, UDP, SOCKS5, HTTP CONNECT, Unix sockets, stdio, and Linux transparent proxying — forward or reverse.
- Built for scale. Every tunnel is a handful of goroutines. Thousands of concurrent connections stay cheap.
- Secure by construction. TLS and mTLS, ECH, JWT-authenticated tunnels, and server-side rules that decide exactly what a client is allowed to reach.
- Operator-friendly. Structured
sloglogging, hot-reloadable certificates, self-signed bootstrap, systemd and Windows service tooling, and a Caddy module. - Embeddable. Clean package layout so
pkg/client,pkg/server, andpkg/protocolcompose into your own binaries.
Grab a binary from Releases, or build it yourself:
git clone https://github.com/DsThakurRawat/TunnelVision.git
cd TunnelVision
make build # -> ./bin/tunnelvision
# or: go build -o tunnelvision ./cmd/tunnelvision (needs Go 1.25+)Stand up a server, then reach a service through it:
# On the far side of the firewall:
tunnelvision server --tls wss://0.0.0.0:8443
# On your machine — expose the remote's Postgres on a local port:
tunnelvision client -L tcp://5432:db.internal:5432 wss://gateway.example.com:8443psql -h 127.0.0.1 -p 5432 now talks to db.internal:5432, tunneled over TLS-wrapped WebSocket.
A tunnel is written as scheme://[listen:]host:port[?options].
# Local SOCKS5 proxy, everything routed out through the server
tunnelvision client -L socks5://127.0.0.1:1080 wss://gateway.example.com
# Local HTTP CONNECT proxy
tunnelvision client -L http://127.0.0.1:3128 wss://gateway.example.com
# Point a single local port at one remote destination
tunnelvision client -L tcp://8080:example.com:443 wss://gateway.example.com
# UDP, e.g. DNS — reclaim idle sessions after 15s
tunnelvision client -L "udp://5353:1.1.1.1:53?timeout_sec=15" wss://gateway.example.com
# Hand the real client IP to the backend with a PROXY v2 header
tunnelvision client -L "tcp://8080:backend.internal:80?proxy_protocol" wss://gateway.example.com
# Unix socket -> remote unix socket; and stdio (great as an SSH ProxyCommand)
tunnelvision client -L unix:///tmp/local.sock:/var/run/app.sock wss://gateway.example.com
tunnelvision client -L stdio://remote-host:22 wss://gateway.example.comSchemes: tcp, udp, socks5, http, unix, stdio, tproxy+tcp, tproxy+udp.
Query options:
| Option | Effect |
|---|---|
?proxy_protocol |
Prepend a HAProxy PROXY v2 header to the target (TCP forward tunnels), so backends log the originating client. |
?timeout_sec=N |
Idle timeout for UDP sessions, seconds. Default 30; 0 disables. |
?login=U&password=P |
Credentials required by a socks5 / http proxy listener. |
# Expose the client's local web server on the server's port 8080
tunnelvision client -R tcp://8080:127.0.0.1:80 wss://gateway.example.com
# Turn the client into an exit node: a SOCKS5 proxy on the server,
# dynamic destinations dialed from the client's network
tunnelvision client -R socks5://0.0.0.0:1080 wss://gateway.example.com
# Same idea over HTTP CONNECT
tunnelvision client -R http://0.0.0.0:3128 wss://gateway.example.comReverse schemes: tcp, udp, unix, socks5, http.
Heads-up on dynamic reverse tunnels. Reverse
udp,socks5, andhttpcarry the per-connection target in an in-band frame between TunnelVision's own client and server. They work Go-to-Go only — the same way--mode wsdoes — and are not wire-compatible with other implementations. Static reversetcp/unixare fully interoperable.
tproxy+tcp and tproxy+udp intercept traffic without the application knowing. They need Linux, CAP_NET_ADMIN (or root), and an iptables rule pointing at the listener:
tunnelvision client -L tproxy+tcp://0.0.0.0:1234 wss://gateway.example.com# True TPROXY (mark-based) on port 1234:
iptables -t mangle -A PREROUTING -p tcp --dport 80 \
-j TPROXY --on-port 1234 --tproxy-mark 0x1/0x1
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100-j REDIRECT (DNAT) setups work too — TunnelVision reads the original destination from the socket and falls back to SO_ORIGINAL_DST when needed. UDP recovers each datagram's original destination via IP_RECVORIGDSTADDR.
# Bootstrap TLS instantly with an in-memory self-signed certificate
tunnelvision server --tls wss://0.0.0.0:8443
# Bring your own certificate and require client certs (mTLS)
tunnelvision server \
--tls-certificate cert.pem --tls-private-key key.pem \
--tls-client-ca-certs ca.pem \
--restrict-config rules.yaml wss://0.0.0.0:8443
# Rotate certificates without downtime — replace the files, then:
kill -HUP "$(pidof tunnelvision)" # also picks up changes automatically within a few seconds- TLS / mTLS — full verification, client certificates, both ends.
- Self-signed bootstrap —
--tlswith no cert/key generates an ephemeral certificate in memory; clients trust it or disable verification. - Hot-reload — server and client certificates reload on file change (polling) or on
SIGHUP. A bad reload is ignored and the last-good certificate stays live. - ECH & SNI control — Encrypted Client Hello, plus SNI override or suppression.
- JWT-authenticated tunnels — every tunnel request is a signed token describing exactly what it may open.
- Server-side rules — a YAML policy restricts which destinations, ports, and path prefixes a client can use.
TunnelVision negotiates one of three carriers:
| Transport | Select with | Notes |
|---|---|---|
| WebSocket (default) | ws:// / wss:// |
Battle-tested framing with deliberate deviations for broad compatibility. |
| Strict RFC 6455 | --mode ws |
Standards-clean WebSocket; interoperates with generic Go WebSocket clients. |
| HTTP/2 | http:// / https:// |
Full-duplex streaming over an HTTP/2 POST. |
systemd — template units manage client and server from config files:
# /etc/tunnelvision/client-myserver.yaml
sudo systemctl enable --now tunnelvision-client@myserver
# /etc/tunnelvision/server-main.yaml
sudo systemctl enable --now tunnelvision-server@mainWindows — register a background task with the bundled PowerShell scripts:
.\packaging\windows\install.ps1 -ConfigPath "C:\path\client.yaml" -BinaryPath "C:\path\tunnelvision.exe"
.\packaging\windows\control.ps1 -Action startCaddy — build TunnelVision into Caddy and let it terminate TLS (including mTLS):
xcaddy build --with github.com/DsThakurRawat/TunnelVision/pkg/caddy{
order tunnelvision before reverse_proxy
}
example.com {
route /tunnelvision/* {
tunnelvision {
prefix /tunnelvision
mode ws
# restrict_config /etc/tunnelvision/rules.yaml
}
}
}Packages — .deb, .rpm, and .apk are attached to each release. Docker images are on the roadmap.
Configure by flags, environment, or a YAML file (--config).
Global flags
--config— path to a YAML config file.--log-lvl—TRACE…ERROR/OFF(defaultINFO).--no-color— plain log output.--nb-worker-threads— accepted for compatibility (TOKIO_WORKER_THREADS); a no-op in Go.
Client flags
-L, --local-to-remote,-R, --remote-to-local— define tunnels.--mode— default orws(strict RFC 6455).--http-upgrade-path-prefix— upgrade path prefix (defaultv1).--jwt-secret— secret used to sign tunnel JWTs.--http-upgrade-credentials,-H, --header,--http-headers-file— customize the upgrade request.--tls-verify-certificate,--tls-certificate,--tls-private-key— verification and client mTLS (hot-reloaded).--tls-sni-override,--tls-sni-disable,--tls-ech-enable— SNI and ECH control.--http-proxy,--http-proxy-login,--http-proxy-password— dial through an HTTP proxy.--connection-min-idle,--connection-retry-max-backoff,--reverse-tunnel-connection-retry-max-backoff— pooling and retry.--socket-so-mark— (Linux)SO_MARKon outgoing sockets.--dns-resolver,--dns-resolver-prefer-ipv4— resolver control.--websocket-ping-frequency,--websocket-mask-frame— keep-alive and frame masking.
Server flags
--mode— default orws.--restrict-to,-r, --restrict-http-upgrade-path-prefix,--restrict-config— access policy.--jwt-secret— verifies tunnel JWT signatures under--mode ws. In default mode, tokens are parsed without cryptographic verification.--insecure-no-jwt-validation— accept unverified HS256 tokens even in--mode ws.--tls— serve TLS; generate an ephemeral self-signed cert if none is supplied.--tls-certificate,--tls-private-key— server cert/key (hot-reloaded on change orSIGHUP).--tls-client-ca-certs— enable mTLS.--socket-so-mark,--dns-resolver,--dns-resolver-prefer-ipv4— networking.--websocket-ping-frequency,--websocket-mask-frame— WebSocket behavior.--http-proxy,--http-proxy-login,--http-proxy-password— route server-side dials through a proxy.--remote-to-local-server-idle-timeout— reap idle reverse-tunnel listeners.
# config.yaml -> tunnelvision --config config.yaml
mode: client # or: server
log_lvl: INFO
client:
remote_addr: wss://gateway.example.com
local_to_remote:
- "tcp://8080:example.com:443"
- "socks5://127.0.0.1:1080"
server:
listen_addr: ws://0.0.0.0:8080
restrict_config: /etc/tunnelvision/rules.yamlimport (
"github.com/DsThakurRawat/TunnelVision/pkg/client"
"github.com/DsThakurRawat/TunnelVision/pkg/protocol"
)
func main() {
c := client.NewClient(client.Config{
ServerURL: "wss://gateway.example.com",
PathPrefix: "v1",
})
ltr, _ := client.ParseTunnelArg("tcp://8080:example.com:443", false)
go c.StartTunnel(ltr)
select {}
}TunnelVision implements a flexible wire protocol for forward tunnels and static reverse (TCP/Unix) tunnels. Dynamic reverse tunnels are a Go-native extension and stay within the TunnelVision client/server pair.
| Capability | Status | Cross-implementation |
|---|---|---|
| TCP forward / reverse | ✅ | ✅ |
| UDP forward | ✅ | ✅ |
| UDP reverse | ✅ | |
| SOCKS5 forward | ✅ | ✅ |
| SOCKS5 reverse | ✅ | |
| HTTP CONNECT forward | ✅ | ✅ |
| HTTP CONNECT reverse | ✅ | |
| Unix sockets / stdio | ✅ | ✅ |
| Transparent proxy (Linux TCP/UDP) | ✅ | ✅ |
PROXY protocol injection (?proxy_protocol) |
✅ | ✅ |
UDP idle timeout (?timeout_sec) |
✅ | ✅ |
| mTLS | ✅ | ✅ |
Ephemeral self-signed TLS (--tls) |
✅ | N/A |
| Certificate hot-reload (poll + SIGHUP) | ✅ | N/A |
| ECH (Encrypted Client Hello) | ✅ | ✅ |
| HTTP/2 transport | ✅ | ✅ |
| JWT authentication | ✅ | ✅ |
| YAML restriction rules | ✅ | ✅ |
Performance. Expect throughput on par with native connections and sub-millisecond added latency; idle memory sits around ~20 MB (Go runtime and goroutine stacks). Numbers are workload- and environment-dependent — measure on your own path.
make fmt— format.make lint && make vet— static analysis.make test— unit and integration tests.make test-interop— run this whenever you touch protocol code.
Pull requests welcome.
MIT — see LICENSE.