1 前置知識學習
1.1 MTU
MTU是最大傳輸單元( Maximum Transmission Unit)的縮寫,指一個接口無需分片所能發送的數據包的最大字節數。
MTU范圍在46 ~ 1500字節,默認一般都是1500。
1)MTU為1500時計算總長度
7字節前導碼+1字節幀開始定界符+6字節的目的MAC+6字節的源MAC+2字節的幀類型+1500字節IP頭及數據+4字節的 FCS = 1526字節。
2)為什么我們抓包得到的最大幀是1514字節?
當數據幀到達網卡時,在物理層上網卡要先去掉前導同步碼和幀開始定界符,然后對幀進行CRC檢驗,如果幀校驗和錯,就丟棄此幀。如果校驗和正確,就去掉FCS再交給“設備驅動程序”做進一步處理。這時我們的抓包軟件才能抓到數據,因此,抓包軟件抓到的是去掉前導碼、幀開始定界符、FCS之外的數據,其最大值是 6+6+2+1500=1514。
3)如果MTU為46,不分片的最大以太幀長度
以太網規定,以太網幀數據域部分最小為46字節,也就是以太網幀最小是 6+6+2+46+4=64。抓包時要除去4個字節的FCS,因此,抓包看到是60字節。
當數據字段的長度小於46字節時,MAC層(由驅動程序)會在數據字段的后面填充數據,以滿足數據幀長不小於64字節。
1.2 IP分片原理
IP分片發生在IP層,不僅源端主機會進行分片,中間的路由器也有可能分片,因為不同的網絡的MTU是不一樣的,如果傳輸路徑上的某個網絡的MTU比源端網絡的MTU要小,路由器就可能對IP數據報再次進行分片。而分片數據的重組只會發生在目的端的IP層。
下圖紅色框里是我們熟悉的IP首部格式,與分片相關的是圖中淺綠色+深綠色的方框(共32位)
1)淺綠框的“16字節標識”
同一個數據報的各個分片的標識是一樣的,目的端會根據這個標識來判斷IP分片是否屬於同一個IP數據報。
2)深綠框的“標志”
占3個標志位。標志0:保留,必須為零;DF:不分段標記;MF:更多分片標記,,如果是最后一個分片,該標志位為0,否則為1。
3)深綠框的“片偏移”
它表示分片在原始數據中的偏移,這里的原始數據是IP層收到的TCP或UDP數據,不包含IP首部。需要注意的是,在分片的數據中,傳輸層的首部只會出現在第一個分片中,因為傳輸層的數據格式對IP層是透明的,傳輸層的首部只有在傳輸層才會有它的作用,IP層不知道也不需要保證在每個分片中都有傳輸層首部。所以,在網絡上傳輸的數據包是有可能沒有傳輸層首部的。
1.3 避免IP分片
在網絡編程中,我們要避免出現IP分片。因為IP層是沒有超時重傳機制的,如果IP層對一個數據包進行了分片,只要有一個分片丟失了,只能依賴於傳輸層進行重傳,結果是所有的分片都要重傳一遍,會大大降低傳輸層傳送數據的成功率,所以我們要避免IP分片。
1)UDP
我們需要在應用層去限制每個包的大小,一般不要超過1472字節(不考慮IP首部選項字段的情況),即以太網MTU(1500) - IP首部(20)- UDP首部(8)。
2)TCP(不用考慮IP分片)
應用層不需要考慮這個問題,因為傳輸層已經幫我們做了。在建立連接的三次握手的過程中,連接雙方會相互通告MSS(Maximum Segment Size,最大報文段長度),MSS肯定是<=網絡層的最大路徑MTU,然后tcp數據封裝成ip數據包通過網絡層發送,當服務器端傳輸層接收到tcp數據之后進行tcp重組。TCP的ip數據包在傳輸過程中是不會發生分片的。
1.4 內核里的分片重組
linux實現分析:https://www.cnblogs.com/wanpengcoder/p/7604715.html
BSD:TCP/IP詳解卷二,第十章
2 IP分片及重組庫
IP分段和重組庫實現IPv4和IPv6數據包的分段和重組。
2.1 數據包分片
數據包分段邏輯將輸入的數據包划分為多個分段。 rte_ipv4_fragment_packet()和rte_ipv6_fragment_packet()函數均假定輸入mbuf數據指向數據包IP報頭的開頭(即L2報頭已被剝離)。為避免復制實際數據包的數據,使用了零拷貝技術(rte_pktmbuf_attach)。
對於每個片段,將創建兩個新的mbuf:
- 直接mbuf,它包含新片段的L3頭。
- 間接mbuf,它附加到帶有原始數據包的mbuf。它的數據字段指向原始數據包數據的開頭加上片段偏移量。
然后,將L3頭部從原始mbuf復制到“直接”mbuf,並進行更新以反映新的分片狀態。請注意,對於IPv4,不重新計算標頭校驗和,並將其設置為零。
最后,每個片段的“直接”和“間接” mbuf通過mbuf的next字段鏈接在一起,以組成新片段的數據包。
調用方可以明確指定應使用哪些內存池來分配“直接”和“間接” mbuf。
有關直接和間接mbuf的更多信息,請參考mbuf庫的直接和間接緩沖區說明。
2.2 數據包重組
2.2.1 IP分片表
分片表中維護已經接收到的數據包片段的信息。
每個IP數據包均由有三個字段唯一標識:<源IP地址>,<目標IP地址>,<ID>。
請注意,片段表上的所有更新/查找操作都不是線程安全的。因此,如果不同的執行上下文(線程/進程)要同時訪問同一張表時,必須提供一些外部同步機制。
每個表條目都可以保存有關數據包的信息,這些信息最多由RTE_LIBRTE_IP_FRAG_MAX(默認為4個)片段組成。
該代碼示例演示了如何創建新的分片表:
frag_cycles = (rte_get_tsc_hz() + MS_PER_S - 1) / MS_PER_S * max_flow_ttl; bucket_num = max_flow_num + max_flow_num / 4; frag_tbl = rte_ip_frag_table_create(max_flow_num, bucket_entries, max_flow_num, frag_cycles, socket_id);
內部分片表是一個簡單的哈希表。基本思想是使用兩個哈希函數和主備兩個hash桶,每個桶下有若干<bucket_entries>。這為每個鍵在哈希表中提供了2 * <bucket_entries>個可能的位置。當沖突發生並且所有2 * <bucket_entries>都被占用時,ip_frag_tbl_add()不會將現有鍵重新插入替代位置,而只會返回失敗。(我的理解,這里是對hash庫里cuckoo算法的簡化說明)。
此外,表中駐留時間長於<max_cycles>的條目被視為無效,可以被新條目替換或刪除。
請注意,重組需要分配大量的mbuf。在任何給定時間,最多可以將(2 * bucket_entries * RTE_LIBRTE_IP_FRAG_MAX * <每個數據包的最大mbufs個數>)存儲在分片表中,以等待剩余的分片。
2.2.2 數據包重組
分片數據包的處理和重組是由rte_ipv4_frag_reassemble_packet()/ rte_ipv6_frag_reassemble_packet()函數完成的。它們要么返回一個指向“包含重組后數據包的有效mbuf“的指針,要么返回NULL(由於某種原因而無法完成據包重組數)。
這些功能包括:
- 在片段表中搜索數據包的<IPv4源地址,IPv4目標地址,數據包ID>。
- 如果找到該條目,則檢查該條目是否已超時。如果是,則釋放所有先前收到的片段,並從條目中刪除有關它們的信息。
- 如果未找到帶有此類密鑰的條目,請嘗試通過以下兩種方法之一創建一個新的鍵:
1)占用一個空條目。
2)刪除超時條目,釋放與它相關聯的mbufs,並在其中存儲具有指定鍵的新條目。
- 使用新的片段信息更新條目,並檢查包是否可以重新組合(包的條目包含所有片段)。
1)如果是,則重新組合數據包,將表的條目標記為空,然后將重新組合的mbuf返回給調用方。
2)如果否,則將NULL返回給調用方。
如果在數據包處理的任何階段遇到錯誤(例如:無法將新條目插入分片表或無效/超時的片段),則該函數將釋放所有與數據包分片相關的信息,標記表條目為無效,並將NULL返回給調用者。
2.2.3 調試日志記錄和統計信息收集
RTE_LIBRTE_IP_FRAG_TBL_STAT配置宏控制分片表的統計信息收集。默認情況下不啟用此宏。
RTE_LIBRTE_IP_FRAG_DEBUG控制IP分片處理和重組的調試日志記錄。默認情況下禁用此宏。請注意,雖然日志記錄包含許多詳細信息,但它減慢了數據包處理的速度,並可能導致許多數據包的丟失。
參考:
DPDK官方編程指南:http://doc.dpdk.org/guides-20.02/prog_guide/ip_fragment_reassembly_lib.html