I hope you've done your homework. In a previous article about networking we set an exercise to configure some machines with static IP addresses.

You probably agree that it would be annoying if you had to change the address range as it is configured in all the different computers.

Wouldn't it be better if we could store this information in the network somehow?

Well, guess what? We can! Its called DHCP.

This works by nominating one computer on the network to act as the master of networking configuration.

Computers which want to opt into the network communicate with this server to be told what addresses to use.

Normally your router-modem that connects you to the internet takes this responsibility, but any machine, real or virtual, may do it.

Example with network namespaces

As before, we're going to use network namespaces to simulate multiple machines sharing a network.

For DHCP to work you need a DHCP server on the managing machine, and a DHCP client on every client machine that will join this network. Since we are only virtualising the networking, we need both on the same machine.

You will likely have dnsmasq and dhclient on your system, since while systemd can provide a DHCP server and client through networkd, this currently is generally only used in servers, embedded and containers, while NetworkManager uses dnsmasq and dhclient on desktops.

While networkd is capable of being both a DHCP client and server, we can only demonstrate its use as a client, since it requires a full container to isolate its configuration from the host.

Configuring the virtual network

We're going to use a network namespace called dhcptest, which will be connected to the host network namespace by a virtual ethernet device.

# ip netns add dhcptest
# ip link add host type veth peer name guest
# ip link set guest netns dhcptest

The subnet of the network linking us to dhcptest we use is 10.0.0.0/24, which ranges from 10.0.0.1 to 10.0.0.255.

Since the machine in dhcptest will be the DHCP server, we need to assign its own address statically.

# ip netns exec dhcptest ip addr add 10.0.0.1 dev guest
# ip netns exec dhcptest ip link set guest up
# ip netns exec dhcptest ip route add 10.0.0.0/24 dev guest

Using dnsmasq as the DHCP server

In a terminal, run:

# ip netns exec dhcptest dnsmasq --dhcp-range=10.0.0.2,10.0.0.254,255.255.255.0 --interface=guest --no-daemon
dnsmasq: started, version 2.72 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect
dnsmasq-dhcp: DHCP, IP range 10.0.0.2 -- 10.0.0.254, lease time 1h
dnsmasq: reading /etc/resolv.conf
dnsmasq: using nameserver 127.0.1.1#53
dnsmasq: read /etc/hosts - 1 addresses

This starts dnsmasq on dhcptest, serving the address range 10.0.0.2 to 10.0.0.254, with the subnet 10.0.0.0/24 (this is the 255.255.255.0 netmask), on theguest` network interface.

--no-daemon forces dnsmasq to stay running in the foreground, which makes it easier to clean up when finished, since it can be cancelled with ^C.

Using dhclient as the DHCP client

In a different terminal to the one that the DHCP server was started in, run:

# dhclient -d host
Internet Systems Consortium DHCP Client 4.3.1
Copyright 2004-2014 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/

Listening on LPF/host/16:c0:28:6b:ea:76
Sending on   LPF/host/16:c0:28:6b:ea:76
Sending on   Socket/fallback
DHCPDISCOVER on host to 255.255.255.255 port 67 interval 3 (xid=0xd6a3b68)
DHCPREQUEST of 10.0.0.105 on host to 255.255.255.255 port 67 (xid=0x683b6a0d)
DHCPOFFER of 10.0.0.105 from 10.0.0.1
DHCPACK of 10.0.0.105 from 10.0.0.1
bound to 10.0.0.105 -- renewal in 1643 seconds.

This shows that it has successfully requested 10.0.0.105 as its address.

The dhclient process will continue to run, as it hasn't been given that address forever, it has been given that address for an hour, at which point it will expire and it must stop using it, but before then dhclient may renew the address to allow us to keep using it.

Using networkd as the DHCP client

First create the config file:

# install -m 644 /dev/stdin /etc/systemd/network/dhcptest.network <<'EOF'
> [Match]
> Name=host
> [Network]
> DHCP=yes
> EOF

Since networkd currently only reads its config at startup, we must restart it for the new config to take effect. (This also has the effect of starting it for the first time, on distros that don't start networkd by default.)

# systemctl restart systemd-networkd.service

This won't give any immediate feedback whether it worked, but you can inspect its status by running:

$ networkctl status host
● 6: host
   Link File: /lib/systemd/network/99-default.link
Network File: /etc/systemd/network/dhcptest.network
        Type: ether
       State: routable (configured)
      Driver: veth
  HW Address: 16:c0:28:6b:ea:76
         MTU: 1500
     Address: 10.0.0.105
              fe80::14c0:28ff:fe6b:ea76
     Gateway: 10.0.0.1
         DNS: 10.0.0.1

Note that this has created some persistent configuration, so the next time the host ethernet device is created networkd will DHCP.

If you don't want to do this in future, you can remove the configuration by running rm /etc/systemd/network/dhcptest.network.

DHCPing successfully

In the terminal running dnsmasq you should see:

dnsmasq-dhcp: DHCPDISCOVER(guest) 10.0.0.105 16:c0:28:6b:ea:76 
dnsmasq-dhcp: DHCPOFFER(guest) 10.0.0.105 16:c0:28:6b:ea:76 
dnsmasq-dhcp: DHCPREQUEST(guest) 10.0.0.105 16:c0:28:6b:ea:76 
dnsmasq-dhcp: DHCPACK(guest) 10.0.0.105 16:c0:28:6b:ea:76 HOSTNAME
dnsmasq-dhcp: not giving name HOSTNAME to the DHCP lease of 10.0.0.105 because the name exists in /etc/hosts with address 127.0.1.1

In another terminal you can see that it configured the address by running:

$ ip addr show dev host
6: host: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 16:c0:28:6b:ea:76 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.105/24 brd 10.0.0.255 scope global host
       valid_lft forever preferred_lft forever
    inet6 fe80::14c0:28ff:fe6b:ea76/64 scope link 
       valid_lft forever preferred_lft forever

Testing the link

We can now prove it works with netcat.

Run ip netns exec dhcptest nc -l 1234 in one terminal, and nc 10.0.0.1 1234 in another, and you will be able to see that text entered is repeated in both terminals.