DHCP源碼分析-報文解析和封裝



       接收到報文後,通過相應的報文解析函數,將 DHCP報文統一解析爲packet結構體。packet 結構用於記錄接收到的報文信息,及處理 DHCP報文時需要的各種輔助變量,其中的 raw 字段用於記錄報文的首地址,options字段用於記錄解析出來的 option。

 

      解析過程是,先解析報文類型、報文長度、客戶端的地址、接口信息等報文頭信息然後逐個解析報文中的選項信息最後將解析形成的 packet 報文下發到相應報文的處理模塊進行處理。


        在解析完DHCP,將其轉換爲內部結構struct packet後,整個函數調用過程如下:

       1.入口函數dhcp;

       2.調用locate_network,確定從哪個subnet分配IP;

       3.根據DHCP包的類型調用dhcp_discover, dhcp_request或者其他相應的函數;

       4.在dhcp_discover/dhcp_request中,調用find_lease確定分配哪個iplease;

       5.調用ack_lease構造reply包;

       6.調用supersede_lease將分配的lease添加到對應的hash中,並寫入到dhcpserver的數據庫文件中;

       7.調用dhcp_reply將包發送給client;



子選項解析



       首先將系統支持的具有子選項的 option 分別封裝爲universe 結構,如將option81 的 suboption封裝爲 fqdn_universe ,option82 的 suboption 封裝爲agent_universe等。在系統初始化時,分別對各 universe 進行初始化。


       解析時,先解析父選項,再遞歸解析子選項。以 DHCPv4 包含的option82 爲例,首先在dhcp_universe 的 code_hash 表中查找到option82,接着檢測到 option82是封裝的 option,然後調用子選項的解析函數對封裝的 option82 進行遞歸解析,注意這時需要使用 agent_universe 封裝的相應操作符對 option82 的 suboption進行相應操作。


       如果一個 option是封裝的 option,則需要根據option 類型和 universe name找到對應的 universe。所有的封裝的option 的 format 都是以'E'開頭(表示是封裝的 option),以'.'結尾的。故查找方法是,查找 option format,先找到'E',並標記地址 S1;然後找到'.',並標記地址 S2,則地址(S1 - S2)就得到了相應option format 的長度 L。


       若長度 L 爲零,且已知 option 對應的universe name,則直接根據 universe name在全局 universes 表中查找相應的universe;若長度 L 不爲零,或者universe name未知,則根據 option format 的首地址 S1 和長度 L 在全局universes 表中查找相應的universe;或者根據optionformat的首地址S1和長度L在全局universe_hash表中 hash 查找相應的 universe。

 


Option相關數據結構



       使用 universe 結構體對一類option 的操作函數進行封裝,同一性質的option封裝成一個universe,配置的所有universe組成了一個全局universes表,比如對 dhcpv4 的相應 option、及 option82 的 suboption分別封裝爲dhcp_universe、agent_universe。系統初始化時,完成了全局 universes 表的初始化;同時以universe name 爲 key 建立了一個全局的universe_hash 表。


       universe 結構中封裝了一系列的操作符,以實現對相應 option 的解析、查找、添加、刪除、遍歷、獲取 option code、length 等操作。系統初始化時,依次配置支持的各種 universe,對相應universe 中的回調函數進行註冊。


       universe 結構中包含有兩張hash 表,對相應類別的 option 的管理通過這兩張hash表完成。這兩張 hash 表是對於某個 universe 而言,用其包含的option 的 name和 code 值爲 key 分別建立起來的,用於判斷某個 option 是否是對應universe 支持的 option。系統初始化時,完成了相應universe 中的兩張 hash 表(name_hash、code_hash)的初始化。


       universe 支持的相應的option 的信息由 option 結構體記錄,包括option 的名稱、code 值、optionvalue 的類型(format)、option 所屬的 universe 的指針以及該結構體被引用的次數。其中format字段,實際上是一個字符組合,表明了option value 的形式,由此可以選擇不同的數據操作方式。

 


解析後的選項管理



       對於從具體報文中解析出來的 option,通過option_state、option_cache 結構體進行管理。有鏈表和hash 表兩種數據結構組織形式,根據 universe 的不同種類和性質,初始化時對相應 universe 配置了不同的操作符,從而在解析時根據具體 universe 封裝的操作符構造鏈表或者hash 表。當 option 種類和數量較多的用hash 表管理,如 dhcp_universe、dhcpv6_universe;option 種類和數量較少的用鏈表管理,如 agent_universe。


       option_state 結構體記錄了 option 的一些操作函數需要用到的輔助字段,包含一個指針數組。當用鏈表管理時,option_state 結構中的數組元素指向解析出來的 option_cache 結構體,構成一個鏈表。當用 hash 表管理時,數組中的每一個元素都指向一張 hash 表,被 hash 的元素是option_cache 結構體。這張 hash 表是在解析option 的時候動態建立的,每解析出一個合法的 option,都會按照一定的hash算法,將其添加到 hash 表中。


       option_cache 結構體記錄解析出來的 option 信息,包含關聯的option 結構體指針和 option 數據緩存結構體data_string。option 結構記錄了option 的名稱、code 值、optionvalue 的類型(format)、option 所屬的 universe 等信息。data_string結構體通過buffer結構體指針,指向瞭解析出來的報文中的實際數據。





應答報文選項填充



       應答 DHCPv4 報文時,先按照優先級順序將 option code 添加到優先級列表中,然後將優先級列表中的 option 填充到 buffer 中,最後將整個 buffer 拷貝到應答報文對應的buffer 中,option 可以填充在應答報文的options、file、sname 三個字段。


       第一步,如果應答的是Client 報文,需要檢查是否有 relay agent options,若有,則將 DHO_DHCP_AGENT_OPTIONS 添加在優先級列表的第一位,並將其保存在臨時的 buffer 中,用於以後拷貝到應答報文中;同時,要在主 buffer 中爲 relayagent options 預留空間。


       第二步,添加協議規定的option code 到優先級列表,這些 option 具有較高的優先級。如果參數請求列表不爲空,則按照請求的順序添加 option 到優先級列表;否則,先添加一些應該具有較高優先級的option,然後按順序添加已配置option中的 standardDHCP options、site option 和封裝的 option。注意,添加時不要改變之前 relay agent options 的優先級,且當優先級列表已滿時,則停止添加。


       第三步,組裝應答option。首先添加 Magic cookie,並按照優先級順序將對應option 保存在臨時的 buffer 中;接着,判斷是否有過載選項,若有,先添加overload option 到 buffer 中, buffer 中的過載部分保存到應答報文相應的file、sname 字段;然後,若之前保存有relay agent options,則將其拷貝到 buffer中;最後,若空間足夠,則添加DHO_END option 到 buffer。這樣,應答option就全部填充到臨時的 buffer 中了。


       第四步,將填充好的 buffer 拷貝到應答報文的options 字段中去。 其中,按照優先級順序將對應 option 保存在臨時的buffer 中的機制,又可包含分割保存(options 太大)和分段保存(options、file、sname 三段)。步驟如下:



       第一步,遍歷優先級列表,清除優先級列表中 code 值重複的 option,保留優先級高的一個。


       第二步,再次遍歷優先級列表,按照表中的順序保存 option。首先,如果有封裝的encapsulation,則先將其添加到臨時的 buffer 中。然後,如果需要保存的optionlength 大於 255 字節,則要進行分割保存,每次取 255 字節,依次保存到options space、first_cutoffspace、second_cutoff space(對應 options、file、sname 三字段);否則直接全部保存到 options buffer、first buffer、secondbuffer(對應 options、file、sname 三字段)。


       第三步,如果需要過載選項,則添加 PAD 和 END option 到 buffer 中。其中,將封裝的 encapsulation 添加到臨時的buffer 中又分兩種情況。第一種封裝是一般的直接的一層封裝('E'ncapsulation),這種封裝的 option format是以"E"開頭;第二種封裝是擴展的多重封裝('e'ncapsulation),即封裝的 option中仍然含有suboption,這種封裝的 option format 是以"e"開頭,後面緊跟"E"。對於第一種封裝,直接將封裝的 option 數據添加到主buffer 的末尾;對於第二種封裝,遞歸的進行解封裝,直到最後一層封裝是'E'ncapsulation,則按照第一種的添加方法進行添加。(本項目中各種 universe 包含的 option 中暫無'e'ncapsulation 的情況,功能保留,用於以後擴展。)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章