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
", 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中。

   

原文地址:https://www.cnblogs.com/Flychown/p/6710041.html