Download raw body.
dhcpd(8): use UDP sockets instead of BPF
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.
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/Makefile,v
diff -u -p -r1.7 Makefile
--- Makefile 12 Apr 2017 19:19:11 -0000 1.7
+++ Makefile 13 Jun 2025 03:27:14 -0000
@@ -2,7 +2,7 @@
.include <bsd.own.mk>
-SRCS= bootp.c confpars.c db.c dhcp.c dhcpd.c bpf.c packet.c log.c \
+SRCS= bootp.c confpars.c db.c dhcp.c dhcpd.c sockets.c packet.c log.c \
dispatch.c print.c memory.c options.c inet.c conflex.c parse.c \
alloc.c tables.c tree.c hash.c convert.c icmp.c pfutils.c sync.c \
udpsock.c
Index: bootp.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/bootp.c,v
diff -u -p -r1.18 bootp.c
--- bootp.c 13 Feb 2017 22:33:39 -0000 1.18
+++ bootp.c 13 Jun 2025 03:27:14 -0000
@@ -66,7 +66,6 @@ bootp(struct packet *packet)
struct packet outgoing;
struct dhcp_packet raw;
struct sockaddr_in to;
- struct in_addr from;
struct tree_cache *options[256];
struct shared_network *s;
struct subnet *subnet = NULL;
@@ -234,6 +233,8 @@ lose:
memset(&outgoing, 0, sizeof outgoing);
memset(&raw, 0, sizeof raw);
outgoing.raw = &raw;
+ outgoing.laddr = packet->laddr;
+ outgoing.raddr = packet->raddr;
/*
* If we didn't get a known vendor magic number on the way in, just
@@ -305,7 +306,7 @@ lose:
else if (subnet->interface_address.len)
memcpy(&raw.siaddr, subnet->interface_address.iabuf, 4);
else
- raw.siaddr = packet->interface->primary_address;
+ raw.siaddr = packet->interface->primary_address.sin_addr;
raw.giaddr = packet->raw->giaddr;
if (hp->group->server_name)
@@ -321,8 +322,6 @@ lose:
else
memcpy(raw.file, packet->raw->file, sizeof(raw.file));
- from = packet->interface->primary_address;
-
/* Report what we're doing... */
log_info("BOOTREPLY for %s to %s (%s) via %s", piaddr(ip_address),
hp->name, print_hw_addr(packet->raw->htype, packet->raw->hlen,
@@ -338,29 +337,26 @@ lose:
/* If this was gatewayed, send it back to the gateway... */
if (raw.giaddr.s_addr) {
+ to.sin_family = AF_INET;
to.sin_addr = raw.giaddr;
to.sin_port = server_port;
- (void) packet->interface->send_packet(packet->interface, &raw,
- outgoing.packet_length, from, &to, packet->haddr);
- return;
- }
-
/*
* If it comes from a client that already knows its address and is not
* requesting a broadcast response, and we can unicast to a client
* without using the ARP protocol, sent it directly to that client.
*/
- else if (!(raw.flags & htons(BOOTP_BROADCAST))) {
+ } else if (!(raw.flags & htons(BOOTP_BROADCAST))) {
+ to.sin_family = AF_INET;
to.sin_addr = raw.yiaddr;
to.sin_port = client_port;
} else {
/* Otherwise, broadcast it on the local network. */
- to.sin_addr.s_addr = INADDR_BROADCAST;
+ to.sin_family = AF_INET;
+ to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
to.sin_port = client_port; /* XXX */
}
errno = 0;
- (void) packet->interface->send_packet(packet->interface, &raw,
- outgoing.packet_length, from, &to, packet->haddr);
+ (void) packet->interface->send_packet(&outgoing, lease, &to);
}
Index: bpf.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/bpf.c,v
diff -u -p -r1.21 bpf.c
--- bpf.c 7 Feb 2025 21:48:26 -0000 1.21
+++ bpf.c 13 Jun 2025 03:27:14 -0000
@@ -1,381 +0,0 @@
-/* $OpenBSD: bpf.c,v 1.21 2025/02/07 21:48:26 bluhm Exp $ */
-
-/* BPF socket interface code, originally contributed by Archie Cobbs. */
-
-/*
- * Copyright (c) 1995, 1996, 1998, 1999
- * The Internet Software Consortium. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of The Internet Software Consortium nor the names
- * of its contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
- * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * This software has been written for the Internet Software Consortium
- * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
- * Enterprises. To learn more about the Internet Software Consortium,
- * see ``http://www.vix.com/isc''. To learn more about Vixie
- * Enterprises, see ``http://www.vix.com''.
- */
-
-#include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <arpa/inet.h>
-
-#include <net/bpf.h>
-#include <net/if.h>
-
-#include <netinet/if_ether.h>
-#include <netinet/in.h>
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "dhcp.h"
-#include "tree.h"
-#include "dhcpd.h"
-#include "log.h"
-
-ssize_t send_packet (struct interface_info *, struct dhcp_packet *,
- size_t, struct in_addr, struct sockaddr_in *, struct hardware *);
-
-/*
- * Called by get_interface_list for each interface that's discovered.
- * Opens a packet filter for each interface and adds it to the select
- * mask.
- */
-int
-if_register_bpf(struct interface_info *info)
-{
- int sock;
-
- if ((sock = open("/dev/bpf", O_RDWR)) == -1)
- fatal("Can't open bpf device");
-
- /* Set the BPF device to point at this interface. */
- if (ioctl(sock, BIOCSETIF, info->ifp) == -1)
- fatal("Can't attach interface %s to bpf device", info->name);
-
- info->send_packet = send_packet;
- return (sock);
-}
-
-void
-if_register_send(struct interface_info *info)
-{
- /*
- * If we're using the bpf API for sending and receiving, we
- * don't need to register this interface twice.
- */
- info->wfdesc = info->rfdesc;
-}
-
-/*
- * Packet read filter program: 'ip and udp and dst port bootps'
- */
-struct bpf_insn dhcp_bpf_filter[] = {
- /* Make sure this is an IP packet... */
- BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
-
- /* Make sure it's a UDP packet... */
- BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
-
- /* Make sure this isn't a fragment... */
- BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
- BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
-
- /* Get the IP header length... */
- BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
-
- /* Make sure it's to the right port... */
- BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SERVER_PORT, 0, 1),
-
- /* If we passed all the tests, ask for the whole packet. */
- BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
-
- /* Otherwise, drop it. */
- BPF_STMT(BPF_RET+BPF_K, 0),
-};
-
-int dhcp_bpf_filter_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn);
-
-
-/*
- * Packet write filter program:
- * 'ip and udp and src port bootps and dst port (bootps or bootpc)'
- */
-struct bpf_insn dhcp_bpf_wfilter[] = {
- /* Make sure this is an IP packet... */
- BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 11),
-
- /* Make sure it's a UDP packet... */
- BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9),
-
- /* Make sure this isn't a fragment... */
- BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
- BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0),
-
- /* Get the IP header length... */
- BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
-
- /* Make sure it's from the right port... */
- BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SERVER_PORT, 0, 4),
-
- /* Make sure it is to the right ports ... */
- BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_PORT, 1, 0),
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, SERVER_PORT, 0, 1),
-
- /* If we passed all the tests, ask for the whole packet. */
- BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
-
- /* Otherwise, drop it. */
- BPF_STMT(BPF_RET+BPF_K, 0),
-};
-
-int dhcp_bpf_wfilter_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn);
-
-void
-if_register_receive(struct interface_info *info)
-{
- struct bpf_version v;
- struct bpf_program p;
- int flag = 1, sz, cmplt = 0;
- int fildrop = BPF_FILDROP_CAPTURE;
-
- /* Open a BPF device and hang it on this interface... */
- info->rfdesc = if_register_bpf(info);
-
- /* Make sure the BPF version is in range... */
- if (ioctl(info->rfdesc, BIOCVERSION, &v) == -1)
- fatal("Can't get BPF version");
-
- if (v.bv_major != BPF_MAJOR_VERSION ||
- v.bv_minor < BPF_MINOR_VERSION)
- fatalx("Kernel BPF version out of range - recompile dhcpd!");
-
- /*
- * Set immediate mode so that reads return as soon as a packet
- * comes in, rather than waiting for the input buffer to fill
- * with packets.
- */
- if (ioctl(info->rfdesc, BIOCIMMEDIATE, &flag) == -1)
- fatal("Can't set immediate mode on bpf device");
-
- if (ioctl(info->rfdesc, BIOCSFILDROP, &fildrop) == -1)
- fatal("Can't set filter-drop mode on bpf device");
-
- /* make sure kernel fills in the source ethernet address */
- if (ioctl(info->rfdesc, BIOCSHDRCMPLT, &cmplt) == -1)
- fatal("Can't set header complete flag on bpf device");
-
- /* Get the required BPF buffer length from the kernel. */
- if (ioctl(info->rfdesc, BIOCGBLEN, &sz) == -1)
- fatal("Can't get bpf buffer length");
- info->rbuf_max = sz;
- info->rbuf = malloc(info->rbuf_max);
- if (!info->rbuf)
- fatalx("Can't allocate %lu bytes for bpf input buffer.",
- (unsigned long)info->rbuf_max);
- info->rbuf_offset = 0;
- info->rbuf_len = 0;
-
- /* Set up the bpf filter program structure. */
- p.bf_len = dhcp_bpf_filter_len;
- p.bf_insns = dhcp_bpf_filter;
-
- if (ioctl(info->rfdesc, BIOCSETF, &p) == -1)
- fatal("Can't install packet filter program");
-
- /* Set up the bpf write filter program structure. */
- p.bf_len = dhcp_bpf_wfilter_len;
- p.bf_insns = dhcp_bpf_wfilter;
-
- if (ioctl(info->rfdesc, BIOCSETWF, &p) == -1)
- fatal("Can't install write filter program");
-
- /* make sure these settings cannot be changed after dropping privs */
- if (ioctl(info->rfdesc, BIOCLOCK) == -1)
- fatal("Failed to lock bpf descriptor");
-}
-
-ssize_t
-send_packet(struct interface_info *interface, struct dhcp_packet *raw,
- size_t len, struct in_addr from, struct sockaddr_in *to,
- struct hardware *hto)
-{
- unsigned char buf[256];
- struct iovec iov[2];
- ssize_t result;
- int bufp = 0;
-
- /* Assemble the headers... */
- assemble_hw_header(interface, buf, &bufp, hto);
- assemble_udp_ip_header(interface, buf, &bufp, from.s_addr,
- to->sin_addr.s_addr, to->sin_port, (unsigned char *)raw, len);
-
- /* Fire it off */
- iov[0].iov_base = (char *)buf;
- iov[0].iov_len = bufp;
- iov[1].iov_base = (char *)raw;
- iov[1].iov_len = len;
-
- result = writev(interface->wfdesc, iov, 2);
- if (result == -1)
- log_warn("send_packet");
- return (result);
-}
-
-ssize_t
-receive_packet(struct interface_info *interface, unsigned char *buf,
- size_t len, struct sockaddr_in *from, struct hardware *hfrom)
-{
- int length = 0, offset = 0;
- struct bpf_hdr hdr;
-
- /*
- * All this complexity is because BPF doesn't guarantee that
- * only one packet will be returned at a time. We're getting
- * what we deserve, though - this is a terrible abuse of the BPF
- * interface. Sigh.
- */
-
- /* Process packets until we get one we can return or until we've
- * done a read and gotten nothing we can return...
- */
- do {
- /* If the buffer is empty, fill it. */
- if (interface->rbuf_offset >= interface->rbuf_len) {
- length = read(interface->rfdesc, interface->rbuf,
- interface->rbuf_max);
- if (length <= 0)
- return (length);
- interface->rbuf_offset = 0;
- interface->rbuf_len = length;
- }
-
- /*
- * If there isn't room for a whole bpf header, something
- * went wrong, but we'll ignore it and hope it goes
- * away... XXX
- */
- if (interface->rbuf_len - interface->rbuf_offset <
- sizeof(hdr)) {
- interface->rbuf_offset = interface->rbuf_len;
- continue;
- }
-
- /* Copy out a bpf header... */
- memcpy(&hdr, &interface->rbuf[interface->rbuf_offset],
- sizeof(hdr));
-
- /*
- * If the bpf header plus data doesn't fit in what's
- * left of the buffer, stick head in sand yet again...
- */
- if (interface->rbuf_offset + hdr.bh_hdrlen + hdr.bh_caplen >
- interface->rbuf_len) {
- interface->rbuf_offset = interface->rbuf_len;
- continue;
- }
-
- /*
- * If the captured data wasn't the whole packet, or if
- * the packet won't fit in the input buffer, all we can
- * do is drop it.
- */
- if (hdr.bh_caplen != hdr.bh_datalen) {
- interface->rbuf_offset = BPF_WORDALIGN(
- interface->rbuf_offset + hdr.bh_hdrlen +
- hdr.bh_caplen);
- continue;
- }
-
- /* Skip over the BPF header... */
- interface->rbuf_offset += hdr.bh_hdrlen;
-
- /* Decode the physical header... */
- offset = decode_hw_header(interface->rbuf +
- interface->rbuf_offset, hdr.bh_caplen, hfrom);
-
- /*
- * If a physical layer checksum failed (dunno of any
- * physical layer that supports this, but WTH), skip
- * this packet.
- */
- if (offset < 0) {
- interface->rbuf_offset = BPF_WORDALIGN(
- interface->rbuf_offset + hdr.bh_caplen);
- continue;
- }
- interface->rbuf_offset += offset;
- hdr.bh_caplen -= offset;
-
- /* Decode the IP and UDP headers... */
- offset = decode_udp_ip_header(interface->rbuf +
- interface->rbuf_offset, hdr.bh_caplen, from,
- hdr.bh_csumflags);
-
- /* If the IP or UDP checksum was bad, skip the packet... */
- if (offset < 0) {
- interface->rbuf_offset = BPF_WORDALIGN(
- interface->rbuf_offset + hdr.bh_caplen);
- continue;
- }
- interface->rbuf_offset += offset;
- hdr.bh_caplen -= offset;
-
- /*
- * If there's not enough room to stash the packet data,
- * we have to skip it (this shouldn't happen in real
- * life, though).
- */
- if (hdr.bh_caplen > len) {
- interface->rbuf_offset = BPF_WORDALIGN(
- interface->rbuf_offset + hdr.bh_caplen);
- continue;
- }
-
- /* Copy out the data in the packet... */
- memcpy(buf, interface->rbuf + interface->rbuf_offset,
- hdr.bh_caplen);
- interface->rbuf_offset = BPF_WORDALIGN(interface->rbuf_offset +
- hdr.bh_caplen);
- return (hdr.bh_caplen);
- } while (!length);
- return (0);
-}
Index: dhcp.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/dhcp.c,v
diff -u -p -r1.57 dhcp.c
--- dhcp.c 11 Jul 2017 10:28:24 -0000 1.57
+++ dhcp.c 13 Jun 2025 03:27:14 -0000
@@ -68,9 +68,8 @@ dhcp(struct packet *packet, int is_udpso
if (!locate_network(packet) && packet->packet_type != DHCPREQUEST)
return;
- if (is_udpsock && packet->packet_type != DHCPINFORM) {
- log_info("Unable to handle a DHCP message type=%d on UDP "
- "socket", packet->packet_type);
+ if (is_udpsock && packet->packet_type == DHCPDISCOVER) {
+ log_info("Unable to handle a DHCPDISCOVER on UDP socket");
return;
}
@@ -529,16 +528,16 @@ dhcpinform(struct packet *packet)
cip.len = 4;
if (packet->raw->ciaddr.s_addr && !packet->raw->giaddr.s_addr) {
if (memcmp(&packet->raw->ciaddr.s_addr,
- packet->client_addr.iabuf, 4) != 0) {
+ &packet->raddr.sin_addr.s_addr, 4) != 0) {
log_info("DHCPINFORM from %s but ciaddr %s is not "
"consistent with actual address",
- piaddr(packet->client_addr),
+ saname(&packet->raddr),
inet_ntoa(packet->raw->ciaddr));
return;
}
memcpy(cip.iabuf, &packet->raw->ciaddr.s_addr, 4);
} else
- memcpy(cip.iabuf, &packet->client_addr.iabuf, 4);
+ memcpy(cip.iabuf, &packet->raddr.sin_addr.s_addr, 4);
log_info("DHCPINFORM from %s", piaddr(cip));
@@ -575,8 +574,6 @@ void
nak_lease(struct packet *packet, struct iaddr *cip)
{
struct sockaddr_in to;
- struct in_addr from;
- ssize_t result;
int i;
struct dhcp_packet raw;
unsigned char nak = DHCPNAK;
@@ -589,6 +586,9 @@ nak_lease(struct packet *packet, struct
memset(&outgoing, 0, sizeof outgoing);
memset(&raw, 0, sizeof raw);
outgoing.raw = &raw;
+ outgoing.laddr = packet->laddr;
+ outgoing.raddr = packet->raddr;
+ outgoing.interface = packet->interface;
/* Set DHCP_MESSAGE_TYPE to DHCPNAK */
i = DHO_DHCP_MESSAGE_TYPE;
@@ -649,7 +649,7 @@ nak_lease(struct packet *packet, struct
0, options, 0, 0, 0, NULL, 0);
/* memset(&raw.ciaddr, 0, sizeof raw.ciaddr);*/
- raw.siaddr = packet->interface->primary_address;
+ raw.siaddr = packet->interface->primary_address.sin_addr;
raw.giaddr = packet->raw->giaddr;
memcpy(raw.chaddr, packet->raw->chaddr, sizeof raw.chaddr);
raw.hlen = packet->raw->hlen;
@@ -666,17 +666,15 @@ nak_lease(struct packet *packet, struct
packet->raw->chaddr), packet->raw->giaddr.s_addr ?
inet_ntoa(packet->raw->giaddr) : packet->interface->name);
+ /* Make sure that the packet is at least as big as a BOOTP packet. */
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
+
/* Set up the common stuff... */
memset(&to, 0, sizeof to);
to.sin_family = AF_INET;
to.sin_len = sizeof to;
- from = packet->interface->primary_address;
-
- /* Make sure that the packet is at least as big as a BOOTP packet. */
- if (outgoing.packet_length < BOOTP_MIN_LEN)
- outgoing.packet_length = BOOTP_MIN_LEN;
-
/*
* If this was gatewayed, send it back to the gateway.
* Otherwise, broadcast it on the local network.
@@ -684,20 +682,13 @@ nak_lease(struct packet *packet, struct
if (raw.giaddr.s_addr) {
to.sin_addr = raw.giaddr;
to.sin_port = server_port;
-
- result = packet->interface->send_packet(packet->interface, &raw,
- outgoing.packet_length, from, &to, packet->haddr);
- if (result == -1)
- log_warn("send_fallback");
- return;
} else {
to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
to.sin_port = client_port;
}
errno = 0;
- result = packet->interface->send_packet(packet->interface, &raw,
- outgoing.packet_length, from, &to, NULL);
+ packet->interface->send_packet(&outgoing, NULL, &to);
}
void
@@ -760,6 +751,8 @@ ack_lease(struct packet *packet, struct
memset(state, 0, sizeof *state);
state->got_requested_address = packet->got_requested_address;
state->shared_network = packet->interface->shared_network;
+ state->laddr = packet->laddr;
+ state->raddr = packet->raddr;
/* Remember if we got a server identifier option. */
i = DHO_DHCP_SERVER_IDENTIFIER;
@@ -980,7 +973,6 @@ ack_lease(struct packet *packet, struct
state->bootp_flags = packet->raw->flags;
state->hops = packet->raw->hops;
state->offer = offer;
- memcpy(&state->haddr, packet->haddr, sizeof state->haddr);
/* Figure out what options to send to the client: */
@@ -1070,32 +1062,35 @@ ack_lease(struct packet *packet, struct
state->options[i]->tree = NULL;
i = DHO_DHCP_SERVER_IDENTIFIER;
- if (!state->options[i]) {
- use_primary:
+ if (!state->options[i] ||
+ /* Find the value of the server identifier... */
+ !tree_evaluate(state->options[i]) ||
+ !state->options[i]->value ||
+ (state->options[i]->len >
+ sizeof state->from.iabuf)) {
+ struct sockaddr_in *sin =
+ &state->ip->primary_address;
state->options[i] = new_tree_cache("server-id");
state->options[i]->value =
- (unsigned char *)&state->ip->primary_address;
- state->options[i]->len =
- sizeof state->ip->primary_address;
+ (unsigned char *)&sin->sin_addr.s_addr;
+ state->options[i]->len = sizeof(sin->sin_addr.s_addr);
state->options[i]->buf_size = state->options[i]->len;
state->options[i]->timeout = -1;
state->options[i]->tree = NULL;
+#if 0
state->from.len = sizeof state->ip->primary_address;
memcpy(state->from.iabuf, &state->ip->primary_address,
state->from.len);
} else {
- /* Find the value of the server identifier... */
- if (!tree_evaluate(state->options[i]))
- goto use_primary;
- if (!state->options[i]->value ||
- (state->options[i]->len >
- sizeof state->from.iabuf))
- goto use_primary;
-
state->from.len = state->options[i]->len;
memcpy(state->from.iabuf, state->options[i]->value,
state->from.len);
+#endif
}
+ state->from.len = sizeof state->ip->primary_address.sin_addr;
+ memcpy(state->from.iabuf, &state->ip->primary_address.sin_addr,
+ state->from.len);
+
/*
* Do not ACK a REQUEST intended for another server.
*/
@@ -1279,10 +1274,10 @@ void
dhcp_reply(struct lease *lease)
{
char ciaddrbuf[INET_ADDRSTRLEN];
- int bufs = 0, packet_length, i;
+ int bufs = 0, i;
struct dhcp_packet raw;
+ struct packet outgoing;
struct sockaddr_in to;
- struct in_addr from;
struct lease_state *state = lease->state;
int nulltp, bootpp;
u_int8_t *prl;
@@ -1292,7 +1287,12 @@ dhcp_reply(struct lease *lease)
fatalx("dhcp_reply was supplied lease with no state!");
/* Compose a response for the client... */
+ memset(&outgoing, 0, sizeof outgoing);
memset(&raw, 0, sizeof raw);
+ outgoing.raw = &raw;
+ outgoing.laddr = state->laddr;
+ outgoing.raddr = state->raddr;
+ outgoing.interface = state->ip;
/* Copy in the filename if given; otherwise, flag the filename
buffer as available for options. */
@@ -1338,8 +1338,9 @@ dhcp_reply(struct lease *lease)
}
/* Insert such options as will fit into the buffer. */
- packet_length = cons_options(NULL, &raw, state->max_message_size,
- state->options, bufs, nulltp, bootpp, prl, prl_len);
+ outgoing.packet_length = cons_options(NULL, &raw,
+ state->max_message_size, state->options,
+ bufs, nulltp, bootpp, prl, prl_len);
/* Having done the cons_options(), we can release the tree_cache
entries. */
@@ -1362,7 +1363,7 @@ dhcp_reply(struct lease *lease)
else if (lease->subnet->interface_address.len)
memcpy(&raw.siaddr, lease->subnet->interface_address.iabuf, 4);
else
- raw.siaddr = state->ip->primary_address;
+ raw.siaddr = state->ip->primary_address.sin_addr;
raw.giaddr = state->giaddr;
@@ -1401,18 +1402,15 @@ dhcp_reply(struct lease *lease)
/* Make sure outgoing packets are at least as big
as a BOOTP packet. */
- if (packet_length < BOOTP_MIN_LEN)
- packet_length = BOOTP_MIN_LEN;
+ if (outgoing.packet_length < BOOTP_MIN_LEN)
+ outgoing.packet_length = BOOTP_MIN_LEN;
/* If this was gatewayed, send it back to the gateway... */
if (raw.giaddr.s_addr) {
to.sin_addr = raw.giaddr;
to.sin_port = server_port;
- memcpy(&from, state->from.iabuf, sizeof from);
-
- (void) state->ip->send_packet(state->ip, &raw,
- packet_length, from, &to, &state->haddr);
+ (void) state->ip->send_packet(&outgoing, lease, &to);
free_lease_state(state, "dhcp_reply gateway");
lease->state = NULL;
@@ -1450,13 +1448,9 @@ dhcp_reply(struct lease *lease)
} else {
to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
to.sin_port = client_port;
- memset(&state->haddr, 0xff, sizeof state->haddr);
}
- memcpy(&from, state->from.iabuf, sizeof from);
-
- (void) state->ip->send_packet(state->ip, &raw, packet_length,
- from, &to, &state->haddr);
+ (void) state->ip->send_packet(&outgoing, lease, &to);
free_lease_state(state, "dhcp_reply");
lease->state = NULL;
Index: dhcpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/dhcpd.c,v
diff -u -p -r1.62 dhcpd.c
--- dhcpd.c 10 Jun 2025 06:29:53 -0000 1.62
+++ dhcpd.c 13 Jun 2025 03:27:14 -0000
@@ -179,6 +179,7 @@ main(int argc, char *argv[])
if (!tmp)
fatalx("calloc");
strlcpy(tmp->name, argv[0], sizeof(tmp->name));
+ tmp->primary_address.sin_family = AF_UNSPEC;
tmp->next = interfaces;
interfaces = tmp;
argc--;
@@ -199,7 +200,7 @@ main(int argc, char *argv[])
exit(0);
db_startup();
- if (!udpsockmode || argc > 0)
+ if (!udpsockmode || interfaces != NULL)
discover_interfaces();
if (syncsend || syncrecv) {
@@ -245,10 +246,20 @@ main(int argc, char *argv[])
icmp_startup(1, lease_pinged);
+ if (interfaces != NULL) {
+ /* do we need a listener for 255.255.255.255? */
+ if (!udpsockmode ||
+ (udpaddr.s_addr != htonl(INADDR_BROADCAST) &&
+ udpaddr.s_addr != htonl(INADDR_ANY)))
+ broadcast_interface();
+
+ lladdr_helper(pw); /* this will fatal if it has trouble */
+ }
+
if (chroot(pw->pw_dir) == -1)
fatal("chroot %s", pw->pw_dir);
if (chdir("/") == -1)
- fatal("chdir(\"/\")");
+ fatal("chdir %s", pw->pw_dir);
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
@@ -266,7 +277,7 @@ main(int argc, char *argv[])
dispatch();
/* not reached */
- exit(0);
+ return (0);
}
__dead void
Index: dhcpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/dhcpd.h,v
diff -u -p -r1.73 dhcpd.h
--- dhcpd.h 10 Jun 2025 06:29:53 -0000 1.73
+++ dhcpd.h 13 Jun 2025 03:27:14 -0000
@@ -38,6 +38,33 @@
* Enterprises, see ``http://www.vix.com''.
*/
+#ifndef ISSET
+#define ISSET(_v, _m) ((_v) & (_m))
+#endif
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#define CMSG_FOREACH(_cmsg, _msgp) \
+ for ((_cmsg) = CMSG_FIRSTHDR((_msgp)); \
+ (_cmsg) != NULL; \
+ (_cmsg) = CMSG_NXTHDR((_msgp), (_cmsg)))
+
+static inline int
+cmsg_match(const struct cmsghdr *cmsg, size_t len, int level, int type)
+{
+ return (cmsg->cmsg_len == CMSG_LEN(len) &&
+ cmsg->cmsg_level == level &&
+ cmsg->cmsg_type == type);
+}
+
+#define CMSG_MATCH(_cmsg, _len, _level, _type) \
+ cmsg_match((_cmsg), (_len), (_level), (_type))
+
+struct passwd;
+struct sockaddr_dl;
+
#define ifr_netmask ifr_addr
#define HAVE_SA_LEN
@@ -79,12 +106,11 @@ struct packet {
int packet_length;
int packet_type;
int options_valid;
- int client_port;
- struct iaddr client_addr;
+ struct sockaddr_in raddr;
+ struct sockaddr_in laddr;
+ time_t expiry;
struct interface_info *interface; /* Interface on which packet
was received. */
- struct hardware *haddr; /* Physical link address
- of local sender (maybe gateway). */
struct shared_network *shared_network;
struct option_data options[256];
int got_requested_address; /* True if client sent the
@@ -134,6 +160,8 @@ struct lease {
};
struct lease_state {
+ struct lease_state *next;
+
struct interface_info *ip;
time_t offered_expiry;
@@ -144,6 +172,8 @@ struct lease_state {
char *server_name;
struct iaddr from;
+ struct sockaddr_in raddr;
+ struct sockaddr_in laddr;
int max_message_size;
u_int8_t *prl;
@@ -162,7 +192,6 @@ struct lease_state {
struct in_addr giaddr;
u_int8_t hops;
u_int8_t offer;
- struct hardware haddr;
};
#define ROOT_GROUP 0
@@ -254,7 +283,7 @@ struct interface_info {
struct shared_network *shared_network;
/* Networks connected to this interface. */
struct hardware hw_address; /* Its physical address. */
- struct in_addr primary_address; /* Primary interface address. */
+ struct sockaddr_in primary_address; /* Primary interface address. */
char name[IFNAMSIZ]; /* Its name... */
int rfdesc; /* Its read file descriptor. */
int wfdesc; /* Its write file descriptor, if
@@ -264,15 +293,13 @@ struct interface_info {
size_t rbuf_offset; /* Current offset into buffer. */
size_t rbuf_len; /* Length of data in buffer. */
- struct ifreq *ifp; /* Pointer to ifreq struct. */
-
int noifmedia;
int errors;
int dead;
u_int16_t index;
int is_udpsock;
- ssize_t (*send_packet)(struct interface_info *, struct dhcp_packet *,
- size_t, struct in_addr, struct sockaddr_in *, struct hardware *);
+ ssize_t (*send_packet)(struct packet *, struct lease *,
+ const struct sockaddr_in *);
};
struct dhcpd_timeout {
@@ -311,7 +338,8 @@ void parse_option_buffer(struct packet
int cons_options(struct packet *, struct dhcp_packet *, int,
struct tree_cache **, int, int, int, u_int8_t *, int);
void do_packet(struct interface_info *, struct dhcp_packet *, int,
- unsigned int, struct iaddr, struct hardware *);
+ const struct sockaddr_in *, const struct sockaddr_in *,
+ const struct sockaddr_dl *);
/* dhcpd.c */
extern time_t cur_time;
@@ -433,17 +461,17 @@ void free_tree_cache(struct tree_cache *
char *print_hw_addr(int, int, unsigned char *);
/* bpf.c */
-int if_register_bpf(struct interface_info *);
void if_register_send(struct interface_info *);
void if_register_receive(struct interface_info *);
ssize_t receive_packet(struct interface_info *, unsigned char *, size_t,
- struct sockaddr_in *, struct hardware *);
+ struct sockaddr_in *, struct sockaddr_in *, struct sockaddr_dl *);
/* dispatch.c */
extern struct interface_info *interfaces;
extern struct protocol *protocols;
extern struct dhcpd_timeout *timeouts;
void discover_interfaces(void);
+void broadcast_interface(void);
void dispatch(void);
int locate_network(struct packet *);
void got_one(struct protocol *);
@@ -480,6 +508,7 @@ struct iaddr ip_addr(struct iaddr, struc
u_int32_t host_addr(struct iaddr, struct iaddr);
int addr_eq(struct iaddr, struct iaddr);
char *piaddr(struct iaddr);
+const char *saname(const struct sockaddr_in *);
/* db.c */
int write_lease(struct lease *);
@@ -493,8 +522,8 @@ void assemble_hw_header(struct interface
void assemble_udp_ip_header(struct interface_info *, unsigned char *,
int *, u_int32_t, u_int32_t, unsigned int, unsigned char *, int);
ssize_t decode_hw_header(unsigned char *, u_int32_t, struct hardware *);
-ssize_t decode_udp_ip_header(unsigned char *, u_int32_t, struct sockaddr_in *,
- u_int16_t);
+ssize_t decode_udp_ip_header(unsigned char *, u_int32_t,
+ struct sockaddr_in *, struct sockaddr_in *, u_int16_t);
u_int32_t checksum(unsigned char *, u_int32_t, u_int32_t);
u_int32_t wrapsum(u_int32_t);
@@ -513,3 +542,7 @@ void pfmsg(char, struct lease *);
/* udpsock.c */
void udpsock_startup(struct in_addr);
+
+/* sockets.c */
+void lladdr_helper(const struct passwd *);
+void lladdr_del(struct lease *);
Index: dispatch.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/dispatch.c,v
diff -u -p -r1.50 dispatch.c
--- dispatch.c 10 Jun 2025 06:29:53 -0000 1.50
+++ dispatch.c 13 Jun 2025 03:27:14 -0000
@@ -47,6 +47,7 @@
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
+#include <net/if_types.h>
#include <netinet/in.h>
@@ -65,7 +66,6 @@
#include "tree.h"
#include "dhcpd.h"
#include "log.h"
-#include "sync.h"
extern int rdomain;
@@ -75,7 +75,8 @@ struct dhcpd_timeout *timeouts;
static int interfaces_invalidated;
static int interface_status(struct interface_info *ifinfo);
-int get_rdomain(char *);
+
+void got_bcast(struct protocol *);
/* Use getifaddrs() to get a list of all the attached interfaces.
For each interface that's of type INET and not the loopback interface,
@@ -89,9 +90,8 @@ discover_interfaces(void)
struct interface_info *last, *next;
struct subnet *subnet;
struct shared_network *share;
- struct sockaddr_in foo;
+ struct sockaddr_in *sin;
int ir;
- struct ifreq *tif;
struct ifaddrs *ifap, *ifa;
if (getifaddrs(&ifap) != 0)
@@ -105,15 +105,6 @@ discover_interfaces(void)
/* Cycle through the list of interfaces looking for IP addresses. */
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
- /*
- * See if this is the sort of interface we want to
- * deal with.
- */
- if ((ifa->ifa_flags & IFF_LOOPBACK) ||
- (ifa->ifa_flags & IFF_POINTOPOINT) ||
- (!(ifa->ifa_flags & IFF_BROADCAST)))
- continue;
-
/* See if we've seen an interface that matches this one. */
for (tmp = interfaces; tmp; tmp = tmp->next)
if (!strcmp(tmp->name, ifa->ifa_name))
@@ -142,37 +133,43 @@ discover_interfaces(void)
struct if_data *ifi = ifa->ifa_data;
struct sockaddr_dl *sdl;
- if (rdomain != ifi->ifi_rdomain)
+ /*
+ * See if this is the sort of interface we want to
+ * deal with.
+ */
+ if (ifi->ifi_type != IFT_ETHER)
+ continue;
+
+ if (rdomain != ifi->ifi_rdomain) {
+ if (ir) {
+ fatalx("Interface %s "
+ "is not in rdomain %d",
+ tmp->name, rdomain);
+ }
continue;
+ }
sdl = (struct sockaddr_dl *)ifa->ifa_addr;
tmp->index = sdl->sdl_index;
tmp->hw_address.hlen = sdl->sdl_alen;
- tmp->hw_address.htype = HTYPE_ETHER; /* XXX */
+ tmp->hw_address.htype = HTYPE_ETHER;
memcpy(tmp->hw_address.haddr,
LLADDR(sdl), sdl->sdl_alen);
} else if (ifa->ifa_addr->sa_family == AF_INET) {
/* Get a pointer to the address... */
- memcpy(&foo, ifa->ifa_addr, sizeof(foo));
+ sin = (struct sockaddr_in *)ifa->ifa_addr;
/* We don't want the loopback interface. */
- if (foo.sin_addr.s_addr == htonl (INADDR_LOOPBACK))
+ if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK) ||
+ sin->sin_addr.s_addr == htonl(INADDR_ANY))
continue;
/* If this is the first real IP address we've
found, keep a pointer to ifreq structure in
which we found it. */
- if (!tmp->ifp) {
- int len = (IFNAMSIZ + ifa->ifa_addr->sa_len);
- tif = malloc(len);
- if (!tif)
- fatalx("no space to remember ifp.");
- strlcpy(tif->ifr_name, ifa->ifa_name,
- IFNAMSIZ);
- memcpy(&tif->ifr_addr, ifa->ifa_addr,
- ifa->ifa_addr->sa_len);
- tmp->ifp = tif;
- tmp->primary_address = foo.sin_addr;
+ if (tmp->primary_address.sin_family == AF_UNSPEC) {
+ tmp->primary_address = *sin;
+ tmp->primary_address.sin_port = server_port;
}
}
}
@@ -181,11 +178,10 @@ discover_interfaces(void)
last = NULL;
for (tmp = interfaces; tmp; tmp = next) {
struct iaddr addr;
-
next = tmp->next;
if (tmp->index == 0) {
- log_warnx("Can't listen on %s - wrong rdomain",
+ log_warnx("Can't listen on %s - wrong type or rdomain.",
tmp->name);
/* Remove tmp from the list of interfaces. */
if (!last)
@@ -195,7 +191,8 @@ discover_interfaces(void)
continue;
}
- if (!tmp->ifp) {
+ sin = &tmp->primary_address;
+ if (sin->sin_family == AF_UNSPEC) {
log_warnx("Can't listen on %s - it has no IP address.",
tmp->name);
/* Remove tmp from the list of interfaces. */
@@ -206,11 +203,9 @@ discover_interfaces(void)
continue;
}
- memcpy(&foo, &tmp->ifp->ifr_addr, sizeof tmp->ifp->ifr_addr);
-
/* Grab the address... */
addr.len = 4;
- memcpy(addr.iabuf, &foo.sin_addr.s_addr, addr.len);
+ memcpy(addr.iabuf, &sin->sin_addr.s_addr, addr.len);
/* If there's a registered subnet for this address,
connect it together... */
@@ -252,7 +247,7 @@ discover_interfaces(void)
if (!tmp->shared_network) {
log_warnx("Can't listen on %s - dhcpd.conf has no "
"subnet declaration for %s.", tmp->name,
- inet_ntoa(foo.sin_addr));
+ inet_ntoa(sin->sin_addr));
/* Remove tmp from the list of interfaces. */
if (!last)
interfaces = interfaces->next;
@@ -274,7 +269,7 @@ discover_interfaces(void)
*/
subnet->interface_address.len = 4;
memcpy(subnet->interface_address.iabuf,
- &foo.sin_addr.s_addr, 4);
+ &sin->sin_addr.s_addr, 4);
}
}
@@ -282,7 +277,7 @@ discover_interfaces(void)
if_register_receive(tmp);
if_register_send(tmp);
log_info("Listening on %s (%s).", tmp->name,
- inet_ntoa(foo.sin_addr));
+ inet_ntoa(sin->sin_addr));
}
if (interfaces == NULL)
@@ -295,6 +290,30 @@ discover_interfaces(void)
freeifaddrs(ifap);
}
+void
+broadcast_interface(void)
+{
+ struct interface_info *bif;
+
+ /* Create a listener for packets to the broadcast address */
+ bif = calloc(1, sizeof(*bif));
+ if (bif == NULL)
+ fatalx("Insufficient memory for broadcast interface");
+
+ strlcpy(bif->name, "broadcast", sizeof(bif->name));
+ bif->primary_address.sin_family = AF_INET;
+ bif->primary_address.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+ bif->primary_address.sin_port = server_port;
+
+ if_register_receive(bif);
+ if_register_send(bif);
+
+ if (shutdown(bif->wfdesc, SHUT_WR) == -1)
+ log_warn("broadcast write shutdown");
+
+ add_protocol(bif->name, bif->rfdesc, got_bcast, bif);
+}
+
/*
* Wait for packets to come in using poll(). When a packet comes in,
* call receive_packet to receive the packet and possibly strip hardware
@@ -308,7 +327,6 @@ dispatch(void)
static struct pollfd *fds;
static int nfds_max;
time_t howlong;
- int nifaces;
for (nfds = 0, l = protocols; l; l = l->next)
nfds++;
@@ -350,15 +368,13 @@ another:
to_msec = -1;
/* Set up the descriptors to be polled. */
- nifaces = 0;
for (i = 0, l = protocols; l; l = l->next) {
if (l->handler == got_one) {
struct interface_info *ip = l->local;
if (ip->dead) {
l->pfd = -1;
continue;
- } else
- nifaces++;
+ }
}
fds[i].fd = l->fd;
@@ -366,9 +382,6 @@ another:
l->pfd = i++;
}
- if (nifaces == 0)
- fatalx("No live interfaces to poll on - exiting.");
-
/* Wait for a packet or a timeout... */
switch (poll(fds, nfds, to_msec)) {
case -1:
@@ -392,13 +405,55 @@ another:
}
}
+void
+got_bcast(struct protocol *l)
+{
+ struct sockaddr_in src = { .sin_family = AF_INET };
+ struct sockaddr_in dst = { .sin_family = AF_INET };
+ struct sockaddr_dl sdl = { .sdl_family = AF_UNSPEC };
+ ssize_t result;
+ union {
+ unsigned char packbuf[4095];
+ struct dhcp_packet packet;
+ } u;
+ struct interface_info *ip = l->local;
+
+ memset(&u, 0, sizeof(u));
+
+ result = receive_packet(ip, u.packbuf, sizeof(u),
+ &src, &dst, &sdl);
+ if (result == -1) {
+ log_warn("broadcast receive_packet failed");
+ return;
+ }
+ if (result == 0)
+ return;
+
+ /*
+ * We need the RECVIF information to figure out where it
+ * actually came from.
+ */
+ if (sdl.sdl_family == AF_UNSPEC || sdl.sdl_index == 0)
+ return;
+
+ for (ip = interfaces; ip != NULL; ip = ip->next) {
+ if (ip->index == sdl.sdl_index)
+ break;
+ }
+ /* drop broadcast packet from unknown interface */
+ if (ip == NULL) {
+ return;
+ }
+
+ do_packet(ip, &u.packet, result, &src, &dst, &sdl);
+}
void
got_one(struct protocol *l)
{
- struct sockaddr_in from;
- struct hardware hfrom;
- struct iaddr ifrom;
+ struct sockaddr_in src = { .sin_family = AF_INET };
+ struct sockaddr_in dst = { .sin_family = AF_INET };
+ struct sockaddr_dl sdl = { .sdl_family = AF_UNSPEC };
ssize_t result;
union {
unsigned char packbuf[4095];
@@ -408,8 +463,8 @@ got_one(struct protocol *l)
memset(&u, 0, sizeof(u));
- if ((result = receive_packet(ip, u.packbuf, sizeof u,
- &from, &hfrom)) == -1) {
+ if ((result = receive_packet(ip, u.packbuf, sizeof(u),
+ &src, &dst, &sdl)) == -1) {
log_warn("receive_packet failed on %s", ip->name);
ip->errors++;
if ((!interface_status(ip)) ||
@@ -428,10 +483,7 @@ got_one(struct protocol *l)
if (result == 0)
return;
- ifrom.len = 4;
- memcpy(ifrom.iabuf, &from.sin_addr, ifrom.len);
-
- do_packet(ip, &u.packet, result, from.sin_port, ifrom, &hfrom);
+ do_packet(ip, &u.packet, result, &src, &dst, &sdl);
}
int
Index: inet.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/inet.c,v
diff -u -p -r1.6 inet.c
--- inet.c 6 Feb 2016 23:50:10 -0000 1.6
+++ inet.c 13 Jun 2025 03:27:14 -0000
@@ -166,3 +166,15 @@ piaddr(struct iaddr addr)
}
return (pbuf);
}
+
+const char *
+saname(const struct sockaddr_in *sin)
+{
+ static char pbuf[32];
+
+ if (inet_ntop(AF_INET, &sin->sin_addr.s_addr,
+ pbuf, sizeof(pbuf)) == NULL)
+ return ("<invalid address>");
+
+ return (pbuf);
+}
Index: memory.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/memory.c,v
diff -u -p -r1.31 memory.c
--- memory.c 28 Jan 2022 06:33:27 -0000 1.31
+++ memory.c 13 Jun 2025 03:27:14 -0000
@@ -644,6 +644,7 @@ release_lease(struct lease *lease)
lt = *lease;
if (lt.ends > cur_time) {
lt.ends = cur_time;
+ lladdr_del(lease);
supersede_lease(lease, <, 1);
log_info("Released lease for IP address %s",
piaddr(lease->ip_addr));
@@ -662,6 +663,8 @@ abandon_lease(struct lease *lease, char
{
struct lease lt;
time_t abtime;
+
+ lladdr_del(lease);
abtime = lease->subnet->group->default_lease_time;
lease->flags |= ABANDONED_LEASE;
Index: options.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/options.c,v
diff -u -p -r1.35 options.c
--- options.c 13 Feb 2017 22:33:39 -0000 1.35
+++ options.c 13 Jun 2025 03:27:14 -0000
@@ -527,8 +527,10 @@ zapfrags:
}
void
-do_packet(struct interface_info *interface, struct dhcp_packet *packet,
- int len, unsigned int from_port, struct iaddr from, struct hardware *hfrom)
+do_packet(struct interface_info *interface,
+ struct dhcp_packet *packet, int len,
+ const struct sockaddr_in *raddr, const struct sockaddr_in *laddr,
+ const struct sockaddr_dl *sdl)
{
struct packet tp;
int i;
@@ -541,10 +543,9 @@ do_packet(struct interface_info *interfa
memset(&tp, 0, sizeof(tp));
tp.raw = packet;
tp.packet_length = len;
- tp.client_port = from_port;
- tp.client_addr = from;
+ tp.raddr = *raddr;
+ tp.laddr = *laddr;
tp.interface = interface;
- tp.haddr = hfrom;
parse_options(&tp);
if (tp.options_valid &&
Index: packet.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/packet.c,v
diff -u -p -r1.15 packet.c
--- packet.c 7 Feb 2025 21:48:26 -0000 1.15
+++ packet.c 13 Jun 2025 03:27:14 -0000
@@ -169,7 +169,7 @@ decode_hw_header(unsigned char *buf, u_i
ssize_t
decode_udp_ip_header(unsigned char *buf, u_int32_t buflen,
- struct sockaddr_in *from, u_int16_t csumflags)
+ struct sockaddr_in *src, struct sockaddr_in *dst, u_int16_t csumflags)
{
struct ip *ip;
struct udphdr *udp;
@@ -206,7 +206,8 @@ decode_udp_ip_header(unsigned char *buf,
return (-1);
}
- memcpy(&from->sin_addr, &ip->ip_src, sizeof(from->sin_addr));
+ memcpy(&src->sin_addr, &ip->ip_src, sizeof(src->sin_addr));
+ memcpy(&dst->sin_addr, &ip->ip_dst, sizeof(dst->sin_addr));
#ifdef DEBUG
if (ntohs(ip->ip_len) != buflen)
@@ -276,7 +277,8 @@ decode_udp_ip_header(unsigned char *buf,
}
}
- memcpy(&from->sin_port, &udp->uh_sport, sizeof(udp->uh_sport));
+ memcpy(&src->sin_port, &udp->uh_sport, sizeof(src->sin_port));
+ memcpy(&dst->sin_port, &udp->uh_sport, sizeof(dst->sin_port));
return (ip_len + sizeof(*udp));
}
Index: sockets.c
===================================================================
RCS file: sockets.c
diff -N sockets.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ sockets.c 13 Jun 2025 03:27:14 -0000
@@ -0,0 +1,563 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2025 The University of Queensland
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+
+#include <arpa/inet.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <netinet/in.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <pwd.h>
+
+#include "dhcp.h"
+#include "tree.h"
+#include "dhcpd.h"
+#include "log.h"
+
+extern int rdomain;
+
+ssize_t send_packet(struct packet *, struct lease *,
+ const struct sockaddr_in *);
+void lladdr_reply(struct protocol *);
+
+int rtseq;
+int llhsock = -1;
+
+/*
+ * Called by get_interface_list for each interface that's discovered.
+ */
+
+void
+if_register_receive(struct interface_info *info)
+{
+ struct sockaddr_in *sin = &info->primary_address;
+ struct ip_mreqn mreqn = {
+ .imr_ifindex = info->index,
+ };
+ int flag;
+ u_char loop;
+ int s;
+
+ if (sin->sin_addr.s_addr == htonl(INADDR_ANY)) {
+ fatalx("Can't bind to %s for %s", inet_ntoa(sin->sin_addr),
+ info->name);
+ }
+
+ s = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
+ if (s == -1)
+ fatal("Can't create socket for %s", info->name);
+
+ flag = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1)
+ fatal("Can't enable SO_REUSEADDR");
+
+ flag = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) == -1)
+ fatal("Can't enable SO_BROADCAST");
+
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF,
+ &mreqn, sizeof(mreqn)) == -1)
+ fatal("Can't set IP_MULTICAST_IF");
+
+ flag = 1;
+ if (setsockopt(s, IPPROTO_IP, IP_RECVIF, &flag, sizeof(flag)) == -1)
+ fatal("Can't enable IP_RECVIF");
+
+ loop = 0;
+ if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &loop, sizeof(loop)) == -1)
+ fatal("Can't disable IP_MULTICAST_LOOP");
+
+ if (bind(s, (struct sockaddr *)sin, sizeof(*sin)) == -1) {
+ fatal("Can't bind to %s for %s", inet_ntoa(sin->sin_addr),
+ info->name);
+ }
+
+ info->rfdesc = s;
+ info->send_packet = send_packet;
+}
+
+void
+if_register_send(struct interface_info *info)
+{
+ info->wfdesc = info->rfdesc;
+}
+
+struct lladdr_deferred {
+ SIMPLEQ_ENTRY(lladdr_deferred) entry;
+ struct interface_info *interface;
+ struct dhcp_packet *raw;
+ size_t len;
+ struct sockaddr_in dst;
+ unsigned int seq;
+};
+
+SIMPLEQ_HEAD(lladdr_deferrals, lladdr_deferred);
+struct lladdr_deferrals llds = SIMPLEQ_HEAD_INITIALIZER(llds);
+
+#ifndef roundup
+#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
+#endif
+
+#define RTMSG_SPACE(_s) roundup(_s, sizeof(long))
+#define RTMSG_NEXT(_s) (void *)((uint8_t *)(_s) + RTMSG_SPACE(sizeof(*_s)))
+
+ssize_t
+send_packet(struct packet *packet, struct lease *lease,
+ const struct sockaddr_in *dst)
+{
+ struct interface_info *interface = packet->interface;
+ struct dhcp_packet *raw = packet->raw;
+ ssize_t result;
+
+ /* should we fake an arp entry for this? */
+ if (lease != NULL &&
+ raw->giaddr.s_addr == htonl(INADDR_ANY) &&
+ !ISSET(raw->flags, htonl(BOOTP_BROADCAST))) {
+ struct rt_msghdr *rtm;
+ struct sockaddr_in *sin;
+ struct sockaddr_dl *sdl;
+ struct lladdr_deferred *d;
+
+ uint8_t rtmsg[RTMSG_SPACE(sizeof(*rtm)) +
+ RTMSG_SPACE(sizeof(*sin)) + /* RTA_DST */
+ RTMSG_SPACE(sizeof(*sdl)) + /* RTA_GATEWAY */
+ RTMSG_SPACE(sizeof(*sin)) + /* RTA_NETMASK */
+ RTMSG_SPACE(sizeof(*sdl)) + /* RTA_IFP */
+ RTMSG_SPACE(sizeof(*sin))]; /* RTA_IFA */
+
+ memset(rtmsg, 0, sizeof(rtmsg));
+
+ rtm = (struct rt_msghdr *)rtmsg;
+ rtm->rtm_msglen = sizeof(rtmsg);
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_type = RTM_ADD;
+ rtm->rtm_hdrlen = sizeof(*rtm);
+ rtm->rtm_index = interface->index;
+ rtm->rtm_tableid = rdomain;
+ rtm->rtm_priority = RTP_CONNECTED - 1; /* XXX */
+ rtm->rtm_flags =
+ RTF_UP | RTF_HOST | RTF_LLINFO;
+ rtm->rtm_addrs =
+ RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP | RTA_IFA;
+ rtm->rtm_seq = ++rtseq;
+
+ if (lease->ends != 0) {
+ rtm->rtm_inits = RTV_EXPIRE;
+ rtm->rtm_rmx.rmx_expire = lease->ends + 1;
+ }
+
+ /* DST */
+ sin = RTMSG_NEXT(rtm);
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ sin->sin_addr = dst->sin_addr;
+
+ /* GATEWAY */
+ sdl = RTMSG_NEXT(sin);
+ sdl->sdl_len = sizeof(*sdl);
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_alen = raw->hlen;
+ memcpy(LLADDR(sdl), raw->chaddr, sdl->sdl_alen);
+
+ /* NETMASK */
+ sin = RTMSG_NEXT(sdl);
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(0xffffffff);
+
+ /* IFP */
+ sdl = RTMSG_NEXT(sin);
+ sdl->sdl_len = sizeof(*sdl);
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_index = interface->index;
+ sdl->sdl_type = IFT_ETHER;
+ sdl->sdl_nlen = strlen(interface->name);
+ memcpy(sdl->sdl_data, interface->name, sdl->sdl_nlen);
+ sdl->sdl_alen = interface->hw_address.hlen;
+ memcpy(LLADDR(sdl), interface->hw_address.haddr, sdl->sdl_alen);
+
+ /* IFA */
+ sin = RTMSG_NEXT(sdl);
+ *sin = interface->primary_address;
+
+ d = malloc(sizeof(*d) + packet->packet_length);
+ if (d == NULL) {
+ log_warn("cannot defer packet for lladdr help");
+ return (-1);
+ }
+
+ result = send(llhsock, rtmsg, sizeof(rtmsg), 0);
+ if (result == -1) {
+ log_warn("lladdr helper send");
+ return (-1);
+ }
+
+ d->interface = interface;
+ d->raw = (struct dhcp_packet *)(d + 1);
+ d->len = packet->packet_length;
+ d->dst = *dst;
+ d->seq = rtm->rtm_seq;
+
+ memcpy(d->raw, raw, d->len);
+
+ SIMPLEQ_INSERT_TAIL(&llds, d, entry);
+ return (0);
+ }
+
+ result = sendto(interface->wfdesc, raw, packet->packet_length, 0,
+ (const struct sockaddr *)dst, sizeof(*dst));
+ if (result == -1)
+ log_warn("send_packet");
+ return (result);
+}
+
+void
+lladdr_reply(struct protocol *protocol)
+{
+ struct rt_msghdr rtm;
+ struct lladdr_deferred *d;
+ struct interface_info *interface;
+ ssize_t result;
+
+ result = recv(llhsock, &rtm, sizeof(rtm), 0);
+ if (result == -1) {
+ log_info("lladdr helper reply");
+ return;
+ }
+
+ log_debug("got a reply from lladdr helper for %u", rtm.rtm_seq);
+
+ d = SIMPLEQ_FIRST(&llds);
+ SIMPLEQ_REMOVE_HEAD(&llds, entry);
+
+ interface = d->interface;
+
+ result = sendto(interface->wfdesc, d->raw, d->len, 0,
+ (const struct sockaddr *)&d->dst, sizeof(d->dst));
+ free(d);
+ if (result == -1)
+ log_warn("send_packet");
+}
+
+ssize_t
+receive_packet(struct interface_info *interface, unsigned char *buf,
+ size_t len, struct sockaddr_in *src, struct sockaddr_in *dst,
+ struct sockaddr_dl *sdl)
+{
+ struct iovec iov[1] = {
+ { .iov_base = buf, .iov_len = len },
+ };
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(*sdl))];
+ } cmsgbuf;
+ struct msghdr msg = {
+ .msg_name = src,
+ .msg_namelen = sizeof(*src),
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ .msg_control = &cmsgbuf.buf,
+ .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t result;
+
+ result = recvmsg(interface->rfdesc, &msg, 0);
+ if (result == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ return (0);
+ default:
+ break;
+ }
+ return (result);
+ }
+
+ *dst = interface->primary_address;
+
+ CMSG_FOREACH(cmsg, &msg) {
+ if (CMSG_MATCH(cmsg, sizeof(*sdl), IPPROTO_IP, IP_RECVIF))
+ *sdl = *(struct sockaddr_dl *)CMSG_DATA(cmsg);
+ }
+
+ return (result);
+}
+
+void
+lladdr_del(struct lease *l)
+{
+ struct rt_msghdr *rtm;
+ struct sockaddr_in *sin;
+ struct sockaddr_dl *sdl;
+
+ uint8_t rtmsg[RTMSG_SPACE(sizeof(*rtm)) +
+ RTMSG_SPACE(sizeof(*sin)) + /* RTA_DST */
+ RTMSG_SPACE(sizeof(*sdl)) + /* RTA_GATEWAY */
+ RTMSG_SPACE(sizeof(*sin)) + /* RTA_NETMASK */
+ RTMSG_SPACE(sizeof(*sdl)) + /* RTA_IFP */
+ RTMSG_SPACE(sizeof(*sin))]; /* RTA_IFA */
+ ssize_t result;
+
+ struct interface_info *interface;
+ struct lease_state *s = l->state;
+
+ if (llhsock == -1 || s == NULL)
+ return;
+
+ /*
+ * Avoid dereferencing s->ip until we're sure it's a local interface.
+ */
+ for (interface = interfaces; interface != NULL;
+ interface = interface->next) {
+ if (s->ip == interface)
+ break;
+ }
+ if (interface == NULL)
+ return;
+
+ memset(rtmsg, 0, sizeof(rtmsg));
+
+ rtm = (struct rt_msghdr *)rtmsg;
+ rtm->rtm_msglen = sizeof(rtmsg);
+ rtm->rtm_version = RTM_VERSION;
+ rtm->rtm_type = RTM_DELETE;
+ rtm->rtm_hdrlen = sizeof(*rtm);
+ rtm->rtm_index = interface->index;
+ rtm->rtm_tableid = rdomain;
+ rtm->rtm_priority = RTP_CONNECTED - 1; /* XXX */
+ rtm->rtm_flags =
+ RTF_UP | RTF_HOST | RTF_LLINFO;
+ rtm->rtm_addrs =
+ RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP | RTA_IFA;
+ rtm->rtm_seq = ++rtseq;
+
+ /* DST */
+ sin = RTMSG_NEXT(rtm);
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ sin->sin_addr = s->ciaddr;
+
+ /* GATEWAY */
+ sdl = RTMSG_NEXT(sin);
+ sdl->sdl_len = sizeof(*sdl);
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_alen = l->hardware_addr.hlen;
+ memcpy(LLADDR(sdl), l->hardware_addr.haddr, sdl->sdl_alen);
+
+ /* NETMASK */
+ sin = RTMSG_NEXT(sdl);
+ sin->sin_len = sizeof(*sin);
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = htonl(0xffffffff);
+
+ /* IFP */
+ sdl = RTMSG_NEXT(sin);
+ sdl->sdl_len = sizeof(*sdl);
+ sdl->sdl_family = AF_LINK;
+ sdl->sdl_index = interface->index;
+ sdl->sdl_type = IFT_ETHER;
+ sdl->sdl_nlen = strlen(interface->name);
+ memcpy(sdl->sdl_data, interface->name, sdl->sdl_nlen);
+ sdl->sdl_alen = interface->hw_address.hlen;
+ memcpy(LLADDR(sdl), interface->hw_address.haddr, sdl->sdl_alen);
+
+ /* IFA */
+ sin = RTMSG_NEXT(sdl);
+ *sin = interface->primary_address;
+
+ result = send(llhsock, rtmsg, sizeof(rtmsg), 0);
+ if (result == -1)
+ log_warn("lladdr del send");
+}
+
+/*
+ * lladdr helper process code
+ */
+
+static void
+lladdr_rtmsg(int fd, int rtsock)
+{
+ char rtmsg[1024]; /* how long is a piece of string? */
+ struct rt_msghdr *rtm = (struct rt_msghdr *)rtmsg;
+ ssize_t rv;
+ size_t len;
+ unsigned int seq;
+
+ rv = recv(fd, rtmsg, sizeof(rtmsg), 0);
+ if (rv == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ log_warn("lladdr helper request");
+ return;
+ }
+ len = rv;
+
+ if (rtm->rtm_version != RTM_VERSION)
+ fatalx("lladdr helper: request rtm_version invalid");
+ if (rtm->rtm_type != RTM_ADD && rtm->rtm_type != RTM_DELETE)
+ fatalx("lladdr helper: request rtm_type invalid");
+ if (rtm->rtm_hdrlen != sizeof(*rtm))
+ fatalx("lladdr helper: request rtm_hdrlen invalid");
+ if (rtm->rtm_msglen >= sizeof(rtmsg))
+ fatalx("lladdr helper: request rtm_msglen is too long");
+ if (rtm->rtm_msglen != len)
+ fatalx("lladdr helper: request rtm_msglen is wrong");
+ if (rtm->rtm_flags &
+ ~(RTF_UP | RTF_HOST | RTF_LLINFO | RTF_STATIC | RTF_MPATH))
+ fatalx("lladdr helper: request rtm_flags has unexpected bits");
+ if (rtm->rtm_addrs &
+ ~(RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP | RTA_IFA))
+ fatalx("lladdr helper: request rtm_addrs has unexpected bits");
+ if (rtm->rtm_inits & ~(RTV_EXPIRE))
+ fatalx("lladdr helper: request rtm_inits has unexpected bits");
+
+ /* XXX check more? */
+
+ seq = rtm->rtm_seq;
+ log_debug("lladdr helper: got %u to push into the kernel", seq);
+
+ rtm->rtm_errno = 0;
+ rv = send(rtsock, rtmsg, len, 0);
+ if (rv == -1) {
+ rtm->rtm_errno = errno;
+ /*
+ * IF this is a dynamic lease, we've tried to ping
+ * the IP to see if anything is camping on it already.
+ * Sending the ping packet has triggered ARP in the
+ * kernel, which creates a route entry to handle the
+ * state of the ARP resolution. If there's no pong,
+ * then we'll end up here trying to create the LLINFO
+ * route entry and failing because it already exists.
+ * Switch to trying to change the existing entry.
+ */
+ if (errno == EEXIST && rtm->rtm_type == RTM_ADD) {
+ rtm->rtm_type = RTM_CHANGE;
+ /* We want this flag clear if the kernel has it set. */
+ rtm->rtm_fmask = RTF_REJECT;
+
+ rv = send(rtsock, rtmsg, len, 0);
+ if (len == -1)
+ rtm->rtm_errno = errno;
+ }
+ }
+
+ /* Report back to the main process what happened */
+ if (rtm->rtm_type == RTM_DELETE)
+ return;
+
+ rv = send(fd, rtm, sizeof(*rtm), 0);
+ if (rv == -1)
+ fatal("lladdr helper: unable to reply to dhcpd");
+}
+
+void
+lladdr_helper(const struct passwd *pw)
+{
+ int llhpair[2];
+ int rtsock;
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0,
+ llhpair) == -1)
+ fatal("lladdr handler socketpair");
+
+ switch (fork()) {
+ case -1:
+ fatal("lladdr hander fork");
+ /* NOTREACHED */
+
+ case 0: /* child */
+ break;
+
+ default: /* parent */
+ close(llhpair[0]);
+ llhsock = llhpair[1];
+ add_protocol("lladdr", llhsock, lladdr_reply, NULL);
+ return;
+ }
+
+ llhsock = 3; /* different address space to the parent */
+ if (dup2(llhpair[0], llhsock) == -1)
+ fatal("lladdr handler dup2");
+ if (closefrom(llhsock + 1) == -1) {
+ if (errno != EBADF)
+ fatal("lladdr helper closefrom");
+ }
+
+ rtsock = socket(AF_ROUTE, SOCK_RAW, 0); /* we can block on rtsock */
+ if (rtsock == -1)
+ fatal("route socket");
+
+ if (chroot(pw->pw_dir) == -1)
+ fatal("lladdr helper chroot %s", pw->pw_dir);
+ if (chdir("/") == -1)
+ fatal("lladdr helper chdir %s", pw->pw_dir);
+
+ /* we can drop some privs */
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid))
+ fatal("lladdr helper can't drop privileges");
+
+ /* no filesystem visibility */
+ if (unveil("/", "") == -1)
+ fatal("lladdr helper unveil %s", pw->pw_dir);
+ if (unveil(NULL, NULL) == -1)
+ fatal("router helper unveil");
+
+#if 0
+ setproctitle("lladdr helper");
+#else
+ setproctitle("arpendage");
+#endif
+
+ for (;;) {
+ struct pollfd pfd[] = {
+ { .fd = llhsock, .events = POLLIN },
+ };
+ int nfds = poll(pfd, nitems(pfd), -1);
+ if (nfds == -1) {
+ if (errno != EINTR)
+ log_warn("poll");
+ continue;
+ }
+ if (nfds == 0)
+ continue;
+
+ if (pfd[0].revents & POLLHUP)
+ fatalx("lladdr helper: ipc socket closed");
+
+ if (pfd[0].revents & POLLIN)
+ lladdr_rtmsg(llhsock, rtsock);
+ }
+
+ exit(1);
+}
Index: udpsock.c
===================================================================
RCS file: /cvs/src/usr.sbin/dhcpd/udpsock.c,v
diff -u -p -r1.12 udpsock.c
--- udpsock.c 22 May 2025 04:24:11 -0000 1.12
+++ udpsock.c 13 Jun 2025 03:27:14 -0000
@@ -39,11 +39,12 @@
#include "dhcpd.h"
#include "log.h"
-void udpsock_handler (struct protocol *);
-ssize_t udpsock_send_packet(struct interface_info *, struct dhcp_packet *,
- size_t, struct in_addr, struct sockaddr_in *, struct hardware *);
+void udpsock_handler(struct protocol *);
+ssize_t udpsock_send_packet(struct packet *, struct lease *,
+ const struct sockaddr_in *);
struct udpsock {
+ struct in_addr bindaddr;
int sock;
};
@@ -51,132 +52,198 @@ void
udpsock_startup(struct in_addr bindaddr)
{
int sock, onoff;
- struct sockaddr_in sin4;
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_addr = bindaddr,
+ .sin_port = server_port,
+ };
struct udpsock *udpsock;
if ((udpsock = calloc(1, sizeof(struct udpsock))) == NULL)
fatal("could not create udpsock");
- memset(&sin4, 0, sizeof(sin4));
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
fatal("creating a socket failed for udp");
+ if (bindaddr.s_addr == htonl(INADDR_ANY) ||
+ bindaddr.s_addr == htonl(INADDR_BROADCAST)) {
+ onoff = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
+ &onoff, sizeof(onoff)) == -1)
+ fatal("Can't enable SO_BROADCAST for udp");
+
+ onoff = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ &onoff, sizeof(onoff)) == -1)
+ fatal("Can't enable SO_REUSEADDR for udp");
+ }
+
onoff = 1;
- if (setsockopt(sock, IPPROTO_IP, IP_RECVIF, &onoff, sizeof(onoff)) !=
- 0)
+ if (setsockopt(sock, IPPROTO_IP, IP_RECVIF,
+ &onoff, sizeof(onoff)) != 0)
fatal("setsocketopt IP_RECVIF failed for udp");
- sin4.sin_family = AF_INET;
- sin4.sin_len = sizeof(sin4);
- sin4.sin_addr = bindaddr;
- sin4.sin_port = server_port;
+ onoff = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR,
+ &onoff, sizeof(onoff)) != 0)
+ fatal("setsocketopt IP_RECVDSTADDR failed for udp");
+
+#if 0
+ onoff = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_RECVDSTPORT,
+ &onoff, sizeof(onoff)) != 0)
+ fatal("setsocketopt IP_RECVDSTPORT failed for udp");
+#endif
- if (bind(sock, (struct sockaddr *)&sin4, sizeof(sin4)) != 0)
- fatal("bind failed for udp");
+ if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+ fatal("Bind failed for %s:%d/udp.", inet_ntoa(sin.sin_addr),
+ ntohs(server_port));
+ }
+ udpsock->bindaddr = bindaddr;
udpsock->sock = sock;
add_protocol("udp", sock, udpsock_handler, udpsock);
- log_info("Listening on %s:%d/udp.", inet_ntoa(sin4.sin_addr),
+ log_info("Listening on %s:%d/udp.", inet_ntoa(sin.sin_addr),
ntohs(server_port));
}
void
udpsock_handler(struct protocol *protocol)
{
- int sockio;
- char cbuf[256], ifname[IF_NAMESIZE];
- ssize_t len;
- struct udpsock *udpsock = protocol->local;
- struct msghdr m;
- struct cmsghdr *cm;
- struct iovec iov[1];
- struct sockaddr_storage ss;
- struct sockaddr_in *sin4;
- struct sockaddr_dl *sdl = NULL;
- struct interface_info iface;
- struct iaddr from, addr;
- unsigned char packetbuf[4095];
+ struct udpsock *udpsock = protocol->local;
+ struct interface_info iface = {
+ .primary_address = {
+ .sin_family = AF_INET,
+ .sin_addr = udpsock->bindaddr,
+ .sin_port = server_port,
+ },
+
+ .is_udpsock = 1,
+ .wfdesc = udpsock->sock,
+ .send_packet = udpsock_send_packet,
+ };
+ struct interface_info *interface = NULL;
+
+ unsigned char packetbuf[4096];
+ struct iovec iov[1] = {
+ { .iov_base = packetbuf, .iov_len = sizeof(packetbuf) },
+ };
+ struct sockaddr_dl *sdl = NULL;
+ struct sockaddr_in raddr;
+ struct sockaddr_in *laddr = &iface.primary_address;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(*sdl)) +
+ CMSG_SPACE(sizeof(laddr->sin_addr)) +
+ CMSG_SPACE(sizeof(laddr->sin_port))];
+ } cmsgbuf;
+ struct cmsghdr *cmsg;
+ struct msghdr msg = {
+ .msg_name = &raddr,
+ .msg_namelen = sizeof(raddr),
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ .msg_control = &cmsgbuf.buf,
+ .msg_controllen = sizeof(cmsgbuf.buf),
+ };
+ ssize_t result;
+
struct dhcp_packet *packet = (struct dhcp_packet *)packetbuf;
- struct hardware hw;
- struct ifreq ifr;
struct subnet *subnet;
+ struct iaddr addr;
- memset(&hw, 0, sizeof(hw));
-
- iov[0].iov_base = packetbuf;
- iov[0].iov_len = sizeof(packetbuf);
- memset(&m, 0, sizeof(m));
- m.msg_name = &ss;
- m.msg_namelen = sizeof(ss);
- m.msg_iov = iov;
- m.msg_iovlen = 1;
- m.msg_control = cbuf;
- m.msg_controllen = sizeof(cbuf);
-
- memset(&iface, 0, sizeof(iface));
- if ((len = recvmsg(udpsock->sock, &m, 0)) == -1) {
+ result = recvmsg(udpsock->sock, &msg, 0);
+ if (result == -1) {
log_warn("receiving a DHCP message failed");
return;
}
- if (ss.ss_family != AF_INET) {
- log_warnx("received DHCP message is not AF_INET");
- return;
- }
- sin4 = (struct sockaddr_in *)&ss;
- for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&m);
- m.msg_controllen != 0 && cm;
- cm = (struct cmsghdr *)CMSG_NXTHDR(&m, cm)) {
- if (cm->cmsg_level == IPPROTO_IP &&
- cm->cmsg_type == IP_RECVIF)
- sdl = (struct sockaddr_dl *)CMSG_DATA(cm);
- }
- if (sdl == NULL) {
- log_warnx("could not get the received interface by IP_RECVIF");
- return;
- }
- if_indextoname(sdl->sdl_index, ifname);
- if ((sockio = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
- log_warn("socket creation failed");
- return;
- }
- strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
- if (ioctl(sockio, SIOCGIFADDR, &ifr, sizeof(ifr)) == -1) {
- log_warn("Failed to get address for %s", ifname);
- close(sockio);
+ if (raddr.sin_family != AF_INET) {
+ log_warnx("received DHCP message is not AF_INET");
return;
}
- close(sockio);
- if (ifr.ifr_addr.sa_family != AF_INET)
- return;
+ CMSG_FOREACH(cmsg, &msg) {
+ if (CMSG_MATCH(cmsg,
+ sizeof(*sdl), IPPROTO_IP, IP_RECVIF))
+ sdl = (struct sockaddr_dl *)CMSG_DATA(cmsg);
+ else if (CMSG_MATCH(cmsg,
+ sizeof(laddr->sin_addr), IPPROTO_IP, IP_RECVDSTADDR))
+ laddr->sin_addr = *(struct in_addr *)CMSG_DATA(cmsg);
+ else if (CMSG_MATCH(cmsg,
+ sizeof(laddr->sin_port), IPPROTO_IP, IP_RECVDSTPORT))
+ laddr->sin_port = *(in_port_t *)CMSG_DATA(cmsg);
+ }
+
+ /*
+ * We might be handling broadcasts for real interfaces.
+ */
+ if (laddr->sin_addr.s_addr == htonl(INADDR_BROADCAST) &&
+ sdl != NULL &&
+ sdl->sdl_family == AF_LINK &&
+ sdl->sdl_index != 0) {
+ struct interface_info *ip;
+
+ for (ip = interfaces; ip != NULL; ip = ip->next) {
+ if (ip->index == sdl->sdl_index) {
+ interface = ip;
+ break;
+ }
+ }
+ }
+
+ if (interface == NULL) {
+ addr.len = sizeof(raddr.sin_addr.s_addr);
+ memcpy(&addr.iabuf, &raddr.sin_addr.s_addr, addr.len);
+
+ if ((subnet = find_subnet(addr)) == NULL)
+ return;
+ iface.shared_network = subnet->shared_network;
- iface.is_udpsock = 1;
- iface.send_packet = udpsock_send_packet;
- iface.wfdesc = udpsock->sock;
- iface.ifp = 𝔦
- iface.index = sdl->sdl_index;
- iface.primary_address =
- ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr;
- strlcpy(iface.name, ifname, sizeof(iface.name));
+ inet_ntop(AF_INET, &raddr.sin_addr,
+ iface.name, sizeof(iface.name));
- addr.len = 4;
- memcpy(&addr.iabuf, &iface.primary_address, addr.len);
+ interface = &iface;
+ }
- if ((subnet = find_subnet(addr)) == NULL)
- return;
- iface.shared_network = subnet->shared_network ;
- from.len = 4;
- memcpy(&from.iabuf, &sin4->sin_addr, from.len);
- do_packet(&iface, packet, len, sin4->sin_port, from, &hw);
+ do_packet(interface, packet, result, &raddr, laddr, sdl);
}
ssize_t
-udpsock_send_packet(struct interface_info *interface, struct dhcp_packet *raw,
- size_t len, struct in_addr from, struct sockaddr_in *to,
- struct hardware *hto)
+udpsock_send_packet(struct packet *packet, struct lease *lease,
+ const struct sockaddr_in *dstp)
{
- return (sendto(interface->wfdesc, raw, len, 0, (struct sockaddr *)to,
- sizeof(struct sockaddr_in)));
+ struct interface_info *interface = packet->interface;
+ const struct sockaddr_in *laddr = &packet->laddr;
+
+ struct iovec iov[1] = {
+ { .iov_base = packet->raw, .iov_len = packet->packet_length },
+ };
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(laddr->sin_addr))];
+ } cmsgbuf;
+ struct cmsghdr *cmsg;
+ struct sockaddr_in dst = *dstp; /* avoid dropping const below */
+ struct msghdr msg = {
+ .msg_name = &dst,
+ .msg_namelen = sizeof(dst),
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ };
+
+ if (laddr != NULL &&
+ laddr->sin_addr.s_addr != htonl(INADDR_BROADCAST)) {
+ msg.msg_control = &cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(laddr->sin_addr));
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_SENDSRCADDR;
+ *(struct in_addr *)CMSG_DATA(cmsg) = laddr->sin_addr;
+ }
+
+ return sendmsg(interface->wfdesc, &msg, 0);
}
dhcpd(8): use UDP sockets instead of BPF