這裏看到非常多的netfilter hook,這是因爲netfilter主要是針對ip層的。
ip層的主要任務有下面5個方面:
1 ip數據包的校驗
2 防火牆的處理(也就是netfilter子系統)
3 處理options(這裏的options包含了一些可選的信息。比如時間戳或者源路由option).
4 切包和組包(由於mtu的存在,因此我們需要切包和組包).
5 接收,輸出和轉發操作。
接下來我們來看ip頭的結構:
- struct iphdr {
- #if defined(__LITTLE_ENDIAN_BITFIELD)
- __u8 ihl:4,
- version:4;
- #elif defined (__BIG_ENDIAN_BITFIELD)
- __u8 version:4,
- ihl:4;
- #else
- #error "Please fix <asm/byteorder.h>"
- #endif
- __u8 tos;
- __be16 tot_len;
- __be16 id;
- ///這裏要注意DF,MF和Fragment offset表示爲frag_off一個域.
- __be16 frag_off;
- __u8 ttl;
- __u8 protocol;
- __sum16 check;
- __be32 saddr;
- __be32 daddr;
- /*The options start here. */
- };
這裏每個字段的意思也就沒什麼好解釋得了,基本上對網絡協議有些瞭解的都會知道。我下面會介紹幾個主要的域。這裏可以看到在
首先來看options域。
前面已經說過了,這個域也就是包含一些其他程序(比如路由)需要的信息。這個域的大小爲0到40位,一般不會超過40個位,再大的options基本上很罕見。
options被分爲2種,一種是單字節的,一種是多字節的。下面我們就來看這兩種的結構:
先來看type字段,它的結構:
clas也就是這個options的類型了,copied如果被設置,則當數據包切片的時候,這個option必須被複制到每一個切好的包。而number則是類型所對應的值:下面這個圖就是ip option的一些類型值:
主要的類型定義在linux/ip.h裏面。
接下來length表示這個option的長度。poionter這個指針表示options的起始位移。option data存儲一些需要的數據。
隨便舉個ip options的例子,比如record route option,這個option用來保存輸出接口的ip地址,但它有大小限制,只能保存9個地址們如果超過九個就會忽略,接下來看下面的圖,a主機發送包到b主機:
最後我們來看一下數據報的切片和組包。
首先來看相關的ip頭裏面的域:
DF,表示不切片,因爲有時切片並組包耗時太長,影響性能。可是如何解決mtu等問題呢,這裏linux採用了Path MTU Discovery算法,來取得所能傳輸的最大mtu,而不是根據輸入幀的頭來判斷。
MF表示需要更多的切好的片。當一個包被切片之後,它設置mg爲true,知道最後一個分片,而這個分片的MF會被設置爲false(也就是0).當接收端接收到這最後一個切片時,就會開始組包,哪怕其他的切片還沒到達。
Fragment offset表示這個分片的數據包在原來數據包中的位移,只有憑藉這個才能正確組包。
ID,ip包的id,一個ip包的所有幀切片的id都是相同的。通過這個域,接受者能知道那些切片是屬於同一個包的。
我們再來看切片和組包有可能會出現的問題。
先來看丟包的問題,首先我們要知道只有當ip包全部接收到(也就是被切片的包)之後,纔會組包並將此數據包發送給高層。
丟包有3種情況:
1 有可能被路由器丟掉。
2 有可能由於crc校驗不通過而被丟掉。
3 有可能被防火牆過濾掉。
解決方法就是,如果一些切片沒有在給定的時間內到達的話,每一個路由器和主機都有一個定時器來清理髮送過的ip包的切片。
這裏要注意,ip層是沒有重傳機制的(ip協議是無連接的),因此必須等待高層來告訴它重傳整個數據包。
重傳的數據包不能重新使用未傳輸成功的數據報的id,也就是id不能相同。
由於kernel不能交換數據到硬盤,因此handling切片在內存中,會影響路由器的性能,因此linux對於切片的內存有了一個限制。
由於ip是一個無連接的協議,因此沒有流量控制什麼的,所以這些都交給上層去做。
標識每一個切片是屬於哪一個數據包,在linux中使用4個域來確定:
源地址,目的地址,ip包id,l4協議類型。
可是這有一個問題,那就是有可能不同的包,這四個參數都是相同的,比如經過nat轉發後的數據包。比如下面的例子,當pc1,pc2發出去的包被路由r修改掉源地址,並切片後,然後同時抵達S,這時就會出問題:
而ipv6就會更好的處理這個問題,他將只允許在原始的host進行切片。
最後看一下packet ID,在linux中,是每個目的地址一個ip packet id,每個id是一個16位的整數。這樣的話就降低了數據包的id有可能老的還沒到,新的就要重新使用老的的id。