From cd5752ea8a71ab304683a599e74f449e5cceb0ac Mon Sep 17 00:00:00 2001 From: John Lockman Date: Tue, 26 May 2026 10:41:31 -0400 Subject: [PATCH 1/2] fix: inject BOOTIF kernel param from requesting MAC at bootscript serve time When a node fetches its bootscript via GET /boot/v1/bootscript?mac=, BSS already has the requesting MAC available. On multi-NIC servers, dracut sends DHCP on all interfaces without BOOTIF, causing it to configure the wrong NIC. This change injects BOOTIF=01- dynamically using the same checkParam() mechanism already used for xname=, nid=, and ds=. checkParam() skips injection if BOOTIF already exists in stored params, so operators who set it explicitly retain control. Nodes fetching by ?name= or ?nid= without a MAC get no BOOTIF injection (mac is empty string, guard prevents injection). Fixes: multi-NIC PXE boot failures where cloud-init configures the wrong network interface. Signed-off-by: John Lockman --- cmd/boot-script-service/default_api.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/boot-script-service/default_api.go b/cmd/boot-script-service/default_api.go index dc779aa..87a11bf 100644 --- a/cmd/boot-script-service/default_api.go +++ b/cmd/boot-script-service/default_api.go @@ -89,6 +89,7 @@ type scriptParams struct { xname string nid string referralToken string + mac string } // Note that we allow an empty string if the env variable is defined as such. @@ -689,6 +690,11 @@ func buildParams(bd BootData, sp scriptParams, role, subRole string) (string, er if sp.referralToken != "" { params = checkParam(params, "bss_referral_token=", sp.referralToken) } + // Add BOOTIF to params to force 1st mac + if sp.mac != "" { + bootif := "01-" + strings.ReplaceAll(sp.mac, ":", "-") + params = checkParam(params, "BOOTIF=", bootif) + } // Inject the cloud init address info into the kernel params. If the target // image does not have cloud-init enabled this wont hurt anything. @@ -841,7 +847,7 @@ func BootscriptGet(w http.ResponseWriter, r *http.Request) { log.Printf("BSS request failed: bootscript request without mac=, name=, or nid= parameter") return } - sp := scriptParams{comp.ID, comp.NID.String(), bd.ReferralToken} + sp := scriptParams{comp.ID, comp.NID.String(), bd.ReferralToken, mac} debugf("bd: %v\n", bd) debugf("comp: %v\n", comp) From ceb3bd8e21755ee13fd6758191449bcf4bd5f5b2 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Thu, 4 Jun 2026 14:13:23 -0400 Subject: [PATCH 2/2] test: add BOOTIF injection tests for various MAC address scenarios Signed-off-by: Alex Lovell-Troy --- cmd/boot-script-service/default_api_test.go | 107 ++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/cmd/boot-script-service/default_api_test.go b/cmd/boot-script-service/default_api_test.go index ba320fe..6d6f40c 100644 --- a/cmd/boot-script-service/default_api_test.go +++ b/cmd/boot-script-service/default_api_test.go @@ -311,6 +311,113 @@ func TestBuildParams_Success(t *testing.T) { "nid=0", fmt.Sprintf("ds=nocloud-net;s=%s/", advertiseAddress), }}, + { + // BOOTIF injection with MAC address + BootData{ + Params: "root=nfs:example/path/to/rootfs:ro", + Kernel: ImageData{Path: "http://example/path/to/vmlinuz"}, + Initrd: ImageData{Path: "http://example/path/to/initramfs.img"}, + }, + scriptParams{ + xname: "x0000c0s0b0n0", + nid: "0", + mac: "aa:bb:cc:dd:ee:ff", + }, + []string{ + "initrd=initrd", + "root=nfs:example/path/to/rootfs:ro", + "xname=x0000c0s0b0n0", + "nid=0", + "BOOTIF=01-aa-bb-cc-dd-ee-ff", + fmt.Sprintf("ds=nocloud-net;s=%s/", advertiseAddress), + }, + }, + { + // No BOOTIF injection when mac is empty + BootData{ + Params: "root=nfs:example/path/to/rootfs:ro", + Kernel: ImageData{Path: "http://example/path/to/vmlinuz"}, + Initrd: ImageData{Path: "http://example/path/to/initramfs.img"}, + }, + scriptParams{ + xname: "x0000c0s0b0n0", + nid: "0", + mac: "", + }, + []string{ + "initrd=initrd", + "root=nfs:example/path/to/rootfs:ro", + "xname=x0000c0s0b0n0", + "nid=0", + fmt.Sprintf("ds=nocloud-net;s=%s/", advertiseAddress), + }, + }, + { + // Existing BOOTIF in params is preserved (not overwritten) + BootData{ + Params: "root=nfs:example/path/to/rootfs:ro BOOTIF=01-11-22-33-44-55-66", + Kernel: ImageData{Path: "http://example/path/to/vmlinuz"}, + Initrd: ImageData{Path: "http://example/path/to/initramfs.img"}, + }, + scriptParams{ + xname: "x0000c0s0b0n0", + nid: "0", + mac: "aa:bb:cc:dd:ee:ff", + }, + []string{ + "initrd=initrd", + "root=nfs:example/path/to/rootfs:ro", + "BOOTIF=01-11-22-33-44-55-66", + "xname=x0000c0s0b0n0", + "nid=0", + fmt.Sprintf("ds=nocloud-net;s=%s/", advertiseAddress), + }, + }, + { + // BOOTIF with uppercase MAC address + BootData{ + Params: "root=nfs:example/path/to/rootfs:ro", + Kernel: ImageData{Path: "http://example/path/to/vmlinuz"}, + Initrd: ImageData{Path: "http://example/path/to/initramfs.img"}, + }, + scriptParams{ + xname: "x0000c0s0b0n0", + nid: "0", + mac: "AA:BB:CC:DD:EE:FF", + }, + []string{ + "initrd=initrd", + "root=nfs:example/path/to/rootfs:ro", + "xname=x0000c0s0b0n0", + "nid=0", + "BOOTIF=01-AA-BB-CC-DD-EE-FF", + fmt.Sprintf("ds=nocloud-net;s=%s/", advertiseAddress), + }, + }, + { + // BOOTIF with all parameters (mac, referralToken, kernel params) + BootData{ + Params: "root=nfs:example/path/to/rootfs:ro", + Kernel: ImageData{Path: "http://example/path/to/vmlinuz", Params: "console=ttyS0"}, + Initrd: ImageData{Path: "http://example/path/to/initramfs.img"}, + }, + scriptParams{ + xname: "x0000c0s0b0n0", + nid: "0", + referralToken: "test_token", + mac: "00:11:22:33:44:55", + }, + []string{ + "initrd=initrd", + "root=nfs:example/path/to/rootfs:ro", + "console=ttyS0", + "xname=x0000c0s0b0n0", + "nid=0", + "bss_referral_token=test_token", + "BOOTIF=01-00-11-22-33-44-55", + fmt.Sprintf("ds=nocloud-net;s=%s/", advertiseAddress), + }, + }, } for _, tc := range test_cases {