前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >服务器处于端口回流的情况下如何获取客户端真实IP

服务器处于端口回流的情况下如何获取客户端真实IP

作者头像
@派大星
发布2024-08-19 15:25:22
970
发布2024-08-19 15:25:22
举报
文章被收录于专栏:码上遇见你

获取客户端真实 IP

出于安全考虑,近期在处理一个记录用户真实 IP 的需求。本来以为很简单,后来发现没有本来以为的简单。这里主要备忘下,如果服务器处于端口回流(hairpin NAT),keepalived,nginx 之后,如何取得客户端的外网 IP。

来自客户端 PC 的流量路径如上,在这样的拓扑中,在应用服务中取得,客户端 PC 的外网 ip,可能会遇到哪些问题呢?(ip 编的随意,为便于说明,不考虑合理)。

编程实现

Java 为例,这个我会。

代码语言:javascript
复制
public static String getClientIP(HttpServletRequest request) {
    String remoteAddr = request.getRemoteAddr();
    return remoteAddr;
}

运行一下,输出呢是 3.3.3.3 。这是因为这个 API 所取得的是 IP 数据包的源地址。Nginx 的反向代理时工作在应用层的,当他收到一个 http 请求时,会对应生成一个新的请求,发送给应用服务,这个请求的 IP 包的源地址是 Nginx 服务器的 IP 即 3.3.3.3。

Nginx 头部注入

因为是应用层,那这个请求 ip 包的源地址肯定就是 3.3.3.3 了,但是在应用层我们可以附加一点信息,以便后面的应用服务,可以通过这个附加信息,了解这个请求对应的原始源地址。这个我也会。

Nginx 中配置。

代码语言:javascript
复制
server {
    ...
    proxy_set_header X-Real-IP $remote_addr;

在应用层 http 协议中,加一个http header X-Real-IP remote_addr。remote_addr 是一个预设变量,代表所代理转发请求的原始源 ip 地址。在 Java 程序中,读取对应的附加信息

代码语言:javascript
复制
public String getRealIp(HttpServletRequest request) {
    String realIp = request.getHeader("X-Real-IP");
    if (realIp != null && !realIp.isEmpty()) {
            return "Client's Real IP: " + realIp;
    }
    return "";
}

运行一下,此时输出2.2.2.2。显然我们向前推进了一步。

Keepalived 负载均衡模式

印象里这里 keepalived 的主要作用应该是解决 nginx 代理服务器的单点问题的,似乎也被配置为负载均衡了?翻了下配置文件,实际的情况如下。

运维大壮说他配置 keepalived 时候多考虑了一步,如果机器活着,Nginx 挂了怎么办,于是又做了一层负载均衡(这种情况虚拟 IP 不会漂移到右边的备机)。他说的也确实不是没有道理。keepalived 的负载均衡貌似是工作在第三层的,那肯定在负载均衡的时候,又对 ip 包的源地址进行了修改。这是网络层,向 Nginx 这样附加信息肯定是不行了。于是,翻了翻手册发现,keepalived 的负载均衡支持三种路由模式,NATDirect RoutingTunneling

  • NAT 模式,会修改源 IP,出入流量都会经过负载均衡器。而 DR 模式,会直接修改 MAC 地址,那回程流量就不再经过负载均衡器了,也就意味这种模式,源地址不会被修改,回程流量会直接发送给源 ip 地址。
  • DR 模式有个要求,就是负载均衡器需要能知道后端服务的 MAC 地址,这是依赖于 ARP 实现的,也就是,要求负载均衡器和后端服务器在同一广播域。恰好我门可以满足。于是。
代码语言:javascript
复制
virtual_server 192.168.11.242 80 {
……
lb_kind DR
……

将负载均衡路由模式切换为 DR 模式。重新看一下这次,取得客户端地址变成了 1.1.1.1, 这一步一坑。为什么到达 keepalived 的 ip 包的源地址会变成,出口路由器的外网地址呢?

路由器端口回流(Hairpin NAT)

离胜利是不远了,此时见多识广的大壮说,这应该是跟端口回流有关,之前有个系统也是类似问题, 你的 web 端口配置了端口回流,如果关掉端口回流就可以取得外网地址了。什么是端口回流?

首先,路由器做了端口映射,1.1.1.1:80->192.168.0.2:80

服务器 A,由于某些原因,不方便使用内网地址 192.168.0.2 访问 B,而要通过外网 IP 或者域名访问服务器 B,即访问 1.1.1.1:80, 按端口转发规则,路由器会将这个来自于内网接口的流量再次转发回内网服务器 B,形成了一个 180 度的急弯——发卡弯,这也就是Haripin NAT的名字由来,十分形象。

如果不做设置,服务器 A 通过访问 1.1.1.1:80 是无法正常访问服务器 B 的。原因是,hairpin 会影响 Tcp 连接建立的握手过程。

  1. A 发送握手请求给入口路由器,路由器修改目的 ip 为 192.68.0.2 ,发送到服务器 B。
  2. B 收到握手请求后,回复握手确认应答给这个握手请求的源 IP 地址,此处是 A 的地址 192.168.0.1
  3. 因为 A,B 同一网络,握手确认会直接到达 A。
  4. A 发现这个握手确认回复的源 ip(192.168.0.2)并不是我期望与之建立连接的握手请求目的地址(1.1.1.1),A 并不认识 B,只认识路由器,导致 TCP 连接无法建立。

解决以上问题的关键,就是让握手确认应当同样经过路由器,发送给 A。因此,需要在之前将握手请求转发给 B 时,同时修改源 ip 地址为(1.1.1.1),如此,B 服务器作出确认回复时,自然也会发送给 1.1.1.1。

但是这个源地址转化(SNAT)的过程,实际上只对于来自内网的流量是有必要的。对于外网流量,其源 IP 本身就处于网络外部,必然会经过再次经过路由器返回。

于是联系管路由器的小明,请他不要偷懒,规则配置的细致一点,不要做无差别的源地址转换。即

  1. 对内网接口流量进行源地址和目标地址转换
  2. 对外网流量只进行目标地址转化。

重新测试。终于输出实际了客户 PC 实际 ip 地址 0.0.0.0

原文链接:https://www.cnblogs.com/uncleguo/p/18347117

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上遇见你 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 获取客户端真实 IP
    • 编程实现
      • Nginx 头部注入
        • Keepalived 负载均衡模式
          • 路由器端口回流(Hairpin NAT)
          相关产品与服务
          负载均衡
          负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档