正确读取 Android DNS:按网络,而不是 resolv.conf
你的 Android 应用通过按网络划分的 resolver 解析,而不是你刚读过的 resolv.conf。先检查 LinkProperties、socket 绑定和 Private DNS。
你在设备上拿到 shell,读取 /etc/resolv.conf,或者翻出一个旧的 net.dns1 值,然后得到一个地址。面前的应用却完全通过另一条路径解析。在当前 Android 版本中,resolver 是按网络划分的:系统为每个 Network 保存策略,并将其应用到发起请求的 socket。文件和旧属性都是遗留物,两者都不能描述你的查询实际走过的路径。
这个落差在 split-tunnel 和 Private DNS 上线时显现出来:同一部手机上的两个应用可以名正言顺地看到不同答案。本文从错误或缺失的解析开始,回溯到拥有它的配置。
resolver 属于网络,而不是设备
Android 9 及以后的名字解析走系统 resolver,它以 socket 所绑定的 Network 为依据。每个网络都有自己的 LinkProperties,这些属性只保存该网络的 DNS 服务器列表、搜索域和 Private DNS 状态。该模型中没有哪一项对应你从文件里读到的设备级单一列表。
VPN 让这种分离变得明显。隧道起来时,VPN 应用提供注册在 VPN 自己网络上的 DNS 服务器,于是经它路由的应用用这些服务器解析,被排除的应用继续使用底层网络。一台设备,两条应答路径,没有矛盾。
从受影响应用内部读取快照
问应用本身。一个拥有 Context 和常规 android.permission.ACCESS_NETWORK_STATE 权限的方法,可以在不碰系统属性的情况下读取快照:
val cm = getSystemService(ConnectivityManager::class.java) ?: return
val network = cm.activeNetwork
val links = network?.let { cm.getLinkProperties(it) }
val dnsServers = links?.dnsServers
val privateDnsActive = links?.isPrivateDnsActive
val privateDnsName = links?.privateDnsServerNameactiveNetwork 是你进程的默认网络,而不是设备上唯一的网络;调用方没有可用默认值时它可以是 null。被阻止的默认 VPN 是常见原因。isPrivateDnsActive 告诉你该网络上的查询是否经 TLS 发往具名 resolver,这会改变简单 UDP 53 探测看到的结果。API 表面记录在 LinkProperties 之下,其中每个值都是配置快照,而不是某条查询抵达某台服务器的证据。
当默认网络不是真正干活的那个
快照只回答默认网络。应用可以把进程或单个 socket 绑定到另一个网络,此时解析跟随该选择,而你的诊断仍报告默认网络。自带 DNS-over-HTTPS resolver 的浏览器根本不查系统列表,所以它的答案说明不了设备其余部分。工作资料应用在另一个用户下运行,有自己的策略。先检查绑定,再去怪服务器。
VPN 集成是另一半。收到 dhcp-option DNS 一行(在 OpenVPN 配置里),和把该地址注册到 Android VPN 网络,是两个独立步骤;缺少到服务器的路由会像地址错误一样悄无声息地失败。在开发机上,构建允许时 adb shell dumpsys connectivity 会按网络打印同样的链路属性,不用写代码就能确认列表。
用一次你被允许做的解析来对照配置
选一个必须在私有命名空间内解析的名字和一个公共名字,把两个结果记在快照旁边:网络、已配置服务器、Private DNS 状态和确切的错误文本。直接向具名服务器查询只能证明那台服务器回答你,不能证明普通应用解析选了它。缓存答案不是一次新测试,所以要清缓存,或使用今天没人查过的名字。
如果 resolver 只服务某个接入网络的客户端,那么它在 VPN 出口之后看到什么就可能决定结果,这是运营方的策略问题,而不是运营商封锁 UDP 53 的证据。在 Private DNS 生效的地方,未限定主机名可能被设计性地拒绝,而同一台服务器对公共名字仍正常应答。读取这些属性就能知道当前是哪种模式,于是你不再猜包过滤。
修好拥有这处不一致的那一层
向上排查,而不是改全局状态。应用绑错了网络,就修绑定。配置发错了 DNS 选项,就修配置。resolver 无法服务你的私有区域而 Private DNS 又很严格,那是设备管理决策,不是到处关掉功能的理由。
把公共 resolver 塞进 DHCP 钩子或系统属性,正是不断制造事故的那种修复:私有名字不再解析,改动活得比 VPN 会话更久,下一个人在这台设备上会找到一个没有主人的过期地址。把改动留在拥有该不一致的 VPN、应用或管理层内,然后重读快照,确认服务器列表真的变了。
最终你要的是一条应用请求绑定到一个网络、一份 resolver 列表和一种 Private DNS 状态。当你能展示这条链时,文件里一个熟悉的地址就不再是任何证据。