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.21 | Chrome 120 |
|---|---|---|
| Cipher Suites 顺序 | TLS_AES_128_GCM_SHA256 优先 | TLS_AES_128_GCM_SHA256 优先 |
| Extensions 数量 | 12~14 | 18~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 占用 (%) | 说明 |
|---|---|---|---|---|
| 裸连(无代理) | 940 | 0 | 2 | 基准 |
| 仅 SOCKS5 代理 | 820 | +3.2 | 8 | 无 TUN,无 DNS 接管 |
| + fake-ip DNS | 795 | +4.1 | 11 | 域名解析本地化 |
| + TUN (system stack) | 680 | +6.8 | 22 | 用户态转发开销 |
| + TUN (gvisor stack) | 520 | +9.4 | 38 | 兼容性好但慢 |
| + uTLS Chrome 指纹 | 670 | +7.1 | 24 | 握手重写开销小 |
| + XTLS-Vision | 645 | +7.6 | 27 | 内层填充 |
| + nftables 全规则 | 640 | +7.8 | 28 | 规则匹配开销 |
| 完整防护栈 | 620 | +8.2 | 31 | 全部叠加 |
结论:完整防护栈相比裸连损失约 34% 吞吐,延迟增加 8ms 左右。对于 4K 视频、大文件下载这类带宽敏感场景,建议用 system stack 而非 gvisor;对于需要极致兼容性的 macOS,gvisor 是唯一选择,但要做好吞吐打七折的心理准备。
4.2 高丢包环境下的协议对比
在 5% 丢包、80ms RTT 的模拟弱网下,不同协议的吞吐表现:
| 协议 | 丢包 0% | 丢包 3% | 丢包 5% | 丢包 10% | 抗丢包机制 |
|---|---|---|---|---|---|
| TCP + BBR | 620 | 410 | 280 | 120 | BBR 拥塞控制 |
| Hysteria2 (QUIC) | 640 | 580 | 520 | 410 | Brutal 拥塞控制,忽略丢包 |
| TUIC v5 | 635 | 560 |
FAQ / 常见问题解答
开了代理为什么 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 代理。
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 优先导致的直连。
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 查询都无出口。
uTLS 指纹模拟到底防的是什么?
防的是基于 TLS ClientHello 的被动指纹(JA3/JA4)与主动探测。Go 标准库 crypto/tls 的 ClientHello 扩展顺序、cipher suite 列表、椭圆曲线偏好与 Chrome/Firefox 差异极大,GFW 与商业风控系统可据此判定为代理流量。uTLS 通过替换 ClientHello 的字段顺序与扩展内容,让握手包与真实 Chrome 一致。配合 XTLS-Vision 或 Reality 使用时,服务端可校验指纹一���性,未匹配的探测请求直接回落到伪装站点。