Specialised and non-static configurations

staticDHCPd is meant to help administrators easily configure static environments, with easy-to-integrate provisioning facilities. However, special cases arise and that’s what makes the software truly powerful. Some of the more interesting setups in the wild will be documented here.

Dynamic hybrids

The motivating case for adding support for dynamic provisioning to staticDHCPd was a LAN party context in which guests need to register their systems before they can be given a static mapping by site administration. Using the dynamism extension, unknown clients can be given configuration that puts them into an isolated subnet on a short lease so they can access a registration system, and the DHCP server itself can send notification of the new arrival to a webservice to streamline the operators’ work.

The rest of this article outlines how to use the sample extensions provided with staticDHCPd. Any site seeking to use dynamic services will almost certainly need to do some customisation, though, so consider at least basic Python knowledge to be a pre-requisite.

Be aware also that, unlike dynamic-provisioning-focused servers, like the ISC’s, not all provisioning semantics are respected and that, unlike staticDHCPd’s static behaviour, this facet of the system is not RFC-compliant. It probably won’t do anything environment-breaking, but be prepared for some weird things; feedback, if you encounter anything curious, is very welcome.

Setup

For the common case, it is enough to install dynamism.py normally.

If you want to do anything cooler, like send a JSON message to a webservice when an unknown MAC appears or block clients after they renew five times, subclass DynamicPool or just hack it in-place. It’s simple code and it’s your environment, so just apply what you find in tutorials on the Internet and have fun!

How stable are dynamic leases?

They’re should be pretty consistent: when IPs are added to the pool, if scapy is available, and if the scan option is enabled, the server will ARP for each address (in parallel, so it’s not slow), setting up leases as hits are found, making your network a living database. Additionally, if a client requests a specific IP after the server is already online (clients often do this when rebooting), that address will be plucked if available.

If scapy is unavailable, you’ll probably get a lot of DECLINEs, but your network will stabilise before too long.

Is DST a factor with leases?

No, DST shouldn’t be relevant. Internally, leases are managed as offsets against UTC, so timezones are only applied when formatting the timestamps for presentation to operators.

PXE support

In general, it should be sufficient to test for option 60 (vendor_class_identifier) in loadDHCPPacket() to see if it matches the device-type you want to net-boot and set options 60, 66 (tftp_server_name), and 67 (bootfile_name) accordingly, as demonstrated in the following example:

vendor_class_identifier = source_packet.getOption('vendor_class_identifier', convert=True)
if vendor_class_identifier and vendor_class_identifier.startswith('PXEClient'):
    #The device may look for a specific value; check your manual
    packet.setOption('vendor_class_identifier', 'PXEServer:staticDHCPd')
    #Tell it where to get its bootfile; IPs are valid, too
    packet.setOption('tftp_server_name', 'bootserver.example.org')
    #Have the device ask for its own MAC, stripped of colons and uppercased
    packet.setOption('bootfile_name', str(mac).replace(':', '').upper() + '.cfg')

Those working with systems derived from BOOTP, rather than DHCP, like embedded BIOS-level stacks, will probably want to do something more like this:

vendor_class_identifier = source_packet.getOption('vendor_class_identifier', convert=True)
if vendor_class_identifier and vendor_class_identifier.startswith('PXEClient'):
    #Tell it where to get its bootfile; your device probably isn't
    #DNS-aware if it's using BOOTP, but the field is free-form text
    packet.setOption('siaddr', DHCP_SERVER_IP) #The same address defined earlier in conf.py
    #Tell it which file to look for; pxelinux.0 is pretty common
    packet.setOption('file', 'pxelinux.0')

The two approaches are not mutually exclusive and well-behaved clients should only look at the fields they understand. But it’s probably safest to use if clauses to be sure that you’re not at risk of confusing a partial implementation.

Of course, you can use other criteria to evaluate whether an option should be set and what its value should be.

In the event that the client tries to hit a ProxyDHCP port (4011, by convention), you’ll need to edit conf.py and assign the port number to PROXY_PORT. This will cause staticDHCPd to bind another port on the same interface(s) as the main DHCP port; full DHCP service will be provided on that port, too, including IP assignment.

The port parameter in loadDHCPPacket() and other functions will allow site-specific code to respond differently depending on how the packet was received; you can use simple tests like this to apply appropriate logic:

if port == PROXY_PORT: #The address defined in conf.py
    #set special fields

Chances are, in most cases, the client will have been assigned an IP over the standard DHCP port already, testable with packet.getOption('ciaddr'), and though it’s highly unlikely, the device may complain if the response contains an IP offer; packet.deleteOption('yiaddr') takes care of this.