Showing posts with label dhcpd. Show all posts
Showing posts with label dhcpd. Show all posts

Thursday, March 6, 2025

Deploying ISC DHCP Server in a systemd Container

ISC DHCP Server in a Container

This repository describes the operation of a software container to run the ISC DHCP service.

Note
ISC DHCP has been deprecated and end-of-lifed as of Dec 2022 in favor of ISC Kea

ISC DHCP has been the standard DHCP server since the release of version 1.0 in 1998. While ISC has deprecated the orginal DHCP as of Dec 2022 in favor of ISC Kea, the oringal DHCP server remains in use and is a standard package on most Linux distributions.

Conventional packages are tightly bound to the OS distribution and to the version of the OS installed on a particular host. Network services and the systems that host them are crititical infrastructure. The fear of downtime often results in aversion to regular updates.

The fear of update failures can be mitigated by providing for atomic rollback. This means that, in the event of an update induced failure, the service can be rolled back reliably to the previous version. Containerized services offer a way to accept frequent updates while also allowing rapid rollback to a previous version without the fear of conflict or system pollution that packaged software risks.

The dhcp container encapsulates the ISC DHCP daemon so that it can run on any Linux host that uses systemd for system initialization and service management.

Container Images

This repository contains scripts to create two different but functionally equivalent container images. Both are based on the Fedora dhcp-server package. This package provides the ISC DHCP server binary: dhcpd.

The dhcpd-fedora image uses the fedora-minimal base image. The dhcpd image is built from scratch.

This document describes the usage of these images to provide a DHCP service on a host capable of running software containers from systemd-containers. To learn how the container images were built, see:

Prerequisites

The system must have the BOOTP and DHCP ports (68/UDP and 69/UDP) free and must be capable of running systemd containers.

  • Static IP addresses
    The DHCP service itself requires that all IP addresses on the host are configured statically. The DHCP client will, obviously, conflict with the server for port bindings. This took me an embarrassingly long time to figure out when testing the container image.

  • containers-common package
    The host server must be able to run software containers and must have the containers-common package installed. This package provides the bindings that allow podman to generate systemd service unit files from a container service file. On Debian this is the golang-github-containers-common.

Configuration

The configuration of the DHCP daemon is the same as if it was running from an installed package. The Service configuration is contained in the /etc/dhcp/dhcpd.conf file and the lease database is in /var/lib/dhcpd/dhcp.leases.

When the service is packaged, the package creates and populates these with default values. A container can’t affect the OS outside its boundaries so the user must create those files before the service is started.

  • /etc/dhcp/dhcpd.conf
    The daemon is configured with the standard configuration file in the standard location. Describing the configuration is outside the scope of this document. See the excellent existing configuration documents for the ISC DHCP daemon.

  • /var/lib/dhcpd/dhcp.leases
    The lease file must exist when the daemon starts. Initially it is an empty file.

    sudo mkdir -p /var/lib/dhcpd ; sudo touch /var/lib/dhcpd.leases

Enable Containerized Services

OS support for Container and VM management are provided by the containers-common package. On Debian based systems this is called golang-github-containers-common

On recent Red Hat and Fedora releases it is installed by default along with podman. On Debian based systems you must install the package or one that depends on it, like the podman package.

sudo apt update ; sudo apt install golang-github-containers-common

Install DHCP container service

Containerized services are defined by a container unit file. This file follows a similar format to the other systemd unit files.

The container service system was originally known as quadlets but now is defined in the podman systemd specification.

The container file below pulls one of the public container images for the ISC DHCP server.

dhcpd.container
[Unit]
Description=ISC DCHP daemon Service Container
After=network-online.target

[Container]
Image=quay.io/markllama/dhcpd:latest
#Image=quay.io/markllama/dhcpd-fedora:latest

# This shouldn't be needed except for reading /etc/dhcp/dhcpd.conf
# Could move it to /opt/dhcp/dhcpd.conf
PodmanArgs=--privileged
Network=host

# Open listening ports
# bootps
PublishPort=67:67/udp
PublishPort=67:67/tcp

# bootpc
PublishPort=68:68/udp
PublishPort=68:68/tcp

# Mount the dhcp config dir into the container workingdir
Volume=/etc/dhcp/dhcpd.conf:/etc/dhcp/dhcpd.conf:ro,Z
Volume=/var/lib/dhcpd/:/var/lib/dhcpd/:rw,Z

[Install]
# Enable in multi-user boot
WantedBy=multi-user.target default.target

#  podman run --detach --name dhcpd \
#    --privileged  \
#    --network host \
#    --volume /etc/dhcp/dhcpd.conf:/etc/dhcp/dhcpd.conf:ro,Z \
#    --volume /var/lib/dhcpd/:/var/lib/dhcpd/:rw,Z \
#    quay.io/markllama/dhcpd

The two container images indicated in the file above are both created from the Fedora RPM dhcp-server. The dhcpd-fedora container image is based on the Fedora 41 base image. The dhcpd image is a minimal image create from scratch, containing only the dhcpd binary and the required shared libraries.

Limitations and Future Work

This is a proof-of-concept project. The container design currently does not support any of these features of the ISC DHCP server

  • No configuration of invocation parameters

  • No IPv6

  • No OMAPI

  • No external database

References

  • DHCP History
    The history of DHCP and of the ISC DHCP server

  • ISC DHCP
    A DHCP service available on all Linux distributions

  • Podman systemd unit file
    The container service unit file specification

  • systemd
    The modern Linux OS initialization and service management system

  • The containers-common package
    The package that provides the container bindings for systemd services

    • Debian golang-github-containers-common

    • Fedora containers-common

Monday, May 19, 2014

Robust and Flexable DHCP and provisioning: An LDAP backed DHCP service.

In the last post I created an empty LDAP database ready to accept content. In this one I mean to add a DHCP service configuration for a single subnet and a test host entry.

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


Finally I get to the actual process of creating the DHCP service.  First the ingredients and a summary of the process. Then the details.

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 Hostnameldap.example.com
Database DNdc=example,dc=com
Admin Username (DN)dc=Manager,dc=example,dc=com
Admin Passwordchangeme

Subnet  Specification
Base Address10.0.2.0
Netmask/24
Gateway10.0.2.2
DNS Servers10.0.2.3
NTP Servers10.0.2.3

Host Entry
MAC Address0a:00:00:00:00:01
IP Address10.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.
  1. On the LDAP server
    1. Prepare the LDAP database for DHCP configuration
      1. Convert the DHCP schema file to LDIF
      2. Import the DHCP schema (as LDIF) into the cn=config database
    2. Convert the DHCP config to LDIF and load it into the database
      1. dhcpServer
      2. dhcpService
      3. dhcpSubnet
      4. dhcpHost
  2. On the DHCP server
    1. Prepare logging
    2. Verify LDAP connectivity
    3. Configure DHCP service
    4. Start DHCP service
    5. Test DHCP service

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:

  1. dhcpServer - The host on which the DHCP service will run
  2. dhcpService - The global settings which control the behavior of the DHCP service
  3. 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.
  1. Security - LDAP over SSL
  2. Security - Add LDAP users for access control
  3. Security - SASL or Kerberos authentication
  4. Security - Database access controls (user ACLs)
  5. 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