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>
