[原创]分析解决lvs fullnat模式下后端服务器获取真实IP地址异常问题

简介: 分析解决lvs fullnat模式下少量的请求记录client IP不是用户真实的IP地址问题.

摘要

分析解决lvs fullnat模式下少量的请求记录client IP不是用户真实的IP地址问题.

原创文章:来自分析lvs fullnat模式下后端服务器获取真实IP地址异常问题

问题背景

lvs fullnat模式下观察后端服务器realserver http/https业务运行系统日志,有时候可以发现有少量的请求记录的client IP不是用户真实的IP地址(存在但出现的概率很小,增加了问题排查的难度),而是属于lvs主机私有的IP地址。关于fullnat的简介可以参考http://www.baokaijian.com/?tag=fullnat, 这里借用一下文中的图片。
1

问题分析

大多数TCP正常建立连接过程
2.png

realserver linux系统属于用户自属管理默认配置MTU=1500、MSS=1460,在TCP建连时可以通过syn+ack报文通告给client,而client端可属于移动端或PC端MTU、MSS的差异性较大,通过syn报文通告给realserver端,从而建立的TCP连接取两者之小值做为链路的MSS。中间的LVS只是做连接与报文转发的负载均衡。

fullnat 模式下获取真实IP地址的原理:
开源的lvs fullnat代码与patch可查阅lvs官方网址http://kb.linuxvirtualserver.org/wiki/IPVS_FULLNAT_and_SYNPROXY

lvs fullnat应用模式通过TCP三次握手的ack,ack+data报文将client端的真实IP地址插入到tcp options中从而带给后端的realserver。而后端realserver toa内核模块中通过hook了tcp_v4_syn_recv_sock()函数,然后调用get_toa_data()从tcp_options中取得客户端的真实IP地址,具体的调用过程下边分析。

非标准的tcp三次握手情况分析

先来看以下几个典型的报文序列是否可以建立三次握手:

  1. 乱序的情况(第三个ack和第四个ack+data报文乱序)
    3.png
  2. 直接使用ack+data报文(或含有push标志)不存在bare ack报文

4.png

3.直接使用fin+ack+data报文( 或含有push标志)

5.png

来点内核TCP代码中深度分析:

看一下建立TCP连接时两次经过的tcp_v4_hnd_req函数流程.

syn报文到来时的处理流程:

tcp_v4_rcv() -->tcp_v4_do_rcv()-->tcp_v4_hnd_req()

查找代表客户端连接的sock结构,如果没有找到则返回代表服务器的sock结构, 然后在tcp_rcv_state_process()函数中调用tcp_v4_conn_request()创建req_sock请求结构并放入半连接队列。

握手阶段收到第三个ack报文或其他携带ACK标志的报文处理,tcp_v4_hnd_req()函数将返回代表连接请求的sock结构.

tcp_v4_rcv() ->tcp_v4_do_rcv()-->tcp_v4_hnd_req()-->tcp_check_req()-->tcp_v4_syn_recv_sock()

tcp_v4_syn_recv_sock()函数中将对报文序列号判断,并完成握手,最后将连接请求从半连接请求移到全连接队列,等待应用层调用accept。

特别注意在tcp_check_req()函数中检查了clinet端到来报文的序列号

/* RFC793 page 36: "If the connection is in any non-synchronized state ...
 * and the incoming segment acknowledges something not yet
 * sent (the segment carries an unacceptable ACK) ...
 * a reset is sent."
 *
 * Invalid ACK: reset will be sent by listening socket
 */
    if ((flg & TCP_FLAG_ACK) &&// 正常情况下 收包的ack_seq = 初始发送的seq + 1(syn)
        (TCP_SKB_CB(skb)->ack_seq !=    //此报序列号不等于发送初始序列号         tcp_rsk(req)->snt_isn + 1 + tcp_s_data_size(tcp_sk(sk))))
        return sk;

结论: 完成握手最后一个clinet端到来报文ack序号必须是client 端tcp连接ISN初始序号+1(没有检查seq序列号),所以只要符合此规则的都可以通过,然后调用调用tcp_v4_syn_recv_sock()从而调用toa模块取client IP tcp options。再解释明白点: 非bare ack乱序报文即带有数据的ack报文,也可以通过。注:至于seq序列号有效性以及影响能不能真正完成连接建立,这是在后续的tcp_ack()中判断了。

接着tcp_v4_syn_recv_sock()函数中创建代表与client端连接的sock结构(设置此条TCP连接状态是TCP_SYN_RECV),然后在tcp_rcv_state_process()函数中case TCP_SYN_RECV代码段中通过tcp_set_state(sk, TCP_ESTABLISHED); 改状态为ESTABLISHED连接状态。

经过在toa模块中探点调试发现:

  1. 从realserver端看到大量使用FIN+ACK+PUSH的含数据报文完成三次握手,这时也不能插入toa options(用户业务很多用http/https这种post请求推数据,realserver收到后会回复response,但若是反向代理的proxy收到后会FIN掉client,同时向后端realserver再发RST报文)
  2. 上述图中因乱序或直接ack+data报文没超但达到了1444字节(假如toa需要16字节); 使用ack+data报文完成了三次握手, 使lvs层不能插入用户的IP:PORT

改进建议

官方lvs fullnat代码:
http://kb.linuxvirtualserver.org/wiki/IPVS_FULLNAT_and_SYNPROXY
Lvs-fullnat-synproxy.tar.gz 包中包含fullnat 和toa的patch.

1、 lvs代码tcp_fnat_in_handler()函数中去掉!tcph->fin的条件判断,可以解决FIN+PUSH+ACK+数据报文中不能携带client端ip与端口的缺陷

6

2、 后端RS建立监听时直接使用 setsockopt, 将MSS改小(如toa需要16字节则可减小到1444字节), 从而client到来的报文可以有插入clientip:port的空间

3、 (第2条的替代方法但会增加lvs处理的复杂性:
TCP握手阶段lvs收到后端RS发送的syn+ack报文时,将其中的MSS改小(如16字节),传给client, 从而client到来的报文就有了插入clientip:port的空间。
另一种方法虽然client发送来的报文已经达到了client与RS协商的MSS(如1460), 但若lvs与RS之间设置的MTU(如9000)要大于client与LVS之间的值1460, 并且tcp options有足够的空间可以强制在tcp options中插入clientip:port)

4、以上修改后最后还是不行(可能存在非标准的client tcp处理),建议客户端程序(特别是手机APP)建立socket时使用 setsockopt 将MSS改小适当的数值(发现存在有部分终端出现通告给其MSS是1444,但仍然发送1460的报文)

备注:

  1. tcp options满载40字节,如果在握手时用户有插入自己的数据占满则lvs判断没有空间再插入用户IP了。
  2. 发现手机终端的MSS值多种多样,这里列里几个值, 1412 1120 1452 1400 1394 1260 1460
相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
758 4
|
程序员
后端|一个分布式锁「失效」的案例分析
小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?
275 2
|
弹性计算 运维 数据安全/隐私保护
云服务器 ECS产品使用问题之如何更改服务器的IP地址或端口号
云服务器ECS(Elastic Compute Service)是各大云服务商阿里云提供的一种基础云计算服务,它允许用户租用云端计算资源来部署和运行各种应用程序。以下是一个关于如何使用ECS产品的综合指南。
|
移动开发 开发框架 小程序
开发H5程序或者小程序的时候,后端Web API项目在IISExpress调试中使用IP地址,便于开发调试
开发H5程序或者小程序的时候,后端Web API项目在IISExpress调试中使用IP地址,便于开发调试
|
前端开发 JavaScript Java
导出excel的两个方式:前端vue+XLSX 导出excel,vue+后端POI 导出excel,并进行分析、比较
这篇文章介绍了使用前端Vue框架结合XLSX库和后端结合Apache POI库导出Excel文件的两种方法,并对比分析了它们的优缺点。
3095 0
|
前端开发 NoSQL 数据库
部署常用的流程,可以用后端,连接宝塔,将IP地址修改好,本地只要连接好了,在本地上前后端跑起来,前端能够跑起来,改好了config.js资料,后端修改好数据库和连接redis,本地上跑成功了,再改
部署常用的流程,可以用后端,连接宝塔,将IP地址修改好,本地只要连接好了,在本地上前后端跑起来,前端能够跑起来,改好了config.js资料,后端修改好数据库和连接redis,本地上跑成功了,再改
|
存储 NoSQL Redis
大事件后端项目33_登录优化-redis_思路分析
大事件后端项目33_登录优化-redis_思路分析
|
前端开发 JavaScript 中间件
基于最新koa的Node.js后端API架构与MVC模式
基于最新koa的Node.js后端API架构与MVC模式
768 1
|
缓存 NoSQL 中间件
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?epoll、poll和select + Reactor模式
【5月更文挑战第19天】`epoll`、`poll`和`select`是Linux下多路复用IO的三种方式。`select`需要主动调用检查文件描述符,而`epoll`能实现回调,即使不调用`epoll_wait`也能处理就绪事件。`poll`与`select`类似,但支持更多文件描述符。面试时,重点讲解`epoll`的高效性和`Reactor`模式,该模式包括一个分发器和多个处理器,用于处理连接和读写事件。Redis采用单线程模型结合`epoll`的Reactor模式,确保高性能。在Redis 6.0后引入多线程,但基本原理保持不变。
332 2
|
网络协议
【qt】TCP的监听 (设置服务器IP地址和端口号)
【qt】TCP的监听 (设置服务器IP地址和端口号)
1198 0