有次一个同事询问,为什么经过一个网页请求经过了Http代理服务器后,网站依然能够获知访问者的真实IP地址。不是经过代理了吗?两个原因无法获得真实IP:TCP连接是在代理和网站之间,而非用户与网站之间的;HTTP协议只是第七层协议,怎么会把IP层的访问者的源IP信息也发送了呢?
实际上我相信当年设计代理服务器的专家们也遇到了同样的问题,就是如何能把访问者的源IP(而不是代理服务器的IP)发送给网站服务器呢?当年squid的专家们设计了这样一种解决办法:在HTTP包头部分插入一个header名为X-Forwarded-For(简称XFF),值为客户端的真实IP地址。这样,尽管TCP连接是代理和网站之间建立的,但网站依然可以通过解析包头并读取X-Forwarded-For而获得用户的真实IP。
除了squid等缓存服务器以外,XFF被大量应用的另一个场景是负载均衡(LB)。当然,当LB用于发布web网站的时候,也可以称为HTTP反向代理。也就是LB负责将用户请求分发给后台服务器池(pool)。那么此时TCP连接也是介于LB和server的,中间没有外部用户什么事儿。很多情况下server上的应用程序要获得用户IP再做进一步处理,此时也需要在LB上启用XFF。
顺便提一下,这个XFF的名称是可以自定义的。比如可以定义为my-client-IP之类,然后记得在web程序那边设定好去取这个名为my-client-IP的头标即可。
上面讲的都是单层代理(负载均衡)的情况。那么当经过了多级代理的话,第二级代理会把前面一级代理的XFF值给覆盖吗?答案是不会。实际上可以用逗号保存多个IP地址,包括用户IP,第一级代理IP,第二级代理IP,直到第n-1级代理IP(如果有n级代理的话)。没有必要保存最后一级代理的IP到XFF中,因为这个IP地址就是在TCP包(确切说是IP包)里的源IP。
在LB上实现XFF的方法
1. 在f5的BigIP产品上,可以设置profile启用XFF。另外也可以用iRule实现:
when HTTP_REQUEST { HTTP::header insert "X-Forwarded-For" [IP::client_addr] }
2. 在netscaler上,这个属性一般是设置在service级别的。启用service的cip属性为ENABLED [xff-name-defined-by-yourself]。同时在netscaler的全局级别启用cip并赋值的话,会默认继承到该设备的所有service上。
全局级别: set ns config -cip ENABLED [xff-name-defined-by-yourself]