Force machines to chainload through Pixiecore's embedded iPXE.

This guarantees that we load the real OS from an iPXE with a known
featureset, rather than rely on the firmware iPXEs to be correct.

Also switch to ipxe.pxe for BIOS boots instead of undionly.kpxe.

ipxe.pxe works when you chainload from one iPXE to another, whereas
undionly.kpxe encounters some kind of poorly explained bug where
it loses the ability to configure networking.

Tested against the following configurations:
 - VirtualBox + BIOS w/ iPXE
 - VirtualBox + BIOS w/ Intel UNDI
 - VirtualBox + EFI
 - KVM + SeaBIOS w/ iPXE
 - KVM + OVMF (EFI)
 - Dell R610 + Dell BIOS/PXE

Fixes #51, fixes #52.
This commit is contained in:
David Anderson 2017-12-24 20:45:09 -08:00
parent 5f8e425f57
commit 029be8d339
6 changed files with 77 additions and 39 deletions

View File

@ -21,7 +21,7 @@ import (
)
func main() {
cli.Ipxe[pixiecore.FirmwareX86PC] = ipxe.MustAsset("undionly.kpxe")
cli.Ipxe[pixiecore.FirmwareX86PC] = ipxe.MustAsset("ipxe.pxe")
cli.Ipxe[pixiecore.FirmwareEFI32] = ipxe.MustAsset("ipxe-i386.efi")
cli.Ipxe[pixiecore.FirmwareEFI64] = ipxe.MustAsset("ipxe-x86_64.efi")
cli.Ipxe[pixiecore.FirmwareEFIBC] = ipxe.MustAsset("ipxe-x86_64.efi")

13
pixiecore/README.testing Normal file
View File

@ -0,0 +1,13 @@
# Testing pixiecore
This is a braindump of the different hardware configurations that
exhibit different behaviors.
| Machine | Firmware | PXE firmware | Notes |
| --- | --- | --- | --- |
| Proxmox VM | SeaBIOS | iPXE | |
| Proxmox VM | OVMF | OVMF | Good UEFI baseline, many UEFI firmwares are derived from OVMF/TianoCore/EDK2e |
| VirtualBox (OSS) | VirtualBox BIOS | customized iPXE | Cannot boot with `next-server` and `filename`, must be booted with iPXE-specific commands |
| VirtualBox (w/ extension pack) | VirtualBox BIOS | Intel UNDI | Good BIOS baseline, Intel UNDI is the basis for many BIOS PXE firmwares |
| VirtualBox (OSS) | VirtualBox EFI | ?? | Unclear what the EFI firmware is, but provides more diversity |
| Dell R610 | Dell BIOS | Dell | The only bare metal machine I have right now with remote management. Strange custom firmwares, also a decent variety test. |

View File

@ -17,6 +17,8 @@
set attempts:int32 10
set x:int32 0
set user-class pixiecore
# Try to get a filename from ProxyDHCP, retrying a couple of times if
# we fail.
:loop

View File

@ -36,7 +36,7 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) error {
s.debug("DHCP", "Ignoring packet from %s: %s", pkt.HardwareAddr, err)
continue
}
mach, isIpxe, fwtype, err := s.validateDHCP(pkt)
mach, client, fwtype, err := s.validateDHCP(pkt)
if err != nil {
s.log("DHCP", "Unusable packet from %s: %s", pkt.HardwareAddr, err)
continue
@ -56,7 +56,7 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) error {
}
s.log("DHCP", "Offering to boot %s", pkt.HardwareAddr)
if isIpxe {
if client == clientSoftwarePixiecoreIPXE {
s.machineEvent(pkt.HardwareAddr, machineStateProxyDHCPIpxe, "Offering to boot iPXE")
} else {
s.machineEvent(pkt.HardwareAddr, machineStateProxyDHCP, "Offering to boot")
@ -69,7 +69,7 @@ func (s *Server) serveDHCP(conn *dhcp4.Conn) error {
continue
}
resp, err := s.offerDHCP(pkt, mach, serverIP, isIpxe, fwtype)
resp, err := s.offerDHCP(pkt, mach, serverIP, client, fwtype)
if err != nil {
s.log("DHCP", "Failed to construct ProxyDHCP offer for %s: %s", pkt.HardwareAddr, err)
continue
@ -94,14 +94,22 @@ func (s *Server) isBootDHCP(pkt *dhcp4.Packet) error {
return nil
}
func (s *Server) validateDHCP(pkt *dhcp4.Packet) (mach Machine, isIpxe bool, fwtype Firmware, err error) {
type clientSoftware int
const (
clientSoftwareGeneric clientSoftware = iota
clientSoftwareIPXE
clientSoftwarePixiecoreIPXE
)
func (s *Server) validateDHCP(pkt *dhcp4.Packet) (mach Machine, client clientSoftware, fwtype Firmware, err error) {
fwt, err := pkt.Options.Uint16(93)
if err != nil {
return mach, false, 0, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err)
return mach, 0, 0, fmt.Errorf("malformed DHCP option 93 (required for PXE): %s", err)
}
fwtype = Firmware(fwt)
if s.Ipxe[fwtype] == nil {
return mach, false, 0, fmt.Errorf("unsupported client firmware type '%d' (please file a bug!)", fwtype)
return mach, 0, 0, fmt.Errorf("unsupported client firmware type '%d' (please file a bug!)", fwtype)
}
guid := pkt.Options[97]
@ -114,41 +122,30 @@ func (s *Server) validateDHCP(pkt *dhcp4.Packet) (mach Machine, isIpxe bool, fwt
// well accept these buggy ROMs.
case 17:
if guid[0] != 0 {
return mach, false, 0, errors.New("malformed client GUID (option 97), leading byte must be zero")
return mach, 0, 0, errors.New("malformed client GUID (option 97), leading byte must be zero")
}
default:
return mach, false, 0, errors.New("malformed client GUID (option 97), wrong size")
return mach, 0, 0, errors.New("malformed client GUID (option 97), wrong size")
}
// iPXE options
supportsHTTP, supportsBzImage := false, false
if len(pkt.Options[175]) > 0 {
bs := pkt.Options[175]
for len(bs) > 0 {
if len(bs) < 2 || len(bs)-2 < int(bs[1]) {
return mach, false, 0, errors.New("malformed iPXE option")
}
switch bs[0] {
case 19:
// This iPXE build supports HTTP.
supportsHTTP = true
case 24:
// This iPXE build supports bzImage.
supportsBzImage = true
}
bs = bs[2+int(bs[1]):]
// We need to identify two special kinds of PXE clients: generic
// iPXE, and the iPXE embedded in Pixiecore.
client = clientSoftwareGeneric
if userClass, err := pkt.Options.String(77); err == nil {
switch userClass {
case "iPXE":
client = clientSoftwareIPXE
case "pixiecore":
client = clientSoftwarePixiecoreIPXE
}
}
// This firmware is an appropriate iPXE if it can speak HTTP and
// bzImage. If not, we'll chainload our own internal iPXE.
isIpxe = supportsHTTP && supportsBzImage
mach.MAC = pkt.HardwareAddr
mach.Arch = fwToArch[fwtype]
return mach, isIpxe, fwtype, nil
return mach, client, fwtype, nil
}
func (s *Server) offerDHCP(pkt *dhcp4.Packet, mach Machine, serverIP net.IP, isIpxe bool, fwtype Firmware) (*dhcp4.Packet, error) {
func (s *Server) offerDHCP(pkt *dhcp4.Packet, mach Machine, serverIP net.IP, client clientSoftware, fwtype Firmware) (*dhcp4.Packet, error) {
resp := &dhcp4.Packet{
Type: dhcp4.MsgOffer,
TransactionID: pkt.TransactionID,
@ -166,11 +163,14 @@ func (s *Server) offerDHCP(pkt *dhcp4.Packet, mach Machine, serverIP net.IP, isI
resp.Options[97] = pkt.Options[97]
}
if isIpxe {
resp.BootFilename = fmt.Sprintf("http://%s:%d/_/ipxe?arch=%d&mac=%s", serverIP, s.HTTPPort, mach.Arch, mach.MAC)
} else {
switch client {
case clientSoftwareGeneric:
resp.BootServerName = serverIP.String()
resp.BootFilename = fmt.Sprintf("%s/%d", mach.MAC, fwtype)
case clientSoftwareIPXE:
resp.BootFilename = fmt.Sprintf("tftp://%s/%s/%d", serverIP, mach.MAC, fwtype)
case clientSoftwarePixiecoreIPXE:
resp.BootFilename = fmt.Sprintf("http://%s:%d/_/ipxe?arch=%d&mac=%s", serverIP, s.HTTPPort, mach.Arch, mach.MAC)
}
if fwtype == FirmwareX86PC {

View File

@ -5,11 +5,11 @@ ipxe:
git clone git://git.ipxe.org/ipxe.git
(cd ipxe && git rev-parse HEAD >COMMIT-ID)
rm -rf ipxe/.git
(cd ipxe/src && make bin/undionly.kpxe EMBED=../../../pixiecore/boot.ipxe)
(cd ipxe/src && make bin/ipxe.pxe EMBED=../../../pixiecore/boot.ipxe)
(cd ipxe/src && make bin-x86_64-efi/ipxe.efi EMBED=../../../pixiecore/boot.ipxe)
(cd ipxe/src && make bin-i386-efi/ipxe.efi EMBED=../../../pixiecore/boot.ipxe)
(cd ipxe && rm -rf bin && mkdir bin)
mv -f ipxe/src/bin/undionly.kpxe ipxe/bin/undionly.kpxe
mv -f ipxe/src/bin/ipxe.pxe ipxe/bin/ipxe.pxe
mv -f ipxe/src/bin-x86_64-efi/ipxe.efi ipxe/bin/ipxe-x86_64.efi
mv -f ipxe/src/bin-i386-efi/ipxe.efi ipxe/bin/ipxe-i386.efi
go-bindata -o ipxe/ipxe-bin.go -pkg ipxe -nometadata -nomemcopy -prefix ipxe/bin/ ipxe/bin

File diff suppressed because one or more lines are too long