udhcpd: send DHCPOFFERs as unicast (unless clients specifically asks for bcast)

RFC 2131 says we should do that.
Evidently, since for so many years no one complained, sending them broadcast
works too, but finally we've got someone who wants RFC-compliand behavior.

function                                             old     new   delta
send_packet                                          141     179     +38
.rodata                                           105680  105681      +1
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 39/0)               Total: 39 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2025-04-07 02:46:39 +02:00
parent a8349b115d
commit d2d23c848a

View file

@ -575,29 +575,51 @@ static void send_packet_to_client(struct dhcp_packet *dhcp_pkt, int force_broadc
const uint8_t *chaddr; const uint8_t *chaddr;
uint32_t ciaddr; uint32_t ciaddr;
// Was: // Logic:
//if (force_broadcast) { /* broadcast */ } //if (force_broadcast) { /* broadcast */ }
//else if (dhcp_pkt->ciaddr) { /* unicast to dhcp_pkt->ciaddr */ } //else if (dhcp_pkt->ciaddr) { /* unicast to dhcp_pkt->ciaddr */ }
// ^^^ dhcp_pkt->ciaddr comes from client's request packet.
// We expect such clients to have an UDP socket listening on that IP.
//else if (dhcp_pkt->flags & htons(BROADCAST_FLAG)) { /* broadcast */ } //else if (dhcp_pkt->flags & htons(BROADCAST_FLAG)) { /* broadcast */ }
//else { /* unicast to dhcp_pkt->yiaddr */ } //else { /* unicast to dhcp_pkt->yiaddr */ }
// But this is wrong: yiaddr is _our_ idea what client's IP is // ^^^ The last case is confusing, but *should* work.
// (for example, from lease file). Client may not know that, // It's a case where client have sent a DISCOVER
// and may not have UDP socket listening on that IP! // and does not have a kernel UDP socket listening on the IP
// We should never unicast to dhcp_pkt->yiaddr! // we are offering in yiaddr (it does not know the IP yet)!
// dhcp_pkt->ciaddr, OTOH, comes from client's request packet, // This *should* work because client *should* listen on a raw socket
// and can be used. // instead at this time (IOW: it should examine ALL IPv4 packets
// "by hand", not relying on kernel's UDP stack.)
if (force_broadcast chaddr = dhcp_pkt->chaddr;
|| (dhcp_pkt->flags & htons(BROADCAST_FLAG))
|| dhcp_pkt->ciaddr == 0 if (dhcp_pkt->ciaddr == 0
|| force_broadcast /* sending DHCPNAK pkt? */
) { ) {
log1s("broadcasting packet to client"); if (dhcp_pkt->flags & htons(BROADCAST_FLAG)
ciaddr = INADDR_BROADCAST; || force_broadcast /* sending DHCPNAK pkt? */
chaddr = MAC_BCAST_ADDR; ) {
// RFC 2131:
// If 'giaddr' is zero and 'ciaddr' is zero, and the broadcast bit is
// set, then the server broadcasts DHCPOFFER and DHCPACK messages to
// 0xffffffff. ...
// In all cases, when 'giaddr' is zero, the server broadcasts any DHCPNAK
// messages to 0xffffffff.
ciaddr = INADDR_BROADCAST;
chaddr = MAC_BCAST_ADDR;
log1s("broadcasting packet to client");
} else {
// If the broadcast bit is not set and 'giaddr' is zero and
// 'ciaddr' is zero, then the server unicasts DHCPOFFER and DHCPACK
// messages to the client's hardware address and 'yiaddr' address.
ciaddr = dhcp_pkt->yiaddr;
log1("unicasting packet to client %ciaddr", 'y');
}
} else { } else {
log1s("unicasting packet to client ciaddr"); // If the 'giaddr'
// field is zero and the 'ciaddr' field is nonzero, then the server
// unicasts DHCPOFFER and DHCPACK messages to the address in 'ciaddr'.
ciaddr = dhcp_pkt->ciaddr; ciaddr = dhcp_pkt->ciaddr;
chaddr = dhcp_pkt->chaddr; log1("unicasting packet to client %ciaddr", 'c');
} }
udhcp_send_raw_packet(dhcp_pkt, udhcp_send_raw_packet(dhcp_pkt,
@ -624,6 +646,10 @@ static void send_packet_to_relay(struct dhcp_packet *dhcp_pkt)
static void send_packet(struct dhcp_packet *dhcp_pkt, int force_broadcast) static void send_packet(struct dhcp_packet *dhcp_pkt, int force_broadcast)
{ {
if (dhcp_pkt->gateway_nip) if (dhcp_pkt->gateway_nip)
// RFC 2131:
// If the 'giaddr' field in a DHCP message from a client is non-zero,
// the server sends any return messages to the 'DHCP server' port on the
// BOOTP relay agent whose address appears in 'giaddr'.
send_packet_to_relay(dhcp_pkt); send_packet_to_relay(dhcp_pkt);
else else
send_packet_to_client(dhcp_pkt, force_broadcast); send_packet_to_client(dhcp_pkt, force_broadcast);