Question
使用 iOS 的网络库 (比如 AFNetwoking, URLSession, Alamofire) 进行请求时, 域名具体是怎样被解析为 IP 地址的 ?
Answer
一次常见的 URLSession / AFNetworking / Alamofire
请求,域名 → IP 的解析全过程 拆成自顶向下 6 个环节, 如下
1 ► 应用层:你写的网络库只是“填表”
AF.request("https://api.example.com/v1/user")
- AFNetworking / Alamofire 最终都调用
URLSession
。 - 这一层只负责把 URL 拆成 host = “api.example.com”,然后把任务交给 Apple URL Loading System。
解析域名不是这些库的职责,它们也不会做任何 DNS 缓存或重写。
2 ► Foundation → Network.framework:准备建立连接
- 从 iOS 13 开始,
URLSession
内部会为每个任务创建一个NWConnection
。
你只要给它 host + port,它就会“自行解析主机名并决定用 IPv6 还是 IPv4”。 - 在更老的系统里,同样的动作由 CFNetwork 下的
CFHost
/SCNetworkReachability
触发,但逻辑基本一致。
3 ► POSIX API:异步 getaddrinfo()
NWConnection
调用getaddrinfo()
(或苹果的异步封装)去拿 A / AAAA 结果列表。
getaddrinfo()
的接口说明里就写明了“返回主机名对应的一组 IP 地址”。- 这一调用并不直接碰网络,而是把查询通过 libsystem_dnssd 发给系统解析器。
4 ► 系统解析器:mDNSResponder
的工作
- 进程间通信
getaddrinfo()
会通过 UNIX domain socket/var/run/mDNSResponder
或 XPC 服务
com.apple.dnssd.service
把请求送到守护进程 **mDNSResponder
。 - 本地快速路径
- 先查进程自己的 DNS 缓存(Happy Eyeballs 里曾用过的地址)。
- 查全局缓存、DNS64 映射、搜索域规则。
- iOS 正式版 默认忽略
/etc/hosts
(只有内部签名版本才会读取)。
- 远程查询
- 选取当前网络接口配置的 DNS 服务器(蜂窝、Wi-Fi、VPN、DNS Proxy、加密 DNS 等)。
- 按需发出 UDP/TCP / DoT / DoH 查询。
- 支持 0-RTT 缓存失效重查、CNAME 链、EDNS0、DNSSEC(仅验证)。
- 回包与缓存
收到应答后写入本地 LRU 缓存,同时把完整的 IP 列表返回给原调用者。
5 ► 连接阶段:挑选并握手
NWConnection
拿到一组 IP 后,会按 Happy Eyeballs v2 (RFC 8305) 同时或错峰打洞,
谁先TCP SYN/QUIC CH
成功就用谁,另一组流量会被放弃。- 若你开启了 HTTP/3 (QUIC),这里还会对 443/UDP 做一次同样的竞争。
6 ► 后续:证书校验 & ATS 策略
- TLS / QUIC 完成后才会进入真正的 HTTP 层。
- 若 App Transport Security 要求 TLS 1.2+、证书钉扎或自签根证书,这些都发生在 DNS 已完成之后。
你能控制 / 优化的点
场景 | 建议 |
---|---|
自定义 DNS(广告屏蔽、内网解析) | 用 NetworkExtension → DNS Proxy / DNS Settings,或把 VPN 配置里的 DNS 指向自建服务器;不要试图直接修改 /etc/hosts 。 |
调试 DNS | 在 macOS/iOS 上用 log stream --info --predicate 'process == "mDNSResponder"' 可看到解析全过程(需要专用 profile 才能取消隐私脱敏)。 |
降低首包延时 | 在服务端同时提供 AAAA & A,并优化 v6 路径;或者在客户端用 NWParameters.preferNoProhibitInterface 关闭 Happy-Eyeballs 的部分等待。 |
防 DNS 劫持 | - 使用 DOH/DOT 解析器;- 对关键接口启用 TLS ALPN + 证书钉扎;- 在应用层对解析结果做 二次校验(IP 白名单、SPKI 哈希)。 |
一句话总结
AFNetworking/URLSession/Alamofire 并不自己做 DNS。
iOS 里真正把域名变成 IP 的,是getaddrinfo()
→ libsystem_dnssd → mDNSResponder → 外部 DNS 服务器 这一条全局链路;
只有理解这条链路,才能在安全、性能或可观测性上做针对性的优化。