有人问:为什么 VPN 用户在应用日志里出现的地址属于网关,或者午饭之后换了一个地址。答案要从这个值是什么讲起:它是 Web 服务器放进请求环境里的一条记录。它不是对隧道的查询,不是证书身份,也不是“这个数字属于同一个人”的承诺。把它当成这三者中的任何一个,都会得出自信却以不同方式出错的结论。

值来自服务器,而不是隧道

PHP 从执行脚本的那一方获得请求环境。PHP 服务器变量参考把这些条目描述为由 Web 服务器创建,其可用性取决于请求是如何到达的。

作为 VPN 命令行钩子运行的脚本,是完全不同的执行环境。它没有 Web 请求,没有客户端套接字,也没有 REMOTE_ADDR,所以把 Web 代码复制进钩子,得到的是空值而不是错值。钩子有自己的环境,由守护进程填充,并且单独文档化。在那里去找 REMOTE_ADDR,等于去找一个从未设置过的变量。

在比较两份日志之前,请记住这个区别。你比较的可能是一次页面请求和一个钩子,那根本算不上比较。先检查执行环境,再看地址。

在相信这个数字之前,先走一遍请求路径

报告的地址是 Web 服务器所看到的那条连接的通信对端。只要在客户端和那台服务器之间插入任何东西,对端就变了。地址转换、连接终止和负载均衡,各自都把观测点向远离用户的方向推进一步。

四种常见路径会带来四种不同的观测结果。

Path to the listenerWhat can be reported
Client routed straight to the private listener, no translationThe client's tunnel address
Gateway translates the source before forwardingThe gateway's translated address
Reverse proxy terminates the client and opens its own backend connectionThe proxy's backend address
Load balancer in front of two proxiesWhichever hop the server treats as its peer

把路径连同目标名称、地址族和每一个转换点一起写下来。没有这张图,正确值和错误值在日志行里看起来完全一样。两行示意图能省下一整个下午的争论。

只有配置了的地方才会发生替换

服务器或框架可以有意替换客户端地址。Apache 的 remote-IP 模块和 NGINX 的 real-IP 模块都会这样做,PHP 收到的值取决于该配置以及传给脚本处理器的参数。

所以 REMOTE_ADDR 并不总是未被触碰的 TCP 对端。它是服务器在应用自身策略之后选择报告的产物。这个区别决定了该值是证据还是装饰。

问清服务器所有者:这套部署里哪个字段才是权威的,发生替换时原始对端如何保留。如果没人答得上来,日志暂时还证明不了任何事。

不要凭信任去读转发头

调用方可以自己发送转发头。把第一个逗号分隔的条目当成已验证的用户地址,等于决定相信任何连上来的人,这正好与这个头的用途相反。头描述的是一条链,只有可信的一层才能为这条链背书。

使用服务器或框架的可信代理支持,并配置真实的代理链,让它丢弃来自链外来源的值。手写的解析正是大多数有意思的故障所在之处。

代理报告的地址也不会改变上游防火墙看到的报文来源。请把 HTTP 元数据和网络过滤当成两件独立的观测,因为它们回答的是不同的问题。

用应用身份来做决定

要访问某个用户的记录,请依赖已认证的主体和应用的授权规则。内部地址可能被重新分配,而一个被转换过的地址可能代表数百个并发用户。这两点都不是地址校验能修补的。

如果运维需要把一次请求关联到某个 VPN 会话,就按你记录过的转换模型,把观测到的地址和时间戳与权威会话记录关联起来。这样才能得到站得住脚的关联;单靠一个地址不行。

做诊断时,只记录你需要的那几个字段,而不是整个服务器数组,因为它可能带着请求头和认证材料。有用的结论说明的是这个值在这套部署里意味着什么,而不是哪个值最像客户端。

相关文章