5.6 Sockets
Before an interface has an address, nothing but a packet socket can send
from 0.0.0.0. So every DHCPv4 broadcast leaves through AF_PACKET, with
an IPv4 and UDP header netd builds itself, and every reply is read from
the same socket.
5.6.1 The packet socket #
One per running client: AF_PACKET, SOCK_DGRAM, protocol ETH_P_IP,
bound to the interface. In the proposed corrected source, a classic BPF filter
passes only an IPv4 UDP datagram that is not a fragment and is addressed
to port 68. It refuses both a non-zero fragment offset and the
more-fragments flag; the don't-fragment flag is allowed. Everything read
is checked again for an IPv4 header and UDP port 68, then decoded as a
DHCP message.
In versions 0.1.7 (described by this manual) and 0.1.8, the filter tests only the fragment offset: a later fragment is refused but a first fragment passes and is decoded as if it were the whole datagram. The proposed source correction above is not present in those releases.
The socket sees every frame on the interface that passes the filter, so it receives both broadcast replies and replies unicast to the lease address.
5.6.2 The broadcast path #
DISCOVER, REQUEST in Requesting and Rebooting, and REQUEST in Rebinding go
out on the packet socket to Ethernet ff:ff:ff:ff:ff:ff, as one IPv4
datagram:
| Field | Value |
|---|---|
| source | 0.0.0.0 |
| destination | 255.255.255.255 |
| TOS | 0x10 |
| identification | 0 |
| flags | don't-fragment |
| TTL | 64 |
| UDP ports | 68 → 67 |
| UDP checksum | 0 (none, which IPv4 permits) |
5.6.3 The unicast path #
REQUEST in Renewing, and RELEASE, go out on a transient AF_INET UDP
socket with SO_REUSEADDR and SO_BINDTODEVICE set to the interface,
bound to the lease address on port 68, sent to the server on port 67, and
closed. The kernel resolves the server's link-layer address. The reply
is read from the packet socket.
Binding port 68 meets the kernel's port reservation for ports 1 to 1023, which the shipped seed grants to SYSTEM, netd's identity.
5.6.4 The absorber #
At startup netd binds one more UDP socket, to 0.0.0.0:68 with
SO_REUSEADDR, and discards whatever arrives on it. Its only purpose is
that a server's unicast reply finds a bound port, so the kernel does not
answer it with ICMP port-unreachable. The packet sockets see the same
datagrams. If it cannot be bound, netd runs without it (§2.1).