全面封堵 WebRTC、IPv6 与 DNS 多重网络泄漏:从内核到浏览器的零指纹隐私防护盾实战

深度拆解 WebRTC 真实 IP 暴露、IPv6 双栈旁路、DNS 明文查询三大泄漏链路,从 Linux 内核策略路由、nftables 强制劫持、Mihomo/Sing-box 分流配置到 Chromium 网络沙盒与 uTLS 指纹模拟,给出可直接落地的生产级防泄漏加固方案与审计命令。

本文解决什么问题?
01 WebRTC 通过 STUN 绑定请求在 UDP 层直接暴露物理网卡 IP,代理隧道形同虚设,风控系统可秒级识别真实出口
02 IPv6 双栈环境下 AAAA 记录优先返回,若代理仅处理 IPv4 则流量从原生 IPv6 接口直连泄漏,且多数机场节点未做 v6 出口
03 DNS 查询未强制走隧道,systemd-resolved / mDNSResponder 从 53 端口明文外发,EDNS Client Subnet 进一步泄露物理位置
04 TLS ClientHello 指纹(JA3/JA4)暴露客户端栈,Chrome 与 Go net/http 默认指纹可被 SNI 阻断与主动探测识别

01. 现实痛点与底层机理解构

先说结论:代理软件能改的是路由表,改不了的是操作系统网络栈的默认行为。WebRTC、IPv6、DNS 这三类泄漏之所以顽固,是因为它们分别绕过了代理的三个假设:TCP 劫持假设、IPv4 单栈假设、应用层 DNS 假设。

1.1 WebRTC 泄漏:UDP socket 的直连本质

Chromium 的 WebRTC 栈在收集 ICE candidate 时,会调用 rtc::BasicNetworkManager 枚举所有网络接口。枚举逻辑在 P2PTransportChannel 里,默认策略 kDefault 会为每个活跃网卡创建一个 UDP socket,向配置的 STUN 服务器发送 Binding Request(RFC 5389)。

[浏览器 JS]
   │  new RTCPeerConnection({iceServers:[{urls:'stun:stun.l.google.com:19302'}]})
   ▼
[libwebrtc P2PTransportChannel]
   │  EnumerateNetworks() → {eth0: 192.168.1.23, wlan0: 10.0.0.5, tun0: 198.18.0.1}
   ▼
[UDP socket bind on eth0] ──► STUN Binding Request ──► stun.l.google.com:19302
   │                                                          │
   │◄──── XOR-MAPPED-ADDRESS = 公网真实 IP ◄──────────────────┘
   ▼
[写入 SDP candidate: 203.0.113.45]

关键点:ICE candidate 里的 srflx 候选是 STUN 服务器看到的外网 IP。如果你的代理只处理 TCP,UDP 19302 直接走物理网卡出去,STUN 返回的就是你家宽带的公网 IP。风控系统拿到这个 IP,和代理出口 IP 一比对,指纹立刻穿帮。

更隐蔽的是 host candidate:即使不开 STUN,RTCPeerConnection 也会把本地网卡的 RFC 1918 地址(如 192.168.1.23)写进 SDP。虽然内网 IP 本身不致命,但结合 mDNS 混淆(.local 主机名)能推断出设备所在网段,配合其他指纹做设备关联。

1.2 IPv6 泄漏:AAAA 优先级的双栈陷阱

双栈环境下的解析顺序是 RFC 6724 定义的地址选择算法:当目标同时有 A 和 AAAA 记录时,若本地有全局 IPv6 地址,优先尝试 AAAA。

[应用发起 getaddrinfo("example.com")]
   │
   ▼
[glibc 解析器返回两条记录]
   ├── A    : 93.184.216.34
   └── AAAA : 2606:2800:220:1:248:1893:25c8:1946
   │
   ▼
[RFC 6724 地址选择]
   │  本地有 2001:db8::/32 全局地址 → 优先 AAAA
   ▼
[connect(2606:2800:...) ] ──► 物理网卡 IPv6 接口 ──► 直连泄漏

代理软件如果只监听 IPv4(很多老版本 Clash 的 redir-port 只处理 v4),AAAA 流量会从原生 v6 走掉。你测 IPv4 出口一切正常,但目标网站看到的是你本地 ISP 的 v6 地址。

1.3 DNS 泄漏:解析器的信任边界

DNS 泄漏的根因是解析发生的位置。代理软件的域名分流(rule-based)需要拿到域名,但如果解析在代理之前发生,明文查询就已经出去了。

# 典型泄漏场景:systemd-resolved 在代理建立前或分流外发起查询
$ resolvectl query example.com
example.com: 93.184.216.34
  -- Information acquired via protocol DNS in 23.4ms.
  -- Data was acquired via local or encrypted transport: no  # ← 明文

# 抓包看得很清楚
$ tcpdump -i eth0 -nn port 53 -c 4
14:22:01 IP 192.168.1.23.54321 > 192.168.1.1.53: 12345+ A? example.com. (29)
14:22:01 IP 192.168.1.1.53 > 192.168.1.23.54321: 12345 1/0/0 A 93.184.216.34 (45)

192.168.1.1 就是你的路由器,它再转发给 ISP DNS。查询明文可见,且 EDNS Client Subnet(RFC 7871)会把你的子网前缀带上,CDN 侧能定位到城市级。

1.4 指纹层:JA3/JA4 与主动探测

即便前三层都堵住,TLS 握手本身还在说话。JA3 指纹是 ClientHello 里 SSLVersion,CipherSuites,Extensions,EllipticCurves,EllipticCurvePointFormats 的 MD5。Go 标准库的指纹和 Chrome 差得很远:

字段Go crypto/tls 1.21Chrome 120
Cipher Suites 顺序TLS_AES_128_GCM_SHA256 优先TLS_AES_128_GCM_SHA256 优先
Extensions 数量12~1418~20
是否含 GREASE否是(RFC 8701)
supported_versions单条 0x0304含 GREASE 值
key_share 曲线X25519 单条X25519 + GREASE
JA3 哈希1d0d0b...(特征明显)cd08e31494f9531f560d64c695473da9

GFW 的主动探测会拿这个指纹做匹配,1d0d0b 开头的 JA3 直接判定为代理工具。uTLS 的作用就是把 ClientHello 重写成 Chrome 的形状,让被动指纹和主动探测都看不出破绽。


02. 网络拓扑与核心交互设计

整个防护体系分四层:内核策略路由 → TUN 网关 → 代理核心 → 浏览器沙盒。数据包从应用到出口要穿过四道关卡,每道关卡都有明确的”必须走隧道”约束。

┌─────────────────────────────────────────────────────────────────────┐
│  Layer 4: 浏览器网络沙盒                                              │
│  ┌───────────────────────────────────────────────────────────┐      │
│  │ Chromium/Brave                                            │      │
│  │  --force-webrtc-ip-handling-policy=disable_non_proxied_udp│      │
│  │  --host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE 127.0.0.1"│    │
│  │  Secure DNS → https://dns.quad9.net/dns-query             │      ���
│  └───────────────────────────────────────────────────────────┘      │
│         │ SOCKS5 127.0.0.1:7891 / HTTP 127.0.0.1:7890               │
└─────────┼───────────────────────────────────────────────────────────┘
          │
┌─────────▼───────────────────────────────────────────────────────────┐
│  Layer 3: 代理核心 (Mihomo / Sing-box)                               │
│  ┌───────────────────────────────────────────────────────────┐      │
│  │  mixed-port: 7890     tun: utun0 (198.18.0.1/15, fd00::1) │      │
│  │  dns: fake-ip (198.18.0.0/15)  enhanced-mode              │      │
│  │  sniffer: TLS/HTTP 域名嗅探,防止 fake-ip 误判            │      │
│  │  uTLS: chrome fingerprint + XTLS-Vision / Reality         │      │
│  └───────────────────────────────────────────────────────────┘      │
│         │ TUN 接口收包                                               │
└─────────┼───────────────────────────────────────────────────────────┘
          │
┌─────────▼───────────────────────────────────────────────────────────┐
│  Layer 2: 内核策略路由 + nftables                                    │
│  ┌───────────────────────────────────────────────────────────┐      │
│  │  ip rule: fwmark 0x1f00 → table 100 (默认路由走 tun)      │      │
│  │  nftables OUTPUT: 53/853 → REDIRECT 1053 (本地 DNS 代理)  │      │
│  │  nftables OUTPUT: udp 19302 → REDIRECT 19302 (STUN 代理)  │      │
│  │  nftables OUTPUT: oif eth0 ip6 daddr != fd00::/8 → REJECT │      │
│  └───────────────────────────────────────────────────────────┘      │
└─────────┼───────────────────────────────────────────────────────────┘
          │
┌─────────▼───────────────────────────────────────────────────────────┐
│  Layer 1: 物理网卡                                                   │
│  eth0: 192.168.1.23/24  +  2001:db8::23/64                          │
│  (仅允许代理进程的隧道流量出站)                                     │
└─────────────────────────────────────────────────────────────────────┘

2.1 数据包完整走线(以访问 https://example.com 为例)

[App: curl https://example.com]
   │ 1. getaddrinfo → glibc 读 /etc/resolv.conf
   ▼
[systemd-resolved 127.0.0.53]  ── 2. 发 UDP 53 到 127.0.0.53
   │
   ▼
[nftables OUTPUT chain]  ── 3. 匹配 udp dport 53 → REDIRECT 到 127.0.0.1:1053
   │
   ▼
[Mihomo DNS server 1053]  ── 4. fake-ip 模式返回 198.18.0.42
   │
   ▼
[App connect(198.18.0.42:443)]
   │ 5. 路由查找 → fwmark 0x1f00 → table 100 → tun0
   ▼
[Mihomo TUN 收包]  ── 6. sniffer 从 TLS ClientHello 提取 SNI=example.com
   │ 7. 域名匹配规则 → 走 proxy 组
   ▼
[uTLS 握手]  ── 8. ClientHello 指纹伪装成 Chrome 120
   │ 9. 通过 XTLS-Vision 封装到节点
   ▼
[节点出口]  ── 10. 真实 IP 203.0.113.45 访问 example.com

每一跳都有对应的拦截点,缺任何一环都会形成泄漏。


03. 生产级客户端与网关配置实战

3.1 内核层:nftables 强制劫持规则

这是整个体系的地基。先堵 DNS 和 STUN,再谈代理。

#!/usr/sbin/nft -f
# /etc/nftables.d/anti-leak.nft
# 作用:强制所有明文 DNS / STUN / 非隧道 IPv6 流量走本地代理

table inet antileak {
    # 本地代理监听端口
    define DNS_PORT   = 1053
    define STUN_PORT  = 19302
    define TUN_IFACE  = "utun0"
    define LAN_IFACE  = "eth0"

    chain output {
        type filter hook output priority mangle; policy accept;

        # ---- 1. 放行代理进程自身的隧道流量(按 fwmark 识别)----
        meta mark 0x1f00 return

        # ---- 2. 放行本地回环 ----
        oifname "lo" return

        # ---- 3. 强制 DNS:所有 53/853 明文查询重定向到本地 DNS ----
        udp dport { 53, 853 } redirect to :$DNS_PORT
        tcp dport { 53, 853 } redirect to :$DNS_PORT

        # ---- 4. 强制 STUN:WebRTC 的 UDP 19302 重定向到本地 STUN 代理 ----
        udp dport { 19302, 3478 } redirect to :$STUN_PORT

        # ---- 5. 封堵非隧道 IPv6 出站(关键!)----
        # 除 ULA (fd00::/8) 与链路本地,其他 v6 目的地一律拒绝
        oifname $LAN_IFACE ip6 daddr != { fd00::/8, fe80::/10 } reject with icmpv6 type admin-prohibited

        # ---- 6. 封堵非隧道 IPv4 出站(除代理进程)----
        # 若使用 TUN 模式,物理网卡只允许代理进程出站,其余 reject
        # 注意:这条规则依赖代理进程打 fwmark,见 3.2 节
    }

    chain prerouting {
        type filter hook prerouting priority mangle; policy accept;
        # Docker / 容器场景下防止容器内 DNS 泄漏
        iifname "docker0" udp dport 53 redirect to :$DNS_PORT
    }
}

加载并持久化:

sudo nft -f /etc/nftables.d/anti-leak.nft
sudo systemctl enable --now nftables
# 校验
sudo nft list table inet antileak

3.2 内核层:策略路由与 fwmark

TUN 模式下必须防止路由环路:代理进程自己发出的隧道流量不能再次进入 TUN。

# /etc/iproute2/rt_tables 追加
echo "100  proxy" | sudo tee -a /etc/iproute2/rt_tables

# 创建策略路由:带 fwmark 0x1f00 的包走主路由表(物理网卡)
sudo ip rule add fwmark 0x1f00 lookup main priority 100
# 其余所有流量走 table 100(默认路由指向 TUN)
sudo ip route add default dev utun0 table 100
sudo ip rule add lookup 100 priority 200

# 校验
ip rule show
# 0:      from all lookup local
# 100:    from all fwmark 0x1f00 lookup main
# 200:    from all lookup 100

Mihomo 侧通过 routing-mark: 0x1f00 让代理进程的出站包带上标记,从而走物理网卡,避免环路。

3.3 Mihomo(Clash Verge Rev / Meta 内核)完整配置

# /etc/mihomo/config.yaml
# 适配 Clash Verge Rev / Mihomo Meta 内核

mixed-port: 7890
socks-port: 7891
allow-lan: false
mode: rule
log-level: info
ipv6: true              # 开启 v6 支持,配合 TUN 的 inet6_address
routing-mark: 0x1f00    # 关键:代理进程出站打标,走物理网卡

# ---------- DNS 防泄漏核心 ----------
dns:
  enable: true
  ipv6: true
  listen: 0.0.0.0:1053
  enhanced-mode: fake-ip          # 所有域名返回 fake-ip,杜绝真实解析外泄
  fake-ip-range: 198.18.0.0/15
  fake-ip-filter:
    - "*.lan"
    - "*.local"
    - "stun.*.*"                  # STUN 域名不 fake,交给 STUN 代理处理
    - "time.*.com"
    - "+.msftconnecttest.com"
  default-nameserver:
    - 223.5.5.5                   # 仅用于解析 DoH 服务器域名
    - 119.29.29.29
  nameserver:
    - https://dns.quad9.net/dns-query      # DoH,走隧道
    - https://1.1.1.1/dns-query
  fallback:
    - tls://8.8.8.8:853                    # DoT,走隧道
    - https://doh.dns.sb/dns-query
  fallback-filter:
    geoip: true
    geoip-code: CN
    ipcidr:
      - 240.0.0.0/4
    domain:
      - "+.google.com"
      - "+.facebook.com"

# ---------- TUN 网关 ----------
tun:
  enable: true
  stack: system                # Linux 用 system,macOS 用 gvisor
  dns-hijack:
    - any:53
    - tcp://any:53
  auto-route: true
  auto-detect-interface: true
  device: utun0
  mtu: 9000                    # 内网 TUN 可放大,实际出口 MTU 由节点决定
  inet6-address:
    - fd00::1/128              # ULA 前缀,避免与物理 v6 冲突

# ---------- 域名嗅探(防止 fake-ip 误判)----------
sniffer:
  enable: true
  override-destination: true
  sniff:
    TLS:
      ports: [443, 8443]
    HTTP:
      ports: [80, 8080]
    QUIC:
      ports: [443]
  skip-domain:
    - "Mijia Cloud"
    - "+.push.apple.com"

# ---------- 代理组 ----------
proxies:
  - name: "hk-01-reality"
    type: vless
    server: hk01.example.com
    port: 443
    uuid: "b831381d-6324-4d53-ad4f-8cda48b30811"
    network: tcp
    tls: true
    flow: xtls-rprx-vision
    servername: www.microsoft.com
    reality-opts:
      public-key: "xL7QmZ8vN2pK4wR9tY6uI3oP5aS1dF0gH2jK4lM6nB="
      short-id: "a1b2c3d4"
    client-fingerprint: chrome    # uTLS 指纹模拟,关键!
    udp: true

proxy-groups:
  - name: "PROXY"
    type: select
    proxies: ["hk-01-reality", "DIRECT"]

rules:
  # 本地与内网直连
  - GEOIP,LAN,DIRECT,no-resolve
  - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR6,::1/128,DIRECT,no-resolve

  # STUN 强制走代理(配合 nftables 重定向)
  - DOMAIN-SUFFIX,stun.l.google.com,PROXY
  - DOMAIN-SUFFIX,stun.services.mozilla.com,PROXY

  # 默认走代理
  - MATCH,PROXY

逐行注解关键项:

  • routing-mark: 0x1f00:与内核 ip rule 配合,代理进程出站包带 mark 走物理网卡,避免 TUN 环路。
  • enhanced-mode: fake-ip:所有域名在本地被 198.18.0.0/15 接管,真实解析在代理侧完成,客户端永远拿不到真实 IP。
  • fake-ip-filter 里的 stun.*.*:STUN 服务器域名不做 fake-ip,否则 ICE 收集会失败,需要交给专门的 STUN 代理处理。
  • client-fingerprint: chrome:uTLS 将 ClientHello 伪装成 Chrome 120,JA3 哈希与真实浏览器一致。
  • flow: xtls-rprx-vision:XTLS-Vision 在 TLS 内层再做一次填充,抵抗 TLS-in-TLS 的流量特征检测。
  • sniffer.override-destination: true:从 TLS ClientHello 的 SNI 中提取真实域名,覆盖 fake-ip 的目标地址,确保分流规则命中。

3.4 Sing-box 等价配置(JSON)

{
  "log": { "level": "info", "timestamp": true },
  "dns": {
    "servers": [
      {
        "tag": "dns-remote",
        "address": "https://dns.quad9.net/dns-query",
        "detour": "proxy"
      },
      {
        "tag": "dns-local",
        "address": "223.5.5.5",
        "detour": "direct"
      }
    ],
    "rules": [
      { "outbound": "any", "server": "dns-local" },
      { "clash_mode": "direct", "server": "dns-local" },
      { "rule_set": "geosite-cn", "server": "dns-local" }
    ],
    "final": "dns-remote",
    "strategy": "prefer_ipv4",
    "independent_cache": true
  },
  "inbounds": [
    {
      "type": "tun",
      "tag": "tun-in",
      "interface_name": "utun0",
      "inet4_address": "198.18.0.1/15",
      "inet6_address": "fd00::1/128",
      "mtu": 9000,
      "auto_route": true,
      "strict_route": true,
      "stack": "system",
      "sniff": true,
      "sniff_override_destination": true,
      "domain_strategy": "prefer_ipv4"
    },
    {
      "type": "mixed",
      "tag": "mixed-in",
      "listen": "127.0.0.1",
      "listen_port": 7890,
      "sniff": true,
      "sniff_override_destination": true
    }
  ],
  "outbounds": [
    {
      "type": "vless",
      "tag": "proxy",
      "server": "hk01.example.com",
      "server_port": 443,
      "uuid": "b831381d-6324-4d53-ad4f-8cda48b30811",
      "flow": "xtls-rprx-vision",
      "packet_encoding": "xudp",
      "tls": {
        "enabled": true,
        "server_name": "www.microsoft.com",
        "utls": {
          "enabled": true,
          "fingerprint": "chrome"
        },
        "reality": {
          "enabled": true,
          "public_key": "xL7QmZ8vN2pK4wR9tY6uI3oP5aS1dF0gH2jK4lM6nB=",
          "short_id": "a1b2c3d4"
        }
      }
    },
    { "type": "direct", "tag": "direct" }
  ],
  "route": {
    "rules": [
      { "protocol": "dns", "outbound": "dns-out" },
      { "ip_is_private": true, "outbound": "direct" },
      { "domain_suffix": ["stun.l.google.com", "stun.services.mozilla.com"], "outbound": "proxy" }
    ],
    "rule_set": [
      {
        "type": "remote",
        "tag": "geosite-cn",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs"
      }
    ],
    "final": "proxy",
    "auto_detect_interface": true
  }
}

Sing-box 的 strict_route: true 会自动配置策略路由和 fwmark,比手动写 ip rule 更省事,但不会自动处理 DNS 53 端口的劫持,仍需 nftables 规则兜底。

3.5 浏览器层:Chromium 网络沙盒启动参数

这是最后一道防线。即使系统层配置完美,浏览器自身的策略也可能绕过代理。

#!/usr/bin/env bash
# /usr/local/bin/chromium-hardened
# 零指纹浏览器启动脚本

exec /usr/bin/chromium \
  # ---- WebRTC 防泄漏 ----
  --force-webrtc-ip-handling-policy=disable_non_proxied_udp \
  --webrtc-ip-handling-policy=disable_non_proxied_udp \
  # ---- DNS 防泄漏:禁用异步 DNS,强制走系统解析器(已被 nftables 接管)----
  --disable-features=AsyncDNS,DnsOverHttps \
  --host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE 127.0.0.1" \
  # ---- 禁用可能绕过代理的功能 ----
  --disable-background-networking \
  --disable-component-update \
  --disable-domain-reliability \
  --disable-sync \
  --no-pings \
  --no-default-browser-check \
  # ---- 指纹一致性 ----
  --user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
  --lang=en-US \
  --accept-lang="en-US,en;q=0.9" \
  # ---- 代理指向本地 mixed-port ----
  --proxy-server="socks5://127.0.0.1:7891" \
  --proxy-bypass-list="<-loopback>" \
  "$@"

关键参数解释:

  • --force-webrtc-ip-handling-policy=disable_non_proxied_udp:强制 WebRTC 只使用代理的 UDP,不枚举物理网卡。这是防 WebRTC 泄漏最直接的手段。
  • --host-resolver-rules="MAP * ~NOTFOUND":把所有域名解析映射为 NOTFOUND,强制浏览器走系统解析器(已被 nftables 重定向到本地 DNS)。EXCLUDE 127.0.0.1 保留本地回环。
  • --disable-features=AsyncDNS:关闭 Chrome 内置的异步 DNS 解析器,避免它在代理建立前发起明文查询。
  • --proxy-bypass-list="<-loopback>":反向排除,连 localhost 也走代理,防止本地服务暴露真实 IP。
  • --disable-background-networking:关闭后台网络请求(组件更新、域名可靠性上报等),这些请求可能绕过代理。

3.6 STUN 代理:堵住 WebRTC 的最后缺口

即便浏览器策略设了 disable_non_proxied_udp,某些 WebRTC 应用(如视频会议)仍需要 STUN。用一个本地 STUN 代理把 UDP 19302 转发到隧道:

# docker-compose.yml
version: "3.9"
services:
  stun-proxy:
    image: alpine/socat:latest
    container_name: stun-proxy
    restart: unless-stopped
    network_mode: host
    command: >
      UDP4-LISTEN:19302,fork,reuseaddr
      UDP4:stun.l.google.com:19302
    # 注意:socat 本身不解析域名,需先用 dig 拿到 IP

更稳妥的做法是用 sing-box 或 mihomo 的 UDP 转发能力,直接让 STUN 走代理隧道:

# mihomo 中追加
listeners:
  - name: stun-in
    type: udp
    port: 19302
    listen: 127.0.0.1
    proxy: PROXY    # 所有 STUN 流量走代理

配合 nftables 的 udp dport 19302 redirect to :19302,WebRTC 的 STUN 请求就会被强制走隧道,STUN 服务器看到的是代理出口 IP,而非真实 IP。


04. 吞吐性能、高丢包对抗与基准压测对比

防泄漏配置本身会带来性能开销,主要是 fake-ip 解析、nftables 规则匹配、uTLS 握手重写、TUN 用户态转发四部分。下面用实测数据量化。

4.1 各层开销基准测试

测试环境:Linux 6.6 内核,Intel i7-12700,1Gbps 对称宽带,节点为香港 BGP 中转。

配置层吞吐 (Mbps)延迟增量 (ms)CPU 占用 (%)说明
裸连(无代理)94002基准
仅 SOCKS5 代理820+3.28无 TUN,无 DNS 接管
+ fake-ip DNS795+4.111域名解析本地化
+ TUN (system stack)680+6.822用户态转发开销
+ TUN (gvisor stack)520+9.438兼容性好但慢
+ uTLS Chrome 指纹670+7.124握手重写开销小
+ XTLS-Vision645+7.627内层填充
+ nftables 全规则640+7.828规则匹配开销
完整防护栈620+8.231全部叠加

结论:完整防护栈相比裸连损失约 34% 吞吐,延迟增加 8ms 左右。对于 4K 视频、大文件下载这类带宽敏感场景,建议用 system stack 而非 gvisor;对于需要极致兼容性的 macOS,gvisor 是唯一选择,但要做好吞吐打七折的心理准备。

4.2 高丢包环境下的协议对比

在 5% 丢包、80ms RTT 的模拟弱网下,不同协议的吞吐表现:

协议丢包 0%丢包 3%丢包 5%丢包 10%抗丢包机制
TCP + BBR620410280120BBR 拥塞控制
Hysteria2 (QUIC)640580520410Brutal 拥塞控制,忽略丢包
TUIC v5635560

FAQ / 常见问题解答

Q1.

开了代理为什么 WebRTC 测试还是显示真实 IP?

WebRTC 的 ICE 候选收集走的是操作系统 UDP socket,Chromium 默认使用 'default' 策略,会枚举所有本地网卡(含物理网卡)并向 STUN 服务器(如 stun.l.google.com:19302)发送 Binding Request。代理软件通常只劫持 TCP 或特定 UDP 端口,STUN 的 UDP 19302 若未强制走 TUN 就被绕过。修复方式:Chromium 启动参数加 --force-webrtc-ip-handling-policy=disable_non_proxied_udp,或在 chrome://flags 关闭;企业级做法是用 nftables 将 UDP 19302 强制 REDIRECT 到本地 STUN 代理。

Q2.

IPv6 泄漏一定要关闭 IPv6 吗?

不一定。彻底关闭是最省事的方案,但会牺牲原生 v6 访问质量。更优雅的做法是让 IPv6 也走代理隧道:在 Mihomo 中开启 ipv6: true 并配置 mixed-stack,同时用 nftables 在 OUTPUT 链对非隧道接口的 v6 流量做 REJECT;Sing-box 侧则通过 tun 的 inet6_address 分配 ULA 前缀。若机场节点没有 v6 出口,最稳妥仍是内核参数 net.ipv6.conf.all.disable_ipv6=1,避免 AAAA 优先导致的直连。

Q3.

DNS 泄漏测试显示的是本地 ISP 的 DNS,怎么强制走隧道?

根因是系统解析器(systemd-resolved 或 glibc 的 /etc/resolv.conf)在代理建立前或分流规则外发起了明文查询。方案有三层:内核层用 nftables 把 53/853 端口的 UDP/TCP 全部 DNAT 到本地 DNS 监听端口;应用层在 Mihomo 配置 dns.enable: true 并设置 enhanced-mode: fake-ip,让所有域名在本地被 fake-ip 接管;浏览器层在 Chromium 中禁用 Async DNS(--disable-features=AsyncDNS)或启用 Secure DNS 指向 DoH。三层叠加后,任何明文 53 查询都无出口。

Q4.

uTLS 指纹模拟到底防的是什么?

防的是基于 TLS ClientHello 的被动指纹(JA3/JA4)与主动探测。Go 标准库 crypto/tls 的 ClientHello 扩展顺序、cipher suite 列表、椭圆曲线偏好与 Chrome/Firefox 差异极大,GFW 与商业风控系统可据此判定为代理流量。uTLS 通过替换 ClientHello 的字段顺序与扩展内容,让握手包与真实 Chrome 一致。配合 XTLS-Vision 或 Reality 使用时,服务端可校验指纹一���性,未匹配的探测请求直接回落到伪装站点。