Skip to content

gogoout/sshfwd.rs

Repository files navigation

              __    ____             __
   __________/ /_  / __/      ______/ /
  / ___/ ___/ __ \/ /_| | /| / / __  /
 (__  |__  ) / / / __/| |/ |/ / /_/ /
/____/____/_/ /_/_/   |__/|__/\__,_/

sshfwd.rs

A TUI-based SSH port forwarding management tool built with Rust. Inspired by k9s' keyboard-driven interface.

Demo

Features

  • Automatic port detection — deploys a lightweight agent that streams listening ports in real time
  • One-key forwardingEnter/f to forward with matching local port, F/Shift+Enter for custom port
  • Reverse forwarding — press m to switch to Reverse mode; pick a local service and expose it on a remote port (SSH -R style)
  • Smart lifecycle management — auto-pauses when remote port disappears, reactivates when it returns (unlike VS Code's stale forwards)
  • Auto-reconnect — transparently reconnects with exponential backoff on connection drop; all forwards restore automatically
  • Clear error recovery — bind failures show a modal to choose a different port (no silent fallbacks)
  • Visual grouping — forwarded ports appear at the top, separated from unforwarded ports
  • Inactive forward visibility — toggle p to show persisted forwards whose remote port isn't running
  • Desktop notifications — batched notifications when ports appear, disappear, or reactivate (disable with --no-notify)
  • Session persistence — remembers active forwards per destination in ~/.sshfwd/forwards.json
  • Pure Rust SSH — no system OpenSSH dependency, uses russh for in-process connections
  • ProxyJump support — recursive tunneling through jump hosts via SSH config

Platform Support

Remote servers (agent):

  • Linux x86_64 / ARM64 (aarch64) — statically linked via musl
  • macOS (Apple Silicon & Intel) — native binaries

Local machine (main app):

  • macOS (Apple Silicon & Intel)
  • Linux (x86_64 / ARM64)
  • Windows via WSL (experimental)

The agent is automatically deployed when you connect. No manual configuration needed.

Installation

cargo install sshfwd

The published crate includes prebuilt agent binaries for all supported platforms. The agent is automatically deployed to remote servers when you connect.

Usage

# Connect to a remote server
sshfwd user@hostname

# Disable desktop notifications
sshfwd user@hostname --no-notify

# Development: override agent binary
sshfwd user@hostname --agent-path ./target/debug/sshfwd-agent

TUI Interface

Forward mode (default) — shows remote listening ports:

╭ ● user@host │ 5 remote ports │ 2 fwd │ M:Fwd ─────╮
│ FWD       PORT    PROTO   PID      COMMAND         │
│▶->:5432  5432    tcp     1234     postgresql/15/..│
│ ->:8080  8080    tcp6    5678     node server.js  │
│ ──────── ──────── ─────── ──────── ────────────────│
│          3000    tcp     9012     ruby bin/rails s│
│          6379    tcp     3456     redis-server    │
╰────────────────────────────────────────────────────╯
 <j/k>Navigate <g/G>Top/Bottom <Enter/f>Forward <F>Custom Port <m>Mode <p>Inactive <q>Quit

Reverse mode (m to toggle) — shows local listening ports and exposes them on the remote:

╭ ● user@host │ 3 local ports │ 1 rev │ M:Rev ──────╮
│ FWD       PORT    PROTO   PID      COMMAND         │
│▶<-:8080  3000    tcp     9012     ruby bin/rails s│
│          5173    tcp     1234     vite             │
│          5432    tcp     3456     postgresql       │
╰────────────────────────────────────────────────────╯
 <j/k>Navigate <g/G>Top/Bottom <Enter/f>Reverse <m>Mode <p>Inactive <q>Quit

<-:8080 means local port 3000 is exposed on remote port 8080. Press Enter on a local port to configure the remote bind port.

Forwarded ports are grouped at the top with a visual separator.

Port Input Modal

When pressing F/Shift+Enter, or when a bind error occurs:

╭─ Forward port 5432 ───────────╮
│                               │
│  Address already in use       │
│  Local port: 5432█            │
│                               │
│  <Enter>Confirm  <Esc>Cancel  │
╰───────────────────────────────╯

Keyboard Shortcuts

Key Action
j / Down Move selection down
k / Up Move selection up
g Jump to top
G Jump to bottom
m Toggle Forward / Reverse mode
Enter / f Toggle forwarding (Forward: same local port; Reverse: opens modal)
F / Shift+Enter Forward with custom local port — Forward mode only
p Toggle inactive persisted forwards
q / Esc / Ctrl+C Quit

Development

Build from Source

Prerequisites:

  • Rust 1.82.0 or later
  • For Linux agent cross-compilation on macOS: brew install filosottile/musl-cross/musl-cross

Build:

git clone https://github.com/gogoout/sshfwd.rs.git
cd sshfwd.rs

# Cross-compile agents for all platforms
./scripts/build-agents.sh

# Build and install the main application
cargo install --path crates/sshfwd

For development, use cargo build --release -p sshfwd to build without installing.

Verification

cargo fmt -- --check
cargo clippy --all-targets --all-features
cargo test --workspace

All checks run automatically in CI. Pull requests must pass before merging.

See CLAUDE.md for development rules and workspace conventions.

Architecture

Workspace Crates:

  1. sshfwd-common — Shared types (ScanResult, ListeningPort, AgentResponse), serialized as JSON
  2. sshfwd-agent — Remote binary deployed via SSH. Parses /proc/net/tcp{,6}, maps inodes to processes, streams JSON snapshots every 2s
  3. sshfwd — Main application: SSH session, agent deployment, TUI, port forwarding

TUI Architecture (Elm / TEA):

  • All state flows through app.rs with a pure Model/Message/update/view pattern
  • Event loop uses the dua-cli pattern: dedicated OS thread for keyboard input, crossbeam_channel::select! multiplexing

Port Forwarding:

  • ForwardManager runs on a tokio runtime alongside discovery; one manager per session cycle, torn down and rebuilt on reconnect
  • Local (->:N): binds a local TcpListener, tunnels accepted connections via channel_open_direct_tcpip
  • Reverse (<-:N): calls tcpip_forward on the SSH server; incoming connections are pushed back via server_channel_open_forwarded_tcpip and forwarded to 127.0.0.1:local_port
  • Forward states: StartingActive / Paused (port disappeared or disconnected) / modal reopened on bind error
  • Forwards persist to ~/.sshfwd/forwards.json keyed by destination; backward-compatible (old files load as Local)
  • Auto-reconnect: exponential backoff 0s → 30s cap; all listener tasks are aborted cleanly on disconnect so ports are released before the next bind

Data Flow:

┌─ Main App ──────────┐                   ┌──── Remote Server ────┐
│                     │                   │                       │
│ 1. Connect (SSH)    │───── russh ───────│ 2. Upload Agent       │
│ 3. Deploy Agent     │──── exec ch ──────│ 4. Run Agent Loop     │
│ 5. Parse JSON       │◄── stdout pipe ───│    (scan every 2s)    │
│ 6. Display TUI      │                   │                       │
│ 7. Forward Ports    │── direct-tcpip ───│ 8. Tunnel Traffic     │
│                     │                   │                       │
└─────────────────────┘                   └───────────────────────┘

Key Design Decisions:

  • Pure Rust SSHrussh avoids spawning SSH master processes that fight with the TUI for terminal control
  • Agent-based discovery — persistent remote process streams port data; no repeated exec calls
  • Hash-based deployment — only uploads agent binary if SHA256 differs from what's already on the remote
  • Atomic upload — temp file → mvchmod +x prevents mid-upload execution
  • Stale cleanup — verifies /proc/{pid}/comm before killing to avoid hitting reused PIDs
  • No random port fallback — bind failures surface immediately via error modal so the user stays in control
  • Reconnect over swap — on disconnect, ForwardManager is torn down (aborting all listener tasks) and rebuilt fresh; simpler than live session swapping and reuses the existing reactivation path

License

Licensed under the MIT license.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be licensed as above, without any additional terms or conditions.

Acknowledgments

  • ratatui — TUI framework
  • russh — Pure Rust SSH
  • k9s — Interface design inspiration
  • dua-cli — Event loop pattern

About

A rust tui application that handles ssh port forwarding (kinda like what vscode has)

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors