Networking for beginners. Part 11: DHCPv4

All parts of the series “Networking for beginners”

In all the previous parts the host already had the address 192.168.1.10, the mask /24, the gateway 192.168.1.1 and a DNS server address. Where from? In the fifth part we saw that all of this can be set by hand, and in server networks that is exactly what people do. But a laptop that is in the office in the morning, in a café in the afternoon and at home in the evening cannot be configured by hand: on each network it needs its own address from that network's range and that network's gateway. This job is done by DHCP — the Dynamic Host Configuration Protocol, RFC 2131. It is also the reason a home network "just works": the router hands out addresses, and you never think about it.

The problem looks paradoxical: to get an address you have to send a request, and you cannot send an IP packet without an address. Let's see how this is worked around.


The format: a legacy of BOOTP

DHCP grew out of BOOTP — a 1985 protocol for booting diskless workstations, which needed to learn their address and where to get a system image. DHCP kept its message format in full and added options and address leases to it. That is why tcpdump still labels it BOOTP/DHCP.

The message travels in UDP (part 9): the client sends from port 68 to port 67, the server replies from 67 to 68. The fixed part is 236 bytes:

 bytes
 ┌───────┬───────┬───────┬───────┐
 │ op    │ htype │ hlen  │ hops  │   op: 1 request, 2 reply; htype 1, hlen 6 — Ethernet; hops — relay counter
 ├───────┴───────┴───────┴───────┤
 │            xid (4)            │   exchange identifier, random, chosen by the client
 ├───────────────┬───────────────┤
 │   secs (2)    │   flags (2)   │   secs — how long the client has been trying; flags: the Broadcast bit
 ├───────────────┴───────────────┤
 │          ciaddr (4)           │   the client's address, if it already has one (renewal)
 ├───────────────────────────────┤
 │          yiaddr (4)           │   "your" — the address the server assigns
 ├───────────────────────────────┤
 │          siaddr (4)           │   the server address for the next boot step (TFTP)
 ├───────────────────────────────┤
 │          giaddr (4)           │   the relay agent's address, if the request came through a router
 ├───────────────────────────────┤
 │          chaddr (16)          │   the client's MAC (6 bytes, the rest zeros)
 ├───────────────────────────────┤
 │          sname (64)           │   server name, almost always empty
 ├───────────────────────────────┤
 │          file (128)           │   boot file name, almost always empty
 ├───────────────────────────────┤
 │ magic cookie 63 82 53 63      │   "DHCP options follow"
 ├───────────────────────────────┤
 │ options: code, length, value …│   up to 255 — the end
 └───────────────────────────────┘

Everything interesting is in the options, each in the format "code (1 byte), length (1 byte), value". The main one is option 53, DHCP Message Type, which gives the message its name:

53 = Type Who sends it
1 DISCOVER client: "is there a DHCP server here?"
2 OFFER server: "yes, I offer an address"
3 REQUEST client: "I'll take this one", and also renewal
4 DECLINE client: "the address is already taken by someone"
5 ACK server: "confirmed, here are the parameters"
6 NAK server: "I can't, start over"
7 RELEASE client: "I no longer need the address"
8 INFORM client: "I have an address, I only need the parameters"

DORA: four messages

Getting an address for the first time takes four messages, and the exchange is named DORA after their first letters:

   client (no address)                                        server 192.168.1.1
     │                                                                 │
     ├── DISCOVER ──────────────── broadcast ─────────────────────────►│
     │   from 0.0.0.0:68 to 255.255.255.255:67                         │
     │   chaddr = my MAC, xid = 0x1a2b3c4d                             │
     │   options: 53=1, 55 (what I need), 61 (client id), 12 (hostname)│
     │                                                                 │
     │◄── OFFER ────────────────── broadcast or to MAC ────────────────┤
     │   yiaddr = 192.168.1.10, same xid                               │
     │   options: 53=2, 54 (server id 192.168.1.1), 51 (lease),        │
     │            1 (mask), 3 (gateway), 6 (DNS)                       │
     │                                                                 │
     ├── REQUEST ──────────────── broadcast ──────────────────────────►│  (all servers hear it)
     │   options: 53=3, 50 (I want 192.168.1.10), 54 (from 192.168.1.1)│
     │                                                                 │
     │◄── ACK ─────────────────────────────────────────────────────────┤
     │   yiaddr = 192.168.1.10, same options                           │
     │                                                                 │
     │ ARP probe for 192.168.1.10 (part 7):                            │
     │   free → address goes onto the interface                        │

DISCOVER. The client has no address, so the source in the IP header is 0.0.0.0 — "no address" from the fifth part — and the destination is 255.255.255.255 — limited broadcast, to everyone on this segment. In the Ethernet frame the destination is ff:ff:ff:ff:ff:ff. Everyone receives the packet, but only a DHCP server answers. The client puts its MAC into chaddr, a random xid by which it will tell replies to its own request from others, and option 55 — the parameter list it needs: mask, gateway, DNS, domain name. If the client had an address last time, it will ask for it with option 50 — servers try to give familiar clients their previous addresses.

OFFER. The server picks a free address from its pool, reserves it temporarily and replies. How do you deliver a reply to a host that has no address? The server writes the offered address into yiaddr and sends the packet itself either to 255.255.255.255 (if the client set the Broadcast flag — stacks that will not accept unicast to a not-yet-assigned address do this), or as a unicast frame straight to the MAC from chaddr with destination IP yiaddr, bypassing ARP: there is no point asking "who has 192.168.1.10" — nobody has that address yet. Linux clients accept the second option and save a broadcast.

REQUEST. The client chooses an offer (if there are several servers, usually the first to arrive) and asks for exactly that one: option 50 with the address and option 54 with the server's address. And again it is a broadcast — not because it cannot do otherwise, but so that the other servers see their offers were rejected and release the addresses they reserved.

ACK. The server records the lease in its database and confirms with the full set of options. Only now does the client assign the address to the interface, the default route via the gateway from option 3 and DNS from option 6. Before that, a careful client checks the address with an ARP probe and, if someone answers, sends the server a DECLINE and starts over; the server marks the address as bad.

In tcpdump it looks like this (-v expands the options):

$ sudo tcpdump -i eth0 -n -v 'udp port 67 or udp port 68'
IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 3c:52:82:1a:2b:3c, length 300, xid 0x1a2b3c4d, Flags [none]
	  Client-Ethernet-Address 3c:52:82:1a:2b:3c
	  Vendor-rfc1048 Extensions
	    Magic Cookie 0x63825363
	    DHCP-Message (53), length 1: Discover
	    Client-ID (61), length 7: ether 3c:52:82:1a:2b:3c
	    Hostname (12), length 6: "laptop"
	    Parameter-Request (55), length 13: Subnet-Mask, BR, Default-Gateway, Domain-Name, Domain-Name-Server, ...
IP 192.168.1.1.67 > 192.168.1.10.68: BOOTP/DHCP, Reply, length 300, xid 0x1a2b3c4d, Flags [none]
	  Your-IP 192.168.1.10
	  Client-Ethernet-Address 3c:52:82:1a:2b:3c
	  Vendor-rfc1048 Extensions
	    Magic Cookie 0x63825363
	    DHCP-Message (53), length 1: Offer
	    Server-ID (54), length 4: 192.168.1.1
	    Lease-Time (51), length 4: 86400
	    Subnet-Mask (1), length 4: 255.255.255.0
	    Default-Gateway (3), length 4: 192.168.1.1
	    Domain-Name-Server (6), length 4: 192.168.1.1
IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 3c:52:82:1a:2b:3c, length 300, xid 0x1a2b3c4d
	    DHCP-Message (53), length 1: Request
	    Requested-IP (50), length 4: 192.168.1.10
	    Server-ID (54), length 4: 192.168.1.1
IP 192.168.1.1.67 > 192.168.1.10.68: BOOTP/DHCP, Reply, length 300, xid 0x1a2b3c4d
	    DHCP-Message (53), length 1: ACK
	    ...

One xid for all four messages, 0.0.0.0 as the client's source, and an OFFER sent to 192.168.1.10, which nobody has yet.


Options

There are several dozen codes, of which a dozen turn up in practice:

Code Name What
1 Subnet Mask the mask
3 Router the default gateway; can be a list
6 Domain Name Server DNS addresses, in order of preference
12 Host Name the client reports its name; the server may register it in DNS
15 Domain Name the domain the client appends to short names
28 Broadcast Address the network's broadcast address
42 NTP Servers time servers
43 Vendor Specific anything, by vendor convention: this is how access points learn the controller's address and IP phones the PBX's address
50 Requested IP Address the client asks for a specific address
51 Lease Time lease duration in seconds
54 Server Identifier the server's address; the client uses it to choose whom to answer
55 Parameter Request List what the client needs
58, 59 Renewal (T1), Rebinding (T2) when to renew, by default 50% and 87.5% of the lease
61 Client Identifier what the client considers itself: usually the MAC, but it can be an arbitrary string; the server keys reservations on it
66, 67 TFTP Server, Bootfile for network boot: where and which file to fetch via TFTP — the simplest file transfer protocol, over UDP; this is how PXE works, booting a machine over the network from the network card's firmware
82 Relay Agent Information inserted by a relay or switch: which port the request came from; the server can use it to assign addresses per wall socket
119 Domain Search a list of domains for resolving short names
121 Classless Static Routes routes the client must add; if the client requested and received it, it must ignore option 3, and people regularly forget this
255 End end of options

The client receives only what it requested in option 55 and what the server is configured to give it. If "DNS doesn't work after getting an address" on a network, the first thing to check is whether option 6 is in the ACK.


The lease and its renewal

An address is issued for a term — a lease, option 51. This is what distinguishes DHCP from BOOTP: an address that is no longer used returns to the pool. The term is set by the administrator: a day for an office, an hour for a guest network with high turnover, a week where stability is desired.

The client does not wait for expiry:

 ACK received         T1 = 50 %              T2 = 87.5 %           100 %
 ├────────────────────┼───────────────────────┼─────────────────────┤
 │  BOUND             │  RENEWING             │  REBINDING          │ address removed,
 │  just works        │  REQUEST unicast      │  REQUEST broadcast  │ DISCOVER again
 │                    │  to its own server    │  to any server      │
  • At T1, halfway through the term, the client sends a unicast REQUEST straight to the server that issued the address (now the client has an address and broadcast is not needed), with ciaddr = its own address. The server replies with an ACK with a new term, and the countdown starts again. If the server is silent, the client retries at increasing intervals.
  • At T2, at 87.5%, the client decides its server has died and sends the REQUEST as a broadcast: any server that knows this address may renew it.
  • At 100% the address is removed from the interface, and everything starts again with DISCOVER. Connections are broken. With a live server this never happens.

When a machine with a saved lease is powered on, it does not send DISCOVER but immediately sends a REQUEST with option 50 — "I had 192.168.1.10, is it still mine?". If the machine is on the same network — ACK, and the address works within half a second. If it has been moved to another network, the server there answers NAK: the address is not from my pool, start over. That is how a laptop that came home from the office does not cling to its office address.

The client sends RELEASE on a clean disconnect (dhclient -r, ipconfig /release) so that the server frees the address immediately. It is not mandatory, and if the cable is pulled the lease lives until the end of its term. That is why in a small pool with high turnover addresses "run out" even though there are few machines on the network: the pool is taken up by leases of devices that have left.

INFORM is for machines with a static address that only need the options: DNS, domain, proxy. The server replies with an ACK without yiaddr and without a lease.


Through a router: relay

Broadcast does not pass through routers (the address 255.255.255.255 from the table in the fifth part). So must there be a DHCP server in every segment? In 1985 there was; in an office with fifty VLANs that is impossible. That is why a router (or L3 switch) can act as a relay agent:

   client 10.0.5.x              router                              server 192.168.1.1
     │                     10.0.5.1 │ 192.168.1.254                        │
     ├── DISCOVER broadcast ───────►│                                      │
     │                              ├── DISCOVER unicast ─────────────────►│  giaddr = 10.0.5.1, hops = 1
     │                              │                                      │  server: giaddr is in 10.0.5.0/24 →
     │                              │                                      │  use the pool for 10.0.5.0/24
     │                              │◄── OFFER unicast to 10.0.5.1 ────────┤  yiaddr = 10.0.5.20
     │◄── OFFER ────────────────────┤                                      │
  1. The relay on interface 10.0.5.1 catches the broadcast DISCOVER, writes its own address in that segment into giaddr, increments hops and sends the packet as unicast to the server whose address is set in its configuration (ip helper-address in Cisco terms).
  2. The server sees giaddr ≠ 0, understands the client is not in its own segment, and chooses the pool by giaddr: 10.0.5.1 belongs to the network 10.0.5.0/24, so the address comes from there. This is the key point: a server can have any number of pools for different networks, and it chooses among them by the relay's address.
  3. The server sends the reply as unicast to giaddr; the relay receives it on port 67 and passes it to the client in its segment: as a broadcast or to the MAC from chaddr.

A relay can add option 82 with the number of the switch port the request came from, and then the server can give a specific address to a specific wall socket, whoever plugs into it. ISPs and large networks do this.


Reservations, pools, conflicts

On the server, a segment's address space is divided into:

  • a pool (range, scope) of dynamic addresses, for example 192.168.1.100–192.168.1.200;
  • reservations: "such-and-such MAC always gets 192.168.1.50" — for printers and servers that need a permanent address but which you want to manage centrally;
  • exclusions: addresses the server leaves alone because they are configured by hand.

The configuration of the simplest server, dnsmasq, fits in three lines:

dhcp-range=192.168.1.100,192.168.1.200,24h
dhcp-option=option:router,192.168.1.1
dhcp-host=00:11:22:33:44:55,192.168.1.50,printer

Before offering an address, a server usually pings it: if someone answers, the address is marked as taken, and the next one is offered. This protects against machines with static addresses inside the pool, but not completely: a machine that is currently switched off will not answer.

Two servers

If there are two DHCP servers in a segment, both will answer a DISCOVER, and the client takes the first OFFER. With two configured servers that split the pool or synchronise leases (failover), this is precisely fault tolerance. With an accidental second one — a router brought from home and plugged into an office socket by its LAN port — it is a classic outage: some machines get an address in 192.168.0.x and a gateway leading nowhere, and which ones depends on whose OFFER arrived first. Half the office works, half does not, and a reboot reshuffles the halves.

The protection is on the switch: DHCP snooping. The switch lets OFFER and ACK through only from ports marked as trusted (where the real server or the uplink to it is) and drops them from all others. It also remembers which address was issued on which port, and this table feeds the Dynamic ARP Inspection from the seventh part. The protocol itself has no protection at all: the client trusts whoever answers.

When there is no server

The client repeats DISCOVER at increasing intervals, and what happens next depends on the system. Windows and macOS assign themselves an address from 169.254.0.0/16 after a minute (APIPA from the fifth part): there is connectivity with neighbours that did the same, but not with the internet. If you see 169.254, DHCP did not answer. Linux with NetworkManager does the same, while dhclient on its own leaves the interface without an address and keeps asking. Meanwhile the client keeps listening and picks up an address as soon as a server appears.


Seeing it for yourself

Request an address again and watch the exchange (in a separate window, the tcpdump from the DORA section):

$ sudo dhclient -r eth0 && sudo dhclient -v eth0
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3
DHCPOFFER of 192.168.1.10 from 192.168.1.1
DHCPREQUEST for 192.168.1.10 on eth0 to 255.255.255.255 port 67
DHCPACK of 192.168.1.10 from 192.168.1.1
bound to 192.168.1.10 -- renewal in 39212 seconds.

"renewal in 39212" is T1, roughly half of a one-day lease. The parameters received:

$ cat /var/lib/dhcp/dhclient.leases        # dhclient
$ nmcli device show eth0 | grep DHCP4      # NetworkManager
$ networkctl status eth0                   # systemd-networkd

On Windows: ipconfig /all shows the server and the time the lease was obtained and expires; ipconfig /release and ipconfig /renew are RELEASE and a new DISCOVER.

Finding a rogue DHCP server: sudo nmap --script broadcast-dhcp-discover sends a DISCOVER and lists everyone who answered, with their addresses and offers. Or simply run tcpdump on udp port 67 and watch which addresses OFFERs come from.


Key takeaways

  • DHCP is UDP 67/68, the BOOTP format plus options. A client without an address sends from 0.0.0.0 to 255.255.255.255, the server replies to the MAC or by broadcast; xid ties the exchange together.
  • DORA: DISCOVER, OFFER, REQUEST, ACK. The REQUEST is broadcast so that the other servers withdraw their offers. After the ACK comes an ARP probe, and on a conflict a DECLINE.
  • Everything useful is in the options: 1 mask, 3 gateway, 6 DNS, 51 lease, 54 server, 121 routes (overrides 3).
  • The lease is renewed at T1 by unicast to its own server, at T2 by broadcast to any server; on power-up — a REQUEST for the old address, and a NAK on a foreign network.
  • Through a router — relay: giaddr tells the server which pool to assign from.
  • Two servers in a segment mean half the network without connectivity; the protection is DHCP snooping. 169.254.x.x means the server did not answer.

Next — Part 12: DNS: how a name turns into an address, where the tree of names lives and who is responsible for which part of it, the full path of a query from your machine through the root servers to the answer, the message format, record types, caching and TTL, and what dig +trace is.

Only registered users can leave a comment.

Sign in or register to leave a comment.