- 论坛徽章:
- 0
|
本帖最后由 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(请原谅我用这么屌丝的描述,因为我实在搞不清这个流程)
来看代码:- if (sk->sk_state == TCP_LISTEN) {
- //1 . 生成一个新连接,此连接初始化为SYN_RECV ,这里走到了
- struct sock *nsk = tcp_v4_hnd_req(sk, skb);
- if (!nsk)
- goto discard;
- if (nsk != sk) {
- //2. 将新连接设置为ESTABLISHED ,这里没有设置成功
- if (tcp_child_process(sk, nsk, skb)) {
- rsk = nsk;
- goto reset;
- }
- return 0;
- }
- }
复制代码 从上面流程看出, 服务器至少在步骤1是成功设置了SYN_RECV状态的,不幸的是服务器
可能在步骤2出了问题,甚至说都没有走到步骤2。从而导致了长时间处于SYN_RECV状态。
从刚才的分析看出,服务器直到这个路径,应该都是一切工作正常的:- s) tcp_v4_do_rcv-->
- a) tcp_v4_hnd_req-->
- b) tcp_check_req(检查报文是否有效)-->
- c) tcp_v4_syn_recv_sock(报文字段有效,准备生成新连接)-->
- d) tcp_create_openreq_child(生成一个处于SYN_RECV的新连接)
复制代码 来反推一下:
1. 根据代码逻辑,d步骤的tcp_create_openreq_child函数一定不返回空(否则就不会有处于SYN_RECV状态的连接了)
从而newsk一定不为空- newsk = tcp_create_openreq_child(sk, req, skb);
- if (!newsk)
- goto exit;
复制代码 2. 因为newsk不为空,c步骤的tcp_v4_syn_recv_sock函数也不会返回空,
因此child也不会为空:- child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb,
- req, NULL);
- if (child == NULL)
- goto listen_overflow;
复制代码 3. 因为child不为空,所以b步骤的tcp_check_req函数里, 不会走到listen_overflow,
于是这个新连接一定会被加入到服务器的连接完成队列里,等待用户态程序通过accept去获取
所以a步骤的tcp_v4_hnd_req函数,应该是正确返回了的(这个其实我没底气)
4. 推断,那只能是tcp_child_process 函数 里没有走到设置 ESTABLISHED 状态- int tcp_child_process(struct sock *parent, struct sock *child,
- struct sk_buff *skb)
- {
- int ret = 0;
- int state = child->sk_state;
- if (!sock_owned_by_user(child)) {
- ret = tcp_rcv_state_process(child, skb, skb->h.th, skb->len); //设置ESTABLISHED 状态
- /* Wakeup parent, send SIGIO */
- if (state == TCP_SYN_RECV && child->sk_state != state)
- parent->sk_data_ready(parent, 0);
- } else {
- /* Alas, it is possible again, because we do lookup
- * in main socket hash table and lock on listening
- * socket does not protect us more.
- */
- sk_add_backlog(child, skb);
- }
- bh_unlock_sock(child);
- sock_put(child);
- return ret;
- }
复制代码 5. 此时这个新连接套接字,应该是没有人持有的,所以会进入tcp_rcv_state_process
6. 推断,tcp_rcv_state_process没有走到最终设置ESTABLISHED
可是这个tcp_rcv_state_process函数是个巨无霸,我没有信心继续往下看了。
请问这里的对协议栈略懂或者不懂的同仁,能否给点意见或鼓励,你们是否有遇到过服务器连接长时间处于SYN_RECV状态的
情况?
如果有,可能会是什么原因?
如果没有,能否推荐一些排查网络问题的手段?
谢谢
|
|