sshuttle 2.0.0 常用 CLI 命令详解
sshuttle 2.0.0 常用 CLI 命令实测详解:主命令隧道全生命周期、DNS 隧道、子网文件、排除子网、守护模式,配套 ssh/ip/iptables/nft/dig/getent/curl/ss 命令的验证用法,全部附真实回显
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-client | 10.89.58.2 | sshuttle 客户端 |
| 跳板服务器 | jump-server | eth1 10.89.58.3 / eth0 10.89.59.2 | SSH 远端,root 免密 |
| 内网应用服务器 | app-server | 10.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 负责建隧道,其余命令负责「证明隧道通了」与「看懂规则在哪」。
三、前置条件
- sshuttle 2.0.0 已安装(四种安装方案见 《Ubuntu 22.04 安装 sshuttle 指南》);
- 到远端的 SSH 免密已配置(同上第九章);
- 客户端 root/sudo 权限;
- 参数与规则的深度原理不在本文展开,需要时查 《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 一类动态端口)。
六、注意事项
- 退出方式决定回收完整性:前台 Ctrl+C、
kill <pid>(SIGTERM)都会触发fw: undoing changes完整回收;SIGKILL(kill -9)会跳过清理,需按 5.3/5.4 节手工核对并删除残留规则。 - 不要用 pkill -f 任意匹配 sshuttle:匹配串容易连带误杀自己正在执行的同名命令行,停守护进程一律
kill $(cat /var/run/sshuttle.pid)。 - 多实例并行要错开端口:防火墙链名就是
sshuttle-<监听端口>,两个实例都用默认端口会冲突;第二实例加-l 127.0.0.1:12345一类的自定义端口。 - 方法与规则体系要一致:nat 与 nft 规则混用会报链不兼容(实测原文与解决见参数篇 FAQ),切换方法前确认上一条隧道已正常退出。
--user仅 nat 方法支持:nft/tproxy 摘要均标注 not available;共享主机按身份隔离选 nat 方法。- 密码不落命令行:
-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)。