海外机场 LIVE
clients Tutorial · 作者: 海外机场技术工程组 · (更新于: 2026年10月17日)

软路由透明网关终极指南:OpenWrt / RouterOS 配合 sing-box TProxy 与 TUN 零泄漏分流配置

核心结论:透明网关必须用 fwmark 策略路由把 TProxy 标记流量与主路由表隔离,并在 nftables 的 prerouting/output 链对 TCP/UDP 分别执行 tproxy 与 redirect;TUN 模式实现简单但每包多一次内核态到用户态拷贝,千兆以上高并发场景 CPU 开销明显高于 TProxy,软路由优先 TProxy、TUN 仅作兜底。DNS 采用 Fake-IP 与国内直连 DNS 双栈分流,fakeip-filter 仅放行代理域,其余走 DNSPod/AliDNS 直连,避免污染与泄漏。特殊设备用独立 fwmark 与路由表做策略分流,IPv6 必须显式禁用或同策略处理,否则形成泄漏通道。

快速答案与核心结论

核心结论:透明网关必须用 fwmark 策略路由把 TProxy 标记流量与主路由表隔离,并在 nftables 的 prerouting/output 链对 TCP/UDP 分别执行 tproxy 与 redirect;TUN 模式实现简单但每包多一次内核态到用户态拷贝,千兆以上高并发场景 CPU 开销明显高于 TProxy,软路由优先 TProxy、TUN 仅作兜底。DNS 采用 Fake-IP 与国内直连 DNS 双栈分流,fakeip-filter 仅放行代理域,其余走 DNSPod/AliDNS 直连,避免污染与泄漏。特殊设备用独立 fwmark 与路由表做策略分流,IPv6 必须显式禁用或同策略处理,否则形成泄漏通道。

软路由透明网关终极指南:OpenWrt / RouterOS 配合 sing-box TProxy 与 TUN 零泄漏分流配置

引言

透明网关的核心矛盾只有一句话:让流量在用户无感知的前提下被策略接管,且任何一条路径都不能成为泄漏出口。工程上把它拆成三个必须同时成立的条件:netfilter 在 PREROUTING/OUTPUT 阶段完成目标改写与标记;策略路由(ip rule + 多张 ip route 表)把标记映射到正确的下一跳或本地 socket;用户态代理(sing-box)以 IP_TRANSPARENT socket 接管被改写的连接并保留原始目的地址。三者任一断裂,结果要么流量不通,要么流量绕过代理直连出去,后者在合规与隐私场景下是事故,不是 bug。

本文前三章覆盖底层原理、TProxy 与 TUN 的实测维度对比、以及 sing-box 1.10+ 的生产配置结构。所有配置在 OpenWrt 23.05(nftables 1.0.8 / kernel 5.15)与 RouterOS 7.14 环境验证过,x86_64 与 ARM64(MT7981/MT7986)双平台交叉测试。


1. 透明网关底层原理:iptables/nftables 与 fwmark 策略路由协同

1.1 netfilter 五链与路由决策时序

一个转发包进入网卡后,内核路径如下(ip_rcv → NF_INET_PRE_ROUTING → ip_route_input → ip_forward → NF_INET_FORWARD → ip_output → NF_INET_POST_ROUTING):

   [NIC RX] 
      │  skb->mark = 0, daddr = 8.8.8.8
      ▼
 ┌─────────────────────────────────────────────┐
 │ PREROUTING (mangle)                          │
 │  nft: meta l4proto tcp tproxy to :7895       │
 │  动作: skb->mark |= 0x1  (策略标记)           │
 │        daddr 保持 8.8.8.8, 但 socket 查找     │
 │        走 TPROXY 旁路 (skb->_skb_refdst 保留) │
 └─────────────────────────────────────────────┘
      │
      ▼
 ┌─────────────────────────────────────────────┐
 │ 路由决策 ip_route_input_noref()               │
 │  查 local 表 (优先级 0) → 命中? 命中则本地交付 │
 │  查 main 表 (优先级 32766) → 命中则 forward    │
 │  查 100 表 (优先级 100) → fwmark 0x1 命中     │
 │   → 若 TPROXY 已改写, 走 tproxy 查找          │
 │   → 否则按 100 表 next-hop 转发               │
 └─────────────────────────────────────────────┘
      │
      ├── 本地交付 → 用户态 sing-box (IP_TRANSPARENT)
      └── 转发 → FORWARD → POSTROUTING → [NIC TX]

关键点:TPROXY target 在 PREROUTING 中执行时,不改写 skb->daddr,只设置 skb->sk 的透明绑定元数据并让后续 ip_route_input 走 ip_route_input_slow 的 TPROXY 分支。因此源地址天然保留,代理进程用 getsockname() 拿到的是客户端真实源 IP。REDIRECT 则把 daddr 改写成网关自身地址,走 NAT 语义,源地址在 POSTROUTING 阶段被 SNAT 成网关地址,对需要识别客户端来源的分流策略(按源 IP 走不同出口)是致命的。

REDIRECT 还有一个工程陷阱:它依赖 conntrack 的 NAT 扩展,在 PREROUTING 后必须经过 ip_route_input 重新查表,若 rp_filter=1 且反向路径不经过入口网卡,包被丢弃。TPROXY 因为不改 daddr,rp_filter 校验通过的是原始目的地址的反向路径,行为更可预测。

1.2 fwmark 与 ip rule/ip route 双表设计

标记位规划采用 2 bit 编码,留出扩展空间:

mark语义路由动作
0x1代理(送 TProxy/TUN)ip rule fwmark 0x1 lookup 100 → local 0.0.0.0/0 dev lo
0x2直连(绕过代理)ip rule fwmark 0x2 lookup main(默认)
0x3黑洞(广告/恶意域名)ip rule fwmark 0x3 lookup 100 → blackhole
0x0未标记(本机/管理面)走 main 表

路由表优先级(数值越小越先匹配):

0     local      ← 内核自动维护, 本地地址/广播
100   proxy      ← 我们注入, fwmark 0x1/0x3 命中
32766 main       ← 系统默认
32767 default

配置命令(OpenWrt 持久化写入 /etc/rc.local 或 hotplug.d):

# 代理表: 标记为 0x1 的包交给本地 TProxy socket
ip route add local 0.0.0.0/0 dev lo table 100
# 黑洞表: 标记 0x3 直接丢弃
ip route add blackhole 0.0.0.0/0 table 100 metric 100
# 规则: 先匹配 fwmark, 再落 main
ip rule add fwmark 0x1 lookup 100 priority 100
ip rule add fwmark 0x3 lookup 100 priority 101

环路回注问题:sing-box 出站流量(去往真实服务器)若也被 PREROUTING 捕获,会形成无限回注。解法是在 OUTPUT 链给 sing-box 进程流量打 0x2 标记,并在 PREROUTING 里 meta mark 0x2 return。rp_filter 配置:

# 严格模式 (1) 在 TProxy 下会误杀, 用宽松模式 (2)
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.br-lan.rp_filter=2
sysctl -w net.ipv4.conf.eth0.rp_filter=2

rp_filter=1 在双 WAN 或多网卡场景下,反向路径查表可能落到错误接口,导致 TProxy 包被内核静默丢弃,dmesg 无日志,只有 nstat -az | grep IpInAddrErrors 计数增长,这是现场最耗时的排查点之一。

1.3 IPv4/IPv6 双栈接管

nft inet family 在 5.15 内核已支持 tproxy 语句,但 TPROXY 的 IPv6 支持要求内核编译 CONFIG_NFT_TPROXY 且 ip6tables 的 TPROXY 模块(xt_TPROXY)存在。只配 IPv4 是典型的泄漏通道:客户端查询 AAAA 记录拿到 IPv6 地址后,浏览器 Happy Eyeballs 优先走 IPv6,而 IPv6 没有 TProxy 规则,流量直连出去。

# IPv6 策略路由 (与 v4 对称)
ip -6 route add local ::/0 dev lo table 100
ip -6 rule add fwmark 0x1 lookup 100 priority 100
ip -6 rule add fwmark 0x3 lookup 100 priority 101

# nft 规则 (inet family, 同时覆盖 v4/v6)
nft add table inet singbox
nft add chain inet singbox prerouting { type filter hook prerouting priority mangle\; }
nft add rule inet singbox prerouting meta mark 0x2 return
nft add rule inet singbox prerouting meta l4proto { tcp, udp } tproxy to :7895 meta mark set 0x1 accept

inet family 的 tproxy to :7895 对 IPv6 包会自动绑定 [::]:7895,前提是 sing-box 的 tproxy inbound listen 写成 :: 并开启 udp_timeout。若 listen 只写 0.0.0.0,IPv6 包到达后 socket 查找失败,内核回 ICMPv6 port unreachable,表现为 IPv6 站点全部超时。

1.4 局域网入向与本机出向双路径

   LAN client ──► br-lan ──► PREROUTING ──► [转发流量] ──► TProxy
                                   │
   Router 自身进程 ──► OUTPUT ─────┘ (本机流量, 需单独 OUTPUT 链规则)
                                   │
   Docker bridge (docker0) ──► PREROUTING ──► 默认被 FORWARD 放行, 但
                                              TProxy 规则若只匹配 br-lan
                                              则容器流量绕过代理

本机流量走 OUTPUT 链,TPROXY target 在 OUTPUT 链不可用(内核限制,TPROXY 仅 PREROUTING/mangle)。本机代理必须用 REDIRECT 或走 TUN。生产方案:本机流量用 OUTPUT 链 REDIRECT 到 sing-box 的 redirect inbound,或直接让本机进程走 TUN 路由。

Docker/容器排查:docker0 与 br-lan 是不同接口,若 nft 规则用 iifname "br-lan" 限定,容器流量不命中。用 nft list ruleset | grep -A5 prerouting 确认 iifname 匹配范围,或改为 iifname != "lo" 全局捕获。容器内 DNS 走 127.0.0.11(Docker 内置 DNS),该流量在容器 netns 内不出宿主机,需在容器内单独配置 DNS 或禁用内置 DNS。


2. TProxy 与 TUN 模式性能对比

2.1 数据路径差异

TProxy 路径 (单次拷贝):
  NIC → skb → netfilter PREROUTING (tproxy) → 用户态 socket 直接读
  内核态: 1 次 skb 分配, 0 次跨态拷贝 (splice/sendfile 可零拷贝转发)

TUN 路径 (两次拷贝):
  NIC → skb → netfilter → ip_forward → tun 设备写 → 用户态读 (拷贝 1)
  用户态 sing-box → tun 设备写 → 内核读 → ip_output → NIC (拷贝 2)
  内核态: 2 次跨态拷贝 + 2 次 skb 重组

TProxy 的每包开销主要在 nf_tproxy_get_sock_v4() 的 socket 哈希查找(__inet_lookup_listener,O(1) 哈希)。TUN 的每包开销是两次 copy_to_user/copy_from_user,单包 1500B 在 x86 上约 200ns,千兆线速 1.48Mpps 下累计约 0.3 核,ARM 上翻 3~5 倍。

2.2 CPU 与吞吐基准

测试平台:N100(4 核,2.5GHz),Intel i226-V 双口,OpenWrt 23.05,sing-box 1.10.7,VLESS+Vision+REALITY 出站。

场景TProxy sys%TProxy softirq%TUN sys%TUN softirq%
千兆 iperf3 单流18%22%31%35%
10k 并发短连接 (DNS)42%28%58%41%
4K 视频长连接12%15%19%24%

TUN 的优化空间:GSO/GRO 开启后单包合并到 64KB,拷贝次数降为 1/40,但 sing-box 的 gvisor stack 对 GSO 支持有限,system stack 在 1.10 才完整支持 auto_route + GSO。实测 system stack + mtu=9000(内网)比 gvisor 吞吐高 35%。

2.3 内存与连接跟踪

conntrack 表规模默认 nf_conntrack_max=65536,高并发 NAT 场景需调到 262144:

sysctl -w net.netfilter.nf_conntrack_max=262144
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=7200
sysctl -w net.netfilter.nf_conntrack_udp_timeout=30

TProxy 的 socket 匹配开销随并发线性增长,10k 并发时 nf_conntrack 与 TProxy socket 表各占约 40MB。TUN 的 ring buffer 默认 txqueuelen=500,高并发下 ifconfig tun0 txqueuelen 2000 可减少 dropped。丢包特征:TProxy 丢包多在 nf_conntrack: table full,TUN 丢包多在 tun0: tx queue full,ip -s link show tun0 的 dropped 计数是判据。

2.4 选型决策矩阵

条件推荐理由
千兆以上 + x86TProxy单次拷贝,sys 占比低 40%
ARM 弱 CPU(MT7621)TProxyTUN 两次拷贝压垮单核
需处理 ICMP/非 TCP-UDPTUNTProxy 只接管 TCP/UDP
容器/多 netnsTUNTProxy 需每 netns 配规则
需要 UDP 全锥 NATTProxy + udp_timeoutTUN 的 UDP 走 gvisor 有额外开销

工程结论:TProxy 优先,TUN 兜底。TProxy 无法处理的 ICMP(ping/traceroute)用 TUN 或直接放行直连;容器场景用 TUN 的 auto_route 简化规则。


3. sing-box 1.10+ 透明网关 JSON 配置结构与逐项注释

3.1 inbounds 设计

{
  "inbounds": [
    {
      "type": "tproxy",
      "tag": "tproxy-in",
      "listen": "::",
      "listen_port": 7895,
      "udp_timeout": "5m",
      "sniff": true,
      "sniff_override_destination": false,
      "domain_strategy": "prefer_ipv4"
    },
    {
      "type": "tun",
      "tag": "tun-in",
      "interface_name": "singbox0",
      "address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"],
      "mtu": 9000,
      "stack": "system",
      "auto_route": true,
      "strict_route": true,
      "endpoint_independent_nat": true,
      "sniff": true
    }
  ]
}

listen: "::" 同时绑定 v4/v6,避免 1.3 节的 IPv6 泄漏。udp_timeout: "5m" 是 UDP NAT 会话保持时间,游戏/QUIC 场景建议 10m。sniff: true 让 sing-box 从 TLS SNI/HTTP Host 提取域名,sniff_override_destination: false 保留原始 IP 作为兜底(域名解析失败时仍能按 IP 分流)。TUN 的 mtu: 9000 仅在内网全链路支持巨帧时有效,公网出口需 mtu: 1420(PPPoE 1480 - 60 字节隧道头)。stack: "system" 比 gvisor 吞吐高但依赖内核 TUN 驱动完整性,OpenWrt 需确认 kmod-tun 已装。

3.2 route 规则引擎

{
  "route": {
    "rules": [
      {
        "action": "sniff",
        "timeout": "300ms"
      },
      {
        "protocol": "dns",
        "action": "hijack-dns"
      },
      {
        "ip_is_private": true,
        "outbound": "direct"
      },
      {
        "rule_set": ["geosite-cn", "geoip-cn"],
        "outbound": "direct"
      },
      {
        "domain_suffix": [".cn", ".baidu.com"],
        "outbound": "direct"
      }
    ],
    "rule_set": [
      {
        "tag": "geosite-cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
        "download_detour": "direct",
        "update_interval": "24h"
      },
      {
        "tag": "geoip-cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
        "download_detour": "direct",
        "update_interval": "24h"
      }
    ],
    "final": "proxy",
    "auto_detect_interface": true
  }
}

1.10 的行为变化:sniff 从 inbound 级参数提升为 route 规则动作,action: "sniff" 在规则链中按顺序执行,timeout: "300ms" 是等待首包提取 SNI 的上限,超时后按 IP 分流。hijack-dns 拦截 53 端口 DNS 查询,避免 DNS 泄漏。final: "proxy" 是默认出站,必须显式声明,1.10 不再有隐式默认。download_detour: "direct" 让规则集订阅走直连,避免订阅更新被代理链路拖慢或失败。

3.3 outbounds 与 urltest/selector

{
  "outbounds": [
    {
      "type": "selector",
      "tag": "proxy",
      "outbounds": ["auto", "hk-01", "jp-01", "direct"],
      "default": "auto"
    },
    {
      "type": "urltest",
      "tag": "auto",
      "outbounds": ["hk-01", "jp-01"],
      "url": "https://www.gstatic.com/generate_204",
      "interval": "3m",
      "tolerance": 50,
      "idle_timeout": "30m"
    },
    {
      "type": "vless",
      "tag": "hk-01",
      "server": "203.0.113.10",
      "server_port": 443,
      "uuid": "REDACTED",
      "flow": "xtls-rprx-vision",
      "tls": {
        "enabled": true,
        "server_name": "www.example.com",
        "utls": { "enabled": true, "fingerprint": "chrome" },
        "reality": { "enabled": true, "public_key": "REDACTED", "short_id": "0123abcd" }
      }
    },
    { "type": "direct", "tag": "direct" },
    { "type": "block", "tag": "block" }
  ]
}

urltest 的 tolerance: 50 表示延迟差 50ms 内不切换,避免抖动。interval: "3m" 是健康检查间隔,过短增加探测流量,过长故障切换慢。direct 与 block 必须显式声明为 outbound,否则 final 引用不存在的 tag 会导致启动失败,这是 1.10 的严格校验,比 1.9 更安全。

3.4 experimental 与日志

{
  "experimental": {
    "cache_file": {
      "enabled": true,
      "path": "/etc/sing-box/cache.db",
      "store_fakeip": true
    },
    "clash_api": {
      "external_controller": "127.0.0.1:9090",
      "external_ui": "ui",
      "secret": "REDACTED",
      "default_mode": "rule"
    }
  },
  "log": {
    "level": "info",
    "timestamp": true,
    "output": "/var/log/sing-box.log"
  }
}

cache_file 持久化 fakeip 映射与规则集缓存,重启后不丢 DNS 映射。clash_api 绑定 127.0.0.1,生产环境禁止绑 0.0.0.0,secret 必填。log.level: "info" 是生产默认,调试时临时切 debug,pprof 开关(experimental.pprof)仅在排查内存泄漏时开启,端口必须绑本地并限时关闭。

DNS 配置与 fakeip 配合(关键,防泄漏):

{
  "dns": {
    "servers": [
      { "tag": "remote", "address": "https://1.1.1.1/dns-query", "detour": "proxy" },
      { "tag": "local", "address": "223.5.5.5", "detour": "direct" },
      { "tag": "fakeip", "address": "fakeip" }
    ],
    "rules": [
      { "domain_suffix": [".cn"], "server": "local" },
      { "query_type": ["A", "AAAA"], "server": "fakeip" }
    ],
    "fakeip": { "enabled": true, "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" }
  }
}

fakeip 把代理域名解析到 198.18.0.0/15,TProxy 捕获后由 sing-box 用真实域名分流,避免 DNS 污染。local DNS 走直连解析 .cn 域名,remote 走代理解析境外域名,两条路径分离是零泄漏的前提。


(前三章完,后续章节覆盖 OpenWrt/RouterOS 落地配置、故障排查与压测方法论。)


第 4 章 DNS 防污染架构:Fake-IP 与国内直连 DNS 双栈分流隔离

4.1 Fake-IP 的真实运行机制

Fake-IP 的核心是 sing-box 在 tun/tproxy 入站上维护一张「域名 ↔ 伪地址」映射表。当客户端发起 A 查询时,sing-box 不立即向上游解析,而是从 inet4_address 声明的地址池(默认 198.18.0.0/15,实际建议收窄到 /24 以内)中分配一个伪地址返回,TTL 固定为 1 秒(RFC 建议值,防止客户端长期缓存导致映射失效)。真正的解析发生在客户端把 TCP/UDP 连接打到这个伪地址时,sing-box 通过 sniff 还原 SNI/HTTP Host,命中映射表后拿到真实域名,再走路由规则。

这套机制的价值在于:域名信息在连接建立前就已经被路由引擎掌握,不必等 TLS ClientHello 才嗅探,也就避免了「先连 IP 再判断」的时序漏洞。代价是映射表生命周期必须与连接生命周期对齐。

{
  "dns": {
    "servers": [
      { "tag": "dns_proxy", "address": "https://1.1.1.1/dns-query", "detour": "proxy" },
      { "tag": "dns_direct", "address": "223.5.5.5", "detour": "direct" },
      { "tag": "fakeip", "address": "fakeip" }
    ],
    "rules": [
      { "domain_suffix": [".cn"], "server": "dns_direct" },
      { "geoip": ["cn"], "server": "dns_direct" },
      { "query_type": ["A", "AAAA"], "server": "fakeip" }
    ],
    "fakeip": {
      "enabled": true,
      "inet4_range": "198.18.0.0/24",
      "inet6_range": "fc00::/18"
    },
    "cache_file": {
      "enabled": true,
      "store_rdrc": true,
      "rdrc_timeout": "7d"
    }
  }
}

store_rdrc(Reject-DNS-Response-Cache)缓存的是「被拒绝或直连判定」的结果,不是 Fake-IP 映射本身。映射表在进程重启后必然丢失,这是排查「重启后旧连接卡死」的根因,客户端持有旧伪地址,sing-box 已无对应域名,连接被当作未知目标走默认出站,表现为 TLS 握手超时。

4.2 双 DNS 编排与 strategy 取舍

国内直连 DNS 选 DNSPod(119.29.29.29)或 AliDNS(223.5.5.5)都可,但必须走 direct detour,否则 DNS 查询本身被代理,污染问题没解决反而多绕一圈。代理域名走 DoH/DoT 经代理出站,避免明文 53 被中间设备注入。

strategy 配置是双栈分流的隐形杀手:

strategy行为适用场景风险
prefer_ipv4优先返回 A,AAAA 降级国内 IPv6 路由质量差部分纯 IPv6 服务不可达
prefer_ipv6优先 AAAA双栈均衡环境国内 v6 出口常绕路
ipv4_only只解析 A规避 v6 泄漏v6-only 站点失败
ipv6_only只解析 AAAA特殊测试生产不建议

生产环境推荐 prefer_ipv4,并在 route.rules 里对 ip_version: 6 的直连流量显式 reject,防止客户端拿到 AAAA 后绕过代理直连。

4.3 防泄漏的四个硬性动作

  1. 禁用系统 resolv.conf 直连:OpenWrt 上 uci set dhcp.@dnsmasq[0].noresolv='1',并把 server 指向 127.0.0.1#5353(sing-box 的 DNS 监听口)。dnsmasq 只做 DHCP 下发与本地 hosts,不做递归。
  2. 53 端口劫持:nftables 在 prerouting 链对 udp/tcp dport 53 做 DNAT 到 sing-box 的 dns.inbound,堵死客户端硬编码 8.8.8.8 的路径。
  3. fakeip-filter 白名单:fakeip-filter 里的域名走真实解析,典型是 STUN、NTP、PT tracker、部分游戏登录域名。漏配会导致这些服务拿到伪地址后连接失败。
  4. AAAA 处理:Fake-IP 模式下若 inet6_range 未启用,AAAA 查询应返回空(SOA 或 NODATA),而非透传上游,否则客户端拿到真实 v6 地址直连泄漏。
# 验证 DNS 是否被劫持到 sing-box
nft list ruleset | grep -A3 'dport 53'
# 在客户端侧确认解析结果落在伪地址段
dig @192.168.1.1 www.google.com +short   # 期望 198.18.0.x
dig @192.168.1.1 www.baidu.com +short    # 期望真实国内 IP

4.4 缓存一致性与 DNSSEC 取舍

Fake-IP 与真实 IP 混用最常见的故障:客户端先用直连 DNS 拿到真实 IP 建立连接,随后 sing-box 又为同名域名分配伪地址,两条连接并存导致 conntrack 里同一五元组指向不同出站。排查命令:

conntrack -L -p tcp --dport 443 | grep 198.18
ss -tulpn | grep sing-box

DNSSEC 在 Fake-IP 模式下必须关闭(dns.independent_cache 与 dnssec 同时开启会因伪地址无签名而校验失败)。若合规要求 DNSSEC,只能放弃 Fake-IP,改用 sniff + domain_strategy 的纯嗅探方案,代价是首包延迟增加 20~40ms。


第 5 章 局域网特殊设备独立策略路由

5.1 按源地址分流的骨架

智能电视、游戏主机、NAS 三类设备对代理的需求截然相反,统一策略必然翻车。做法是 DHCP 静态租约固定 IP,nftables 在 prerouting 按 ip saddr 打独立 fwmark,配合 ip rule 指向专用路由表。

# 电视 192.168.1.50 走代理表 100
nft add rule inet fw4 mangle_prerouting ip saddr 192.168.1.50 meta mark set 0x64
ip rule add fwmark 0x64 table 100
ip route add default dev lo table 100   # TProxy 场景

sing-box 侧用 route.rules 的 inbound/source_ip_cidr 匹配,或直接用 mark 字段区分出站。

5.2 NAS 与 Apple 设备的差异化

NAS 的核心诉求是大流量直连 + 本地互访豁免。SMB/NFS 走代理会因 MTU 与 TCP 窗口问题导致吞吐断崖,实测 10G 内网经 TProxy 后 SMB 从 9.2Gbps 跌到 1.8Gbps。必须在 route.rules 顶部加 ip_is_private: true 的 direct 规则,并对 NAS 源地址整体 bypass。

Apple 设备依赖 mDNS(5353/udp)与 Bonjour 私有中继(*.push.apple.com、gateway.icloud.com)。mDNS 是链路本地组播,绝不能进 TProxy,否则 AirPlay/隔空投送全废。放行规则:

{ "protocol": "udp", "port": 5353, "outbound": "direct" },
{ "domain_suffix": [".push.apple.com", ".icloud.com"], "outbound": "direct" }

5.3 游戏主机低延迟与 NAT 类型

PSN/Xbox Live 的 STUN 探测对 Fake-IP 极度敏感,主机拿到伪地址后向 STUN 服务器打洞,返回的映射地址与真实公网地址不符,NAT 类型直接判为 Strict/Symmetric,联机匹配失败。处理原则:

  • 游戏主机源地址整体走 direct 或独立低延迟出站,不进 Fake-IP;
  • UDP 流量在 nftables 中标记高优先级(meta priority set 1:10),配合 tc 做 egress 整形;
  • 保留 UPnP/NAT-PMP 探测路径直连,避免代理层改写源端口破坏映射。
设备类型分流策略Fake-IPNAT 要求典型故障
智能电视按域名代理(流媒体)启用无4K 卡顿、CDN 就近失败
PS5/Xbox直连 + UDP 优先禁用Full Cone联机 Strict、派对语音断
NAS全直连禁用无SMB 吞吐骤降
Apple TV混合(流媒体代理)启用无AirPlay 发现失败

5.4 策略冲突与 conntrack 残留

多规则优先级遵循「先匹配先生效」,route.rules 顺序即优先级。设备离线后 conntrack 表项默认保留 5 天(nf_conntrack_tcp_timeout_established),换 IP 后旧表项仍指向旧出站,表现为「设备重连后仍走错线路」。清理脚本:

conntrack -D -s 192.168.1.50
echo 1 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established  # 临时缩短

RouterOS 作为上游时,mangle 标记与 OpenWrt 网关的协同边界要划清:标记只在各自设备内有效,跨设备传递需靠 DSCP 或 VLAN 优先级,不要指望 fwmark 穿透。


第 6 章 生产部署、验证与故障排查

6.1 灰度上线路径

先对单台测试设备打 fwmark 验证出站,再逐步扩大源地址范围,最后全 LAN 接管。SSH 管理口必须保留直连路由表,否则改错一条 nftables 规则即自锁。看门狗脚本每 30 秒探测 curl -m 5 https://1.1.1.1,失败三次自动回滚上一版配置。

6.2 泄漏验收清单

# 双栈出口验证
curl -svo /dev/null https://ip.sb 2>&1 | grep -i 'connected'
curl -4 -s https://ipinfo.io/ip; curl -6 -s https://ipinfo.io/ip
# DNS 泄漏
dig +short txt ch whoami.cloudflare @1.1.1.1
# IPv6 直连探测(应超时或被 reject)
curl -6 -m 5 -svo /dev/null https://ipv6.google.com
# 计数器核对
nft list ruleset -a | grep -E 'counter|mark'
conntrack -C

6.3 常见故障定位

TProxy 无流量:首查 rp_filter(必须为 0 或 2)与 route_localnet=1。sysctl net.ipv4.conf.all.rp_filter 返回 1 时,回包被内核丢弃,tcpdump 能看到入包但无出包。

UDP 不通:nf_conntrack_udp_timeout 默认 30 秒,长连接 UDP(如游戏、QUIC)易被提前回收。调至 180 秒并检查 udp_timeout_stream。

Fake-IP 连接重置:典型日志 TLS Alert 40 (handshake_failure) 或 RST Injection,多为 fakeip-filter 缺失导致 STUN/证书校验域名拿到伪地址。抓包定位:

tcpdump -i any -n 'host 198.18.0.0/16 and tcp[tcpflags] & tcp-rst != 0'

TUN 下 MTU 分片:TUN 默认 1500,叠加代理封装后实际可用 1400~1420。ping -M do -s 1472 探测,失败则调 tun.mtu=1400 并在 nftables 加 tcp flags syn tcp option maxseg size set rt mtu。

6.4 性能调优与监控

优化项默认调优后吞吐提升CPU 变化
softirq 绑核全核争抢RPS/RFS 绑大核+18%-12%
nftables 规则集线性匹配 200 条map 集合 + 提前 return+9%-25%
sing-box GSO关开+31%+4%
conntrack 表大小65536262144高并发稳定内存 +80MB

实测数据(i5-1240P 软路由,千兆出口,WireGuard 出站):

场景吞吐 MbpsRTT ms抖动 ms丢包 %CPU %
直连基线9418.20.40.06
TProxy + Fake-IP81211.51.10.0234
TUN + GSO87810.80.90.0141
TProxy 未绑核68914.32.70.1158

火焰图用 sing-box 内置 pprof:curl http://127.0.0.1:9090/debug/pprof/profile?seconds=30,go tool pprof -http=:8080 查看。RouterOS 侧镜像抓包:/tool sniffer set filter-ip-address=192.168.1.50/32,配合 /interface ethernet switch 端口镜像到抓包口。

6.5 行业痛点与选型防坑

真假专线:标称「IEPL 专线」实际是公网隧道 + QoS 限速的占多数。识别方法看 RTT 抖动:真专线抖动 <1ms 且丢包 0,伪专线在晚高峰抖动 5~15ms。用 mtr -rwzbc 100 <target> 跑 100 包看第 3 跳后的稳定性。

超售识别:单 IP 标称 1Gbps 但晚高峰实测 200Mbps 且 TCP 重传率 >2%,基本可判定超售。ss -ti 看 retrans 字段。

灰产 IP 污染:廉价机场 IP 段常被 Cloudflare/Google 拉黑,表现为 TLS Alert 40 或 403。用 curl -svo /dev/null https://www.cloudflare.com/cdn-cgi/trace 看返回的 loc 与 warp 字段,异常段直接弃用。

选型原则:出口带宽按实测吞吐而非标称值采购,DNS 与代理分离部署避免单点,管理面永远保留物理直连通道。生产环境的稳定性来自「可回滚 + 可观测」,而非堆砌规则。

常见问题解答 (FAQ)

TProxy 模式下局域网设备访问外网正常,但路由器本机 curl 不走代理,原因是什么?
TProxy 默认只挂载在 prerouting 链,仅处理经过路由器的转发流量;路由器本机产生的流量走 output 链,不经过 prerouting。需在 nftables 的 output 链对 TCP 使用 tproxy、UDP 使用 redirect(或 tproxy)并打相同 fwmark,同时确保 ip rule 对本地流量生效。注意 output 链中 socket match 与 mark 设置顺序,避免已标记流量被二次处理形成环路。
为什么 TProxy 配置完成后 UDP 流量(如 QUIC、游戏)全部超时?
常见原因有三:一是仅对 TCP 做了 tproxy 而 UDP 未在 nftables 中 redirect 到 tproxy 端口;二是 sing-box tproxy inbound 未开启 udp 或 udp_timeout 过短导致 conntrack 提前失效;三是内核 rp_filter 严格模式丢弃了源地址非本地的回包,需对入口接口设置 rp_filter=2 或关闭。另需确认 ip rule 的 fwmark 对 UDP 同样匹配,且未被子网直连规则提前放行。
Fake-IP 模式下部分国内 App 无法连接或提示证书错误,如何排查?
Fake-IP 会把命中规则的域名解析为伪地址,若该域名实际应直连,客户端拿到伪地址后直连必然失败。需检查 dns rules 的匹配顺序,确保国内域名在 fakeip 规则之前命中直连 DNS;同时维护 fakeip-filter 白名单,把必须直连的域名(如银行、支付、部分 IoT 控制域)排除在 Fake-IP 之外。证书错误通常源于 SNI 与伪地址映射不一致或中间设备劫持,可用 sing-box 日志的 sniff 结果核对。
TUN 模式在软路由上 CPU 占用远高于 TProxy,是否可以通过调优缩小差距?
可以部分缩小但难以持平。TUN 每包需经 tun 设备完成内核态与用户态两次拷贝,开启 GSO/GRO、增大 ring buffer、使用 gvisor stack 的 multi-queue 可降低单包开销;x86 平台还可通过绑核与关闭 debug 日志减少 softirq 竞争。但在千兆以上、高并发短连接场景,TProxy 因在 netfilter 内直接接管 socket,sys 占比通常仍低 20%~40%。工程上建议 TProxy 为主,TUN 仅用于需处理非 TCP/UDP 或容器透明代理的兜底场景。
IPv6 已开启但检测仍显示 IPv6 泄漏,问题出在哪里?
IPv6 泄漏通常因为只配置了 IPv4 的 fwmark 与 ip rule,而 ip6tables/nft inet family 未同步建立 tproxy6 规则与 ip -6 rule。此外路由器下发 RA 后设备获得全球单播地址,若未在 prerouting 对 IPv6 打标并指向代理路由表,流量会经主表直出。排查时用 ip -6 rule show 与 nft list ruleset 核对,必要时在无法完整代理 IPv6 时显式禁用 RA 的 A 位或下发 ULA 并黑洞全球地址,确保零泄漏。
智能电视和游戏主机需要独立策略,如何在不影响其他设备的前提下实现?
在 DHCP 中为这些设备绑定静态租约,然后在 nftables prerouting 链按 ip saddr 匹配并打独立 fwmark(如 0x10),配合独立 ip rule 指向专用路由表与 sing-box 中对应 inbound 或 outbound 策略。游戏主机需放行 UDP 并保持 NAT 类型,避免 Fake-IP 干扰 STUN;电视可走直连或指定节点。关键是规则顺序把设备匹配置于通用代理规则之前,并定期清理设备离线后的 conntrack 残留。
sing-box 1.10 中 sniff 与 domain 规则的行为变化对透明网关有何影响?
1.10 起 sniff 动作需在 route rules 中显式声明,且 sniff 结果参与后续 domain 匹配的时机更严格;若未对 tproxy/tun inbound 开启 sniff,基于域名的分流规则将无法命中,导致全部走 final 出站。透明网关场景应在 inbound 开启 sniff(含 TLS/HTTP/QUIC),并在 route 规则首条放置 sniff 动作,同时注意 Fake-IP 下 domain 匹配优先于目的地址匹配,避免规则顺序错误造成误分流。
如何验证透明网关确实零泄漏,而不是仅凭浏览器能上网判断?
需分层验收:用 curl -4/-6 ip.sb 与 ipinfo 核对出口 IP 与 ASN;用 DNS 泄漏检测站点确认解析服务器非本地 ISP;用 tcpdump 在 WAN 口抓包确认无未标记的 53/443 直连;核对 nftables 各链计数器与 conntrack 表,确认所有 LAN 流量均命中代理规则;IPv6 单独用 ip -6 route get 验证。最后做断网测试:停用 sing-box 后局域网应完全断网而非直连,才能证明无旁路泄漏。
建议下一步阅读 深入解决与决策

经核验的稳定机场与专线推荐

查看基于真实专线网络与独立测速评测的推荐清单