1.TCP三次握手建立連接

在TCP中,面向連接的傳輸需要經過三個階段:連接建立、數據傳輸和連接終止。

三次握手建立連接

在我們的例子中,一個稱為客户的應用程序希望使用TCP作為運輸層協議來和另一個稱為服務器的應用程序建立連接。

這個過程從服務器開始。服務器程序告訴它的TCP自己已準備好接受連接。這個請求稱為被動打開請求。雖然服務器的TCP已準備好接受來自世界上任何一個機器的連接,但是它自己並不能完成這個連接。

客户程序發出的請求稱為主動打開。打算與某個開放的服務器進行連接的客户告訴它的TCP,自己需要連接到某個特定的服務器上。TCP現在可以開始進行如下圖所示的三向握手過程。

wiresharktcp三次握手分析_客户端

 

每個報文段的首部字段值都是完整的,並且可能還有一些可選字段也有相應的數值,不過,為了方便我們理解每個階段,只畫出了其中很少幾個字段。圖中顯示了序號、確認號、控制標誌(只有那些置1的)以及有關的窗口大小。這個階段的三個步驟如下所示。

1.客户發送第一個報文段(SYN報文段),在這個報文段中只有SYN標誌置為1。這個報文段的作用是同步序號。在我們的例子中,客户選擇了一個隨機數作為第一個序號,並把這個序號發送給服務器。這個序號稱為初始序號(ISN)。 請注意,這個報文段中不包括確認號,也沒有定義窗口大小。只有當一個報文段中包含了確認時,定義窗口大小才有意義。這個報文段還可以包含一些選項。請注意,SYN報文段是一個控制報文段,它不攜帶任何數據。但是,它消耗了一個序號。當數據傳送開始時,序號就應當加1。我們可以説,SYN報文段不包含真正的數據,但是我們可以想象它包含了一個虛字節。

 SYN報文段不攜帶任何數據,但是它要消耗一個序號。

2.服務器發送第二個報文段,即SYN + ACK報文段,其中的兩個標誌(SYN和ACK)置為1。這個報文段有兩個目的。首先,它是另一個方向上通信的SYN報文段。服務器使用這個報文段來同步它的初始序號,以便從服務器向客户發送字節。其次,服務器還通過ACK標誌來確認已收到來自客户端的SYN報文段,同時給出期望從客户端收到的下一個序號。因為這個報文段包含了確認,所以它還需要定義接收窗口大小,即rwnd (由客户端使用)。

SYN+ACK報文段不攜帶數據,但要消耗一個序號。

3.客户發送第三個報文段。這僅僅是一一個ACK報文段。它使用ACK標誌和確認號:字段來確認收到了第二個報文段。請注意,這個報文段的序號和SYN報文段使用的序號一樣,也就是説,這個ACK報文段不消耗任何序號。客户還必須定義服務器的窗口大小。在某些實現中,連接階段的第三個報文段可以攜帶客户的第一一個數據塊。在這種情況下,第三個報文段必須有一個新的序號來表示數據中的第一個字節的編號。通常,第三個報文段不攜帶數據,因而不消耗序號。

ACK報文段如果不攜帶數據就不消耗序號。

 

2.TCP協議三次握手相關源代碼

TCP的三次握手從用户程序的角度看就是客户端connect和服務端accept建立起連接時背後的完成的工作,在內核socket接口層這兩個socket API函數對應着sys_connect和sys_accept函數,進一步對應着sock->opt->connect和sock->opt->accept兩個函數指針,在TCP協議中這兩個函數指針對應着tcp_v4_connect函數和inet_csk_accept函數。

 

tcp_v4_connect函數

客户端調用tcp_v4_connect函數發起一個TCP連接。tcp_v4_connect函數設置了 TCP_SYN_SENT,並調用了 tcp_connect(sk)函數來實際構造SYN併發送出去。

tcp_connect函數的作用為具體負責構造一個攜帶SYN標誌位的TCP頭併發送出去,同時還設置了計時器超時重發。

140/* This will initiate an outgoing connection. */
141int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
142{
...
171    rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
172                  RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
173                  IPPROTO_TCP,
174                  orig_sport, orig_dport, sk);
...
215    /* Socket identity is still unknown (sport may be zero).
216     * However we set state to SYN-SENT and not releasing socket
217     * lock select source port, enter ourselves into the hash tables and
218     * complete initialization after this.
219     */
220    tcp_set_state(sk, TCP_SYN_SENT);//設置TCP_SYN_SENT
...
227    rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
228                   inet->inet_sport, inet->inet_dport, sk);
...
246    err = tcp_connect(sk);//實際構造SYN報文段,併發送SYN報文段
...
264}
265EXPORT_SYMBOL(tcp_v4_connect);


inet_csk_accept函數

服務端調用inet_csk_accept函數,從隊列中取出一個連接請求。

如果隊列為空則通過inet_csk_wait_for_connect阻塞住等待客户端的連接,inet_csk_wait_for_connec無限for循環,一旦出現連接請求則跳出循環。

289/*
290 * This will accept the next outstanding connection.
291 */
292struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
293{
294    struct inet_connection_sock *icsk = inet_csk(sk);
295    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
296    struct sock *newsk;
297    struct request_sock *req;
298    int error;
299
300    lock_sock(sk);
301
302    /* We need to make sure that this socket is listening,
303     * and that it has something pending.
304     */
305    error = -EINVAL;
306    if (sk->sk_state != TCP_LISTEN)
307        goto out_err;
308
309    /* Find already established connection */
310    if (reqsk_queue_empty(queue)) {                  
311        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
...
318        error = inet_csk_wait_for_connect(sk, timeo);//隊列為空無限for循環等待
319        if (error)
320            goto out_err;
321    }
322    req = reqsk_queue_remove(queue);
323    newsk = req->sk;
324
325    sk_acceptq_removed(sk);
326    if (sk->sk_protocol == IPPROTO_TCP && queue->fastopenq != NULL) {
327        spin_lock_bh(&queue->fastopenq->lock);
328        if (tcp_rsk(req)->listener) {
329            /* We are still waiting for the final ACK from 3WHS
330             * so can't free req now. Instead, we set req->sk to
331             * NULL to signify that the child socket is taken
332             * so reqsk_fastopen_remove() will free the req
333             * when 3WHS finishes (or is aborted).
334             */
335            req->sk = NULL;
336            req = NULL;
337        }
...
344    return newsk;
...
350}

 

3.跟蹤調試代碼驗證握手過程

給tcp_v4_connect函數和inet_csk_accept函數,還有__sys_socket,__sys_bind,__sys_listen,__sys_connect打上斷點,追蹤代碼執行過程

wiresharktcp三次握手分析_服務器_02

 

追蹤TCP通信的代碼發現,服務器端先執行__sys_socket函數創建套接字,接着調用__sys_bind函數綁定套接字和服務器的地址,然後調用__sys_connect監聽,接着會調用__sys_accept4,進入inet_csk_accept函數,此時還未有客户端請求連接,所以隊列為空,進入inet_csk_wait_for_connect的for循環,直到客户端請求連接。

wiresharktcp三次握手分析_TCP_03

 

下面是對客户端的追蹤,客户端先調用__sys_socket創建套接字,然後調用__sys_connect請求建立連接,其中tcp_v4_connect是真正實現連接的函數,此函數設置構造SYN,併發出去。

服務器端調用inet_csk_accept,結束for循環,從隊列中取出連接請求,建立連接。

wiresharktcp三次握手分析_wiresharktcp三次握手分析_04

 

在設置斷點追蹤的同時,抓包驗證三次握手的具體過程。可以發現,客户端調用__sys_connect將連接請求SYN報文段發出,是第一次握手;之後服務器調用__sys_accpt4響應連接請求,完成後兩次握手過程。從調用__sys_connect到__sys_accept4函數響應連接請求並返回之間就是三次握手的時間。