2026/9/23

透明分流下的 DNS

应用通过 Specola 的本地 HTTP/SOCKS5 端口访问网络时,会直接交出目标域名CONNECT api.github.com:443。域名规则很好实现。

透明接管不一样。程序调用 connect() 时早已完成解析,手里只剩一个 IP 地址。CDN 后面多个毫不 相关的域名可能共用同一个 IP,同一个域名也可能解析出很多 IP。如果不做额外处理, DOMAIN-SUFFIX,github.com,work 根本无从匹配。

Specola 如何把域名和连接对应起来

使用 Linux eBPF 后端时,Specola 的 DNS 模块始终启用:

  1. 在内核中拦截 DNS 查询。 在任何放行判断之前,cgroup hook 就把发往 53 端口的 UDP 和 TCP 查询交给 Core 的 DNS 模块;
  2. Core 应答并记录。 每次应答时,Core 记录哪个域名得到了哪个地址,以及这个域名命中的规则;
  3. 内核获知候选地址。 属于带规则域名的地址会写入 BPF 候选 map,之后对这些地址的 connect() 会被重定向给 Core;
  4. Core 还原域名。 被重定向的连接到达后,Core 通过地址查到域名,再应用域名规则。

没有规则的域名正常解析,其连接按 FINAL 处理。使用 FINAL,DIRECT 时,它们始终走内核快速 路径。

Fake-IP

透明接管推荐使用 Fake-IP:

[dns.tun]
mode = "fake-ip"
fake-ip-range = "198.18.0.1/16"
fake-ip-filter-mode = "blacklist"
fake-ip-filter = ["+.lan", "+.local"]

对于规则要求走代理的域名,Core 不返回真实地址,而是从 198.18.0.0/15 地址池中分配一个。 这个地址相当于一个令牌:每个域名唯一,CDN 共享 IP 不再造成信息丢失;对它的连接总是进入 Core,由 Core 映射回域名。

两个额外的好处:

DIRECT 的域名拿到真实地址,直连流量的行为与不使用 Specola 时完全一致。命中 fake-ip-filter 的域名(如 *.lan 这类本地名称)总是返回真实地址。走 REJECT 的域名返回 REFUSED

另一种模式 mode = "redir-host" 对所有域名返回真实地址,只适合坚持要求真实 IP 的软件,并且 会遇到上面说的 CDN 歧义问题。

需要避开的坑

自带加密 DNS 的应用。 开启“安全 DNS”的浏览器和部分命令行工具会自己通过 HTTPS 解析域名, Specola 看不到这些查询,它们的连接也就不带域名。可以关闭应用内置的 DoH,或者用 PROCESS-NAMEIP-CIDR 规则匹配它。

已建立的连接。 连接保持建立时的决策。修改规则后,重启长连接客户端或等待它重新连接。

IPv6。 eBPF 后端只接管 IPv4。如果域名有 AAAA 记录且应用优先使用 IPv6,这条连接会绕过 Specola。保持 [dns].ipv6 = false(默认值),让被接管的应用拿到 IPv4 应答。

嗅探只是兜底,不能替代 DNS。 Specola 也能读取被重定向连接的 HTTP Host 头或 TLS SNI。这在 连接到达 Core 之后有用,但在 FINAL,DIRECT 下,没有命中的连接根本不会被重定向,也就无从嗅探。

完整的 DNS 选项见 DNS 手册