2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-22 07:38:08 +00:00

test(blackboxtest): read the boot file from the BOOTP header only

bootfile returned option 67 when the reply carried one and the file
header otherwise. xCAT names the loader in the header, and the firmware
it serves does not read option 67 (dhcp-wire.md). A backend that moved
the loader to option 67 and left the header empty passed every loader
assertion.

bootfile now reads the header only, and each "no boot file" check also
asserts that option 67 is absent.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-18 11:05:06 -03:00
parent dad2d9332c
commit c3fa5405b0
7 changed files with 25 additions and 14 deletions
+8 -7
View File
@@ -185,16 +185,17 @@ a flag letter (`S` `O` `E` `N`); `E` sends the name in wire format.
Reply fields: the BOOTP header (`msgtype` `xid` `yiaddr` `siaddr` `ciaddr`
`giaddr` `chaddr` `file` `sname` `secs` `flags` `src_ip` `src_mac`), the
aliases `address` `mac` `next_server`, any option as `54`, `option:54`,
`opt54` or `server_id`, and three fields a client reads from two places:
`opt54` or `server_id`, and three derived fields:
- `bootfile`: option 67 when sent, else the `file` header. ISC fills the
header, dnsmasq answers in option 67, and firmware reads either.
- `bootfile`: the loader, from the BOOTP `file` header only. xCAT names the
loader there, and the firmware it serves does not read option 67. A "no boot
file" check also asserts `67 absent`.
- `dns_name`: option 81 when sent, else option 12. Kea answers option 81 in
option 81; ISC with `ignore client-updates` sends option 12 only.
- `fqdn_flags`: option 81's flags as letters, absent without option 81.
Assert on those rather than on `file` or one option, unless the one place is
the point. `msgtype` is `BOOTREPLY` for a reply with no option 53.
Assert on `dns_name` rather than on option 12 or 81, unless one option is the
point. `msgtype` is `BOOTREPLY` for a reply with no option 53.
The client MAC is a random locally administered address, so the host's own
network stack never takes a reply as its own. The tool answers ARP only for an
@@ -265,8 +266,8 @@ not ok 3 - pxe-bios-x86/bios-discover: bootfile == pxelinux.0
---
expected: 'pxelinux.0'
received: 'xcat/xnba.kpxe'
reply: OFFER from 10.99.0.1 (9a:3d:4f:3d:59:1b) xid=0xc45c6376 yiaddr=10.99.0.175 siaddr=10.99.0.1 file=''
options: {1 (subnet_mask)=255.255.255.0, 54 (server_id)=10.99.0.1, 67 (bootfile_name)=xcat/xnba.kpxe}
reply: OFFER from 10.99.0.1 (9a:3d:4f:3d:59:1b) xid=0xc45c6376 yiaddr=10.99.0.175 siaddr=10.99.0.1 file='xcat/xnba.kpxe'
options: {1 (subnet_mask)=255.255.255.0, 54 (server_id)=10.99.0.1}
sent: DISCOVER mac=02:da:16:d0:80:dd xid=0xc45c6376 client_arch=0x0000
attempt: 1 of 3
...
@@ -32,6 +32,7 @@ assert =
msgtype == OFFER
yiaddr in %(pool)s
bootfile absent
67 absent
[scenario present-loader-is-still-named]
description = S-12: An architecture whose loader is still there is unaffected
@@ -43,6 +43,7 @@ assert =
yiaddr == %(booted_ip)s
bootfile != %(booted_script)s
bootfile absent
67 absent
[scenario booted-pxe-node-is-not-sent-a-loader]
description = S-31: An installed node provisioned with netboot=pxe is not handed pxelinux either
@@ -59,6 +60,7 @@ assert =
msgtype == OFFER
yiaddr == %(booted_pxe_ip)s
bootfile absent
67 absent
[scenario booted-node-is-not-sent-a-script-on-the-second-stage]
description = S-31: And is not sent one when the loader asks again as xNBA
@@ -78,3 +80,4 @@ assert =
yiaddr == %(booted_ip)s
bootfile != %(booted_script)s
bootfile absent
67 absent
@@ -190,6 +190,7 @@ assert =
msgtype == OFFER
option:209 == %(petitboot_conf)s
bootfile absent
67 absent
[scenario noip-interface-is-denied]
description = S-07: An interface marked *NOIP* is not answered at all
@@ -174,6 +174,7 @@ assert =
msgtype == OFFER
option:209 == %(opal_conf)s
bootfile absent
67 absent
[scenario s390x-qemu]
description = S-16: A QEMU s390x client is given a conf-file and no loader
@@ -185,6 +186,7 @@ assert =
msgtype == OFFER
option:209 == %(s390x_conf)s
bootfile absent
67 absent
[scenario vendor-class-etherboot]
description = S-18: A client that identifies itself by vendor class alone still boots
@@ -439,12 +439,12 @@ class DhcpReply(Reply):
self.raw_options = raw_options
def bootfile(self):
"""What a client boots: option 67 when sent, else the `file` header.
"""The loader a node boots: the BOOTP `file` header.
ISC fills the header, dnsmasq answers in option 67, and firmware
reads whichever arrived.
The firmware xCAT serves reads the header. A loader named only in
option 67 does not boot it.
"""
return self.options.get(67) or self.fields.get("file", "")
return self.fields.get("file", "")
def dns_name(self):
"""The node's name: option 81 when sent, else option 12.
@@ -114,11 +114,14 @@ class DhcpFields(unittest.TestCase):
for target in ("54", "option:54", "server_id", "opt54"):
self.assertTrue(check("%s == 10.0.0.1" % target, reply).ok, target)
def test_the_boot_file_prefers_option_67(self):
def test_the_boot_file_is_the_header_and_never_option_67(self):
# xCAT names the loader in the BOOTP header; its firmware does not
# read option 67.
reply = offer({67: "grub2.aarch64"})
self.assertTrue(check("bootfile absent", reply).ok)
reply.fields["file"] = "pxelinux.0"
self.assertTrue(check("bootfile == grub2.aarch64", reply).ok)
self.assertTrue(check("file == pxelinux.0", reply).ok)
self.assertTrue(check("bootfile == pxelinux.0", reply).ok)
self.assertTrue(check("67 == grub2.aarch64", reply).ok)
def test_the_dns_name_prefers_option_81_and_falls_back_to_12(self):
self.assertTrue(check("dns_name == n1", offer({12: "n1"})).ok)