好吧,原本已经写好的博文在我点击保存以后仍是莫名丢失了,人生就是这样,到处均可能充满了狗血,但连滚带爬也不能屈服,因而深夜从新来过,文思可能不如上一次那么流畅,但好在是技术文章,主要看气质吧。并发
下面是华丽的分割线less
============================================================socket
继续前面的话题。tcp
咱们已经知道,当client端断开链接(close)以后,server端进入CLOSE_WAIT状态,若是server端不进行任何动做的话这个状态能够一直持续下去,而client端的链接则最终会由于超时而销毁。函数
那么,随之而来,新的问题又来了,若是一段时间以后client端复用了以前的四元组信息发起新的链接请求,又会是什么样的情形呢?spa
下面先经过抓包来直观地感觉一下这个过程(为了便于分析tcp报文中的sequence number,特别取消了Wireshark中的相对序列号分析功能,Edit--->Preferences--->Protocols--->TCP--->Analyze TCP sequence numbers):code
上图的抓包能够分为这么几个阶段:
server
1)client端向server端发起链接并发送若干数据段(No. 1 - No. 22);ip
2)client端关闭链接,server端对数据和FIN分别回应ACK后没有后续动做,这时server端将维持在CLOSE_WAIT状态,而client端则在FIN_WAIT_2状态下暂停一段时间后超时退出(No.23 - No.25);
原型
3)client端复用以前的四元组从新发起链接,server端以ACK进行回应,且应答号与SYN中的初始序列号不一致,而是延续了上一个链接的应答号,这触发了client端的RST(No.26 - No.28);
4)约1s以后client端协议栈尝试从新创建链接并成功;
下面咱们就去Kernel里面扒一扒对应的实现吧,重点集中在如下三个场景:
处于CLOSE_WAIT状态的链接收到一个复用的新链接请求时会怎么样?
处于SYN_SENT状态的链接收到一个序列号不匹配的应答时会怎么样?
处于CLOSE_WAIT状态的链接收到RST时会怎样?
Kernel版本3.6.1。
协议栈中处理tcp协议的入口函数是tcp_v4_rcv,其原型以下:
int tcp_v4_rcv(struct sk_buff *skb);
函数tcp_v4_rcv只有一个参数,就是接收报文的sk_buff结构,在该函数中会以四元组信息(sip, sport, dip, dport)来计算hash值,并根据计算结果分别在established表和listen表中查找对应的struct sock* 结构。因为client端复用了四元组信息,且server端的链接并无销毁,所以可以匹配到原有的struct sock* 结构,该结构中的状态信息sk_state将做为后续处理的重要依据。接着流程转入tcp_v4_do_rcv函数:
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb);
能够看到,tcp_v4_do_rcv函数的参数除了存放报文内容的sk_buff结构外,就是上一步中查到的struct sock* 结构啦,这个函数内只处理了TCP_ESTABLISHED和TCP_LISTEN两种状态,更多的内容交付给了tcp_rcv_state_process函数:
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len)
tcp_rcv_state_process函数中的内容可就丰富多了,全部的状态几乎都在这里等待检阅,这也是本文的重点所在,对照上面总结的三个场景来分别进行分析。
1) 处于CLOSE_WAIT状态的链接收到一个复用的新链接请求时会怎么样:
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { ... ... /* step 7: process the segment text */ switch (sk->sk_state) { case TCP_CLOSE_WAIT: case TCP_CLOSING: case TCP_LAST_ACK: if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))/*报文中的序列号晚于指望接收的序列号*/ break; case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: /* RFC 793 says to queue data in these states, * RFC 1122 says we MUST send a reset. * BSD 4.4 also does reset. */ if (sk->sk_shutdown & RCV_SHUTDOWN) { if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq && after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA); tcp_reset(sk); return 1; } } /* Fall through */ case TCP_ESTABLISHED: tcp_data_queue(sk, skb); queued = 1; break; } /* tcp_data could move socket to TIME-WAIT */ if (sk->sk_state != TCP_CLOSE) { tcp_data_snd_check(sk); tcp_ack_snd_check(sk); } if (!queued) { discard: __kfree_skb(skb); } return 0; }
能够看到,在处于CLOSE_WAIT状态的链接上收到一个报文的序列号晚于(大于)指望接收的序列号时,会以ACK来进行回应。
2) 处于SYN_SENT状态的链接收到一个序列号不匹配的应答时会怎么样:
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { ... ... case TCP_SYN_SENT: queued = tcp_rcv_synsent_state_process(sk, skb, th, len); if (queued >= 0) return queued; /* Do step6 onward by hand. */ tcp_urg(sk, skb, th); __kfree_skb(skb); tcp_data_snd_check(sk); return 0; } ... ... } static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { ... ... if (th->ack) { /* rfc793: * "If the state is SYN-SENT then * first check the ACK bit * If the ACK bit is set * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send * a reset (unless the RST bit is set, if so drop * the segment and return)" */ if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) || after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) goto reset_and_undo; if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr && !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, tcp_time_stamp)) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSACTIVEREJECTED); goto reset_and_undo; } /* Now ACK is acceptable. * * "If the RST bit is set * If the ACK was acceptable then signal the user "error: * connection reset", drop the segment, enter CLOSED state, * delete TCB, and return." */ ... ... /* "fifth, if neither of the SYN or RST bits is set then * drop the segment and return." */ discard_and_undo: tcp_clear_options(&tp->rx_opt); tp->rx_opt.mss_clamp = saved_clamp; goto discard; reset_and_undo: tcp_clear_options(&tp->rx_opt); tp->rx_opt.mss_clamp = saved_clamp; return 1; }
tcp_rcv_synsent_state_process函数中的注释很明确的说明了当链接处在SYN_SENT状态时首先检查ACK位,若是该 ACK位置位而且报文中的应答号不大于初始序列号,则触发RST。
3) 处于CLOSE_WAIT状态的链接收到RST时会怎样:
在tcp_rcv_state_process函数中有一段调用:
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { ... ... if (!tcp_validate_incoming(sk, skb, th, 0)) return 0; ... ... } static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, int syn_inerr) { ... ... /* Step 2: check RST bit */ if (th->rst) { /* RFC 5961 3.2 : * If sequence number exactly matches RCV.NXT, then * RESET the connection * else * Send a challenge ACK */ if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) tcp_reset(sk); else tcp_send_challenge_ack(sk); goto discard; } ... ... }
能够看到,当收到RST且报文中携带的序列号与指望序列号一致时,会调用tcp_reset函数,这个函数在以前的文章中也有分析过,其中会对处于CLOSE_WAIT状态的链接赋予EPIPE错误码,并触发SIGPIPE信号。
至此,整个流程就合拢了。
PS. 聪明的你必定知道tcp端口号是如何复用的,没错,就是bind函数咯。