获取客户端真实 IP 地址的最佳实践

一、背景

1. 业务上云带来性能收益

公司从去年全面推动业务上云,而以往 IDC 架构部署上,接入层采用典型的 4 层 LVS 多机房容灾架构,在业务高峰时期,扩容困难(受限于物理机资源和 LVS 内网网段的网络规划),且抵挡不住 HTTPS 卸载引发的高 CPU 占用。

而经过压力测试发现,使用腾讯云 7 层 CLB 负载均衡进行 HTTPS 卸载,性能得到极大提升。测试数据也表明,IDC 旧架构中,启用 HTTPS 会带来 90% 以上的性能损耗。

2. 架构调整引发多次故障

引入腾讯云 7 层 CLB 负载均衡产品,带了了巨大的性能提升,却也给业务带来了痛苦,主要核心问题是获取客户端的真实 IP 上。

当前现状是业务语言异构(PHP + Go),多数业务已经历服务化改造,但缺乏服务发现机制,服务与服务之间的调用依赖域名和 DNS 解析,大部分都是 HTTP 服务。

在架构调整后,由于未能 100% 覆盖测试,导致漏测的服务经常拿到错误的客户端 IP 地址,造成的后果是损失大量的用户。这些用户会因为短信验证码发送限制、IP 登录频次过高而无法登录、充值,给公司带来巨大损失。

3. 未来的路应该怎么走?

更进一步讲,当前业务如何抵挡外界的 DDoS 攻击、请求机器人、SQL 注入等等,最简单的是接入高防 IP、WAF 应用防火墙,而请求经过多轮转发,同样也有获取客户端真实 IP 的问题。

再者,业务也在逐步容器化,享受 Kubernetes 弹性扩容的便利,怎么平滑迁移也是非常值得深思的。

假设有一天某个同学,不小心配置有误——应用层拿到的,很有可能是高防 IP 或者 WAF 的 IP,业务绝对无法忍受。

显然,确定一个业务无感知的方案并成功落地迫在眉睫。

然而翻遍整个互联网,几乎没有文章能把这些看起来很简单的事情捋清楚、讲明白,更不用说最佳实践。

大多数人都是抄抄配置,潦潦草草上线,方案并没有普适性。

这篇文章也是我在这段时间的研究中总结出来的宝贵经验,希望对读者能有些许帮助。文章篇幅较长,难免有错误之处,还请各位看官斧正,感激不尽:)

二、名词释义

1. REMOTE-ADDR
  • Nginx + PHP 模式下,REMOTE-ADDR 为远端的 IP 地址,可通过 $_SERVER['REMOTE-ADDR'] 获取;
  • 它代表与上一层建立 TCP 连接的 IP 地址;
  • 网站无代理时(客户端->服务端),WEB服务器(Nginx,Apache等)会设置该值为客户端 IP;
  • 网站存在代理时(客户端->代理->服务端),该值为代理的 IP。
proxy_set_header REMOTE-ADDR $remote_addr;
2. X-Forwarded-For

X-Forwarded-For 是一个 HTTP 扩展头部。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。

  • 格式为英文逗号 + 空格隔开,例如:X-Forwarded-For: IP0(client), IP1(proxy), IP2(proxy);

  • 中间经过的代理,会逐层追加至末尾;

  • IP0 离服务端最远,然后是每一级代理设备的 IP,IP2 直连服务端。

  • 如果客户端伪造 IP 地址,格式为:X-Forwarded-For: 伪造的 IP 地址 1, [伪造的 IP 地址 2…], IP0(client), IP1(proxy), IP2(proxy)。

3. X-Real-IP

注:CLB <=> SLB,为腾讯云和阿里云不同产品的称呼,均为负载均衡。

典型的调用链路:

client --> ① [CLB-7]gateway --域名--> ② [CLB-7]server(③ nginx + ④ go/php)
  • X-Real-IP 为建立 TCP 连接的上一跳的 IP 地址;
  • 对于 ④ 而言,X-Real-IP 为 ① 网关的 NAT 公网出口 IP 地址,或 gateway 的内网 IP 地址,该结论通过生产环境 tcpdump 抓包验证得到;
  • 公网调用下,① 网关 调用 ② 7 层 CLB,再到应用层 ③④,此时 ④ 拿到的 X-Real-IP 为 ① 的 NAT 公网出口地址(7 层 CLB 会重写 X-Real-IP 头部,并追加 X-Forwarded-For 头部);
  • 内网环境中,原理相似,只不过拿到的是 gateway 的内网 IP 地址;
  • 中间可能被 ③ nginx 重写,此时等同于 REMOTE-ADDR。

比如以下最常见的 nginx 配置:

proxy_set_header  REMOTE-ADDR     $remote_addr;
proxy_set_header  X-Real-IP       $remote_addr;
proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;

REMOTE-ADDR 和 X-Real-IP 都是 nginx 的 $remote_addr 变量,再传递给下游。

三、面临困境

1. 运维侧
  • 业务线配置五花八门,没有统一。具体表现在 nginx.conf 和 vhost 配置在不同的业务线有很大区别;
  • vhost 成千上万,nginx 内部存在多重转发,外部也有网关转发过来的流量,且网关不止一套,捋不清链路容易导致线上故障;
  • 缺乏完善的 QA 验证流程,变更没办法 100% 覆盖测试,最终结果就是尽可能少变更,但这不是长久之计;
  • 存在开发自行维护信任 IP 的情况,所以运维不敢随便变更,因为变更前需要通知开发整改,开发有自己的时间排期,处理起来效率极其低下;
  • 为了尽可能少修改原先的配置,部分机器组接入了腾讯云的 TOA 模块,用来获取客户端真实 IP 地址,而阿里云没有相似的产品,如果没有统一的方案,没办法上线阿里云,实现不了双云双活的目标等等。
2. 开发侧

各个业务线使用的技术栈不统一,存在多种获取客户端 IP 的方案,需要找到一种尽可能少修改代码,或者一点都不需要修改代码的方案。

PHP 以 Laravel 框架为例(底层是 Symfony 框架),发现内部取了 $_SERVER[‘REMOTE_ADDR’] 变量:

public function getClientIp()
{
   
    $ipAddresses = $this->getClientIps();
    return $ipAddresses[0]; // 1. 取第一个 IP 地址。
}
public function getClientIps()
{
   
    $ip = $this->server->get('REMOTE_ADDR');
    if (!$this->isFromTrustedProxy())</
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值