根据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 (<, 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;}