您的位置:首页 > 理论基础 > 计算机网络

#TCP你学得会# 之 client重用连接之时

2016-01-29 00:00 232 查看
摘要: 当client端连接已经销毁,而server端仍然处于CLOSE_WAIT状态时,client重新发起连接请求并复用之前的四元组,会发生什么?

好吧,本来已经写好的博文在我点击保存之后还是莫名丢失了,人生就是这样,处处都可能充满了狗血,但连滚带爬也不能屈服,于是深夜重新来过,文思可能不如上一次那么流畅,但好在是技术文章,主要看气质吧。

下面是华丽的分割线

============================================================

继续前面的话题。

我们已经知道,当client端断开连接(close)之后,server端进入CLOSE_WAIT状态,如果server端不进行任何动作的话这个状态可以一直持续下去,而client端的连接则最终会因为超时而销毁。

那么,随之而来,新的问题又来了,如果一段时间之后client端复用了之前的四元组信息发起新的连接请求,又会是什么样的情形呢?

下面先通过抓包来直观地感受一下这个过程(为了便于分析tcp报文中的sequence number,特别取消了Wireshark中的相对序列号分析功能,Edit--->Preferences--->Protocols--->TCP--->Analyze TCP sequence numbers):



上图的抓包可以分为这么几个阶段:

1)client端向server端发起连接并发送若干数据段(No. 1 - No. 22);

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函数咯。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Kernel tcp CLOSE_WAIT