《LwIP協(xié)議棧源碼詳解——TCP/IP協(xié)議的實(shí)現(xiàn)》IP分片重裝2

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

轉(zhuǎn)載:

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

【嵌牛導(dǎo)讀】:上一節(jié)還遺留有一個(gè)問(wèn)題:IP分片包是怎么被插入相應(yīng)的組裝鏈表的。這里用一個(gè)直觀的圖形來(lái)解釋這一過(guò)程。

【嵌牛鼻子】:IP層

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

【嵌牛正文】:

上一節(jié)還遺留有一個(gè)問(wèn)題:IP分片包是怎么被插入相應(yīng)的組裝鏈表的。這里用一個(gè)直觀的圖形來(lái)解釋這一過(guò)程。VISO表示不太會(huì)用,圖畫得亂78糟的。

圖中展示了有兩個(gè)數(shù)據(jù)包正在被組裝的過(guò)程,兩個(gè)ip_reassdata結(jié)構(gòu)分別用于兩個(gè)數(shù)據(jù)包的組裝過(guò)程。第一個(gè)數(shù)據(jù)包已經(jīng)組裝好了兩個(gè)分片數(shù)據(jù),第二個(gè)組裝好了一個(gè)分片數(shù)據(jù)。LWIP并不像標(biāo)準(zhǔn)協(xié)議中描述的那樣,另外分配一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)描述各個(gè)分片數(shù)據(jù),而是直接利用各個(gè)分片數(shù)據(jù),將數(shù)據(jù)中IP頭部的前八個(gè)字節(jié)拿來(lái)存儲(chǔ)組裝過(guò)程中的相關(guān)信息,因此每個(gè)進(jìn)來(lái)的IP分片數(shù)據(jù)包頭都被改變了,也因此要使用ip_reassdata結(jié)構(gòu)中的iphdr字段來(lái)暫時(shí)保存IP數(shù)據(jù)包的完整頭部。

這八個(gè)字節(jié)被變成了什么值呢,這就是結(jié)構(gòu)體ip_reass_helper的內(nèi)容了。這個(gè)結(jié)構(gòu)體就是組裝過(guò)程中最為重要的結(jié)構(gòu)體了??纯矗@恐怕是最簡(jiǎn)單的一個(gè)結(jié)構(gòu)體了。

struct ip_reass_helper {

struct pbuf *next_pbuf;

u16_t start;

u16_t end;

};

next_pbuf字段在組裝過(guò)程中用來(lái)連接各個(gè)分片數(shù)據(jù)包;start字段用來(lái)記錄該分片數(shù)據(jù)包數(shù)據(jù)在整個(gè)IP包中的起始位置;同理,end字段表示在IP包中的結(jié)束位置。

函數(shù)ip_reass_chain_frag_into_datagram_and_validate(PS:神,這個(gè)函數(shù)名太長(zhǎng)了)用來(lái)向一個(gè)ip_reassdata結(jié)構(gòu)中插入一個(gè)IP分片包pbuf,插入后檢查該IP包是否被組裝完畢,未組裝完畢則返回0,否則返回非0值。很明顯,這個(gè)函數(shù)的輸入?yún)?shù)有兩個(gè),ip_reassdata結(jié)構(gòu)和分片包pbuf。下面仔細(xì)看看這個(gè)函數(shù)名最長(zhǎng)的函數(shù)到底干了些什么工作。

首先它會(huì)從該IP分片包的頭部中提取信息,包括分片數(shù)據(jù)的起始偏移地址offset和分片數(shù)據(jù)的長(zhǎng)度len,之后它將該IP分片包的頭部得前八個(gè)字節(jié)強(qiáng)制轉(zhuǎn)換為ip_reass_helper結(jié)構(gòu),并將start字段的值置為offset;end字段的值置為offset+len;next_pbuf字段的值置為NULL。到這里,進(jìn)來(lái)的這個(gè)分片包已經(jīng)被改變面貌了,它的IP頭部已經(jīng)被毀壞,下面就會(huì)將這個(gè)改頭換面的分片包進(jìn)行插入操作了。插入操作主要是利用比較各個(gè)ip_reass_helper的start和end字段的值以確定這個(gè)分片包被插在鏈表中的哪個(gè)位置,插入位置的尋找是整個(gè)組裝過(guò)程中最難理解的部分了,但從原理上看是很簡(jiǎn)單的一個(gè)過(guò)程,這里不再對(duì)查找插入位置及插入操作的源碼做解析。

到這步,分片的數(shù)據(jù)包已經(jīng)被插入到相應(yīng)的ip_reassdata結(jié)構(gòu)后的鏈表中了,此時(shí)這個(gè)函數(shù)名最長(zhǎng)的函數(shù)要檢查是否這個(gè)ip_reassdata對(duì)應(yīng)的數(shù)據(jù)包已經(jīng)組裝完畢。這里要分兩步來(lái)看,第一是判斷該數(shù)據(jù)包的最后一個(gè)分片包是否已經(jīng)到來(lái),在上面一節(jié)中已經(jīng)講過(guò),當(dāng)收到的IP分片包為某個(gè)IP包的最后一個(gè)分片時(shí),函數(shù)ip_reass會(huì)把對(duì)應(yīng)ip_reassdata結(jié)構(gòu)的flags字段置1,所以,如果檢測(cè)到某個(gè)ip_reassdata結(jié)構(gòu)的flags字段仍為0,說(shuō)明最后一個(gè)分片還未被收到,IP數(shù)據(jù)包組裝肯定還未完成,此時(shí),函數(shù)名最長(zhǎng)的函數(shù)直接返回0即可。第二步,如果發(fā)現(xiàn)flags字段置1,說(shuō)明最后一個(gè)分片包已經(jīng)收到,但是整個(gè)IP是否被組裝完畢還是未知,因?yàn)樵诰W(wǎng)絡(luò)上,分片包不是每次都能按次序到達(dá),因此,收到的最后一個(gè)分片數(shù)據(jù)包不一定是最后一個(gè)分片包。此時(shí)需要遍歷ip_reassdata結(jié)構(gòu)后面的各個(gè)分片包鏈表,以檢測(cè)是否還有分片包未被接收到。

到這步,ip_reass_chain_frag_into_datagram_and_validate函數(shù)就完成工作了,它將分片的數(shù)據(jù)插入了某個(gè)ip_reassdata結(jié)構(gòu)的數(shù)據(jù)分片鏈表,并檢測(cè)該IP數(shù)據(jù)包是否被組裝完畢,組裝完畢則返回一個(gè)非0值,否則返回0值。

這里,我們有回到了ip_reass函數(shù),ip_reass通過(guò)函數(shù)調(diào)用將某個(gè)分片數(shù)據(jù)包插入相應(yīng)的個(gè)ip_reassdata結(jié)構(gòu)后,通過(guò)函數(shù)的返回值來(lái)決定要做的下一步操作。如果數(shù)據(jù)包組裝未完成,ip_reass函數(shù)需要向調(diào)用它的函數(shù)返回一個(gè)空指針,如果數(shù)據(jù)包組裝完成,它就要向調(diào)用它的函數(shù)返回這個(gè)組裝好的數(shù)據(jù)包。從上面的圖中可以看出,被組裝好的數(shù)據(jù)包是全部分片被掛接在一個(gè)ip_reassdata結(jié)構(gòu)上的,ip_reass函數(shù)需要從這個(gè)ip_reassdata結(jié)構(gòu)上取下相應(yīng)的各個(gè)分片數(shù)據(jù),并刪除各個(gè)分片中的不必要信息,然后將整個(gè)數(shù)據(jù)包返回給調(diào)用者。為了完成這個(gè)任務(wù),ip_reass函數(shù)進(jìn)行了下面的工作。

先將ip_reassdata結(jié)構(gòu)中的iphdr字段各個(gè)值做相應(yīng)的修正,如修正報(bào)文總長(zhǎng)度、校驗(yàn)和,然后將iphdr字段全部拷貝到第一個(gè)分片包的IP頭部字段中,這樣整個(gè)IP數(shù)據(jù)包的頭部就出現(xiàn)在第一個(gè)分片信息包中了,接下來(lái)從第二個(gè)分片包開始,將它們的IP頭部信息刪除,這樣,一個(gè)完整的IP數(shù)據(jù)包就重新組裝好了。最后,調(diào)用ip_reass_dequeue_datagram函數(shù)刪除數(shù)據(jù)包組裝過(guò)程中使用的ip_reassdata結(jié)構(gòu)體,即從上圖所述的reassdatagrams鏈表刪除對(duì)應(yīng)reassdata的節(jié)點(diǎn)。好了,功德圓滿!

這個(gè)圈子繞得有點(diǎn)大,本來(lái)是在講ip_input函數(shù)的,結(jié)果就講到了ip_reass函數(shù),后來(lái)又講到了ip_reass_chain_frag_into_datagram_and_validate函數(shù)。沒(méi)辦法,誰(shuí)讓后面兩個(gè)函數(shù)是為ip_input函數(shù)服務(wù)的呢!ip_input函數(shù)使用ip_reass組裝好的數(shù)據(jù)包遞交到上層TCP或UDP等應(yīng)用中去,在前面已經(jīng)講過(guò)了。

現(xiàn)在我們還是要回到原路上,走上ip_input這條道路。ip_input的基本流程已經(jīng)在前面講得很清楚了,還有一個(gè)函數(shù)為它服務(wù),即ip_forward函數(shù),它主要是完成數(shù)據(jù)包的轉(zhuǎn)發(fā)工作,當(dāng)設(shè)備接收到的數(shù)據(jù)包不是給自己的時(shí)候,它就可以選擇將該數(shù)據(jù)包轉(zhuǎn)發(fā)出去,本來(lái)這里沒(méi)有必要講ip_forward函數(shù)的,因?yàn)樵谝话愕膽?yīng)用中,這項(xiàng)功能會(huì)被禁止,設(shè)備收到不是給自己的數(shù)據(jù)包時(shí),將在ip_input函數(shù)處理的初期被丟棄。但到目前,我們還未涉及到任何關(guān)于IP數(shù)據(jù)包發(fā)送的內(nèi)容,考慮了很久,還是覺(jué)得應(yīng)該把ip_forward函數(shù)講解一下,因?yàn)閿?shù)據(jù)包的轉(zhuǎn)發(fā)與數(shù)據(jù)包的發(fā)送是完全一樣的原理,使用了完全相同的接口函數(shù),因此講解了ip_forward函數(shù)就等于講解了IP層數(shù)據(jù)包發(fā)送的所有工作細(xì)節(jié)。在TCP層或UDP層必然涉及到數(shù)據(jù)包的發(fā)送工作,在這里就利用ip_forward函數(shù)將IP層數(shù)據(jù)包發(fā)送的整個(gè)過(guò)程講解清楚,這樣邏輯清楚,利于理解!

當(dāng)收到一個(gè)IP數(shù)據(jù)包后,LWIP會(huì)遍歷所有網(wǎng)絡(luò)接口的IP地址,判斷這個(gè)數(shù)據(jù)包是不是給自己的,如果不是,就要調(diào)用收到該數(shù)據(jù)包的那個(gè)網(wǎng)絡(luò)接口將數(shù)據(jù)包轉(zhuǎn)發(fā)出去。但是不慌,轉(zhuǎn)發(fā)前還要檢測(cè)這個(gè)包是不是一個(gè)廣播包,如果是,直接丟棄,不做處理?,F(xiàn)在來(lái)看看數(shù)據(jù)包轉(zhuǎn)發(fā)函數(shù)ip_forward做了哪些工作呢,這個(gè)函數(shù)的輸入?yún)?shù)有三個(gè):要轉(zhuǎn)發(fā)的數(shù)據(jù)包指針,要轉(zhuǎn)發(fā)的數(shù)據(jù)包的IP報(bào)頭指針,收到該數(shù)據(jù)包的的網(wǎng)絡(luò)接口數(shù)據(jù)結(jié)構(gòu)netif指針。

首先,調(diào)用ip_route函數(shù)找到轉(zhuǎn)發(fā)該數(shù)據(jù)包應(yīng)該使用的網(wǎng)絡(luò)接口,ip_route函數(shù)以數(shù)據(jù)包IP報(bào)頭中的目標(biāo)地址為參數(shù),查找應(yīng)該使用的相關(guān)結(jié)構(gòu)。如果找不到滿足要求的接口,則選擇缺省網(wǎng)絡(luò)接口。ip_route函數(shù)現(xiàn)在這里打住,在講完ip_forward函數(shù)之后,再對(duì)它進(jìn)行詳細(xì)的講解。

ip_forward檢查ip_route函數(shù)找到的網(wǎng)絡(luò)接口是否為有效,所謂有效,即不能為空,也不能為接收到該IP包的那個(gè)接口。當(dāng)判定網(wǎng)絡(luò)接口為無(wú)效時(shí),數(shù)據(jù)包不會(huì)被轉(zhuǎn)發(fā)。當(dāng)可以用某個(gè)網(wǎng)絡(luò)接口轉(zhuǎn)發(fā)數(shù)據(jù)包時(shí),ip_forward先將該IP報(bào)頭中TTL字段值減1,若TTL變?yōu)?,則需要向源主機(jī)發(fā)送一份超時(shí)ICMP信息,表示當(dāng)前數(shù)據(jù)包的生存周期到了,這個(gè)數(shù)據(jù)包在這里被丟棄,不會(huì)被轉(zhuǎn)發(fā)出去。至于怎樣發(fā)送這個(gè)超時(shí)的ICMP信息包,這就涉及到IP層數(shù)據(jù)包的發(fā)送函數(shù)ip_output了,我們將在后面慢慢道來(lái)。

接下來(lái)函數(shù)重新計(jì)算頭部校驗(yàn)和,因?yàn)轭^部TTL字段的值已經(jīng)被修改,最后調(diào)用netif結(jié)構(gòu)注冊(cè)的output函數(shù),該函數(shù)將數(shù)據(jù)包組裝成以太網(wǎng)數(shù)據(jù)幀并發(fā)送出去。前面說(shuō)過(guò)了,這個(gè)函數(shù)就是etharp_output。

到這里ip_forward函數(shù)的工作就完成了,還剩下兩個(gè)問(wèn)題,ip_route函數(shù)和怎樣發(fā)送一個(gè)超時(shí)的ICMP信息包出去。這里講解第一個(gè)問(wèn)題,第二個(gè)問(wèn)題放在ICMP部分。ip_route函數(shù)以目標(biāo)IP地址為輸入?yún)?shù),然后在網(wǎng)絡(luò)接口結(jié)構(gòu)鏈表netif_list上找尋與該IP地址在同一子網(wǎng)上的網(wǎng)絡(luò)接口,若找到則返回滿足要求的網(wǎng)絡(luò)接口,若找不到則返回缺省網(wǎng)絡(luò)接口。如此的簡(jiǎn)單,不多說(shuō)。

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

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

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