日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

从抓包的角度分析connect()函数的连接过程

發布時間:2023/12/10 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从抓包的角度分析connect()函数的连接过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章來源:點擊打開鏈接

這篇文章主要是從tcp連接建立的角度來分析客戶端程序如何利用connect函數和服務端程序建立tcp連接的,了解connect函數在建立連接的過程中底層協議棧做了哪些事情。

tcp三次握手

在正式介紹connect函數時,我們先來看一下tcp三次握手的過程,下面這個實驗是客戶端通過telnet遠程登錄服務端的例子,telnet協議是基于tcp協議,我們可以通過wireshark抓包工具看到客戶端和服務端之間三次握手的過程,12.1.1.1是客戶端的ip地址,12.1.1.2是服務端的ip地址。

下面是我們通過wireshark抓取到的tcp三次握手的數據包:

我們看到客戶端遠程登錄服務端時,首先發送了一個SYN報文,其中目標端口為23(遠程登錄telnet協議使用23端口),初始序號seq = 0,并設置自己的窗口rwnd = 4128(rwnd是一個對端通告的接收窗口,用于流量控制)

然后服務端回復了一個SYN + ACK報文,初始序號seq = 0ack = 1(在前一個包的seq基礎上加1),同時也設置自己的窗口rwnd = 4128

然后客戶端收到服務端的SYN + ACK報文時,回復了一個ACK報文,表示確認建立tcp連接,序號為seq = 1,?ack = 1(在前一個包的seq基礎上加1), 設置窗口rwnd = 4128,此時客戶端和服務端之間已經建立tcp連接。

connect函數

前面我們在介紹tcp三次握手的時候說過,客戶端在跟服務端建立tcp連接時,通常是由客戶端主動向目標服務端發起tcp連接建立請求,服務端被動接受tcp連接請求;同時服務端也會發起tcp連接建立請求,表示服務端希望和客戶端建立連接,然后客戶端會接受連接并發送一個確認,這樣雙方就已經建立好連接,可以開始通信。

這里說明一下:可能有的小伙伴會感到疑惑,為啥服務端也要跟客戶端建立連接呢?其實這跟tcp采用全雙工通信的方式有關。對于全雙工通信,簡單來說就是兩端可以同時收發數據,如下圖所示:

我們再回到正題,那么在網絡編程中,肯定也有對應的函數做到跟上面一樣的事情,沒錯,就是connect(連接)。顧名思義,connect函數就是用于客戶端程序和服務端程序建立tcp連接的。

一般來說,客戶端使用connect函數跟服務端建立連接,肯定要指定一個ip地址和端口號(相當于客戶端的身份標識),要不然服務端都不知道你是誰?憑什么跟你建立連接。同時還得指明服務端的ip地址和端口號,也就是說,你要跟誰建立連接。

connect函數原型:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

參數說明:
sockfd:客戶端的套接字文件描述符
addr:要連接的套接字地址,這是一個傳入參數,指定了要連接的套接字地址信息(例如IP地址和端口號)
addrlen:是一個傳入參數,參數addr的大小,即sizeof(addr)

返回值說明:連接建立成功返回0,失敗返回-1并設置errno

connect函數在建立tcp連接的過程中用到了一個非常重要的隊列,那就是未決連接隊列,這個隊列用來管理tcp的連接,包括已完成三次握手的tcp連接和未完成三次握手的tcp連接,下面我們就來詳細介紹一下未決連接隊列。

未決連接隊列
未決連接隊列是指服務器接收到客戶端的連接請求,但是尚未被處理(也就是未被accept,后面會說)的連接,可以理解為未決連接隊列是一個容器,這個容器存儲著這些尚未被處理的鏈接。

當一個客戶端進程使用 connect 函數發起請求后,服務器進程就會收到連接請求,然后檢查未決連接隊列是否有空位,如果未決隊列滿了,就會拒絕連接,那么客戶端調用的connect 函數返回失敗。

如果未決連接隊列有空位,就將該連接加入未決連接隊列。當 connect 函數成功返回后,表明tcp的“三次握手”連接已完成,此時accept函數獲取到一個客戶端連接并返回。

在上圖中,在未決連接隊列中又分為2個隊列:

未完成隊列(未決隊列):即客戶端已經發出SYN報文并到達服務器,但是在tcp三次握手連接完成之前,這些套接字處于SYN_RCVD狀態,服務器會將這些套接字加入到未完成隊列。

已完成隊列:即剛剛完成tcp三次握手的tcp連接,這些套接字處于ESTABLISHED狀態,服務器會將這些套接字加入到已完成隊列。

我們來看一下連接建立的具體過程,如圖所示:

服務端首先調用listen函數監聽客戶端的連接請求,然后調用accept函數阻塞等待取出未決連接隊列中的客戶端連接,如果未決連接隊列一直為空,這意味著沒有客戶端和服務器建立連接,那么accept就會一直阻塞。

當客戶端一調用connect函數發起連接時,如果完成tcp三次握手,那么accept函數會取出一個客戶端連接(注意:是已經建立好的連接)然后立即返回。

上面就是客戶端和服務端在網絡中的狀態變遷的具體過程,前面我們在學習tcp三次握手的過程中還知道,服務端和客戶端在建立連接的時候會設置自己的一個接收緩沖區窗口rwnd的大小。

服務端在發送SYN + ACK數據報文時會設置并告知對方自己的接收緩沖區窗口大小,客戶端在發送ACK數據報文時也會設置并告知對方自己的接收緩沖區窗口大小。

注意,accept函數調用成功,返回的是一個已經完成tcp三次握手的客戶端連接。如果在三次握手的過程中(最后一步),服務端沒有接收到客戶端的ACK,則說明三次握手還沒有建立完成,accept函數依然會阻塞。

關于tcp三次握手連接建立的幾種狀態:SYN_SENTSYN_RCVDESTABLISHED
SYN_SENT:當客戶端調用connect函數向服務端發送SYN包時,客戶端就會進入?SYN_SENT狀態,并且還會等待服務器發送第二個SYN + ACK包,因此SYN_SENT狀態就是表示客戶端已經發送SYN包。

SYN_RCVD:當服務端接收到客戶端發送的SYN包并確認時,服務端就會進入?SYN_RCVD狀態,這是tcp三次握手建立的一個很短暫的中間狀態,一般很難看到,?SYN_RCVD狀態表示服務端已經確認收到客戶端發送的SYN包。

ESTABLISHED:該狀態表示tcp三次握手連接建立完成。

對于這兩個隊列需要注意幾點注意:

1.?未完成隊列和已完成隊列的總和不超過listen函數的backlog參數的大小。listen函數的簽名如下:

int listen(int sockfd, int backlog);

2.?一旦該連接的tcp三次握手完成,就會從未完成隊列加入到已完成隊列中

3.?如果未決連接隊列已滿,當又接收到一個客戶端SYN時,服務端的tcp將會忽略該SYN,也就是不會理客戶端的SYN,但是服務端并不會發送RST報文,原因是:客戶端tcp可以重傳SYN,并期望在超時前未決連接隊列找到空位與服務端建立連接,這當然是我們所希望看到的。如果服務端直接發送一個RST的話,那么客戶端的connect函數將會立即返回一個錯誤,而不會讓tcp有機會重傳SYN,顯然我們也并不希望這樣做。

但是不排除有些linux實現在未決連接隊列滿時,的確會發送RST。但是這種做法是不正確的,因此我們最好忽略這種情況,處理這種額外情況的代碼也會降低客戶端程序的健壯性。

connect函數出錯情況

由于connect函數是在建立tcp連接成功或失敗才返回,返回成功的情況本文上面已經介紹過了。這里我們介紹connect函數返回失敗的幾種情況:
第一種
當客戶端發送了SYN報文后,沒有收到確認則返回ETIMEDOUT錯誤,值得注意的是,失敗一次并不會馬上返回ETIMEDOUT錯誤。即當你調用了connect函數,客戶端發送了一個SYN報文,沒有收到確認就等6s后再發一個SYN報文,還沒有收到就等24s再發一個(不同的linux系統設置的時間可能有所不同,這里以BSD系統為主)。這個時間是累加的,如果總共等了75s后還是沒收到確認,那么客戶端將返回ETIMEDOUT錯誤。

對于linux系統,改變這個系統上限值也比較容易,由于需要改變系統配置參數,你需要root權限。
相關的命令是sysctl net.ipv4.tcp_syn_retries(針對于ipv4)。
在設置該值時還是要比較保守的,因為每次syn包重試的間隔都會增大(比如BSD類的系統實現中間隔會以2到3倍增加),所有tcp_syn_retries的一個微小變化對connect超時時間的影響都非常大,不過擴大這個值也不會有什么壞處,因為你代碼中設置的超時值都能夠生效。但是如果代碼中沒有設置connect的超時值,那么connect就會阻塞很久,你發現對端機器down掉的間隔就更長。
作者建議設置這個值到6或者7,最多8。6對應的connect超時為45s,7對應90s,8對應190s。

你能通過以下命令修改該值:

sysctl -w net.ipv4.tcp_syn_retries=6

查看該值的命令是:

sysctl net.ipv4.tcp_syn_retries

如果希望重啟后生效,將net.ipv4.tcp_syn_retries = 6放入/etc/sysctl.conf中。

這種情況一般是發生在服務端的可能性比較大,也就是服務端當前所處網絡環境流量負載過高,網絡擁塞了,然后服務端收到了客戶端的SYN報文卻來不及響應,或者發送的響應報文在網絡傳輸過程中老是丟失,導致客戶端遲遲收不到確認,最后返回ETIMEDOUT錯誤。

我們可以簡單復現一下這種情況,這個實驗是基于CentOS系統進行的,具體過程如下所示:

1. 首先通過iptables -F把Centos上的防火墻規則清理掉,然后再通過iptables -I INPUT -p tcp --syn -i lo -j DROP命令把本地的所有SYN包都過濾掉(模擬服務端當前網絡不穩定)。

執行以下命令:

1iptables -F
2iptables -I INPUT -p tcp --syn -i lo -j DROP

2. 然后通過nc命令向本地的環回地址127.0.0.1發起tcp連接請求(相當于自己跟自己發起tcp連接),來模擬客戶端跟服務端發起tcp連接,但是服務器端就是不響應,最后導致客戶端的tcp連接建立請求超時,并終止tcp連接。

3. 然后再通過tcpdump工具把客戶端和服務端建立tcp連接過程中的數據報都抓取下來,由于我們設置的服務器偵聽端口號是10086,這里我們可以通過tcpdump -i any port 10086命令來過濾所有網卡的10086端口的數據包。如上圖所示,localhost.39299代表客戶端,localhost.10086代表服務端,客戶端總共向服務端發送了6個SYN報文,這6個SYN包的間隔時間分別是1s,2s,4s,8s,16s,這些時間累積加起來總共為31s,其實客戶端在發送最后一個SYN報文時還等待了一段時間,然后才超時。也就是說,客戶端在發送了第一個SYN報文時,會設置了一個計時器并開始計時,在最后一個SYN報文還沒收到服務端的確認時,這個計時器就會超時,然后關閉tcp連接。

第二種
客戶端連接一個服務器沒有偵聽的端口。

過程是:客戶端發送了一個SYN報文后,然后服務端回復了一個RST報文,說明這是一個異常的tcp連接,服務端發送了RST報文重置這個異常的tcp連接。

這種情況一般為拒絕連接請求,比如:客戶端想和服務端建立tcp連接,但是客戶端的連接請求中使用了一個不存在或沒有偵聽的端口(比如:這個端口超出65535的范圍),那么服務端就可以發送RST報文段拒絕這個請求。

拒絕連接一般是由服務器主動發起的,因為客戶端發起請求連接攜帶的目的端口,可能服務器并沒有開啟LISTEN狀態。因此服務器在收到這樣的報文段后會發送一個RST報文段,在這個報文里把RSTACK都置為1,它確認了SYN報文段并同時重置了該tcp連接,然后服務器等待另一個連接。客戶端在收到RST+ACK報文段后就會進入CLOSED狀態。

這里以通過20000不存在的端口遠程登錄為例:

tcpdump抓取到的數據包如下:

113:35:08.609549 IP 192.168.98.137.49057 > 192.168.0.102.dnp: Flags [S], seq 2919679902, win 14600, options [mss 1460,sackOK,TS val 39134059 ecr 0,nop,wscale 6], length 0
213:35:09.610018 IP 192.168.98.137.49057 > 192.168.0.102.dnp: Flags [S], seq 2919679902, win 14600, options [mss 1460,sackOK,TS val 39135059 ecr 0,nop,wscale 6], length 0
313:35:09.610115 IP 192.168.0.102.dnp > 192.168.98.137.49057: Flags [R.], seq 1766537774, ack 2919679903, win 64240, length 0
413:35:10.610188 IP 192.168.0.102.dnp > 192.168.98.137.49057: Flags [R.], seq 3482791532, ack 1, win 64240, length 0

通過分析tcpdump工具抓取的數據發現,RST報文段不攜帶數據。

第三種
如果客戶端調用connect函數向服務端發送了一個SYN報文,這個SYN報文在網絡傳輸過程中經過某個路由器時,正好這個路由器出問題了,缺少到達目的地的路由,不能把這個SYN報文轉發給目的地址,那么該路由器會丟棄這個SYN報文,并同時給客戶端發送一個Destination unreachable(主機不可達)的ICMP差錯報文。客戶端的linux內核會保存這個Destination unreachable的ICMP差錯報文,同時按第一種情況繼續發送SYN報文,如果在規定的時間超時后還沒收到服務端的響應報文,那么linux內核會把保存的ICMP差錯報文作為EHOSTUNREACHENETUNREACH錯誤返回給客戶端的應用進程。

下面的這個實驗就是用來說明第三種情況,幫助理解,大家能看明白就行了,可以不用去做這個實驗,當然,有興趣的同學可以去模擬一下。

然后client遠程登錄server成功。

上圖中沒有指定telnet端口號,使用默認端口號23。

這是抓取到的數據包,client在遠程登錄server時,發起了SYN連接請求。

現在我們來模擬client設備出故障,刪除R1設備到server的路由信息

no ip route 12.1.3.0 255.255.255.0 12.1.2.2

client再登錄server時就會失敗,我們從抓取到的數據包可以發現,client發送了一個SYN報文,然后R1設備收到這個SYN報文時,發現自己不能到達server,于是會把這個SYN報文丟棄掉,并向client發送了一個目標主機不可達的ICMP差錯報文,于是client發送了RST報文來關閉這條異常的tcp連接。


全文完。


文章由網友song投稿,經張小方修改部分內容和文字錯誤。其博客地址是:

https://blog.csdn.net/q1007729991


總結

以上是生活随笔為你收集整理的从抓包的角度分析connect()函数的连接过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。