Skip to content

[iOS] 自引用 DNS 配置(dns.listen: 0.0.0.0 + proxy-server-nameserver: udp://127.0.0.1)导致 "dns resolve failed: couldn't find ip"——通配 UDP 监听器在 NE socket scope 下没有回环面 #56

Description

@AllenGong0

环境

  • 设备:iPhone (iOS 18.x)
  • App 构建:0920-0459;core 日志显示 tunnel started core=1.19.31
  • 同一订阅在 Clash Verge (macOS) 和 ClashMi (iOS) 上均正常

问题描述

订阅使用了常见的"自引用本地 DNS"写法(机场配置,用户无法修改)。此处仅展示与问题相关的字段,上游解析器已省略:

dns:
  enable: true
  listen: '0.0.0.0:7874'
  enhanced-mode: fake-ip
  proxy-server-nameserver: ['udp://127.0.0.1:7874']
  # nameserver / default-nameserver / nameserver-policy / fake-ip-filter:配置中存在,此处省略

在 iOS 版 Clash (Hako) 上,隧道启动并显示已连接,但所有出站连接都失败,原因是代理节点自身的域名无法解析。日志反复出现:

connect error: dns resolve failed: couldn't find ip

失败以每 5 秒一批的节奏重复,与 DNS 客户端超时一致,即查询是被静默黑洞而非被拒绝。同一份订阅在 Clash Verge (macOS) 和 ClashMi (iOS) 上不做任何修改即可正常使用。

根因分析(基于公开源码阅读)

我在公开仓库(commit 35c9bb6)中定位到了确切的缺口:

  • dns/server.go:103 按配置原样绑定 DNS UDP 服务——本订阅是通配地址监听(dns.listen: 0.0.0.0:7874),而 bind/hako/config_pipeline.go 明确按原文保留 dns.listen。
  • 在 NE socket scope 下,bind/hako/listener_scope_darwin.go 通过两种方式修复入站监听器的回环可达性:地址显式为回环的监听器被绑定到回环接口(listenerScopeControl);通配 TCP 监听器会获得回环伴生监听器(listenerLoopbackCompanion)。通配 UDP 两者都得不到——文件头明确记录:"Wildcard UDP keeps today's behavior -- the loopback face of a wildcard packet conn is a recorded gap, not a silent one."
  • 出站方向已经修复:发往 127.0.0.1:7874 的查询会被豁免接口绑定(bind/hako/hook.go 的 interfaceScopableTarget;component/dialer/dialer.go 的 peer 豁免),所以数据包确实能到达回环接口。
  • 最终效果:proxy-server-nameserver: udp://127.0.0.1:7874 的查询被送到一个在 extension scope 下对回环"失聪"的通配 UDP socket。查询在 5 秒后超时(dns/client.go),Resolver.LookupIP 返回 ErrIPNotFound("couldn't find ip"),随后所有节点拨号都以 dns resolve failed: couldn't find ip 失败——与我的日志完全吻合,包括 5 秒的重试节奏。

值得注意的是,同样的配置若写成 dns.listen: 127.0.0.1:7874 今天是可以工作的(该监听器形态会被绑定到回环接口)。失败的形态专门是通配 listen——而这正是面向桌面的订阅普遍采用的写法。这也与 bind/hako/hook.go 中的注释一致:127.0.0.1 形态已被处理,0.0.0.0 形态似乎就是剩余的那个已记录缺口。

其他与此解读吻合的日志证据:

  • [Apple] inbound listeners run under the Network Extension socket scope; ...——证明 listener-scope 层在此构建中已激活。
  • 没有 Start DNS server(UDP) error——通配绑定本身成功,监听器只是听不到回环。
  • 已上报的 deviations:allow-lan(stripped)、store-fake-ip(forced true)、19 条 PROCESS-NAME 规则(unavailable)。proxy-server-nameserver 未被上报为 stripped——与它被原样保留一致。

期望行为

希望 Clash (Hako) 能像桌面客户端和 ClashMi 一样处理 proxy-server-nameserver 指向自身 dns.listen 的配置。可能的修复方向:

  1. 将回环伴生监听器机制扩展到通配 UDP packet conn;或
  2. 检测 proxy-server-nameserver 指向所配置的 dns.listen 端口(自引用)的条目,改为路由到进程内 resolver,而不是走回环 socket 跳一跳;或
  3. 至少为这种配置形态发布 deviation/诊断提示(就像 allow-lan / store-fake-ip 那样)。

复现步骤

  1. 导入包含上述 DNS 配置的订阅
  2. 连接
  3. 日志中反复出现 dns resolve failed: couldn't find ip;无任何流量通过

补充说明

  • 在 App 中开启"覆盖 DNS"并手动指定公共解析器可以绕过(workaround),同时说明配置其余部分没有问题。
  • 日志中的 Apple deviations(store-fake-ip 强制为 true、allow-lan 被剥离、PROCESS-NAME 规则不可用)看起来都是预期行为,不是本问题的原因。

日志(已脱敏,时间统一为 +08:00)

===== core =====
23:48:12.204 INFO  [Memory] GC pacing armed: GOMEMLIMIT=39321600 (NE budget default; no explicit limit was configured)
23:48:12.208 INFO  [Apple] inbound listeners run under the Network Extension socket scope; loopback faces will be explicitly bound to the loopback interface
23:48:12.214 WARNING  [Apple] default path en0 (index=16) resolves `system` nameservers through [<路由器IPv6-DNS>]:53, 192.168.50.1:53, read from this network's scoped resolvers
23:48:12.215 INFO  [Apple] default path -> en0 (index=16 expensive=false constrained=false ipv4=true ipv6=true)
23:48:12.321 INFO  [Apple] allow-lan permitted=false
23:48:12.323 WARNING  [Apple iosPacketTunnel] 19 PROCESS-NAME rules, first at rules[1286]: the owner metadata it tests cannot be resolved in this profile; the rule is kept and evaluated against empty metadata, exactly as with find-process-mode off
23:48:12.472 INFO  Initial configuration complete, total time: 96ms
23:48:12.516 INFO  [Apple] deviations published: entry=start seq=1 document=535407B sha256=3a4999d88613319f rows=3
23:48:12.517 WARNING  [Apple] deviation allow-lan: given="false" effective="the configured listener stays on 127.0.0.1 instead of every interface" category=stripped -- exposing the device to the local network is a decision the person holding it makes, not one an imported subscription makes for them; the app has not recorded that agreement yet
23:48:12.518 WARNING  [Apple] deviation profile.store-fake-ip: given="not set (core default: false)" effective="true: fake-ip mappings are written to disk so they survive an extension restart" category=forced
23:48:12.519 WARNING  [Apple] deviation rules: given="19 rule(s), first at rules[1286]" effective="these rules never match on this platform; traffic falls through to the next rule" category=unavailable -- the metadata they test is not available to an Apple packet tunnel, so the comparison never succeeds
23:48:12.520 INFO  [mem] after parsing the configuration: 18.2 MiB of the extension's budget
23:48:12.521 INFO  [mem] before applying it (providers load here): 18.2 MiB of the extension's budget

(约 17 秒无日志——providers 加载中;期间无任何 DNS 监听器报错)

23:48:29.128 WARNING  [TCP] dial 📢 <策略组A> (match Domain/mtalk.google.com) 198.18.0.1:57579 --> mtalk.google.com:443 error: <节点域名>:12011 connect error: dns resolve failed: couldn't find ip
23:48:29.128 WARNING  [TCP] dial <策略组B> (match DomainKeyword/google) 198.18.0.1:64725 --> www.google.com:443 error: <节点域名>:12011 connect error: dns resolve failed: couldn't find ip
23:48:29.128 WARNING  [UDP] dial <策略组B> (match DomainKeyword/google) 198.18.0.1:61382 --> www.google.com:443 error: couldn't find ip
23:48:29.128 WARNING  [TCP] dial <策略组B> (match DomainKeyword/google) 198.18.0.1:57586 --> www.google.com:443 error: <节点域名>:12011 connect error: dns resolve failed: couldn't find ip
23:48:34.136 WARNING  [TCP] dial 📢 <策略组A> (match Domain/mtalk.google.com) 198.18.0.1:57587 --> mtalk.google.com:443 error: <节点域名>:12011 connect error: dns resolve failed: couldn't find ip
23:48:34.136 WARNING  [UDP] dial <策略组B> (match DomainKeyword/google) 198.18.0.1:64533 --> optimizationguide-pa.googleapis.com:443 error: couldn't find ip
23:48:39.140 WARNING  [TCP] dial <策略组B> (match DomainKeyword/google) 198.18.0.1:57590 --> optimizationguide-pa.googleapis.com:443 error: <节点域名>:12011 connect error: dns resolve failed: couldn't find ip
23:48:44.140 WARNING  [TCP] dial <策略组B> (match DomainKeyword/google) 198.18.0.1:64727 --> play.googleapis.com:443 error: <节点域名>:12011 connect error: dns resolve failed: couldn't find ip
(此后每 5 秒重复同样的 couldn't find ip,持续到断开连接,此处省略)

===== app =====
23:48:11.824 INFO  activation build=0920-0459 total=196ms providers=30ms(n=0 reused=0 fetched=0 from=own(1)) finalize=66ms preflight=56ms geosite=43ms
23:48:12.107 WARNING  vpn status changed  to=connecting
23:48:12.198 INFO  system resolvers before the tunnel: <路由器IPv6-DNS> 192.168.50.1
23:48:12.201 WARNING  allow-lan permitted=false
23:48:12.218 INFO  span proxies.offline 116.3ms nodes=328
23:48:12.522 INFO  tunnel opened from pre-applied settings fd=11 bridge=none
23:48:12.630 INFO  tunnel started  core=1.19.31 status=running
23:48:12.632 WARNING  vpn status changed  to=connected
23:48:12.634 INFO  widget: published connected group=<策略组B> reason=start reloads=true in 3 ms up=0 down=0 proxy=0/0 direct=0/0 reject=0 conns=0/0 egress=🇯🇵 <日本节点> members=16

另一次会话的日志中还有这条汇总诊断:

[Apple] 20 outbound connections in a row failed and none succeeded: ... dns resolve failed: couldn't find ip. the tunnel reports itself connected, so this is happening after the tunnel came up

补充一个交叉引用:#3 中官方评论(2026-08-27)曾描述过这个配置形态——"把 dns.proxy-server-nameserver 指向核自己的 DNS 监听地址……主机名就会拿到 198.18.x.x 而超时",并称"这一点上游 mihomo 的行为完全相同,我们实测比对过"。

两点与本 issue 的实测不一致,供参考:

  1. 同一份订阅在 Clash Verge (macOS) 和 ClashMi (iOS) 上均正常——至少在 iOS 的 dns.listen: 0.0.0.0 场景下,Hako 的行为与上游/其他客户端并不一致。
  2. 本 issue 的失败模式不是"拿到 fake-ip 198.18.x.x",而是 ErrIPNotFound(couldn't find ip,约 5 秒超时的静默黑洞)——对应 listener_scope_darwin.go 文件头记录的通配 UDP packet conn 无回环面的缺口(出站方向已被 hook.go 豁免修复,包能送到 lo0,但通配 UDP 监听器听不到)。

补充一个可参考的先例:Clash Verge Rev 在客户端层把 proxy-server-nameserver / proxy-server-nameserver-policy / nameserver-policy 定义为 "provider DNS fields"
(https://github.com/clash-verge-rev/clash-verge-rev/blob/ff1ce92/src-tauri/src/config/dns.rs#L18),其"DNS 覆写"开启时会用自身配置替换这些字段(默认模板将 proxy-server-nameserver 置空,https://github.com/clash-verge-rev/clash-verge-rev/blob/ff1ce92/src-tauri/src/utils/init.rs#L387),
并在检测到 profile 携带这些字段而用户未确认时自动禁用覆写并弹 dns_override::auto_disabled 提示(同 dns.rs 文件内 DnsOverrideState)。

感谢!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions