记一次周期性connect timed out问题的定位

背景:总是出现周期性地调用openstack接口connect timed out报错,调用方和接收方都说不是自己的问题,怀疑是对方的问题,争执不休,只能抓包看了。

抓包小技巧

抓包命令很简单,tcpdump -i eth0 -w openstack.cap。

由于问题是周期性出现的,不知道抓包要多久,抓到的包可能非常大,所以指定50M大小的包,查了官方文档来抓包,tcpdump -i eth0 -s0 -C 50 -w openstack.cap,分别在调用openstack的客户端以及和opensatck服务端上相应网口抓包。

如果磁盘空间有限制,如果抓包只能是10个G的空间,按照50M大小的包来算,只能抓204个包,tcpdump -i eth0 -s0 -C 50 -c 204  -w  openstack.cap。

如果ssh客户端老是退出,可以启动在后台工作,nohup tcpdump -i eth0 -s0 -C 50 -c 204  -w  openstack.cap &。

如果需要抓某个时间段内的包,可以定时抓包,使用crontab。crontab  -e像vim一样编辑和保存定时任务列表。比如在每天的凌晨2点半开始抓包,抓1个50M大小的包如果需要30分钟,又想在9点时间之前将抓包命令停掉。

30  2  *  *  *  nohup tcpdump -i eth0 -s0 -C 50 -c 204  -w  openstack.cap &

0  9  *  *  *  pid=` ps -ef | grep tcpdunp `;for i in $pid; done echo $pid ; kill -9 $pid

抓包结果

抓到的包有200个,怎么找相应的报错信息呢?整体上从抓包中Ctrl+F查找package detail中相应时间点或报错字符,告诉你两个方法:

1) 从业务角度出发,看下业务日志中是否有相应的报错信息,然后在合适的抓包文件(每50M大小的包是有最后写文件的时间)中找出相应的时间点和接口URL。

2) 在合适的抓包文件(每50M大小的包是有最后写文件的时间)中找出相应报错信息,比如timed out。

找到报错信息后,可以右击某条记录,选择Fellow TCP flow,幸运的话,可以看到一次TCP从建接到结束的整个过程,会看到TCP3次握手和TCP四次挥手过程。

TCP第三次握手失败

看了以上抓包信息后,TCP第3次握手失败,这是为什么呢?(可以自行脑补下什么是TCP三次握手呢?)

从上面可以看出,当客户端收到SYN+ACK后,会发出ACK给服务器,客户端就认为连接上了服务器。 一般情况下,服务器会收到客户端的ACK,服务器认为自己连接上了客户端。不过,也有可能服务器并没有收到ACK。这时候,客户端认为自己连上了服务器,而服务器认为自己并没有连上客户端。客户端的动作,接下来可能会直接发送数据给服务器,就像上图中的PSH+ACK。服务器可能会收到PSH+ACK,也可能没有收到。但不管如何,服务器此时的状态还是SYN_RECV。服务器不会进行任何动作,除了催促客户端”我还需要一个ACK"。因此,3秒钟后,服务器会重新发送SYN+ACK给客户端。若6秒后还没有得到ACK,服务器会再次发送....上面的客户端发送了PSH+ACK,有可能会得到服务器的回应ACK+SYN,也有可能没有(比较ACK+SYN需要3秒的超时时间)。不管如何,客户端希望服务器回复的是指定序号的ACK。客户端也会设定一个超时定时器,它会再次发送PSH+ACK来提醒服务器“我需要你的ACK来确认你是否收到数据”

现在问题清楚了,由于第三次握手失败,导致服务器的状态不对。客户端虽然认为自己已经建立了连接,但是数据发送不到服务器。 可是为什么会这样?

客户的网络结构和链接提到的只是有点不一样(请求和响应的路不一样):

对于服务器来说,它的路由ICMP的重定向包修改。因此,每次发往客户端的数据包都不会经过路由器。而客户端发给服务器的数据包每次都会经过路由器。

路由器上的防火墙再转发第3次握手的SYN包时,检查到服务器并没有将第2次握手SYN+ACK包,认为客户端的第3次握手的SYN是无效的,从而并没有转发这个数据包。导致服务器收不到第3次握手无法建立连接。

解决办法是路由器禁止掉ICMP重定向,或者服务器忽略掉路由器的ICMP重定向请求。

本案例来源于:https://blog.csdn.net/king523103/article/details/47776933

脑补下TCP三次握手和TCP四次挥手
TCP三次握手+TCP四次挥手

TCP三次握手建立连接

第1次握手:客户端发送syn包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认;

第2次握手:服务器收到syn包,必须确认客户端的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第3次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成3次握手。

你说建立个连接为啥要发3次请求呢?我想是这样的,比如你要给大山那头喊话,你一喊话,山那头就能收到你的话嘛?假设山那头碰巧收到你的话,也回了话,说我在线等你哈。假设你收到山那头的话,肯定也会回话,我也在线了。这就是整个过程,哪一环出了问题,连接就建立不起来:1) 服务端没收到第1次握手,建立不起来;2) 即使服务端收到了第1次握手但客户端又没有收到第2次握手,建立不起来;3) 即使服务端没收到第3次握手,连接也建立不起来。当然了,每次握手如果失败了,发送方会不断重试的,见抓包中的TCP Retransmission,每次默认30秒的重试时长。

理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

TCP四次挥手断开连接

第1次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接收数据。

第2次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。

第3次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。

第4次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成4次挥手。

你说建立个连接要3次,断开连接为何要4次呢?双方建立连接后,如果有任意方要断开连接,这个时候不传输数据,双方协商下断开也就是了。想断开的一方其实是不想再发数据给对方了,但不代表不收数据了,当然这个时候如果之前发送的数据失败了,还是会重新发出去的,这也是第1次挥手的情形。当对端收到断开连接的请求时可能会接着发生数据,发送结束后也会给对方说断开连接吧,这个时候不代表不收数据了,前发送的数据失败了还是会重新发出去的,这也是第2次挥手的情形。当想断开一方收到对端也要断开连接了,自己发送的所有数据也发送成功了,发送确认我再也不给你发消息了,但还可以收。当对端收到想断开一方不再发送消息了,自己发送的数据也确认发生完了,那就彻底断开连接了,byebye您那。

参考资料

http://man.he.net/?topic=tcpdump§ion=all

https://www.jianshu.com/p/347634e92f92

https://baijiahao.baidu.com/s?id=1618114723935605183&wfr=spider&for=pc

你可能感兴趣的:(记一次周期性connect timed out问题的定位)