DHCP uses too much memory: reducing dhcp memory consumption by careful use of range6 statements
DHCP can use significantly more memory than anticipated for DHCPv6 subnets; how much memory is consumed can be significantly reduced by strategic use of range6 statements, supplemented by static host assignments (real or dummy). There are two options:
- Use the range6 statement only in the "start addr/cidr" format - there will be one pool and hash table for each range identified this way.
- Use the range6 statement in the "lo addr hi addr" format, but choose the ranges carefully so that DHCP creates as few pools as are reasonable when it divides the range of addresses into indvidual CIDR blocks.
In both cases, addresses that should not be allocated from the pool can be excluded by adding them to the configuration as fixed addresses. These fixed address assignments can be real (devices are expected to request leases for them) or dummy. (Note that you cannot overlap DHCPv4 static address with DHCPv4 dynamic pools - this configuration recommendation applies only to DHCPv6.)
DHCPv4 lease pools have their entries pre-allocated and created while dhcpd is starting up (and this process has no knowledge of any fixed address assignments elsewhere in the configuration - which is why an administrator should not overlap IPv4 dynamic pools with static lease assignments).
Obviously, when DHCPv6 was written, it was not possible to operate the same way because the v6 address space is so vast. So DHCPv6 entries are added as needed when a lease is being allocated. (Note that since this process takes into account any addresses already assigned, including fixed-address hosts, it is possible to overlap IPv6 dynamic pools with static lease assignments - this turns out to be very useful when configuring subnets in order to optimize memory use.)
The structure for each DHCPv6 lease is not placed into memory until the lease has been granted, so instead, access to the lease structures belonging to each pool are managed by means of a hash table.
Each IPv6 pool is given its own hash table, and each hash table (with 9973 entries) occupies an initial space of approximately 40 Kbytes (for a 32-bit build of DHCP - 80 Kbytes for a 64-bit build, but since there are no optimizations in ISC DHCP for 64-bit builds, there are no clear advantages to counterbalance this anyway). This means that a server that has a large number of small pools is going to need more memory than a server that has fewer large pools. This memory consumption occurs even before any leases are allocated to clients.
The number of pools actually created by the server varies depending upon the characteristics of the address ranges declared. The server translates each "range6" statement into a series of one or more "start addr/cidr" pools.
For range6 statements already expressed in this form no translation is necessary and the server will create exactly one pool for each such range. The size of the "prefix" has no bearing whatsoever; it will always create one pool per "start addr/cidr" range.
For ranges expressed as "lo addr hi addr", the server uses an internal function range2cidr() to calculate the needed "start addr/cidr" pools.
As a worked example, consider:
range6 2001:db8::0 2001:db8::f3;
This range actually results in 5 internal pools:
|2001:db8::f0/126||2001:db8::f0 - 2001:db8::f3|
|2001:db8::e0/124||2001:db8::e0 - 2001:db8::ef|
|2001:db8::c0/123||2001:db8::c0 - 2001:db8::df|
|2001:db8::80/122||2001:db8::80 - 2001:db8::bf|
|2001:db8::/121||2001:db8::00 - 2001:db8::7f|
Wherever possible, then, ranges should be declared such that they do not split octets, or so that a reasonable number of pools will be created (taking into account the number of clients expected to be obtaining leases from each pool). Changing the above range to this:
range6 2001:db8::0 2001:db8::ff;
results in a single pool:
Changing that range to skip only the first address
range6 2001:db8::1 2001:db8::ff;
is even worse than the original as this one takes eight pools!
2001:db8::80/121 2001:db8::40/122 2001:db8::20/123 2001:db8::10/124 2001:db8::8/125 2001:db8::4/126 2001:db8::2/127 2001:db8::1/128
In fact, in such a case the administrator could create a host reservation for the first address, 2001:db801 (dummy or real), and start the range at 2001:db800. Even if there's no host to claim that first address, the server will create a single pool, 2001:db8::/120, instead of eight and will still avoid the first address.
The ISC DHCP code, however, only increases the table when it runs out of entries. Therefore, the default hash table size has been chosen to be big enough for the average operator.
However, it's not good for performance for dhcpd to approach the point at which it runs out of hash table entries; therefore, administrators may wish to take the hash table size (9973) into consideration when defining range6 settings along with their own knowledge of the likely pool sizes (i.e. number of devices likely to assigned leases in each pool).