gaga is a small, fast SSH tunnel and proxy CLI written in Rust. It uses a single SSH transport and supports two operating modes:
- TUN mode: the default mode. It opens an OpenSSH
tun@openssh.comchannel and forwards layer-3 IPv4/IPv6 packets. - SOCKS5 mode: a local SOCKS5 listener that forwards each
CONNECTrequest through an SSHdirect-tcpipchannel.
The tool is built for practical bastion, jump-host, and lightweight VPN-like workflows where OpenSSH is already available.
- Features
- Mode Overview
- Installation
- Quick Start
- Command Line Options
- Configuration File
- Dry Run And Preflight
- TUN Mode
- SOCKS5 Mode
- Transparent TCP Redirect
- SSH Authentication
- Host Key Verification
- Routing Behavior
- Operational Examples
- Troubleshooting
- Security Notes
- Development
- Project Maintainer
- License
- Single SSH session for TUN or SOCKS5 traffic.
- OpenSSH
tun@openssh.comsupport for layer-3 TUN forwarding. - SOCKS5 proxy mode for application-level tunneling.
- Linux route management for TUN mode with route restore on exit.
- Automatic SOCKS5 fallback when the server rejects
tun@openssh.com. - Optional Linux iptables transparent TCP redirect for fallback or SOCKS5 mode.
- OpenSSH config discovery through
ssh -G. IdentityFileordering andIdentitiesOnlybehavior aligned with OpenSSH.- File key, password, and ssh-agent authentication support.
known_hostshost key verification by default.- SSH keepalive, reconnect, connect timeout, and reconnect backoff controls.
- Strict TOML configuration parsing.
--dry-runfor merged settings.--preflightfor local prerequisite checks before opening network resources.- Quiet default-friendly logging, plus
--quiet,-v, andRUST_LOGcontrol.
| Mode | Command | Needs root? | Best for |
|---|---|---|---|
| TUN | gaga user@host |
Usually yes | Subnet routing and VPN-like layer-3 tunnels |
| SOCKS5 | gaga -m socks5 user@host |
No | Per-application proxying |
| TUN fallback to SOCKS5 | gaga user@host |
Only for Linux transparent redirect | Servers without PermitTunnel |
| SOCKS5 plus iptables redirect | gaga -m socks5 --iptables-redir-all-tcp user@host |
Yes | Local IPv4 TCP transparent capture |
TUN mode is not a complete VPN manager. DNS, policy routing, firewall behavior, NAT, and IPv6 handling remain operating-system responsibilities. SOCKS5 mode is simpler but applications must use the proxy explicitly unless transparent TCP redirect is enabled on Linux.
cargo build --releaseRun the built binary:
./target/release/gaga --version./install.shInstall system dependencies only:
./install.sh --deps-onlyInstall the binary into a custom directory:
./install.sh --prefix "$HOME/.local/bin"TUN mode requires:
/dev/net/tun- root or
CAP_NET_ADMINon the client iproute2for automatic route installation- OpenSSH
sshdon the server PermitTunnel yesorPermitTunnel point-to-pointon the server
Transparent TCP redirect requires:
- Linux
- root
iptables- IPv4 TCP traffic
SOCKS5 mode normally does not require root.
gaga -m socks5 user@example.comTest from another terminal:
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.megaga -m socks5 --socks-bind 127.0.0.1 --socks-port 2080 user@example.comTest:
curl --socks5-hostname 127.0.0.1:2080 https://example.comsudo gaga user@example.com 10.0.0.0/8 192.168.10.0/24On Linux, gaga creates the local TUN interface and installs routes similar to:
ip route replace 10.0.0.0/8 dev gaga0
ip route replace 192.168.10.0/24 dev gaga0It captures the previous route rows and attempts to restore them on exit.
sudo gaga --tun-name corp0 user@example.com 10.20.0.0/16gaga -c config.example.toml user@example.comCLI flags override file values.
gaga --dry-run -c config.example.toml user@example.comgaga --preflight -c config.example.toml user@example.com 10.0.0.0/8--preflight does not open SSH connections or local listeners. It checks local requirements such as ssh, known_hosts, identity files, agent socket, /dev/net/tun, ip, and iptables.
Display the authoritative option list:
gaga --helpImportant options:
| Option | Description |
|---|---|
| `-m, --mode <tun | socks5>` |
-i, --identity <FILE> |
Tries one explicit private key before password fallback. |
-p, --password |
Prompts for SSH password immediately instead of trying keys first. |
--no-password-fallback |
Disables automatic password prompt after public-key authentication fails. |
-A, --agent |
Uses ssh-agent authentication only. |
--known-hosts <FILE> |
Overrides the known_hosts path. |
--insecure |
Disables host key verification. Not recommended. |
--keepalive <SECS> |
SSH keepalive interval. 0 disables keepalive. |
--connect-timeout <SECS> |
Timeout for TCP connect, SSH handshake, and authentication. 0 disables the timeout. |
--reconnect |
Retries SSH connect/auth failures with exponential backoff. |
--reconnect-delay-ms <MS> |
Initial reconnect delay. |
--max-reconnect-delay-ms <MS> |
Maximum reconnect backoff delay. |
--dry-run |
Prints effective settings without opening network resources. |
--preflight |
Prints effective settings and checks local prerequisites. |
-q, --quiet |
Shows only warnings and errors. Cannot be combined with -v or --dry-run. |
-v, --verbose |
Shows more technical logs. Use -vv for trace-level gaga logs. |
--no-auto-routes |
Disables automatic ip route replace in TUN mode. |
--no-tun-fallback-socks |
Disables SOCKS5 fallback when TUN is rejected. |
--no-iptables-on-tun-fallback |
Disables iptables redirect during TUN fallback. |
--iptables-redir-all-tcp |
Enables transparent local IPv4 TCP redirect in SOCKS5 mode. |
--transparent-redir-port <PORT> |
Uses a fixed transparent redirect listener port. |
Example:
[default]
mode = "tun"
keepalive = 30
connect_timeout = 30
reconnect = true
reconnect_delay_ms = 2000
max_reconnect_delay_ms = 60000
[default.socks5]
bind = "127.0.0.1"
port = 1080Run with the file:
gaga -c config.example.toml user@example.comUse SOCKS5 as the default mode:
[default]
mode = "socks5"
keepalive = 30
connect_timeout = 15
reconnect = true
reconnect_delay_ms = 1000
max_reconnect_delay_ms = 30000
[default.socks5]
bind = "127.0.0.1"
port = 1080Override the config from the CLI:
gaga -c config.toml --mode tun user@example.com 10.0.0.0/8The parser is strict. Unknown keys fail fast:
[default]
keepaliv = 30The correct key is keepalive.
gaga --dry-run -c config.toml user@example.com 10.0.0.0/8Dry run prints effective settings and exits. It is useful before long-running sessions or when validating config precedence.
gaga --preflight -c config.toml user@example.com 10.0.0.0/8Preflight checks:
sshcommand availability.known_hostsavailability unless--insecureis set.- Explicit identity file existence.
- ssh-agent socket validity when
--agentis set. /dev/net/tunfor Linux TUN mode.ipcommand for automatic Linux routes.iptablescommand when transparent redirect is required.
Successful SOCKS5 preflight:
gaga --preflight -m socks5 user@example.comExpected failure for a missing key:
gaga --preflight -i ~/.ssh/missing_key user@example.comTUN is the default mode:
sudo gaga user@example.comRoute specific subnets:
sudo gaga user@example.com 10.0.0.0/8Route multiple subnets:
sudo gaga user@example.com 10.0.0.0/8 172.16.0.0/12 192.168.50.0/24Disable automatic route installation:
sudo gaga --no-auto-routes user@example.com 10.0.0.0/8Then add routes manually if needed:
sudo ip route replace 10.0.0.0/8 dev gaga0Server-side OpenSSH configuration:
PermitTunnel yes
or:
PermitTunnel point-to-point
Reload OpenSSH:
sudo systemctl reload sshdSome distributions use ssh instead:
sudo systemctl reload sshIf the server rejects tun@openssh.com, gaga falls back to SOCKS5 by default.
Disable fallback:
sudo gaga --no-tun-fallback-socks user@example.com 10.0.0.0/8Keep fallback but skip iptables redirect:
sudo gaga --no-iptables-on-tun-fallback user@example.com 10.0.0.0/8Start SOCKS5 explicitly:
gaga -m socks5 user@example.comDefault listener:
127.0.0.1:1080
Custom listener:
gaga -m socks5 --socks-bind 127.0.0.1 --socks-port 2080 user@example.comIPv6 listener:
gaga -m socks5 --socks-bind ::1 --socks-port 2080 user@example.com
curl --socks5-hostname '[::1]:2080' https://ifconfig.meTest with curl:
curl --socks5-hostname 127.0.0.1:2080 https://ifconfig.meUse with Git:
git -c http.proxy=socks5h://127.0.0.1:1080 clone https://github.com/rust-lang/rust.gitUse with an environment variable:
ALL_PROXY=socks5h://127.0.0.1:1080 curl https://example.comUse socks5h when you want DNS resolution to happen through the proxy. Some clients resolve DNS locally when socks5:// is used.
On Linux, gaga can redirect local IPv4 TCP traffic into the SOCKS5 tunnel:
sudo gaga -m socks5 --iptables-redir-all-tcp user@example.comThis mode:
- Creates a dedicated iptables NAT chain.
- Excludes loopback traffic.
- Excludes the SSH control connection target.
- Redirects local IPv4 OUTPUT TCP traffic to a transparent listener.
- Removes the chain on normal exit.
Use a fixed transparent redirect port:
sudo gaga -m socks5 --iptables-redir-all-tcp --transparent-redir-port 12345 user@example.comLimitations:
- IPv4 TCP only.
- UDP is not captured.
- DNS often uses UDP, so transparent-only usage can still leak or fail DNS.
- Browser QUIC/HTTP3 traffic uses UDP and is not captured.
For browsers, using an explicit SOCKS5 proxy is usually more reliable:
ALL_PROXY=socks5h://127.0.0.1:1080 firefoxor configure the browser/system SOCKS5 proxy settings.
When -i, -p, and -A are omitted:
- gaga runs
ssh -G -- <target>. - It reads
IdentityFileentries in OpenSSH order. - It respects
IdentitiesOnly yes. - If
IdentitiesOnly no, it appends common~/.sshdefault key paths. - On Unix, if file authentication fails and
SSH_AUTH_SOCKis set, it tries ssh-agent. - If public-key and agent authentication fail and an interactive terminal is available, it prompts for the SSH account password.
Disable the final password prompt when you need strict key-only behavior:
gaga --no-password-fallback user@example.comgaga -i ~/.ssh/work_ed25519 user@example.comIf the key is rejected and the process has an interactive terminal, gaga asks for the SSH account password. Add --no-password-fallback for key-only behavior.
With a custom port:
gaga -i ~/.ssh/work_ed25519 user@example.com:2222With IPv6:
gaga -i ~/.ssh/work_ed25519 user@[2001:db8::10]:2222gaga -A user@example.comInspect agent keys:
ssh-add -lgaga -p user@example.comThis skips key and agent attempts and prompts immediately. On Unix, the password is read from the controlling terminal and terminal echo is disabled while typing.
--password and --agent are mutually exclusive.
Host key verification is enabled by default and uses:
~/.ssh/known_hosts
Use a custom file:
gaga --known-hosts ./known_hosts user@example.comAdd a host key:
ssh-keyscan -H example.com >> ~/.ssh/known_hostsAdd a host key for a custom port:
ssh-keyscan -p 2222 -H example.com >> ~/.ssh/known_hostsDisable host key verification:
gaga --insecure user@example.com--insecure accepts man-in-the-middle risk. Use it only for temporary testing.
In TUN mode on Linux, subnet arguments drive route installation:
sudo gaga user@example.com 10.0.0.0/8The equivalent manual route is:
sudo ip route replace 10.0.0.0/8 dev gaga0Before installing a route, gaga snapshots:
ip route show to 10.0.0.0/8On exit, it attempts to:
- Delete the route it installed.
- Re-add the previously captured route rows.
In SOCKS5 mode, subnet arguments are validated and logged but not applied to OS routes.
sudo gaga -i ~/.ssh/corp_ed25519 admin@bastion.corp.example.com 10.0.0.0/8 172.16.0.0/12Preflight:
sudo gaga --preflight -i ~/.ssh/corp_ed25519 admin@bastion.corp.example.com 10.0.0.0/8gaga -m socks5 -i ~/.ssh/work_ed25519 user@example.comIn another terminal:
ALL_PROXY=socks5h://127.0.0.1:1080 curl https://ifconfig.megaga -m socks5 --reconnect --reconnect-delay-ms 1000 --max-reconnect-delay-ms 30000 user@example.comConfig version:
[default]
mode = "socks5"
keepalive = 20
connect_timeout = 15
reconnect = true
reconnect_delay_ms = 1000
max_reconnect_delay_ms = 30000
[default.socks5]
bind = "127.0.0.1"
port = 1080Run:
gaga -c long-running.toml user@example.comsudo gaga --no-tun-fallback-socks user@example.com 10.0.0.0/8sudo gaga --no-iptables-on-tun-fallback user@example.com 10.0.0.0/8sudo gaga -m socks5 --iptables-redir-all-tcp user@example.comFor remote DNS, prefer explicit proxy configuration:
ALL_PROXY=socks5h://127.0.0.1:1080 curl https://example.comDefault port:
gaga -m socks5 user@2001:db8::10Explicit port:
gaga -m socks5 user@[2001:db8::10]:2222gaga --connect-timeout 0 user@example.comUse this carefully; network failures can then block longer.
Default output is concise and intended for normal terminal use. Show only warnings and errors:
gaga --quiet user@example.comShow technical details:
gaga -v user@example.comPlain ssh user@host does not open a TUN channel. gaga TUN mode does.
Check the server:
sudo sshd -T | grep -i permitTunnelExpected values:
permittunnel yes
or:
permittunnel point-to-point
Set one of these in sshd_config:
PermitTunnel yes
Reload OpenSSH:
sudo systemctl reload sshdCheck the device:
ls -l /dev/net/tunRun with root:
sudo gaga user@example.com 10.0.0.0/8Some systems allow granting the binary CAP_NET_ADMIN:
sudo setcap cap_net_admin+ep "$(command -v gaga)"Run preflight:
sudo gaga --preflight user@example.com 10.0.0.0/8Check ip:
ip -VManual route:
sudo ip route replace 10.0.0.0/8 dev gaga0Add the key:
ssh-keyscan -H example.com >> ~/.ssh/known_hostsCustom port:
ssh-keyscan -p 2222 -H example.com >> ~/.ssh/known_hostsIf the key changed, verify why first. If the server was reinstalled:
ssh-keygen -R example.com
ssh-keyscan -H example.com >> ~/.ssh/known_hostsInstall OpenSSH client or pass an explicit key:
gaga -i ~/.ssh/id_ed25519 user@example.comTransparent TCP redirect does not capture UDP/DNS or QUIC. Prefer explicit SOCKS5 settings:
ALL_PROXY=socks5h://127.0.0.1:1080 firefoxFaster retry:
gaga --reconnect --reconnect-delay-ms 500 --max-reconnect-delay-ms 10000 user@example.comSlower retry:
gaga --reconnect --reconnect-delay-ms 5000 --max-reconnect-delay-ms 120000 user@example.comStrict parsing catches typos.
Wrong:
[default]
reconect = trueCorrect:
[default]
reconnect = true--insecuredisables host key verification and allows man-in-the-middle risk.- Transparent redirect can affect all local IPv4 TCP output. Run
--preflightfirst. - TUN mode needs root or
CAP_NET_ADMIN; only grant capabilities to trusted binaries. - Keep private key and config file permissions restrictive.
- Prefer key or agent authentication for automation.
- gaga does not fully manage firewall, DNS, NAT, or policy routing.
Primary repository:
https://github.com/cumakurt/gaga
SSH remote:
git@github.com:cumakurt/gaga.git
The crate metadata is defined in Cargo.toml. The vendored russh patch is selected through:
[patch.crates-io]
russh = { path = "vendor/russh" }Patch-specific notes are kept in:
vendor/russh/GAGA-PATCH.md
src/
main.rs CLI entry point, logging setup, dry-run/preflight dispatch
cli.rs clap argument model and validation
config.rs strict TOML config loading and CLI/file merge
diagnostics.rs local preflight checks
shutdown.rs Ctrl+C and SIGTERM handling
ssh/
auth.rs password, key, and ssh-agent authentication
client.rs SSH connection, timeout, keepalive, and auth orchestration
handler.rs host key verification
mod.rs mode dispatch and reconnect loop
tunnel/
openssh_l3.rs OpenSSH TUN frame encoding/decoding
tun.rs local TUN bridge
socks.rs SOCKS5 and transparent TCP relay
linux_orig_dst.rs Linux SO_ORIGINAL_DST lookup
routing/
linux.rs iproute2 route install/restore helpers
restore_guard.rs route restore guard
iptables_redir_*.rs transparent TCP redirect backends
utils/
target.rs user@host parser
openssh_g.rs OpenSSH ssh -G identity discovery
known_hosts.rs known_hosts helpers
paths.rs path expansion
Install a Rust toolchain with rustup, then build:
cargo buildRun the CLI from source:
cargo run -- --helpRun a local no-network check:
cargo run -- --dry-run -m socks5 user@example.comRun local prerequisite checks:
cargo run -- --preflight -m socks5 user@example.comFormat:
cargo fmt -- --checkTest:
cargo testLint:
cargo clippy --all-targets --all-features -- -D warningsThese checks should pass before every commit.
Release build:
cargo build --releaseRelease build with a temporary target directory:
CARGO_TARGET_DIR=/tmp/gaga-target cargo build --releaseSOCKS5 smoke test:
cargo run -- -m socks5 user@example.com
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.meTUN smoke test on Linux:
sudo target/debug/gaga user@example.com 10.0.0.0/8
ip route show to 10.0.0.0/8Preflight before a TUN test:
sudo target/debug/gaga --preflight user@example.com 10.0.0.0/8Transparent redirect smoke test:
sudo target/debug/gaga -m socks5 --iptables-redir-all-tcp user@example.comDefault logs are concise and omit timestamps and module targets. Use -v for module targets and debug-level details:
gaga -v user@example.comUse RUST_LOG for explicit filtering:
RUST_LOG=gaga=debug,russh=warn gaga user@example.comQuiet mode only shows warnings and errors:
gaga --quiet user@example.com- Keep user-facing CLI output in English.
- Keep code comments and documentation in English.
- Prefer small, explicit error messages with actionable context.
- Do not log secrets, private key material, or password values.
- Keep OS-changing behavior explicit and reversible.
- Keep Linux-specific behavior behind
cfg(target_os = "linux")or platform modules. - Add unit tests for parsers, config merge behavior, frame decoding, and edge cases.
- Treat vendored
russhchanges as high-risk and document them invendor/russh/GAGA-PATCH.md.
Some sandboxed workspaces may mount .git as read-only metadata. In that case, a separate git directory can be used:
git --git-dir=.git-local --work-tree=. status
git --git-dir=.git-local --work-tree=. commit -m "message"For normal local clones, standard git commands are expected:
git status
git commit -m "message"
git pushAuthor: Cuma Kurt <cumakurt@gmail.com>
GitHub: https://github.com/cumakurt/gaga
LinkedIn: https://www.linkedin.com/in/cuma-kurt-34414917/
GNU Affero General Public License v3.0 or later. See LICENSE and Cargo.toml.
