手把手教你IP數(shù)據(jù)包接收過(guò)程(超詳細(xì))
在《深度剖析Linux 網(wǎng)絡(luò)中斷下半部處理(看完秒懂)》一文中介紹過(guò),當(dāng)網(wǎng)卡接收到網(wǎng)絡(luò)數(shù)據(jù)包后,會(huì)由網(wǎng)卡驅(qū)動(dòng)通過(guò)調(diào)用 netif_rx 函數(shù)把數(shù)據(jù)包添加到待處理隊(duì)列中,然后喚起網(wǎng)絡(luò)中斷下半部處理。
而網(wǎng)絡(luò)中斷下半部處理由 net_rx_action 函數(shù)完成的,其主要功能就是從待處理隊(duì)列中獲取一個(gè)數(shù)據(jù)包,然后根據(jù)數(shù)據(jù)包的網(wǎng)絡(luò)層協(xié)議類型來(lái)找到相應(yīng)的處理接口來(lái)處理數(shù)據(jù)包。我們通過(guò) 圖1 來(lái)展示 net_rx_action 函數(shù)的處理過(guò)程:

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈(zèng)送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)?
?

網(wǎng)絡(luò)層的處理接口保存在 ptype_base 數(shù)組中,其元素的類型為 packet_type 結(jié)構(gòu),而索引為網(wǎng)絡(luò)層協(xié)議的類型,所以通過(guò)網(wǎng)絡(luò)層協(xié)議的類型就能找到對(duì)應(yīng)的處理接口,我們先來(lái)看看 packet_type 結(jié)構(gòu)的定義:
ptype_base 數(shù)組的定義如下:
從 ptype_base 數(shù)組的定義可知,其只有 16 個(gè)元素,那么如果內(nèi)核超過(guò) 16 種網(wǎng)絡(luò)層協(xié)議怎么辦?我們可以從網(wǎng)絡(luò)層處理接口的注冊(cè)函數(shù) dev_add_pack 中找到答案:
從 dev_add_pack 函數(shù)的實(shí)現(xiàn)可知,內(nèi)核把 ptype_base 數(shù)組當(dāng)成了哈希表,而鍵值就是網(wǎng)絡(luò)層協(xié)議類型,哈希函數(shù)就是對(duì)協(xié)議類型與 15 進(jìn)行與操作。如果有沖突,就通過(guò) next 指針把沖突的處理接口連接起來(lái)。
我們?cè)賮?lái)看看 IP 協(xié)議是怎樣注冊(cè)處理接口的,如下代碼:
從上面的代碼可以看到,在 ip_init 函數(shù)中調(diào)用了 dev_add_pack(&ip_packet_type) 函數(shù)來(lái)注冊(cè)了 IP 協(xié)議的處理接口,而 IP 協(xié)議的處理接口為 ip_rcv 函數(shù)。
我們?cè)倏纯?net_rx_action 函數(shù)的處理:
現(xiàn)在就非常清晰了,就是根據(jù)數(shù)據(jù)包的網(wǎng)絡(luò)層協(xié)議類型,然后從 ptype_base 數(shù)組中找到對(duì)應(yīng)的處理接口處理數(shù)據(jù)包,如 IP 協(xié)議的數(shù)據(jù)包就調(diào)用 ip_rcv 函數(shù)處理。
處理IP數(shù)據(jù)包
通過(guò)上面的分析,我們知道當(dāng)內(nèi)核接收到一個(gè) IP 數(shù)據(jù)包后,會(huì)調(diào)用 ip_rcv 函數(shù)處理這個(gè)數(shù)據(jù)包,下面我們來(lái)分析一下 ip_rcv 函數(shù)的實(shí)現(xiàn):
ip_rcv 函數(shù)主要對(duì)數(shù)據(jù)包的合法性進(jìn)行驗(yàn)證,如果數(shù)據(jù)包是合法的,那么就調(diào)用 ip_rcv_finish 函數(shù)繼續(xù)對(duì)數(shù)據(jù)包進(jìn)行處理。
我們繼續(xù)分析 ip_rcv_finish 函數(shù)的實(shí)現(xiàn):
為了簡(jiǎn)單起見(jiàn),我們?nèi)サ袅藢?duì) IP 選項(xiàng)的處理。在上面的代碼中,如果數(shù)據(jù)包的輸入路由緩存還沒(méi)設(shè)置,那么先調(diào)用 ip_route_input 函數(shù)獲取數(shù)據(jù)包的輸入路由緩存(ip_route_input 函數(shù)將會(huì)在 路由子系統(tǒng) 一章介紹,暫時(shí)可以忽略這個(gè)函數(shù))。
設(shè)置好數(shù)據(jù)包的路由緩存后,就調(diào)用路由緩存的 input 方法處理數(shù)據(jù)包。如果數(shù)據(jù)包是發(fā)送給本機(jī)的,那么路由緩存的 input 方法將會(huì)被設(shè)置為 ip_local_deliver(由 ip_route_input 函數(shù)設(shè)置)。
所有,如果數(shù)據(jù)包是發(fā)送給本機(jī),那么最終會(huì)調(diào)用 ip_local_deliver 函數(shù)處理數(shù)據(jù)包,我們繼續(xù)來(lái)分析 ip_local_deliver 函數(shù):
ip_local_deliver 函數(shù)首先判斷數(shù)據(jù)包是否為一個(gè) IP 分片(IP 分片將在下一篇文章介紹,暫時(shí)可以忽略),如果是就調(diào)用 ip_defrag 函數(shù)對(duì)數(shù)據(jù)包進(jìn)行分片重組處理。如果數(shù)據(jù)包不是一個(gè)分片或者分片重組成功,那么最終調(diào)用 ip_local_deliver_finish 函數(shù)處理數(shù)據(jù)包。
ip_local_deliver_finish 函數(shù)是 IP 層處理數(shù)據(jù)包的最后一步,我們接著分析 ip_local_deliver_finish 函數(shù)的實(shí)現(xiàn):
在上面代碼中,我們省略對(duì)原始套接字的處理(原始套接字將會(huì)在 原始套接字 一章中介紹)。ip_local_deliver_finish 函數(shù)的主要工作如下:
通過(guò)數(shù)據(jù)包的 IP 頭部獲取到上層協(xié)議(傳輸層)類型。
根據(jù)傳輸層協(xié)議類型從 inet_protos 數(shù)組中查找對(duì)應(yīng)的處理函數(shù)。
調(diào)用傳輸層協(xié)議的處理函數(shù)處理數(shù)據(jù)包。
inet_protos 數(shù)組保存了傳輸層協(xié)議的處理函數(shù),其的定義如下:
不同的傳輸層協(xié)議處理函數(shù),會(huì)根據(jù)其協(xié)議類型的值保存到 inet_protos 數(shù)組中。由于 inet_protos 數(shù)組只有32個(gè)元素,所以保存處理函數(shù)時(shí),需要將協(xié)議值與32進(jìn)行取模操作,得到一個(gè) 0 ~ 31 的值,然后把處理函數(shù)保存到 inet_protos 數(shù)組對(duì)應(yīng)位置上。如果有多個(gè)協(xié)議發(fā)生沖突,那么就通過(guò) next 字段連接起來(lái)。
通過(guò)調(diào)用 inet_add_protocol 函數(shù),可以向 inet_protos 數(shù)組注冊(cè)傳輸層協(xié)議的處理函數(shù)。例如 TCP協(xié)議 的處理函數(shù)定義如下:
所以,當(dāng)接收到一個(gè) TCP 協(xié)議數(shù)據(jù)包時(shí),將會(huì)調(diào)用 tcp_v4_rcv 函數(shù)處理此數(shù)據(jù)包。
最后,我以一幅圖來(lái)展示處理 IP 數(shù)據(jù)包的函數(shù)調(diào)用鏈:
