《TCP/IP詳解卷2:實現》筆記--IP:網際協議
本章介紹IP分組的結構和基本的IP處理過程,包括輸入,轉發和輸出。下圖顯示了IP層常見的組織形式。
在之前的文章中,我們看到了網路介面如何把到達的IP分組放到IP輸入佇列ipintrq中去,並如何呼叫一個軟體中斷,如下圖所示:
因為硬體中斷的優先順序比軟體中斷的要高,所以在發生一次軟體中斷之前,有的分組可能會被放到佇列中。在軟體中斷中,ipintr
函式不斷從ipintrq中移走和處理分組,直到對壘為空。在最終的目的地,IP把分組重灌為資料包,並通過函式呼叫把該資料包直接
傳給適當的運輸層協議。如果分組沒有到達最後的目的地,並且如果主機被配置成一個路由器,則IP把分組傳給ip_forward。傳輸
協議和ip_forward把要輸出的分組傳給ip_output,由ip_output完成ip首部,選擇輸出介面以及在必要時對分組分片。最終的分組
被傳給合適的網路介面輸出函式。
當產生差錯時,IP丟棄該分組,並在某些條件下向分組的源站發出一個差錯報文,這些報文是ICMP的一部分。
1.IP分組
我們把傳輸層協議交給IP的資料稱為報文。典型的報文包含一個傳輸層首部和應用程式資料。下圖所示的傳輸協議時UDP。IP在報
文的首部前加上它自己的首部形成一個數據包。如果在選定的網路中,資料報長度太大,IP就把資料包分裂成幾個分片,每個分片
包含它自己的IP首部和一段原來的資料報的資料。
下圖顯示了IP首部的結構。
下圖包含了IP結構中的各成員的名字
標準的IP首部長度是20個位元組,所以ip_hl必須大於等於5.大於5表示IP選項緊跟在標準首部後,如ip_hl的最大值為15,允許最多
40各位元組的選項。ip_hl是以4位元組為單位計算的。
2.輸入處理:ipintr函式
當介面把分組放到ipintrq上排隊後,通過schednetisr呼叫一個軟中斷。當該軟中斷髮生時,如果IP處理過程已經由schednetisr
排程,則核心呼叫ipintr。在呼叫ipintr之前,cpu的優先順序被改成splnet。
ipintr是一個大函式,主要分4部分討論:
1.對到達分組驗證
2.選項處理及轉發
3.分組重灌
4.分用
其中選項處理和分用重灌比較複雜,會在以後作為單獨的章節進行說明。
2.1.驗證
把分組從ipintrq中取出,驗證他們的內容,損壞和有差錯的分組被自動丟棄。 IP版本: 如果網路介面沒有指派IP地址,則必須丟掉所有的IP分組。 在ipintr訪問任何IP首部欄位之前,它必須證實ip_v是4. IP首部檢驗和: ipintr把由in_cksum計算出來的檢驗和儲存首部的ip_sum欄位中。一個未被破壞的首部應該具有0檢驗和。如果為非0,則該 分組被自動丟棄。下面的章節具體介紹in_cksum。 3.位元組順序 NTOHS巨集將IP首部中的所有16bit的值從網路位元組序轉換成主機位元組序:分組長度(ip_len)、資料報識別符號(ip_id)和分片 偏移(ip_off)。 4.分組長度 如果分組的邏輯長度(ip_len)比儲存在mbuf中的資料量(m_pkthdr.len)大,並且有些位元組被丟失了,就必須丟棄該分組, 如果mbuf比分組大,則去掉多餘的位元組。 丟失位元組的一個常見原因是因為資料到達某個沒有或只有很少快取的串列埠裝置,裝置丟棄到達的位元組。 多餘位元組可能產生,如在某個乙太網裝置上,當一個IP分組的大小比乙太網要求的最小長度還要小時。傳送該幀是加上的多餘 位元組就在這裡被丟棄。2.2.選項處理和轉發
接下來呼叫ip_dooptions來處理IP選項,然後決定分組是否到達它最後的目的地。如果分組沒有到達最後目的地,則Net/3會
嘗試轉發該分組;如果分組到達最後目的地,就被交付給合適的傳輸層協議。
1.選項處理
ip_dooptions處理選項,如果ip_dooptions返回0,ipintr將繼續處理該分組;否則ip_dooptions通過轉發或丟棄分組完成對該
分組的處理,ipintr可以處理輸入佇列中的下一個分組。
處理完選項後,ipintr通過把ip首部內的ip_dst與配置的所有本地介面的ip地址比較,以決定分組是否已經到達最終目的地。ipintr
必須考慮與介面相關的幾個廣播地址、一個或多個單播地址以及任意多個多播地址。
2.轉發
如果ip_dst與所有地址都不匹配,分組還沒有達到最終目的地。如果不準備轉發,則丟棄分組,否則,ip_forward嘗試把分組路由
到它的最終目的地。
2.3.分組重灌和分用
ipintr函式最後進行分組的重組和分組,在後面的章節會詳細進行介紹。
最後ipintr呼叫選定protosw結構中的pr_input函式來處理資料報包含的運輸報文,當pr_input返回時,ipintr繼續處理ipintrq中的
下一個分組。
3.IP頭部檢驗:in_cksum函式
IP首部校驗和的計算方法: 1.把校驗和欄位清零。 2.然後對每16位(2位元組)進行二進位制反碼求和,反碼求和的意思是先對每16位求和,再將得到的和轉為反碼。 接下來詳細描述反碼求和的步驟:看下面的程式碼 基本演算法: [cpp] view plain copy print?- SHORT checksum(USHORT* buffer, int size)
- {
- unsigned long cksum = 0;
- while(size>1)
- {
- cksum += *buffer++;
- size -= sizeof(USHORT);
- }
- if(size)
- {
- cksum += *(UCHAR*)buffer;
- }
- cksum = (cksum>>16) + (cksum&0xffff);
- cksum += (cksum>>16);
- return (USHORT)(~cksum);
- }
SHORT checksum(USHORT* buffer, int size)
{
unsigned long cksum = 0;
while(size>1)
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if(size)
{
cksum += *(UCHAR*)buffer;
}
cksum = (cksum>>16) + (cksum&0xffff);
cksum += (cksum>>16);
return (USHORT)(~cksum);
}
引數buffer是指向16位整數的指標,剛開始指向的是IP首部的起始地址,引數size是IP首部的大小。while迴圈是將IP首部的內容
以16位為單元加在一起,如果沒有整除(即size還有餘下的不足16位的部分),則加上餘下的部分,此時的cksum就是相加後的結
果,這個結果往往超出了16位,因為校驗和是16位的,所以要將高16位和計算得到的cksum再加工。
再加工第一步:cksum = (cksum>>16) + (cksum&0xffff); sum>>16是將高16位移位到低16位,sum&0xffff是取出低16位,相加得到新的cksum。
再加工第二步:cksum += (cksum>>16); 第一步相加時很可能會產生進位,因此要再次把進位移到低16位進行相加。
這樣就加工好了,接下來就是取反,並強制轉換為16位,這樣就得到了最終的校驗和。
校驗和計算出來了,接下來就是該如何校驗:
接收方進行校驗時,也是對每16位進行二進位制反碼求和。接收方計算校驗和時的首部與傳送方計算校驗和時的首部相比,多了一
個傳送方計算出來的校驗和。因此,如果首部在傳輸過程中沒有發生差錯,那麼接收方計算的結果應該為全一,因為接收方計算除
校驗和以外的部分得到值是校驗和的反碼,再加多出來的校驗和當然是全一了。
最後對上述過程舉個例子:
IP頭:
45 00 00 31
89 F5 00 00
6E 06 00 00(校驗欄位)
DE B7 45 5D -> 222.183.69.93
C0 A8 00 DC -> 192.168.0.220
計算:
4500 + 0031 +89F5 + 0000 + 6e06+ 0000 + DEB7 + 455D + C0A8 + 00DC =3 22C4
0003 + 22C4 = 22C7
~22C7 = DD38 ->即為應填充的校驗和
當接受到IP資料包時,要檢查IP頭是否正確,則對IP頭進行檢驗,方法同上:
計算:
4500 + 0031 +89F5 + 0000 + 6E06+ DD38 + DEB7 + 455D + C0A8 + 00DC =3 FFFC
0003 + FFFC = FFFF
得到的結果是全一,正確。