Skip to content

cloudapp3/vmflow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vmflow - Go TCP/UDP Port Forwarding

A bounded-resource Layer 4 (L4) proxy and embeddable Go networking runtime.

vmflow is a self-hosted, cross-platform TCP/UDP port forwarding tool written in pure Go. Run it as a single-binary network proxy or embed the forwarding runtime in your own Go control plane, with explicit resource limits, hot-reloadable rules, a terminal UI, and Prometheus metrics.

Docs CI Go Reference License: MIT

Documentation: Website · 中文 · Docs source

完整使用指南: 中文文档源码 —— 覆盖安装、配置、运维、安全加固与排错。English quick reference is below; the deep guide is in the public docs source.

vmflow TUI dashboard, rules, and precheck views

Real TUI output from the current vmflow build.

TCP/UDP port forwarding features

  • TCP, UDP, and tcp+udp port forwarding
  • Per-rule IPv4/IPv6 source allowlists and denylists with CIDR support
  • Configurable TCP connection limits and bounded UDP sessions, with rejection/drop counters
  • Rule lifecycle management: start, stop, restart, and full snapshot apply
  • Config-driven foreground process with hot reload
  • Local CLI/TUI management for rules, stats, precheck, reload, and metrics
  • Read-only stdio MCP server for local AI-assisted diagnostics
  • Bearer-token auth with viewer/admin roles
  • Structured logs in text or JSON format
  • Prometheus-compatible /metrics
  • Optional durable per-rule cumulative traffic and drop counters
  • Rule precheck for loops, duplicate ports, and unavailable listeners
  • Embeddable Go runtime for products that need in-process forwarding
  • Terminal dashboard and rule management via vmflow tui

Common use cases

  • Forward TCP services such as SSH, databases, and internal APIs between hosts.
  • Relay UDP services with bounded sessions, queues, idle cleanup, and drop metrics.
  • Run a self-hosted Layer 4 proxy on Linux, macOS, or Windows as a native service.
  • Inspect forwarding state and traffic from a local MCP client without exposing a new network service.
  • Embed TCP/UDP forwarding into a Go application, VPS panel, or network control plane.

Quick start

Install the current MCP-enabled prerelease and a colocated config (Linux/macOS):

VMFLOW_BIN_DIR="$(
  curl -fsSL https://raw.githubusercontent.com/cloudapp3/vmflow/main/install.sh \
    | bash -s -- --version v0.2.0-rc.4 --print-install-dir
)" \
  && [ -n "$VMFLOW_BIN_DIR" ] \
  && [ "${VMFLOW_BIN_DIR#/}" != "$VMFLOW_BIN_DIR" ] \
  && [ -x "$VMFLOW_BIN_DIR/vmflow" ] \
  && export PATH="$VMFLOW_BIN_DIR:$PATH"

Omit --version v0.2.0-rc.4 to follow the latest stable release instead. The unversioned installer currently resolves to v0.1.1, which predates the MCP and Source IP policy features documented on this branch. The installer accepts that release's legacy examples/config.yaml archive layout as well as the top-level config.yaml layout used by current releases.

Without an explicit --dir, the installer first reuses a conventional existing vmflow installation so upgrades keep its colocated config. A fresh root install uses /usr/local/bin; an unprivileged user prefers ~/.local/bin or ~/bin when either is already in PATH, then falls back to ~/.local/bin. For an automatic home-directory install, the installer adds an idempotent entry to the selected shell's common startup file (.zshrc, .bashrc, or .profile) and prints the exact reload command; other shells receive a warning. The captured directory and export above make the selected binary available in the current shell without guessing its location.

The first install creates config.yaml beside the binary with mode 0600. Reinstalling or upgrading replaces only the binary and preserves the existing config. Running vmflow uses the colocated config by default; -config overrides that path.

The bundled SSH forwarding example is disabled and listens on loopback only. Review its listen address, target, and firewall exposure before setting enabled: true; a first launch starts the control plane without exposing a new forwarding port.

Start vmflow in one terminal:

vmflow

Query it from another terminal:

vmflow ctl rules
vmflow ctl stats
vmflow ctl metrics
vmflow ctl precheck

Open the terminal UI or show build metadata:

vmflow tui
vmflow version -json

To explicitly request a system installation, use --system. It writes directly when already root and otherwise validates sudo before downloading, then uses sudo only to prepare, inspect, and write the target directory:

curl -fsSL https://raw.githubusercontent.com/cloudapp3/vmflow/main/install.sh \
  | bash -s -- --version v0.2.0-rc.4 --system

When a non-root user requests --system, a newly created colocated config stays root-owned with mode 0600; the installer therefore prints sudo/root commands for verification and startup. The default quick start above uses a per-user install instead and makes vmflow directly runnable in the current shell.

The installer downloads GitHub Release archives, verifies checksums.txt with SHA-256 by default, and installs vmflow plus, only when absent, a colocated config.yaml. Override the automatic directory with --dir PATH or VMFLOW_INSTALL_DIR; explicit directories never modify shell startup files. Use --no-modify-path to disable startup-file changes for an automatic user install, --print-install-dir for machine-readable PATH integration, and --skip-verify to skip checksum verification if needed. For private releases or higher GitHub API limits, set GITHUB_TOKEN or GH_TOKEN.

Or build from source:

git clone https://github.com/cloudapp3/vmflow.git
cd vmflow
go build -trimpath -o vmflow ./cmd/vmflow
cp -n examples/config.yaml config.yaml
./vmflow

Configuration

See examples/config.yaml:

version: 1
control_port: 19090                  # management always binds to 127.0.0.1
udp_max_sessions: 256              # all UDP sessions in this process

log:
  level: info
  format: text

stats:
  persist: false                    # opt in to cumulative counter persistence
  # path: /var/lib/vmflow/stats.json # optional; relative paths use the config dir
  flush_interval: 60s               # minimum 1s; requires restart when changed

auth:
  enabled: false
  tokens:
    - name: admin
      token: change-me
      role: admin

rules:
  - rule_id: ssh-forward
    name: ssh-forward
    protocol: tcp
    listen_addr: 127.0.0.1
    listen_port: 2201
    target_addr: 127.0.0.1
    target_port: 22
    enabled: false
    max_conn: 0                     # TCP: unlimited; UDP: default of 256
    source_ip_mode: allowlist       # off, allowlist, or denylist
    source_ips:                     # literal IPv4/IPv6 or CIDR; max 256
      - 203.0.113.8
      - 198.51.100.0/24
      - 2001:db8:100::/48

source_ip_mode controls admission independently for each forwarding rule. With allowlist, only peers matching source_ips are accepted; with denylist, matching peers are rejected. Omit the fields or use off to allow all peers. A configured allowlist or denylist must contain at least one entry. Invalid modes, hostnames, malformed addresses, empty entries, and lists larger than 256 are rejected during startup and precheck. TCP is checked before the connection consumes max_conn or dials the target. UDP is checked before a session consumes per-rule or manager-wide capacity. Updating a source IP policy restarts that rule and closes its established TCP connections and UDP sessions so the new policy takes effect immediately.

The policy matches the socket peer address. Behind NAT or a Layer 4 proxy that address may be the gateway or proxy rather than the original client. vmflow does not trust forwarded HTTP headers or PROXY protocol metadata. This is an application-level admission control and does not replace a cloud firewall, security group, or host firewall for volumetric attack filtering.

udp_max_sessions limits active UDP sessions across every rule owned by the process (default 256, maximum 4096) and can be changed by config reload. Lowering it below current usage does not terminate established sessions; it rejects new UDP sessions until usage falls below the new limit. For each UDP rule, max_conn: 0 uses the default of 256; for TCP, max_conn: 0 remains unlimited. On a tcp+udp rule, max_conn is enforced independently for TCP connections and UDP sessions; UDP sessions also consume the process-wide limit. Each UDP session owns a socket, a receive goroutine, and a 64 KiB receive buffer. Check available memory and open-file limits before raising either cap.

Set stats.persist: true to preserve per-rule upload/download byte totals, source-IP denial totals, and UDP rejection/drop counters across restarts. Active connection counts and rates remain process-local. The foreground default is stats.json beside the loaded config. The installed Linux systemd unit uses its managed state directory (/var/lib/vmflow/stats.json), including when running with --user vmflow. An explicit relative stats.path is resolved beside the config file. vmflow refuses to start if the stats path aliases the config file or cannot be written.

Hot reload applies only rules and udp_max_sessions. Changes to the local management port, auth, TLS, logging, bot, ACME, certificate cache, certificate review, or stats persistence require a vmflow process restart and are rejected by reload while the old runtime settings remain active.

The management listener always binds to 127.0.0.1; control_port changes only its port. Use an SSH tunnel when CLI/TUI access is needed from another host. Existing control_listen_addr values that name a loopback address are accepted for one migration release with a deprecation warning. Non-loopback legacy values are rejected; replace the field with control_port before upgrading.

TUI rule management

vmflow tui -token ADMIN_TOKEN can manage rules and udp_max_sessions when auth is enabled and the token has the admin role. Viewer tokens and sessions with auth disabled are read-only. In the Rules view use n/e/c to create, edit, or copy, space to toggle, d to delete, g for the global UDP limit, P to precheck, A to apply, and u to discard the draft. The rule editor's Source IPs / CIDRs field accepts comma-separated entries.

Apply writes the validated draft to the config loaded by the running process, which is the config.yaml beside the resolved vmflow binary by default (or the explicit -config path). Auth, TLS, logging, and other process settings still require editing YAML and restarting vmflow.

Commands

vmflow               [-config path] [-control-port 19090]
vmflow ctl           [-token TOKEN] <rules|stats|metrics|precheck|reload>
vmflow tui           [-token TOKEN]
vmflow mcp           [-token TOKEN]
vmflow version       [-json]
vmflow update        [--check] [--version tag]
vmflow service       (install|uninstall|status) [--config path] [--binary path] [--user name] [--log-file path]
                     [--control-port port] [--extra-arg value]...
vmflow uninstall     [--dry-run]

Self-update is supported on Linux and macOS. On Windows, download and install the release ZIP manually.

Aliases are available: ctl=c, tui=t, version=v, update=u, and service=svc.

Run as a service (boot startup)

Register vmflow as a native OS service so it starts at boot and restarts on crash — one command on every platform:

sudo vmflow service install --config /usr/local/bin/config.yaml

For safety, service install refuses to register a service that points at a relative path, a user-writable binary or config file, or either file under user-writable parent directories (symlinks are resolved first). Install vmflow into a protected root/admin-owned path such as /usr/local/bin/vmflow, /opt/vmflow/vmflow, or C:\Program Files\vmflow\vmflow.exe, and keep the service config in a protected root/admin-owned location such as /usr/local/bin/config.yaml or /etc/vmflow/config.yaml; pass --binary if you need to point at the installed binary explicitly. A service running as a dedicated user needs explicit read access to the root-owned config (for example, owner root, group vmflow, mode 0640).

The config is parsed before the OS service definition is changed. Running service install again updates the existing definition and restarts the service with the new settings.

  • Linux: writes and reloads a systemd unit, enables it, restarts the service, and verifies it is active. Logs go to journald (journalctl -u vmflow). The unit runs as root by default with CAP_NET_BIND_SERVICE (so it can bind privileged ports), Restart=on-failure, and a writable /var/lib/vmflow state directory. Pass --user vmflow to run under a dedicated account (created if missing).
  • macOS: writes a launchd daemon (KeepAlive restarts on crash) and bootstraps it. Logs land under /var/log/vmflow/ (override with --log-file).
  • Windows: registers a Windows Service (start=auto, restart-on-failure) visible in services.msc. Because the SCM provides no stdout, logs default to C:\ProgramData\vmflow\logs\vmflow.log; override with --log-file if needed.

Uninstall with sudo vmflow service uninstall (config and logs are left in place). Inspect with vmflow service status.

For a complete removal, run sudo vmflow uninstall. It prints the full plan and requires confirmation before removing the service, binary, platform-default config, installer-owned colocated config, persistent traffic statistics, logs, update cache, and vmflow-owned certificate caches. Stats files are parsed and revalidated immediately before removal; changed or unrecognized files are kept. An unowned colocated config.yaml is preserved. External TLS certificate and key files are never removed because they may be shared with other services. Custom certificate cache directories are left in place unless the directory is exclusively owned by vmflow and contains a .vmflow-owned marker; use --dry-run to inspect the plan without changing the system.

For a user installation created by install.sh, run the installer-level uninstall so it can also remove its exact PATH block from .zshrc, .bashrc, or .profile after the binary cleanup succeeds:

curl -fsSL https://raw.githubusercontent.com/cloudapp3/vmflow/main/install.sh \
  | bash -s -- --uninstall

Use sudo bash -s -- --uninstall only for a root-owned system installation. Re-running the installer-level uninstall also removes a stale installer-owned PATH block when the vmflow files have already been deleted. Similar user-written PATH lines and symlinked or non-writable shell startup files are left untouched.

Local management

The supported management interfaces are vmflow ctl, vmflow tui, and the read-only vmflow mcp adapter. The daemon uses an internal loopback-only control channel to implement them; that transport is not a public integration API and carries no compatibility promise. control_port changes the local port without making it externally reachable.

For remote administration, forward the loopback port over SSH and continue to use the CLI/TUI locally:

ssh -N -L 19090:127.0.0.1:19090 user@server
vmflow ctl rules

Source-IP policy denials are exposed in rule stats and as vmflow_rule_source_ip_denied_total; the counter represents denied TCP connections or UDP datagrams according to the rule protocol. UDP admission-attempt failures and rate-limit queue-capacity drops are exposed as vmflow_udp_session_rejected_total and vmflow_udp_packets_dropped_total metrics. Manager-wide usage is exposed as vmflow_udp_sessions_active and vmflow_udp_sessions_limit.

MCP server

vmflow mcp starts a foreground, tools-only MCP server over stdio. It connects to an already running local vmflow daemon and exits when the MCP client disconnects. It does not start forwarding, listen on an MCP network port, or modify the daemon configuration.

Available tools:

Tool Purpose
get_vmflow_status Connection, version, authorization, rule-count, traffic, and degraded-state summary
list_forwarding_rules Filtered rule summaries without endpoint or source-policy details
get_forwarding_rule Full configuration, running state, and statistics for one rule
get_traffic_stats Filtered and sorted per-rule counters plus aggregate totals
run_config_precheck Read-only validation of the daemon's current persisted configuration

Use a dedicated viewer token when daemon authentication is enabled. Prefer the VMFLOW_CONTROL_TOKEN environment variable so the token is not visible in the process command line:

auth:
  enabled: true
  tokens:
    - name: mcp-viewer
      token: replace-with-a-long-random-token
      role: viewer

Claude Desktop configuration:

{
  "mcpServers": {
    "vmflow": {
      "command": "/usr/local/bin/vmflow",
      "args": ["mcp"],
      "env": {
        "VMFLOW_CONTROL_TOKEN": "replace-with-a-long-random-token"
      }
    }
  }
}

Codex configuration:

[mcp_servers.vmflow]
command = "/usr/local/bin/vmflow"
args = ["mcp"]
env = { VMFLOW_CONTROL_TOKEN = "replace-with-a-long-random-token" }

If control_port is not 19090, add "-addr", "http://127.0.0.1:PORT" to the client arguments. MCP management addresses are restricted to localhost and loopback IPs. For a remote daemon, run vmflow mcp on that host (for example through SSH) rather than exposing its management listener.

Rule details contain target addresses, source IP policies, domains, and remarks; traffic and precheck results can also reveal local network topology. Tool results are sent to the model configured by the MCP client. The server exposes no write tools, raw configuration, bot tokens, certificate private keys, shell execution, file access, prompts, or resources. Precheck may resolve the targets already present in the daemon configuration.

Telegram Bot

vmflow can run an optional Telegram bot for querying and controlling forwarding from a chat. Configure it in config.yaml:

bot_token: "123456:ABC-DEF..."     # Telegram bot token from @BotFather
bot_chat: 123456789                 # your chat ID (from @userinfobot)
bot_control_token: "admin-xxx"      # admin auth token; lets the bot write. Omit for read-only.

The bot only responds to bot_chat (chat-ID allowlist). Private chats and groups are supported; confirmation results stay in the originating chat. Set bot_control_token to an admin auth.tokens entry to enable write commands; without it the bot is read-only. Bot control requires auth.enabled: true.

Commands:

  • /status, /rules, /detail <id> — read-only queries
  • /reload — reload configuration from disk
  • /stop <id>, /start_rule <id>, /toggle <id> — disable / enable / toggle a rule (persists to config.yaml with precheck + optimistic locking)

Write commands use the same authenticated internal control path as the TUI and vmflow ctl, so they get precheck, transactional apply with rollback, optimistic concurrency, and audit logging. Concurrent configuration edits are reported to the chat with a retry hint. Bot requests stay in-process.

Configuring the bot from the TUI

You can also configure and control the bot at runtime from the TUI without editing config.yaml or restarting the daemon:

  • Press b in the Dashboard or Rules view to open the Bot panel (running state + current settings; tokens are masked).
  • e edits bot_token / bot_chat / bot_control_token (token fields are masked) — Ctrl+S verifies the Bot token with Telegram, persists to config.yaml, and rebuilds the bot goroutine in place.
  • s / x start / stop the bot at runtime (not persisted; a daemon restart restores from config).
  • r refreshes.

Bot configuration changes use the same conflict-aware persistence path as rule edits. A rejected token leaves the existing bot and file untouched; a file commit failure restores the previous runtime bot. Daemon restart is not required and forwarding is not interrupted.

Embedding vmflow

Use the top-level package when vmflow is embedded into another Go service:

rt := vmflow.New()
defer rt.Close()

result := rt.Apply(rules) // []engine.Rule
stats := rt.SnapshotAll()

The embedding application owns persistence, auth, UI, audit logs, and business rules. vmflow owns only in-process forwarding, rule lifecycle, and real-time counters. See the embedding guide.

License

MIT. See THIRD_PARTY_NOTICES.md for bundled dependency licenses.

About

Self-hosted TCP/UDP port forwarding and Layer 4 (L4) proxy in Go. Cross-platform single binary with hot reload, TUI, Prometheus metrics, and an embeddable runtime.

Topics

Resources

License

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors