网站制作软件安卓版,长沙网站建设长沙,昆明建站网址,iis7搭建asp网站目录 一、DHCP(Dynamic Host Configuration Protocol)1.1 前置知识1.2 参考链接1.3 IP地址分配代码分析rfc2131.cdhcp-common.cdhcp.c 1.4 几个小问题1.4.1 连续IP模式#xff08;sequential_ip#xff09;1.4.2 重新连接使用IP地址1.4.3 续约租期1.4.4 不同的MAC地址分配到相… 目录 一、DHCP(Dynamic Host Configuration Protocol)1.1 前置知识1.2 参考链接1.3 IP地址分配代码分析rfc2131.cdhcp-common.cdhcp.c 1.4 几个小问题1.4.1 连续IP模式sequential_ip1.4.2 重新连接使用IP地址1.4.3 续约租期1.4.4 不同的MAC地址分配到相同IP 一、DHCP(Dynamic Host Configuration Protocol)
1.1 前置知识
之前也学习了一下总结了一些概念和抓包分析此处不赘述。 DHCP和PPPoE协议以及抓包分析
1.2 参考链接
24-Openwrt dnsmasq DNS and DHCP configuration rfc2131文档 DHCP协议详解
1.3 IP地址分配代码分析
本次着重看了这一块代码其它部分再后续补充。 吐槽一下源代码的格式真是一言难尽缩进乱七八糟而且有的空格有的tab看着也难受格式不标准有的都不能正确缩放还是让GPT转化了一下再看的。
rfc2131.c
if (mess_type 0 !pxe) {/* BOOTP request */struct dhcp_netid id, bootp_id;struct in_addr *logaddr NULL;/* must have a MAC addr for bootp */if (mess-htype 0 || mess-hlen 0 || (context-flags CONTEXT_PROXY))return 0;if (have_config(config, CONFIG_DISABLE))message _(disabled);end mess-options 64; /* BOOTP vend area is only 64 bytes *///如果配置中设置了 CONFIG_NAME 标志则设置主机名hostname和域名domain。if (have_config(config, CONFIG_NAME)) {hostname config-hostname;domain config-domain;}
//遍历配置中的网络标识列表netid并将它们添加到当前的 netid 列表中if (config) {struct dhcp_netid_list *list;for (list config-netid; list; list list-next) {list-list-next netid;netid list-list;}}/* Match incoming filename field as a netid. */if (mess-file[0]) {memcpy(daemon-dhcp_buff2, mess-file, sizeof(mess-file));daemon-dhcp_buff2[sizeof(mess-file) 1] 0; /* ensure zero term. */id.net (char *)daemon-dhcp_buff2;id.next netid;netid id;}/* Add bootp as a tag to allow different options, address ranges etc for BOOTP clients */bootp_id.net bootp;bootp_id.next netid;netid bootp_id;//运行标识的处理函数run_tag_if以获取最终的 netid 列表。tagif_netid run_tag_if(netid);//遍历 dhcp_ignore 列表如果与 netid 列表中的标识匹配则将消息设置为 ignored。for (id_list daemon-dhcp_ignore; id_list; id_list id_list-next) {if (match_netid(id_list-list, tagif_netid, 0)) {message _(ignored);break;}}if (!message) {int nailed 0;
//检查是否已配置 IP 地址have_config(config, CONFIG_ADDR)。如果配置了尝试分配指定的 IP 地址。if (have_config(config, CONFIG_ADDR)) {nailed 1;logaddr config-addr;mess-yiaddr config-addr;if ((lease lease_find_by_addr(config-addr)) (lease-hwaddr_len ! mess-hlen ||lease-hwaddr_type ! mess-htype ||memcmp(lease-hwaddr, mess-chaddr, lease-hwaddr_len) ! 0)){message _(address in use);}}else {
//如果没有配置 IP 地址尝试从已分配的地址中查找与客户端 MAC 地址匹配的租约lease_find_by_clientif (!(lease lease_find_by_client(mess-chaddr, mess-hlen, mess-htype, NULL, 0)) ||!address_available(context, lease-addr, tagif_netid)){if (lease) {/* lease exists, wrong network. */lease_prune(lease, now);lease NULL;}
//如果没有找到匹配的租约或该地址不可用address_available则尝试分配一个新的 IP 地址if (!address_allocate(context, mess-yiaddr, mess-chaddr, mess-hlen, tagif_netid, now, loopback)) {message _(no address available);}}else {mess-yiaddr lease-addr;}}//如果分配了 IP 地址检查该地址是否在正确的网络上narrow_context。如果不在正确的网络上设置错误消息为 wrong network。if (!message !(context narrow_context(context, mess-yiaddr, netid))) {message _(wrong network);}//更新 netid 列表并重新运行标识处理函数run_tag_if。else if (context-netid.net) {context-netid.next netid;tagif_netid run_tag_if(context-netid);}log_tags(tagif_netid, ntohl(mess-xid));//遍历 bootp_dynamic 列表检查是否配置了与 netid 列表匹配的地址。如果没有找到则设置错误消息为 no address configured。if (!message !nailed) {for (id_list daemon-bootp_dynamic; id_list; id_list id_list-next) {if ((!id_list-list) || match_netid(id_list-list, tagif_netid, 0)) {break;}}if (!id_list) {message _(no address configured);}}//如果没有错误消息并且没有租约可用!lease尝试为分配的 IP 地址创建一个租约lease4_allocate。if (!message !lease !(lease lease4_allocate(mess-yiaddr))) {message _(no leases left);}//如果成功分配租约设置租约的硬件地址、主机名、租约到期时间等信息。if (!message) {logaddr mess-yiaddr;lease_set_hwaddr(lease, mess-chaddr, NULL, mess-hlen, mess-htype, 0, now, 1);if (hostname) {lease_set_hostname(lease, hostname, 1, get_domain(lease-addr), domain);}/* infinite lease unless nailed in dhcp-host line. */lease_set_expires(lease, have_config(config, CONFIG_TIME) ? config-lease_time : 0xffffffff, now);lease_set_interface(lease, int_index, now);//清空消息中的选项do_options为客户端提供配置选项。clear_packet(mess, end);do_options(context, mess, end, NULL, hostname, get_domain(mess-yiaddr), netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0);}}daemon-metrics[METRIC_BOOTP];log_packet(BOOTP, logaddr, mess-chaddr, mess-hlen, iface_name, NULL, message, mess-xid);return message ? 0 : dhcp_packet_size(mess, agent_id, real_end);
}然后看一个各个函数的具体实现
dhcp-common.c
//根据匹配条件更新给定的网络标识列表并重新运行标识处理逻辑。
struct dhcp_netid *run_tag_if(struct dhcp_netid *tags)
{struct tag_if *exprs;struct dhcp_netid_list *list;// 遍历服务器中定义的标识处理表达式tag_iffor (exprs daemon-tag_if; exprs; exprs exprs-next) {// 检查当前标识处理表达式是否与当前网络标识列表匹配if (match_netid(exprs-tag, tags, 1)) {// 对于匹配的表达式遍历操作集合setfor (list exprs-set; list; list list-next) {// 将操作列表中的操作添加到网络标识列表tags的开头list-list-next tags;tags list-list;}}}// 返回更新后的网络标识列表tagsreturn tags;
}dhcp.c
struct dhcp_context *address_available(struct dhcp_context *context,struct in_addr taddr,struct dhcp_netid *netids)
{/* 检查地址是否适用于此网络检查所有可能的范围。确保该地址没有被服务器自身使用。 */unsigned int start, end, addr ntohl(taddr.s_addr);struct dhcp_context *tmp;// 检查提供的地址是否与服务器的路由器地址匹配for (tmp context; tmp; tmp tmp-current) {if (taddr.s_addr context-router.s_addr) {return NULL; // 服务器的路由器地址不可用}}// 遍历每个上下文及其地址范围以查找可用的地址for (tmp context; tmp; tmp tmp-current) {start ntohl(tmp-start.s_addr);end ntohl(tmp-end.s_addr);// 检查地址是否在当前上下文的范围内且匹配给定的网络标识if (!(tmp-flags (CONTEXT_STATIC | CONTEXT_PROXY)) addr start addr end match_netid(tmp-filter, netids, 1)) {return tmp; // 地址在此上下文中可用}}return NULL; // 地址不可用
}int address_allocate(struct dhcp_context *context,struct in_addr *addrp, unsigned char *hwaddr, int hw_len,struct dhcp_netid *netids, time_t now, int loopback)
{/* 寻找一个可用的地址排除正在使用的地址和分配给特定硬件地址/客户端标识/主机名的地址首先尝试返回与netids匹配的上下文。 */struct in_addr start, addr;struct dhcp_context *c, *d;int i, pass;unsigned int j;/* 对hwaddr进行哈希使用SDBM哈希算法。即使在具有类似值的“字符串”上似乎也能得到良好的分散效果。 */for (j 0, i 0; i hw_len; i)j hwaddr[i] (j 6) (j 16) - j;/* j 0 是标记 */if (j 0)j 1;for (pass 0; pass 1; pass)for (c context; c; c c-current) {if (c-flags (CONTEXT_STATIC | CONTEXT_PROXY))continue;else if (!match_netid(c-filter, netids, pass))continue;else {if (option_bool(OPT_CONSEC_ADDR))/* 种子是此上下文中最大的现存租约地址 */start lease_find_max_addr(c);else/* 基于hwaddr选择种子 */start.s_addr htonl(ntohl(c-start.s_addr) ((j c-addr_epoch) % (1 ntohl(c-end.s_addr) - ntohl(c-start.s_addr))));/* 循环直到找到一个可用的地址。 */addr start;do {/* 排除服务器正在使用的地址。 */for (d context; d; d d-current) {if (addr.s_addr d-router.s_addr) {break;}}/* 以.255和.0结尾的地址在Windows上有问题即使使用超网也是如此。例如dhcp-range192.168.0.1,192.168.1.254,255,255,254.0那么192.168.0.255是有效的IP地址但在Windows上却不是因为它在C类范围内。请参阅KB281579。因此我们不分配这些地址以避免难以诊断的问题。感谢Bill。 */if (!d !lease_find_by_addr(addr) !config_find_by_address(daemon-dhcp_conf, addr) (!IN_CLASSC(ntohl(addr.s_addr)) ||((ntohl(addr.s_addr) 0xff) ! 0xff ((ntohl(addr.s_addr) 0xff) ! 0x0)))) {/* 在连续IP模式下跳过等于客户端拒绝的地址数量的地址。这应该避免同一客户端在拒绝地址后再次被分配相同的地址。 */if (option_bool(OPT_CONSEC_ADDR) c-addr_epoch) {c-addr_epoch--;} else {struct ping_result *r;if ((r do_icmp_ping(now, addr, j, loopback))) {/* 连续IP模式我们最近为另一个客户端提供了此地址不同的哈希不要再次提供给此客户端。 */if (!option_bool(OPT_CONSEC_ADDR) || r-hash j) {*addrp addr;return 1;}} else {/* 地址正在使用中扰动地址选择以便不太可能再次尝试此地址。 */if (!option_bool(OPT_CONSEC_ADDR)) {c-addr_epoch;}}}}addr.s_addr htonl(ntohl(addr.s_addr) 1);if (addr.s_addr htonl(ntohl(c-end.s_addr) 1)) {addr c-start;}} while (addr.s_addr ! start.s_addr);}}return 0; // 无可用地址
}
上述算法过程举例如下假设有一个 DHCP 上下文 c其中包含以下信息
c-start.s_addr 表示分配地址的起始地址假设为 192.168.1.100以网络字节序表示。c-end.s_addr 表示分配地址的结束地址假设为 192.168.1.200以网络字节序表示。c-addr_epoch 是一个地址时代值假设为 5。j 是之前计算得到的 SDBM 哈希值假设为 12345。
现在我们来演示如何通过上述代码生成一个新的分配地址 计算地址范围内的总地址数1 ntohl(c-end.s_addr) - ntohl(c-start.s_addr) 1 192.168.1.200 - 192.168.1.100 101。 计算 SDBM 哈希值和地址时代的影响(j c-addr_epoch) % (1 101) (12345 5) % 102 12350 % 102 46。 计算新的分配地址的偏移量ntohl(c-start.s_addr) 46 3232235876 46 3232235922。 将计算得到的偏移量转换为网络字节序htonl(3232235922) 192.168.1.122。
因此根据上述计算生成的新分配地址将是 192.168.1.122。这个过程确保了生成的地址在指定的地址范围内同时通过 SDBM 哈希值和地址时代进行了调整。
struct dhcp_context *narrow_context(struct dhcp_context *context,struct in_addr taddr,struct dhcp_netid *netids)
{/* 我们从一组可能的上下文开始所有这些上下文都在当前的物理接口上。这些上下文通过 -current 进行链接。在这里我们有一个地址并返回与该地址对应的实际上下文。请注意如果地址来自dhcp-host并且位于任何dhcp-range之外则可能没有匹配的上下文。在这种情况下如果可能的话我们会返回静态范围或者如果失败的话返回正确子网上的任何上下文。如果有多个上下文这是一个不稳定的配置也许应该有一个警告。 */struct dhcp_context *tmp;if (!(tmp address_available(context, taddr, netids))) {for (tmp context; tmp; tmp tmp-current) {if (match_netid(tmp-filter, netids, 1) is_same_net(taddr, tmp-start, tmp-netmask) (tmp-flags CONTEXT_STATIC)) {break;}}if (!tmp) {for (tmp context; tmp; tmp tmp-current) {if (match_netid(tmp-filter, netids, 1) is_same_net(taddr, tmp-start, tmp-netmask) !(tmp-flags CONTEXT_PROXY)) {break;}}}}/* 现在只允许一个上下文 */if (tmp) {tmp-current NULL;}return tmp;
}
narrow_context 的函数用于根据给定的地址和网络标识来缩小上下文的范围。它首先尝试根据给定的地址和网络标识来查找可用的上下文。如果找不到匹配的可用上下文则会尝试在同一子网中查找静态范围或者如果没有静态范围则查找任何与正确子网匹配的上下文。最后函数会将选定的上下文链表中的 current 指针设置为 NULL以确保只返回一个上下文。函数将返回选定的上下文如果找不到合适的上下文则返回 NULL。
1.4 几个小问题
1.4.1 连续IP模式sequential_ip
上述代码中有提到这个模式openwrt中对应的配置字段就是sequential_ip。对应的描述如下
NameTypeDefaultOptionDescriptionsequential_ipboolean0–dhcp-sequential-ipDnsmasq is designed to choose IP addresses for DHCP clients using a hash of the client’s MAC address. This normally allows a client’s address to remain stable long-term, even if the client sometimes allows its DHCP lease to expire. In this default mode IP addresses are distributed pseudo-randomly over the entire available address range. There are sometimes circumstances (typically server deployment) where it is more convenient to have IP addresses allocated sequentially, starting from the lowest available address, and setting this parameter enables this mode. Note that in the sequential mode, clients which allow a lease to expire are much more likely to move IP address; for this reason it should not be generally used.Dnsmasq用于使用客户端MAC地址的哈希为DHCP客户端选择IP地址。这通常允许客户端的地址长期保持稳定即使客户端有时允许其DHCP租约到期。在此默认模式下IP地址在整个可用地址范围内伪随机分布。有时在某些情况下通常是服务器部署从最低可用地址开始按顺序分配IP地址更方便并且设置此参数可以启用此模式。请注意在顺序模式中允许租约到期的客户端更有可能移动IP地址由于这个原因它不应该被普遍使用。 可以看到按顺序分配设置成功
1.4.2 重新连接使用IP地址
在RFC 2131文档第3.2节描述了客户端重新使用先前分配的网络地址时的客户端-服务器交互过程
根据文档中的描述如果客户端希望重新连接并使用先前分配的网络地址客户端可以选择省略先前部分描述的一些步骤。客户端可以通过向服务器发送DHCPREQUEST消息来请求使用先前分配的网络地址。在DHCPREQUEST消息中客户端将其网络地址填入“请求的IP地址”选项中并且不填写“ciaddr”字段。服务器收到DHCPREQUEST消息后如果具有客户端的配置参数信息则会向客户端发送DHCPACK消息。服务器不应检查客户端的网络地址是否已被使用。客户端在收到DHCPACK消息后将配置参数应用于自身并记录DHCPACK消息中指定的租约持续时间。此时客户端已经重新连接并配置完成。
请注意根据文档中的描述如果客户端在DHCPACK消息中检测到分配的IP地址已经被使用则客户端必须发送DHCPDECLINE消息给服务器并重新启动配置过程以请求新的网络地址。如果客户端收到DHCPNAK消息则表示无法重新使用先前分配的网络地址客户端必须重新启动配置过程并按照文档中描述的完整流程进行操作。 重用以前分配的网络地址时DHCP客户端和服务器之间交换的消息时间轴图
注客户端通常不会在正常关闭期间放弃其租约。只有在客户端明确需要放弃其租期的情况下例如客户端即将移动到另一个子网客户端才会发送DHCPRELEASE消息。
1.4.3 续约租期
在RFC 2131文档的第4.4.5节中描述了租期续约的过程 客户端在T1之前选择续约或延长租期客户端可以选择在T1之前续约或延长租期。这意味着客户端可以在租期即将到期之前主动向服务器发送DHCPREQUEST消息来请求续约。 服务器根据网络管理员设置的策略来决定是否延长租期服务器可以根据网络管理员设置的策略来决定是否延长客户端的租期。这意味着服务器可以根据自己的策略来决定是否接受客户端的续约请求。 服务器应返回调整后的T1和T2的值服务器应返回调整后的T1和T2的值以考虑租期剩余的时间。这意味着服务器应根据租期剩余时间来调整T1和T2的值并将其返回给客户端。 在续约和重新绑定状态下如果客户端收不到DHCPREQUEST消息的响应客户端应等待剩余时间的一半在续约状态下是T2的一半在重新绑定状态下是租期剩余时间的一半直到最少等待60秒然后重新发送DHCPREQUEST消息。
具体有以下三种情况 1当clientIP地址已经用到50%的时间续租一下client端就会以单播形式向服务端发送一个DHCP Request包当server响应时就会回应一个ACK包会重新约定一个时间。
2当clientIP地址已经用到50%的时间续租一下client端就会以单播形式向服务端发送一个DHCP Request包server没有响应client会继续使用当使用到87.5%时会在续租一次同时就以广播的方式是发送一个request包server这时收到响应以后就会回应一个ACK包重新约定一个时间。
3当clientIP地址已经用到50%的时间续租一下client端就会以单播形式向服务端发送一个DHCP Request包server没有响应client会继续使用当使用到87.5%时会在续租一次同时就以广播的方式是发送一个request包如果server还是没有响应client那就直接使用到过期。 DHCP客户端的状态转换图
1.4.4 不同的MAC地址分配到相同IP
这是无意间收到的一个提问可以参考学习。 multiple offers with same IP to different MAC addresses