(注:为保障网络与运维安全,文内业务域名、公网 IP、节点信息均已做深度脱敏与模糊化替换)
有些排障过程堪比精神污染。你以为你在和一个见鬼的域名搏斗,其实你在和两套 Linux 配置哲学、一段开源代码里的私心、以及你自己几个月前随手敲下的那行配置互相凌迟。
事情的起因非常朴素:我在调试一个常用站点 app.example.com 时,被一个离奇的“DNS 双标”卡住了整整一个下午。
- 我的电脑终端、浏览器,实际建立连接的 IP 永远是顽固的
47.*.*.200; - 但在我的 zash 面板(Zashboard)以及在线网页工具 diggui 里测,返回的却清一色是
43.*.*.202。
两套工具,同一个网络,测同一个域名,给出了两套毫无瓜葛的答案。我感觉这台跑着 OpenWrt 的软路由在背着我搞精神分裂。
自己对着两边输出瞪了半天眼,越看越烦,干脆把这破事扔给 AI,看看它能整出什么花活。
01. 高智商呆子的第一轮教科书式推演#
刚进场时,AI 表现得像个刚从 Stanford 网络实验室毕业的高材生,自信、专业,而且带着一种未经生产环境摧残的清澈。
它调起本地工具,对着目标一通乱嗅,很快扒出了前两套底细:
首先,这个域名背后接入了云厂商的新一代边缘安全加速(ESA / CDN)。权威解析挂着一条 CNAME 调度链:app.example.com.a1.edge-gslb.net,背靠一个庞大的 Anycast 动态节点池。云厂商的 GSLB 会根据客户端的来源网络动态发牌——大陆直连给 47.*.*.200,境外或香港访问给 43.*.*.202。
接着,它构造了一个极其刁钻的测试:
Resolve-DnsName app.example.com -Server 1.2.3.4powershell向一个根本不存在的假 IP 1.2.3.4 发起查询,局域网在 1 毫秒内瞬间返回了 47.*.*.200。
“看,破案了,”AI 几乎要为自己的敏锐鼓掌,“你的局域网网关(192.168.1.1)强制劫持了所有去往 53 端口的 UDP 流量。你在电脑上走的是国内宽带直连解析,而面板测试走的是香港代理,所以一个 200 一个 202。这是正常的分流调度。”
如果换作平常,这个严丝合缝的解释足以糊弄过去。但我心里很清楚,这事根本没那么简单。
02. 一张截图打碎“正统解释”#
“但我很确定,我这流量绝对走的是代理。”我打下这行字,顺手把活跃连接详情的截图甩了过去。
连接链路上清清楚楚挂着代理节点,流量也正在欢快地跑着,但顶部的 Resolved address 却依然死死咬着 47.*.*.200 不放。
这一刻,AI 没法再用“你其实走的是直连”来糊弄我了,它被迫收起了背课本的姿态,下潜到 Mihomo 内核的底层机制里:
在 TProxy 透明代理架构下,客户端发起连接时,Windows 必须先从本地拿到一个 IP(也就是 47.*.*.200),然后才能发出 TCP 握手包。当这个数据包穿过网关的瞬间,Mihomo 的入站拦截器把它按住,并在连接档案上盖了个戳:Resolved address = 47.*.*.200。紧接着,Mihomo 通过 TLS ClientHello 域名嗅探(Sniffer) 提取出真实域名,判定走代理节点,并直接将域名封装发往海外。
“所以,”AI 试图挽尊,“Resolved address 只是本地握手留在网关上的‘出生证’,远端代理节点实际上是在海外重新解析并建立连接的。”
03. 灵魂反问与开源代码里的私心#
这个解释在技术层面上再次闭环了。但我盯着屏幕,越想越不对劲。
我追问了一句:“不对啊,我的软路由拦截了所有 DNS,Mihomo 明明会拦截局域网的 DNS 请求的!如果电脑发出的 DNS 是 Mihomo 拦截并处理的,同一个内核,凭什么给电脑返回 200,在面板里测却返回 202?!”
如果电脑拿到的 200 也是 Mihomo 亲手给的,那为什么在同一个控制面板的“DNS Query”里,它自己查出来的却是 202?它在自己的脑子里装了两个人格吗?
这个反问直接把 AI 逼到了死角。我把软路由上真实的 YAML 配置全盘倒给了它。
AI 对着这份配置文件掘地三尺,终于在代码堆里翻出了那两行被绝大多数人忽略的配置:
respect-rules: true
direct-nameserver:
- 223.5.5.5
- 119.29.29.29
nameserver:
- https://dns.google/dns-query
- https://dns.cloudflare.com/dns-queryyamlMihomo 源码里的真正逻辑在这个瞬间被彻底扒光:
-
电脑发起真实 DNS 查询时(监听端口 1053):
因为开启了respect-rules: true,Mihomo 在解析一个域名之前,必须先拿这个域名去跑一遍分流规则。该域名一路向下匹配,命中了默认的GeoIP, cn -> DIRECT。
判定为直连后,Mihomo 触发了它的内核私心:强行废弃境外nameserver,切换为direct-nameserver(国内公共 DNS 223.5.5.5)! 阿里 DNS 解析出的自然是专供国内的47.*.*.200。为了向我证明,AI 甚至现场用原始 UDP 报文直击网关的 1053 端口,当场抓出了47.*.*.200的活口。 -
面板的 DNS Query 为什么返回 202?
因为 Web 面板调用的是 REST API:GET /dns/query。
在 Mihomo 源码中,这个 API 纯粹用于单点调试,根本不跑分流规则(不执行 respect-rules)! 它直挺挺地向默认的nameserver(Google/Cloudflare DoH)发请求,返回的自然是针对境外出口的香港节点43.*.*.202。
推导到这里,堪称教科书级别的严谨:开源软件的行为差异、API 调试接口与核心数据面的割裂、分流规则对 DNS 栈的动态污染……所有逻辑像瑞士钟表一样咬合得天衣无缝。
然而,本案最具黑色幽默的荒诞一幕,才刚刚拉开序幕。
04. 宏大叙事的崩塌与幽灵现身#
正当 AI 还在源码堆里推演得起劲时,我盯着屏幕,脑子里突然电光石火地想起来一桩几个月前的陈年旧事。
几个月前,我看这个站点的 SSL 证书快到期了——这对我来说是一个高频且关键的节点,不然我也犯不着大费周章去动 hosts。当时眼瞅着证书临期,又总觉得这种节点透着一股随时可能失联的草台感,生怕哪天解析断联影响日常使用。出于一种工程师特有的容灾强迫症,我当机立断扒出了它当时的一个直连 IP,在软路由的 hosts 里写死了一条静态解析,作为万无一失的备用通道。
结果后来这服务跌跌撞撞居然一直在苟延残喘,不知什么时候证书也续上了。而我当年那番如临大敌的“深谋远虑”,早被忘到了九霄云外。
“等一下,”我在对话框里敲下这行字,“我之前好像在路由器 hosts 里写过这个域名,而且配置里开了 use-system-hosts: true。但我刚才明明去 LuCI Web 网页后台把那条规则删了啊,为什么没生效?”
只要你被 OpenWrt 毒打过,就会瞬间明白这个系统分裂的灵魂:
- 你在 LuCI 网页后台(网络 -> DHCP/DNS)里满头大汗增删的:本质上操作的是 OpenWrt 那套看似优雅的 UCI 配置文件:
/etc/config/dhcp; - 你底层系统真正生效的:是标准静态文本:
/etc/hosts。
这就是 OpenWrt 上坑杀无数人的经典“双轨制”陷阱:
- Dnsmasq 和开启了
use-system-hosts的 Mihomo,在启动时带着一种机械的虔诚,同时读取了这两个文件并做合并; - LuCI 网页端和底层的
/etc/hosts之间,根本不存在任何双向同步机制! - 当我在网页后台自以为是地点下“保存并应用”,后台程序只是在
/etc/config/dhcp里抹去了那几行代码;而躺在/etc/hosts里的那一行代码,如同幽灵一般毫发无损!
前面推演了一大通云端 Anycast GSLB、TProxy 嗅探生命周期、respect-rules 源码级竞速分支……宏大叙事建得比天还高。到头来,那个幽灵依然躺在底层的静态文本里,无声地嘲弄着我和 AI 的智商。
我打开 MobaXterm(虽然为了显得像个硬核黑客,在博客里我本该写“我熟练地敲下 ssh 并 vi /etc/hosts”,但算了,坦白局:我其实就是双击了 MobaXterm 侧栏的 SFTP 文件树),点开 /etc/hosts。
几个月前写下的那行静态解析,赫然在目。
我按下删除键,点保存,随手重启了服务。
再跑一次测试,行了,53、1053、电脑本地全变回了标准的 CNAME 和 43.*.*.202。
05. 人机博弈的残局#
这场排障最讽刺的地方在于:AI 对整个协议栈的推演几乎没有一处是错的——从 ESA CDN 的动态调度、TProxy 嗅探生命周期,到 Mihomo 源码里 respect-rules 对 direct-nameserver 的暴力分流,它在几秒钟内重构出了一套堪比教科书的计算机网络闭环。
在它的逻辑模型里,这个世界像瑞士钟表一样严密。它唯一算不出来的,是现实世界从来不是教科书。真实的生产环境永远是由“生怕脆弱节点失联搞了本地保活”、“随手改了个底层文件”、以及“以为网页点了保存就算生效”这串充满人类特质的荒诞链条拼凑出来的缝合怪。
如果没有人类在关键时刻那几句带着烦躁的直觉反问,再庞大的算力,也会在第二步就心满意足地溺死在自己构建的逻辑闭环里。
它负责精通整个计算机网络,而我负责在它把理论推向神坛时,默默在泥潭里翻出自己几个月前埋下的雷。
不过话说回来——至少下一次再遇到类似撞鬼的域名时,我和它都知道,先去翻翻那该死的 /etc/hosts。