Skip to content

GitHangar/gaga

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gaga logo

gaga

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.com channel and forwards layer-3 IPv4/IPv6 packets.
  • SOCKS5 mode: a local SOCKS5 listener that forwards each CONNECT request through an SSH direct-tcpip channel.

The tool is built for practical bastion, jump-host, and lightweight VPN-like workflows where OpenSSH is already available.

Table Of Contents

Features

  • Single SSH session for TUN or SOCKS5 traffic.
  • OpenSSH tun@openssh.com support 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.
  • IdentityFile ordering and IdentitiesOnly behavior aligned with OpenSSH.
  • File key, password, and ssh-agent authentication support.
  • known_hosts host key verification by default.
  • SSH keepalive, reconnect, connect timeout, and reconnect backoff controls.
  • Strict TOML configuration parsing.
  • --dry-run for merged settings.
  • --preflight for local prerequisite checks before opening network resources.
  • Quiet default-friendly logging, plus --quiet, -v, and RUST_LOG control.

Mode Overview

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.

Installation

Build From Source

cargo build --release

Run the built binary:

./target/release/gaga --version

Install With The Script

./install.sh

Install system dependencies only:

./install.sh --deps-only

Install the binary into a custom directory:

./install.sh --prefix "$HOME/.local/bin"

Linux Requirements

TUN mode requires:

  • /dev/net/tun
  • root or CAP_NET_ADMIN on the client
  • iproute2 for automatic route installation
  • OpenSSH sshd on the server
  • PermitTunnel yes or PermitTunnel point-to-point on the server

Transparent TCP redirect requires:

  • Linux
  • root
  • iptables
  • IPv4 TCP traffic

SOCKS5 mode normally does not require root.

Quick Start

Start A Local SOCKS5 Proxy

gaga -m socks5 user@example.com

Test from another terminal:

curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

Use A Custom SOCKS5 Port

gaga -m socks5 --socks-bind 127.0.0.1 --socks-port 2080 user@example.com

Test:

curl --socks5-hostname 127.0.0.1:2080 https://example.com

Route Subnets Through TUN

sudo gaga user@example.com 10.0.0.0/8 192.168.10.0/24

On 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 gaga0

It captures the previous route rows and attempts to restore them on exit.

Use A Custom TUN Interface Name

sudo gaga --tun-name corp0 user@example.com 10.20.0.0/16

Load A Configuration File

gaga -c config.example.toml user@example.com

CLI flags override file values.

Inspect Settings Without Starting Anything

gaga --dry-run -c config.example.toml user@example.com

Check Local Prerequisites

gaga --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.

Command Line Options

Display the authoritative option list:

gaga --help

Important 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.

Configuration File

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 = 1080

Run with the file:

gaga -c config.example.toml user@example.com

Use 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 = 1080

Override the config from the CLI:

gaga -c config.toml --mode tun user@example.com 10.0.0.0/8

The parser is strict. Unknown keys fail fast:

[default]
keepaliv = 30

The correct key is keepalive.

Dry Run And Preflight

Dry Run

gaga --dry-run -c config.toml user@example.com 10.0.0.0/8

Dry run prints effective settings and exits. It is useful before long-running sessions or when validating config precedence.

Preflight

gaga --preflight -c config.toml user@example.com 10.0.0.0/8

Preflight checks:

  • ssh command availability.
  • known_hosts availability unless --insecure is set.
  • Explicit identity file existence.
  • ssh-agent socket validity when --agent is set.
  • /dev/net/tun for Linux TUN mode.
  • ip command for automatic Linux routes.
  • iptables command when transparent redirect is required.

Successful SOCKS5 preflight:

gaga --preflight -m socks5 user@example.com

Expected failure for a missing key:

gaga --preflight -i ~/.ssh/missing_key user@example.com

TUN Mode

TUN is the default mode:

sudo gaga user@example.com

Route specific subnets:

sudo gaga user@example.com 10.0.0.0/8

Route multiple subnets:

sudo gaga user@example.com 10.0.0.0/8 172.16.0.0/12 192.168.50.0/24

Disable automatic route installation:

sudo gaga --no-auto-routes user@example.com 10.0.0.0/8

Then add routes manually if needed:

sudo ip route replace 10.0.0.0/8 dev gaga0

Server-side OpenSSH configuration:

PermitTunnel yes

or:

PermitTunnel point-to-point

Reload OpenSSH:

sudo systemctl reload sshd

Some distributions use ssh instead:

sudo systemctl reload ssh

TUN Rejection And Fallback

If 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/8

Keep fallback but skip iptables redirect:

sudo gaga --no-iptables-on-tun-fallback user@example.com 10.0.0.0/8

SOCKS5 Mode

Start SOCKS5 explicitly:

gaga -m socks5 user@example.com

Default listener:

127.0.0.1:1080

Custom listener:

gaga -m socks5 --socks-bind 127.0.0.1 --socks-port 2080 user@example.com

IPv6 listener:

gaga -m socks5 --socks-bind ::1 --socks-port 2080 user@example.com
curl --socks5-hostname '[::1]:2080' https://ifconfig.me

Test with curl:

curl --socks5-hostname 127.0.0.1:2080 https://ifconfig.me

Use with Git:

git -c http.proxy=socks5h://127.0.0.1:1080 clone https://github.com/rust-lang/rust.git

Use with an environment variable:

ALL_PROXY=socks5h://127.0.0.1:1080 curl https://example.com

Use socks5h when you want DNS resolution to happen through the proxy. Some clients resolve DNS locally when socks5:// is used.

Transparent TCP Redirect

On Linux, gaga can redirect local IPv4 TCP traffic into the SOCKS5 tunnel:

sudo gaga -m socks5 --iptables-redir-all-tcp user@example.com

This 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.com

Limitations:

  • 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 firefox

or configure the browser/system SOCKS5 proxy settings.

SSH Authentication

Default Behavior

When -i, -p, and -A are omitted:

  1. gaga runs ssh -G -- <target>.
  2. It reads IdentityFile entries in OpenSSH order.
  3. It respects IdentitiesOnly yes.
  4. If IdentitiesOnly no, it appends common ~/.ssh default key paths.
  5. On Unix, if file authentication fails and SSH_AUTH_SOCK is set, it tries ssh-agent.
  6. 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.com

Explicit Key

gaga -i ~/.ssh/work_ed25519 user@example.com

If 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:2222

With IPv6:

gaga -i ~/.ssh/work_ed25519 user@[2001:db8::10]:2222

Agent-Only Authentication

gaga -A user@example.com

Inspect agent keys:

ssh-add -l

Password Authentication

gaga -p user@example.com

This 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

Host key verification is enabled by default and uses:

~/.ssh/known_hosts

Use a custom file:

gaga --known-hosts ./known_hosts user@example.com

Add a host key:

ssh-keyscan -H example.com >> ~/.ssh/known_hosts

Add a host key for a custom port:

ssh-keyscan -p 2222 -H example.com >> ~/.ssh/known_hosts

Disable host key verification:

gaga --insecure user@example.com

--insecure accepts man-in-the-middle risk. Use it only for temporary testing.

Routing Behavior

In TUN mode on Linux, subnet arguments drive route installation:

sudo gaga user@example.com 10.0.0.0/8

The equivalent manual route is:

sudo ip route replace 10.0.0.0/8 dev gaga0

Before installing a route, gaga snapshots:

ip route show to 10.0.0.0/8

On exit, it attempts to:

  1. Delete the route it installed.
  2. Re-add the previously captured route rows.

In SOCKS5 mode, subnet arguments are validated and logged but not applied to OS routes.

Operational Examples

Access A Corporate Network Through A Bastion

sudo gaga -i ~/.ssh/corp_ed25519 admin@bastion.corp.example.com 10.0.0.0/8 172.16.0.0/12

Preflight:

sudo gaga --preflight -i ~/.ssh/corp_ed25519 admin@bastion.corp.example.com 10.0.0.0/8

Tunnel One Application Through SOCKS5

gaga -m socks5 -i ~/.ssh/work_ed25519 user@example.com

In another terminal:

ALL_PROXY=socks5h://127.0.0.1:1080 curl https://ifconfig.me

Long-Running SOCKS5 Session

gaga -m socks5 --reconnect --reconnect-delay-ms 1000 --max-reconnect-delay-ms 30000 user@example.com

Config 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 = 1080

Run:

gaga -c long-running.toml user@example.com

Require TUN And Fail If It Is Rejected

sudo gaga --no-tun-fallback-socks user@example.com 10.0.0.0/8

Fallback To SOCKS5 Without iptables

sudo gaga --no-iptables-on-tun-fallback user@example.com 10.0.0.0/8

SOCKS5 With Transparent TCP Capture

sudo gaga -m socks5 --iptables-redir-all-tcp user@example.com

For remote DNS, prefer explicit proxy configuration:

ALL_PROXY=socks5h://127.0.0.1:1080 curl https://example.com

IPv6 Targets

Default port:

gaga -m socks5 user@2001:db8::10

Explicit port:

gaga -m socks5 user@[2001:db8::10]:2222

Disable Connect Timeout

gaga --connect-timeout 0 user@example.com

Use this carefully; network failures can then block longer.

Quiet Output

Default output is concise and intended for normal terminal use. Show only warnings and errors:

gaga --quiet user@example.com

Show technical details:

gaga -v user@example.com

Troubleshooting

tun@openssh.com Rejected Or ConnectFailed

Plain ssh user@host does not open a TUN channel. gaga TUN mode does.

Check the server:

sudo sshd -T | grep -i permitTunnel

Expected values:

permittunnel yes

or:

permittunnel point-to-point

Set one of these in sshd_config:

PermitTunnel yes

Reload OpenSSH:

sudo systemctl reload sshd

TUN Device Creation Fails

Check the device:

ls -l /dev/net/tun

Run with root:

sudo gaga user@example.com 10.0.0.0/8

Some systems allow granting the binary CAP_NET_ADMIN:

sudo setcap cap_net_admin+ep "$(command -v gaga)"

Routes Are Not Installed

Run preflight:

sudo gaga --preflight user@example.com 10.0.0.0/8

Check ip:

ip -V

Manual route:

sudo ip route replace 10.0.0.0/8 dev gaga0

Host Key Verification Fails

Add the key:

ssh-keyscan -H example.com >> ~/.ssh/known_hosts

Custom port:

ssh-keyscan -p 2222 -H example.com >> ~/.ssh/known_hosts

If the key changed, verify why first. If the server was reinstalled:

ssh-keygen -R example.com
ssh-keyscan -H example.com >> ~/.ssh/known_hosts

ssh -G Is Not Available

Install OpenSSH client or pass an explicit key:

gaga -i ~/.ssh/id_ed25519 user@example.com

Browser Looks Offline

Transparent TCP redirect does not capture UDP/DNS or QUIC. Prefer explicit SOCKS5 settings:

ALL_PROXY=socks5h://127.0.0.1:1080 firefox

Reconnect Is Too Fast Or Too Slow

Faster retry:

gaga --reconnect --reconnect-delay-ms 500 --max-reconnect-delay-ms 10000 user@example.com

Slower retry:

gaga --reconnect --reconnect-delay-ms 5000 --max-reconnect-delay-ms 120000 user@example.com

Config Key Is Not Recognized

Strict parsing catches typos.

Wrong:

[default]
reconect = true

Correct:

[default]
reconnect = true

Security Notes

  • --insecure disables host key verification and allows man-in-the-middle risk.
  • Transparent redirect can affect all local IPv4 TCP output. Run --preflight first.
  • 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.

Development

Repository

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

Source Layout

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

Local Setup

Install a Rust toolchain with rustup, then build:

cargo build

Run the CLI from source:

cargo run -- --help

Run a local no-network check:

cargo run -- --dry-run -m socks5 user@example.com

Run local prerequisite checks:

cargo run -- --preflight -m socks5 user@example.com

Quality Gates

Format:

cargo fmt -- --check

Test:

cargo test

Lint:

cargo clippy --all-targets --all-features -- -D warnings

These checks should pass before every commit.

Release Builds

Release build:

cargo build --release

Release build with a temporary target directory:

CARGO_TARGET_DIR=/tmp/gaga-target cargo build --release

Runtime Testing

SOCKS5 smoke test:

cargo run -- -m socks5 user@example.com
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

TUN smoke test on Linux:

sudo target/debug/gaga user@example.com 10.0.0.0/8
ip route show to 10.0.0.0/8

Preflight before a TUN test:

sudo target/debug/gaga --preflight user@example.com 10.0.0.0/8

Transparent redirect smoke test:

sudo target/debug/gaga -m socks5 --iptables-redir-all-tcp user@example.com

Logging

Default logs are concise and omit timestamps and module targets. Use -v for module targets and debug-level details:

gaga -v user@example.com

Use RUST_LOG for explicit filtering:

RUST_LOG=gaga=debug,russh=warn gaga user@example.com

Quiet mode only shows warnings and errors:

gaga --quiet user@example.com

Coding Guidelines

  • 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 russh changes as high-risk and document them in vendor/russh/GAGA-PATCH.md.

Git Notes

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 push

Project Maintainer

Author: Cuma Kurt <cumakurt@gmail.com>
GitHub: https://github.com/cumakurt/gaga
LinkedIn: https://www.linkedin.com/in/cuma-kurt-34414917/

License

GNU Affero General Public License v3.0 or later. See LICENSE and Cargo.toml.

About

gaga is a small, fast SSH tunnel and proxy

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Rust 91.8%
  • Shell 8.2%