sshuttle 2.0.0 运行参数与路由规则详解
sshuttle 2.0.0 全参数实测详解:--help 全表与真实默认值、-v 启动摘要逐行解读、nat/nft/tproxy 三种转发方法的防火墙规则快照与回收动作、DNS/自动发现/守护/权限/命名空间等参数分类实测与场景化模板
sshuttle 2.0.0 运行参数与路由规则详解
目标:把 sshuttle 2.0.0 的全部命令行参数讲透——每个参数给真实默认值、实测回显与生效验证,并把三种 Linux 转发方法(nat/nft/tproxy)在本机防火墙里留下的规则逐条摆出来,让读者既能照抄场景模板,也能在排障时看懂规则从哪来、往哪去。本文全部命令与回显来自 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(pip venv 安装于 /opt/sshuttle-venv,安装过程见 《Ubuntu 22.04 安装 sshuttle 指南》)
- 组件版本:Python 3.10.12、OpenSSH_8.9p1、iptables v1.8.7 (nf_tables)、nftables v1.0.2 (Lester Gooch)、curl 7.81.0
- 演示拓扑:
| 角色 | 主机名 | 地址 | 说明 |
|---|---|---|---|
| 办公端(跑 sshuttle) | office-client | 10.89.58.2 | 本机,无 systemd 托管需求,命令行运行 |
| 跳板服务器(SSH 远端) | jump-server | eth1 10.89.58.3 / eth0 10.89.59.2 | root 免密 SSH,双网卡 |
| 内网应用服务器 | app-server | 10.89.59.3:80 | 目标服务,页面内容为 sshuttle tunnel demo - served by app-server 10.89.59.0/24 |
- 基线状态(未起隧道时):办公端直连 10.89.59.0/24 不通(curl 返回 HTTP=000、退出码 28),路由表只有 58 段直连路由——这是后文每个实验的「实验前」对照基准。
二、前置条件
- sshuttle 2.0.0 已安装:见 《Ubuntu 22.04 安装 sshuttle 指南》,本文统一使用 venv 入口
/opt/sshuttle-venv/bin/sshuttle(软链接后即sshuttle)。 - 到远端的 SSH 免密已配置:
ssh root@10.89.58.3 hostname直接返回jump-server,无需密码(配置步骤见安装指南第九章)。 - 客户端具备 root/sudo:改写防火墙规则的前提。
- 基线验证:确认「起隧道前不通、路由表干净」,后文所有验证都以此为对照:
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
curl -s -m 3 -o /dev/null -w "%{http_code}" http://10.89.59.3/; echo; echo "exit=$?"
000
exit=28
💡 说明:
HTTP=000表示连接未建立,exit=28是 curl 的超时退出码。基线不通才能证明后文的「通了」是 sshuttle 的功劳。
三、参数查看与修改方式
3.1 用 --help 查看全部参数
sshuttle --help
实测全量输出(2.0.0):
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.
3.2 用 -V 确认版本
sshuttle -V
2.0.0
3.3 用 -v 看启动摘要(最重要的排障视图)
任何排障的第一步都是加 -v 启动,摘要把「方法、协议开关、转发/排除子网、监听地址」一次列全。实测(--method nft -r root@10.89.58.3 10.89.59.0/24 -v):
Starting sshuttle proxy (version 2.0.0).
c : Starting firewall manager with command: ['/opt/sshuttle-venv/bin/sshuttle', '-v', '--method', 'nft', '--firewall']
fw: Starting firewall manager with Python version 3.10.12
fw: ready method name nft.
c : Using default IPv4 listen address 127.0.0.1
c : IPv6 enabled: Using default IPv6 listen address ::1
c : Method: nft
c : IPv4: on
c : IPv6: on
c : UDP : off (not available with nft method)
c : DNS : off (available)
c : User: off (not available with nft method)
c : Subnets to forward through remote host (type, IP, cidr mask width, startPort, endPort):
c : (<AddressFamily.AF_INET: 2>, '10.89.59.0', 24, 0, 0)
c : Subnets to exclude from forwarding:
c : (<AddressFamily.AF_INET: 2>, '127.0.0.1', 32, 0, 0)
c : (<AddressFamily.AF_INET6: 10>, '::1', 128, 0, 0)
c : (<AddressFamily.AF_INET: 2>, '10.89.58.3', 32, 0, 0)
c : TCP redirector listening on ('::1', 12300, 0, 0).
c : TCP redirector listening on ('127.0.0.1', 12300).
c : Starting client with Python version 3.10.12
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.
逐块解读:
| 摘要行 | 含义 |
|---|---|
Method: nft | 实际选中的转发方法(与 --method 传入一致;auto 时显示自动选中的结果) |
IPv4: on / IPv6: on | 双栈转发已启用(--disable-ipv6 可关掉 IPv6) |
UDP : off (not available with nft method) | UDP 转发能力状态,括号里说明该不可用是方法限制还是未开启 |
DNS : off (available) | DNS 隧道未开(加 --dns 变 on) |
User: off (not available with nft method) | 按用户限定的 owner 匹配是否生效 |
Subnets to forward | 将被转发的子网列表(十元组形式:协议族/IP/掩码宽/起始端口/结束端口) |
Subnets to exclude | 排除列表——自动包含 127.0.0.1/32、::1/128,以及 2.0.0 新增的远端服务器自身 IP(10.89.58.3) |
TCP redirector listening on 12300 | 本地透明代理监听端口(默认 12300,可用 -l 改) |
s: Running server on remote host with /usr/bin/python3 | 远端服务进程已用该解释器拉起 |
c : Connected to server. | SSH 通道建立完成,隧道就绪 |
3.4 参数的修改入口
sshuttle 没有常驻配置文件,参数只有两个入口:命令行(本文全部内容)与子网文件(-s,见 5.1 节)。需要「持久化」时用 shell 别名、systemd 单元(见安装指南 5.6 节)或 --daemon 模式固化启动参数。
四、参数总览(含真实默认值)
按功能分组的全参数表,默认值取自 3.1 节 --help 原文与 3.3 节实测摘要:
子网与排除
| 参数 | 作用 | 默认值 |
|---|---|---|
IP/MASK[:PORT[-PORT]](位置参数) | 待转发子网,空格分隔多个,可限定端口范围 | 必填(无默认) |
-x IP/MASK[:PORT[-PORT]] | 排除子网,可重复 | 无(自动排除 127.0.0.1/32、::1/128 与远端服务器 IP) |
-X PATH | 从文件读取排除列表 | 无 |
-s PATH | 从文件读取转发子网列表 | 无(默认从命令行读) |
转发方法与监听
| 参数 | 作用 | 默认值 |
|---|---|---|
--method TYPE | 转发方法,可选 auto/nft/nat/tproxy/pf/ipfw | auto(Linux 实测自动选中 nat) |
-l [IP:]PORT | 本地透明代理监听地址与端口 | 实测 127.0.0.1:12300(IPv6 ::1 同端口) |
--disable-ipv6 | 关闭 IPv6 转发 | 不禁用(IPv6 on) |
-t [MARK] | tproxy 方法的防火墙标记值 | 0x01 |
DNS
| 参数 | 作用 | 默认值 |
|---|---|---|
--dns | 劫持本机 DNS 查询经隧道由远端代答 | off |
--ns-hosts IP[,IP] | 指定要劫持的 DNS 服务器列表 | 无(默认取本机 resolv.conf 中的 nameserver) |
--to-ns IP[:PORT] | 远端代答使用的 DNS 服务器 | 无(默认用远端 /etc/resolv.conf 里的服务器) |
自动发现
| 参数 | 作用 | 默认值 |
|---|---|---|
-N, --auto-nets | 远端回传路由表自动生成转发子网 | off |
-H, --auto-hosts | 持续扫描远端主机名并更新本地 /etc/hosts | off |
--seed-hosts HOSTNAME[,HOSTNAME] | 初始扫描种子主机名 | 无 |
连接远端
| 参数 | 作用 | 默认值 |
|---|---|---|
-r [USERNAME[:PASSWORD]@]ADDR[:PORT] | SSH 远端 | 必填(无默认) |
--python PATH | 远端 Python 解释器路径 | 无(实测自动用远端 /usr/bin/python3) |
-e CMD | 建立 SSH 连接用的命令 | ssh |
--no-cmd-delimiter | 不在远端命令前加双横线分隔符 | 默认加分隔符 |
--remote-shell PROGRAM | 指定远端 shell(2.0.0 新增,Windows 可用 cmd/powershell) | 无(默认 POSIX shell) |
守护与日志
| 参数 | 作用 | 默认值 |
|---|---|---|
-D, --daemon | 后台守护运行 | 前台运行 |
--pidfile PATH | PID 文件路径(仅 --daemon 有效) | ./sshuttle.pid |
--syslog | 日志交给 syslog | --daemon 时默认开启 |
-v | 详细日志,可叠加(-vv/-vvv 更细) | 关 |
--wrap NUM | 通道计数重启阈值 | 无(测试用途) |
延迟控制
| 参数 | 作用 | 默认值 |
|---|---|---|
--no-latency-control | 牺牲延迟换吞吐 | 关(默认开延迟控制,摘要显示 latency control setting = True) |
--latency-buffer-size SIZE | 延迟控制缓冲区大小 | 官方未标注具体默认值 |
权限与 sudo
| 参数 | 作用 | 默认值 |
|---|---|---|
--user USER | 转发规则只对指定 Linux 用户生效 | off(nft/tproxy 方法不可用) |
--group GROUP | 转发规则只对指定用户组生效 | off |
--sudoers-no-modify | 打印免密 sudo 配置到标准输出(不落盘) | 无 |
--sudoers-user SUDOERS_USER | 指定免密配置面向的用户/组 | 当前用户(仅配合 --sudoers-no-modify) |
--no-sudo-pythonpath | 调用 sudo 时不设置 PYTHONPATH | 默认设置 PYTHONPATH |
--firewall / --hostwatch | 内部参数(防火墙子进程/hostwatch 子进程入口),不手工调用 | — |
网络命名空间(2.0.0 新增)
| 参数 | 作用 | 默认值 |
|---|---|---|
--namespace NAMESPACE | 在指定名称的网络命名空间内运行 | 无 |
--namespace-pid NAMESPACE_PID | 进入指定 PID 进程所在命名空间 | 无 |
💡 说明:与 1.0.5 相比,2.0.0 的参数面多了
--group、--namespace/--namespace-pid、--remote-shell、--sudoers-no-modify(替代旧的--sudoers自动改写),--tmark补充了默认值标注。升级选型依据见 《sshuttle 2.0.0 介绍与选型指南》。
五、核心参数分类详解
5.1 子网语法、-x 排除与 -s 子网文件
引导:转发目标是一切行为的起点。位置参数语法为 IP/MASK[:PORT[-PORT]],可以只转发特定端口段;-x 把子网从转发范围剔除;子网多时用 -s 文件收纳。
基础用法实测(转发整个 59 段,curl 打到目标页):
sshuttle -r root@10.89.58.3 10.89.59.0/24
# 另开终端
curl -s -m 5 http://10.89.59.3/
sshuttle tunnel demo - served by app-server 10.89.59.0/24
-x 排除实测(全网段转发但排除目标主机,规则里出现 RETURN 直通项):
sshuttle -v -r root@10.89.58.3 0.0.0.0/0 -x 10.89.59.3/32
启动摘要节选:
c : User: on
c : Subnets to forward through remote host (type, IP, cidr mask width, startPort, endPort):
c : (<AddressFamily.AF_INET: 2>, '10.89.59.0', 24, 0, 0)
c : Subnets to exclude from forwarding:
c : (<AddressFamily.AF_INET: 2>, '10.89.59.3', 32, 0, 0)
c : (<AddressFamily.AF_INET: 2>, '127.0.0.1', 32, 0, 0)
对应防火墙链节选(目标 IP 精确 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 -d 10.89.59.0/24 -p tcp -j REDIRECT --to-ports 12300
-A sshuttle-12300 -m addrtype --dst-type LOCAL -j RETURN
-s 子网文件实测。文件内容:
cat /opt/subnets.txt
10.89.59.0/24
配合 -l 127.0.0.1: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
💡 说明:本地监听端口(默认 12300)会直接写进防火墙链名
sshuttle-<端口>;多实例并行运行时要给每个实例不同的-l端口,否则链名冲突。
0.0.0.0/0 全网转发与远端 IP 自动排除实测。转发全网段时的防火墙链:
-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 -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:我们没有手工写 -x,这是 2.0.0 的自动行为(Release Notes #1191)——远端服务器自身 IP 被自动排除,保证 SSH 连接不被自己的转发规则劫持。排除列表同样能看到它:
c : Subnets to exclude from forwarding:
c : (<AddressFamily.AF_INET: 2>, '127.0.0.1', 32, 0, 0)
c : (<AddressFamily.AF_INET6: 10>, '::1', 128, 0, 0)
c : (<AddressFamily.AF_INET: 2>, '10.89.58.3', 32, 0, 0)
全网转发期间到远端的 SSH 依然直连可用(未被劫持):
ssh root@10.89.58.3 hostname; echo "exit=$?"
jump-server
exit=0
5.2 --method:转发方法与防火墙规则全解
引导:--method 决定 sshuttle 用哪套防火墙机制劫持流量,是路由规则篇的核心。Linux 下三种方法的自动选择、规则形态、能力边界与回收动作,逐一实测如下。
能力对照(来自 3.3 节实测摘要的 UDP/DNS/User 行):
| 方法 | TCP | UDP | DNS | --user | 依赖 |
|---|---|---|---|---|---|
| nat | 是 | 否 | 是 | 是 | iptables nat 表(REDIRECT) |
| nft | 是 | 否 | 是 | 否(not available with nft method) | nftables |
| tproxy | 是 | 是 | 是 | 否(not available with tproxy method) | iptables mangle 表 + 手工策略路由(见 5.9) |
如何选择:普通 TCP/DNS 需求用默认 nat;系统已全面转向 nftables 管理时用 nft 保持体系统一;确需转发 UDP 才上 tproxy(多两条手工路由的成本)。
5.2.1 auto:自动选择(实测选 nat)
sshuttle -v -v -v --method auto -r root@10.89.58.3 10.89.59.0/24
输出节选(-vvv 下防火墙管理器自述其方法判定过程):
c : Starting firewall manager with command: ['/opt/sshuttle-venv/bin/sshuttle', '-v', '-v', '-v', '--method', 'auto', '--firewall']
fw: Selecting a method automatically...
fw: Method 'nat' was automatically selected.
fw: ready method name nat.
c : Method: nat
在装有 iptables 的 Ubuntu 22.04 上,auto 依据源码内置顺序(nat → nft → pf → ipfw,2026-10-20 对 2.0.0 源码 methods 判定顺序的核查)选中 nat。日常使用直接 --method auto 即可,无需显式指定。
5.2.2 nat:iptables REDIRECT(默认落地)
启动时防火墙动作(-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: ip6tables -w -t nat -N sshuttle-12300
fw: ip6tables -w -t nat -F sshuttle-12300
fw: ip6tables -w -t nat -I OUTPUT 1 -j sshuttle-12300
fw: ip6tables -w -t nat -I PREROUTING 1 -j sshuttle-12300
fw: ip6tables -w -t nat -A sshuttle-12300 -j RETURN --dest ::1/128 -p tcp
fw: ip6tables -w -t nat -A sshuttle-12300 -j RETURN -m addrtype --dst-type LOCAL
fw: iptables -w -t nat -N sshuttle-12300
fw: iptables -w -t nat -F 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 RETURN --dest 127.0.0.1/32 -p tcp
fw: iptables -w -t nat -A sshuttle-12300 -j RETURN --dest 10.89.58.3/32 -p tcp
fw: iptables -w -t nat -A sshuttle-12300 -j REDIRECT --dest 10.89.59.0/24 -p tcp --to-ports 12300
fw: iptables -w -t nat -A sshuttle-12300 -j RETURN -m addrtype --dst-type LOCAL
动作解读:建独立链 sshuttle-12300 并挂到 nat 表 OUTPUT 与 PREROUTING 链首;链内依次是「本机回环 RETURN → 远端服务器 IP RETURN → 目标子网 REDIRECT 到 12300 → 本机地址类型 RETURN」。SSH 会话目标(10.89.58.3)被 RETURN 直通,因此隧道自身的 SSH 流量不会被重定向。
落盘后的规则快照(iptables-save -t nat,输出节选,仅保留 sshuttle 相关行):
-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
关键特性验证——nat 方法不改路由表与策略路由。隧道期间 curl 目标页成功,同时 ip route 与 ip rule 与基线完全一致:
sshuttle tunnel demo - served by app-server 10.89.59.0/24
default via 10.89.58.1 dev eth0
10.89.58.0/24 dev eth0 proto kernel scope link src 10.89.58.2
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
(首行为 curl 实测结果;其后是隧道运行中的 ip route 与 ip rule,与 2.1 节基线逐字一致。)
退出时的回收动作(实测,先数规则条数 3→清理→0):
3
0
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
即:从 OUTPUT/PREROUTING 摘链(-D)→ 清空链内规则(-F)→ 删除链(-X)。正常 Ctrl+C 或 kill 后环境自动还原。
5.2.3 nft:nftables NAT 表
启动摘要见 3.3 节。nft 方法建两张 inet 表(IPv4/IPv6 各一),实测的 IPv4 表全量:
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
}
}
规则语义与 nat 方法一一对应:output/prerouting 两个 hook 挂 jump → 链内「非 IPv4 return → 本机地址 return → 回环 return → 远端服务器 IP return → 目标子网 redirect to :12300」。退出时整表删除:
fw: undoing changes.
fw: nft delete table inet sshuttle-ipv6-12300
fw: nft delete table inet sshuttle-ipv4-12300
5.2.4 tproxy:mangle 标记 + TPROXY(唯一支持 UDP)
tproxy 不做 REDIRECT,而是在 mangle 表给流量打标记(fwmark)并用 TPROXY 目标送进本地监听端口。启动时实测动作(节选):
fw: iptables -w -t mangle -I OUTPUT 1 -j sshuttle-m-12300
fw: iptables -w -t mangle -I PREROUTING 1 -j sshuttle-t-12300
fw: iptables -w -t mangle -A sshuttle-t-12300 -j RETURN -m addrtype --dst-type LOCAL
fw: iptables -w -t mangle -A sshuttle-d-12300 -j MARK --set-mark 0x01
fw: iptables -w -t mangle -A sshuttle-d-12300 -j ACCEPT
fw: iptables -w -t mangle -A sshuttle-t-12300 -m socket -j sshuttle-d-12300 -m tcp -p tcp
fw: iptables -w -t mangle -A sshuttle-t-12300 -j RETURN --dest 127.0.0.1/32 -m tcp -p tcp
fw: iptables -w -t mangle -A sshuttle-t-12300 -j RETURN --dest 10.89.58.3/32 -m tcp -p tcp
fw: iptables -w -t mangle -A sshuttle-m-12300 -j MARK --set-mark 0x01 --dest 10.89.59.0/24 -m tcp -p tcp
fw: iptables -w -t mangle -A sshuttle-t-12300 -j TPROXY --tproxy-mark 0x01 --dest 10.89.59.0/24 -m tcp -p tcp --on-port 12300
fw: iptables -w -t mangle -A sshuttle-m-12300 -j MARK --set-mark 0x01 --dest 10.89.59.0/24 -m udp -p udp
fw: iptables -w -t mangle -A sshuttle-t-12300 -j TPROXY --tproxy-mark 0x01 --dest 10.89.59.0/24 -m udp -p udp --on-port 12300
启动摘要确认 UDP 能力开启:
c : Method: tproxy
c : IPv4: on
c : IPv6: on
c : UDP : on
c : DNS : off (available)
c : User: off (not available with tproxy method)
注意:tproxy 依赖「打了 0x01 标记的包走 local 路由」的策略路由,sshuttle 不会替你加,必须手工预置(见 5.9 节与 FAQ 7.1)。
5.3 DNS 参数:--dns / --ns-hosts / --to-ns
引导:连上内网 IP 却解析不了内网域名是最常见的第二步需求。--dns 把本机 DNS 查询交给远端代答;--ns-hosts 显式指定劫持哪些 DNS 服务器;--to-ns 指定远端用哪台 DNS 代答。
隧道前——本机 resolv.conf 与解析结果基线:
cat /etc/resolv.conf | grep nameserver
nameserver 127.0.0.11
nameserver 8.8.8.8
getent hosts target; echo "exit=$?"
exit=2
(target 是内网主机名,无隧道时解析失败退出码 2。)
隧道中(--dns -v 启动)实测摘要:
c : DNS : on
c : DNS requests normally directed at these servers will be redirected to remote:
c : DNS listening on ('::1', 12298, 0, 0).
c : DNS listening on ('127.0.0.1', 12298).
sshuttle 动态选择监听端口承接 DNS 查询(本环境实测为 12298),并自动捕获 resolv.conf 里的公网 DNS 服务器纳入重定向列表:
c : DNS requests normally directed at these servers will be redirected to remote:
c : (<AddressFamily.AF_INET: 2>, '8.8.8.8')
解析验证——内网主机名成功由远端代答:
getent hosts target; echo "exit=$?"
10.89.59.3 target
exit=0
dig +short target
10.89.59.3
💡 说明:
--to-ns在需要指定远端代答服务器时使用(如--to-ns 10.89.59.2);不指定时 sshuttle 用远端 /etc/resolv.conf 里的服务器。--ns-hosts则用于本机 DNS 服务器不在 resolv.conf 里的特殊场景(如手工指向了内网 DNS)。
5.4 自动发现:-N / -H / --seed-hosts
引导:子网列表也可以不手工写——-N 让远端回传路由表自动生成转发列表;-H 持续扫描远端可达主机名并维护本地 /etc/hosts。
-N auto-nets 实测摘要(转发列表自动生成):
c : Subnets to forward through remote host (type, IP, cidr mask width, startPort, endPort):
c : NOTE: Additional subnets to forward may be added below by --auto-nets.
c : Subnets to exclude from forwarding:
c : (<AddressFamily.AF_INET: 2>, '127.0.0.1', 32, 0, 0)
c : (<AddressFamily.AF_INET6: 10>, '::1', 128, 0, 0)
连接建立后远端回传路由:
s: auto-nets:True
c : Connected to server.
s: available routes:
s: 2/10.89.58.0/24
即远端把自己路由表中的直连网段(10.89.58.0/24)自动列入转发候选。本次实测随后触发了一次 iptables/nftables 兼容性报错(成因与解决见 FAQ 7.2),复现时建议在干净规则环境下运行。
-H auto-hosts 实测(--auto-hosts -v):
c : seed_hosts: []
HH: Starting hostwatch with Python version 3.10.12
fw: Warning: Using a non-atomic way to overwrite /etc/hosts that can corrupt the file if multiple processes write to it simultaneously.
fw: Warning: Using a non-atomic way to overwrite /etc/hosts that can corrupt the file if multiple processes write to it simultaneously.
HH: 前缀是 hostwatch 子进程日志;两行 Warning 是 sshuttle 覆写 /etc/hosts 时的官方提示(非原子写方式,多进程并发写有损坏风险)。本环境远端无 PTR 记录可扫,未发现新主机名——这是如实结果:auto-hosts 的实际收益取决于远端网络的反向解析配置,--seed-hosts 可提供初始种子改善发现率。
5.5 连接远端:-r / --python / -e / --remote-shell
-r root@10.89.58.3:本文全部实验的连接形态;支持user[:password]@addr[:port]完整语法(密码出现在命令行有泄露风险,生产一律用密钥免密)。--python:远端有多个 Python 时显式指定,如--python /usr/bin/python3.12。实测不指定时远端自动选中/usr/bin/python3(见 3.3 节摘要行s: Running server on remote host with /usr/bin/python3 (version 3.10.12))。-e:替换 SSH 命令,例如经跳板二跳时-e "ssh -J bastion"。默认值ssh。--remote-shell(2.0.0 新增):远端不是 POSIX shell 时指定解释器,Windows 目标用cmd或powershell。本文 Linux↔Linux 场景无需此参。--no-cmd-delimiter:默认情况下 sshuttle 会在远端命令前加--分隔符防止参数注入式误解析;个别老旧 SSH 环境解析异常时用此参关闭(属兼容性开关,正常无需使用)。
5.6 守护与日志:-D / --pidfile / --syslog / -v
引导:长期转发要脱离终端运行。-D 进入守护模式,--pidfile 固化 PID 文件位置,--syslog 把日志交给系统日志。
完整生命周期实测(启动→确认 PID→验证转发→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
curl -s -m 5 http://10.89.59.3/
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
要点:① --daemon 启动命令本身返回 0 即后台就绪;② PID 文件守护期间存在、kill 后由 sshuttle 自动删除;③ kill 后防火墙规则清零、网络即刻还原——守护模式的退出清理与前台 Ctrl+C 等价;④ 日志默认走 syslog(/var/log/syslog 可查),前台排障才用 -v。
5.7 权限控制:--user / --group / sudo 相关
引导:多人共用的主机上,往往只希望某个用户的流量走隧道。--user/--group 把防火墙规则限定到特定身份;sudo 免密配置解决非 root 用户反复输密码的问题。
--user dev 实测(转发规则只对 UID 1000 生效,mangle 表 owner 匹配 + 标记跳转):
-A OUTPUT -m owner --uid-owner 1000 -j MARK --set-xmark 0x300c/0xffffffff
-A OUTPUT -m mark --mark 0x300c -j sshuttle-12300
机制解读:先用 mangle 表给「UID 1000 发出的包」打 0x300c 标记,再在 nat 表按标记跳进 sshuttle 链——其他用户流量不匹配 owner 规则,完全不受影响。摘要行显示 User: on(见 5.1 节)。注意该能力仅 nat 方法可用(nft/tproxy 摘要均标注 not available)。
sudo 免密配置由 --sudoers-no-modify 生成(打印到标准输出、经管理员审核后手工安装,官方明示该配置存在被扩大权限的风险),完整输出与安全说明见 《Ubuntu 22.04 安装 sshuttle 指南》第 9.4 节。
5.8 网络命名空间:--namespace / --namespace-pid(2.0.0 新增)
引导:把转发影响面限制在一个 netns 里,宿主网络零改动——多隧道并行、测试场景的利器。注意语义:--namespace 进入的是已存在的命名空间(/var/run/netns/<名>),不是新建。
命名空间不存在时的实测报错:
ns: fatal: The namespace '/var/run/netns/shuttletest' does not exists.
Cannot open network namespace "shuttletest": No such file or directory
先用 iproute2 准备命名空间与出口(veth 对 + NAT):
ip netns add shuttletest
ip link add v-ns0 type veth peer name v-ns1
ip link set v-ns1 netns shuttletest
ip addr add 10.90.0.1/24 dev v-ns0
ip link set v-ns0 up
ip netns exec shuttletest ip addr add 10.90.0.2/24 dev v-ns1
ip netns exec shuttletest ip link set v-ns1 up
ip netns exec shuttletest ip link set lo up
ip netns exec shuttletest ip route add default via 10.90.0.1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o eth0 -j MASQUERADE
逐步确认:
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
基线:命名空间内直连目标不通(HTTP=000,退出码 28——与宿主基线一致):
ip netns exec shuttletest curl -s -m 3 -o /dev/null -w "%{http_code}\n" http://10.89.59.3/
000
命名空间内起隧道:
sshuttle --namespace shuttletest -r root@10.89.58.3 10.89.59.0/24
启动日志确认进入命名空间(进入的是已存在的 netns):
ns: Entering namespace '/var/run/netns/shuttletest'
ns: Namespace '/var/run/netns/shuttletest' successfully set
c : Method: nat
效果对照实测——ns 内通、ns 外照旧不通:
ip netns exec shuttletest curl -s -m 5 http://10.89.59.3/
sshuttle tunnel demo - served by app-server 10.89.59.0/24
curl -s -m 3 -o /dev/null -w "%{http_code}\n" http://10.89.59.3/; echo "exit=$?"
000
exit=28
这证明转发规则被严格限制在 shuttletest 内,宿主不受影响。清理(隧道退出后):
ip netns del shuttletest
ip link del v-ns0
iptables -t nat -D POSTROUTING -s 10.90.0.0/24 -o eth0 -j MASQUERADE
--namespace-pid <PID> 是同一能力的另一种入口:进入指定进程所在的命名空间,适合复用某个服务进程已有的 netns 而不必显式建名。
5.9 tproxy 专属:-t/--tmark 与手工策略路由
引导:tproxy 是唯一支持 UDP 的方法,但它要求「fwmark 标记的包查本地路由表」的策略路由预置——sshuttle 不代办,漏掉就是「隧道起来了却不通」。
不预置策略路由时的实测结果(隧道启动正常但访问超时):
ip rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
curl -s -m 3 -o /dev/null -w "%{http_code}\n" http://10.89.59.3/; echo "exit=$?"
000
exit=28
(ip rule 里没有 fwmark 规则,curl 超时——规则缺失与不通的因果就在这里。)
手工预置两条路由后重跑(mark 值与 --tmark 一致,默认 0x01):
ip rule add fwmark 0x1 lookup 100
ip route add local default dev lo table 100
再起 tproxy 隧道验证:
sshuttle -v --method tproxy -r root@10.89.58.3 10.89.59.0/24
# 另开终端
curl -s -m 5 http://10.89.59.3/
sshuttle tunnel demo - served by app-server 10.89.59.0/24
通了。--tmark 用于标记值冲突时自定义(如 --tmark 0x2,此时手工路由同步改为 fwmark 0x2)。用完删除预置路由:
ip rule del fwmark 0x1 lookup 100
ip route del local default dev lo table 100
5.10 其他参数
--disable-ipv6:关闭 IPv6 转发。默认双栈开启(摘要IPv6: on),无需 IPv6 时可关,少一半规则。--no-latency-control:默认开启的延迟控制以小缓冲换低交互延迟;跑大带宽基准测试时可关闭换取吞吐,摘要行对应latency control setting = True/False。--latency-buffer-size SIZE:延迟控制缓冲大小,官方帮助未标注默认值;仅在前一参数开启时生效。--wrap NUM:通道号计数重启阈值,官方标注测试用途,生产无需触碰。
六、场景化调用模板
以下模板均以本文实测拓扑取值(远端 root@10.89.58.3、目标 10.89.59.0/24),替换为自己的环境值即可直接使用。每条模板的机制细节都在第五章对应小节。
模板一:跳板访问内网(最常用)
sshuttle -r root@10.89.58.3 10.89.59.0/24
模板二:全网转发 + DNS 代答
sshuttle --dns -r root@10.89.58.3 0.0.0.0/0
模板三:需要转发 UDP(tproxy)
ip rule add fwmark 0x1 lookup 100
ip route add local default dev lo table 100
sshuttle --method tproxy -r root@10.89.58.3 10.89.59.0/24
模板四:后台守护 + 固定 PID 文件
sshuttle --daemon --pidfile /var/run/sshuttle.pid -r root@10.89.58.3 10.89.59.0/24
模板五:只对指定用户生效
sudo sshuttle --user dev -r root@10.89.58.3 10.89.59.0/24
模板六:限制在网络命名空间内
sshuttle --namespace shuttletest -r root@10.89.58.3 10.89.59.0/24
模板速查:日常运维选一;要解析内网域名选二;UDP 服务(如内网 DNS/日志流)选三;长期运行选四并配合 systemd(见安装指南 5.6 节);共享主机按身份隔离选五;测试隔离或多隧道并行选六。
七、常见问题
7.1 tproxy 隧道起来了但不通(HTTP=000)
现象:--method tproxy 启动正常、摘要显示 UDP : on,但 curl 超时(实测 000、exit=28),ip rule 里没有 fwmark 规则。原因:tproxy 机制要求打标记的包查 local 路由,sshuttle 不代办这两条策略路由。解决(实测有效,见 5.9 节前后对照):
ip rule add fwmark 0x1 lookup 100
ip route add local default dev lo table 100
7.2 报「chain is incompatible, use 'nft' tool」
现象(--auto-nets 实测原文):
iptables v1.8.7 (nf_tables): chain `sshuttle-12300' in table `nat' is incompatible, use 'nft' tool.
原因:系统中存在 nftables 创建的表/链,iptables 的 nf_tables 后端拒绝操作同名/同表资源——常见于此前用 --method nft 跑过、或防火墙管理工具混用。解决:统一转发方法(显式 --method nat 或 --method nft,与现存规则体系一致);确有遗留表时 nft list tables 排查后 nft delete table <family> <表名> 清理,再启动。
7.3 非 root 运行报三连 fatal
现象与解决见 《Ubuntu 22.04 安装 sshuttle 指南》第 9.5 节(fw: fatal: You must have root privileges ... 等三条实测原文与 sudo 免密方案)。核心结论:这是权限问题而非网络问题,提权或配置 NOPASSWD 即解。
7.4 全网转发会把自己 SSH 断掉吗
不会(2.0.0 起)。实测 0.0.0.0/0 转发时,远端服务器 IP 10.89.58.3 被自动写入 RETURN 排除规则、SSH 会话保持直连(5.1 节实测 ssh root@10.89.58.3 hostname 在隧道中返回 jump-server、exit=0)。1.x 时代需手工 -x <服务器IP>,2.0.0 的 Release Notes #1191 将其自动化。
7.5 auto-hosts 运行时刷 Warning
现象:--auto-hosts 启动后日志持续出现 fw: Warning: Using a non-atomic way to overwrite /etc/hosts ...(实测原文见 5.4 节)。说明:sshuttle 覆写 /etc/hosts 采用非原子方式,多进程并发写有损坏文件的理论风险;单机单人使用可接受,介意时避免多实例同时开 -H,或改用 --seed-hosts 一次性指定主机名。
7.6 --namespace 报「namespace does not exists」
现象:ns: fatal: The namespace '/var/run/netns/shuttletest' does not exists.(实测原文见 5.8 节)。原因:--namespace 进入的是已存在命名空间,不负责创建。解决:先 ip netns add <名> 再运行 sshuttle(完整准备与清理命令见 5.8 节)。
7.7 转发方法到底怎么选
结论(依据 5.2 节实测):只用 TCP/DNS 时选默认(auto 自动落 nat);防火墙体系已统一 nftables 时选 nft;确需 UDP 才用 tproxy 并接受两条手工路由的维护成本。三种方法的规则快照、能力矩阵与回收行为已在 5.2 节逐一实测,排障时对照即可定位「规则在哪、缺了什么」。
八、参考链接
- 官方文档·requirements(转发方法的环境要求):https://sshuttle.readthedocs.io/en/stable/requirements.html
- 官方文档·how-it-works(逐连接转发机制):https://sshuttle.readthedocs.io/en/stable/how-it-works.html
- 官方源码仓库(方法判定顺序核查依据):https://github.com/sshuttle/sshuttle
- GitHub Releases(2.0.0 新特性 #1191 自动排除远端 IP):https://github.com/sshuttle/sshuttle/releases
- 本专栏前置篇:《Ubuntu 22.04 安装 sshuttle 指南》、《sshuttle 2.0.0 介绍与选型指南》