git: ab9130c737c7 - main - boot-test.sh: Add a http server per interface

From: Warner Losh <imp_at_FreeBSD.org>
Date: Sat, 26 Sep 2026 06:44:52 UTC
The branch main has been updated by imp:

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

commit ab9130c737c718391cf5318a773ae500a955a4f9
Author:     Warner Losh <imp@FreeBSD.org>
AuthorDate: 2026-09-24 14:39:21 +0000
Commit:     Warner Losh <imp@FreeBSD.org>
CommitDate: 2026-09-26 06:43:53 +0000

    boot-test.sh: Add a http server per interface
    
    Add the built-in python http server, bound to each of the interaces we
    create to expand network testing to include http:// in various
    scenarios.
    
    Sponsored by:           Netflix
---
 tools/boot/boot-test.sh   | 41 ++++++++++++++++++++++++++++++++++++++++-
 tools/boot/boot-test.sh.8 | 32 ++++++++++++++++++++++++--------
 2 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/tools/boot/boot-test.sh b/tools/boot/boot-test.sh
index 023d2589824c..fd11f1a39f6b 100755
--- a/tools/boot/boot-test.sh
+++ b/tools/boot/boot-test.sh
@@ -49,6 +49,7 @@ pkg_for() {
     *ipxe*)		echo sysutils/ipxe ;;
     *syslinux*|*memdisk*) echo sysutils/syslinux ;;
     *edk2*)		echo emulators/qemu ;;	# edk2-*.fd ship with qemu
+    python3)		echo lang/python3 ;;
     *)			echo "" ;;		# makefs/mkimg etc. = base
     esac
 }
@@ -1322,6 +1323,21 @@ netboot_network_setup() {
 	done < "${resolved}"
     fi
 
+    # Each vmnet also gets its own python3 http.server (see netboot_helper),
+    # so http:// fetches (netboot-http-efi) have something to hit at
+    # ${next-server} alongside dnsmasq's tftp-root. Confirm the pid count
+    # still matches the plan and every one of them is still alive.
+    if ${converged}; then
+	if [ -s "${NETBOOT_STATE_DIR}/httpd.pids" ] && \
+		[ "$(wc -l < "${NETBOOT_STATE_DIR}/httpd.pids")" -eq "$(wc -l < "${resolved}")" ]; then
+	    while read pid; do
+		ps -p "${pid}" > /dev/null 2>&1 || { converged=false; break; }
+	    done < "${NETBOOT_STATE_DIR}/httpd.pids"
+	else
+	    converged=false
+	fi
+    fi
+
     if ${converged}; then
 	echo "  Existing vmnet(4)/dnsmasq setup already matches -- no sudo needed."
     else
@@ -1342,6 +1358,7 @@ netboot_network_setup() {
 netboot_helper() {
     planfile=$1
     [ -r "${planfile}" ] || die "netboot helper: cannot read plan ${planfile}"
+    need_cmd python3
     invoker=${SUDO_UID:-$(id -u)}
     mkdir -p "${NETBOOT_STATE_DIR}"
 
@@ -1351,6 +1368,12 @@ netboot_helper() {
 	kill "$(cat ${NETBOOT_STATE_DIR}/dnsmasq.pid)" 2>/dev/null || true
 	rm -f "${NETBOOT_STATE_DIR}/dnsmasq.pid"
     fi
+    if [ -s "${NETBOOT_STATE_DIR}/httpd.pids" ]; then
+	while read pid; do
+	    kill "${pid}" 2>/dev/null || true
+	done < "${NETBOOT_STATE_DIR}/httpd.pids"
+    fi
+    : > "${NETBOOT_STATE_DIR}/httpd.pids"
     for vmnet in $(ifconfig -g boot-test 2>/dev/null); do
 	ifconfig "${vmnet}" destroy
     done
@@ -1381,6 +1404,16 @@ EOF
 	ifconfig "${vmnet}" inet "${gw}/${prefix}" up
 	chown "${invoker}" "/dev/${vmnet}"
 
+	# One http.server per subnet, bound only to that subnet's own gw
+	# address (not 0.0.0.0), so concurrently-running tests never collide
+	# on port 80. Serves the exact same tree as dnsmasq's tftp-root above
+	# -- http:// and tftp:// fetches of the same path see identical bytes.
+	# Harmless for tests that never issue an http:// fetch; only
+	# netboot-http-efi actually relies on it today.
+	python3 -m http.server 80 --bind "${gw}" --directory "${tftpdir}" \
+	    > "${NETBOOT_STATE_DIR}/httpd-${vmnet}.log" 2>&1 &
+	echo $! >> "${NETBOOT_STATE_DIR}/httpd.pids"
+
 	# dnsmasq's dhcp-boot only sets DHCP option 67 (bootfile-name), never
 	# the classic fixed-length BOOTP "file" field -- confirmed by comparing
 	# against QEMU's own slirp DHCP server, which sets that field directly
@@ -1417,7 +1450,8 @@ EOF
 
     md5 -q "${planfile}" > "${NETBOOT_STATE_DIR}/state.hash"
     chown "${invoker}" "${NETBOOT_STATE_DIR}" "${conf}" "${planfile}" \
-	"${NETBOOT_STATE_DIR}/dnsmasq.pid" "${NETBOOT_STATE_DIR}/state.hash"
+	"${NETBOOT_STATE_DIR}/dnsmasq.pid" "${NETBOOT_STATE_DIR}/state.hash" \
+	"${NETBOOT_STATE_DIR}"/httpd.pids "${NETBOOT_STATE_DIR}"/httpd-*.log
 }
 
 # Manual cleanup: `sudo sh boot-test.sh --netboot-teardown`. Not run
@@ -1427,6 +1461,11 @@ netboot_teardown() {
     if [ -s "${NETBOOT_STATE_DIR}/dnsmasq.pid" ]; then
 	kill "$(cat ${NETBOOT_STATE_DIR}/dnsmasq.pid)" 2>/dev/null || true
     fi
+    if [ -s "${NETBOOT_STATE_DIR}/httpd.pids" ]; then
+	while read pid; do
+	    kill "${pid}" 2>/dev/null || true
+	done < "${NETBOOT_STATE_DIR}/httpd.pids"
+    fi
     for vmnet in $(ifconfig -g boot-test 2>/dev/null); do
 	ifconfig "${vmnet}" destroy
     done
diff --git a/tools/boot/boot-test.sh.8 b/tools/boot/boot-test.sh.8
index dda97cc5d71d..69c1e705d444 100644
--- a/tools/boot/boot-test.sh.8
+++ b/tools/boot/boot-test.sh.8
@@ -1,7 +1,7 @@
 .\"
 .\" SPDX-License-Identifier: BSD-2-Clause
 .\"
-.Dd July 5, 2026
+.Dd September 24, 2026
 .Dt BOOT-TEST.SH 8
 .Os
 .Sh NAME
@@ -215,13 +215,28 @@ invokes
 once to create the interfaces
 .Pq chowning the resulting device nodes back to the invoking user
 and start
-.Xr dnsmasq 8 .
+.Xr dnsmasq 8
+plus, on every one of those private
+.Li /30 Ns s ,
+a
+.Xr python3 1
+.Cm http.server
+bound to that subnet's gateway address and serving the same tree as
+.Xr dnsmasq 8 Ns 's
+tftp-root.
+.Ar netboot-http-efi
+forces its root device to an http:// URL and fetches its kernel and
+modules over that server via the loader's own
+.Dq Li http://host/path
+support rather than TFTP; every other netboot test simply ignores it.
 Because the interfaces persist until explicitly destroyed,
 .Nm
-leaves them and
-.Xr dnsmasq 8
-running afterward; subsequent runs detect the existing setup by comparing a
-hash of the intended configuration and skip
+leaves them,
+.Xr dnsmasq 8 ,
+and the
+.Xr python3 1
+servers running afterward; subsequent runs detect the existing setup by
+comparing a hash of the intended configuration and skip
 .Xr sudo 8
 entirely, so the prompt is normally seen once per boot rather than once per
 run.
@@ -245,10 +260,11 @@ from the base system, and the
 .Xr jq 1 ,
 .Xr expect 1 ,
 .Xr qemu 1 ,
+.Xr dnsmasq 8 ,
 and
-.Xr dnsmasq 8
+.Xr python3 1
 packages
-.Pq Pa textproc/jq , Pa lang/expect , Pa emulators/qemu , Pa dns/dnsmasq .
+.Pq Pa textproc/jq , Pa lang/expect , Pa emulators/qemu , Pa dns/dnsmasq , Pa lang/python3 .
 The network boot tests additionally require the
 .Pa sysutils/ipxe
 and