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

来自客户端PC的流量路径如上,在这样的拓扑中,在应用服务中取得,客户端PC的外网ip,可能会遇到哪些问题呢?(ip 编的随意,为便于说明,不考虑合理)。
- 编程实现
Java 为例,这个我会。
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 中配置。
server {
...
proxy_set_header X-Real-IP $remote_addr;
在应用层http协议中,加一个http header。X-Real-IP:$remote_addr. $remote_addr 是一个预设变量,代表所代理转发请求的原始源ip地址。
在Java 程序中,读取对应的附加信息
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 的负载均衡支持三种路由模式,NAT,Direct Routing 和 Tunneling。
NAT 模式,会修改源IP,出入流量都会经过负载均衡器。而DR模式,会直接修改MAC地址,那回程流量就不再经过负载均衡器了,也就意味这种模式,源地址不会被修改,回程流量会直接发送给源ip地址。
DR模式有个要求,就是负载均衡器需要能知道后端服务的MAC地址,这是依赖于ARP实现的,也就是,要求负载均衡器和后端服务器在同一广播域。恰好我门可以满足。于是。
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
OK,一波三折,跌宕起伏,写到这里~
获取客户端真实IP备忘的更多相关文章
- 获取客户端真实ip
// 获取客户端真实ip() protected function getIP() { global $ip; if (getenv("HTTP_CLIENT_IP")) $ip ...
- Java Web 获取客户端真实IP
Java Web 获取客户端真实IP 发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限定的时候,需要首先获取该真实的IP.一般分为两种情况: 方 ...
- 获取客户端真实IP地址
Java-Web获取客户端真实IP: 发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限定的时候,需要首先获取该真实的IP. 一般分为两种情况: ...
- nginx 代理模式下,获取客户端真实IP
最近做博友推荐,发现个小问题,用$_SERVER['REMOTE_ADDR'];得到的都是服务器的地址192.168.96.52,搜索了一下,发现问题,改为$_SERVER['HTTP_X_REAL_ ...
- Java 获取客户端真实IP地址
本文基于方法 HttpServletRequest.getHeader 和 HttpServletRequest.getRemoteAddr 介绍如何在服务器端获取客户端真实IP地址. 业务背景 服务 ...
- Nginx反向代理后应用程序获取客户端真实IP
Nginx反向代理后,Servlet应用通过request.getRemoteAddr()取到的IP是Nginx的IP地址,并非客户端真实IP,通过request.getRequestURL()获取的 ...
- 伪造IP及获取客户端真实IP地址
Fiddler支持自定义规则,可以实现对HTTP请求数据发送给Server前或HTTP应答数据发送给浏览器前进行修改.下面的例子将演示如何向所有HTTP请求数据中增加一个头.1)打开Fiddler,点 ...
- Kubernets中获取客户端真实IP总结
1. 导言 绝大多数业务场景都是需要知道客户端IP的 在k8s中运行的业务项目,如何获取到客户端真实IP? 本文总结了通行的2种方式 要答案的直接看方式一.方式二和总结 SEO 关键字 nginx i ...
- Tomcat 8.5中获取客户端真实IP及协议
获取客户端真实IP ServletRequest接口提供了getRemoteAddr方法用于获取客户端IP,但是当客户端通过代理服务器访问后端服务器的时候,服务器调用getRemoteAddr方法会返 ...
- 某云负载均衡获取客户端真实IP的问题
某云负载均衡真实IP的问题,我们这边已经遇到过两次了.而且每次和售后沟通的时候都大费周折,主要是要给售后说明白目前文档的获取真实IP是有问题的,他们觉得文档上说明的肯定没问题,售后要是不明白,他们不会 ...
随机推荐
- spring之NamedParameterJdbcTemplate返回自增列值
以前使用JdbcTemplate来获取自增列的值,现在发现NamedParameterJdbcTemplate也可以,而且后者大部分情况下,其实更加方便. 这种方便主要是在于代码维护方面:我们更加习惯 ...
- 15-容器简介与Docker安装
传统企业应用构建 应用被直接部署在操作系统之上,并且操作系统直接安装于硬件之上应用被操作系统绑定 操作系统被硬件绑定 缺点 部署非常慢(启动应用的速度非常慢) 成本非常高(原有服务器的操作系统不支持这 ...
- FFmpeg开发笔记(三十二)利用RTMP协议构建电脑与手机的直播Demo
不管是传统互联网还是移动互联网,实时数据传输都是刚需,比如以QQ.微信为代表的即时通信工具,能够实时传输文本和图片.其中一对一的图文通信叫做私聊,多对多的图文通信叫做群聊. 除了常见的图文即时通信,还 ...
- LLM推理 - Nvidia TensorRT-LLM 与 Triton Inference Server
1. LLM部署-TensorRT-LLM与Triton 随着LLM越来越热门,LLM的推理服务也得到越来越多的关注与探索.在推理框架方面,tensorrt-llm是非常主流的开源框架,在Nvidia ...
- 在 VSCode 中编写 Markdown 的进阶指南
最新版的 Visual Studio Code 对 Markdown 的支持已显著提升,其在预览方面的体验甚至可以与 Markdown Preview Enhanced 插件相比.本文将介绍一些优化方 ...
- 使用docker搭建ELK分式日志同步方案
ELK作为业界最常用日志同步方案,我们今天尝试一下使用docker快速搭建一套ELK方案.ELK使用国内加速源拉取的镜像比较旧,有条件的朋友可以拉取官网的源.elasticsearch作为日志储存库( ...
- 使用Sequelize
访问MySQL 当我们安装好MySQL后,Node.js程序如何访问MySQL数据库呢? 访问MySQL数据库只有一种方法,就是通过网络发送SQL命令,然后,MySQL服务器执行后返回结果. 我们可以 ...
- yb课堂之订单列表接口开发 《十七》
订单列表接口开发 VideoOrderController.java VideoOrderService.java VideoOrderServiceImpl.java VideoOrderMappe ...
- 一套基于 Ant Design 和 Blazor 的开源企业级组件库
前言 今天大姚给大家分享一套基于Ant Design和Blazor的开源(MIT License).免费的企业级组件库(喜欢Ant Design风格的同学推荐使用):Ant Design Blazor ...
- Curve 进入 CNCF Sandbox,完善统一云原生开源存储拼图
2022 年 6 月 15 日,云原生计算基金会 (CNCF) 宣布,分布式存储系统 Curve 被正式接纳为 CNCF 沙箱(Sandbox)项目.Curve 由网易数帆开源,提供块存储和文件存储能 ...