TCP之FIN_WAIT_2状态处理流程
概述
在主动关闭⽅发送了FIN之后,进⼊FIN_WAIT_1状态,在此状态收到了ACK,则进⼊FIN_WAIT_2状态,⽽FIN_WAIT_2后续要做的⼯作是等待接收对端发过来的FIN包,并且发送ACK,进⽽进⼊到TIME_WAIT状态;本⽂主要关注从FIN_WAIT_1进⼊FIN_WAIT_2状态,以及在FIN_WAIT_2状态来包或者定时器触发后的处理流程;
进⼊FIN_WAIT_2
tcp_rcv_state_process函数中对于ack的处理步骤中,假如连接处于FIN_WAIT_1,且数据均已经被确认完,则进⼊TIME_WAIT_2状态;如果⽆需在该状态等待(linger2<0),或者收到了乱序数据段,则直接关闭连接;如果需要等待,则需要判断等待时间与TIMEWAIT时间的⼤⼩关系,若>TIMEWAIT_LEN,则添加TIME_WAIT_2定时器,否则直接进⼊TIME_WAIT接管(其⼦状态仍然是FIN_WAIT_2),接管之后会添加TIME_WAIT定时器;
另,tcp_clo函数调⽤时,如果当前状态是FIN_WAIT_2也会⽤相似⽅式进⼊TIME_WAIT接管,不再单独介绍;
1int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
2 {
3/* step 5: check the ACK field */
4 acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
5 FLAG_UPDATE_TS_RECENT) > 0;
6
7switch (sk->sk_state) {
8ca TCP_FIN_WAIT1: {
9int tmo;
10
11/* If we enter the TCP_FIN_WAIT1 state and we are a
12 * Fast Open socket and this is the first acceptable
13 * ACK we have received, this would have acknowledged
14 * our SYNACK so stop the SYNACK timer.
15*/
16if (req) {
乐理考试17/* Return RST if ack_q is invalid.
18 * Note that RFC793 only says to generate a
19 * DUPACK for it but for TCP Fast Open it ems
20 * better to treat this ca like TCP_SYN_RECV
21 * above.
22*/
23if (!acceptable)
24return1;
25/* We no longer need the request sock. */
26 reqsk_fastopen_remove(sk, req, fal);
27 tcp_rearm_rto(sk);
28 }
29
30/* 发送数据未确认完毕 */
31if (tp->snd_una != tp->write_q)
32break;
33
34/* 进⼊FIN_WAIT_2状态 */
35 tcp_t_state(sk, TCP_FIN_WAIT2);
36
怎样网络营销
37/* 关闭发送端 */
38 sk->sk_shutdown |= SEND_SHUTDOWN;
39
40/* 路由缓存确认 */
41 sk_dst_confirm(sk);
42
43/* 套接⼝不是DEAD状态,状态发⽣变化,唤醒等待进程 */
44if (!sock_flag(sk, SOCK_DEAD)) {
45/* Wake up lingering clo() */
46 sk->sk_state_change(sk);
47break;
48 }
49
50/* linger2<0,⽆需在FIN_WAIT_2等待 */
51if (tp->linger2 < 0) {
52/* 关闭连接 */
53 tcp_done(sk);
54 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
55return1;
56 }
57
58/* 收到期望序号以后的数据段(data, fin) */
59if (TCP_SKB_CB(skb)->end_q != TCP_SKB_CB(skb)->q &&
60 after(TCP_SKB_CB(skb)->end_q - th->fin, tp->rcv_nxt)) {
61/* Receive out of order FIN after clo() */
62if (tp->syn_fastopen && th->fin)
63 tcp_fastopen_active_disable(sk);
64/* 关闭连接 */
65 tcp_done(sk);
66 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
牛仔服装67return1;
68 }
69
70/* 获取FIN_WAIT_2等待时间 */
71 tmo = tcp_fin_time(sk);
72
73/* > TIMEWAIT_LEN,加⼊FIN_WAIT_2定时器 */
74if (tmo > TCP_TIMEWAIT_LEN) {
75 inet_csk_ret_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
76 }
77/* 有fin?? 或者被⽤户进程锁定,加⼊FIN_WAIT_2定时器 */
78el if (th->fin || sock_owned_by_ur(sk)) {
79/* Bad ca. We could lo such FIN otherwi.
80 * It is not a big problem, but it looks confusing
81 * and not so rare event. We still can lo it now,
82 * if it spins in bh_lock_sock(), but it is really
83 * marginal ca.
84*/
85 inet_csk_ret_keepalive_timer(sk, tmo);
86 }
87/* 正常等待时间< TIMEWAIT_LEN,进⼊TIMEWAIT接管状态 */
88el {
89 tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
90goto discard;
91 }
92break;
93 }
94 }
状态转换触发
FIN_WAIT_2状态的⾛向有以下⼏个流程触发点,(1)TIME_WAIT_2定时器未超时时间内,收到数据段触发; (2)TIME_WAIT_2定时器超时触发; (3)TIME_WAIT定时器未超时时间内,收到数据段触发; (4)TIME_WAIT定时器超时触发;
(1) TIME_WAIT_2定时器未超时时间内,收到数据段触发,如果设置FIN标记,则直接进⼊TIME_WAIT状态;
在函数tcp_rcv_state_process处理数据段的过程中,FIN_WAIT_2状态最终会调⽤tcp_data_queue来处理数据段;
1int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
2 {
3/* step 7: process the gment text */
4switch (sk->sk_state) {
5ca TCP_CLOSE_WAIT:
6ca TCP_CLOSING:
7ca TCP_LAST_ACK:
8if (!before(TCP_SKB_CB(skb)->q, tp->rcv_nxt))
9break;
10ca TCP_FIN_WAIT1:
11ca TCP_FIN_WAIT2:
12/* RFC 793 says to queue data in the states,
13 * RFC 1122 says we MUST nd a ret.
14 * BSD 4.4 also does ret.
15*/
16if (sk->sk_shutdown & RCV_SHUTDOWN) {
17if (TCP_SKB_CB(skb)->end_q != TCP_SKB_CB(skb)->q &&
18 after(TCP_SKB_CB(skb)->end_q - th->fin, tp->rcv_nxt)) {荣国府和宁国府的关系
19 NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
20 tcp_ret(sk);
21return1;
22 }
23 }
24/* Fall through */
25ca TCP_ESTABLISHED:
26 tcp_data_queue(sk, skb);
27 queued = 1;
28break;
29 }
30 }
tcp_data_queue在处理数据段的时候,有对FIN标记的检查,如果有该标记,则进⼊tcp_fin函数;
1static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
2 {
3/* ... */
4if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
5 tcp_fin(sk);
6/* ... */
7 }
tcp_fin函数中,如果此时连接状态为FIN_WAIT_2,则发送ACK,并且直接进⼊TIME_WAIT状态;在tcp_time_wait函数处理中,会删除当前控制块,所以FIN_WAIT_2定时器也就不存在了;
1void tcp_fin(struct sock *sk)
2 {
3/* ... */
4switch (sk->sk_state) {
5ca TCP_FIN_WAIT2:
6/* Received a FIN -- nd ACK and enter TIME_WAIT. */
7 tcp_nd_ack(sk);
8 tcp_time_wait(sk, TCP_TIME_WAIT, 0);
9break;
10 }
11/* ... */
12 }
(2)TIME_WAIT_2定时器超时触发,如果linger2<0,或者等待时间<=TIMEWAIT_LEN,直接发送ret关闭连接;如果linger2>=0,且等待时间>TIMEWAIT_LEN,则进⼊TIME_WAIT接管;
1static void tcp_keepalive_timer (unsigned long data)
2 {
3/*...*/
4/* 处于fin_wait2且socket即将销毁,⽤作FIN_WAIT_2定时器 */
5if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
6
7/* 停留在FIN_WAIT_2的停留时间>=0 */
8if (tp->linger2 >= 0) {
9/* 获取时间差值 */
10const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;
11
12/* 差值>0,等待时间>TIME_WAIT时间,则进⼊TIME_WAIT状态 */
13if (tmo > 0) {
14 tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
15goto out;
16 }
17 }
18
19/* 发送rst */
20 tcp_nd_active_ret(sk, GFP_ATOMIC);
21goto death;
22 }
23/*...*/
24 }
(3)TIME_WAIT定时器未超时时间内,收到数据段触发,若收到合法的FIN,则进⼊真正的TIME_WAIT状态;
tcp_v4_rcv收⼊数据段过程中,会对TIME_WAIT状态做特别处理,⽽对于TIME_WAIT⼦状态的处理在函数tcp_timewait_state_process中; 1int tcp_v4_rcv(struct sk_buff *skb)
2 {
3/*...*/
4 do_time_wait:
5if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
6 inet_twsk_put(inet_twsk(sk));
7goto discard_it;
8 }
9
10/* 校验和错误 */
11if (tcp_checksum_complete(skb)) {
12 inet_twsk_put(inet_twsk(sk));
13goto csum_error;
14 }
15
16/* TIME_WAIT⼊包处理 */
17switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
18
19/* 收到syn */
20ca TCP_TW_SYN: {
21/* 查找监听控制块 */
22struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
23 &tcp_hashinfo, skb,
24 __tcp_hdrlen(th),
25 iph->saddr, th->source,
26 iph->daddr, th->dest,
27 inet_iif(skb));
28
29/* 找到 */
30if (sk2) {
31/* 删除tw控制块 */
32 inet_twsk_deschedule_put(inet_twsk(sk));
33/* 记录监听控制块 */
34 sk = sk2;
35 refcounted = fal;
36
37/* 进⾏新请求的处理 */
38goto process;
39 }
40/* Fall through to ACK */
41 }
42
43/* 发送ack */
44ca TCP_TW_ACK:
45 tcp_v4_timewait_ack(sk, skb);
46break;
47/* 发送rst */
低钾血症吃什么48ca TCP_TW_RST:
49 tcp_v4_nd_ret(sk, skb);
50/* 删除tw控制块 */
51 inet_twsk_deschedule_put(inet_twsk(sk));
52goto discard_it;
53/* 成功*/
54ca TCP_TW_SUCCESS:;
55 }
56goto discard_it;
57 }
tcp_timewait_state_process函数处理流程中,如果TIME_WAIT的⼦状态为FIN_WAIT_2,并且收到了合法的FIN之后,会进⼊真正的TIME_WAIT状态,即⼦状态也为TIME_WAIT,并且设置TIME_WAIT定时器;
1enum tcp_tw_status
2 tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
3const struct tcphdr *th)
4 {
5/*...*/
6/* ⼦状态是FIN_WAIT2 */
7if (tw->tw_substate == TCP_FIN_WAIT2) {
8/* Just repeat all the checks of tcp_rcv_state_process() */
9
10/* Out of window, nd ACK */
11/* 序号回绕或者数据超出窗⼝范围,发送ack */
12if (paws_reject ||
13 !tcp_in_window(TCP_SKB_CB(skb)->q, TCP_SKB_CB(skb)->end_q,
14 tcptw->tw_rcv_nxt,
15 tcptw->tw_rcv_nxt + tcptw->tw_rcv_wnd))
16return tcp_timewait_check_oow_rate_limit(
17 tw, skb, LINUX_MIB_TCPACKSKIPPEDFINWAIT2);
18
19/* rst,则停⽌调度,销毁tw控制块 */
20if (th->rst)香港资料
21goto kill;
22
23/* syn && 序号>= 期望接收序号 */
24if (th->syn && !before(TCP_SKB_CB(skb)->q, tcptw->tw_rcv_nxt))
25return TCP_TW_RST;
26
27/* Dup ACK? */
28/* ⾮ack || 以前的ack || 新的⽆数据ack */
29if (!th->ack ||
30 !after(TCP_SKB_CB(skb)->end_q, tcptw->tw_rcv_nxt) ||简短美句
31 TCP_SKB_CB(skb)->end_q == TCP_SKB_CB(skb)->q) {
32 inet_twsk_put(tw);
33return TCP_TW_SUCCESS;
34 }
35
36/* New data or FIN. If new data arrive after half-duplex clo,
37 * ret.
38*/
39/* 不是fin,或者fin有数据 */
40if (!th->fin ||
41 TCP_SKB_CB(skb)->end_q != tcptw->tw_rcv_nxt + 1)
42return TCP_TW_RST;
43
44/* FIN arrived, enter true time-wait state. */
45/* fin包,进⼊真正的TIME_WAIT */
46 tw->tw_substate = TCP_TIME_WAIT;
47
48/* 设置下⼀次接收序号 */
49 tcptw->tw_rcv_nxt = TCP_SKB_CB(skb)->end_q;
50
51/* 设置时间戳 */
52if (tmp_opt.saw_tstamp) {
53 tcptw->tw_ts_recent_stamp = get_conds();
54 tcptw->tw_ts_recent = v_tsval;
55 }
56
57/*重新设置tw定时器 */
58 inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);
59
60/* 发送ack */
61return TCP_TW_ACK;
62 }
63/*...*/
64 }蒜蓉粉丝扇贝
(4)TIME_WAIT定时器超时触发,定时器超时,将tw控制块从ehash和bhash中删除,在收到数据段会发送ret;定时器超时会进⼊到tw_timer_handler处理函数,该函数在统计信息之后,调⽤inet_twsk_kill;
1static void tw_timer_handler(unsigned long data)
2 {
3struct inet_timewait_sock *tw = (struct inet_timewait_sock *)data;
4
5if (tw->tw_kill)
6 __NET_INC_STATS(twsk_net(tw), LINUX_MIB_TIMEWAITKILLED);
7el
8 __NET_INC_STATS(twsk_net(tw), LINUX_MIB_TIMEWAITED);
9 inet_twsk_kill(tw);
10 }
inet_twsk_kill从ehash和bhash中把tw控制块删除,并且释放之;
1static void inet_twsk_kill(struct inet_timewait_sock *tw)
2 {
3struct inet_hashinfo *hashinfo = tw->tw_dr->hashinfo;
4 spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);
5struct inet_bind_hashbucket *bhead;
6
7 spin_lock(lock);
8 sk_nulls_del_node_init_rcu((struct sock *)tw);
9 spin_unlock(lock);
10
11/* Disassociate with bind bucket. */
12 bhead = &hashinfo->bhash[inet_bhashfn(twsk_net(tw), tw->tw_num,
13 hashinfo->bhash_size)];
14
15 spin_lock(&bhead->lock);
16 inet_twsk_bind_unhash(tw, hashinfo);
17 spin_unlock(&bhead->lock);
18
19 atomic_dec(&tw->tw_dr->tw_count);
20 inet_twsk_put(tw);
21 }