用于 sing-box TProxy 网关场景的透明代理控制脚本与 Debian companion package,负责发现 sing-box TProxy 入站、生成 nftables 规则、配置 Linux 策略路由,并提供可审计的 systemd 集成入口。
本项目面向“单臂代理网关 / OPNsense PBR / FakeDNS + FakeIP / 透明代理”部署场景。架构设计和 OPNsense 落地说明见 文档。
tproxy_ctrl.sh 会生成并应用一个 inet nftables 表,可用于:
- 将 TCP/UDP 流量重定向到本机 TProxy 端口。
- 启用 IPv4、IPv6 或双栈透明代理路由。
- 劫持 DNS 流量,并将其送入 TProxy 路径。
- 为 IPv4 和/或 IPv6 FakeIP CIDR 流量设置代理路由。
- 可选地代理本机发出的流量,并通过 mark 或 UID 绕过指定流量以避免代理回环。
- 为带 mark 的流量配置 Linux 策略路由规则和本地路由。
- 清理脚本管理的 nftables 表和策略路由。
脚本面向具备 nft、ip、sysctl 的 Linux 系统。非 dry-run 的实际应用操作通常需要 root 权限。
scripts/tproxy_ctrl.sh:主控制脚本源码,用于设置和清理 TProxy 相关规则。scripts/sing-gateway:Debian companion package 的用户入口源码,用于发现 sing-box 配置、验证并委托tproxy_ctrl.sh。packaging/、debian/:sing-gateway包的默认配置、systemd drop-in 模板和 Debian 打包元数据。docs/:架构设计、OPNsense 实践和 Debian companion package 说明。tests/run.sh:无外部依赖的 POSIX shell 回归测试套件。Makefile:可选维护者辅助入口,make deb仅委托标准 Debian 构建工具。
- 代理网关接入架构:角色定义、三种接入方式、VIP 语义、FakeDNS/FakeIP、PBR、TPROXY、direct/bypass、IPv6、MTU/PMTUD、HA 与安全边界。
- OPNsense 代理网关实践:OPNsense + 单网卡 Linux 代理网关的 VIP、DNAT、PBR、Alias、NAT、验证清单和常见错误。
- sing-gateway Debian companion package:包文件布局、默认配置、systemd drop-in、启用/禁用和打包生命周期。
tproxy_enhence.md 中记录的 TPROXY 硬化项已经落实到 scripts/tproxy_ctrl.sh、测试和 README/架构文档中,不再作为独立待办文档保留。
Debian/Ubuntu 可按 sing-box 官方 APT 源安装:
sudo mkdir -p /etc/apt/keyrings
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
cat <<'EOF' | sudo tee /etc/apt/sources.list.d/sagernet.sources
Types: deb
URIs: https://deb.sagernet.org/
Suites: *
Components: *
Enabled: yes
Signed-By: /etc/apt/keyrings/sagernet.asc
EOF
sudo apt-get update
sudo apt-get install sing-boxsing-box 配置中需要至少一个 tproxy inbound,例如:
{
"type": "tproxy",
"tag": "tproxy-in",
"listen": "::",
"listen_port": 9898,
"sniff": true,
"sniff_override_destination": false
}安装 Debian 包后,编辑:
sudoedit /etc/sing-gateway/gateway.conf最小示例:
STACK=all
IN_IFACE=eth0
NF_TABLE=transparent_proxy
ROUTE_TABLE4=100
ROUTE_TABLE6=106
ROUTE_MARK=0x01
RP_FILTER=checkFakeIP / DNS 劫持示例:
STACK=all
IN_IFACE=eth0
FAKEIP_V4=198.18.0.0/15
FAKEIP_V6=fc00::/18
HIJACK_DNS=1
DNS_BYPASS4=10.0.0.1
LOCAL_ADDR4=10.255.255.10
LOCAL_TCP_PORTS=22,7890,9898
LOCAL_UDP_PORTS=53
RP_FILTER=checkDocker、WireGuard、管理接口或其他虚拟接口不应放入 IN_IFACE。如果只希望代理从 OPNsense/主网关 PBR 导入的流量,通常只填写代理网关连接主网关的生产接口。
sudo sing-gateway check
sudo sing-gateway print-command
sudo sing-gateway print-nft
sudo sing-gateway enable
sudo systemctl restart sing-box.service禁用:
sudo sing-gateway disable
sudo systemctl restart sing-box.service也可以不安装包,直接使用 scripts/tproxy_ctrl.sh 进行 dry-run 或手动应用,见下文。
查看帮助:
sh scripts/tproxy_ctrl.sh --help以 dry-run 方式生成默认 IPv4 规则:
sh scripts/tproxy_ctrl.sh set --dry-run以 dry-run 方式生成双栈 nftables 规则:
sh scripts/tproxy_ctrl.sh set --stack=all --dry-run以生产网关方式限定入口接口、保护本机服务并启用 FakeIP/DNS 审计:
sh scripts/tproxy_ctrl.sh set \
--stack=all \
--in-iface=eth0 \
--local-addr4=127.0.0.1,10.0.0.10 \
--local-tcp-ports=22,7890,9898 \
--local-udp-ports=53 \
--dns-bypass4=10.0.0.10 \
--fake-ip4=198.18.0.0/15 \
--hijack-dns \
--dry-run将生成的 nftables 规则保存到文件:
sh scripts/tproxy_ctrl.sh set --stack=all --save=./tproxy.nft --dry-run应用 IPv4 TProxy 路由:
sudo sh scripts/tproxy_ctrl.sh set --stack=v4应用双栈 TProxy 路由,并启用 DNS 劫持和 FakeIP 路由:
sudo sh scripts/tproxy_ctrl.sh set \
--stack=all \
--hijack-dns \
--fake-ip4=198.18.0.0/15 \
--fake-ip6=fc00::/18代理本机发出的流量,并通过 ignore mark 避免回环:
sudo sh scripts/tproxy_ctrl.sh set \
--proxy-local \
--ignore-mark=0x20代理本机发出的流量,并绕过指定 UID 的代理进程:
sudo sh scripts/tproxy_ctrl.sh set \
--proxy-local \
--ignore-uid=1000清理脚本管理的 nftables 和路由状态:
sudo sh scripts/tproxy_ctrl.sh unsetDebian 系统上推荐使用 sing-gateway companion package 提供的显式启用流程。安装包本身是惰性的,不会创建 active drop-in、重启 sing-box、调用 nftables、修改路由或修改 sysctl。
典型流程:
sudo sing-gateway check
sudo sing-gateway print-command
sudo sing-gateway print-nft
sudo sing-gateway enable
sudo systemctl restart sing-box.servicesing-gateway enable 会验证配置,把 /etc/systemd/system/sing-box.service.d/10-sing-gateway.conf 创建为指向 /usr/lib/sing-gateway/sing-box.service.d/10-sing-gateway.conf 的受管 symlink,写入 /var/lib/sing-gateway/enabled 作为后续清理依据,执行 systemctl daemon-reload,并提示用户重启 sing-box;它不会自动启动或重启 sing-box。需要临时跳过启用前验证时可使用 sing-gateway enable --force,但服务启动时仍会通过 ExecStartPre=+sing-gateway check 失败关闭。--force 只跳过启用前验证;清理 state 仍来自 gateway.conf 中显式配置的值或默认值,而不是完整解析后的 sing-box 配置。
禁用集成:
sudo sing-gateway disable该命令只会移除指向打包模板的受管 symlink,并且仅在 /var/lib/sing-gateway/enabled 存在时才使用其中记录的启用时参数清理 nftables/策略路由状态;不会根据当前 gateway.conf 推断清理目标,也不会删除普通文件或无关 symlink。更多 Debian 包说明见 docs/sing-gateway.md。
在 Debian/Ubuntu 构建环境中安装维护者工具:
sudo apt-get update
sudo apt-get install --no-install-recommends \
build-essential devscripts debhelper lintian dpkg-dev标准构建路径以 debian/ 元数据为唯一事实来源:
dpkg-buildpackage -us -uc -b安装 devscripts 后也可以使用等价命令:
debuild -us -uc -b可选的本地辅助命令 make deb 只是调用 dpkg-buildpackage -us -uc -b,不会复制包元数据或文件列表。
构建产物会写入仓库父目录,通常包括:
../sing-gateway_<version>_all.deb
../sing-gateway_<version>_<arch>.changes
../sing-gateway_<version>_<arch>.buildinfo
安装前检查包元数据、文件列表和 lintian 输出:
test "$(cat debian/source/format)" = "3.0 (native)"
dpkg-parsechangelog --show-field Version | grep -v -- '-'
grep -R "GPL-3+" LICENSE debian/copyright
dpkg-deb --info ../sing-gateway_*_all.deb
dpkg-deb --contents ../sing-gateway_*_all.deb
lintian ../sing-gateway_<version>_<arch>.changes ../sing-gateway_*_all.debdebian/source/format 应为 3.0 (native),debian/changelog 版本不应包含 Debian revision 后缀(例如 -1),项目 LICENSE 与 debian/copyright 应声明 GPL-3+,并在 Debian 版权元数据中指向 /usr/share/common-licenses/GPL-3。dpkg-deb --info 应展示包名、版本、架构、依赖、维护者和描述;文件列表应包含 CLI、控制脚本、默认配置、文档和 systemd drop-in 模板。
发布 Debian 包时,维护者推送匹配 v* 的标签会触发 GitHub Release 工作流;普通分支 push 和 pull request 不会发布 release 或上传资产。工作流会先比较标签去掉前导 v 后的版本与 dpkg-parsechangelog --show-field Version 输出,二者必须完全一致才会继续构建、检查和发布。例如 v0.1.0 只会发布 debian/changelog 版本为 0.1.0 的包。
标签工作流仍使用仓库根目录下的标准命令 dpkg-buildpackage -us -uc -b,然后运行 dpkg-deb --info、dpkg-deb --contents 和严格 lintian 校验。校验成功后,工作流会为该标签创建或更新 GitHub Release,并上传生成的 .deb、.changes 和 .buildinfo 文件作为 release assets。该工作流只发布 GitHub Release 资产,不生成或发布 apt repository、Packages 索引、Release 文件或 InRelease 元数据。
建议只在一次性 Debian/Ubuntu VM 或容器中测试安装生命周期:
sudo apt-get install ./../sing-gateway_*_all.deb
test ! -e /etc/systemd/system/sing-box.service.d/10-sing-gateway.conf
command -v sing-gateway
dpkg -L sing-gateway
sudo sing-gateway enable
test -L /etc/systemd/system/sing-box.service.d/10-sing-gateway.conf
test "$(readlink /etc/systemd/system/sing-box.service.d/10-sing-gateway.conf)" = "/usr/lib/sing-gateway/sing-box.service.d/10-sing-gateway.conf"
test -f /var/lib/sing-gateway/enabled
sudo apt-get remove sing-gateway
test ! -e /etc/systemd/system/sing-box.service.d/10-sing-gateway.conf
sudo install -d /etc/sing-gateway
printf keep | sudo tee /etc/sing-gateway/admin.keep >/dev/null
sudo apt-get purge sing-gateway
test -f /etc/sing-gateway/admin.keep
sudo rm -f /etc/sing-gateway/admin.keep
rmdir /etc/sing-gateway 2>/dev/null || true还应验证没有 /var/lib/sing-gateway/enabled 时执行 package remove 不会调用 sing-gateway disable、tproxy_ctrl.sh unset、nftables 或策略路由清理。安装包必须保持惰性:安装时不创建 active drop-in、不启动或重启 sing-box、不调用 nftables、不修改路由或 sysctl。只有显式运行 sing-gateway enable 才会启用集成;remove/purge 清理路径也不应启动或重启服务。purge 只用 rmdir 清理空目录,不会递归删除 /etc/sing-gateway 中的管理员文件。
如果使用发行版或上游提供的 sing-box.service,建议通过 systemd drop-in 添加规则,不直接修改原始 service 文件,以保持升级兼容性:
sudo systemctl edit sing-box.service只需要添加 ExecStartPost 和 ExecStopPost:
[Service]
ExecStartPost=+/etc/sing-box/tproxy_ctrl.sh set --route-table4=100 --route-mark=0x01 --tproxy-port=9898 --fake-ip4=198.18.0.1/15 --hijack-dns --proxy-local --ignore-uid=990
ExecStopPost=+/etc/sing-box/tproxy_ctrl.sh unset --route-table4=100 --route-mark=0x01 --tproxy-port=9898 --fake-ip4=198.18.0.1/15 --hijack-dns --proxy-local --ignore-uid=990说明:
+表示该命令以 root 权限执行,用于配置 nftables、策略路由和 sysctl。sing-box.service默认已经使用User=sing-box运行,通常不需要在 drop-in 中再次手动配置User=/Group=。- 启用
--proxy-local时必须通过--ignore-uid=<UID>或--ignore-mark=<MARK>跳过 sing-box 自身流量,避免代理回环。 --ignore-uid需要填写数字 UID,可用id -u sing-box查看;上例中的990仅为示例值。
应用并检查最终合并结果:
sudo systemctl daemon-reload
sudo systemctl restart sing-box.service
systemctl cat sing-box.service通用参数:
set:生成/应用 nftables 与策略路由配置。unset:移除脚本管理的 nftables 与策略路由配置。--dry-run:只打印将要执行或生成的内容,不实际应用。--save=<FILE>:将生成的 nftables 规则保存到文件。-h、--help:显示帮助信息。
TProxy 参数:
--stack=v4|v6|all:启用 IPv4、IPv6 或双栈。默认值:v4。--nf-table=<NAME>:nftables 表名。必须是安全标识符:只能包含字母、数字和下划线,且不能以数字开头。--route-table4=<ID>:IPv4 路由表 ID。默认值:100。--route-table6=<ID>:IPv6 路由表 ID。默认值:106。--route-mark=<MARK>:用于策略路由的包 mark。默认值:0x01。--tproxy-port=<PORT>:本机 TProxy 端口。默认值:9898。--proxy-local:重路由本机 output 流量。--ignore-mark=<MARK>:绕过带有该 mark 的本机 output 流量。--ignore-uid=<UID>:绕过来自该 UID 的本机 output 流量。--in-iface=<iface[,iface...]>:入口接口 allow-list;配置后 prerouting/divert 只处理指定接口进入的流量。--bypass4=<CIDR[,CIDR...]>/--bypass6=<CIDR[,CIDR...]>:自定义 nft/kernel bypass 目的网段。该 bypass 不等同于 sing-boxdirectoutbound。--local-addr4=<IP[,IP...]>/--local-addr6=<IP[,IP...]>:绕过本机/网关服务地址。--dns-bypass4=<IP[,IP...]>/--dns-bypass6=<IP[,IP...]>:绕过本地、内部或管理 DNS 目的地址。--local-tcp-ports=<PORT[,PORT...]>/--local-udp-ports=<PORT[,PORT...]>:绕过管理端口、显式代理端口、TPROXY listener、健康检查端口等本机服务端口。--dns-bypass-ports=<PORT[,PORT...]>:DNS hijack/reroute 匹配端口,默认53。--rp-filter=off|check|loose|strict|disable:IPv4 rp_filter 策略(仅作用于net.ipv4.conf.*);默认不处理,check发现 strict 值会失败,loose/disable会显式写入安全值。--enable-kernel-bypass:显式启用 kernel bypass 所需 forwarding sysctl。基础 TPROXY 本地路由不再无条件开启 forwarding。
FakeIP 与 DNS 参数:
--fake-ip4=<CIDR>:IPv4 FakeIP CIDR,仅在启用 IPv4 时生效。--fake-ip6=<CIDR>:IPv6 FakeIP CIDR,仅在启用 IPv6 时生效。--hijack-dns:将 DNS 流量送入 TProxy 路径。
- 建议先使用
--dry-run检查生成的 nftables 规则。 - 非 dry-run 的
set会调用nft、ip和sysctl,可能改变主机路由和防火墙状态。 unset会移除脚本管理的 nftables 表和策略路由,但不会关闭 IPv4 或 IPv6 转发。--proxy-local必须配合--ignore-mark或--ignore-uid使用;否则脚本会拒绝执行,以避免本机代理回环。--proxy-local的--ignore-mark不能与--route-mark相同。- FakeIP CIDR 会优先于 private/custom bypass 进入 TPROXY;与
--bypass4/6完全相同的 FakeIP CIDR 会被拒绝。 - 启用
--hijack-dns前应通过本地地址/端口或--dns-bypass4/6排除已由 OPNsense DNAT 到 W:53 的 DNS listener 以及内部 DNS/AD/管理 DNS。 - 如果 nftables 规则已应用但路由设置失败,脚本会尝试回滚脚本管理的 nftables 表。
- FakeIP PBR:OPNsense 应将 FakeIP 目标网段策略路由到 Proxy Gateway VIP,同时避免将代理网关自身流量再次 PBR 回代理。
- DNS DNAT:如果 OPNsense 已将
VIP:53DNAT 到 W 的 DNS listener,Linux 侧必须把 W 的 DNS 地址/端口作为本机服务或 DNS bypass 先接受,不应让--hijack-dns再次捕获。 - Bogon/anti-spoofing:
198.18.0.0/15等 FakeIP/测试网段可能被 bogon 规则拦截;需要在 OPNsense 上为相关接口和 PBR/NAT 路径配置例外。 - PMTUD/ICMP:脚本默认先接受非 TCP/UDP 流量,以保留 ICMP、ICMPv6、ND/RA/MLD 和 PMTUD 控制流量;上游防火墙也不应阻断这些控制报文。
- Kernel bypass:只有使用
--enable-kernel-bypass时才期望 Linux 转发被 nft accept 的绕过流量;同时需要 OPNsense NAT、防火墙、anti-spoofing 和 PBR 防环路规则配套。
在仓库根目录运行完整测试套件:
sh tests/run.sh测试套件使用 POSIX sh 编写,不依赖 Bats、Python、Node 等外部测试框架。测试会在临时目录中创建假的 nft、ip 和 sysctl 命令,因此不需要 root 权限,也不会修改宿主机网络状态。
测试覆盖范围包括:
- CLI 参数校验和边界值检查。
- 安全与不安全的 nft 表名。
- route mark、TProxy 端口、路由表 ID、UID、FakeIP CIDR。
- IPv4、IPv6、双栈 dry-run 规则生成。
- DNS 劫持、FakeIP、本机代理绕过规则及规则顺序。
- 合法/非法调用下的保存与不保存行为。
- 使用 fake 命令验证非 dry-run 命令调用顺序。
- 幂等路由设置和清理容错。
- nftables 应用成功但路由设置失败时的回滚行为。
如果系统存在兼容的 Linux nft 命令,可以启用可选的真实 nft 解析校验:
TPROXY_TEST_NFT_CHECK=1 sh tests/run.sh默认情况下该解析校验会跳过,因此测试套件可在没有 nftables 的环境中通过,包括 macOS 开发机。
涉及行为变更时,建议使用本仓库的 OpenSpec 流程:
/opsx-explore → /opsx-propose → new session → /opsx-apply → /opsx-verify → /opsx-archive
提交前建议运行:
sh tests/run.sh
git diff --check