sshuttle 2.0.0 常用 CLI 命令详解

sshuttle 2.0.0 常用 CLI 命令实测详解:主命令隧道全生命周期、DNS 隧道、子网文件、排除子网、守护模式,配套 ssh/ip/iptables/nft/dig/getent/curl/ss 命令的验证用法,全部附真实回显

sshuttleVPN安全SSH2026/10/21

sshuttle 2.0.0 常用 CLI 命令详解

目标:把日常使用 sshuttle 时真正会敲的命令一次讲全——sshuttle 主命令的完整生命周期用法,加上验证与排障必用的配套命令(ssh、ip、iptables、nft、dig、getent、curl、ss),每条命令都给真实输入与实测回显。本文全部内容来自 2026-10-20 在 Ubuntu 22.04.5 LTS 三台实机(办公端 10.89.58.2 / 跳板服务器 10.89.58.3 / 内网应用 10.89.59.3)上的真实执行记录。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64),三台实机
  • sshuttle:2.0.0(venv 安装于 /opt/sshuttle-venv,入口软链接至 /usr/local/bin/sshuttle)
  • 配套组件实测版本:Python 3.10.12、OpenSSH_8.9p1(Ubuntu-3ubuntu0.18, OpenSSL 3.0.2)、iptables v1.8.7 (nf_tables)、nftables v1.0.2 (Lester Gooch)、curl 7.81.0、git 2.34.1、sudo 1.9.9
  • 演示拓扑:
角色主机名地址说明
办公端(执行所有命令)office-client10.89.58.2sshuttle 客户端
跳板服务器jump-servereth1 10.89.58.3 / eth0 10.89.59.2SSH 远端,root 免密
内网应用服务器app-server10.89.59.3:80目标服务(HTTP 演示页)
  • 基线:未起隧道时,办公端到 10.89.59.3 的 HTTP 请求超时(curl 返回 000、退出码 28);目标主机名 target 解析失败(getent 退出码 2)。

二、工具概览

命令角色本文覆盖内容
sshuttle主角:建立/停止透明代理隧道版本查询、基础隧道全生命周期、DNS 隧道、子网文件、排除子网、守护模式
ssh隧道的传输底座,免密链路验证版本、免密登录验证
ip查看路由与策略路由、管理网络命名空间ip route/ip rule/ip netns 全流程
iptables查看/核对 nat 方法留下的防火墙规则iptables-save -t nat、规则计数
nft查看/核对 nft 方法留下的表nft list table、整表删除
dig / getent验证 DNS 隧道效果隧道前后解析对照
curl验证 TCP 转发效果隧道前后 HTTP 对照(000 → 200)
ss确认 sshuttle 本地监听端口ss -ltnp 过滤监听

使用逻辑一句话:sshuttle 负责建隧道,其余命令负责「证明隧道通了」与「看懂规则在哪」。

三、前置条件

  1. sshuttle 2.0.0 已安装(四种安装方案见 《Ubuntu 22.04 安装 sshuttle 指南》);
  2. 到远端的 SSH 免密已配置(同上第九章);
  3. 客户端 root/sudo 权限;
  4. 参数与规则的深度原理不在本文展开,需要时查 《sshuttle 2.0.0 运行参数与路由规则详解》。

四、sshuttle 主命令详解

4.1 版本与帮助

sshuttle -V
2.0.0
sshuttle --help
usage: sshuttle [-l [ip:]port] -r [user@]sshserver[:port] <subnets...>

positional arguments:
  IP/MASK[:PORT[-PORT]]...
                        capture and forward traffic to these subnets
                        (whitespace separated)

options:
  -h, --help            show this help message and exit
  -l [IP:]PORT, --listen [IP:]PORT
                        transproxy to this ip address and port number
  -H, --auto-hosts      continuously scan for remote hostnames and update
                        local /etc/hosts as they are found
  -N, --auto-nets       automatically determine subnets to route
  --dns                 capture local DNS requests and forward to the remote
                        DNS server
  --ns-hosts IP[,IP]    capture and forward DNS requests made to the following
                        servers (comma separated)
  --to-ns IP[:PORT]     the DNS server to forward requests to; defaults to
                        servers in /etc/resolv.conf on remote side if not
                        given.
  --method TYPE         auto, nft, nat, tproxy, pf, ipfw
  --python PATH         path to python interpreter on the remote server
  -r [USERNAME[:PASSWORD]@]ADDR[:PORT], --remote [USERNAME[:PASSWORD]@]ADDR[:PORT]
                        ssh hostname (and optional username and password) of
                        remote sshuttle server
  -x IP/MASK[:PORT[-PORT]], --exclude IP/MASK[:PORT[-PORT]]
                        exclude this subnet (can be used more than once)
  -X PATH, --exclude-from PATH
                        exclude the subnets in a file (whitespace separated)
  -v, --verbose         increase debug message verbosity (can be used more
                        than once)
  -V, --version         print the sshuttle version number and exit
  -e CMD, --ssh-cmd CMD
                        the command to use to connect to the remote [ssh]
  --no-cmd-delimiter    do not add a double dash before the python command
  --remote-shell PROGRAM
                        alternate remote shell program instead of defacto
                        posix shell. For Windows targets it would be either
                        `cmd` or `powershell` unless something like git-bash
                        is in use.
  --seed-hosts HOSTNAME[,HOSTNAME]
                        comma-separated list of hostnames for initial scan
                        (may be used with or without --auto-hosts)
  --no-latency-control  sacrifice latency to improve bandwidth benchmarks
  --latency-buffer-size SIZE
                        size of latency control buffer
  --wrap NUM            restart counting channel numbers after this number
                        (for testing)
  --disable-ipv6        disable IPv6 support
  -D, --daemon          run in the background as a daemon
  -s PATH, --subnets PATH
                        file where the subnets are stored, instead of on the
                        command line
  --syslog              send log messages to syslog (default if you use
                        --daemon)
  --pidfile PATH        pidfile name (only if using --daemon) [./sshuttle.pid]
  --user USER           apply all the rules only to this linux user
  --group GROUP         apply all the rules only to this linux group
  --firewall            (internal use only)
  --hostwatch           (internal use only)
  --sudoers-no-modify   Prints a sudo configuration to STDOUT which allows a
                        user to run sshuttle without a password. This option
                        is INSECURE because, with some cleverness, it also
                        allows the user to run any command as root without a
                        password. The output also includes a suggested method
                        for you to install the configuration.
  --sudoers-user SUDOERS_USER
                        Set the user name or group with %group_name for
                        passwordless operation. Default is the current user.
                        Only works with the --sudoers-no-modify option.
  --no-sudo-pythonpath  do not set PYTHONPATH when invoking sudo
  -t [MARK], --tmark [MARK]
                        tproxy optional traffic mark with provided MARK value
                        in hexadecimal (default '0x01')
  --namespace NAMESPACE
                        Run inside of a net namespace with the given name.
  --namespace-pid NAMESPACE_PID
                        Run inside the net namespace used by the process with
                        the given pid.

4.2 基础隧道全生命周期(-r + 子网)

一条命令起隧道(前台运行):

sshuttle -v -r root@10.89.58.3 10.89.59.0/24

实测启动输出(节选,-v 下能看到防火墙动作):

c : Connecting to server...
 s: Running server on remote host with /usr/bin/python3 (version 3.10.12)
 s: latency control setting = True
 s: auto-nets:False
c : Connected to server.
fw: setting up.
fw: iptables -w -t nat -N sshuttle-12300
fw: iptables -w -t nat -I OUTPUT 1 -j sshuttle-12300
fw: iptables -w -t nat -I PREROUTING 1 -j sshuttle-12300
fw: iptables -w -t nat -A sshuttle-12300 -j REDIRECT --dest 10.89.59.0/24 -p tcp --to-ports 12300

验证转发生效(隧道运行中另开终端):

curl -s -m 5 http://10.89.59.3/
sshuttle tunnel demo - served by app-server 10.89.59.0/24
curl -s -o /dev/null -w "%{http_code}\n" -m 5 http://10.89.59.3/
200

前台 Ctrl+C 停止,实测回收输出:

fw: undoing changes.
fw: iptables -w -t nat -D OUTPUT -j sshuttle-12300
fw: iptables -w -t nat -D PREROUTING -j sshuttle-12300
fw: iptables -w -t nat -F sshuttle-12300
fw: iptables -w -t nat -X sshuttle-12300

回收后规则清零确认:

iptables -t nat -S | grep -c sshuttle
0

4.3 DNS 隧道(--dns)

sshuttle --dns -v -r root@10.89.58.3 10.89.59.0/24

实测启动摘要(DNS 相关行):

c : DNS : on
c : DNS requests normally directed at these servers will be redirected to remote:
c : DNS listening on ('127.0.0.1', 12298).

隧道中解析内网主机名 target(隧道前 getent hosts target 退出码为 2,解析失败):

getent hosts target; echo "exit=$?"
10.89.59.3      target
exit=0
dig +short target
10.89.59.3

4.4 子网文件与自定义监听(-s / -l)

子网列表放文件(本文实测文件 /opt/subnets.txt,内容为 10.89.59.0/24),同时用 -l 把本地监听改到 12345:

sshuttle -s /opt/subnets.txt -l 127.0.0.1:12345 -r root@10.89.58.3

实测效果:防火墙链名随端口变为 sshuttle-12345:

-A sshuttle-12345 -d 10.89.59.0/24 -p tcp -j REDIRECT --to-ports 12345
-A sshuttle-12345 -m addrtype --dst-type LOCAL -j RETURN

转发验证:

sshuttle tunnel demo - served by app-server 10.89.59.0/24

4.5 排除子网(-x)

全网转发但放行某台主机直连(不进隧道):

sshuttle -v -r root@10.89.58.3 0.0.0.0/0 -x 10.89.59.3/32

实测规则(被排除地址精确 RETURN,其余全网 REDIRECT):

-A sshuttle-12300 -d 10.89.59.3/32 -p tcp -j RETURN
-A sshuttle-12300 -d 127.0.0.1/32 -p tcp -j RETURN
-A sshuttle-12300 -d 10.89.58.3/32 -p tcp -j RETURN
-A sshuttle-12300 -p tcp -j REDIRECT --to-ports 12300
-A sshuttle-12300 -m addrtype --dst-type LOCAL -j RETURN

💡 说明:-d 10.89.58.3/32 -j RETURN 不是我们写的——2.0.0 会自动排除远端服务器自身 IP,防止 SSH 会话被自己的转发规则劫持。

4.6 守护模式(-D / --pidfile / kill)

sshuttle --daemon --pidfile /var/run/sshuttle.pid -r root@10.89.58.3 10.89.59.0/24; echo "start-exit=$?"
start-exit=0
cat /var/run/sshuttle.pid
5414

守护期间转发验证:

sshuttle tunnel demo - served by app-server 10.89.59.0/24

停止守护进程:

kill $(cat /var/run/sshuttle.pid)
ls -l /var/run/sshuttle.pid
ls: cannot access '/var/run/sshuttle.pid': No such file or directory
iptables -t nat -S | grep -c sshuttle
0

要点:PID 文件由 sshuttle 在退出时自动删除(报 No such file 不是异常);规则同步清零。

五、配套命令详解

5.1 ssh:免密链路验证

ssh -V
OpenSSH_8.9p1 Ubuntu-3ubuntu0.18, OpenSSL 3.0.2 15 Mar 2022
ssh root@10.89.58.3 hostname
jump-server

不提示密码直接返回主机名,即免密链路正常——这是 sshuttle 一切远程动作的前提(公钥分发步骤见安装指南 9.1–9.3 节)。

5.2 ip:路由、策略路由与命名空间

查看路由表(验证 nat 方法不改路由):

ip route
default via 10.89.58.1 dev eth0 
10.89.58.0/24 dev eth0 proto kernel scope link src 10.89.58.2 

查看策略路由(tproxy 排障关键):

ip rule
0:	from all lookup local
32766:	from all lookup main
32767:	from all lookup default

tproxy 方法需要预置两条策略路由后才能转发(缺规则时 curl 超时 000,补上即通,前后对照实测见参数篇 5.9 节):

ip rule add fwmark 0x1 lookup 100
ip route add local default dev lo table 100

网络命名空间(--namespace 的前置准备,全流程实测):

ip netns add shuttletest
ip netns list
shuttletest (id: 1)
ip netns exec shuttletest ip -4 addr show v-ns1 | grep inet
    inet 10.90.0.2/24 scope global v-ns1

隧道结束后清理:

ip netns del shuttletest

5.3 iptables:nat 方法规则核对

导出 nat 表规则(输出节选,仅保留 sshuttle 相关行):

iptables-save -t nat
-N sshuttle-12300
-A sshuttle-12300 -d 127.0.0.1/32 -p tcp -j RETURN
-A sshuttle-12300 -d 10.89.58.3/32 -p tcp -j RETURN
-A sshuttle-12300 -d 10.89.59.0/24 -p tcp -j REDIRECT --to-ports 12300
-A sshuttle-12300 -m addrtype --dst-type LOCAL -j RETURN
-P OUTPUT ACCEPT
-A OUTPUT -j sshuttle-12300
-P PREROUTING ACCEPT
-A PREROUTING -j sshuttle-12300

规则计数(快速判断「隧道是否还残留规则」):

iptables -t nat -S | grep -c sshuttle
0

(隧道运行中该值为 3 以上:OUTPUT/PREROUTING 挂链 + 业务链;清零即环境已还原。)

5.4 nft:nftables 表核对与清理

列出 sshuttle 的表:

nft list table inet sshuttle-ipv4-12300
table inet sshuttle-ipv4-12300 {
	chain prerouting {
		type nat hook prerouting priority dstnat; policy accept;
		jump sshuttle-ipv4-12300
	}

	chain output {
		type nat hook output priority -100; policy accept;
		jump sshuttle-ipv4-12300
	}

	chain sshuttle-ipv4-12300 {
		meta nfproto != ipv4 return
		fib daddr type local return
		meta l4proto tcp ip daddr 127.0.0.1 return
		meta l4proto tcp ip daddr 10.89.58.3 return
		meta l4proto tcp ip daddr 10.89.59.0/24 redirect to :12300
	}
}

清理遗留表(sshuttle 正常退出会自己删;异常中断后才需手工执行):

nft delete table inet sshuttle-ipv4-12300
nft delete table inet sshuttle-ipv6-12300

(对应 sshuttle 正常回收时的实测日志 fw: nft delete table inet sshuttle-ipv6-12300 / fw: nft delete table inet sshuttle-ipv4-12300。)

5.5 dig 与 getent:DNS 验证

dig 查询(隧道中):

dig +short target
10.89.59.3

getent 走系统解析链验证(更贴近应用真实行为):

getent hosts target; echo "exit=$?"
10.89.59.3      target
exit=0

对照:隧道前同一命令退出码为 2(解析失败)。两个命令的分工:dig 直接问 DNS 服务器,getent 走 nsswitch 完整解析链——验证 --dns 效果时建议两个都跑。

5.6 curl:HTTP 连通性验证

带超时与状态码的标准验证式:

curl -s -o /dev/null -w "%{http_code}\n" -m 5 http://10.89.59.3/
200

取页面内容确认打到了目标服务:

curl -s -m 5 http://10.89.59.3/
sshuttle tunnel demo - served by app-server 10.89.59.0/24

隧道前基线(超时退出码 28):

curl -s -m 3 -o /dev/null -w "%{http_code}" http://10.89.59.3/; echo; echo "exit=$?"
000
exit=28

-m 超时务必带上:基线不通时无超时的 curl 会挂住很久。

5.7 ss:确认本地监听端口

隧道运行中:

ss -ltnp | grep -E "12300|1229"
LISTEN 0      10         127.0.0.1:12300      0.0.0.0:*    users:(("sshuttle",pid=6045,fd=7))
LISTEN 0      10             [::1]:12300         [::]:*    users:(("sshuttle",pid=6045,fd=5))

两个监听分别对应 IPv4(127.0.0.1:12300)与 IPv6(::1:12300)本地代理入口;用 -l 自定义端口后此处显示新端口(DNS 隧道开启时还会额外出现 12298 一类动态端口)。

六、注意事项

  1. 退出方式决定回收完整性:前台 Ctrl+C、kill <pid>(SIGTERM)都会触发 fw: undoing changes 完整回收;SIGKILL(kill -9)会跳过清理,需按 5.3/5.4 节手工核对并删除残留规则。
  2. 不要用 pkill -f 任意匹配 sshuttle:匹配串容易连带误杀自己正在执行的同名命令行,停守护进程一律 kill $(cat /var/run/sshuttle.pid)。
  3. 多实例并行要错开端口:防火墙链名就是 sshuttle-<监听端口>,两个实例都用默认端口会冲突;第二实例加 -l 127.0.0.1:12345 一类的自定义端口。
  4. 方法与规则体系要一致:nat 与 nft 规则混用会报链不兼容(实测原文与解决见参数篇 FAQ),切换方法前确认上一条隧道已正常退出。
  5. --user 仅 nat 方法支持:nft/tproxy 摘要均标注 not available;共享主机按身份隔离选 nat 方法。
  6. 密码不落命令行:-r user:password@host 语法虽存在,但密码会进 shell 历史;一律用密钥免密。

七、常见问题

7.1 curl 一直是 000、exit=28

排查顺序:① ss -ltnp 确认 12300 在监听(未监听说明隧道没起来);② ssh root@<远端> hostname 确认免密没失效;③ 加 -v 重启看 Subnets to forward 是否包含目标网段;④ 用了 tproxy 就检查 ip rule 是否有 fwmark 条目(见参数篇 5.9 节)。

7.2 getent/dig 解析还是失败

确认启动命令带了 --dns(摘要 DNS : on);本机 resolv.conf 里的 DNS 服务器要能被 sshuttle 捕获(摘要 DNS requests normally directed at these servers 列表),特殊环境用 --ns-hosts 显式指定。

7.3 报「You must have root privileges」

fw: fatal: You must have root privileges (or enable su/sudo) to set the firewall——权限问题,用 root 或按安装指南 9.4 节配置 sudo 免密;非交互环境的 sudo: a terminal is required 报错同因同解。

7.4 kill 之后 pidfile 不见了是坏了吗

不是。PID 文件由 sshuttle 退出时自动删除(4.6 节实测 ls 报 No such file,且规则计数为 0),这正是干净退出的标志。

7.5 起隧道时报 iptables 链不兼容

iptables v1.8.7 (nf_tables): chain sshuttle-12300' in table nat' is incompatible, use 'nft' tool.——nftables 与 iptables-nft 规则混用所致,统一 --method 或清理遗留表(详细成因与处理见参数篇 FAQ)。

八、参考链接

  1. 官方文档·usage:https://sshuttle.readthedocs.io/en/stable/usage.html
  2. 官方文档·setups(平台相关用法):https://sshuttle.readthedocs.io/en/stable/setups.html
  3. GitHub 仓库:https://github.com/sshuttle/sshuttle
  4. 本专栏系列:《sshuttle 2.0.0 介绍与选型指南》、《Ubuntu 22.04 安装 sshuttle 指南》、《sshuttle 2.0.0 运行参数与路由规则详解》