《LwIP協(xié)議棧源碼詳解——TCP/IP協(xié)議的實現(xiàn)》IP層輸入

姓名:朱小鵬 ? ?學(xué)號:16010130023

轉(zhuǎn)載:

http://blog.sina.com.cn/s/blog_62a85b950101anw5.html

【嵌牛導(dǎo)讀】:對于IP層主要討論信息包的接收、分片數(shù)據(jù)包重裝、信息包的發(fā)送和轉(zhuǎn)發(fā)三個內(nèi)容。IP數(shù)據(jù)報頭結(jié)構(gòu)如下所示,其中,選項字段是可以沒有的,所以通常的IP數(shù)據(jù)報頭長度為20個字節(jié)。

【嵌牛鼻子】:IP層

【嵌牛提問】:LWIP中的IP層如何進(jìn)行信息包的接收、分片數(shù)據(jù)包重裝、信息包的發(fā)送和轉(zhuǎn)發(fā)?

【嵌牛正文】:

對于IP層主要討論信息包的接收、分片數(shù)據(jù)包重裝、信息包的發(fā)送和轉(zhuǎn)發(fā)三個內(nèi)容。IP數(shù)據(jù)報頭結(jié)構(gòu)如下所示,其中,選項字段是可以沒有的,所以通常的IP數(shù)據(jù)報頭長度為20個字節(jié)。

第一個字段是4bit的版本號,對于IPv4,該值為4;對于IPv6,該值為6。

接下來的4bit字段用于記錄首部長度,以字為單位。所以對于不含任何選項字段的IP報頭,則該長度值為5,由于該字段最大值為15,所以其能描述的最大IP報頭長度為15*4=60字節(jié)。

再下來是一個8bit的服務(wù)類型字段,該字段主要用于描述該IP數(shù)據(jù)包急需的服務(wù)類型,如最小延時、最大吞吐量、最高可靠性、最小費用等。這個字段在LWIP中沒啥用處。

16位的總長度字段描述了整個IP數(shù)據(jù)報,包括IP數(shù)據(jù)報頭的總字節(jié)數(shù)。理論上說,IP數(shù)據(jù)包總長度最大可達(dá)65535字節(jié),但在實際應(yīng)用中,底層鏈路可不允許這么大的數(shù)據(jù)包出現(xiàn)在鏈路上,因為這會大大增加數(shù)據(jù)出錯的可能性,所以在鏈路層往往會對大的IP數(shù)據(jù)包進(jìn)行分片,當(dāng)然這些都是后話。

接下來的16位標(biāo)識字段用于標(biāo)識IP層發(fā)送出去的每一份IP數(shù)據(jù)報,每發(fā)送一份報文,則該值加1。然后的3位標(biāo)志和13位片偏移字段用于在IP數(shù)據(jù)包分片時使用,這里先不討論。LWIP的較高版本才支持IP分片功能。

TTL字段描述該IP數(shù)據(jù)包最多能被轉(zhuǎn)發(fā)的次數(shù),每經(jīng)過一次轉(zhuǎn)發(fā),該值會減1,當(dāng)該值為0時,一個ICMP報文會被返回至源主機(jī)。

8位協(xié)議字段用來描述該IP數(shù)據(jù)包是來自于上層的哪個協(xié)議,該值為1表示為ICMP協(xié)議,該值為2表示IGMP協(xié)議,該值為6表示TCP協(xié)議,該值為17表UDP協(xié)議。

16位首部校驗和只針對IP首部做校驗,它并不關(guān)心其內(nèi)部數(shù)據(jù)在傳輸過程中出錯與否,對于數(shù)據(jù)的校驗是上層協(xié)議負(fù)責(zé)的,如ICMP、IGMP、TCP、UDP協(xié)議都會計算它們頭部以及整個數(shù)據(jù)區(qū)的長度。這里再COPY一段這個校驗和是怎樣生成以及在接收端是如何實驗校驗的。

在發(fā)送端為了計算一份數(shù)據(jù)報的IP檢驗和,首先把檢驗和字段置為0。然后,對首部中每個16 bit進(jìn)行二進(jìn)制反碼求和(整個首部看成是由一串16 bit的字組成),結(jié)果存在檢驗和字段中。當(dāng)接收端收到一份I P數(shù)據(jù)報后,同樣對首部中每個16 bit進(jìn)行二進(jìn)制反碼的求和。由于接收方在計算過程中包含了發(fā)送方保存在首部中的檢驗和字段,因此,如果首部在傳輸過程中沒有發(fā)生任何差錯,那么接收方計算的結(jié)果應(yīng)該為全1。如果結(jié)果不是全1(即檢驗和錯誤),那么IP就丟棄收到的數(shù)據(jù)報。但是不生成差錯報文,由上層去發(fā)現(xiàn)丟失的數(shù)據(jù)報并進(jìn)行重傳。

接下來是兩個32位的IP地址,不啰嗦了。最后一個字段是任選字段,不同的協(xié)議會選擇性的使用該字段,這里也不討論。

現(xiàn)在來看看LWIP中是怎么樣來描述這個IP數(shù)據(jù)報頭的,使用的結(jié)構(gòu)體叫ip_hdr:

struct ip_hdr {

PACK_STRUCT_FIELD(u16_t _v_hl_tos);//前三個字段:版本號、首部長度、服務(wù)類型

PACK_STRUCT_FIELD(u16_t _len);//總長度

PACK_STRUCT_FIELD(u16_t _id);//標(biāo)識字段

PACK_STRUCT_FIELD(u16_t _offset); // 3位標(biāo)志和13位片偏移字段

#define IP_RF 0x8000//

#define IP_DF 0x4000//不分組標(biāo)識位掩碼

#define IP_MF 0x2000//后續(xù)有分組到來標(biāo)識位掩碼

#define IP_OFFMASK 0x1fff//獲取13位片偏移字段的掩碼

PACK_STRUCT_FIELD(u16_t _ttl_proto);// TTL字段和協(xié)議字段

PACK_STRUCT_FIELD(u16_t _chksum);//首部校驗和字段

PACK_STRUCT_FIELD(struct ip_addr src);//源IP地址

PACK_STRUCT_FIELD(struct ip_addr dest);//目的IP地址

} PACK_STRUCT_STRUCT;

注意結(jié)構(gòu)體聲明的時候定義了幾個宏定義:IP_RF、IP_DF、IP_MF、IP_OFFMASK,它們是在求與分組相關(guān)兩個字段時要用到的掩碼,也可以在結(jié)構(gòu)體的外面進(jìn)行定義,無影響。

前面講過,從以太網(wǎng)底層進(jìn)來的數(shù)據(jù)包經(jīng)過ethernet_input函數(shù)分發(fā)給IP模塊或者ARP模塊,分發(fā)給IP模塊是通過調(diào)用ip_input函數(shù)完成的,當(dāng)然在遞交前,ethernet_input需要將數(shù)據(jù)包去掉以太網(wǎng)頭?,F(xiàn)在來看看數(shù)據(jù)包傳遞給ip_input后,該函數(shù)進(jìn)行了哪些方面的工作。這里我們先不涉及其內(nèi)部關(guān)于DHCP協(xié)議的相關(guān)處理。

第一件事是檢查IP頭部的版本號,如果該值不為4,則立即丟棄該數(shù)據(jù)包。更高版本的LWIP協(xié)議??梢灾С諭Pv6,但這里我們只討論IPv4。接下來函數(shù)檢查IP數(shù)據(jù)報頭是否只保存于一個pbuf中,如果不是 ,也直接丟棄該IP包,這是因為LWIP不允許IP數(shù)據(jù)包頭被分裝在不同的pbuf里面。同時,函數(shù)檢查IP報頭中的總長度字段是否大于遞交上來的數(shù)據(jù)包總長度,如果是,則說明存在傳輸錯誤,直接丟棄數(shù)據(jù)包。

然后是對IP數(shù)據(jù)報頭做校驗,該工作是函數(shù)inet_chksum完成的,如果校驗不通過則直接丟棄數(shù)據(jù)包。inet_chksum函數(shù)在后續(xù)有需要時會詳細(xì)講解。

接著,需要在這里對數(shù)據(jù)包進(jìn)行截斷操作,按照IP包頭記錄的總長度字段截取數(shù)據(jù)包,因為經(jīng)過ethernet_input傳遞上來的數(shù)據(jù)包只被去除了以太網(wǎng)數(shù)據(jù)包頭部,而對于可能存在的以太網(wǎng)填充字段和一定存在的以太網(wǎng)校驗字段(最后一字節(jié))沒做處理,我們在這里對它們進(jìn)行截斷,得到完整無冗余的IP數(shù)據(jù)包。

然后,函數(shù)檢測IP數(shù)據(jù)包中的目的IP地址是否與本機(jī)的相符,本機(jī)的IP地址是保存在netif結(jié)構(gòu)體變量中的,一個系統(tǒng)可能有著多個網(wǎng)卡設(shè)備,這就意味著它有多個netif結(jié)構(gòu)體變量分別用于描述這些網(wǎng)卡設(shè)備,也意味著本機(jī)有著多個IP地址,這些netif結(jié)構(gòu)體是被連接在netif_list鏈表上的。ip_input函數(shù)會遍歷netif_list鏈表上的netif結(jié)構(gòu)以找到匹配的IP地址,并記錄該netif結(jié)構(gòu)體變量,也即記錄該網(wǎng)卡。從這點看來,在ARP部分內(nèi)容中,對于某個接收到的ARP請求包,也應(yīng)該按照這種方式進(jìn)行遍歷后再給出ARP相應(yīng)更好,而源代碼并沒有這樣做,當(dāng)然,這只是個人意見。當(dāng)遍歷完成后,如果依舊沒有得到與匹配的netif結(jié)構(gòu)體變量,這說明該數(shù)據(jù)包不是給本機(jī)的,此時需要對數(shù)據(jù)包進(jìn)行轉(zhuǎn)發(fā)或者丟棄工作,這是通過宏定義IP_FORWARD來完成的,這里注意不要對廣播數(shù)據(jù)包進(jìn)行轉(zhuǎn)發(fā)。

再接下來,根據(jù)目標(biāo)IP地址判斷數(shù)據(jù)包是否為廣播或多播IP數(shù)據(jù)包,LWIP不對這些類型的數(shù)據(jù)包進(jìn)行相應(yīng)。

再接下來的工作可以說是ip_input函數(shù)中最復(fù)雜最難理解的部分,這就是IP分片數(shù)據(jù)包的重裝,ip_input函數(shù)通過數(shù)據(jù)包的3位標(biāo)志和13位片偏移字段判斷發(fā)給自己的該IP包是不是分片包,如果是,則需要將該分片包暫存,等到接收完所有分片包后,統(tǒng)一將整個數(shù)據(jù)包遞交給上層應(yīng)用程序。這是萬言難盡得過程,先在這里打住,我們在以后的內(nèi)容里面細(xì)細(xì)討論。如果是分片包,且不是最后一片,則函數(shù)到這里就返回了。

終于,能到達(dá)這一步的數(shù)據(jù)包必然是未分片的或經(jīng)過分片完整重裝后的數(shù)據(jù)包。此時,ip_input函數(shù)根據(jù)IP數(shù)據(jù)包頭內(nèi)部的協(xié)議字段判斷該數(shù)據(jù)包應(yīng)該被遞交給哪個上層協(xié)議,并調(diào)用相應(yīng)的函數(shù)遞交數(shù)據(jù)包。是UDP協(xié)議,則調(diào)用udp_input函數(shù);是TCP協(xié)議,則調(diào)用tcp_input函數(shù);是ICMP協(xié)議,則調(diào)用icmp_input函數(shù);是IGMP協(xié)議,則調(diào)用igmp_input函數(shù);如果都不是,則調(diào)用函數(shù)icmp_dest_unreach返回一個協(xié)議不可達(dá)ICMP數(shù)據(jù)包給源主機(jī),同時刪除數(shù)據(jù)包。

寫完收工!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容