姓名:朱小鵬 ? ?學號:16010130023
轉(zhuǎn)載:
http://blog.sina.com.cn/s/blog_62a85b950101anwr.html
【嵌牛導讀】:上一節(jié)還遺留有一個問題:IP分片包是怎么被插入相應的組裝鏈表的库继。這里用一個直觀的圖形來解釋這一過程行疏。
【嵌牛鼻子】:IP層
【嵌牛提問】:LWIP中的IP層如何進行信息包的接收耕皮、分片數(shù)據(jù)包重裝翰守、信息包的發(fā)送和轉(zhuǎn)發(fā)煤杀?
【嵌牛正文】:
上一節(jié)還遺留有一個問題:IP分片包是怎么被插入相應的組裝鏈表的钮追。這里用一個直觀的圖形來解釋這一過程晃财。VISO表示不太會用痹升,圖畫得亂78糟的建炫。
圖中展示了有兩個數(shù)據(jù)包正在被組裝的過程,兩個ip_reassdata結(jié)構(gòu)分別用于兩個數(shù)據(jù)包的組裝過程疼蛾。第一個數(shù)據(jù)包已經(jīng)組裝好了兩個分片數(shù)據(jù)肛跌,第二個組裝好了一個分片數(shù)據(jù)。LWIP并不像標準協(xié)議中描述的那樣察郁,另外分配一個數(shù)據(jù)結(jié)構(gòu)來描述各個分片數(shù)據(jù)衍慎,而是直接利用各個分片數(shù)據(jù),將數(shù)據(jù)中IP頭部的前八個字節(jié)拿來存儲組裝過程中的相關(guān)信息皮钠,因此每個進來的IP分片數(shù)據(jù)包頭都被改變了稳捆,也因此要使用ip_reassdata結(jié)構(gòu)中的iphdr字段來暫時保存IP數(shù)據(jù)包的完整頭部。
這八個字節(jié)被變成了什么值呢麦轰,這就是結(jié)構(gòu)體ip_reass_helper的內(nèi)容了乔夯。這個結(jié)構(gòu)體就是組裝過程中最為重要的結(jié)構(gòu)體了砖织。看看末荐,這恐怕是最簡單的一個結(jié)構(gòu)體了侧纯。
struct ip_reass_helper {
struct pbuf *next_pbuf;
u16_t start;
u16_t end;
};
next_pbuf字段在組裝過程中用來連接各個分片數(shù)據(jù)包;start字段用來記錄該分片數(shù)據(jù)包數(shù)據(jù)在整個IP包中的起始位置甲脏;同理眶熬,end字段表示在IP包中的結(jié)束位置。
函數(shù)ip_reass_chain_frag_into_datagram_and_validate(PS:神块请,這個函數(shù)名太長了)用來向一個ip_reassdata結(jié)構(gòu)中插入一個IP分片包pbuf聋涨,插入后檢查該IP包是否被組裝完畢,未組裝完畢則返回0负乡,否則返回非0值牍白。很明顯,這個函數(shù)的輸入?yún)?shù)有兩個抖棘,ip_reassdata結(jié)構(gòu)和分片包pbuf茂腥。下面仔細看看這個函數(shù)名最長的函數(shù)到底干了些什么工作。
首先它會從該IP分片包的頭部中提取信息切省,包括分片數(shù)據(jù)的起始偏移地址offset和分片數(shù)據(jù)的長度len最岗,之后它將該IP分片包的頭部得前八個字節(jié)強制轉(zhuǎn)換為ip_reass_helper結(jié)構(gòu),并將start字段的值置為offset朝捆;end字段的值置為offset+len般渡;next_pbuf字段的值置為NULL。到這里芙盘,進來的這個分片包已經(jīng)被改變面貌了驯用,它的IP頭部已經(jīng)被毀壞,下面就會將這個改頭換面的分片包進行插入操作了儒老。插入操作主要是利用比較各個ip_reass_helper的start和end字段的值以確定這個分片包被插在鏈表中的哪個位置蝴乔,插入位置的尋找是整個組裝過程中最難理解的部分了,但從原理上看是很簡單的一個過程驮樊,這里不再對查找插入位置及插入操作的源碼做解析薇正。
到這步,分片的數(shù)據(jù)包已經(jīng)被插入到相應的ip_reassdata結(jié)構(gòu)后的鏈表中了囚衔,此時這個函數(shù)名最長的函數(shù)要檢查是否這個ip_reassdata對應的數(shù)據(jù)包已經(jīng)組裝完畢挖腰。這里要分兩步來看,第一是判斷該數(shù)據(jù)包的最后一個分片包是否已經(jīng)到來练湿,在上面一節(jié)中已經(jīng)講過猴仑,當收到的IP分片包為某個IP包的最后一個分片時,函數(shù)ip_reass會把對應ip_reassdata結(jié)構(gòu)的flags字段置1鞠鲜,所以宁脊,如果檢測到某個ip_reassdata結(jié)構(gòu)的flags字段仍為0断国,說明最后一個分片還未被收到贤姆,IP數(shù)據(jù)包組裝肯定還未完成榆苞,此時,函數(shù)名最長的函數(shù)直接返回0即可霞捡。第二步坐漏,如果發(fā)現(xiàn)flags字段置1,說明最后一個分片包已經(jīng)收到碧信,但是整個IP是否被組裝完畢還是未知赊琳,因為在網(wǎng)絡上,分片包不是每次都能按次序到達砰碴,因此躏筏,收到的最后一個分片數(shù)據(jù)包不一定是最后一個分片包。此時需要遍歷ip_reassdata結(jié)構(gòu)后面的各個分片包鏈表呈枉,以檢測是否還有分片包未被接收到趁尼。
到這步,ip_reass_chain_frag_into_datagram_and_validate函數(shù)就完成工作了猖辫,它將分片的數(shù)據(jù)插入了某個ip_reassdata結(jié)構(gòu)的數(shù)據(jù)分片鏈表酥泞,并檢測該IP數(shù)據(jù)包是否被組裝完畢,組裝完畢則返回一個非0值啃憎,否則返回0值芝囤。
這里,我們有回到了ip_reass函數(shù)辛萍,ip_reass通過函數(shù)調(diào)用將某個分片數(shù)據(jù)包插入相應的個ip_reassdata結(jié)構(gòu)后悯姊,通過函數(shù)的返回值來決定要做的下一步操作。如果數(shù)據(jù)包組裝未完成贩毕,ip_reass函數(shù)需要向調(diào)用它的函數(shù)返回一個空指針挠轴,如果數(shù)據(jù)包組裝完成,它就要向調(diào)用它的函數(shù)返回這個組裝好的數(shù)據(jù)包耳幢。從上面的圖中可以看出岸晦,被組裝好的數(shù)據(jù)包是全部分片被掛接在一個ip_reassdata結(jié)構(gòu)上的,ip_reass函數(shù)需要從這個ip_reassdata結(jié)構(gòu)上取下相應的各個分片數(shù)據(jù)睛藻,并刪除各個分片中的不必要信息启上,然后將整個數(shù)據(jù)包返回給調(diào)用者。為了完成這個任務店印,ip_reass函數(shù)進行了下面的工作冈在。
先將ip_reassdata結(jié)構(gòu)中的iphdr字段各個值做相應的修正,如修正報文總長度按摘、校驗和包券,然后將iphdr字段全部拷貝到第一個分片包的IP頭部字段中纫谅,這樣整個IP數(shù)據(jù)包的頭部就出現(xiàn)在第一個分片信息包中了,接下來從第二個分片包開始溅固,將它們的IP頭部信息刪除付秕,這樣,一個完整的IP數(shù)據(jù)包就重新組裝好了侍郭。最后询吴,調(diào)用ip_reass_dequeue_datagram函數(shù)刪除數(shù)據(jù)包組裝過程中使用的ip_reassdata結(jié)構(gòu)體,即從上圖所述的reassdatagrams鏈表刪除對應reassdata的節(jié)點亮元。好了猛计,功德圓滿!
這個圈子繞得有點大爆捞,本來是在講ip_input函數(shù)的奉瘤,結(jié)果就講到了ip_reass函數(shù),后來又講到了ip_reass_chain_frag_into_datagram_and_validate函數(shù)煮甥。沒辦法盗温,誰讓后面兩個函數(shù)是為ip_input函數(shù)服務的呢!ip_input函數(shù)使用ip_reass組裝好的數(shù)據(jù)包遞交到上層TCP或UDP等應用中去苛秕,在前面已經(jīng)講過了肌访。
現(xiàn)在我們還是要回到原路上,走上ip_input這條道路艇劫。ip_input的基本流程已經(jīng)在前面講得很清楚了吼驶,還有一個函數(shù)為它服務,即ip_forward函數(shù)店煞,它主要是完成數(shù)據(jù)包的轉(zhuǎn)發(fā)工作蟹演,當設備接收到的數(shù)據(jù)包不是給自己的時候,它就可以選擇將該數(shù)據(jù)包轉(zhuǎn)發(fā)出去顷蟀,本來這里沒有必要講ip_forward函數(shù)的酒请,因為在一般的應用中,這項功能會被禁止鸣个,設備收到不是給自己的數(shù)據(jù)包時羞反,將在ip_input函數(shù)處理的初期被丟棄。但到目前囤萤,我們還未涉及到任何關(guān)于IP數(shù)據(jù)包發(fā)送的內(nèi)容昼窗,考慮了很久,還是覺得應該把ip_forward函數(shù)講解一下涛舍,因為數(shù)據(jù)包的轉(zhuǎn)發(fā)與數(shù)據(jù)包的發(fā)送是完全一樣的原理澄惊,使用了完全相同的接口函數(shù),因此講解了ip_forward函數(shù)就等于講解了IP層數(shù)據(jù)包發(fā)送的所有工作細節(jié)。在TCP層或UDP層必然涉及到數(shù)據(jù)包的發(fā)送工作掸驱,在這里就利用ip_forward函數(shù)將IP層數(shù)據(jù)包發(fā)送的整個過程講解清楚肛搬,這樣邏輯清楚,利于理解毕贼!
當收到一個IP數(shù)據(jù)包后温赔,LWIP會遍歷所有網(wǎng)絡接口的IP地址,判斷這個數(shù)據(jù)包是不是給自己的帅刀,如果不是让腹,就要調(diào)用收到該數(shù)據(jù)包的那個網(wǎng)絡接口將數(shù)據(jù)包轉(zhuǎn)發(fā)出去远剩。但是不慌扣溺,轉(zhuǎn)發(fā)前還要檢測這個包是不是一個廣播包,如果是瓜晤,直接丟棄锥余,不做處理。現(xiàn)在來看看數(shù)據(jù)包轉(zhuǎn)發(fā)函數(shù)ip_forward做了哪些工作呢痢掠,這個函數(shù)的輸入?yún)?shù)有三個:要轉(zhuǎn)發(fā)的數(shù)據(jù)包指針驱犹,要轉(zhuǎn)發(fā)的數(shù)據(jù)包的IP報頭指針,收到該數(shù)據(jù)包的的網(wǎng)絡接口數(shù)據(jù)結(jié)構(gòu)netif指針足画。
首先雄驹,調(diào)用ip_route函數(shù)找到轉(zhuǎn)發(fā)該數(shù)據(jù)包應該使用的網(wǎng)絡接口,ip_route函數(shù)以數(shù)據(jù)包IP報頭中的目標地址為參數(shù)淹辞,查找應該使用的相關(guān)結(jié)構(gòu)医舆。如果找不到滿足要求的接口,則選擇缺省網(wǎng)絡接口象缀。ip_route函數(shù)現(xiàn)在這里打住蔬将,在講完ip_forward函數(shù)之后,再對它進行詳細的講解央星。
ip_forward檢查ip_route函數(shù)找到的網(wǎng)絡接口是否為有效霞怀,所謂有效,即不能為空莉给,也不能為接收到該IP包的那個接口毙石。當判定網(wǎng)絡接口為無效時,數(shù)據(jù)包不會被轉(zhuǎn)發(fā)颓遏。當可以用某個網(wǎng)絡接口轉(zhuǎn)發(fā)數(shù)據(jù)包時徐矩,ip_forward先將該IP報頭中TTL字段值減1,若TTL變?yōu)?州泊,則需要向源主機發(fā)送一份超時ICMP信息丧蘸,表示當前數(shù)據(jù)包的生存周期到了,這個數(shù)據(jù)包在這里被丟棄,不會被轉(zhuǎn)發(fā)出去力喷。至于怎樣發(fā)送這個超時的ICMP信息包刽漂,這就涉及到IP層數(shù)據(jù)包的發(fā)送函數(shù)ip_output了,我們將在后面慢慢道來弟孟。
接下來函數(shù)重新計算頭部校驗和贝咙,因為頭部TTL字段的值已經(jīng)被修改,最后調(diào)用netif結(jié)構(gòu)注冊的output函數(shù)拂募,該函數(shù)將數(shù)據(jù)包組裝成以太網(wǎng)數(shù)據(jù)幀并發(fā)送出去庭猩。前面說過了,這個函數(shù)就是etharp_output陈症。
到這里ip_forward函數(shù)的工作就完成了蔼水,還剩下兩個問題,ip_route函數(shù)和怎樣發(fā)送一個超時的ICMP信息包出去录肯。這里講解第一個問題趴腋,第二個問題放在ICMP部分。ip_route函數(shù)以目標IP地址為輸入?yún)?shù)论咏,然后在網(wǎng)絡接口結(jié)構(gòu)鏈表netif_list上找尋與該IP地址在同一子網(wǎng)上的網(wǎng)絡接口优炬,若找到則返回滿足要求的網(wǎng)絡接口,若找不到則返回缺省網(wǎng)絡接口厅贪。如此的簡單蠢护,不多說。