“三次握手,四次揮手”你確定你了解么?(之三)
連接隊(duì)列
在外部請(qǐng)求到達(dá)時(shí),被服務(wù)程序最終感知到前,連接可能處于SYN_RCVD狀態(tài)或是ESTABLISHED狀態(tài),但還未被應(yīng)用程序接受。
對(duì)應(yīng)地,服務(wù)器端也會(huì)維護(hù)兩種隊(duì)列,處于SYN_RCVD狀態(tài)的半連接隊(duì)列,而處于ESTABLISHED狀態(tài)但仍未被應(yīng)用程序accept的為全連接隊(duì)列。如果這兩個(gè)隊(duì)列滿了之后,就會(huì)出現(xiàn)各種丟包的情形。
1 2 | 查看是否有連接溢出 netstat -s | grep LISTEN |
半連接隊(duì)列滿了
在三次握手協(xié)議中,服務(wù)器維護(hù)一個(gè)半連接隊(duì)列,該隊(duì)列為每個(gè)客戶端的SYN包開設(shè)一個(gè)條目(服務(wù)端在接收到SYN包的時(shí)候,就已經(jīng)創(chuàng)建了request_sock結(jié)構(gòu),存儲(chǔ)在半連接隊(duì)列中),該條目表明服務(wù)器已收到SYN包,并向客戶發(fā)出確認(rèn),正在等待客戶的確認(rèn)包。這些條目所標(biāo)識(shí)的連接在服務(wù)器處于Syn_RECV狀態(tài),當(dāng)服務(wù)器收到客戶的確認(rèn)包時(shí),刪除該條目,服務(wù)器進(jìn)入ESTABLISHED狀態(tài)。
目前,Linux下默認(rèn)會(huì)進(jìn)行5次重發(fā)SYN-ACK包,重試的間隔時(shí)間從1s開始,下次的重試間隔時(shí)間是前一次的雙倍,5次的重試時(shí)間間隔為1s, 2s, 4s, 8s, 16s, 總共31s, 稱為
指數(shù)退避
,第5次發(fā)出后還要等32s才知道第5次也超時(shí)了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s, TCP才會(huì)把斷開這個(gè)連接。由于,SYN超時(shí)需要63秒,那么就給攻擊者一個(gè)攻擊服務(wù)器的機(jī)會(huì),攻擊者在短時(shí)間內(nèi)發(fā)送大量的SYN包給Server(俗稱SYN flood攻擊),用于耗盡Server的SYN隊(duì)列。對(duì)于應(yīng)對(duì)SYN 過(guò)多的問(wèn)題,linux提供了幾個(gè)TCP參數(shù):tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 來(lái)調(diào)整應(yīng)對(duì)。
參數(shù) | 作用 |
---|---|
tcp_syncookies | SYNcookie將連接信息編碼在ISN(initialsequencenumber)中返回給客戶端,這時(shí)server不需要將半連接保存在隊(duì)列中,而是利用客戶端隨后發(fā)來(lái)的ACK帶回的ISN還原連接信息,以完成連接的建立,避免了半連接隊(duì)列被攻擊SYN包填滿。 |
tcp_syncookies | 內(nèi)核放棄建立連接之前發(fā)送SYN包的數(shù)量。 |
tcp_synack_retries | 內(nèi)核放棄連接之前發(fā)送SYN+ACK包的數(shù)量 |
tcp_max_syn_backlog | 默認(rèn)為1000. 這表示半連接隊(duì)列的長(zhǎng)度,如果超過(guò)則放棄當(dāng)前連接。 |
tcp_abort_on_overflow | 如果設(shè)置了此項(xiàng),則直接reset. 否則,不做任何操作,這樣當(dāng)服務(wù)器半連接隊(duì)列有空了之后,會(huì)重新接受連接。Linux堅(jiān)持在能力許可范圍內(nèi)不忽略進(jìn)入的連接 ??蛻舳嗽谶@期間會(huì)重復(fù)發(fā)送sys包,當(dāng)重試次數(shù)到達(dá)上限之后,會(huì)得到connection time out 響應(yīng)。 |
全連接隊(duì)列滿了
當(dāng)?shù)谌挝帐謺r(shí),當(dāng)server接收到ACK包之后,會(huì)進(jìn)入一個(gè)新的叫 accept 的隊(duì)列。
當(dāng)accept隊(duì)列滿了之后,即使client繼續(xù)向server發(fā)送ACK的包,也會(huì)不被響應(yīng),此時(shí)ListenOverflows+1,同時(shí)server通過(guò)tcp_abort_on_overflow來(lái)決定如何返回,0表示直接丟棄該ACK,1表示發(fā)送RST通知client;相應(yīng)的,client則會(huì)分別返回read timeout
?或者?connection reset by peer
。另外,tcp_abort_on_overflow是0的話,server過(guò)一段時(shí)間再次發(fā)送syn+ack給client(也就是重新走握手的第二步),如果client超時(shí)等待比較短,就很容易異常了。而客戶端收到多個(gè) SYN ACK 包,則會(huì)認(rèn)為之前的 ACK 丟包了。于是促使客戶端再次發(fā)送 ACK ,在 accept隊(duì)列有空閑的時(shí)候最終完成連接。若 accept隊(duì)列始終滿員,則最終客戶端收到 RST 包(此時(shí)服務(wù)端發(fā)送syn+ack的次數(shù)超出了tcp_synack_retries)。
服務(wù)端僅僅只是創(chuàng)建一個(gè)定時(shí)器,以固定間隔重傳syn和ack到服務(wù)端
參數(shù) | 作用 |
---|---|
tcp_abort_on_overflow | 如果設(shè)置了此項(xiàng),則直接reset. 否則,不做任何操作,這樣當(dāng)服務(wù)器半連接隊(duì)列有空了之后,會(huì)重新接受連接。Linux堅(jiān)持在能力許可范圍內(nèi)不忽略進(jìn)入的連接 ??蛻舳嗽谶@期間會(huì)重復(fù)發(fā)送sys包,當(dāng)重試次數(shù)到達(dá)上限之后,會(huì)得到connection time out 響應(yīng)。 |
min(backlog, somaxconn) | 全連接隊(duì)列的長(zhǎng)度。 |
命令
netstat -s命令
1 2 3 | [root<a href="http://www.jobbole.com/members/server">@server</a> ~]#??netstat -s | egrep "listen|LISTEN" 667399 times the listen queue of a socket overflowed 667399 SYNs to LISTEN sockets ignored |
上面看到的 667399 times ,表示全連接隊(duì)列溢出的次數(shù),隔幾秒鐘執(zhí)行下,如果這個(gè)數(shù)字一直在增加的話肯定全連接隊(duì)列偶爾滿了。
1 | [root<a href="http://www.jobbole.com/members/server">@server</a> ~]#??netstat -s | grep TCPBacklogDrop |
查看 Accept queue 是否有溢出
ss命令
1 2 3 4 | [root<a href="http://www.jobbole.com/members/server">@server</a> ~]#??ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN???? 0??????128 *:6379 *:* LISTEN???? 0??????128 *:22 *:* |
如果State是listen狀態(tài),Send-Q 表示第三列的listen端口上的全連接隊(duì)列最大為50,第一列Recv-Q為全連接隊(duì)列當(dāng)前使用了多少。
非 LISTEN 狀態(tài)中 Recv-Q 表示 receive queue 中的 bytes 數(shù)量;Send-Q 表示 send queue 中的 bytes 數(shù)值。
小結(jié)
當(dāng)外部連接請(qǐng)求到來(lái)時(shí),TCP模塊會(huì)首先查看max_syn_backlog,如果處于SYN_RCVD狀態(tài)的連接數(shù)目超過(guò)這一閾值,進(jìn)入的連接會(huì)被拒絕。根據(jù)tcp_abort_on_overflow字段來(lái)決定是直接丟棄,還是直接reset.
從服務(wù)端來(lái)說(shuō),三次握手中,第一步server接受到client的syn后,把相關(guān)信息放到半連接隊(duì)列中,同時(shí)回復(fù)syn+ack給client. 第三步當(dāng)收到客戶端的ack, 將連接加入到全連接隊(duì)列。
一般,全連接隊(duì)列比較小,會(huì)先滿,此時(shí)半連接隊(duì)列還沒(méi)滿。如果這時(shí)收到syn報(bào)文,則會(huì)進(jìn)入半連接隊(duì)列,沒(méi)有問(wèn)題。但是如果收到了三次握手中的第3步(ACK),則會(huì)根據(jù)tcp_abort_on_overflow字段來(lái)決定是直接丟棄,還是直接reset.此時(shí),客戶端發(fā)送了ACK, 那么客戶端認(rèn)為三次握手完成,它認(rèn)為服務(wù)端已經(jīng)準(zhǔn)備好了接收數(shù)據(jù)的準(zhǔn)備。但此時(shí)服務(wù)端可能因?yàn)槿B接隊(duì)列滿了而無(wú)法將連接放入,會(huì)重新發(fā)送第2步的syn+ack, 如果這時(shí)有數(shù)據(jù)到來(lái),服務(wù)器TCP模塊會(huì)將數(shù)據(jù)存入隊(duì)列中。一段時(shí)間后,client端沒(méi)收到回復(fù),超時(shí),連接異常,client會(huì)主動(dòng)關(guān)閉連接。
“三次握手,四次揮手”redis實(shí)例分析
我在dev機(jī)器上部署redis服務(wù),端口號(hào)為6379,
通過(guò)tcpdump工具獲取數(shù)據(jù)包,使用如下命令
1 2 | tcpdump -w /tmp/a.cap port 6379 -s0 -w把數(shù)據(jù)寫入文件,-s0設(shè)置每個(gè)數(shù)據(jù)包的大小默認(rèn)為68字節(jié),如果用-S 0則會(huì)抓到完整數(shù)據(jù)包 |
在dev2機(jī)器上用redis-cli訪問(wèn)dev:6379, 發(fā)送一個(gè)ping, 得到回復(fù)pong
停止抓包,用tcpdump讀取捕獲到的數(shù)據(jù)包
1 2 | tcpdump -r /tmp/a.cap -n -nn -A -x| vim - (-x 以16進(jìn)制形式展示,便于后面分析) |
共收到了7個(gè)包。
抓到的是IP數(shù)據(jù)包,IP數(shù)據(jù)包分為IP頭部和IP數(shù)據(jù)部分,IP數(shù)據(jù)部分是TCP頭部加TCP數(shù)據(jù)部分。
IP的數(shù)據(jù)格式為:
它由固定長(zhǎng)度20B+可變長(zhǎng)度構(gòu)成。
1 2 3 4 5 | 10:55:45.662077 IP dev2.39070 > dev.6379: Flags [S], seq 4133153791, win 29200, options [mss 1460,sackOK,TS val 2959270704 ecr 0,nop,wscale 7], length 0 ????????0x0000:??4500 003c 08cf 4000 3606 14a5 0ab3 b561 ????????0x0010:??0a60 5cd4 989e 18eb f65a ebff 0000 0000 ????????0x0020:??a002 7210 872f 0000 0204 05b4 0402 080a ????????0x0030:??b062 e330 0000 0000 0103 0307 |
對(duì)著IP頭部格式,來(lái)拆解數(shù)據(jù)包的具體含義。
字節(jié)值 | 字節(jié)含義 |
---|---|
0x4 | IP版本為ipv4 |
0x5 | 首部長(zhǎng)度為5 * 4字節(jié)=20B |
0x00 | 服務(wù)類型,現(xiàn)在基本都置為0 |
0x003c | 總長(zhǎng)度為3*16+12=60字節(jié),上面所有的長(zhǎng)度就是60字節(jié) |
0x08cf | 標(biāo)識(shí)。同一個(gè)數(shù)據(jù)報(bào)的唯一標(biāo)識(shí)。當(dāng)IP數(shù)據(jù)報(bào)被拆分時(shí),會(huì)復(fù)制到每一個(gè)數(shù)據(jù)中。 |
0x4000 | 3bit 標(biāo)志 + 13bit 片偏移 。3bit 標(biāo)志對(duì)應(yīng) R、DF、MF。目前只有后兩位有效,DF位:為1表示不分片,為0表示分片。MF:為1表示“更多的片”,為0表示這是最后一片。13bit 片位移:本分片在原先數(shù)據(jù)報(bào)文中相對(duì)首位的偏移位。(需要再乘以8 ) |
0x36 | 生存時(shí)間TTL。IP報(bào)文所允許通過(guò)的路由器的最大數(shù)量。每經(jīng)過(guò)一個(gè)路由器,TTL減1,當(dāng)為 0 時(shí),路由器將該數(shù)據(jù)報(bào)丟棄。TTL 字段是由發(fā)送端初始設(shè)置一個(gè) 8 bit字段.推薦的初始值由分配數(shù)字 RFC 指定。發(fā)送 ICMP 回顯應(yīng)答時(shí)經(jīng)常把 TTL 設(shè)為最大值 255。TTL可以防止數(shù)據(jù)報(bào)陷入路由循環(huán)。 此處為54. |
0x06 | 協(xié)議類型。指出IP報(bào)文攜帶的數(shù)據(jù)使用的是哪種協(xié)議,以便目的主機(jī)的IP層能知道要將數(shù)據(jù)報(bào)上交到哪個(gè)進(jìn)程。TCP 的協(xié)議號(hào)為6,UDP 的協(xié)議號(hào)為17。ICMP 的協(xié)議號(hào)為1,IGMP 的協(xié)議號(hào)為2。該 IP 報(bào)文攜帶的數(shù)據(jù)使用 TCP 協(xié)議,得到了驗(yàn)證。 |
0x14a5 | 16bitIP首部校驗(yàn)和。 |
0x0ab3 b561 | 32bit源ip地址。 |
0x0a60 5cd4 | 32bit目的ip地址。 |
剩余的數(shù)據(jù)部分即為TCP協(xié)議相關(guān)的。TCP也是20B固定長(zhǎng)度+可變長(zhǎng)度部分。
字節(jié)值 | 字節(jié)含義 |
---|---|
0x989e | 16bit源端口。1161616+81616+1416+11=39070 |
0x18eb | 16bit目的端口6379 |
0xf65a ebff | 32bit序列號(hào)。4133153791 |
0x0000 0000 | 32bit確認(rèn)號(hào)。 |
0xa | 4bit首部長(zhǎng)度,以4byte為單位。共10*4=40字節(jié)。因此TCP報(bào)文的可選長(zhǎng)度為40-20=20 |
0b000000 | 6bit保留位。目前置為0. |
0b000010 | 6bitTCP標(biāo)志位。從左到右依次是緊急 URG、確認(rèn) ACK、推送 PSH、復(fù)位 RST、同步 SYN 、終止 FIN。 |
0x7210 | 滑動(dòng)窗口大小,滑動(dòng)窗口即tcp接收緩沖區(qū)的大小,用于tcp擁塞控制。29200 |
0x872f | 16bit校驗(yàn)和。 |
0x0000 | 緊急指針。僅在 URG = 1時(shí)才有意義,它指出本報(bào)文段中的緊急數(shù)據(jù)的字節(jié)數(shù)。當(dāng) URG = 1 時(shí),發(fā)送方 TCP 就把緊急數(shù)據(jù)插入到本報(bào)文段數(shù)據(jù)的最前面,而在緊急數(shù)據(jù)后面的數(shù)據(jù)仍是普通數(shù)據(jù)。 |
可變長(zhǎng)度部分,協(xié)議如下:
字節(jié)值 | 字節(jié)含義 |
---|---|
0x0204 05b4 | 最大報(bào)文長(zhǎng)度為,05b4=1460. 即可接收的最大包長(zhǎng)度,通常為MTU減40字節(jié),IP頭和TCP頭各20字節(jié) |
0x0402 | 表示支持SACK |
0x080a b062 e330 0000 0000 | 時(shí)間戳。Ts val=b062 e330=2959270704, ecr=0 |
0x01 | 無(wú)操作 |
0x03 0307 | 窗口擴(kuò)大因子為7. 移位7, 乘以128 |
這樣第一個(gè)包分析完了。dev2向dev發(fā)送SYN請(qǐng)求。也就是三次握手中的第一次了。
SYN seq(c)=4133153791
第二個(gè)包,dev響應(yīng)連接,ack=4133153792. 表明dev下次準(zhǔn)備接收這個(gè)序號(hào)的包,用于tcp字節(jié)注的順序控制。dev(也就是server端)的初始序號(hào)為seq=4264776963, syn=1.SYN ack=seq(c)+1 seq(s)=4264776963
第三個(gè)包,client包確認(rèn),這里使用了相對(duì)值應(yīng)答。seq=4133153792, 等于第二個(gè)包的ack. ack=4264776964.ack=seq(s)+1, seq=seq(c)+1
至此,三次握手完成。接下來(lái)就是發(fā)送ping和pong的數(shù)據(jù)了。
接著第四個(gè)包。
1 2 3 4 5 6 | 10:55:48.090073 IP dev2.39070 > dev.6379: Flags [P.], seq 1:15, ack 1, win 229, options [nop,nop,TS val 2959273132 ecr 3132256230], length 14 ????????0x0000:??4500 0042 08d1 4000 3606 149d 0ab3 b561 ????????0x0010:??0a60 5cd4 989e 18eb f65a ec00 fe33 5504 ????????0x0020:??8018 00e5 4b5f 0000 0101 080a b062 ecac ????????0x0030:??bab2 6fe6 2a31 0d0a 2434 0d0a 7069 6e67 ????????0x0040:??0d0a |
tcp首部長(zhǎng)度為32B, 可選長(zhǎng)度為12B. IP報(bào)文的總長(zhǎng)度為66B, 首部長(zhǎng)度為20B, 因此TCP數(shù)據(jù)部分長(zhǎng)度為14B. seq=0xf65a ec00=4133153792
ACK, PSH. 數(shù)據(jù)部分為2a31 0d0a 2434 0d0a 7069 6e67 0d0a
1 2 3 4 5 6 | 0x2a31???????? -> *1 0x0d0a???????? -> rn 0x2434???????? -> $4 0x0d0a???????? -> rn 0x7069 0x6e67??-> ping 0x0d0a???????? -> rn |
dev2向dev發(fā)送了ping數(shù)據(jù),第四個(gè)包完畢。
第五個(gè)包,dev2向dev發(fā)送ack響應(yīng)。
序列號(hào)為0xfe33 5504=4264776964, ack確認(rèn)號(hào)為0xf65a ec0e=4133153806=(4133153792+14).
第六個(gè)包,dev向dev2響應(yīng)pong消息。序列號(hào)fe33 5504,確認(rèn)號(hào)f65a ec0e, TCP頭部可選長(zhǎng)度為12B, IP數(shù)據(jù)報(bào)總長(zhǎng)度為59B, 首部長(zhǎng)度為20B, 因此TCP數(shù)據(jù)長(zhǎng)度為7B.
數(shù)據(jù)部分2b50 4f4e 470d 0a, 翻譯過(guò)來(lái)就是+PONGrn
.
至此,Redis客戶端和Server端的三次握手過(guò)程分析完畢。
總結(jié)
“三次握手,四次揮手”看似簡(jiǎn)單,但是深究進(jìn)去,還是可以延伸出很多知識(shí)點(diǎn)的。比如半連接隊(duì)列、全連接隊(duì)列等等。以前關(guān)于TCP建立連接、關(guān)閉連接的過(guò)程很容易就會(huì)忘記,可能是因?yàn)橹皇撬烙浻脖沉藥讉€(gè)過(guò)程,沒(méi)有深入研究背后的原理。
所以,“三次握手,四次揮手”你真的懂了嗎?
參考資料
【redis】https://segmentfault.com/a/1190000015044878
【tcp option】https://blog.csdn.net/wdscq1234/article/details/52423272
【滑動(dòng)窗口】https://www.zhihu.com/question/32255109
【全連接隊(duì)列】http://jm.taobao.org/2017/05/25/525-1/
【client fooling】 https://github.com/torvalds/linux/commit/5ea8ea2cb7f1d0db15762c9b0bb9e7330425a071
【backlog RECV_Q】http://blog.51cto.com/59090939/1947443
【定時(shí)器】https://www.cnblogs.com/menghuanbiao/p/5212131.html
【隊(duì)列圖示】https://www.itcodemonkey.com/article/5834.html
【tcp flood攻擊】https://www.cnblogs.com/hubavyn/p/4477883.html
【MSS MTU】https://blog.csdn.net/LoseInVain/article/details/53694265