udhcpd源码分析3--IP租赁管理
1:重要的结构体
全局链表的成员struct dhcpOfferedAddr *leases 记录了当前租赁出去的IP信息
/* leases.h */
struct dhcpOfferedAddr { u_int8_t chaddr[16]; u_int32_t yiaddr; /* network order */ u_int32_t expires; /* host order */ };
结构体三个成员分别记录客户端MAC(为什么不是6字节?),租赁出去的IP地址,以及到期时间(time(0) + server_config.lease).
2:读入lease_file
/* dhcpd.c */
leases = malloc(sizeof(struct dhcpOfferedAddr) * server_config.max_leases); memset(leases, 0, sizeof(struct dhcpOfferedAddr) * server_config.max_leases); read_leases(server_config.lease_file);
根据配置文件中的max_leases参数分配空间,read_leases读入lease_file文件,将记录的IP租赁信息更新到leases链表中,有了leases链表还要有lease_file文件的原因是一旦udhcpd异常挂掉,重启之后能够恢复之前的租赁IP信息到leases链表里.
/* files.c */
/* * 将lease_file文件中记录的租赁出去的IP信息更新到链表struct dhcpOfferedAddr *leases中 * read_leases函数主要是在udhcpd意外重启后恢复之前租赁出去IP的信息到struct dhcpOfferedAddr *leases中 * add_lease是具体更新struct dhcpOfferedAddr *leases链表的函数,比如在server发送offer报文后应该将租赁 出去的IP记录到链表中,这时候会调用add_lease函数 */ void read_leases(char *file) { FILE *fp; unsigned int i = 0; struct dhcpOfferedAddr lease; if (!(fp = fopen(file, "r"))) { LOG(LOG_ERR, "Unable to open %s for reading", file); return; } while (i < server_config.max_leases && (fread(&lease, sizeof lease, 1, fp) == 1)) { /* ADDME: is it a static lease */ if (lease.yiaddr >= server_config.start && lease.yiaddr <= server_config.end) { lease.expires = ntohl(lease.expires); if (!server_config.remaining) lease.expires -= time(0); if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) { LOG(LOG_WARNING, "Too many leases while loading %s\n", file); break; } i++; } } DEBUG(LOG_INFO, "Read %d leases", i); fclose(fp); }
与其方向相反的,在程序适当时候需要将leases链表中的信息更新到lease_file文件中就调用write_lease函数
/* 通过遍历struct dhcpOfferedAddr *leases指向的链表更新lease_file文件内容, server_config.remaining 为真表示lease_file文件中存储的过期时间是绝对时间 (time(0) + expires)否则存储相对时间(expires) */ void write_leases(void) { FILE *fp; unsigned int i; char buf[255]; time_t curr = time(0); unsigned long lease_time; if (!(fp = fopen(server_config.lease_file, "w"))) { LOG(LOG_ERR, "Unable to open %s for writing", server_config.lease_file); return; } for (i = 0; i < server_config.max_leases; i++) { if (leases[i].yiaddr != 0) { if (server_config.remaining) { if (lease_expired(&(leases[i])))//如果地址过期设置为0 lease_time = 0; else lease_time = leases[i].expires - curr; } else lease_time = leases[i].expires; lease_time = htonl(lease_time); fwrite(leases[i].chaddr, 16, 1, fp); fwrite(&(leases[i].yiaddr), 4, 1, fp); fwrite(&lease_time, 4, 1, fp); } } fclose(fp); if (server_config.notify_file) { sprintf(buf, "%s %s", server_config.notify_file, server_config.lease_file); system(buf); } }
有关leases链表的操作函数都在leases.c文件中,主要有下面这些:
/* leases.c */
1: struct dhcpOfferedAddr *add_lease(u_int8_t *chaddr, u_int32_t yiaddr, unsigned long lease)
/* add a lease into the table, clearing out any old ones 先清空chaddr,yiaddr对应的链表节点,找到最早到期的节点,将新节点赋值到最早到期的节点位置 如果没有到期的IP则返回NULL */ struct dhcpOfferedAddr *add_lease(u_int8_t *chaddr, u_int32_t yiaddr, unsigned long lease) { struct dhcpOfferedAddr *oldest; /* clean out any old ones */ clear_lease(chaddr, yiaddr); oldest = oldest_expired_lease(); if (oldest) { memcpy(oldest->chaddr, chaddr, 16); oldest->yiaddr = yiaddr; oldest->expires = time(0) + lease; } return oldest; }
2: void clear_lease(u_int8_t *chaddr, u_int32_t yiaddr)
/* clear every lease out that chaddr OR yiaddr matches and is nonzero 遍历leases链表找到chaddr或yiaddr对应的节点,将节点置0. */ void clear_lease(u_int8_t *chaddr, u_int32_t yiaddr) { unsigned int i, j; for (j = 0; j < 16 && !chaddr[j]; j++); /* j==16 表示chaddr数组为空,只需要比较yiaddr(小技巧) */ for (i = 0; i < server_config.max_leases; i++) if ((j != 16 && !memcmp(leases[i].chaddr, chaddr, 16)) || (yiaddr && leases[i].yiaddr == yiaddr)) { memset(&(leases[i]), 0, sizeof(struct dhcpOfferedAddr)); } }
3: struct dhcpOfferedAddr *oldest_expired_lease(void)
/* Find the oldest expired lease, NULL if there are no expired leases 找到leases链表中最早到期的节点,返回节点地址.没有到期节点返回NULL */ struct dhcpOfferedAddr *oldest_expired_lease(void) { struct dhcpOfferedAddr *oldest = NULL; unsigned long oldest_lease = time(0); unsigned int i; for (i = 0; i < server_config.max_leases; i++) if (oldest_lease > leases[i].expires) { oldest_lease = leases[i].expires; oldest = &(leases[i]); } return oldest; }
4: int lease_expired(struct dhcpOfferedAddr *lease)
/* true if a lease has expired IP租赁到期返回true否则返回false */ int lease_expired(struct dhcpOfferedAddr *lease) { return (lease->expires < (unsigned long) time(0)); }
5: struct dhcpOfferedAddr *find_lease_by_chaddr(u_int8_t *chaddr)
/* Find the first lease that matches chaddr, NULL if no match 通过chaddr值找到leases中节点,返回节点地址. */ struct dhcpOfferedAddr *find_lease_by_chaddr(u_int8_t *chaddr) { unsigned int i; for (i = 0; i < server_config.max_leases; i++) if (!memcmp(leases[i].chaddr, chaddr, 16)) return &(leases[i]); return NULL; }
6: struct dhcpOfferedAddr *find_lease_by_yiaddr(u_int32_t yiaddr)
/* Find the first lease that matches yiaddr, NULL is no match 通过yiaddr值找到leases中节点,返回节点地址. */ struct dhcpOfferedAddr *find_lease_by_yiaddr(u_int32_t yiaddr) { unsigned int i; for (i = 0; i < server_config.max_leases; i++) if (leases[i].yiaddr == yiaddr) return &(leases[i]); return NULL; }
7: u_int32_t find_address(int check_expired)
/* find an assignable address, it check_expired is true, we check all the expired leases as well. * Maybe this should try expired leases by age... * 在地址池中返回一个没有被分配的地址. */ u_int32_t find_address(int check_expired) { u_int32_t addr, ret; struct dhcpOfferedAddr *lease = NULL; addr = ntohl(server_config.start); /* addr is in host order here */ for (;addr <= ntohl(server_config.end); addr++) { /* 排除地址池中.0和.255结尾的地址 */ /* ie, 192.168.55.0 */ if (!(addr & 0xFF)) continue; /* ie, 192.168.55.255 */ if ((addr & 0xFF) == 0xFF) continue; /* lease is not taken */ ret = htonl(addr); if ((!(lease = find_lease_by_yiaddr(ret)) || /* or it expired and we are checking for expired leases */ (check_expired && lease_expired(lease))) && /* and it isn't on the network */ !check_ip(ret)) { return ret; break; } } return 0; }
8: int check_ip(u_int32_t addr)
/* check is an IP is taken, if it is, add it to the lease table 检测此地址是否有在被其它lan pc所使用,检测的方式是用此IP广播arp报文, 根据是否有回应判断此IP是否被占用.(比如某个lan pc 是使用的static IP, 且此IP在udhcpd的地址池中,在udhcpd分配ip给客户端时必须做此检查(检查有就要将此IP添加到leases链表中表示已被分配), 否则会造成IP冲突). */ int check_ip(u_int32_t addr) { struct in_addr temp; /* arpping 发送一个arp广播包,经过一段时间等待后如果此IP没有被局域网内的主机使用就收不到单播回复,返回1 */ if (arpping(addr, server_config.server, server_config.arp, server_config.interface) == 0) { temp.s_addr = addr; LOG(LOG_INFO, "%s belongs to someone, reserving it for %ld seconds", inet_ntoa(temp), server_config.conflict_time);
/* blank_chaddr 黑户? 因为不知道使用这个IP的主机的MAC地址 */ add_lease(blank_chaddr, addr, server_config.conflict_time); return 1; } else return 0; }
3:总结
所有被租赁出去的IP地址,客户端MAC和到期时间(绝对时间-->leases链表中的到期时间都是绝对时间,而保存到lease_file中的到期时间有两种,绝对时间和相对时间<配置文件中remaining为no则和leases链表一致保存绝对时间,为yes则保存相对时间>),维护在两个地方,一个当然就是struct dhcpOfferedAddr *leases指针指向的全局链表,另一个就是配置文件指定的lease_file本地文件.本地文件主要是为了防止udhcpd异常重启后租赁信息的丢失而设置的,在files.c文件中的read_leases函数就是读取lease_file文件中的记录通过add_lease来恢复之前的被租赁出去的IP信息.而write_leases函数的方向刚好相反,它是将leases链表中的信息更新到lease_file文件中做记录.
add_lease是更新struct dhcpOfferedAddr *leases链表的触发者,所以租赁IP的更新主要靠add_lease函数,add_lease只在sendACK和sendOffer中被调用,也就是说有新的客户端来连接就会更新一下链表(将旧的信息替换为新的信息)。而write_leases函数则会在server收到SIGUSER1信号或者socket空闲时不停将struct dhcpOfferedAddr *leases链表信息记录到lease_file中。