Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 30 Jul 2026 02:27:07 +0000
From:      Alexander Motin <mav@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 88229544807f - main - stand: Cleanup use of cached DHCP response packet
Message-ID:  <6a6ab67b.1920b.1269126c@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by mav:

URL: https://cgit.FreeBSD.org/src/commit/?id=88229544807f0f8aedd202a980f251df5757fe5c

commit 88229544807f0f8aedd202a980f251df5757fe5c
Author:     Alexander Motin <mav@FreeBSD.org>
AuthorDate: 2026-07-30 02:12:22 +0000
Commit:     Alexander Motin <mav@FreeBSD.org>
CommitDate: 2026-07-30 02:26:46 +0000

    stand: Cleanup use of cached DHCP response packet
    
     - When cached response is available, actually use xid from one
    instead of using its byte-swapped value for BIOS and 1 for UEFI.
     - If cached response is not available, generate pseudo-random xid,
    since use of a constant may cause conflicts if two systems are
    booting same time, and server sends responses as broadcast.
     - When cached response is available, skip DHCP DISCOVER/OFFER
    and just send REQUEST to the DHCP server from the cached response.
    We could skip this phase too and just use the cached response, but
    we don't know whether firmware requested all of DHCP options we'd
    like to get.
    
    Tested on amd64 Supermicro X11DPI-NT for both BIOS and EFI, with
    and without cached response packet.
---
 stand/efi/libefi/efinet.c |   1 -
 stand/i386/libi386/pxe.c  |   5 --
 stand/libsa/bootp.c       | 135 +++++++++++++++++++++++++++++++++++-----------
 3 files changed, 105 insertions(+), 36 deletions(-)

diff --git a/stand/efi/libefi/efinet.c b/stand/efi/libefi/efinet.c
index d3d975d21268..5c391d38a265 100644
--- a/stand/efi/libefi/efinet.c
+++ b/stand/efi/libefi/efinet.c
@@ -324,7 +324,6 @@ efinet_init(struct iodesc *desc, void *machdep_hint)
 #endif
 
 	bcopy(net->Mode->CurrentAddress.Addr, desc->myea, 6);
-	desc->xid = 1;
 }
 
 static void
diff --git a/stand/i386/libi386/pxe.c b/stand/i386/libi386/pxe.c
index aa392b83718c..e0752eadccc2 100644
--- a/stand/i386/libi386/pxe.c
+++ b/stand/i386/libi386/pxe.c
@@ -413,11 +413,6 @@ pxe_netif_init(struct iodesc *desc, void *machdep_hint)
 	for (i = 0; i < len; ++i)
 		desc->myea[i] = mac[i];
 
-	if (bootp_response != NULL)
-		desc->xid = bootp_response->bp_xid;
-	else
-		desc->xid = 0;
-
 	bio_free(undi_info_p, sizeof(*undi_info_p));
 	undi_open_p = bio_alloc(sizeof(*undi_open_p));
 	if (undi_open_p == NULL)
diff --git a/stand/libsa/bootp.c b/stand/libsa/bootp.c
index ac37553c6d34..5143bc91ff08 100644
--- a/stand/libsa/bootp.c
+++ b/stand/libsa/bootp.c
@@ -120,7 +120,7 @@ bootp_fill_request(unsigned char *bp_vend)
 void
 bootp(int sock)
 {
-	void *pkt;
+	void *pkt = NULL;
 	struct iodesc *d;
 	struct bootp *bp;
 	struct {
@@ -128,36 +128,28 @@ bootp(int sock)
 		struct bootp wbootp;
 	} wbuf;
 	struct bootp *rbootp;
+	bool init_reboot = false;
 
 	DEBUG_PRINTF(1, ("bootp: socket=%d\n", sock));
 	if (!bot)
 		bot = getsecs();
-	
+
 	if (!(d = socktodesc(sock))) {
 		printf("bootp: bad socket. %d\n", sock);
 		return;
 	}
 	DEBUG_PRINTF(1, ("bootp: socktodesc=%lx\n", (long)d));
 
+restart:
 	bp = &wbuf.wbootp;
 	bzero(bp, sizeof(*bp));
 
 	bp->bp_op = BOOTREQUEST;
 	bp->bp_htype = 1;		/* 10Mb Ethernet (48 bits) */
 	bp->bp_hlen = 6;
-	bp->bp_xid = htonl(d->xid);
 	MACPY(d->myea, bp->bp_chaddr);
 	strncpy(bp->bp_file, bootfile, sizeof(bp->bp_file));
 	bcopy(vm_rfc1048, bp->bp_vend, sizeof(vm_rfc1048));
-#ifdef SUPPORT_DHCP
-	bp->bp_vend[4] = TAG_DHCP_MSGTYPE;
-	bp->bp_vend[5] = 1;
-	bp->bp_vend[6] = DHCPDISCOVER;
-	bootp_fill_request(&bp->bp_vend[7]);
-
-#else
-	bp->bp_vend[4] = TAG_END;
-#endif
 
 	d->myip.s_addr = INADDR_ANY;
 	d->myport = htons(IPPORT_BOOTPC);
@@ -165,47 +157,130 @@ bootp(int sock)
 	d->destport = htons(IPPORT_BOOTPS);
 
 #ifdef SUPPORT_DHCP
-	expected_dhcpmsgtype = DHCPOFFER;
-	dhcp_ok = 0;
+	/*
+	 * If a DHCP reply is already cached (populated by a lower layer
+	 * that captured it from firmware, e.g. UEFI PXE Base Code), enter
+	 * RFC 2131 § 4.3.2 INIT-REBOOT: skip DISCOVER/OFFER and go
+	 * straight to REQUEST with option 50 = cached IP.  The DHCP
+	 * server confirms the lease with an ACK containing whatever
+	 * options this client asks for, which may be a superset of what
+	 * the firmware asked for (e.g. option 26 interface-MTU, option 16
+	 * swap-server).  On NAK or timeout, fall back to a full
+	 * DISCOVER/OFFER/REQUEST/ACK cycle.
+	 */
+	if (bootp_response != NULL &&
+	    bootp_response->bp_yiaddr.s_addr != INADDR_ANY) {
+		init_reboot = true;
+		rbootp = bootp_response;
+		d->xid = ntohl(rbootp->bp_xid);
+		DEBUG_PRINTF(1, ("bootp: using cached DHCP reply "
+		    "(INIT-REBOOT), yiaddr=%s xid=0x%08x\n",
+		    inet_ntoa(rbootp->bp_yiaddr), (unsigned)d->xid));
+	} else
 #endif
+	if (d->xid == 0) {
+		/*
+		 * RFC 951 / RFC 2131 § 4.1 recommend a random xid.
+		 * Without a real RNG in the loader, seed from the
+		 * interface MAC (host-unique) mixed with the current
+		 * time (unique across reboots on the same host).  This
+		 * avoids the pre-existing behavior of every fresh
+		 * BOOTP/DHCP client sending xid=0, which risks
+		 * broadcast-reply mis-correlation when several PXE
+		 * clients boot concurrently — bootprecv() enforces
+		 * xid matching.
+		 */
+		memcpy(&d->xid, &d->myea[2], sizeof(d->xid));
+		d->xid ^= (uint32_t)getsecs();
+	}
+	bp->bp_xid = htonl(d->xid);
 
-	if(sendrecv(d,
-		    bootpsend, bp, sizeof(*bp),
-		    bootprecv, &pkt, (void **)&rbootp, NULL) == -1) {
-	    printf("bootp: no reply\n");
-	    return;
+	if (!init_reboot) {
+#ifdef SUPPORT_DHCP
+		bp->bp_vend[4] = TAG_DHCP_MSGTYPE;
+		bp->bp_vend[5] = 1;
+		bp->bp_vend[6] = DHCPDISCOVER;
+		bootp_fill_request(&bp->bp_vend[7]);
+		expected_dhcpmsgtype = DHCPOFFER;
+		dhcp_ok = 0;
+#else
+		bp->bp_vend[4] = TAG_END;
+#endif
+
+		if (sendrecv(d,
+			    bootpsend, bp, sizeof(*bp),
+			    bootprecv, &pkt, (void **)&rbootp, NULL) == -1) {
+			printf("bootp: no reply\n");
+			return;
+		}
 	}
 
 #ifdef SUPPORT_DHCP
-	if(dhcp_ok) {
+	if (dhcp_ok || init_reboot) {
 		uint32_t leasetime;
+		int off;
+
+		bp->bp_vend[4] = TAG_DHCP_MSGTYPE;
+		bp->bp_vend[5] = 1;
 		bp->bp_vend[6] = DHCPREQUEST;
 		bp->bp_vend[7] = TAG_REQ_ADDR;
 		bp->bp_vend[8] = 4;
 		bcopy(&rbootp->bp_yiaddr, &bp->bp_vend[9], 4);
-		bp->bp_vend[13] = TAG_SERVERID;
-		bp->bp_vend[14] = 4;
-		bcopy(&dhcp_serverip.s_addr, &bp->bp_vend[15], 4);
-		bp->bp_vend[19] = TAG_LEASETIME;
-		bp->bp_vend[20] = 4;
+		off = 13;
+		if (!init_reboot) {
+			/* SELECTING: server-id identifies the server whose
+			 * OFFER we accepted.  INIT-REBOOT omits it (RFC 2131
+			 * § 4.3.2). */
+			bp->bp_vend[off++] = TAG_SERVERID;
+			bp->bp_vend[off++] = 4;
+			bcopy(&dhcp_serverip.s_addr, &bp->bp_vend[off], 4);
+			off += 4;
+		}
+		bp->bp_vend[off++] = TAG_LEASETIME;
+		bp->bp_vend[off++] = 4;
 		leasetime = htonl(300);
-		bcopy(&leasetime, &bp->bp_vend[21], 4);
-		bootp_fill_request(&bp->bp_vend[25]);
+		bcopy(&leasetime, &bp->bp_vend[off], 4);
+		off += 4;
+		bootp_fill_request(&bp->bp_vend[off]);
 
 		expected_dhcpmsgtype = DHCPACK;
 
-		free(pkt);
-		if(sendrecv(d,
+		if (pkt != NULL) {
+			free(pkt);
+			pkt = NULL;
+		}
+		if (sendrecv(d,
 			    bootpsend, bp, sizeof(*bp),
 			    bootprecv, &pkt, (void **)&rbootp, NULL) == -1) {
+			if (init_reboot) {
+				printf("bootp: INIT-REBOOT failed, "
+				    "falling back to DISCOVER\n");
+				free(bootp_response);
+				bootp_response = NULL;
+				bootp_response_size = 0;
+				init_reboot = false;
+				if (pkt != NULL) {
+					free(pkt);
+					pkt = NULL;
+				}
+				goto restart;
+			}
 			printf("DHCPREQUEST failed\n");
 			return;
 		}
+		if (init_reboot)
+			DEBUG_PRINTF(1, ("bootp: INIT-REBOOT ACK received\n"));
 	}
 #endif
 
 	myip = d->myip = rbootp->bp_yiaddr;
-	servip = rbootp->bp_siaddr;
+	/*
+	 * Preserve a pre-set servip (e.g. seeded by PXE-consuming code
+	 * from a ProxyDHCP offer) when the ACK's siaddr is zero, which is
+	 * typical for non-PXE-aware primary DHCP servers.
+	 */
+	if (rbootp->bp_siaddr.s_addr != INADDR_ANY)
+		servip = rbootp->bp_siaddr;
 	if (rootip.s_addr == INADDR_ANY)
 		rootip = servip;
 	bcopy(rbootp->bp_file, bootfile, sizeof(bootfile));


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a6ab67b.1920b.1269126c>