根据dhcp协议规定,默认情况下client需要在T1/T2这两个时间向server发送DHCPREQUEST来续租ip(T1 = 50% * 租期, T2 = 87.5% * 租期)。当然,client在T1时刻发送DHCPREQUEST后没有收到对应的DHCPACK,才会在T2时刻再次尝试发送DHCPREQUEST。对server来说,如果在整个租期内都没有收到client的DHCPREQUEST续租请求,则需要重置lease的状态,使其可以被分配给其他client。

   以下就根据isc-dhcp的代码,主要从两个方面来简单分析server端是如何维护地址池租期的。

(注:在lease结构体中关键的成员starts/ends/sort_time,分别表示lease的开始/结束/下次排序时间

1. 续租成功,更新lease

   在server收到client发来的DHCPREQUEST报文后,构建DHCPACK回复时,同时更新对应lease的开始和结束时间。可以看看isc-dhcp中的代码:

/* At this point, we have a lease that we can offer the client.
       Now we construct a lease structure that contains what we want,
       and call supersede_lease to do the right thing with it. */
    lt = (struct lease *)0;
    result = lease_allocate (&lt, MDL);
    if (result != ISC_R_SUCCESS) {
        log_info ("%s: can't allocate temporary lease structure: %s",
              msg, isc_result_totext (result));
        free_lease_state (state, MDL);
        if (host)
            host_dereference (&host, MDL);
        return;
    }
                                                                
    /* Use the ip address of the lease that we finally found in
       the database. */
    lt -> ip_addr = lease -> ip_addr;
    /* Start now. */
    lt -> starts = cur_time;
    /* Figure out how long a lease to assign.    If this is a
       dynamic BOOTP lease, its duration must be infinite. */
    if (offer) {
        ……
    } else {
        lt->flags |= BOOTP_LEASE;
        ……
        lt -> ends = state -> offered_expiry = cur_time + lease_time;
        lt -> next_binding_state = FTS_ACTIVE;
    }

2. 租约到期,释放lease

   定时器是个很好的工具,但此处不对isc的timer做介绍。主要阅读dhcp的定时更新地址池内leases的代码。

/* Timer called when a lease in a particular pool expires. */
void pool_timer (vpool)
    void *vpool;
{
    struct pool *pool;
    struct lease *next = (struct lease *)0;
    struct lease *lease = (struct lease *)0;
#define FREE_LEASES 0
#define ACTIVE_LEASES 1
#define EXPIRED_LEASES 2
#define ABANDONED_LEASES 3
#define BACKUP_LEASES 4
#define RESERVED_LEASES 5
    struct lease **lptr[RESERVED_LEASES+1];
    TIME next_expiry = MAX_TIME;
    int i;
    struct timeval tv;  // 定时器时间间隔
    pool = (struct pool *)vpool;
    lptr [FREE_LEASES] = &pool -> free;
    lptr [ACTIVE_LEASES] = &pool -> active;
    lptr [EXPIRED_LEASES] = &pool -> expired;
    lptr [ABANDONED_LEASES] = &pool -> abandoned;
    lptr [BACKUP_LEASES] = &pool -> backup;
    lptr[RESERVED_LEASES] = &pool->reserved;
/* 遍历所有状态下的lease */
    for (i = FREE_LEASES; i <= RESERVED_LEASES; i++) {
        /* If there's nothing on the queue, skip it. */
        if (!*(lptr [i]))
            continue;
#if defined (FAILOVER_PROTOCOL)
        ……
#endif     
        lease_reference (&lease, *(lptr [i]), MDL);
        while (lease) {
            /* Remember the next lease in the list. */
            if (next)
                lease_dereference (&next, MDL);
            if (lease -> next)
                lease_reference (&next, lease -> next, MDL);
            /* If we've run out of things to expire on this list, stop. */
            /* 由于一开始就维护着链表按时间来排序,当sort_time
               在cur_time之后,则可以终止本次遍历。因为接下来的
               所有lease的sort_time均大于cur_time */
            if (lease -> sort_time > cur_time) {
                if (lease -> sort_time < next_expiry)
                    next_expiry = lease -> sort_time;
                break;
            }
            /* If there is a pending state change, and
               this lease has gotten to the time when the
               state change should happen, just call
               supersede_lease on it to make the change
               happen. */
            if (lease->next_binding_state != lease->binding_state)
            {
#if defined(FAILOVER_PROTOCOL)
        ……
#endif
                /* 更改lease状态 */
                supersede_lease(lease, NULL, 1, 1, 1);
            }
            lease_dereference (&lease, MDL);
            if (next)
                lease_reference (&lease, next, MDL);
        }
        if (next)
            lease_dereference (&next, MDL);
        if (lease)
            lease_dereference (&lease, MDL);
    }
    if (next_expiry != MAX_TIME) {
        pool -> next_event_time = next_expiry;
        tv . tv_sec = pool -> next_event_time;
        tv . tv_usec = 0;
        /* 设置下次pool_timer执行时间 */
        add_timeout (&tv, pool_timer, pool,
                 (tvref_t)pool_reference,
                 (tvunref_t)pool_dereference);
    } else
        pool -> next_event_time = MIN_TIME;
}