深入理解TCP協議運行過程和系統調用過程
本次實驗主要從以下幾個方面展開:
- TCP/IP 分層結構
- TCP協議運行過程中的系統調用
- 跟蹤驗證
一、TCP/IP分層結構
相信所有考過研的小伙伴對TCP/IP模型的分層結構一定不陌生。我們在本科學習的時候,其實學習的5層結構,IEEE定義的網絡模型結構是7層,而在真正生活中應用的則是4層結構,這4層分別是:應用層,傳輸層,網際層、網絡接口層。這里我總結了四層、五層和七層結構的對比圖,有遺忘的小伙伴可以看看。如下

二、TCP協議運行過程中的系統調用
通常來講,TCP協議的運行過程可以用“三次握手”和“四次揮手”來總結
-
TCP協議的“三次握手”
(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之間可以開始傳輸數據了。

-
TCP協議的“四次揮手”
(1)Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。
(2)Server收到FIN后,發送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN占用一個序號),Server進入CLOSE_WAIT狀態。
(3)Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。
(4)Client收到FIN后,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手。

-
TCP的運行過程的轉態轉換

我們從上圖的轉態轉換以及上次實驗的結果得出,典型的TCP客戶端和服務器應用程序發出一系列TCP系統調用以實現某些功能,這些系統調用包括socket()、bind()、listen()、accept()、connect()、send()、receieve()、close()、shutdown()。當TCP應用運行的時候,會發生如下的事情:
- 首先,套接字接受進行的任何TCP系統調用。套接字層驗證TCP應用程序傳遞的參數的正確性。
- 套接字層下面是協議層,其中包含協議的實際實現。當套接字層對協議層進行調用時,它確保對兩個層之間共享的 數據結構具有獨占訪問權限。這樣做是為了避免任何數據結構損壞。
- 各種網絡設備驅動程序運行在接口層,該接口從數據鏈路層接受數據並向物理鏈路傳輸數據。

Socket
socket (struct proc ∗p, struct socket_args ∗uap, int retval)
struct sock_args
{
int domain,
int type,
int protocol;
};
在socket系統調用中 :
- p是指向socket調用的進程proc結構的指針
- uap是指向socket_args結構的指針,該結構包含在socket系統調用中傳遞給進程的參數
- retval是系統調用的返回值
socket系統調用創建通過分配新的描述符來創建新的套接字。新的描述符返回到調用過程。任何后續的系統調用都使用創建的套接字標識。套接字系統調用還將協議分配給創建的套接字描述符。domain,type和protocol參數值指定要分配給創建的套接字的族,類型和協議。

Bind
bind (struct proc ∗p, struct bind_args ∗uap, int ∗retval)
struct bind_args
{ int s;
caddr_t name;
int namelen;
};
在bind系統調用中:
- 是套接字描述符
- name是指向包含網絡傳輸地址的緩沖區的指針
- namelen代表緩沖區的大小
bind系統調用將本地網絡傳輸地址與套接字相關聯。當客戶端進程發出連接系統調用使,內核負責進行隱式綁定。服務器進程通常必須發出明確的綁定請求,然后才能接受連接或開始於客戶端的通信

Listen
listen (struct proc ∗p, struct listen_args ∗uap, int ∗retval);
struct listen_args
{ int s;
int backlog;
};
在listen系統調用中:
- s是套接字描述符
- backlog是套接字上連接數的隊列限制
listen系統調用向協議指示服務器進程已准備好接受套接字上的任何新的傳入連接。但是可以排隊的連接數有限制,在此之后,任何其他連接請求都將被忽略。

Accept
accept(struct proc ∗p, struct accept_args ∗uap, int ∗retval);
struct accept_args
{
int s;
caddr_t name;
int ∗anamelen;
};
在listen系統調用中:
- s是套接字描述符
- name 是一個緩沖區,其中包含外部主機的網絡傳輸地址
- anamelen是name緩沖區的大小
accept系統調用是等待傳入連接的阻塞調用。一旦處理了連接請求,accept將返回一個新的套接字描述符。這個新的套接字已連接到客戶端,其他套接字仍保持LISTEN狀態以接受進一步的連接

Connect
connect (struct proc ∗p, struct connect_args ∗uap, int ∗retval);
struct connect_args
{
int s;
caddr_t name;
int namelen;
};
在connect系統調用:
- s是套接字描述符
- name是指向具有外部IP /端口地址對的緩沖區的指針
- namelen是緩沖的大小
客戶端進程通常會調用connect系統調用以連接到服務器進程。如果客戶端進程在啟動連接之前未顯式發出bind系統調用,則堆棧將處理本地套接字上的隱式綁定。

Shutdown
shutdown (struct proc ∗p, struct shutdown_args ∗uap, int ∗retval);
Struct shutdown_args
{
int s;
int how;
}
在shutdown系統調用中:
- s是套接字描述符
- how指定要關閉連接的哪一端。值分別為0,1,2,分別用於指定如何關閉從客戶端到服務端的連接,從服務端向客戶端的連接,或者把兩個連接都關閉。
shutdown系統調用將關閉連接的一端或兩端。如果需要關閉的是服務端指向客戶端的連接,那么將丟棄接收緩沖區中存在的所有數據,並關閉連接的那一端。如果關閉的是客戶端指向服務端的連接,TCP發送所有剩余數據,然后終止連接的寫端

Close
soo_close(struct file ∗fp , struct proc ∗p);
在close系統調用中:
- fp是指向文件結構的指針
- p是指向調用過程的proc結構的指針
close系統調用將關閉或終止套接字上的所有掛起的連接

Send
sendmsg ( struct proc∗p, struct sendmsg_args ∗uap, int retval);
struct sendmsg_args
{
int s;
caddr_t msg;
int flags;
};
在send系統調用中:
- s是套接字描述符
- msg是指向msghdr結構的指針
- flags是控制信息

Receieve
recvmsg(struct proc ∗p, struct recvmsg_args ∗uap , int ∗retval);
struct recvmsg_args
{
int s,
struct msghdr ∗msg,
int flags,
};
在Receieve系統調用中
- s 是套接字描述符
- msg是指向msghdr結構的指針
- flags是控制信息

三、跟蹤驗證
- 首先,在啟動qemu
qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -append nokaslr -s -S - 然后另開一個命令行輸入
gdb,進入調試模式,然后輸入file linux-5.0.1/vmlinux加載符號表, - 設置斷點。按照上面的分析,如下圖所示

- 輸入
target remote:1234建立連接 - 不停的按c回車運行,直到運行結束。
運行結果如下圖所示:


結合上面的tcp運行過程和以及實際跟蹤情況來看,會調用socket系統調用,這個系統調用對應着 sys_connect和sys_accept,這兩個函數又對應着剛剛我們設置短點的函數tcp_v4_connect和inet_csk_accept函數。
inet_csk_accept函數會從客戶端發出的請求中取出一個請求,如果沒有客戶端發出連接請求,那么服務端就會調用inet_csk_wait_for_connet函數並阻塞監聽端口,等待客戶端發出連接請求。
除了上面這個,服務端還會使用到sys_socket,sys_socket在其內部又調用了socket_create用於創建套接字
一旦請求來到,服務端就會調用sys_connect ,這個函數又會調用inet_csk_accept函數,這個函
tcp_v4_connect函數用於發起一個tcp連接請求,主要是客戶端會調用這個
實驗參考:
https://developer.ibm.com/articles/au-tcpsystemcalls/
https://github.com/mengning/net/tree/master
