DHCP uses too much memory: reducing dhcp memory consumption by careful use of range6 statements
  • 18 Oct 2018
  • 5 Minutes to read
  • Contributors
  • Dark
    Light
  • PDF

DHCP uses too much memory: reducing dhcp memory consumption by careful use of range6 statements

  • Dark
    Light
  • PDF

Article Summary

Summary:

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:

  1. 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.
  2. 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.)

Detail:

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

What is a hash table?
A hash table is a table of pointers to locations - in the case of a pool of IPv6 leases, it is a list of pointers to the full lease structures. Each pointer is 'found' by taking the lease address/range that has been granted and running it through a hashing algorithm. The result is an offset into the table - and at this location should be found the pointer to the lease structure in the pool in memory. The table also contains the pre-hashed lease address/range - this is because of hash collisions. When multiple leases hash to the same table offset, then at that location a linked list is created. Any code that accesses the hash table has to check that the entry found by following the hash offset is the entry that it is looking for, and if not, then it follows a chain of 'next' pointers until either the end of the chain is reached, or a matching entry has been found.

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:

Pool:Addresses covered:
2001:db8::f0/1262001:db8::f0 - 2001:db8::f3
2001:db8::e0/1242001:db8::e0 - 2001:db8::ef
2001:db8::c0/1232001:db8::c0 - 2001:db8::df
2001:db8::80/1222001:db8::80 - 2001:db8::bf
2001:db8::/1212001: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:

2001:db8::/120

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.

What happens if the number of entries in the pool is bigger than the hash table?
The ISC DHCP code is not as sophisticated as the BIND code when handling hash table sizes. The BIND code monitors the length of collision chains (that's when two IDs hash to the same value) and increases the table size when the chains get to be long enough that they could be affecting throughput/performance. This re-sizing happens long before the table has run out of entries.

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).

Having the right number and size of pools is better than having too few or too many
Ideally the DHCP administrator should be aiming to use the range6 statement to create an appropriate number of pools based on their knowledge of the clients likely to be requesting leases. Therefore, it may be make sense, depending on the production environment, to declare multiple ranges as explicit CIDR blocks to achieve this. This article aims to explain how DHCPv6 lease allocation works so that administrators can make their own configuration decisions based on a good understanding of what is happening internally.