conduit is a protocol-converting proxy service written in Go.
It can accept inbound proxy traffic via:
- HTTP proxy (including
CONNECTfor HTTPS tunneling) - SOCKS5 proxy (no-auth)
- Transparent proxy listener (Linux, FreeBSD, or OpenBSD)
It can forward outbound connections:
- Directly to the destination
- Via an upstream HTTP or HTTPS proxy (with optional basic auth)
- Via an upstream SOCKS5 proxy (with optional user+pass)
- Via an upstream SSH server using SSH dynamic port forwarding (like "ssh -D")
The HTTP proxy (when not using the CONNECT method) uses the Go standard library's proxy support, inheriting its high performance, connection pooling, and standards conformance.
The SOCKS5 proxy, the transparent proxy, and HTTP proxy when using the CONNECT method pass TCP data as-is, without trying to interpret the protocol. After setting up the connection, data is transferred on Linux via the zero-copy splice() mechanism to maximize throughput.
Requirements:
- Go 1.25+ (the module targets Go 1.25)
Build a local binary:
go build -o conduit .Run tests:
go test ./...Run from the repo root:
conduit --http-listen 127.0.0.1:8080HTTP proxy (direct):
conduit \
--http-listen 127.0.0.1:8080 \
--upstream direct://SOCKS5 proxy (direct):
conduit \
--socks5-listen 127.0.0.1:1080 \
--upstream direct://HTTP proxy that forwards outbound connections via an upstream HTTP proxy:
conduit \
--http-listen 127.0.0.1:8080 \
--upstream http://10.0.0.2:3128HTTP proxy that forwards outbound connections via an upstream SOCKS5 proxy:
conduit \
--http-listen 127.0.0.1:8080 \
--upstream socks5://10.0.0.3:1080HTTP proxy that forwards outbound connections via an upstream SSH server (password auth):
conduit \
--http-listen 127.0.0.1:8080 \
--upstream ssh://user:pass@10.0.0.4:22HTTP proxy that forwards outbound connections via an upstream SSH server (key auth via agent):
conduit \
--http-listen 127.0.0.1:8080 \
--upstream ssh://user@10.0.0.4:22HTTP proxy that forwards outbound connections via an upstream SSH server (key file):
conduit \
--http-listen 127.0.0.1:8080 \
--upstream ssh://user@10.0.0.4:22 \
--ssh-key ~/.ssh/id_ed25519Listener flags (any can be omitted to disable that listener):
--http-listen=IP:port--socks5-listen=IP:port--tproxy-listen=IP:port(Linux only)
Debug flags:
--debug-listen=IP:port(enables/debug/pprof)--verbose(default: false): log per-connection errors.
Forwarding flags:
--upstream=direct:// | http://[user:pass@]host:port | https://[user:pass@]host:port | socks5://[user:pass@]host:port | ssh://user[:pass]@host:portYou can use theALL_PROXYenvironment variable instead of the--upstreamflag to set this, which might be better if it includes a password.--ssh-key=agent|path|""(default:agentifSSH_AUTH_SOCKis set, else empty): SSH key source for ssh:// upstream. Useagentfor SSH agent, a file path for a private key (OpenSSH format), or empty to disable key auth. If both key and password are provided, both methods are offered to the server.--ssh-known-hosts=path|""(default:~/.ssh/known_hosts): Path to known_hosts file for SSH host key verification. Unknown hosts are automatically added on first connection (trust on first use). Empty disables host verification.
Timeout behavior:
--dial-timeoutbounds DNS lookups and TCP connect.--negotiation-timeoutbounds protocol handshakes (HTTP CONNECT and SOCKS5 negotiation/CONNECT).--http-idle-timeoutlimits how long HTTP connections remain idle before being closed.- After negotiation completes, there's no explicit timeout on connections. It is assumed that either the client or server will close as needed, or that TCP keepalive will detect and remove stale connections.
TCP keepalive is optionally applied to all accepted TCP connections and all outbound TCP dials, so the kernel will detect when connections are stale:
--tcp-keepalive=on|off|keepidle:keepintvl:keepcnton: enable keepalive with kernel defaultsoff: disable keepalivekeepidle:keepintvl:keepcnt: enable keepalive and (where supported) set:keepidle(seconds)keepintvl(seconds)keepcnt(count)
- HTTP (non-CONNECT) uses
net/http/httputil.ReverseProxy.- A custom
RoundTripperdials via the configured forwarding mode.
- A custom
- HTTP CONNECT uses HTTP hijacking and then bidirectional
io.Copypiping. - SOCKS5 server supports:
- No-auth negotiation
CONNECTcommand- IPv4/IPv6/domain targets
- Upstream SOCKS5 forwarding uses
github.com/txthinking/socks5.- Outbound proxy connections are dialed with the internal dialer interface (
DialContext). - SOCKS5 negotiation and CONNECT are performed via shared helpers in
internal/socks5(built on the library's low-level protocol API).
- Outbound proxy connections are dialed with the internal dialer interface (
- Upstream SSH forwarding uses
golang.org/x/crypto/ssh.- A single SSH transport connection is established lazily and reused.
- Each proxied outbound connection opens a new
direct-tcpipchannel over the shared SSH transport. - Authentication supports password, public key, SSH agent, or combinations. Keys from the SSH agent are used by default when
SSH_AUTH_SOCKis set. - Host key checking uses
~/.ssh/known_hostsby default (trust on first use). Can be disabled with--ssh-known-hosts=off. - Servers commonly have a low limit of max forwarded connections (MaxSessions defaults to 10), which this doesn't handle well.
- After connections are negotiated, we try to preserve the Linux zero-copy fast path.
The transparent proxy listener intercepts redirected TCP connections and forwards them to their original destinations.
Uses IP_TRANSPARENT socket option and SO_ORIGINAL_DST to retrieve the original destination.
- Requires appropriate iptables/nftables TPROXY rules to redirect traffic.
- Example iptables rule:
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY \ --tproxy-mark 0x1/0x1 --on-port 8080
Uses IP_BINDANY socket option. The original destination is preserved in the socket's local address by IPFW/PF.
- Requires appropriate IPFW fwd or PF rdr-to rules to redirect traffic.
- Requires root or
PRIV_NETINET_BINDANYprivilege. - Example IPFW rule:
ipfw add fwd 127.0.0.1,8080 tcp from any to any 80 in - Example PF rule:
pass in on em0 proto tcp to port 80 rdr-to 127.0.0.1 port 8080
Uses SO_BINDANY socket option (at socket level, unlike FreeBSD's protocol level). The original destination is preserved in the socket's local address by PF.
- Requires appropriate PF rdr-to rules to redirect traffic.
- Requires root privileges.
- May need
divert-replyon outgoing rules for return traffic. - Example PF rules:
pass in on em0 proto tcp to port 80 rdr-to 127.0.0.1 port 8080 pass out on em0 proto tcp divert-reply
On platforms other than Linux, FreeBSD, and OpenBSD, --tproxy-listen returns an error (build remains portable).
- TPROXY robustness:
- Improve validation/diagnostics around kernel/sysctl prerequisites.
- HTTP proxy correctness/performance:
- Consider connection reuse tuning and explicit transport settings (idle conns, max conns per host, etc.).
- Add explicit filtering/handling for hop-by-hop headers as needed for edge cases.
- Security/authentication:
- Add optional auth for HTTP proxy and SOCKS5.
- Add allow/deny lists.
- Observability:
- Structured logging.
- Prometheus metrics.
- Graceful shutdown:
- Drain active tunnel connections more gracefully (currently relies on listener/server close).
- Context handling for upstream SOCKS5:
- Verify cancellation behavior across all failure modes (DNS, connect, handshake) and add targeted tests.