Arch Linux-based Home Router, Part IV (dhcpd)

Most off-the-shelf home routers contain a DHCP (Dynamic Host Control Protocol) server, to assign IP addresses on your network.  Your ISP likely has a DHCP server that assigns your WAN IP address to your router's WAN interface.  The page linked above lists a few different options, I elected the Internet Systems Consortium DHCP server.  Install the dhcp package.

Configuration file

The ISC DHCP server can serve both IPv4 and IPv6 addresses.  To serve both IPv4 and IPv6, the administrator needs to start dhcpd separately in each mode.  Thus, two services will run, one for IPv4, one for IPv6.  Because I intend to set up IPv6 eventually, I created two files, /etc/dhcp/dhcpd.conf (for IPv4), and /etc/dhcp/dhcpd6.conf (for IPv6).  This article focuses on the IPv4 configuration.

The standard dhcpd in Arch expects the configuration to be in /etc/dhcpd.conf.  This is referred to in the dhcpd4.service file.  I prefer the configuration files to be in /etc/dhcp, so I created a symlink /etc/dhcpd.conf -> /etc/dhcp/dhcpd.conf.  It may have been better to edit the ExecStart line in the service file, but either way will work.  I did edit the service file and appended the interfaces enp4s0 and enp4s0.66.  The Arch wiki suggests creating a service template, and passing the particular interface when enabling and starting the service.  Either way will work, but the template service takes a bit more work, and you have to start multiple services that way, one for each interface you want dhcpd to listen on.  You can also leave the interfaces out of the service file, but then it will listen on ALL interfaces, including the WAN interface.  Which might not be what you want.

Fortunately the administrator doesn't need to create the dhcpd.conf from scratch.  Installing the dhcp package creates a populated /etc/dhcpd.conf.  The Arch wiki suggests copying this to /etc/dhcpd.conf.example and starting clean.  It is up to the administrator, there are plenty of examples in the default configuration.

Before getting into the specific parts of the configuration, there are global settings.  The first setting is ddns-update-style none;, the comment above it says this parameter will have the DHCP service update the DNS record when a lease is confirmed, the none option means this feature is turned off.  I assume integration with your DNS service is required for this to work, the default is to have it off with this setting.

The next is to set the domain and the name servers for the domain:

option domain-name "ceti.";
option domain-name-servers barbican.ceti;

barbican.ceti is the fully-qualified domain name (FQDN) of my router.  A barbican classically is the fortified gate on the outer wall of a castle, it usually has a drawbridge, a portcullis, and my personal favorite, murder holes!  So I think it's a befitting name for an edge gateway for a home network.  ceti is named after the star system Ceti Alpha where Usrula K. LeGuin's The Dispossessed is set, the fifth book in LeGuin's Hainish Cycle.  The main character in that story discovered the physics behind the ansible, a faster-than-light communication device.  This is where the Ansible project gets its name.

Next, I define the default and maximum lease times:

default-lease-time 86400;
max-lease-time 604800;

I've set the default lease time to a single day (86,400 seconds), and the max lease time to a week (604,800 seconds).  This will prevent too many DHCP requests on the network, so clients won't need to renew their leases that often.  If I recall correctly, the defaults are much lower than this, and can cause the local network connections to stop working should the router be offline for any reason (the lease will expire, and if the router is down renewal requests will go unanswered, causing the clients to disconnect).

The next global parameter is authoritative;, which should be uncommented if it is the authoritative DHCP server for the local network.  The log-facility local7; defines which log level the DHCP daemon logs.  This setting will allow the daemon to log to the systemd journal.  

The annotated default configuration suggests a specific emtpy service definition, which will help the DHCP daemon understand the network topology.  I left it in, but I'm not sure it's absolutely necessary:

subnet 10.152.187.0 netmask 255.255.255.0 {
}

IPv4 Range

The first step in defining a range of IPv4 addresses leases is to determine the range the administrator wants to lease out.  If you don't want or have a need for any hosts in your network to have static IPv4 addresses or static leases, you can define the range to take up the entire subnet, minus the network, broadcast, and router addresses.  However, I want to be able to refer to some hosts on my network by hostname, and rather than configure them with static IPv4 addresses, I've defined static leases (so they always get the same IPv4 address).  More on that in the next section.

To define a range of IPv4 address for the DHCP server to lease, define the range like so:

subnet 10.20.30.0 netmask 255.255.255.0 {
    range 10.20.30.200 10.20.30.248;
    option subnet-mask 255.255.255.0;
    option broadcast-address 10.20.30.255;
    option routers 10.20.30.254;
    option domain-name-servers 10.20.30.254;
}

The subnet 10.20.30.0 is the subnet I'm defining, with a netmask in dotted decimal notation (255.255.255.0, it looks like dhcpd doesn't accept CIDR notation [i.e. /24]).  The range parameter specifies the first address 10.20.30.200, and the last address 10.20.30.248 in the range.  A DHCP lease for this subnet will be any address between the first and last address, inclusive.  So, as I have this defined, I can have up to 49 leases before the range is exhausted (or full, depending on how you want to look at it).  For my small network, this should be plenty (most times I only have about ten devices, according to my UniFi network topology graph).  I also specify the subnet-mask, broadcast-address, routers, and domain-name-servers options. routers and domain-name-servers point to barbican's LAN IPv4 address.

Next, I defined a wider range for my IoT subnet:

subnet 172.16.87.0 netmask 255.255.255.0 {
    range 172.16.87.100 172.16.87.248;
    option subnet-mask 255.255.255.0;
    option broadcast-address 172.16.87.255;
    option routers 172.16.87.254;
    option domain-name-servers 172.16.87.254;
}

172.16.87.254 is barbican's IoT address.

Static leases

Since I haven't linked dhcpd to my DNS server (bind, more on that in the next article), so I've defined static leases.  These require the hardware address, otherwise known as the Medium Access Control (MAC) address.  On Linux, you can determine this with the ip link command in a terminal shell (it will be the link/ether address).  Alternatively, if the Linux system is a bit older, the deprecated ifconfig command can be used.  In Windows, you should be able to drop to a cmd/PowerShell and execute the ipconfig /all command to see the MAC address.  On macOS, you should be able to find the MAC address in the System settings/network section.

A definition for a static lease looks like this:

host radon {
    hardware ethernet f8:94:c2:16:49:2d;
    fixed-address 10.20.30.77;
    option routers 10.20.30.254;
}

The hardware ethernet address is the MAC address, and the fixed-address is the static IPv4 address I've set for my laptop's WiFi adapter.  The routers option is the barbican LAN address, again.  As you may have noticed, the fixed-address is NOT in the IPv4 range defined earlier.  I would hope the ISC DHCP daemon is smart enough not to lease an IPv4 address that matches a static lease, but having overlap will unnecessarily reduce the range of available IPv4 addresses in the pool.

The number of hosts the administrator defines with static leases is arbitrary, any number can be defined.  

Final Thoughts

The ISC DHCP daemon has many more options, and can get quite complicated.  As with firewalld, reading the manual page (man dhcpd.conf) delineates the possibilities.

NEXT STEPS

The following articles continue this series on setting up an Arch Linux-based Home Router:

  1. Arch Linux-based Home Router, Part I
  2. Arch Linux-based Home Router, Part II (systemd-networkd and sysctl/kernel)
  3. Arch Linux-based Home Router, Part III (firewalld configuration)
  4. Arch Linux-based Home Router, Part IV (dhcpd configuration) (This article)
  5. Arch Linux-based Home Router, Part V (bind)
  6. Arch Linux-based Home Router, Part VI (DDNS)