
现象回顾
劫持 WIFI 网络环境, Sqi-dns-hijacking 把 core.sqiapp.com 解析到 203.208.39.99, 已知目标 IP 已开启 443 端口。
你的 NSURLSession 任务在 didFinishCollecting 回调里拿到一条 NSURLSessionTaskTransactionMetrics,但:
(Local Address) (null)
(Remote Address) (null)
Apple 的计量规则
localAddress / remoteAddress 只有在 “这条连接被真正用来传输应用层数据” 之后才会被填充。
Apple 文档对两个属性都写着几乎相同的一句(高亮是我加的):
If the app didn’t use the connection, this value is nil
在下面这些情况下,系统会认为“连接没被用过”:
| 情形 | 结果 | 为什么地址为 nil |
|---|---|---|
| TLS 握手/证书校验失败 (例如被劫持到 203.208.39.99,证书与 core.sqiapp.com 不匹配,ATS 或你自己的 Pinning 在 ClientHello 之后就终止连接) |
task 直接报 NSURLErrorServerCertificateUntrusted 等错误 |
没有任何应用数据发出,连接标记为 “unused” |
| 域名解析失败 / TCP 握手没做完 | 地址自然拿不到 | 同上 |
| iCloud Private Relay 或代理 ( isProxyConnection == YES) |
地址显示为中转服务器,而不是目标 | 如果隐私策略再收紧,也可能给 nil |
等待网络 (waitsForConnectivity) 时被你自己取消 |
地址 nil |
连接还没真正建立 |
Apple Developer Forums 的示例日志和本文讨论的现象一模一样:当域名错误或握手失败时,地址字段为 (null)。
为什么在 Sqi-dns-hijacking 网络下触发
DNS 指向了 Google 的 203.208.39.99,但
该主机提供的证书不匹配 core.sqiapp.com → ATS 信任评估失败。
URLSession 在 TCP 三次握手后、还没把任何 HTTP 请求数据发出去就中止。
交易记录被标记为 “connection unused”,于是 localAddress / remoteAddress 均为 nil。
只要握手失败得足够早,哪怕 443 端口是通的,Metrics 里仍然没有地址。
如何得到真实地址?
让握手成功(取消 Pinning、换成受信证书或临时连真服务器)后再看 Metrics。
改用 Network framework (NWConnection) —— 你可以在还没发 TLS 握手之前就拿到 NWEndpoint 的 IP。
确保设备没开 iCloud Private Relay / VPN / 代理,否则看到的也是中转地址。
小结
NSURLSessionTaskTransactionMetrics 只在 连接真正被“用了” 以后才填写源/目的 IP。
被 DNS 劫持导致 TLS 失败时,连接被视为未使用 → 地址字段是 nil。
这完全符合 Apple 的设计,不是 bug,只能通过让连接走到“发出第一字节的 HTTP 请求”这一步才会有地址数据。
拓展之关于Metrics
发音
英, 美 ['metrɪks]
metrics(/ˈmeɪ-trɪks/)= “度量指标、量化衡量值”
| 场景 | 常见中文译法 | 内涵 | 例子 |
|---|---|---|---|
| 一般商务 / 产品 | 指标、衡量标准 | 用数字衡量目标是否达成 | business metrics = “业务指标”(DAU、转化率) |
| 计算机 / 运维 | 性能指标、监控指标 | 描述系统健康状态的量化值 | CPU metrics, latency metrics |
| iOS / Apple API | Transaction Metrics | 对一次网络事务采集到的所有网络性能数据 | NSURLSessionTaskTransactionMetrics 内的 fetchStartDate, localAddress 等 |
| 学术 / 质量评价 | 评价指标 | 客观度量某对象优劣的数字 | evaluation metrics(准确率、召回率) |
| 数学(单数 metric) | 度量 | 一种距离函数 | 欧几里得距离是一种 metric |
用法要点
集合名词
metrics 常当不可数集合名词用:
Collect metrics every 30 seconds.
单数 metric = “某一个指标”
Latency is a critical metric for real-time apps.
词源
来自希腊语 metron “量尺”。
在 Apple 网络栈里的含义
NSURLSessionTaskTransactionMetrics 把一次 TCP/TLS 事务的各个 metrics(开始时间、握手耗时、IP 地址等)打包成一个对象,供开发者诊断网络性能。
一句话记住:metrics = 一切用数字“量尺”去衡量事物状态或效果的指标。
拓展之localAddress vs. remoteAddress

二者一字之差、含义完全相反
| 属性 | 中文直译 | 代表谁的 IP | 典型取值示例 | 何时可能为 nil |
|---|---|---|---|---|
localAddress |
本地地址 | 你这台设备 在发起这条网络连接时所用的网络接口 IP(WLAN、蜂窝、VPN 隧道等) | 192.168.10.23(Wi-Fi)10.0.0.4(蜂窝 NAT64) |
① 连接因 TLS 或 DNS 错误在真正“用”之前就被放弃;② 构建在匿名代理 / Private Relay 之上并被 Apple 隐私规则隐藏;③ 任务处于等待联网 (waitsForConnectivity) 即被取消 (CSDN博客) |
remoteAddress |
远端地址 | 对端主机(或第一跳代理)的 IP —— 即 TCP 三次握手 / QUIC CHLO 时真正握上的目标 | 203.208.39.99(直连服务器)172.20.0.8(公司 HTTP 代理) |
同上;另外当通过透明代理 / VPN 层封装时,看到的是代理出口而不是最终服务器 (掘金) |
一句话记住:
localAddress看“我是谁”;
remoteAddress看“我在跟谁直接握手”。
进一步要点
端口配对
同时还有 localPort / remotePort,能让你知道源端口(客户端随机)与目标端口(通常 443、80……)的对应关系。
多路径场景
在 Wi-Fi + 蜂窝共存的 Multipath TCP / QUIC 中,这两个字段记录“首条流”的本地/远端 IP;如果之后系统切到另一条路径,它们不会实时更新。
代理 / VPN / Private Relay
当 isProxyConnection == YES 时,remoteAddress 是代理节点;再往后的真实服务器地址你拿不到。Private Relay 情况下,苹果为了隐私可能把两端 IP 都置为 nil。
nil ≠ BUG
只要操作系统认定“连接尚未被真正用于传输应用层数据”,地址字段按照规范就必须是 nil,与你看到的 Sqi-dns-hijacking 场景一致。
借助这两个字段,你可以快速回答诸如“究竟走 Wi-Fi 还是蜂窝?”、“TCP 打到哪台机器?”之类的疑问——前提是连接已经成功完成且未被额外的隐私层隐藏。




















暂无评论内容