TCP/IP協議棧在Linux內核中的運行時序分析


 

目錄 

一、TCP/IP和網絡分層介紹

    1.TCP/IP概念

    2.TCP/IP協議棧組成

    3.OSI模型和TCP/IP模型

        1)應用層

        2)傳輸層

        3)網際層

        4)網絡訪問層

二、Socket介紹

    1.什么是socket

    2.Socket通信

    3.相關函數及作用

    4.相關數據結構

        1)sockaddr

        2)sockaddr_in

        3)struct in_addr

三、Linux內核網絡結構

    1.網絡結構

    2.有關的數據結構

        1)sk_buff

        2)net_device

四、send過程

    1.總體流程

        1) 應用層

        2)傳輸層

        3) 網絡層

        4)鏈路層

    2.gdb調試跟蹤

    3.部分源代碼分析

        1)tcp_sendmsg()

        2)ip_sendmsg()

五、recv過程

    1.總體流程

        1)鏈路層

        2)網絡層

        3)傳輸層

        4)應用層

    2.gdb調試跟蹤

    3.部分源代碼分析

        1)tcp_v4_rev()

        2)tcp_data_queue()

六、時序圖

七、參考文獻

 

一、TCP/IP和網絡分層介紹

1.TCP/IP概念

互聯網協議套件(Internet Protocol Suite,縮寫IPS)是網絡通信模型,以及整個網絡傳輸協議家族,為網際網絡的基礎通信架構。它常通稱為TCP/IP協議族,簡稱TCP/IP,因為該協議家族的兩個核心協議:TCP(傳輸控制協議)和IP(網際協議),為該家族中最早通過的標准。由於在網絡通訊協議普遍采用分層的結構,當多個層次的協議共同工作時,類似計算機科學中的堆棧,因此又稱為TCP/IP協議棧。這些協議最早發源於美國國防部(縮寫為DoD)的ARPA網項目,因此也稱作DoD模型(DoD Model)。這個協議族由互聯網工程任務組負責維護。

TCP/IP提供了點對點鏈接的機制,將資料應該如何封裝、尋址、傳輸、路由以及在目的地如何接收,都加以標准化。它將軟件通信過程抽象化為四個抽象層,采取協議堆棧的方式,分別實現出不同通信協議。協議族下的各種協議,依其功能不同,分別歸屬到這四個層次結構之中,常視為是簡化的七層OSI模型。

2.TCP/IP協議棧組成

整個通信網絡的任務,可以划分成不同的功能區塊,即所謂的層級(layer),用於互聯網的協議可以比照TCP/IP參考模型進行分類。TCP/IP協議棧起始於第三層協議IP(網際協議)。所有這些協議都在相應的RFC文檔中討論及標准化。重要的協議在相應的RFC文檔中均標記狀態:“必須”(required),“推薦”(recommended),“可選”(selective)。其他的協議還可能有“試驗”(experimental)或“歷史”(historic)的狀態。”

3.OSI模型和TCP/IP模型

OSI模型 協議或功能物件
7.應用層 例如HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、NFS、RTSP、XMPP、Whois、ENRP、TLS
6.表示層 例如XDR、ASN.1、NCP、TLS、ASCII
5.會話層 例如ASAP、ISO 8327 / CCITT X.225、RPC、NetBIOS、Winsock、BSD sockets、SOCKS、密碼驗證協議
4.傳輸層 例如TCP、UDP、RTP、SCTP、SPX、ATP、IL
3.網絡層 例如IP、ICMP、IPX、BGP、OSPF、RIP、IGRP、EIGRP、ARP、RARP、X.25
2.數據鏈路層 例如以太網、令牌環、HDLC、幀中繼、ISDN、ATM、IEEE 802.11、FDDI、PPP
1.物理層 例如調制解調器、無線電、光纖
TCP/IP模型 協議或功能物件
4.應用層 例如HTTP、FTP、DNS(如BGP和RIP這樣的路由協議,盡管由於各種各樣的原因它們分別運行在TCP和UDP上,仍然可以將它們看作網絡層的一部分)
3.傳輸層 例如TCP、UDP、RTP、SCTP<(如OSPF這樣的路由協議,盡管運行在IP上也可以看作是網絡層的一部分)
2.網際層 對於TCP/IP來說這是因特網協議(IP)(如ICMP和IGMP這樣的必須協議盡管運行在IP上,也仍然可以看作是網絡互連層的一部分;ARP不運行在IP上)
1.網絡訪問層 例如以太網、Wi-Fi、MPLS等。

通常人們認為OSI模型的最上面三層(應用層、表示層和會話層)在TCP/IP組中是一個應用層。由於TCP/IP有一個相對較弱的會話層,由TCP和RTP下的打開和關閉連接組成,並且在TCP和UDP下的各種應用提供不同的端口號,這些功能能夠由單個的應用程序(或者那些應用程序所使用的庫)增加。與此相似的是,IP是按照將它下面的網絡當作一個黑盒子的思想設計的,這樣在討論TCP/IP的時候就可以把它當作一個獨立的層。

1)應用層

該層包括所有和應用程序協同工作,利用基礎網絡交換應用程序專用的數據的協議。 應用層是大多數普通與網絡相關的程序為了通過網絡與其他程序通信所使用的層。這個層的處理過程是應用特有的;數據從網絡相關的程序以這種應用內部使用的格式進行傳送,然后編碼成標准協議的格式。

一些特定的程序視為在此層運行。它們提供服務直接支持用戶應用。這些程序和它們對應的協議包括HTTP(萬維網服務)、FTP(文件傳輸)、SMTP(電子郵件)、SSH(安全遠程登錄)、DNS(名稱⇔IP地址尋找)以及許多其他協議。

每一個應用層(TCP/IP參考模型的最高層)協議一般都會使用到兩個傳輸層協議之一: 面向連接的TCP傳輸控制協議和無連接的包傳輸的UDP用戶數據報文協議。

2)傳輸層

傳輸層(transport layer)的協議,能夠解決諸如端到端可靠性(“數據是否已經到達目的地?”)和保證數據按照正確的順序到達這樣的問題。在TCP/IP協議組中,傳輸協議也包括所給數據應該送給哪個應用程序。 在TCP/IP協議組中技術上位於這個層的動態路由協議通常認為是網絡層的一部分;一個例子就是OSPF(IP協議89)。

TCP(IP協議6)是一個“可靠的”、面向鏈接的傳輸機制,它提供一種可靠的字節流保證數據完整、無損並且按順序到達。TCP盡量連續不斷地測試網絡的負載並且控制發送數據的速度以避免網絡過載。另外,TCP試圖將數據按照規定的順序發送。這是它與UDP不同之處,這在實時數據流或者路由高網絡層丟失率應用的時候可能成為一個缺陷。 較新的SCTP也是一個“可靠的”、面向鏈接的傳輸機制。它是面向記錄而不是面向字節的,它在一個單獨的鏈接上提供通過多路復用提供的多個子流。它也提供多路自尋址支持,其中鏈接終端能夠以多個IP地址表示(代表多個實體接口),這樣的話即使其中一個連接失敗了也不中斷。它最初是為電話應用開發的(在IP上傳輸SS7),但是也可以用於其他的應用。

UDP(IP協議號17)是一個無鏈接的數據報協議。它是一個“盡力傳遞”(best effort)或者說“不可靠”協議——不是因為它特別不可靠,而是因為它不檢查數據包是否已經到達目的地,並且不保證它們按順序到達。如果一個應用程序需要這些特性,那它必須自行檢測和判斷,或者使用TCP協議。 UDP的典型性應用是如流媒體(音頻和視頻等)這樣按時到達比可靠性更重要的應用,或者如DNS查找這樣的簡單查詢/響應應用,如果創建可靠的鏈接所作的額外工作將是不成比例地大。 DCCP目前正由IETF開發。它提供TCP流動控制語義,但對於用戶來說保留UDP的數據報服務模型。

3)網際層

TCP/IP協議族中的網際層(internet layer)在OSI模型中叫做網絡層(network layer)。正如最初所定義的,網絡層解決在一個單一網絡上傳輸數據包的問題。類似的協議有X.25和ARPANET的Host/IMP Protocol。 隨着因特網思想的出現,在這個層上添加附加的功能,也就是將數據從源網絡傳輸到目的網絡。這就牽涉到在網絡組成的網上選擇路徑將數據包傳輸,也就是因特網。 在因特網協議組中,IP完成數據從源發送到目的的基本任務。IP能夠承載多種不同的高層協議的數據;這些協議使用一個唯一的IP協議號進行標識。ICMP和IGMP分別是1和2。 一些IP承載的協議,如ICMP(用來發送關於IP發送的診斷信息)和IGMP(用來管理多播數據),它們位於IP層之上但是完成網絡層的功能,這表明因特網和OSI模型之間的不兼容性。所有的路由協議,如BGP、OSPF、和RIP實際上也是網絡層的一部分,盡管它們似乎應該屬於更高的協議棧。

4)網絡訪問層

網絡訪問層實際上並不是因特網協議組中的一部分,但是它是數據包從一個設備的網絡層傳輸到另外一個設備的網絡層的方法。這個過程能夠在網卡的軟件驅動程序中控制,也可以在韌體或者專用芯片中控制。這將完成如添加報頭准備發送、通過實體介質實際發送這樣一些數據鏈路功能。另一端,鏈路層將完成數據幀接收、去除報頭並且將接收到的包傳到網絡層。 然而,鏈路層並不經常這樣簡單。它也可能是一個虛擬專有網絡(VPN)或者隧道,在這里從網絡層來的包使用隧道協議和其他(或者同樣的)協議組發送而不是發送到實體的接口上。VPN和通道通常預先建好,並且它們有一些直接發送到實體接口所沒有的特殊特點(例如,它可以加密經過它的數據)。由於現在鏈路“層”是一個完整的網絡,這種協議組的遞歸使用可能引起混淆。但是它是一個實現常見復雜功能的一個優秀方法。(盡管需要注意預防一個已經封裝並且經隧道發送下去的數據包進行再次地封裝和發送)。

二、Socket介紹

1.什么是socket

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部。

Socket起源於Unix,而Unix/Linux 基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式 來操作。Socket就是該模式的一個實現,Socket即是一種特殊的文件,一些Socket函數就是對其進行的操作(讀/寫IO、打開、關閉)

2.Socket通信

Socket保證了不同計算機之間的通信,也就是網絡通信。對於網站,通信模型是服務器與客戶端之間的通信。兩端都建立了一個Socket對象,然后通過Socket對象對數據進行傳輸。通常服務器處於一個無限循環,等待客戶端的連接。

如圖為TCP建立Socket連接的函數調用過程:image-20210123203858811

 

3.相關函數及作用

  1. int socket(int domain, int type, int protocol):創建一個新的套接字,返回套接字描述符

  2. int bind(int sockfd,struct sockaddr * my_addr,int addrlen):為套接字指明一個本地端點地址TCP/IP協議使用sockaddr_in結構,包含IP地址和端口號,服務器使用它來指明熟知的端口號,然后等待連接

  3. int listen(int sockfd,int input_queue_size):面向連接的服務器使用它將一個套接字置為被動模式,並准備接收傳入連接。用於服務器,指明某個套接字連接是被動的

  4. int accept(int sockfd, struct sockaddr *addr, int *addrlen):獲取傳入連接請求,返回新的連接的套接字描述符

  5. int connect(int sockfd,struct sockaddr *server_addr,int sockaddr_len):同遠程服務器建立主動連接,成功時返回0,若連接失敗返回-1。

  6. int send(int sockfd, const void * data, int data_len, unsigned int flags):在TCP連接上發送數據,返回成功傳送數據的長度,出錯時返回-1

  7. int recv(int sockfd, void *buf, int buf_len,unsigned int flags):從TCP接收數據,返回實際接收的數據長度

  8. close(int sockfd):撤銷套接字

4.相關數據結構

1)sockaddr

1 struct sockaddr {
2    unsigned short   sa_family;
3    char             sa_data[14];
4 };

 

這是一個通用的套接字地址結構,在大多數套接字函數調用中都需要使用它。 成員字段的說明如下。sa_family包括以下可選值。每個值代表一種地址族(address family),在基於IP的情況中,都使用AF_INET。

  • AF_INET

  • AF_UNIX

  • AF_NS

  • AF_IMPLINK

sa_data長為14字節,根據地址類型解釋協議特定地址。

2)sockaddr_in

1 struct sockaddr_in {
2    short int            sin_family;
3    unsigned short int   sin_port;
4    struct in_addr       sin_addr;
5    unsigned char        sin_zero[8];
6 };

 

其中,sin_family和sockadd的sa_family一樣,包括四個可選值。

sin_port是端口號,16位長,網絡字節序(network byte order);sin_addr是IP地址,32位長,網絡字節序(network byte order)。sin_zero,8個字節,設置為0。

3)struct in_addr

1 struct in_addr {
2    unsigned long s_addr;
3 };

 

就是一個32位的IP地址,同樣是網絡字節序。

關於字節序,補充一些內容:

  • Little Endian - 在該方案中,低位字節存儲在起始地址(A)上,高位字節存儲在下一個地址(A + 1)上。

  • Big Endian - 在該方案中,高位字節存儲在起始地址(A)上,低位字節存儲在下一個地址(A + 1)上。

為了允許具有不同字節順序約定的機器相互通信,Internet協議為通過網絡傳輸的數據指定了規范的字節順序約定。 這稱為網絡字節順序。在建立Internet套接字連接時,必須確保sockaddr_in結構的sin_port和sin_addr成員中的數據在網絡字節順序中表示。

不用擔心這幾個數據結構以及字節序,因為socket接口非常貼心地准備好了各種友好的接口。

  • htons() Host to Network Short

  • htonl() Host to Network Long

  • ntohl() Network to Host Long

  • ntohs() Network to Host Short

三、Linux內核網絡結構

1.網絡結構

Linux內核采用分層結構處理網絡數據包。分層結構與網絡協議的結構匹配,既能簡化數據包處理流程,又便於擴展和維護。

在Linux內核中,對網絡部分按照網絡協議層、網絡設備層、設備驅動功能層和網絡媒介層的分層體系設計。網絡驅動功能層主要通過網絡驅動程序實現。網絡設備驅動程序的主要任務如下:

  • 接收目的地為當前主機的數據包,並將其傳遞給網絡層,之后再將其傳遞給傳輸層。

  • 傳輸當前主機生成的外出數據包或轉發當前主機收到的數據包。

在Linux內核,所有的網絡設備都被抽象為一個接口處理,該接口提供了所有的網絡操作。

net_device結構表示網絡設備在內核中的情況,也就是網絡設備接口。網絡設備接口既包括軟件虛擬的網絡設備接口,如環路設備,也包括了網絡硬件設備,如以太網卡。Linux內核有一個dev_base的全局指針,指向一個設備鏈表,包括了系統內的所有網絡設備。該設備鏈表每個節點是一個網絡設備。

在net_device結構中提供了許多供系統訪問和協議層調用的設備方法,包括初始化、打開關閉設備、數據包發送和接收等。

2.有關的數據結構

1)sk_buff

內核對網絡數據包的處理都是基於sk_buff結構的,該結構是內核網絡部分最重要的數據結構。網絡協議棧中各層協議都可以通過對該結構的操作實現本層協議數據的添加或者刪除。使用sk_buff結構避免了網絡協議棧各層來回復制數據導致的效率低下。

sk_buff結構可以分為兩個部分,一部分是存儲數據包緩存,另一部分是由一組用於內核管理的指針組成。

sk_buff管理的指針最主要的是下面4個:

  • head指向數據緩沖(PackertData)的內核首地址;

  • data指向當前數據包的首地址;

  • tail指向當前數據包的尾地址;

  • end 指向數據緩沖的內核尾地址。

數據包的大小在內核網絡協議棧的處理過程中會發生改變,因此data和tail指針也會不斷變化,而head和tail指針是不會發生改變的。對各層設置指針的是方便了協議棧對數據包的處理。

sk_buff結構表示一個包含報頭的數據包,我們一般稱之為SKB(套接字緩沖區),它能夠處理可變長數據,能夠很容易地在數據區頭尾部添加和移除數據,且盡量避免數據的復制。每一個SKB都在設備結構中標識發送報文的目的地或接收發送報文的來源地,通常每個報文使用一個SKB表示,SKB主要用於在網絡驅動程序和應用程序之間傳遞、復制數據包。

  • 當應用程序要發送一個數據包時,數據通過系統調用提交到內核中,系統會分配一個SKB來存儲數據,然后往下層傳遞,在傳遞給網絡驅動后才將其釋放。

  • 當網絡設備接收到數據包后,同樣要分配一個SKB來存儲數據,然后往上傳遞,最終在數據復制到應用程序后才釋放。

2)net_device

Linux內核中網絡設備最重要的數據結構就是net_device結構了,它是網絡驅動程序最重要的部分。 net_device結構保存在include/linux/netdevices.h頭文件,理解該結構對理解網絡設備驅動有很大幫助。

內核中所有網絡設備的信息和操作都在net_device設備中,無論是注冊網絡設備,還是設置網絡設備參數,都用到該結構。

下面是主要數據成員。

    • 設備名稱

    • 總線參數

    • 協議參數

    • 鏈接層變量

    • 接口標志

四、send過程

1.總體流程

1) 應用層

  首先網絡應用調用Socket 的API函數 socket()(該函數定義在/usr/include/sys/socket.h文件中) ,創建一個 socket(函數會調用系統調用 socket()),並最終調用內核函數的 sock_create (定義在net/socket.c)方法,成功后返回一個socket描述符。

  對於TCP,應用接着調用 connect()函數,使得客戶端和服務器端通過該 socket 建立一個連接。然后可以調用send函數發出一個 message 給接收端。sock_sendmsg 被調用,調用相應協議的發送函數。

2)傳輸層

數據到了傳輸層的處理,以TCP協議為例。TCP主要處理:(1)構造 TCP segment (2)計算 checksum (3)發送回復(ACK)包 (4)滑動窗口(sliding windown)等保證可靠性的操作。

不同的協議針對的發送函數不一樣,TCP調用 tcp_sendmsg 函數。

  如果是tcp協議的流程,tcp_sendmsg()的主要工作是把用戶層的數據,填充到skb中。然后調用tcp_push()來發送,tcp_push函數調用tcp_write_xmit()函數,其又將調用發送函數tcp_transmit_skb,所有的SKB都經過該函數進行發送。最后進入到ip_queue_xmit到網絡層。因為tcp會進行重傳控制,所以有tcp_write_timer函數,進行定時。

3) 網絡層

  ip_queue_xmit(skb)會檢查skb->dst路由信息。如果沒有,就會去選擇一個路由。

  填充IP包的各個字段,比如版本、包頭長度、TOS等。當報文的長度大於mtu,gso的長度不為0就會調用 ip_fragment 進行分片。ip_fragment 函數中,會檢查 IP_DF 標志位,如果待分片IP數據包禁止分片,則調用 icmp_send()向發送方發送一個原因為需要分片而設置了不分片標志的目的不可達ICMP報文,並丟棄報文,即設置IP狀態為分片失敗,釋放skb,返回消息過長錯誤碼。

 

4)鏈路層

  數據鏈路層在不可靠的物理介質上提供可靠的傳輸。該層的作用包括:物理地址尋址、數據的成幀、流量控制、數據的檢錯、重發等。這一層數據的單位稱為幀(frame)。從dev_queue_xmit函數開始,位於net/core/dev.c文件中。

2.gdb調試跟蹤

3.部分源代碼分析

1)tcp_sendmsg()

 1 int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size)
 2 {
 3     ...
 4     //檢測三次握手是否成功
 5     if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) &&
 6         ! (tcp_passive_fastopen(sk)) {
 7  
 8         /* 等待連接的建立,成功時返回值為0 */
 9         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
10             goto do_error;
11     }
12     ...
13 }

 

SOCK_STREAM類socket的TCP層操作函數集實例為tcp_prot,其中使用tcp_sendmsg()來發送數據。

1 struct proto tcp_prot = {
2     .name = "TCP",
3     .owner = THIS_MODULE,
4     ...
5     .sendmsg = tcp_sendmsg,
6     ...
7 };

 

2)ip_sendmsg()

 

ip_queue_xmit最終也是調用ip_local_out發送本機的數據包。

  1 int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
  2 {
  3     struct sock *sk = skb->sk;
  4     struct inet_sock *inet = inet_sk(sk);
  5     struct ip_options_rcu *inet_opt;
  6     struct flowi4 *fl4;
  7     struct rtable *rt;
  8     struct iphdr *iph;
  9     int res;
 10     /* 判斷數據包是否有路由,如果已經有了,就直接跳到
 11 packet_routed */
 12     rcu_read_lock();
 13     inet_opt = rcu_dereference(inet->inet_opt);
 14     fl4 = &fl->u.ip4;
 15     rt = skb_rtable(skb);
 16     if (rt != NULL)
 17         goto packet_routed;
 18     /* 從套接字獲得合法的路由(需要檢查是否過期)
 19  */
 20     rt = (struct rtable *)__sk_dst_check(sk, 0);
 21     if (rt == NULL) {
 22         __be32 daddr;
 23         daddr = inet->inet_daddr;
 24         /* 如果有
 25 IP 嚴格路由選項,則使用選項中的地址作為目的地址進行路由查詢
 26  */
 27         if (inet_opt && inet_opt->opt.srr)
 28             daddr = inet_opt->opt.faddr;
 29         /* 進行路由查找
 30  */
 31         rt = ip_route_output_ports(sock_net(sk), fl4, sk,
 32                      daddr, inet->inet_saddr,
 33                      inet->inet_dport,
 34                      inet->inet_sport,
 35                      sk->sk_protocol,
 36                      RT_CONN_FLAGS(sk),
 37                      sk->sk_bound_dev_if);
 38         if (IS_ERR(rt))
 39             goto no_route;
 40         /* 根據路由的接口的特性設置套接字特性
 41  */
 42         sk_setup_caps(sk, &rt->dst);
 43     }
 44     /* 給數據包設置路由
 45  */
 46     skb_dst_set_noref(skb, &rt->dst);
 47 packet_routed:
 48     /* 如果有
 49 IP嚴格路由選項
 50  */
 51     if (inet_opt && inet_opt->opt.is_strictroute && fl4->daddr != rt->rt_gateway)
 52         goto no_route;
 53     /* 分配
 54 IP首部和選項空間
 55  */
 56     skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
 57     /* 設置
 58 IP首部位置
 59  */
 60     skb_reset_network_header(skb);
 61     /* 得到數據包
 62 IP首部的指針
 63  */
 64     iph = ip_hdr(skb);
 65     /* 構建
 66 IP首部
 67  */
 68     *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
 69     /* 如不能分片,則在
 70 IP首部設置
 71 IP_DF標志
 72  */
 73     if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df)
 74         iph->frag_off = htons(IP_DF);
 75     else
 76         iph->frag_off = 0;
 77     iph->ttl      = ip_select_ttl(inet, &rt->dst);
 78     iph->protocol = sk->sk_protocol;
 79     iph->saddr    = fl4->saddr;
 80     iph->daddr    = fl4->daddr;
 81     /* Transport layer set skb->h.foo itself. */
 82     /* 構建
 83 IP選項
 84  */
 85     if (inet_opt && inet_opt->opt.optlen) {
 86         iph->ihl += inet_opt->opt.optlen >> 2;
 87         ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
 88     }
 89     /* 選擇合適的
 90 IP identifier */
 91     ip_select_ident_more(iph, &rt->dst, sk,
 92                (skb_shinfo(skb)->gso_segs ?: 1) - 1);
 93     /* 根據套接字選項,設置數據包的優先級和標記
 94  */
 95     skb->priority = sk->sk_priority;
 96     skb->mark = sk->sk_mark;
 97     /* 發送數據包
 98  */
 99     res = ip_local_out(skb);
100     rcu_read_unlock();
101     return res;
102 no_route:
103     rcu_read_unlock();
104     IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
105     kfree_skb(skb);
106     return -EHOSTUNREACH;
107 }

 

五、recv過程

1.總體流程

1)鏈路層

包到達機器的物理網卡時候觸發一個中斷,並將通過DMA傳送到位於 linux kernel 內存中的rx_ring。中斷處理程序分配 skb_buff 數據結構,並將接收到的數據幀從網絡適配器I/O端口拷貝到skb_buff 緩沖區中,並設置 skb_buff 相應的參數,這些參數將被上層的網絡協議使用,例如skb->protocol;

然后發出一個軟中斷(NET_RX_SOFTIRQ,該變量定義在include/linux/interrupt.h 文件中),通知內核接收到新的數據幀。進入軟中斷處理流程,調用 net_rx_action 函數。包從 rx_ring 中被刪除,進入 netif _receive_skb 處理流程。

netif_receive_skb根據注冊在全局數組 ptype_all 和 ptype_base 里的網絡層數據報類型,把數據報遞交給不同的網絡層協議的接收函數(INET域中主要是ip_rcv和arp_rcv)。

 

2)網絡層

網絡IP層的入口函數在ip_rcv函數。

ip_rcv函數調用第三層協議的接收函數處理該skb包,進入第三層網絡層處理。

該函數首先會做包括checksum在內的各種檢查,如果需要的話會做 IP defragment(分片合並),最終到達 ip_rcv_finish 函數。

  ip_rcv_finish 函數會調用ip_route_input函數,進入路由處理環節。會調用 ip_route_input 來更新路由,然后查找 route,決定該會被發到本機還是會被轉發還是丟棄:

  如果發到本機的話,調用 ip_local_deliver 函數,可能會做 de-fragment(合並多個包),並調用ip_local_deliver_finish。最后調用下一層接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。如果需要轉發,則進入轉發流程,調用 dev_queue_xmit,進入鏈路層處理流程。如果不是發送到本機的話就要進行轉發,則調用ip_forward轉發。

3)傳輸層

傳輸層 TCP 處理入口在tcp_v4_rcv函數(位於 linux/net/ipv4/tcp_ipv4.c 文件中),首先會做一些完整性檢查,發現問題直接將包丟棄。如果是tcp,則調用tcp_v4_do_rcv。

然后sk->sk_state == TCP_ESTABLISHED,調用tcp_rcv_established。

調用tcp_data_queue方法將報文放入隊列中。然后用tcp_ofo_queue方法報文插入receive隊列的。

4)應用層

  應用調用 read 或者 recv 時,該調用會被映射為/net/socket.c 中的 sys_recv 系統調用,並被轉化為 sys_recvfrom 調用,然后調用 sock_recvmsg 函數。

  對於 INET 類型的 socket,/net/ipv4/af_inet.c 中的 inet_recvmsg 方法會被調用,它會調用相關協議的數據接收方法。

  TCP 會調用 tcp_recvmsg。該函數從 socket buffer 中拷貝數據到buffer。

2.gdb調試跟蹤

3.部分源代碼分析

1)tcp_v4_rev()

該函數主要工作就是根據tcp頭部信息查到處理報文的socket對象,然后檢查socket狀態做不同處理,我們這里是監聽狀態TCP_LISTEN,直接調用函數tcp_v4_do_rcv()。

  1 /*
  2  *    From tcp_input.c
  3  */
  4  
  5 //網卡驅動-->netif_receive_skb()--->ip_rcv()--->ip_local_deliver_finish()---> tcp_v4_rcv()
  6 int tcp_v4_rcv(struct sk_buff *skb)
  7 {
  8     struct net *net = dev_net(skb->dev);
  9     const struct iphdr *iph;
 10     const struct tcphdr *th;
 11     bool refcounted;
 12     struct sock *sk;
 13     int ret;
 14  
 15     //如果不是發往本地的數據包,則直接丟棄
 16     if (skb->pkt_type != PACKET_HOST)
 17         goto discard_it;
 18  
 19     /* Count it even if it's bad */
 20     __TCP_INC_STATS(net, TCP_MIB_INSEGS);
 21  
 22  
 23     ////包長是否大於TCP頭的長度
 24     if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
 25         goto discard_it;
 26  
 27     //tcp頭   --> 不是很懂為何老是獲取tcp頭
 28     th = (const struct tcphdr *)skb->data;
 29  
 30     if (unlikely(th->doff < sizeof(struct tcphdr) / 4))
 31         goto bad_packet;
 32     
 33     if (!pskb_may_pull(skb, th->doff * 4))
 34         goto discard_it;
 35  
 36     /* An explanation is required here, I think.
 37      * Packet length and doff are validated by header prediction,
 38      * provided case of th->doff==0 is eliminated.
 39      * So, we defer the checks. */
 40  
 41     if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo))
 42         goto csum_error;
 43     
 44     //得到tcp的頭  --> 不是很懂為何老是獲取tcp頭
 45     th = (const struct tcphdr *)skb->data;
 46  
 47     //得到ip報文頭
 48     iph = ip_hdr(skb);
 49     /* This is tricky : We move IPCB at its correct location into TCP_SKB_CB()
 50      * barrier() makes sure compiler wont play fool^Waliasing games.
 51      */
 52     memmove(&TCP_SKB_CB(skb)->header.h4, IPCB(skb),
 53         sizeof(struct inet_skb_parm));
 54     barrier();
 55  
 56     TCP_SKB_CB(skb)->seq = ntohl(th->seq);
 57     TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
 58                     skb->len - th->doff * 4);
 59     TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
 60     TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th);
 61     TCP_SKB_CB(skb)->tcp_tw_isn = 0;
 62     TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
 63     TCP_SKB_CB(skb)->sacked     = 0;
 64  
 65 lookup:
 66     //根據源端口號,目的端口號和接收的interface查找sock對象------>先在建立連接的哈希表中查找------>如果沒找到就從監聽哈希表中找 
 67  
 68     //對於建立過程來講肯是監聽哈希表中才能找到
 69     sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
 70                    th->dest, &refcounted);
 71     
 72     //如果找不到處理的socket對象,就把數據報丟掉
 73     if (!sk)
 74         goto no_tcp_socket;
 75  
 76 process:
 77  
 78     //檢查sock是否處於半關閉狀態
 79     if (sk->sk_state == TCP_TIME_WAIT)
 80         goto do_time_wait;
 81  
 82     if (sk->sk_state == TCP_NEW_SYN_RECV) {
 83         struct request_sock *req = inet_reqsk(sk);
 84         struct sock *nsk;
 85  
 86         sk = req->rsk_listener;
 87         if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {
 88             sk_drops_add(sk, skb);
 89             reqsk_put(req);
 90             goto discard_it;
 91         }
 92         if (unlikely(sk->sk_state != TCP_LISTEN)) {
 93             inet_csk_reqsk_queue_drop_and_put(sk, req);
 94             goto lookup;
 95         }
 96         /* We own a reference on the listener, increase it again
 97          * as we might lose it too soon.
 98          */
 99         sock_hold(sk);
100         refcounted = true;
101         nsk = tcp_check_req(sk, skb, req, false);
102         if (!nsk) {
103             reqsk_put(req);
104             goto discard_and_relse;
105         }
106         if (nsk == sk) {
107             reqsk_put(req);
108         } else if (tcp_child_process(sk, nsk, skb)) {
109             tcp_v4_send_reset(nsk, skb);
110             goto discard_and_relse;
111         } else {
112             sock_put(sk);
113             return 0;
114         }
115     }
116     if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
117         __NET_INC_STATS(net, LINUX_MIB_TCPMINTTLDROP);
118         goto discard_and_relse;
119     }
120  
121     if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
122         goto discard_and_relse;
123  
124     if (tcp_v4_inbound_md5_hash(sk, skb))
125         goto discard_and_relse;
126  
127     nf_reset(skb);
128  
129     if (tcp_filter(sk, skb))
130         goto discard_and_relse;
131     
132     //tcp頭部   --> 不是很懂為何老是獲取tcp頭
133     th = (const struct tcphdr *)skb->data;
134     iph = ip_hdr(skb);
135 
136     skb->dev = NULL;
137 
138     //如果socket處於監聽狀態 --> 我們重點關注這里
139     if (sk->sk_state == TCP_LISTEN) {
140         ret = tcp_v4_do_rcv(sk, skb);
141         goto put_and_return;
142     }
143 
144     sk_incoming_cpu_update(sk);
145 
146     bh_lock_sock_nested(sk);
147     tcp_segs_in(tcp_sk(sk), skb);
148     ret = 0;
149     
150     //查看是否有用戶態進程對該sock進行了鎖定
151     //如果sock_owned_by_user為真,則sock的狀態不能進行更改
152     if (!sock_owned_by_user(sk)) {
153         if (!tcp_prequeue(sk, skb))
154             //-------------------------------------------------------->
155             ret = tcp_v4_do_rcv(sk, skb);
156     } else if (tcp_add_backlog(sk, skb)) {
157         goto discard_and_relse;
158     }
159     bh_unlock_sock(sk);
160 
161 put_and_return:
162     if (refcounted)
163         sock_put(sk);
164 
165     return ret;
166 
167 no_tcp_socket:
168     if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
169         goto discard_it;
170 
171     if (tcp_checksum_complete(skb)) {
172 csum_error:
173         __TCP_INC_STATS(net, TCP_MIB_CSUMERRORS);
174 bad_packet:
175         __TCP_INC_STATS(net, TCP_MIB_INERRS);
176     } else {
177         tcp_v4_send_reset(NULL, skb);
178     }
179 
180 discard_it:
181     /* Discard frame. */
182     kfree_skb(skb);
183     return 0;
184 
185 discard_and_relse:
186     sk_drops_add(sk, skb);
187     if (refcounted)
188         sock_put(sk);
189     goto discard_it;
190 
191 do_time_wait:
192     if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
193         inet_twsk_put(inet_twsk(sk));
194         goto discard_it;
195     }
196 
197     if (tcp_checksum_complete(skb)) {
198         inet_twsk_put(inet_twsk(sk));
199         goto csum_error;
200     }
201     switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
202     case TCP_TW_SYN: {
203         struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
204                             &tcp_hashinfo, skb,
205                             __tcp_hdrlen(th),
206                             iph->saddr, th->source,
207                             iph->daddr, th->dest,
208                             inet_iif(skb));
209         if (sk2) {
210             inet_twsk_deschedule_put(inet_twsk(sk));
211             sk = sk2;
212             refcounted = false;
213             goto process;
214         }
215         /* Fall through to ACK */
216     }
217     case TCP_TW_ACK:
218         tcp_v4_timewait_ack(sk, skb);
219         break;
220     case TCP_TW_RST:
221         tcp_v4_send_reset(sk, skb);
222         inet_twsk_deschedule_put(inet_twsk(sk));
223         goto discard_it;
224     case TCP_TW_SUCCESS:;
225     }
226     goto discard_it;
227 }

 

2)tcp_data_queue()

tcp_data_queue作用為數據段的接收處理,其中分為多種情況:

(1) 無數據,釋放skb,返回;

(2) 預期接收的數據段,a. 進行0窗口判斷;b. 進程上下文,復制數據到用戶空間;c. 不滿足b或者b未完整拷貝此skb的數據段,則加入到接收隊列;d. 更新下一個期望接收的序號;e. 若有fin標記,則處理fin;f. 亂序隊列不為空,則處理亂序;g. 快速路徑的檢查和設置;h. 喚醒用戶空間進程讀取數據;

(3) 重傳的數據段,進入快速ack模式,釋放該skb;

(4) 窗口以外的數據段,進入快速ack模式,釋放該skb;

(5) 數據段重疊,在進行0窗口判斷之后,進行(2)中的加入接收隊列,以及>=d的流程;

(6) 亂序的數據段,調用tcp_data_queue_ofo進行亂序數據段的接收處理;

  1 static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
  2 {
  3     struct tcp_sock *tp = tcp_sk(sk);
  4     bool fragstolen = false;
  5     int eaten = -1;
  6   7     /* 無數據 */
  8     if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) {
  9         __kfree_skb(skb);
 10         return;
 11     }
 12  13     /* 刪除路由緩存 */
 14     skb_dst_drop(skb);
 15  16     /* 去掉tcp首部 */
 17     __skb_pull(skb, tcp_hdr(skb)->doff * 4);
 18  19     tcp_ecn_accept_cwr(tp, skb);
 20  21     tp->rx_opt.dsack = 0;
 22  23     /*  Queue data for delivery to the user.
 24      *  Packets in sequence go to the receive queue.
 25      *  Out of sequence packets to the out_of_order_queue.
 26      */
 27     /* 預期接收的數據段 */
 28     if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
 29         /* 窗口為0,不能接收數據 */
 30         if (tcp_receive_window(tp) == 0)
 31             goto out_of_window;
 32  33         /* Ok. In sequence. In window. */
 34         /* 進程上下文 */
 35  36         /* 當前進程讀取數據 */
 37         if (tp->ucopy.task == current &&
 38             /* 用戶空間讀取序號與接收序號一致&& 需要讀取的數據不為0 */
 39             tp->copied_seq == tp->rcv_nxt && tp->ucopy.len &&
 40             /* 被用戶空間鎖定&& 無緊急數據 */
 41             sock_owned_by_user(sk) && !tp->urg_data) {
 42  43             /* 帶讀取長度和數據段長度的較小值 */
 44             int chunk = min_t(unsigned int, skb->len,
 45                       tp->ucopy.len);
 46             /* 設置running狀態 */
 47             __set_current_state(TASK_RUNNING);
 48  49             /* 拷貝數據 */
 50             if (!skb_copy_datagram_msg(skb, 0, tp->ucopy.msg, chunk)) {
 51                 tp->ucopy.len -= chunk;
 52                 tp->copied_seq += chunk;
 53                 /* 完整讀取了該數據段 */
 54                 eaten = (chunk == skb->len);
 55  56                 /* 調整接收緩存和窗口 */
 57                 tcp_rcv_space_adjust(sk);
 58             }
 59         }
 60  61         /* 未拷貝到用戶空間或者未拷貝完整數據段 */
 62         if (eaten <= 0) {
 63 queue_and_out:
 64             /* 沒有拷貝到用戶空間,對內存進行檢查 */
 65             if (eaten < 0) {
 66                 if (skb_queue_len(&sk->sk_receive_queue) == 0)
 67                     sk_forced_mem_schedule(sk, skb->truesize);
 68                 else if (tcp_try_rmem_schedule(sk, skb, skb->truesize))
 69                     goto drop;
 70             }
 71  72             /* 添加到接收隊列 */
 73             eaten = tcp_queue_rcv(sk, skb, 0, &fragstolen);
 74         }
 75  76         /* 更新下一個期望接收的序號*/
 77         tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq);
 78         /* 有數據 */
 79         if (skb->len)
 80             tcp_event_data_recv(sk, skb);
 81  82         /* 標記有fin,則處理 */
 83         if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
 84             tcp_fin(sk);
 85  86         /* 亂序隊列有數據,則處理 */
 87         if (!RB_EMPTY_ROOT(&tp->out_of_order_queue)) {
 88  89             /* 將亂序隊列中的數據段轉移到接收隊列 */
 90             tcp_ofo_queue(sk);
 91  92             /* RFC2581. 4.2. SHOULD send immediate ACK, when
 93              * gap in queue is filled.
 94              */
 95             /* 亂序數據段處理完畢,需要立即發送ack */
 96             if (RB_EMPTY_ROOT(&tp->out_of_order_queue))
 97                 inet_csk(sk)->icsk_ack.pingpong = 0;
 98         }
 99 100         if (tp->rx_opt.num_sacks)
101             tcp_sack_remove(tp);
102 103         /* 快路檢查 */
104         tcp_fast_path_check(sk);
105 106         /* 向用戶空間拷貝了數據,則釋放skb */
107         if (eaten > 0)
108             kfree_skb_partial(skb, fragstolen);
109 110         /* 不在銷毀狀態,則喚醒進程讀取數據 */
111         if (!sock_flag(sk, SOCK_DEAD))
112             sk->sk_data_ready(sk);
113         return;
114     }
115 116     /* 重傳 */
117     if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
118         /* A retransmit, 2nd most common case.  Force an immediate ack. */
119         NET_INC_STATS(sock_net(sk), LINUX_MIB_DELAYEDACKLOST);
120         tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);
121 122 out_of_window:
123         /* 進入快速ack模式 */
124         tcp_enter_quickack_mode(sk);
125 126         /*  調度ack */
127         inet_csk_schedule_ack(sk);
128 drop:
129         /* 釋放skb */
130         tcp_drop(sk, skb);
131         return;
132     }
133 134     /* Out of window. F.e. zero window probe. */
135     /* 窗口以外的數據,比如零窗口探測報文段 */
136     if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
137         goto out_of_window;
138 139     /* 進入快速ack模式 */
140     tcp_enter_quickack_mode(sk);
141 142     /* 數據段重疊 */
143     if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
144         /* Partial packet, seq < rcv_next < end_seq */
145         SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n",
146                tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
147                TCP_SKB_CB(skb)->end_seq);
148 149         tcp_dsack_set(sk, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);
150 151         /* If window is closed, drop tail of packet. But after
152          * remembering D-SACK for its head made in previous line.
153          */
154         /* 窗口為0,不能接收 */
155         if (!tcp_receive_window(tp))
156             goto out_of_window;
157         goto queue_and_out;
158     }
159 160     /* 接收亂序數據段 */
161     tcp_data_queue_ofo(sk, skb);
162 }

 

 

六、時序圖

 

七、參考文獻

  1. https://zh.wikipedia.org/zh-hans/TCP/IP%E5%8D%8F%E8%AE%AE%E6%97%8F

  2. https://developer.aliyun.com/article/548918

  3. https://developer.aliyun.com/article/549169

  4. https://zhuanlan.zhihu.com/p/106271407

  5. https://zhuanlan.zhihu.com/p/109826876

  6. https://zhuanlan.zhihu.com/p/59296026

  7. https://blog.51cto.com/liucw/1221140

  8. https://blog.csdn.net/qq_34258344/article/details/105848293

  9. https://www.cnblogs.com/sammyliu/p/5225623.html

  10. https://blog.csdn.net/zhangskd/article/details/48207553

  11. https://www.cnblogs.com/wlcxsj2019/p/12098991.html

  12. https://www.cnblogs.com/wanpengcoder/p/11752133.html

 

2021-01-24 17:54:35

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM