一、TCP報文格式
TCP報文格式圖:

上圖中有幾個字段介紹下:
(1)序號:Seq序號,占32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。
(2)確認序號:Ack序號,占32位,只有ACK標志位為1時,確認序號字段才有效,Ack=Seq+1。
(3)標志位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義如下:
(A)URG:緊急指針(urgent pointer)有效。
(B)ACK:確認序號有效。
(C)PSH:接收方應該盡快將這個報文交給應用層。
(D)RST:重置連接。
(E)SYN:發起一個新連接。
(F)FIN:釋放一個連接。
二、三次握手的機制與過程
所謂三次握手(Three-Way Handshake)即建立TCP連接,就是指建立一個TCP連接時,需要客戶端和服務端總共發送3個包以確認連接的建立。在socket編程中,這一過程由客戶端執行connect來觸發,整個流程如下圖所示:

假設 client 的連接請求延遲,client 就會發出第二次連接請求,server 端回復后則建立了連接,進行通信。等到通信結束,連接關閉,此時有可能第一次的連接請求才到 server 端,那么此時server 回復報文之后,就認為連接已建立,但在 client 端看來, 根本沒有發起連接請求(連接建立重試后,已完成通信並關閉連接),所以會忽略 server 端的報文,也不會向這個鏈接發送數據。此時對於 server 端來說,就會因為維護這個鏈接而導致資源浪費。
三次握手的詳細過程如下:
(1)第一次握手:Client將標志位SYN置為1,隨機產生一個值seq=J,並將該數據包發送給Server,Client進入SYN_SENT狀態,等待Server確認。
(2)第二次握手:Server收到數據包后由標志位SYN=1知道Client請求建立連接,Server將標志位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求,Server進入SYN_RCVD狀態。
(3)第三次握手:Client收到確認后,檢查ack是否為J+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨后Client與Server之間可以開始傳輸數據了。
關於SYN攻擊:
在三次握手過程中,Server發送SYN-ACK之后,收到Client的ACK之前的TCP連接稱為半連接(half-open connect),此時Server處於SYN_RCVD狀態,當收到ACK后,Server轉入ESTABLISHED狀態。SYN攻擊就是Client在短時間內偽造大量不存在的IP地址,並向Server不斷地發送SYN包,Server回復確認包,並等待Client的確認,由於源地址是不存在的,因此,Server需要不斷重發直至超時,這些偽造的SYN包將產時間占用未連接隊列,導致正常的SYN請求因為隊列滿而被丟棄,從而引起網絡堵塞甚至系統癱瘓。SYN攻擊時一種典型的DDOS攻擊,檢測SYN攻擊的方式非常簡單,即當Server上有大量半連接狀態且源IP地址是隨機的,則可以斷定遭到SYN攻擊了,使用如下命令可以讓之現行:
#netstat -nap | grep SYN_RECV
整個TCP狀態圖示意如下:
三、 Linux中TCP握手過程實現
在Linux應用編程如果設置為非阻塞模式,則連接時,connect發送SYN包后立即返回-EINPROGRESS,表示操作正在處理中;隨后應用可以在connect返回后做一些其它的處理,最后在select函數中來捕獲socket的連接、讀寫、異常事件以觸發相關操作,下面我們看看內核中的相關實現:
一、客戶端支持
client發送2個包,一個SYN包,一個對服務器的響應ACK包。
client函數調用鏈:connect-->sys_connect->inet_stream_connect->tcp_connect...
看inet_stream_connect中實現的部分代碼段:
switch (sock->state) { ... /*此處調用tcp_connect函數發送SYN包*/ err = sk->prot->connect(sk, uaddr, addr_len); if (err < 0) //出錯則退出 goto out; sock->state = SS_CONNECTING; /* 此處僅設置socket的狀態為SS_CONNECTING表示連接狀態正在處理; * 不同之處在於非阻塞情況下,返回值設置為-EINPROGRESS表示操作正在處理 * 而阻塞式情況則在獲得ACK包后將返回值置為-EALREADY. */ err = -EINPROGRESS; break; } timeo = sock_sndtimeo(sk, flags&O_NONBLOCK); //注意,如果此時設置了非阻塞選項,則timeo返回0 //如果socket對應的sock狀態是SYN包已發送或收到SYN包並發送了ACK包,並等待對端發送第三此的ACK包 if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) { /* 錯誤返回碼err前面已經設置 */ if (!timeo || !inet_wait_for_connect(sk, timeo)) /*注意上面所判斷的2中情況,1、如果是非阻塞模式,則!timeo為1,則直接跳到out返回-EINPROGRESS結束connect函數 2、若為阻塞模式,則在inet_wait_for_connect函數中通過schedule_timeout函數放棄cpu控制權睡眠,等待服務器端 發送ACK響應包后被喚醒繼續處理。如果沒有異常出現,則置socket狀態為SS_CONNECTED,表示連接成功,正確返回 */ goto out; err = sock_intr_errno(timeo); if (signal_pending(current)) /*處理未決信號*/ goto out; } ... sock->state = SS_CONNECTED; err = 0; out: release_sock(sk); return err;
上面的描述有一個問題:對服務器的響應ACK包是什么時候發送的?對於非阻塞模式,應該是應用處理過程中的某個異步時間;對於阻塞模式,則是在inet_wait_for_connect函數中睡眠時處理。即網卡在收到對方的ack包后,上傳給對應的socket時發送服務器的響應ACK包,函數調用鏈為:netif_rx-->net_rx_action-->...(IP層處理)-->tcp_v4_rcv-->tcp_v4_do_rcv-->tcp_rcv_state_process-->tcp_rcv_synsent_state_process-->tcp_send_synack-->tcp_transmit_skb...
發送SYN包后,socket對應的sock的狀態變成TCPF_SYN_SENT,網卡收到服務器的ack傳到tcp層時,根據TCPF_SYN_SENT狀態,做相關判斷后再發送用於第三次握手的ack包。至此,將socket的狀態改為連接建立,即TCP_ESTABLISHED。 具體的代碼大家可以根據我提供的函數調用鏈查看。
注意,以TCPF_前綴開頭的狀態都表示是中間狀態,而已TCP_為前綴的狀態才是socket的一個相對穩定的狀態。
二、服務器端支持
服務器端此時必須是監聽狀態,則其函數調用鏈為:
netif_rx-->net_rx_action-->...(IP層處理)-->tcp_v4_rcv-->tcp_v4_do_rcv-->
tcp_rcv_state_process-->tcp_v4_conn_request-->tcp_v4_send_synack...
在tcp_v4_conn_request,中部分代碼如下:
case TCP_LISTEN: if(th->ack) /*監聽時收到的ack包都丟棄?*/ return 1; if(th->syn) {/*如果是SYN包,則調用tcp_v4_conn_request*/ if(tp->af_specific->conn_request(sk, skb) < 0) return 1; ...