Thuta Learning
AdvancedDevOps & Toolsbeginner

DHCP and Address Assignment

What you'll walk away with

  • Explain the core ideas behind DHCP and Address Assignment
  • Read the diagram and trace how data actually moves through it
  • Run the sample code and verify its output

Build the mental model

Hardcoding an address into every host does not survive contact with reality. Networks get renumbered, laptops move between subnets, and two people typing the same address into two machines produce a conflict nobody can debug from the application layer. DHCP moves address assignment to the network itself, so a host arrives knowing nothing and leaves fully configured.

The exchange is DORA. The client, which has no IP and therefore cannot address anyone directly, broadcasts a DISCOVER. Any DHCP server on the segment may reply with an OFFER of a specific address. The client picks one and sends a REQUEST, and here is the part that surprises people: the REQUEST is broadcast, not unicast, even though the client now knows exactly which server it wants. It is broadcast precisely because there may have been several offers. Every server on the segment hears the REQUEST, sees whose address was named, and the losers release the addresses they had tentatively reserved. A unicast REQUEST would leave the other servers holding phantom reservations until they timed out, quietly draining their pools. Finally the chosen server sends an ACK, and that is what actually makes the lease real.

A lease is a time limit, not a gift. At T1, fifty percent of the lease, the client unicasts a renewal to its own server. If that fails, at T2, eighty-seven and a half percent, it gives up on that server and broadcasts a rebind to anyone listening. Only if both fail does it drop the address and start over.

And an address alone is useless. The same exchange also carries the subnet mask, the default gateway, and the DNS resolvers. When a host has an IP but reaches nothing, a wrong DHCP-supplied gateway is a far more likely culprit than the address itself.

text
THE DORA EXCHANGE WITH TWO SERVERS
----------------------------------
Client (no IP yet)          Server A            Server B
     |                          |                   |
     |--D-- DISCOVER (bcast) -->|                   |
     |--D-- same broadcast -----|------------------>|
     |                          |                   |
     |<-O-- OFFER .100 ---------|                   |
     |<-O-- OFFER .240 ---------|-------------------|
     |                          |                   |
     |  [ client picks A's offer ]                  |
     |                          |                   |
     |--R-- REQUEST .100 ------>|                   |
     |--R-- ...still a BROADCAST|------------------>|
     |                          |                   |
     |                          |   B hears it lost |
     |                          |   and returns     |
     |                          |   .240 to its pool|
     |                          |                   |
     |<-A-- ACK .100 + mask ----|                   |
     |      + gateway + DNS     |                   |
     v                          v                   v

Why is REQUEST a broadcast?
  So the LOSING servers learn they lost. Unicast would leave
  B holding a phantom reservation on .240 until it timed out.

Lease timers (lease = 3600s):
  T1 = 50%    = 1800s  renew   unicast to Server A
  T2 = 87.5%  = 3150s  rebind  broadcast to anyone
  T  = 100%   = 3600s  address released, back to DISCOVER

Connect it to a real scenario

A common support ticket reads: some people in the office cannot get online, others are fine, and it changes every morning. That intermittency pattern is the signature of a pool that is too small, not of a flaky access point.

First, check whether the affected machine has an address at all. A 169.254.x.x address on Windows, or no address on Linux, means DISCOVER got no usable OFFER: the client is talking and nobody is answering, which is either pool exhaustion or a DHCP relay that is not forwarding broadcasts across a router boundary. Broadcasts do not cross routers, so a client on a different VLAN from the server needs a relay agent configured, and forgetting one is the classic cause of DHCP working on one floor and not the other.

Second, compare the lease time against the number of devices, not the number of people. A /24 with a hundred usable addresses and an eight-hour lease serves far fewer than a hundred people once phones associate, take a lease, and wander off, because each holds an address for eight hours after it left. Shortening the lease on a guest network is usually the entire fix.

The simulation below makes this concrete: a four-address pool, five clients, and the moment a lease expires the address is recycled to the refused client.

Try the working example

python
import ipaddress

# A small DHCP scope, the kind a home router or a lab VLAN would have.
POOL_START = ipaddress.IPv4Address("192.168.10.100")
POOL_END = ipaddress.IPv4Address("192.168.10.103")   # deliberately tiny
LEASE_SECONDS = 3600

# Everything DHCP hands over BESIDES the address. Forgetting that these
# come from DHCP too is why a "wrong gateway" bug looks like a DNS bug.
OPTIONS = {
    "subnet_mask": "255.255.255.0",
    "router": "192.168.10.1",
    "dns": "192.168.10.1, 1.1.1.1",
}


class Scope:
    def __init__(self, start, end):
        self.addresses = [ipaddress.IPv4Address(int(start) + i)
                          for i in range(int(end) - int(start) + 1)]
        self.leases = {}      # address -> (mac, expires_at)

    def free(self, now):
        taken = {a for a, (_, exp) in self.leases.items() if exp > now}
        return [a for a in self.addresses if a not in taken]

    def offer(self, mac, now):
        # Same client asking again gets the SAME address back if it can.
        for addr, (owner, exp) in self.leases.items():
            if owner == mac and exp > now:
                return addr
        available = self.free(now)
        return available[0] if available else None

    def ack(self, mac, addr, now):
        self.leases[addr] = (mac, now + LEASE_SECONDS)
        return now + LEASE_SECONDS


scope = Scope(POOL_START, POOL_END)
CLOCK = 0   # hardcoded virtual clock in seconds; no wall-clock calls

clients = ["aa:00:01", "aa:00:02", "aa:00:03", "aa:00:04", "aa:00:05"]

print("scope 192.168.10.100-192.168.10.103   lease " +
      str(LEASE_SECONDS) + "s")
print("")

for mac in clients:
    offered = scope.offer(mac, CLOCK)
    if offered is None:
        print(mac + "  DISCOVER -> no OFFER (pool exhausted)")
        continue
    expires = scope.ack(mac, offered, CLOCK)
    print(mac + "  DISCOVER -> OFFER " + str(offered) +
          " -> REQUEST -> ACK  expires t=" + str(expires))

print("")
print("gateway=" + OPTIONS["router"] + "  mask=" + OPTIONS["subnet_mask"])
print("dns=" + OPTIONS["dns"])
print("")

# T1 is 50% of the lease: the client unicasts a renewal to its server.
# T2 is 87.5%: it gives up on that server and broadcasts to anyone.
print("T1 (renew, unicast)  at t=" + str(LEASE_SECONDS // 2))
print("T2 (rebind, bcast)   at t=" + str(LEASE_SECONDS * 7 // 8))
print("")

# Now let the first lease expire and watch the address get recycled.
CLOCK = 4000
print("at t=" + str(CLOCK) + " free addresses: " +
      str(len(scope.free(CLOCK))))
print("aa:00:05 DISCOVER -> OFFER " + str(scope.offer("aa:00:05", CLOCK)))
You should see
scope 192.168.10.100-192.168.10.103   lease 3600s

aa:00:01  DISCOVER -> OFFER 192.168.10.100 -> REQUEST -> ACK  expires t=3600
aa:00:02  DISCOVER -> OFFER 192.168.10.101 -> REQUEST -> ACK  expires t=3600
aa:00:03  DISCOVER -> OFFER 192.168.10.102 -> REQUEST -> ACK  expires t=3600
aa:00:04  DISCOVER -> OFFER 192.168.10.103 -> REQUEST -> ACK  expires t=3600
aa:00:05  DISCOVER -> no OFFER (pool exhausted)

gateway=192.168.10.1  mask=255.255.255.0
dns=192.168.10.1, 1.1.1.1

T1 (renew, unicast)  at t=1800
T2 (rebind, bcast)   at t=3150

at t=4000 free addresses: 4
aa:00:05 DISCOVER -> OFFER 192.168.10.100

5-minute try-it

Change LEASE_SECONDS to 600 and the second CLOCK value to 700. Do all five clients get an address now? Then have one client send DISCOVER twice and confirm it gets the same address back, and explain why that sticky behaviour is useful.

One important caution

Expecting DHCP to work across a router: DISCOVER is a broadcast, so a client on a different subnet from the server needs a relay agent.

Sizing a pool by headcount rather than by device count and lease length: phones that associate briefly still hold an address for the whole lease after they leave.

RFC 2131 - Dynamic Host Configuration ProtocolComputer Networking

Easy traps

  • Expecting DHCP to work across a router: DISCOVER is a broadcast, so a client on a different subnet from the server needs a relay agent.
  • Sizing a pool by headcount rather than by device count and lease length: phones that associate briefly still hold an address for the whole lease after they leave.
  • Validate sample code in a local or test environment before applying it to a production network.

Exercise

Change LEASE_SECONDS to 600 and the second CLOCK value to 700. Do all five clients get an address now? Then have one client send DISCOVER twice and confirm it gets the same address back, and explain why that sticky behaviour is useful.

You'll know it worked when: scope 192.168.10.100-192.168.10.103 lease 3600s aa:00:01 DISCOVER -> OFFER 192.168.10.100 -> REQUEST -> ACK expires t=3600 aa:00:02 DISCOVER -> OFFER 192.168.10.101 -> REQUEST -> ACK expires t=3600 aa:00:03 DISCOVER -> OFFER 192.168.10.102 -> REQUEST -> ACK expires t=3600 aa:00:04 DISCOVER -> OFFER 192.168.10.103 -> REQUEST -> ACK expires t=3600 aa:00:05 DISCOVER -> no OFFER (pool exhausted) gateway=192.168.10.1 mask=255.255.255.0 dns=192.168.10.1, 1.1.1.1 T1 (renew, unicast) at t=1800 T2 (rebind, bcast) at t=3150 at t=4000 free addresses: 4 aa:00:05 DISCOVER -> OFFER 192.168.10.100

DHCP and Address Assignment | Thuta Learning