v0.6.1: latest iPXE + automatic NIC driver fallback (more devices, zero toggle)

Mirrors the worthwhile device-support wins from iVentoy 1.0.24→1.0.35 onto our
(very different) proxy-DHCP + iPXE-chainload architecture. iVentoy's other
changes are inapplicable (arm64-server / distro-display fixes live in its
injected Linux, which we don't have), niche (iSCSI), or closed-source
(Matrix Boot).

iPXE refreshed (mirrors 1.0.35 "Update iPXE")
- Pin the from-source build to ipxe/ipxe master @ 2026-06-09
  (95ffbf4745553e8a207922389929e1943c0237c0) — newer NIC drivers + EFI fixes.
  The pin also busts the cached ipxe-build Docker layer so the release
  actually recompiles iPXE; build-ipxe.sh now shallow-fetches an exact SHA.

Automatic NIC driver fallback (mirrors 1.0.34 "driver/boot-file mode" — but
no operator toggle, per request)
- New DriverMode {Firmware, Builtin} in core; ClientArch::ipxe_bootfile_mode
  maps each arch to either the firmware-net build (snponly/undionly, default)
  or the all-drivers build (ipxe.efi/ipxe.pxe/ipxe-i386.efi/ipxe-arm64.efi).
- The DHCP proxy serves Firmware by default — byte-for-byte unchanged, so
  hardware that boots today never regresses. A new DriverEscalation state
  machine watches for the tell-tale failure: a MAC re-PXE-boots (fresh
  firmware DISCOVER) without ever completing the iPXE-user-class handoff that
  proves the firmware NIC stack worked. That MAC is automatically escalated to
  iPXE's own NIC drivers, and the choice is sticky after a confirmed handoff
  (debounced for the :67/:4011 same-boot pair, TTL-pruned, capped). It just
  works — no settings, no UI.
- All-drivers binaries fetched per arch (ipxe.pxe + i386/arm64 native EFI;
  x86_64 ipxe.efi already built from source with PNG); ipxe-assets embeds
  *.pxe and logs availability per (arch, mode).

Core principles intact: DHCP-proxy-only, container-first, Rust-focused (the
logic is all Rust; only the iPXE fetch/build stays shell), Windows hard-rules
untouched (this never goes near Windows boot).

Validation: clippy clean; full workspace test suite green (core 99 incl. new
DriverMode tests, dhcp-proxy +4 escalation tests, http-api 31+68, iso-store
61, tftp 6, bin 2); fmt-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Miles Ward
2026-06-09 11:18:07 -04:00
co-authored by Claude Opus 4.8
parent 24879fcc90
commit 4f193cac05
12 changed files with 444 additions and 55 deletions
+110 -10
View File
@@ -23,6 +23,25 @@ pub enum ClientArch {
Unknown(u16),
}
/// Which iPXE network backend to advertise to a client (v0.6.1).
///
/// OpenPXE serves [`DriverMode::Firmware`] first (the firmware's own NIC
/// stack, via `snponly`/`undionly`) and only escalates a specific MAC to
/// [`DriverMode::Builtin`] (iPXE's bundled NIC drivers) automatically, when a
/// firmware-net boot fails to chainload. There is no operator toggle — the
/// DHCP proxy decides per client.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DriverMode {
/// Reuse the firmware UNDI/SNP NIC stack (`snponly.efi`, `undionly.kpxe`).
/// Default, smallest, most reliable for chainloading.
#[default]
Firmware,
/// iPXE's own bundled NIC drivers (`ipxe.efi`, `ipxe.pxe`). Fallback for
/// hardware whose firmware NIC stack is missing or buggy.
Builtin,
}
impl ClientArch {
#[must_use]
pub fn from_option_93(value: u16) -> Self {
@@ -39,18 +58,47 @@ impl ClientArch {
/// Default iPXE binary filename to return via TFTP for this architecture.
/// Uses `snponly` variants which reuse the firmware's UNDI/SNP network
/// stack — smaller binaries and broader hardware compatibility than the
/// all-drivers-included `ipxe.efi`.
/// all-drivers-included `ipxe.efi`. Equivalent to
/// [`Self::ipxe_bootfile_mode`] with [`DriverMode::Firmware`]; kept as a
/// convenience for the common firmware-net path.
#[must_use]
pub fn ipxe_bootfile(self) -> Option<&'static str> {
Some(match self {
Self::LegacyX86 => "undionly.kpxe",
Self::Ia32Uefi => "snponly-i386.efi",
Self::X64Uefi => "snponly.efi",
// ARM32 UEFI: upstream boot.ipxe.org does not publish a prebuilt
// snponly variant for this arch. We return None so the DHCP
// proxy declines rather than advertising a file we can't serve.
Self::Arm32Uefi | Self::Unknown(_) => return None,
Self::Arm64Uefi => "snponly-arm64.efi",
self.ipxe_bootfile_mode(DriverMode::Firmware)
}
/// iPXE binary filename for this architecture under a given network
/// [`DriverMode`].
///
/// * [`DriverMode::Firmware`] — the `snponly`/`undionly` builds that reuse
/// the firmware's UNDI/SNP NIC stack. Smallest, and the most reliable
/// choice for chainloading because the firmware just proved its network
/// works by downloading the NBP. This is the default first attempt.
/// * [`DriverMode::Builtin`] — the all-drivers `ipxe.efi`/`ipxe.pxe`
/// builds that carry iPXE's *own* NIC drivers. The automatic fallback
/// for clients whose firmware NIC stack is missing or buggy (v0.6.1):
/// the DHCP proxy escalates a MAC to this mode when a firmware-net boot
/// never completes the iPXE handoff. iPXE still includes the `snp`
/// driver here too, so it degrades gracefully.
#[must_use]
pub fn ipxe_bootfile_mode(self, mode: DriverMode) -> Option<&'static str> {
Some(match (self, mode) {
// Legacy x86 BIOS: UNDI (firmware) vs full native-driver build.
(Self::LegacyX86, DriverMode::Firmware) => "undionly.kpxe",
(Self::LegacyX86, DriverMode::Builtin) => "ipxe.pxe",
// IA32 UEFI.
(Self::Ia32Uefi, DriverMode::Firmware) => "snponly-i386.efi",
(Self::Ia32Uefi, DriverMode::Builtin) => "ipxe-i386.efi",
// x86_64 UEFI — the overwhelmingly common modern client.
(Self::X64Uefi, DriverMode::Firmware) => "snponly.efi",
(Self::X64Uefi, DriverMode::Builtin) => "ipxe.efi",
// ARM64 UEFI.
(Self::Arm64Uefi, DriverMode::Firmware) => "snponly-arm64.efi",
(Self::Arm64Uefi, DriverMode::Builtin) => "ipxe-arm64.efi",
// ARM32 UEFI: upstream boot.ipxe.org publishes no prebuilt binary
// for this arch in either mode. Unknown arches likewise. Return
// None so the DHCP proxy declines rather than advertising a file
// we can't serve.
(Self::Arm32Uefi | Self::Unknown(_), _) => return None,
})
}
@@ -133,6 +181,58 @@ mod tests {
assert_eq!(ClientArch::Unknown(0xFFFF).ipxe_bootfile(), None);
}
#[test]
fn bootfile_default_is_firmware_mode() {
// The convenience method must equal the explicit Firmware mode.
for a in [
ClientArch::LegacyX86,
ClientArch::Ia32Uefi,
ClientArch::X64Uefi,
ClientArch::Arm64Uefi,
ClientArch::Arm32Uefi,
ClientArch::Unknown(0x99),
] {
assert_eq!(
a.ipxe_bootfile(),
a.ipxe_bootfile_mode(DriverMode::Firmware)
);
}
}
#[test]
fn builtin_mode_maps_to_all_drivers_binaries() {
assert_eq!(
ClientArch::LegacyX86.ipxe_bootfile_mode(DriverMode::Builtin),
Some("ipxe.pxe")
);
assert_eq!(
ClientArch::X64Uefi.ipxe_bootfile_mode(DriverMode::Builtin),
Some("ipxe.efi")
);
assert_eq!(
ClientArch::Ia32Uefi.ipxe_bootfile_mode(DriverMode::Builtin),
Some("ipxe-i386.efi")
);
assert_eq!(
ClientArch::Arm64Uefi.ipxe_bootfile_mode(DriverMode::Builtin),
Some("ipxe-arm64.efi")
);
// No binary for ARM32 / unknown in either mode.
assert_eq!(
ClientArch::Arm32Uefi.ipxe_bootfile_mode(DriverMode::Builtin),
None
);
assert_eq!(
ClientArch::Unknown(0x99).ipxe_bootfile_mode(DriverMode::Builtin),
None
);
}
#[test]
fn driver_mode_default_is_firmware() {
assert_eq!(DriverMode::default(), DriverMode::Firmware);
}
#[test]
fn firmware_class_detects_ipxe_over_pxeclient() {
let c = FirmwareClass::classify(Some(b"PXEClient:Arch:00007"), Some(b"iPXE"));
+1 -1
View File
@@ -21,7 +21,7 @@ pub mod settings;
pub mod sso;
pub mod wol;
pub use arch::{ClientArch, FirmwareClass};
pub use arch::{ClientArch, DriverMode, FirmwareClass};
pub use auth::{AdminAccount, AdminPublic, AdminStore};
pub use boot_log::{BootEvent, BootLog};
pub use branding::{ext_for_mime, BrandingStore, LogoSlot, ALLOWED_LOGO_MIMES, MAX_LOGO_BYTES};