This section is a long argument describing the advantages of using a backing database for DHCP. You can skip it if you're already convinced.
Why use a database?
There are significant reasons to use a proper database (yes, LDAP is a database) for DHCP management.
- Update without restart
- Avoid ad hoc file parsing or generation
- Reduce configuration sites
The use of a flat file for configuration and data, the use of an inaccessible in-memory database and the network limitations of the DHCP protocol all pose problems for all but the smallest networks. Backing the DHCP services with a database can address all three.
Testing: Emit and Collect Test DHCP Queries - dhtest
It turns out that there aren't many tools for testing DHCP responses. I found several but they were only in source code. The one I decided to use is called dhtest and it's available from Github:
https://github.com/saravana815/dhtest
It builds cleanly on Fedora 19 and 20.
git clone https://github.com/saravana815/dhtest Cloning into 'dhtest'... remote: Reusing existing pack: 53, done. remote: Total 53 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (53/53), done. cd dhtest make gcc -c -o dhtest.o dhtest.c gcc -c -o functions.o functions.c gcc dhtest.o functions.o -o dhtest
When it runs successfully this is what it looks like
sudo ./dhtest --mac 0a:00:00:00:00:01 \ --interface p2p1 --server 10.0.2.15 --verbose DHCP discover sent - Client MAC : 0a:00:00:00:00:01 DHCP offer received - Offered IP : 10.0.2.16 DHCP offer details ---------------------------------------------------------- DHCP offered IP from server - 10.0.2.16 Next server IP(Probably TFTP server) - 10.0.2.4 Subnet mask - 255.255.255.0 Router/gateway - 10.0.2.2 DNS server - 10.0.2.3 Lease time - 1 Days 0 Hours 0 Minutes DHCP server - 10.0.2.2 ---------------------------------------------------------- DHCP request sent - Client MAC : 0a:00:00:00:00:01 DHCP ack received - Acquired IP: 10.0.2.16 DHCP ack details ---------------------------------------------------------- DHCP offered IP from server - 10.0.2.16 Next server IP(Probably TFTP server) - 10.0.2.4 Subnet mask - 255.255.255.0 Router/gateway - 10.0.2.2 DNS server - 10.0.2.3 Lease time - 1 Days 0 Hours 0 Minutes DHCP server - 10.0.2.2 ----------------------------------------------------------
Procedure
Ingredients
Before starting there are a set of parameters that should be defined. The DHCP server will need to gain access to the LDAP service and the DHCP server configuration in the LDAP database must reflect the network on which the DHCP server resides. I also add one dummy test host that I can use for validation.
LDAP Server | |
---|---|
LDAP Server Hostname | ldap.example.com |
Database DN | dc=example,dc=com |
Admin Username (DN) | dc=Manager,dc=example,dc=com |
Admin Password | changeme |
Subnet Specification | |
---|---|
Base Address | 10.0.2.0 |
Netmask | /24 |
Gateway | 10.0.2.2 |
DNS Servers | 10.0.2.3 |
NTP Servers | 10.0.2.3 |
Host Entry | |
---|---|
MAC Address | 0a:00:00:00:00:01 |
IP Address | 10.0.2.16 |
Recipe
Running DHCP with LDAP (conceptually) requires two different servers. You can run them both on the same host if you want. Adjust your IP addresses and hostnames to your environment.
- On the LDAP server
- Prepare the LDAP database for DHCP configuration
- Convert the DHCP config to LDIF and load it into the database
- On the DHCP server
LDAP Server Host
Convert DHCP Schema to LDIF
The DHCP schema for LDAP isn't part of the standard OpenLDAP server packages. On Fedora it's part of the DHCP package. On Debian it's part of a special package which includes the DHCP server with LDAP integration: isc-dhcp-ldap. Because the LDAP schema file is provided as part of the DHCP server packaging, it must be transferred to the LDAP server to be loaded into the database schema set.
Even then the schema is provided in the older LDAP schema format. I need it in LDIF format so that I can load it like the others. Fortunately it's possible to load the older schema into memory and then write them out as LDIF using slapcat. The trick is to convince it to use a special alternate configuration file which just imports the old form schema and then dump the config as LDIF. There are a couple of tweaks to make on the resulting LDIF. The schema object is created with an array index of zero (0). That has to be removed. Slapcat also adds a CRC, and some reference and time stamp information that won't apply to the schema definition when it is loaded into a new database.
The section of code below will produce a file named
dhcp.ldif
. It takes the dhcp.schema
file as input. It uses a temporary file for the LDAP configuration which only loads the DHCP schema and a temporary directory to contain the resulting LDIF config tree which slapcat
produces as a matter of course.#!/bin/sh # Create the required tmp file/directory mkdir slapd.d echo 'include /etc/openldap/schema/dhcp.schema' > slapd.conf # load the schema and then dump it in LDIF format slapcat -f slapd.conf -F slapd.d -n0 -l dhcp.ldif \ -H ldap:///cn={0}dhcp,cn=schema,cn=config # remove the CRC, array index and timestamp/UUID entries sed -i -e '/CRC32/d ; s/{0}dhcp/dhcp/ ; /structuralObjectClass/,$d' \ dhcp.ldif # remove the tmp file/directory rm -rf slapd.d rm slapd.conf sudo cp dhcp.ldif /etc/openldap/schema/dhcp.ldif
(remember, this runs on the LDAP server host)
Import DHCP schema into configuration database
Once I have a the DHCP schema in LDIF format I can load it the same way I loaded the stock schema. This will be the last command which must run as root on the LDAP server and uses local authentication.
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// /etc/openldap/schema/dhcp.ldif
From this point on I'll be adding things not to the config database but to the hdb database using the RootDN and RootPW account.
Load the DHCP configuration into the LDAP server
The DHCP service configuration (as expressed in LDIF) requires three objects to describe a minimal working DHCP service:
- dhcpServer - The host on which the DHCP service will run
- dhcpService - The global settings which control the behavior of the DHCP service
- dhcpSubnet - A description of a subnet to which the DHCP server is connected
Making changes to any of these objects will require a restart of the affected DHCP daemon processes.
DHCP Server
The LDAP
dhcpServer
object is the hook to which the dhcpd process will attach when it starts up. This object contains the DN of the top of the DHCP service configuration.
LDAP object classes are additive. That is, a single entry in the database will commonly have more than one objectClass attribute. The objectClass attributes declare the set of attributes which the object can have and
there is no limit (other than conflict) to the combinations.
I believe that the
dhcpServer
objectClass can be combined with the NIS host class so that information about particular hosts can be unified under a single object.
# # Define the DHCP host entry which will be used by the DHCP service on startup # This is the configuration entry hook # dn: cn=dhcp-host,dc=example,dc=com cn: dhcp-host objectClass: top objectClass: dhcpServer dhcpServiceDN: cn=dhcp-service,dc=example,dc=com
DHCP Service
The
dhcpService
object is the root of the DHCP daemon configuration information.
All of the objects which define a DHCP service configuration will be children of this object. That is, the DN of the dhcpService
object will be the suffix for the rest of the objects that define the configuration.
There are two types of attribute which all objects in the DHCP configuration can have. These are the
dhcpStatement
and dhcpOption
attributes. These correspond to normal statement lines and option lines in the traditional dhcpd.conf
file.
The dhcpService attributes define the deamon behavior and any global options which would apply to all query responses.
# The root object of the DHCP service # All elements of the DHCP configuration will use this DN for a suffix. # dn: cn=dhcp-service,dc=example,dc=com cn: dhcp-service objectClass: top objectClass: dhcpService objectClass: dhcpOptions dhcpPrimaryDN: cn=dhcp-host, dc=example,dc=com dhcpStatements: authoritative dhcpStatements: ddns-update-style none dhcpStatements: max-lease-time 43200 dhcpStatements: default-lease-time 3600 dhcpStatements: allow booting dhcpStatements: allow bootp dhcpOption: domain-name "example.com" dhcpOption: domain-name-servers 10.0.2.3
DHCP Subnet
The DHCP service needs a subnet definition so that it knows what interface(s) to bind to. A DHCP server listens for discovery requests. There's no point in listening if there are no networks to listen on, so the daemon will exit.
# DHCP Subnet object # dn: cn=10.0.2.0, cn=dhcp-service,dc=example,dc=com cn: 10.0.2.0 objectClass: top objectClass: dhcpSubnet dhcpNetMask: 24 dhcpOption: routers 10.0.2.2
Test DHCP Lease Reservation
# A Test Host Lease Reservation # The definition of a host: name, MAC, IP address # Additional options can control PXE boot and OS installation # dn: cn=testhost, cn=dhcp-service,dc=example,dc=com cn: testhost objectClass: top objectClass: dhcpHost objectClass: dhcpOptions dhcpHWAddress: ethernet 0a:00:00:00:00:01 dhcpStatements: fixed-address 10.0.2.16 dhcpOption: host-name "testhost"
DHCP Server Host
These operations configure the DHCP server host and the dhcp daemon.
Prepare Logging (Optional)
I like to be able to view the logs for critical services separately from the rest of the system logs. This can make it easier. For this I'll add a config file for rsyslog which filters the dhcpd log entries to a file of their own. This doesn''t change the behavior at all, it just makes viewing the logs simpler.
First, create an empty log file (rsyslog doesn't like to create files that don't exist)
sudo touch /var/log/dhcpd.log
Then create the rsyslog config entry in
/etc/rsyslog.d
cat <<EOF >/etc/rsyslog.d/dhcpd.conf if $programname == "dhcpd" then /var/log/dhcpd.log EOF
Finally, restart the rsyslog daemon
sudo systemctl restart rsyslog
Verify LDAP access
Before trying to connect the DHCP server to the LDAP service, I need to verify that the DHCP host can make the required connection and retrieve the dhcpServer entry which is the anchor for the configuration data.
ldapsearch -H ldap://ldap.example.com \ -x -w changeme \ -D cn=Manager,dc=example,dc=com \ -b dc=example,dc=com \ objectClass=dhcpServer
Set the DHCP server configuration - use LDAP server
When the dhcpd is configured for an LDAP database, the configuration file is a lot smaller than is typical. It merely identifies where to find the configuration. It can also indicate whether the daemon should read the configuration once and load it into memory, or resolve each query with a check of the database. Finally, it can write a copy of the configuration in the traditional format for verification.
# DHCP Host Location ldap-server "ldap.example.com" ; ldap-port 389 ; # A user with read/write access to the database ldap-username "cn=Manager,dc=example,dc=com" ; ldap-password "changeme" ; # Identify the root object of the config ldap-base-dn "dc=example,dc=com" ; ldap-dhcp-server-cn "dhcp-host" ; # All queries check the database ldap-method dynamic ; # Write the DHCP config for validation # An empty file must exist before starting the daemon # And it must be writable by the dhcpd user #ldap-debug-file "/var/log/dhcp-ldap-startup.log" ;
Start the DHCP server
sudo systemctl start dhcpd
Verify that the daemon has started and is serving queries for the subnet
May 16 20:06:53 fedora-20-x64 dhcpd: Internet Systems Consortium DHCP Server 4.2 .6 May 16 20:06:53 fedora-20-x64 dhcpd: Copyright 2004-2014 Internet Systems Consor tium. May 16 20:06:53 fedora-20-x64 dhcpd: All rights reserved. May 16 20:06:53 fedora-20-x64 dhcpd: For info, please visit https://www.isc.org/ software/dhcp/ May 16 20:06:53 fedora-20-x64 dhcpd: Wrote 0 leases to leases file. May 16 20:06:53 fedora-20-x64 dhcpd: Listening on LPF/p2p1/08:00:27:35:3b:b0/10. 0.2.0/24 May 16 20:06:53 fedora-20-x64 dhcpd: Sending on LPF/p2p1/08:00:27:35:3b:b0/10. 0.2.0/24 May 16 20:06:53 fedora-20-x64 dhcpd: Sending on Socket/fallback/fallback-net
Verify Operation
sudo dhtest --verbose --mac 0a:00:00:00:00:01 --interface eth0 --server 10.0.2.15 ... May 16 20:11:49 fedora-20-x64 dhcpd: DHCPDISCOVER from 0a:00:00:00:00:01 via eth- May 16 20:11:49 fedora-20-x64 dhcpd: DHCPOFFER on 10.0.2.16 to 0a:00:00:00:00:01 via eth0 May 16 20:11:49 fedora-20-x64 dhcpd: DHCPREQUEST for 10.0.2.16 (10.0.2.2) from 0 a:00:00:00:00:01 via eth0 May 16 20:11:49 fedora-20-x64 dhcpd: DHCPACK on 10.0.2.16 to 0a:00:00:00:00:01 v ia eth0
Additional Work
This is a very simple example. There is considerable work that is still needed for a production system.
- Security - LDAP over SSL
- Security - Add LDAP users for access control
- Security - SASL or Kerberos authentication
- Security - Database access controls (user ACLs)
- HA - LDAP database replication
References
- DHCP LDAP Patch
https://github.com/dcantrell/ldap-for-dhcp/wiki - An Early example:
https://skalyanasundaram.wordpress.com/dhcp/dhcp-with-ldap-support/ - dhtest - DHCP emitter/responder
https://github.com/saravana815/dhtest