为什么 iPhone 应用显示一个 IP,服务器记录的却是另一个
接口地址、套接字端点、服务器记录的来源,回答的是不同的问题。VPN 增加上下文,但不会把它们变成一个数字。
客服工单里说,应用显示一个地址,服务器却记录了另一个,然后问到底哪一个在撒谎。两个都没有;这是对两件不同事物的两次观察。绑定在某个网络接口上的地址、绑定在某个套接字上的地址,以及远端服务看到的来源地址,回答的是三个不同的问题。VPN 只是给手机增加了一层网络上下文,并不会把这三者合并成整个设备唯一的数字。先读工单里的字段名,再读地址。
工单通常在描述什么
设想一部连着 Wi-Fi、隧道已经建立的手机。Wi-Fi 接口持有一个局域网地址,隧道接口持有一个分配到的内部地址,而从隧道出去的请求到达公网服务时,来源是途中经过任何转换之后仍然存活的那个地址。打印内部地址的应用和在服务器上记录转换后来源的日志都是准确的,这两个值永远不会相同。
这三类观察的差别不在于它们描述了什么,而在于它们能证明什么。
| Observation | What it describes | What it cannot prove |
|---|---|---|
| An address on a local interface | One configured attachment: Wi-Fi, cellular or a tunnel | The source a remote site receives |
| A socket's local endpoint | The binding used by that one connection | The address after translation further along |
| The peer address at a service | What arrived at that observation point | Any other interface on the phone |
把那张三列表放在把地址写进工单的代码旁边,因为这里的大部分混乱来自串列,而不是网络故障。
套接字调用能回答什么、不能回答什么
在 API 层面,getsockname 报告的是当前绑定到某个具体套接字的本地端点。对于已连接的套接字,那就是内核为该连接选定的来源,它已经比任何接口列表都更具体。它并不是公网地址查询,在未绑定的套接字上调用它几乎什么都说明不了。它返回的值随后仍可能在路径上被改写,所以关于服务实际收到了什么,只有服务自身的观察才是权威的。
在 Apple 平台上,接口这一侧有单独的入口:隧道设置会列出分配给隧道接口的地址。读这些内容能知道隧道是如何配置的,但不知道某个请求走了哪条路径,因为这由路由和按应用规则为每条连接分别决定。
分流隧道让这个差异变得可见。一个请求可能从隧道出去,而下一个发往不同目的地或不同地址族的请求则不走隧道,所以同一应用相隔几秒的两次结果可以既诚实又不同。
选择与问题匹配的那次观察
要有意识地选,而不是把所有东西都埋点。要看后端看到什么,就在后端记录来源;如果前面有反向代理,就从你的部署实际设置的转发头里读客户端地址,而不是从客户端发来的任何东西里读。
要判断隧道是否已配置,读接口。要判断这个应用的流量是否走了隧道,就从该应用向你能控制的目的地发流量,并比对两端的来源。
按应用规则值得单独一句话。浏览器的结果只是关于那个浏览器的证据,而受管的按应用隧道可以在同一时刻把同一部手机上的两个应用放到不同路径上。验证覆盖范围意味着从涉事应用、在涉事地址族中发流量。
接口名不是检测器
也不要把接口名当作 VPN 检查。这些名字不是公开契约,而且一部手机可以同时有多个连接,只有其中一个承载你的请求。
字段名要按它承载的那次观察来命名。local_interface_address、socket_local_endpoint 和 service_observed_source 又笨又明确,而一周后这个值落进客服工单时,你要的正是这种明确。一个名为 device IP 的列会把三者都收进来,然后被当成同一个值来信。
把身份挡在地址列之外
共享的 VPN 出口不是用户身份,私有地址也不是访问凭据。两者描述的都是路径而不是人,把其中任何一个当作认证,就会把一次路由意外变成一次授权决定。
当有人问这部手机是什么地址时,有用的回答是反问:哪条连接、哪个观察点。getsockname 的定义值得为中间那种情况引用一次,因为它解决了套接字的问题,又没假装解决另外两个。
然后写下三者中你的产品真正需要显示哪一个,并把另外两个从界面上删掉。