免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 17352 | 回复: 7
打印 上一主题 下一主题

[网络子系统] <协议栈 已解决 >服务器连接老是处于SYN_RECV状态 (附目前数据分析) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-12-11 23:23 |只看该作者 |倒序浏览
本帖最后由 becomefish 于 2012-12-26 23:32 编辑

工作职位的性质要求我们跟踪各种故障,上到应用下到驱动,也就是传说中的门门懂,样样瘟。

这不,最近又出现了一个上层ftp连接不上的问题,折腾的够呛。

服务器环境是x86_64, linux kernel 2.6.21.7,  ipv4

windows客户端,通过tcp命令登录服务器,发现连接不上。


于是,通过在服务器端tcpdump抓包,发现在TCP的三次握手过程中出现了灵异现象。
服务器在收到来自客户端的ACK报文后,并没有建立最终连接ESTABLISHED,
而是再次发送SYN+ACK报文,要求客户端响应,并且是每隔4秒,7秒,13秒给客户端发SYN+ACK报文。
而客户端每次也给予了ACK回复,但服务器似乎没有收到ACK一样,一直这么
发呀发(最多发5次就断开)



于是干脆直接在x86_64服务器上,通过telnet 本地ip 21的方式来尝试登录ftp,发现仍然登录不上。
但奇怪的是,telnet 23端口是可以连接上的。

telnet 本地ip 21端口登录不上时,netstat -ta显示, 客户端连接已经是ESTABLISHED状态,
但服务端连接仍一直处于SYN_RECV状态。

# netstat -ta
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 (null):8899             (null):*                LISTEN      
tcp        0      0 (null):8900             (null):*                LISTEN      
tcp        4      0 (null):65101            (null):*                LISTEN      
tcp        0      0 (null):10000            (null):*                LISTEN      
tcp        0      0 (null):65201            (null):*                LISTEN      
tcp        6      0 (null):21               (null):*                LISTEN      
tcp        0      0 (null):21               (null):48497            SYN_RECV     telnet 21 服务端
tcp        0      0 (null):22               (null):*                LISTEN      
tcp        0      0 (null):23               (null):*                LISTEN      
tcp        6      0 (null):65021            (null):*                LISTEN      
tcp        0      0 (null):10000            (null):52008            ESTABLISHED
tcp        0      0 (null):65101            (null):1901             ESTABLISHED
tcp        0      0 (null):23               (null):1696             ESTABLISHED
tcp        0      0 (null):10000            (null):43797            ESTABLISHED
tcp        0      0 (null):22               (null):3325             ESTABLISHED
tcp        0      0 (null):22               (null):4323             ESTABLISHED
tcp        0      0 (null):65101            (null):3803             ESTABLISHED
tcp        0      0 (null):48497            (null):21               ESTABLISHED   telnet 21 客户端
tcp        3      0 (null):65101            (null):41294            CLOSE_WAIT  
tcp        1      0 (null):65101            (null):41295            CLOSE_WAIT  
tcp        0      0 (null):22               (null):52921            TIME_WAIT   
tcp        0      0 (null):43797            (null):10000            ESTABLISHED
tcp        1      0 (null):65021            (null):36673            CLOSE_WAIT  
tcp        1      0 (null):65021            (null):36672            CLOSE_WAIT  
tcp        0      0 (null):22               (null):4218             ESTABLISHED
tcp        1      0 (null):65021            (null):4415             CLOSE_WAIT  
tcp        0      0 (null):52008            (null):10000            ESTABLISHED
tcp        1      0 (null):65021            (null):4408             CLOSE_WAIT  
tcp        0     52 (null):22               (null):4700             ESTABLISHED
tcp        0      0 (null):8899             (null):4871             ESTABLISHED
tcp        1      0 (null):21               (null):45831            CLOSE_WAIT  
tcp        1      0 (null):21               (null):48192            CLOSE_WAIT  
tcp        0      0 (null):8899             (null):3952             ESTABLISHED
tcp        1      0 (null):21               (null):57252            CLOSE_WAIT  
tcp        1      0 (null):21               (null):37330            CLOSE_WAIT  
tcp        1      0 (null):21               (null):34007            CLOSE_WAIT  
tcp        1      0 (null):21               (null):45023            CLOSE_WAIT  


可悲的是,这个故障在我折腾了一会后(不停的telnet ip 21) ,竟然消失了,ftp也能连接,netstat看到的状态
也要么是ESTABLISHED ,要么是LISTEN, 腰不也疼了,腿不也抽筋了。而且,随便我再怎么搞,这个
故障也不复现了。


只好开始了艰苦卓绝的代码学习。对于一个之前只在上学时接触过一点jsp网页编程(和网络沾点边)的人来说,
TCP握手的代码实现,实在让人精疲力尽。

下面的流程是我的一些学习理解,以及对这个故障的分析:

1) 首先,SYN_RECV状态的连接是怎么来的?
   当服务器端收到来自客户端的ACK报文,并正确解析后,会新生成一个连接,并初始化此连接的状态为SYN_RECV。
   然后这个连接被放在服务器端的已完成连接队列里,等待服务器用户程序accept来取走。

2) 那什么时候,这个新SYN_RECV状态的连接会被置状态为ESTABLISHED?
   当这个新生成的连接,继续往下走,一切顺利的话
   会置ESTABLISHED(请原谅我用这么屌丝的描述,因为我实在搞不清这个流程)


来看代码:
  1.         if (sk->sk_state == TCP_LISTEN) {
  2.             //1 . 生成一个新连接,此连接初始化为SYN_RECV ,这里走到了
  3.                 struct sock *nsk = tcp_v4_hnd_req(sk, skb);
  4.                 if (!nsk)
  5.                         goto discard;

  6.                 if (nsk != sk) {
  7.                   //2. 将新连接设置为ESTABLISHED  ,这里没有设置成功
  8.                         if (tcp_child_process(sk, nsk, skb)) {
  9.                                 rsk = nsk;
  10.                                 goto reset;
  11.                         }
  12.                         return 0;
  13.                 }
  14.         }
复制代码
从上面流程看出, 服务器至少在步骤1是成功设置了SYN_RECV状态的,不幸的是服务器
可能在步骤2出了问题,甚至说都没有走到步骤2。从而导致了长时间处于SYN_RECV状态。

从刚才的分析看出,服务器直到这个路径,应该都是一切工作正常的:
  1. s)  tcp_v4_do_rcv-->

  2. a)  tcp_v4_hnd_req-->

  3. b)  tcp_check_req(检查报文是否有效)-->

  4. c)  tcp_v4_syn_recv_sock(报文字段有效,准备生成新连接)-->

  5. d)  tcp_create_openreq_child(生成一个处于SYN_RECV的新连接)
复制代码
来反推一下:

      
1. 根据代码逻辑,d步骤的tcp_create_openreq_child函数一定不返回空(否则就不会有处于SYN_RECV状态的连接了)
  从而newsk一定不为空
  1.   newsk = tcp_create_openreq_child(sk, req, skb);
  2. if (!newsk)
  3.   goto exit;
复制代码
2. 因为newsk不为空,c步骤的tcp_v4_syn_recv_sock函数也不会返回空,
因此child也不会为空:
  1.    child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,
  2.          req, NULL);
  3.     if (child == NULL)
  4.    goto listen_overflow;
复制代码
3. 因为child不为空,所以b步骤的tcp_check_req函数里, 不会走到listen_overflow,
    于是这个新连接一定会被加入到服务器的连接完成队列里,等待用户态程序通过accept去获取
     所以a步骤的tcp_v4_hnd_req函数,应该是正确返回了的(这个其实我没底气)

4. 推断,那只能是tcp_child_process 函数 里没有走到设置 ESTABLISHED 状态
  1.     int tcp_child_process(struct sock *parent, struct sock *child,
  2.         struct sk_buff *skb)
  3. {
  4. int ret = 0;
  5. int state = child->sk_state;

  6. if (!sock_owned_by_user(child)) {
  7.   ret = tcp_rcv_state_process(child, skb, skb->h.th, skb->len); //设置ESTABLISHED 状态

  8.   /* Wakeup parent, send SIGIO */
  9.   if (state == TCP_SYN_RECV && child->sk_state != state)
  10.    parent->sk_data_ready(parent, 0);
  11. } else {
  12.   /* Alas, it is possible again, because we do lookup
  13.    * in main socket hash table and lock on listening
  14.    * socket does not protect us more.
  15.    */
  16.   sk_add_backlog(child, skb);
  17. }

  18. bh_unlock_sock(child);
  19. sock_put(child);
  20. return ret;
  21. }
复制代码
5. 此时这个新连接套接字,应该是没有人持有的,所以会进入tcp_rcv_state_process

6.  推断,tcp_rcv_state_process没有走到最终设置ESTABLISHED


可是这个tcp_rcv_state_process函数是个巨无霸,我没有信心继续往下看了。

请问这里的对协议栈略懂或者不懂的同仁,能否给点意见或鼓励,你们是否有遇到过服务器连接长时间处于SYN_RECV状态的
情况?
如果有,可能会是什么原因?
如果没有,能否推荐一些排查网络问题的手段?

谢谢


论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
2 [报告]
发表于 2012-12-12 07:49 |只看该作者
回复 1# becomefish
可是这个tcp_rcv_state_process函数是个巨无霸,我没有信心继续往下看了。

请问这里的对协议栈略懂或者不懂的同仁,能否给点意见或鼓励,你们是否有遇到过服务器连接长时间处于SYN_RECV状态的
情况?
如果有,可能会是什么原因?

首先关于第一点,如果想解决问题的话,对于一个区区200多行的函数,即使跟踪进去也就是1000行多点的代码,这都静不下心来看的话,以后遇到问题你还是不能解决。

关于服务器端处于syn_recv的状态,但是client端处于established的状态,原因就是服务器端应用层accpet太忙,导致acceptd队列满所致。

   

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
3 [报告]
发表于 2012-12-12 08:08 |只看该作者
回复 1# becomefish
6.  推断,tcp_rcv_state_process没有走到最终设置ESTABLISHED

你的推断基本正确,但是最后却走错的函数。
服务器端收到ack的处理流程:
tcp_v4_do_rcv--->tcp_v4_hnd_req--->tcp_check_req--->inet_csk(sk)->icsk_af_ops->syn_recv_sock
这里对应的tcp协议栈就是tcp_v4_syn_recv_sock
在这个函数一共不到100行,在最开始的地方就是:
  1. if (sk_acceptq_is_full(sk))
  2.         goto exit_overflow;
复制代码
BTW:其实你距离分析出最后的原因就差一点,可惜放弃了。。。


   

论坛徽章:
0
4 [报告]
发表于 2012-12-12 09:34 |只看该作者
如果是因为a c ce p t队列过长引起丢弃,
那服务器不应该一直出现SYC_RECV状态的连接吧。
因为tcp_v4_syn_recv_sock如果刚开始
就goto exit_overflow的话,就不会走到
tcp_create_openreq_child去创建
SYC_RECV状态的连接了。

论坛徽章:
0
5 [报告]
发表于 2012-12-17 16:06 |只看该作者
本帖最后由 Modifix 于 2012-12-17 16:06 编辑

父sock应该一直处于TCP_LISTEN状态,当收到SYN报文后,会在request_sock_queue中添加一个request_sock,然后发送SYN+ACK。

在收到ACK包后,查找到request_sock,tcp_check_req--->inet_csk(sk)->icsk_af_ops->syn_recv_sock,跟据父sock及request_sock调用tcp_create_openreq_child创建子sock,并置子sock状态为SYN-RECEIVE。

同楼主所说,goto exit_overflow的话还未走到tcp_create_openreq_child。


请书香兄解释详细点,为吾等解惑,3Q!!

BTW,我看的内核版本是2.6.32.60

论坛徽章:
0
6 [报告]
发表于 2012-12-26 23:31 |只看该作者
to Modifix
一直没上来回复这个帖子。
版主说的对,确实是因为accept不及时的问题。我通过gdb attach ftp服务器,再在客户端通过ftp连接超过listen上限,可复现这个问题。
我们之前的理解出入在于,
通过netstat 看到的套接字状态,并不是
sock->sk_state字段,而是另外一个字段。

论坛徽章:
0
7 [报告]
发表于 2012-12-28 17:18 |只看该作者
回复 6# becomefish


哪个字段呢?


   

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
8 [报告]
发表于 2012-12-31 18:20 |只看该作者
回复 7# Modifix
看一下代码 net/ipv4/tcp_ipv4.c里面的tcp4_seq_show函数

这里面显示tcp状态的时候,并没有直接使用kernel 协议栈的sk->state,而且有一个简单的转换。

   
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP