Contributed by Peter N. M. Hansteen on from the modernizing BPFoonery dept.
dhcpd(8)
is not quite as reliable as one would want in providing the requested
data to the actual requestor.
After some rounds of discussion and experimentation,
David Gwynne (dlg@
) is circulating a
diff
on tech@
that switches the daemon to use
UDP
sockets instead of
bpf.
The motivation is summarized as,
tl;dr this replaces bpf with udp sockets in dhcpd, mostly to make it better at replying with the ip that requests were sent to.
and the full message, with the subject dhcpd(8): use UDP sockets instead of BPF reads,
List: openbsd-tech Subject: dhcpd(8): use UDP sockets instead of BPF From: David Gwynne <david () gwynne ! id ! au> Date: 2025-06-13 3:29:20 tl;dr this replaces bpf with udp sockets in dhcpd, mostly to make it better at replying with the ip that requests were sent to. ive been hacking on this because of a problem at work, which i want to solve by setting up a bunch of "anycast" dhcp servers. ie, i want to have multiple dhcpd on separate servers with the same IP assigned as an alias on all of them.
bpf does not make this easy. the bpf code unconditionally steals all dhcp packets entering the interface, regardless of dst ip, and unconditionally replies to them using the primary address on the interface. the "anycast" ip is effectively lost. if i use dhcpd -u, i am limited to handling DHCPINFORM messages. florian and i have been talking on and off for a few years about whether it's possible to implement a dhcp client using udp sockets rather than bpf, so i took the learnings from those experiments and tried to hack them into dhcpd. this removes bpf from dhcpd. instead, each interface has a udp listener for it's primary IP. eg, i have etherip0 with 192.168.0.1 on it, so i bind a udp socket to 192.168.0.1. if i have at least one of these interface listeners, then i also bind a socket to 255.255.255.255. this allows me to receive packets from dhcp clients attempting their initial DISCOVER. packets received on this socket have the IP_RECVIF sockopt enabled, which lets me marry the request up with which interface it was recved on. replies to these DISCOVER packets will come from the interface IP, ie, 192.168.0.1 in my setup. to support clients that want the replies broadcast to 255.255.255.255, the IP_MULTICAST_IF and SO_BROADCAST sockopts are enabled on the interface socket. IP_MULTICAST_IF is necessary to tell the kernel which interface a packet to 255.255.255.255 is supposed to come out. however, if the broadcast flag is not set, then the server is supposed to unicast the reply to the client. the problem with this is udp sockets will rely on ARP to know where to send that reply to, but the client hasn't got an address yet, so it wont reply to that arp request. the solution to this problem is i inject a route into the kernel with the ip to ethernet address mapping in it. because only a process that currently has root privs can add routes, ive made a stupid little helper process that proxies the route addtion. further requests to and replies from the dhcp server should go to and come from this interface ip respectively. however, unless i assign my "anycast" ip as the primary address on a supported interface, this doesnt help me. i'd have to create a vether interface with the anycast IP, but unless i enable ip forwarding i wont be able to receive these packets coming from the real interface im connected with. so i've tweaked the udpsock code to be more usable. part of it is changing the udpsock handling inside the guts of dhcpd so it is allowed to handle relayed requests (ie, giaddr in the request is != 0.0.0.0). the other tweaks were to udpsock itself, basically letting it use IP_SENDSRC cmsgs so if you have a wildcard listener it will do it's best to reply from the expected address. to support all the above i had to carry the addresses a messages was recv()ed with all the way through to where the relevant send()s are. this was more annoying than i thought cos some replies are deferred (thanks icmp). while i've tried to make dhcpd work the same as it did before this change, there is a big semantic difference that's outside it's control. bpf operated before pf, so you didn't have to write rules in pf.conf to allow dhcpd to work. because udp socket processing happens as part of the network stack, dhcp packets are now subject to pf. if you have a default deny ruleset, you have to explicitly allow dhcp packets in your ruleset. i'm using something like this at home: pass in on vport107 inet proto udp to { vport107:0 255.255.255.255 } port bootps pass out quick on vport107 inet proto udp from vport107:0 port bootps to port bootpc i've been using this in production as the backend for dhcp relays to talk to for a couple of weeks now, and at home with a bunch stupid devices talking directly to the udp sockets on the local net.
Followed by the code that implements the change, in a diff that will require a recent -current
checkout.
If any of this sounds familiar to you or you are simply feeling adventurous, this is a chance to test and report back any observations.
By n/a (Cabal) on
Hopefully dhcpleased in the future!
Reply