Server Howto: DHCP

This guide assumes you already have a working installation of Red Hat Enterprise Linux, or one of its clones: CentOS Linux, Rocky Linux, et al, and you wish to install an IPv4 DHCP server, perhaps to move the function from an in-house/in-office router that is not offering all of the features you require. This guide does not cover IPv6, because hardly anyone is bothering to use it, and it is too complex!

This particular example is taken from a working CentOS 7 server and modified with an example IP address range.

yum commands are given here. You can substitute for dnf if you are using RHEL 8 or one of its clones.


1: Install

The first thing to do is install the DHCP server software: yum install dhcp (as root). This will also install dhcp-common and dhcp-libs.

You will find the configuration files lurking in /etc/dhcp


2: dhcpd.conf

Sample dhcpd.conf and dhcpd6.conf files are included, along with other directories that we do not need to worry about.

The dhcpd.conf file can be edited with your favourite text editor. I use Nano as it is simple and does what I require.

The dhcpd.conf file listed below is taken from my in-house CentOS 7 server. It offers a DHCP pool, fixed-IP assignments, dynamic DNS updates, and PXE/UEFI boot options, with an MTU of 9000 to max-out the 1 Gb/s Ethernet connections.

You will need to follow the other guides offered on this site if you want to replicate this set-up.

Text in red explains what the line achieves and is not part of the config file.

# DHCP Daemon Configuration File    -your choice of comments
#
# Gaztronics
#
# Last updated: 24th August 2021    -handy to mark when you last made a change

# Global    -this applies to all connecting DHCP clients
#
authoritative;    -we are the only DHCP server in the village!
deny duplicates;    -no clones here!
option domain-name "gaztronics.net";    -set this to your own domain name
option domain-name-servers 192.168.30.253;    -set this to the address of your DNS server
option routers 192.168.30.254;    -this is the IP address of your default gateway (Internet router)
option ntp-servers 192.168.30.253;    -set this if you run an in-house NTP daemon
option interface-mtu 9000;    -tells all clients to use an MTU of 9000 (important for PXEboot to work)
default-lease-time 28800;    -in seconds - this is 8 hours
allow booting;    -we are allowing PXEboot
allow bootp;    -we are allowing PXEboot
option space PXE;    -the following lines define PXE booting parameters
option PXE.mtftp-ip code 1 = ip-address;
option PXE.mtftp-cport code 2 = unsigned integer 16;
option PXE.mtftp-sport code 3 = unsigned integer 16;
option PXE.mtftp-tmout code 4 = unsigned integer 8;
option PXE.mtftp-delay code 5 = unsigned integer 8;
option arch code 93 = unsigned integer 16; # RFC4578
option option-128 code 128 = string;
option option-129 code 129 = text;

class "pxeclients" {    -this section is required to provide UEFI network boot as well as PXE
          match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
          next-server 192.168.30.253;    -IP address of server offering tftpboot

    # UEFI x86-64 boot (RFC4578 architecture types 7, 8 and 9)
    if option arch = 00:07 {
          filename "uefi/shim.efi";    -path to /var/lib/tftpboot/uefi/shim.efi file
    } else if option arch = 00:08 {
          filename "uefi/shim.efi";
    } else if option arch = 00:09 {
          filename "uefi/shim.efi";
    } else {
          # PXE boot
          filename "pxelinux.0";    -path to /var/lib/tftpboot/pxelinux.0
    }

    }

ddns-updates on;    -will pass DNS updates to BIND daemon
ddns-update-style interim;    -using interim style
ddns-domainname "gaztronics.net";    -to this domain name
ddns-rev-domainname "in-addr.arpa.";    -and this reverse DNS domain name
update-static-leases on;    -DHCP controls the static DNS lease time
update-optimization false;    -do not optimise
do-forward-updates true;    -take client DDNS information, not necessarily what we set here

zone gaztronics.net { primary 127.0.0.1; }    -push updates through localhost as BIND is on this server
zone 30.168.192.in-addr.arpa. { primary 127.0.0.1; }    -push updates through localhost

subnet 192.168.30.0 netmask 255.255.255.0 {    -subnet declaration for our network - set to your chosen IP range
range 192.168.30.100 192.168.30.200;    -pool address range from 100 to 200

host tron {    -fixed IP assignment with MAC of network card and declared hostname
hardware ethernet 40:8D:5C:78:20:9F;
fixed-address 192.168.30.1;
ddns-hostname "tron"; }

host orac {    -laptop NIC fixed assignment
hardware ethernet 28:D2:44:ED:B9:2E;
fixed-address 192.168.30.2;
ddns-hostname "orac"; }

host orac-wifi {    -laptop Wi-Fi fixed assignment
hardware ethernet E8:B1:FC:6D:82:24;
fixed-address 192.168.30.3;
ddns-hostname "orac-wifi"; }

# VM
host fedora {    -VirtualBox virtual machine example
hardware ethernet 08:00:27:58:D9:48;
fixed-address 192.168.30.4;
ddns-hostname "fedora"; }

host M600 {    -time-server at the top of the /24 range
hardware ethernet 00:13:95:0B:DB:EB;
fixed-address 192.168.30.249;
ddns-hostname "ntp"; }

host NPI5DF144 {    -fixed assignment for printer (very important!)
hardware ethernet E4:E7:49:5D:F1:44;
fixed-address 192.168.30.250;
ddns-hostname "printer"; }


# 192.168.30.252 - hp-switch    -comments for devices with fixed address not controlled via DHCP
# 192.168.30.253 - liberator
# 192.168.30.254 - gateway

} # - end of subnet

As you can see from the above example config file the network range used is: 192.168.30.x and it is a /24 subnet; i.e. it can be used for 254 devices. I prefer to set the default gateway at the top of the range (.254). Some routers default to .1, and they are changed if I have anything to do with them! You can use any of the 250-odd ranges from 192.168.0.0 to 192.168.254.0 on your network.

If you chose, say 192.168.123.0/24 for your in-house/office network, you will need to change IP addresses and reverse DNS references to suit. For example, the line zone 30.168.192.in-addr.arpa. { primary 127.0.0.1; } would need to be changed to: zone 123.168.192.in-addr.arpa. { primary 127.0.0.1; }. Subnet and range addresses, plus your fixed assignments would need to match your chosen IP range, or your DHCP server will fail to start.

As I am nice, you can download the above dhcpd.conf file to modify for your own requirements. Or wget http://www.gaztronics.net/downloads/dhcpd.conf from within /etc/dhcp.

MTU

A note on Maximum Transmission Unit. Most network adapters default to 1500 bytes, which is the standard for 100 Mb/s Ethernet. 1 Gb/s Ethernet should be capable of handling packets are large as 9000 bytes. However, a number of routers on the market are unable to handle a packet as large as 9000 bytes as they are using cheap PHY chips. You may have to experiment with your network before you default everything to 9000.

An MTU of 9000 is set in my configuration as my primary network operates on a 24-port Managed switch, with a smaller Netgear GS308, and these can all handle "Jumbo packets". A lot of Ethernet ports in both domestic and business routers cannot, so check the specifications and test first.

If your PXE / UEFI network boot refuses to work, re-configure your dhcpd.conf to an MTU of 1500 and restart the daemon. You may need to try this even if your server's NIC is happy running at 9000. Some network stacks in cut-down systems, such as Digital Video Recorders, may ignore the DHCP instruction to use 9000 and default to 1500.

There are a number of articles on the Internet detailing how to test your network for 9000 bytes. There are some oddities with the way ICMP echo requests are handled with such large packets.

Printers

If you see a fellow SysAdmin setting a fixed-IP address inside the printer's control panel, slap them away and tell them a real SysAdmin uses DHCP. Once you have the MAC address of the printer's NIC, there is no reason to not create a fixed assignment in your DHCP configuration. This ensures that any future network changes (e.g. changing the DNS servers) are rolled-out to every device on the network with zero fuss. Trust me on this one: I had to run around fixing printers in a company where the idiot SysAdmins had not bothered to do things properly. They changed to new DNS servers on different IP addresses, then wondered why all the printers with their hard-coded details started playing up.


3: Testing

Pre systemd, the SysV init scripts included a handy configuration check function you could call to check if your config file was working, or was full of errors. systemd appears to have done away with this rather handy feature. There is a way to re-create it. In /etc/dhcp create a file called configtest and add the following text:

#!/bin/bash
#
# Script to check dhcpd.conf
# part stolen from old init.d script
#
# Last updated: 6th September 2016

# Paths n stuff
#
exec=/usr/sbin/dhcpd
config=/etc/dhcp/dhcpd.conf
RETVAL=0


# Test the config for errors
#
$exec -q -t -cf $config
RETVAL=$?
if [ $RETVAL -eq 1 ]; then
$exec -t -cf $config
else
echo "Syntax: OK" >&2
fi


# Clean up
#
unset exec
unset config
unset RETVAL


exit 0

Or you can cheat and download it. Or wget http://www.gaztronics.net/downloads/configtest from within /etc/dhcp

Give the file 755 permissions in order to run it as a script: chmod 755 configtest

You can now run the script from inside the /etc/dhcp directory by simply entering: ./configtest and pressing return.

If you see "Syntax: OK" you are ready to go. If not, dhcpd will helpfully spit-out all of your errors.


4: systemctl

Before you go ahead and enable/start DHCP, you need to ensure you have completed the BIND set-up to allow Dynamic DNS updates to work correctly.

If you are now ready to enable the DHCP server, use the following command: systemctl enable dhcpd

And start the server with: systemctl start dhcpd

It is a good idea to have a second SSH terminal running to your server so you can watch the logging. dhcpd messages are written-out to /var/log/messages unless you re-configure syslog to write-out to a dhcpd.log - something you may want to do in a busy office with large subnets and/or multiple vlans (do not forget logrotate!). The command: tail -f /var/log/messages | grep dhcp will detail all of the dhcpd daemon activities.

If the daemon starts correctly, you should see something like this in /var/log/messages:

dhcpd: Internet Systems Consortium DHCP Server 4.2.5
dhcpd: Copyright 2004-2013 Internet Systems Consortium.
dhcpd: All rights reserved.
dhcpd: For info, please visit https://www.isc.org/software/dhcp/
dhcpd: WARNING: Host declarations are global. They are not limited to the scope you declared them in.
dhcpd: Not searching LDAP since ldap-server, ldap-port and ldap-base-dn were not specified in the config file
dhcpd: Wrote 0 class decls to leases file.
dhcpd: Wrote 0 deleted host decls to leases file.
dhcpd: Wrote 0 new dynamic host decls to leases file.
dhcpd: Wrote 65 leases to leases file.
dhcpd: Listening on LPF/eth0/64:51:06:f8:38:78/192.168.30.0/24
dhcpd: Sending on LPF/eth0/64:51:06:f8:38:78/192.168.30.0/24
dhcpd: Sending on Socket/fallback/fallback-net

And if your leases and ddns are working, you should see something like this:

dhcpd: DHCPREQUEST for 192.168.254.2 from 00:c0:b7:88:9d:84 via eth1
dhcpd: DHCPACK on 192.168.254.2 to 00:c0:b7:88:9d:84 via eth1
dhcpd: Added new forward map from ups.gaztronics.net to 192.168.254.2
dhcpd: Added reverse map from 2.254.168.192.in-addr.arpa. to ups.gaztronics.net

5: firewall

The above assumes you have no iptables firewalling running on your in-house/office server. If you are running an iptables-based firewall, you will need to open up the TCP and UDP ports used by DHCP if you want your client machines to be assigned an address ... unless you have a simple ruleset that simply allows everything to pass to and from, say eth0. If that is the case, there is little point in running iptables!

Add to your iptables ruleset, the following lines, which assume your NIC is "eth0":

iptables -A INPUT -i eth0 -p tcp --sport 68 --dport 67 -j ACCEPT
iptables -A INPUT -i eth0 -p udp --sport 68 --dport 67 -j ACCEPT

iptables -A OUTPUT -o eth0 -p tcp -s 192.168.30.253/255.255.255.0 --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT
iptables -A OUTPUT -o eth0 -p udp -s 192.168.30.253/255.255.255.0 --sport 67 -d 255.255.255.255 --dport 68 -j ACCEPT

Caveat Emptor: check and double-check your iptables syntax before applying it. Do not take my word for it! It is very easy to lock yourself out of a remote server if you load the wrong rules!


Page updated: 28th August 2021