bigywater.tech
网络协议 · 73

操作系统怎么解析名字:getaddrinfo、search 域、ndots 与 /etc/hosts

应用调用 getaddrinfo 之后,到底发生了什么?操作系统里的“存根解析器”按什么规则选名字服务器、要不要加搜索域、先查 /etc/hosts 还是先发 DNS、.local 为什么走多播?这篇用抓包把一次次 getaddrinfo 在线路上的真实动作逐个看清,并且只保留本机自己发出的包。

2 个包
一次 getaddrinfo 在线路上的全部 DNS 流量:1 个 A 查询 + 1 个响应(没有 AAAA)
lan. → 裸名
单标签名字先加搜索域再问原名的顺序
5002 ms
.local 名字解析失败所需时间,等 mDNS 超时

解析器列表

实测 · 本机执行输出
  默认(非作用域)解析器共 7 个:
    resolver #1 | search domain[0] : lan | nameserver[0] : 114.114.114.114 | nameserver[1] : 223.5.5.5 | flags    : Request A records
    resolver #2 | domain   : local | options  : mdns | timeout  : 5 | flags    : Request A records | order    : 300000
    resolver #3 | domain   : 254.169.in-addr.arpa | options  : mdns | timeout  : 5 | flags    : Request A records | order    : 300200
    resolver #4 | domain   : 8.e.f.ip6.arpa | options  : mdns | timeout  : 5 | flags    : Request A records | order    : 300400
    resolver #5 | domain   : 9.e.f.ip6.arpa | options  : mdns | timeout  : 5 | flags    : Request A records | order    : 300600
    resolver #6 | domain   : a.e.f.ip6.arpa | options  : mdns | timeout  : 5 | flags    : Request A records | order    : 300800
    resolver #7 | domain   : b.e.f.ip6.arpa | options  : mdns | timeout  : 5 | flags    : Request A records | order    : 301000
  /etc/hosts 的文件头说明它在 macOS 上“不被大多数进程用于 DNS 主机名解析”——但里面的 localhost / broadcasthost 仍有效(第四节验证)

macOS 按系统配置里的 7 个解析器按后缀选择:1 个负责普通名字,其余 6 个是 mDNS(local 与链路本地反向域)。

只问 A 记录

实测 · 本机执行输出
  AF_UNSPEC:耗时 291 ms,失败 gaierror;抓到 2 个包
    +    0.0 ms  → 114.114.114.114      查询  [('w73-rzavjmvt.example.com.', 'A')]
    +  285.2 ms  ← 114.114.114.114      响应  [('w73-rzavjmvt.example.com.', 'A')]  RCODE 3,回答 0
  只要 IPv4(AF_INET):耗时 259 ms;抓到 2 个包
    +    0.0 ms  → 114.114.114.114      查询  [('w73-ngvtezst.example.com.', 'A')]
    +  256.1 ms  ← 114.114.114.114      响应  [('w73-ngvtezst.example.com.', 'A')]  RCODE 3,回答 0
  AF_INET 时发出的查询类型:['A']

没有可用的 IPv6,配置标志是 Request A records:即使要求 IPv4+IPv6,线上也只有 A 查询。

搜索域

实测 · 本机执行输出
  耗时 55 ms,失败:gaierror [Errno 8] nodename nor servname provided, or not known;抓到 4 个包
    +    0.0 ms  → 114.114.114.114      查询  [('w73hostbdgchl.lan.', 'A')]
    +   27.7 ms  ← 114.114.114.114      响应  [('w73hostbdgchl.lan.', 'A')]  RCODE 3,回答 0
    +   31.0 ms  → 114.114.114.114      查询  [('w73hostbdgchl.', 'A')]
    +   46.7 ms  ← 114.114.114.114      响应  [('w73hostbdgchl.', 'A')]  RCODE 3,回答 0
  出现过的查询名字:['w73hostbdgchl.', 'w73hostbdgchl.lan.'](带不带搜索域后缀、是否有裸名字)
  这次查询发往的服务器:['114.114.114.114']

单标签名字先加搜索域 lan 问一次,NXDOMAIN 后再问不带后缀的裸名字。

/etc/hosts

实测 · 本机执行输出
  localhost:耗时 0.8 ms,结果 ['127.0.0.1', '::1'];抓到的 DNS / mDNS 包 0 个
  broadcasthost:耗时 2.8 ms,结果 ['255.255.255.255'];抓到的 DNS / mDNS 包 1 个

localhost 等条目不发 DNS 包;自定义条目是否生效没有测。

.local 与 mDNS

实测 · 本机执行输出
  getaddrinfo("w73-rskhta.local"):耗时 5002 ms,失败:gaierror;抓到 4 个包:
    +    0.0 ms  mDNS 本机→224.0.0.251  查询  [('w73-rskhta.local.', 'A')]
    + 1004.6 ms  mDNS 本机→224.0.0.251  查询  [('w73-rskhta.local.', 'A')]
    + 3723.7 ms  mDNS 本机→224.0.0.251  查询  [('_adb-tls-connect._tcp.local.', <RdataType.ANY: 255>), ('_adb-tls-pairing._tcp.local.', <RdataType.ANY: 255>), ('_adb._tcp.local.', <RdataType.ANY: 255>)]
    + 4012.0 ms  mDNS 本机→224.0.0.251  查询  [('w73-rskhta.local.', 'A')]
  按(类型, 目的地址)计数:{('mdns', '224.0.0.251'): 4}

.local 名字向 224.0.0.251 发多播查询,查不到要等 5 秒超时。

分步演示

存根解析器
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。抓包只保留 UDP 53 里含测试标签的 DNS 包和本机自己发出的 mDNS 包,不保留局域网里其它设备的任何流量(第一次抓包带进了其它设备的名字和地址,已丢弃重做);第四节没有 root,不能修改 /etc/hosts
    • 没有验证:/etc/hosts 里自定义条目是否生效;多个名字服务器之间的故障切换(第一个服务器不响应时何时换第二个);ndots 规则(macOS 的多标签名字是否也先加搜索域);作用域查询(scoped)的解析器;DNS-SD / Bonjour 服务发现;Python 的 getaddrinfo 与 dscacheutil / dig 走的路径差别
    京ICP备2026067502号-1京公网安备11010502063487号