关于Linux内核4.12之前版本中, tcp_tw_recycle开启后NAT环境总是出问题的分析

  问题出现的场景很简单,nat网关下,有几台服务器,需要访问企业内部的某个的API服务器, API服务器上rcycle设置为1(4.12内核版本之前有这个设置,之后这个属性取消了,理论上也不会出现这种问题了),就在NAT下客户端并发量比较大的情况下,出现连接不上的情况(应该是SYN后,没有收到SYN ACK,连接被丢弃)。
特意深入了解了TCP的协议,进行问题分析。
  首先,出现这种问题,是因为client机器的tcp_timestamps属性开启了(这里其实只要client开启了这个,服务端开不开tcp_timestamps,在传输中,在tcp协议的option字段下,会追加timestamp,这个属性其实比较有用,所以系统是默认开启的),同时,API服务器开启了 tcp_tw_recycle属性导致的。
  先说明一下tcp_timestamps属性的作用。
内核协议栈中,有个PAWS的检查(好像是防止SEQ回绕),这个出现这个检查的原因是因为在每个TCP链接中,有一个SEQ序号的问题,这个需要在传输中是要递增的,如果一个长连接,不停的在传输数据,会导致SEQ一直增加,会出现增加到MAX的时候(忘了SEQ是几个字节,但总会出现所有位都是1的情况),再+1,就会又从0开始了,这样CLIENT和SERVER交互的时候,就会出现两边找不到对应SEQ的情况,所以,增加了时间戳,哪怕SEQ对不上,但只要后面的数据包的时间戳比之前的大,那这个数据包就是有效的,不会被丢弃。
上面只是简单描述,实际上并不如此简单,只是说明,内核会增加这么一个检查,默认检查的是四元组的连接,并且,内核中,也有这种进出连接的缓存,方便快速检查。后面会针对PAWS检查,做更详细的文章。)
 
 但是,如果服务器上开启了 tw_recycle,则会增加一个 per-host的PAWS检查,从名字上看,就是每主机检查,这里的“每主机”,却只通过ip地址来限定主机了,这就会导致NAT后面的服务器,如果时间不同步(哪怕时间同步了,依然会有很大概率时间戳上出问题),会导致连接API服务器的时候,会出现某些连接被丢弃的情况,就是因为从API服务器上看,NAT后面这些服务器发起的连接,被API服务器认为是一台主机发起的,这样子,再做timestamp检查的时候,出问题的概率就大了。
从协议栈的源码,很容易能看出些问题来。
找了内核3.10版本的源码来分析,
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb){ 函数中,有对应的检查逻辑。
isn = TCP_SKB_CB(skb)->when;
if(!isn){
......
if (tmp_opt.saw_tstamp && tcp_death_row.sysctl_tw_recycle && (dst = inet_csk_route_req(sk, &fl4, req)) != NULL && fl4.daddr == saddr) { if (!tcp_peer_is_proven(req, dst, true)) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED); goto drop_and_release; } } 

 

补充:这里有条件 !isn, 没有特别懂这个isn是干嘛的,(如果非要牵强的解释,貌似isn是上面提到的初始化序列号, 但是吧,又觉得不太对,因为tcp_skb_cb里when后面的解释是用来计算 RTT的(路由往返时间)感觉这东西跟时间戳有关??(当然,在connect中,SEQ非要算时延,也是可以的吧? +1 = 4MS,不过乱糟糟的,wireshark里面有个raw_seq,又不知道是咋整出来的?所以理论上,不为0的可能性很大,所以这个判断条件,是会命中的),不过肯定不是IS SYNC标识,因为 TCP_SKB_CB中,有个TCP_FLAGS的属性,这个应该是那些各种SYN ACK FIN之类的标志

这里可以大概看出来,如果数据包中的option属性中,包含了tstamp(也就是如果client如果开了时间戳),且本地(服务端)tw_recycle属性也开启了,就从 inet_csk_route_req中取出  f14, 这里的inet_csk_route_req,有介绍说是出路由缓存,大概意思就是跟本次连接有关的上次传输(或者连接???)的信息吧??

然后紧接着比较了f14的目标IP和本次请求的来源IP, 按照出路由,那么目标IP肯定是NAT的外网IP, 而本次请求的来源IP,自然也是NAT的IP

那么,只看IP地址相同,就会进入tcp_peer_is_proven,这里面会检查时间戳,自然就会出现如果时间戳有差异,会被API服务器丢弃连接的情况了。

这个问题,其实很多人都在网上有分析过,但是,看了不少文章,很少有人把per-host PAWS检查明确的解释为只检查两次连接的IP,就会继续检查时间戳。

当然,这个问题,也是因为自己对内核的一些逻辑实在不清楚导致的,也许大家的知识储备中,对这种情况十分清楚,导致文章里,也仅是一笔带过。

这个分析里,依然很多细节都不清楚:

 PAWS具体是为了解决什么问题?

 SEQ到底如何递增到最大值?

 INET_CSK_ROUTE_REQ函数的具体功能和逻辑是什么?

这些留待之后有时间,再继续深入了。

posted @ 2024-01-29 10:04  姜大伟  阅读(52)  评论(0编辑  收藏  举报