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 的情况,功能保留,用于以后扩展。)



猜你喜欢

转载自blog.csdn.net/wuyongpeng0912/article/details/51474106
今日推荐