在 TIME_WAIT 狀態(tài)的 TCP 連接,收到 SYN 后會(huì)發(fā)生什么?

      網(wǎng)友投稿 817 2025-03-31

      周末跟朋友討論了一些 TCP 的問題,在查閱《Linux 服務(wù)器高性能編程》這本書的時(shí)候,發(fā)現(xiàn)書上寫了這么一句話:

      書上說,處于 TIME_WAIT 狀態(tài)的連接,在收到相同四元組的 SYN 后,會(huì)回 RST 報(bào)文,對(duì)方收到后就會(huì)斷開連接。

      起初,我看到也覺得這個(gè)邏輯也挺符合常理的,但是當(dāng)我自己去啃了 TCP 源碼后,發(fā)現(xiàn)并不是這樣的。

      所以,今天就來討論下這個(gè)問題,「在 TCP 正常揮手過程中,處于 TIME_WAIT 狀態(tài)的連接,收到相同四元組的 SYN 后會(huì)發(fā)生什么?」

      問題現(xiàn)象如下圖,左邊是服務(wù)端,右邊是客戶端:

      先說結(jié)論

      在跟大家分析 TCP 源碼前,我先跟大家直接說下結(jié)論。

      針對(duì)這個(gè)問題,關(guān)鍵是要看 SYN 的「序列號(hào)和時(shí)間戳」是否合法,因?yàn)樘幱?TIME_WAIT 狀態(tài)的連接收到 SYN 后,會(huì)判斷 SYN 的「序列號(hào)和時(shí)間戳」是否合法,然后根據(jù)判斷結(jié)果的不同做不同的處理。

      先跟大家說明下, 什么是「合法」的 SYN?

      合法 SYN:客戶端的 SYN 的「序列號(hào)」比服務(wù)端「期望下一個(gè)收到的序列號(hào)」要大,并且 SYN 的「時(shí)間戳」比服務(wù)端「最后收到的報(bào)文的時(shí)間戳」要大。

      非法 SYN:客戶端的 SYN 的「序列號(hào)」比服務(wù)端「期望下一個(gè)收到的序列號(hào)」要小,或者 SYN 的「時(shí)間戳」比服務(wù)端「最后收到的報(bào)文的時(shí)間戳」要小。

      上面 SYN 合法判斷是基于雙方都開啟了 TCP 時(shí)間戳機(jī)制的場景,如果雙方都沒有開啟 TCP 時(shí)間戳機(jī)制,則 SYN 合法判斷如下:

      合法 SYN:客戶端的 SYN 的「序列號(hào)」比服務(wù)端「期望下一個(gè)收到的序列號(hào)」要大。

      非法 SYN:客戶端的 SYN 的「序列號(hào)」比服務(wù)端「期望下一個(gè)收到的序列號(hào)」要小。

      收到合法 SYN

      如果處于 TIME_WAIT 狀態(tài)的連接收到「合法的 SYN 」后,就會(huì)重用此四元組連接,跳過 2MSL 而轉(zhuǎn)變?yōu)?SYN_RECV 狀態(tài),接著就能進(jìn)行建立連接過程。

      用下圖作為例子,雙方都啟用了 TCP 時(shí)間戳機(jī)制,TSval 是發(fā)送報(bào)文時(shí)的時(shí)間戳:

      上圖中,在收到第三次揮手的 FIN 報(bào)文時(shí),會(huì)記錄該報(bào)文的 TSval (21),用 ts_recent 變量保存。然后會(huì)計(jì)算下一次期望收到的序列號(hào),本次例子下一次期望收到的序列號(hào)就是 301,用 rcv_nxt 變量保存。

      處于 TIME_WAIT 狀態(tài)的連接收到 SYN 后,因?yàn)?SYN 的 seq(400) 大于 rcv_nxt(301),并且 SYN 的 TSval(30) 大于 ts_recent(21),所以是一個(gè)「合法的 SYN」,于是就會(huì)重用此四元組連接,跳過 2MSL 而轉(zhuǎn)變?yōu)?SYN_RECV 狀態(tài),接著就能進(jìn)行建立連接過程。

      收到非法的 SYN

      如果處于 TIME_WAIT 狀態(tài)的連接收到「非法的 SYN 」后,就會(huì)再回復(fù)一個(gè)第四次揮手的 ACK 報(bào)文,客戶端收到后,發(fā)現(xiàn)并不是自己期望收到確認(rèn)號(hào)(ack num),就回 RST 報(bào)文給服務(wù)端。

      用下圖作為例子,雙方都啟用了 TCP 時(shí)間戳機(jī)制,TSval 是發(fā)送報(bào)文時(shí)的時(shí)間戳:

      上圖中,在收到第三次揮手的 FIN 報(bào)文時(shí),會(huì)記錄該報(bào)文的 TSval (21),用 ts_recent 變量保存。然后會(huì)計(jì)算下一次期望收到的序列號(hào),本次例子下一次期望收到的序列號(hào)就是 301,用 rcv_nxt 變量保存。

      處于 TIME_WAIT 狀態(tài)的連接收到 SYN 后,因?yàn)?SYN 的 seq(200) 小于 rcv_nxt(301),所以是一個(gè)「非法的 SYN」,就會(huì)再回復(fù)一個(gè)與第四次揮手一樣的 ACK 報(bào)文,客戶端收到后,發(fā)現(xiàn)并不是自己期望收到確認(rèn)號(hào),就回 RST 報(bào)文給服務(wù)端。

      客戶端等待一段時(shí)間還是沒收到 SYN + ACK 后,就會(huì)超時(shí)重傳 SYN 報(bào)文,重傳次數(shù)達(dá)到最大值后,就會(huì)斷開連接。

      PS:這里先埋一個(gè)疑問,處于 TIME_WAIT 狀態(tài)的連接,收到 RST 會(huì)斷開連接嗎?

      源碼分析

      下面源碼分析是基于 Linux 4.2 版本的內(nèi)核代碼。

      Linux 內(nèi)核在收到 TCP 報(bào)文后,會(huì)執(zhí)行 tcp_v4_rcv 函數(shù),在該函數(shù)和 TIME_WAIT 狀態(tài)相關(guān)的主要代碼如下:

      int tcp_v4_rcv(struct sk_buff *skb) { struct sock *sk; ... //收到報(bào)文后,會(huì)調(diào)用此函數(shù),查找對(duì)應(yīng)的 sock sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source, th->dest, sdif, &refcounted); if (!sk) goto no_tcp_socket; process: //如果連接的狀態(tài)為 time_wait,會(huì)跳轉(zhuǎn)到 do_time_wait if (sk->sk_state == TCP_TIME_WAIT) goto do_time_wait; ... do_time_wait: ... //由tcp_timewait_state_process函數(shù)處理在 time_wait 狀態(tài)收到的報(bào)文 switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) { // 如果是TCP_TW_SYN,那么允許此 SYN 重建連接 // 即允許TIM_WAIT狀態(tài)躍遷到SYN_RECV case TCP_TW_SYN: { struct sock *sk2 = inet_lookup_listener(....); if (sk2) { .... goto process; } } // 如果是TCP_TW_ACK,那么,返回記憶中的ACK case TCP_TW_ACK: tcp_v4_timewait_ack(sk, skb); break; // 如果是TCP_TW_RST直接發(fā)送RESET包 case TCP_TW_RST: tcp_v4_send_reset(sk, skb); inet_twsk_deschedule_put(inet_twsk(sk)); goto discard_it; // 如果是TCP_TW_SUCCESS則直接丟棄此包,不做任何響應(yīng) case TCP_TW_SUCCESS:; } goto discard_it; }

      該代碼的過程:

      在 TIME_WAIT 狀態(tài)的 TCP 連接,收到 SYN 后會(huì)發(fā)生什么?

      接收到報(bào)文后,會(huì)調(diào)用 __inet_lookup_skb() 函數(shù)查找對(duì)應(yīng)的 sock 結(jié)構(gòu);

      如果連接的狀態(tài)是 TIME_WAIT,會(huì)跳轉(zhuǎn)到 do_time_wait 處理;

      由 tcp_timewait_state_process() 函數(shù)來處理收到的報(bào)文,處理后根據(jù)返回值來做相應(yīng)的處理。

      先跟大家說下,如果收到的 SYN 是合法的,tcp_timewait_state_process() 函數(shù)就會(huì)返回 TCP_TW_SYN,然后重用此連接。如果收到的 SYN 是非法的,tcp_timewait_state_process() 函數(shù)就會(huì)返回 TCP_TW_ACK,然后會(huì)回上次發(fā)過的 ACK。

      接下來,看 tcp_timewait_state_process() 函數(shù)是如何判斷 SYN 包的。

      enum tcp_tw_status tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb, const struct tcphdr *th) { ... //paws_reject 為 false,表示沒有發(fā)生時(shí)間戳回繞 //paws_reject 為 true,表示發(fā)生了時(shí)間戳回繞 bool paws_reject = false; tmp_opt.saw_tstamp = 0; //TCP頭中有選項(xiàng)且舊連接開啟了時(shí)間戳選項(xiàng) if (th->doff > (sizeof(*th) >> 2) && tcptw->tw_ts_recent_stamp) { //解析選項(xiàng) tcp_parse_options(twsk_net(tw), skb, &tmp_opt, 0, NULL); if (tmp_opt.saw_tstamp) { ... //檢查收到的報(bào)文的時(shí)間戳是否發(fā)生了時(shí)間戳回繞 paws_reject = tcp_paws_reject(&tmp_opt, th->rst); } } .... //是SYN包、沒有RST、沒有ACK、時(shí)間戳沒有回繞,并且序列號(hào)也沒有回繞, if (th->syn && !th->rst && !th->ack && !paws_reject && (after(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt) || (tmp_opt.saw_tstamp && //新連接開啟了時(shí)間戳 (s32)(tcptw->tw_ts_recent - tmp_opt.rcv_tsval) < 0))) { //時(shí)間戳沒有回繞 // 初始化序列號(hào) u32 isn = tcptw->tw_snd_nxt + 65535 + 2; if (isn == 0) isn++; TCP_SKB_CB(skb)->tcp_tw_isn = isn; return TCP_TW_SYN; //允許重用TIME_WAIT四元組重新建立連接 } if (!th->rst) { // 如果時(shí)間戳回繞,或者報(bào)文里包含ack,則將 TIMEWAIT 狀態(tài)的持續(xù)時(shí)間重新延長 if (paws_reject || th->ack) inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN, TCP_TIMEWAIT_LEN); // 返回TCP_TW_ACK, 發(fā)送上一次的 ACK return TCP_TW_ACK; } inet_twsk_put(tw); return TCP_TW_SUCCESS; }

      如果雙方啟用了 TCP 時(shí)間戳機(jī)制,就會(huì)通過 tcp_paws_reject() 函數(shù)來判斷時(shí)間戳是否發(fā)生了回繞,也就是「當(dāng)前收到的報(bào)文的時(shí)間戳」是否大于「上一次收到的報(bào)文的時(shí)間戳」:

      如果大于,就說明沒有發(fā)生時(shí)間戳繞回,函數(shù)返回 false。

      如果小于,就說明發(fā)生了時(shí)間戳回繞,函數(shù)返回 true。

      從源碼可以看到,當(dāng)收到 SYN 包后,如果該 SYN 包的時(shí)間戳沒有發(fā)生回繞,也就是時(shí)間戳是遞增的,并且 SYN 包的序列號(hào)也沒有發(fā)生回繞,也就是 SYN 的序列號(hào)「大于」下一次期望收到的序列號(hào)。就會(huì)初始化一個(gè)序列號(hào),然后返回 TCP_TW_SYN,接著就重用該連接,也就跳過 2MSL 而轉(zhuǎn)變?yōu)?SYN_RECV 狀態(tài),接著就能進(jìn)行建立連接過程。

      如果雙方都沒有啟用 TCP 時(shí)間戳機(jī)制,就只需要判斷 SYN 包的序列號(hào)有沒有發(fā)生回繞,如果 SYN 的序列號(hào)大于下一次期望收到的序列號(hào),就可以跳過 2MSL,重用該連接。

      如果 SYN 包是非法的,就會(huì)返回 TCP_TW_ACK,接著就會(huì)發(fā)送與上一次一樣的 ACK 給對(duì)方。

      在 TIME_WAIT 狀態(tài),收到 RST 會(huì)斷開連接嗎?

      在前面我留了一個(gè)疑問,處于 TIME_WAIT 狀態(tài)的連接,收到 RST 會(huì)斷開連接嗎?

      會(huì)不會(huì)斷開,關(guān)鍵看 net.ipv4.tcp_rfc1337 這個(gè)內(nèi)核參數(shù)(默認(rèn)情況是為 0):

      如果這個(gè)參數(shù)設(shè)置為 0, 收到 RST 報(bào)文會(huì)提前結(jié)束 TIME_WAIT 狀態(tài),釋放連接。

      如果這個(gè)參數(shù)設(shè)置為 1, 就會(huì)丟掉 RST 報(bào)文。

      源碼處理如下:

      enum tcp_tw_status tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb, const struct tcphdr *th) { .... //rst報(bào)文的時(shí)間戳沒有發(fā)生回繞 if (!paws_reject && (TCP_SKB_CB(skb)->seq == tcptw->tw_rcv_nxt && (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || th->rst))) { //處理rst報(bào)文 if (th->rst) { //不開啟這個(gè)選項(xiàng),當(dāng)收到 RST 時(shí)會(huì)立即回收tw,但這樣做是有風(fēng)險(xiǎn)的 if (twsk_net(tw)->ipv4.sysctl_tcp_rfc1337 == 0) { kill: //刪除tw定時(shí)器,并釋放tw inet_twsk_deschedule_put(tw); return TCP_TW_SUCCESS; } } else { //將 TIMEWAIT 狀態(tài)的持續(xù)時(shí)間重新延長 inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN); } ... return TCP_TW_SUCCESS; } }

      TIME_WAIT 狀態(tài)收到 RST 報(bào)文而釋放連接,這樣等于跳過 2MSL 時(shí)間,這么做還是有風(fēng)險(xiǎn)。

      sysctl_tcp_rfc1337 這個(gè)參數(shù)是在 rfc 1337 文檔提出來的,目的是避免因?yàn)?TIME_WAIT 狀態(tài)收到 RST 報(bào)文而跳過 2MSL 的時(shí)間,文檔里也給出跳過 2MSL 時(shí)間會(huì)有什么潛在問題。

      TIME_WAIT 狀態(tài)之所以要持續(xù) 2MSL 時(shí)間,主要有兩個(gè)目的:

      防止歷史連接中的數(shù)據(jù),被后面相同四元組的連接錯(cuò)誤的接收;

      保證「被動(dòng)關(guān)閉連接」的一方,能被正確的關(guān)閉;

      詳細(xì)的為什么要設(shè)計(jì) TIME_WAIT 狀態(tài),我在這篇有詳細(xì)說明:如果 TIME_WAIT 狀態(tài)持續(xù)時(shí)間過短或者沒有,會(huì)有什么問題?

      雖然 TIME_WAIT 狀態(tài)持續(xù)的時(shí)間是有一點(diǎn)長,顯得很不友好,但是它被設(shè)計(jì)來就是用來避免發(fā)生亂七八糟的事情。

      《UNIX網(wǎng)絡(luò)編程》一書中卻說道:TIME_WAIT 是我們的朋友,它是有助于我們的,不要試圖避免這個(gè)狀態(tài),而是應(yīng)該弄清楚它。

      所以,我個(gè)人覺得將 net.ipv4.tcp_rfc1337 設(shè)置為 1 會(huì)比較安全。

      總結(jié)

      在 TCP 正常揮手過程中,處于 TIME_WAIT 狀態(tài)的連接,收到相同四元組的 SYN 后會(huì)發(fā)生什么?

      如果雙方開啟了時(shí)間戳機(jī)制:

      如果客戶端的 SYN 的「序列號(hào)」比服務(wù)端「期望下一個(gè)收到的序列號(hào)」要大,并且SYN 的「時(shí)間戳」比服務(wù)端「最后收到的報(bào)文的時(shí)間戳」要大。那么就會(huì)重用該四元組連接,跳過 2MSL 而轉(zhuǎn)變?yōu)?SYN_RECV 狀態(tài),接著就能進(jìn)行建立連接過程。

      如果客戶端的 SYN 的「序列號(hào)」比服務(wù)端「期望下一個(gè)收到的序列號(hào)」要小,或者SYN 的「時(shí)間戳」比服務(wù)端「最后收到的報(bào)文的時(shí)間戳」要小。那么就會(huì)再回復(fù)一個(gè)第四次揮手的 ACK 報(bào)文,客戶端收到后,發(fā)現(xiàn)并不是自己期望收到確認(rèn)號(hào),就回 RST 報(bào)文給服務(wù)端。

      在 TIME_WAIT 狀態(tài),收到 RST 會(huì)斷開連接嗎?

      如果 net.ipv4.tcp_rfc1337 參數(shù)為 0,則提前結(jié)束 TIME_WAIT 狀態(tài),釋放連接。

      如果 net.ipv4.tcp_rfc1337 參數(shù)為 1,則會(huì)丟掉該 RST 報(bào)文。

      完!

      TCP/IP

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:示波器數(shù)據(jù)分析軟件(示波儀軟件)
      下一篇:顯示最高值和最低值的折線圖能夠根據(jù)數(shù)據(jù)源變化自動(dòng)更新(折線圖顯示最大值)
      相關(guān)文章
      亚洲丰满熟女一区二区哦| 亚洲线精品一区二区三区| 久久久久亚洲AV成人片| 亚洲日韩v无码中文字幕| 国产亚洲精品资在线| 精品国产香蕉伊思人在线在线亚洲一区二区 | 亚洲视频在线免费播放| 久久精品蜜芽亚洲国产AV| 亚洲成AV人片在WWW色猫咪| 在线观看亚洲av每日更新| 亚洲无码在线播放| 亚洲乱码中文字幕手机在线| 亚洲乱码中文字幕手机在线 | 色噜噜AV亚洲色一区二区| 久久精品夜色噜噜亚洲A∨| 亚洲一区二区三区乱码A| 国产亚洲美女精品久久久| 亚洲人成网7777777国产| 亚洲av最新在线网址| 亚洲国产成人久久精品影视| 91亚洲一区二区在线观看不卡 | 亚洲中文久久精品无码| 亚洲国产成人片在线观看 | 中文无码亚洲精品字幕| 亚洲永久在线观看| 无码乱人伦一区二区亚洲| 亚洲精品无码久久千人斩| 久久亚洲精品无码VA大香大香| 亚洲精品在线不卡| 人人狠狠综合久久亚洲婷婷| 亚洲国产成人久久综合区| 亚洲第一成年免费网站| 久久亚洲日韩看片无码| 久久伊人亚洲AV无码网站| 亚洲av色香蕉一区二区三区蜜桃| 亚洲无限乱码一二三四区| 亚洲成a人片在线观看无码专区| 国产大陆亚洲精品国产| 中文字幕在线观看亚洲日韩| 亚洲成人福利在线观看| 亚洲男人都懂得羞羞网站|