Merge pull request 'fix: PXE boot Content-Length, firewall zones, UEFI improvements' (#1) from fix/pxe-boot-issues into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
173
bastion.sh
173
bastion.sh
@@ -117,14 +117,20 @@ esac
|
|||||||
command -v python3 >/dev/null || die "python3 not found"
|
command -v python3 >/dev/null || die "python3 not found"
|
||||||
command -v curl >/dev/null || die "curl not found"
|
command -v curl >/dev/null || die "curl not found"
|
||||||
|
|
||||||
if ! command -v dnsmasq >/dev/null; then
|
INSTALL_PKGS=()
|
||||||
log "Installing dnsmasq..."
|
command -v dnsmasq >/dev/null || INSTALL_PKGS+=(dnsmasq)
|
||||||
|
[[ -f /usr/share/ipxe/undionly.kpxe ]] || INSTALL_PKGS+=(ipxe-bootimgs-x86)
|
||||||
|
[[ -f /usr/share/ipxe/arm64-efi/snponly.efi ]] || INSTALL_PKGS+=(ipxe-bootimgs-aarch64)
|
||||||
|
[[ -f /usr/include/efi/efi.h ]] || INSTALL_PKGS+=(gnu-efi-devel)
|
||||||
|
|
||||||
|
if [[ ${#INSTALL_PKGS[@]} -gt 0 ]]; then
|
||||||
|
log "Installing ${INSTALL_PKGS[*]}..."
|
||||||
if command -v dnf >/dev/null; then
|
if command -v dnf >/dev/null; then
|
||||||
dnf install -y dnsmasq
|
dnf install -y "${INSTALL_PKGS[@]}"
|
||||||
elif command -v apt-get >/dev/null; then
|
elif command -v apt-get >/dev/null; then
|
||||||
apt-get install -y dnsmasq
|
apt-get install -y "${INSTALL_PKGS[@]}"
|
||||||
else
|
else
|
||||||
die "Cannot install dnsmasq — install it manually"
|
die "Cannot install packages — install manually: ${INSTALL_PKGS[*]}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -132,6 +138,7 @@ fi
|
|||||||
IFACE="${IFACE:-$(ip route | awk '/default/ {print $5; exit}')}"
|
IFACE="${IFACE:-$(ip route | awk '/default/ {print $5; exit}')}"
|
||||||
SERVER_IP="$(ip -4 addr show "$IFACE" | awk '/inet / {split($2,a,"/"); print a[1]; exit}')"
|
SERVER_IP="$(ip -4 addr show "$IFACE" | awk '/inet / {split($2,a,"/"); print a[1]; exit}')"
|
||||||
NETWORK="$(echo "$SERVER_IP" | awk -F. '{print $1"."$2"."$3".0"}')"
|
NETWORK="$(echo "$SERVER_IP" | awk -F. '{print $1"."$2"."$3".0"}')"
|
||||||
|
GATEWAY="$(ip route | awk '/default/ {print $3; exit}')"
|
||||||
|
|
||||||
[[ -n "$SERVER_IP" ]] || die "Cannot detect IP on interface $IFACE"
|
[[ -n "$SERVER_IP" ]] || die "Cannot detect IP on interface $IFACE"
|
||||||
log "Interface: ${BOLD}$IFACE${NC} IP: ${BOLD}$SERVER_IP${NC} Network: ${BOLD}$NETWORK${NC}"
|
log "Interface: ${BOLD}$IFACE${NC} IP: ${BOLD}$SERVER_IP${NC} Network: ${BOLD}$NETWORK${NC}"
|
||||||
@@ -177,10 +184,10 @@ cleanup() {
|
|||||||
|
|
||||||
if $FW_OPENED && command -v firewall-cmd >/dev/null; then
|
if $FW_OPENED && command -v firewall-cmd >/dev/null; then
|
||||||
log "Removing firewall rules..."
|
log "Removing firewall rules..."
|
||||||
firewall-cmd --quiet --remove-service=dhcp 2>/dev/null || true
|
firewall-cmd --quiet ${FW_ZONE_FLAG:-} --remove-service=dhcp 2>/dev/null || true
|
||||||
firewall-cmd --quiet --remove-service=tftp 2>/dev/null || true
|
firewall-cmd --quiet ${FW_ZONE_FLAG:-} --remove-service=tftp 2>/dev/null || true
|
||||||
firewall-cmd --quiet --remove-port=${HTTP_PORT}/tcp 2>/dev/null || true
|
firewall-cmd --quiet ${FW_ZONE_FLAG:-} --remove-port=${HTTP_PORT}/tcp 2>/dev/null || true
|
||||||
firewall-cmd --quiet --remove-port=4011/udp 2>/dev/null || true
|
firewall-cmd --quiet ${FW_ZONE_FLAG:-} --remove-port=4011/udp 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "State preserved in $STATEFILE"
|
log "State preserved in $STATEFILE"
|
||||||
@@ -188,7 +195,7 @@ cleanup() {
|
|||||||
}
|
}
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
# ──── Download artifacts (cached) ─────────────────────────────────
|
# ──── Prepare boot artifacts ─────────────────────────────────────
|
||||||
download() {
|
download() {
|
||||||
local url="$1" dest="$2" label="$3"
|
local url="$1" dest="$2" label="$3"
|
||||||
if [[ -f "$dest" ]]; then
|
if [[ -f "$dest" ]]; then
|
||||||
@@ -196,17 +203,83 @@ download() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
log " ${label} — downloading..."
|
log " ${label} — downloading..."
|
||||||
curl -# -L -o "$dest" "$url" || die "Failed to download $label from $url"
|
curl -# -L -f -o "$dest" "$url" || die "Failed to download $label from $url"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_if_missing() {
|
||||||
|
local src="$1" dest="$2" label="$3"
|
||||||
|
if [[ -f "$dest" ]]; then
|
||||||
|
log " ${label} — cached"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
[[ -f "$src" ]] || die "${label}: source not found at $src"
|
||||||
|
cp "$src" "$dest"
|
||||||
|
log " ${label} — copied from $src"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_pxeloader() {
|
||||||
|
local src="$1" dest="$2" label="$3"
|
||||||
|
|
||||||
|
if [[ -f "$dest" ]]; then
|
||||||
|
log " ${label} — cached ($(stat -c%s "$dest") bytes)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log " ${label} — building UEFI PXE loader stub..."
|
||||||
|
|
||||||
|
local builddir="$BASTION_DIR/pxeloader-build"
|
||||||
|
mkdir -p "$builddir"
|
||||||
|
|
||||||
|
local gnuefi_lib gnuefi_inc
|
||||||
|
gnuefi_lib="/usr/lib"
|
||||||
|
gnuefi_inc="/usr/include/efi"
|
||||||
|
|
||||||
|
# Compile
|
||||||
|
gcc -I"$gnuefi_inc" -I"$gnuefi_inc/x86_64" -I"$gnuefi_inc/protocol" \
|
||||||
|
-DGNU_EFI_USE_MS_ABI -fPIC -fshort-wchar -ffreestanding \
|
||||||
|
-fno-stack-protector -mno-red-zone -maccumulate-outgoing-args \
|
||||||
|
-Wall -Os -c -o "$builddir/pxeloader.o" "$src" || die "PXE loader compile failed"
|
||||||
|
|
||||||
|
# Link
|
||||||
|
ld -nostdlib -znocombreloc -shared -Bsymbolic \
|
||||||
|
-T "$gnuefi_lib/elf_x86_64_efi.lds" \
|
||||||
|
"$gnuefi_lib/crt0-efi-x86_64.o" \
|
||||||
|
"$builddir/pxeloader.o" \
|
||||||
|
-o "$builddir/pxeloader.so" \
|
||||||
|
-lgnuefi -lefi -L"$gnuefi_lib" || die "PXE loader link failed"
|
||||||
|
|
||||||
|
# Convert to PE/COFF EFI binary
|
||||||
|
objcopy -j .text -j .sdata -j .data -j .dynamic -j .rodata -j .dynsym \
|
||||||
|
-j .rel -j .rela -j .rel.* -j .rela.* -j .rel* -j .rela* \
|
||||||
|
-j .reloc --target efi-app-x86_64 \
|
||||||
|
"$builddir/pxeloader.so" "$dest" || die "PXE loader objcopy failed"
|
||||||
|
|
||||||
|
local size
|
||||||
|
size="$(stat -c%s "$dest")"
|
||||||
|
log " ${label} — built (${size} bytes / $((size/1024)) KB)"
|
||||||
}
|
}
|
||||||
|
|
||||||
FEDORA_MIRROR="https://download.fedoraproject.org/pub/fedora/linux/releases/${FEDORA_VERSION}/Everything/${ARCH}/os"
|
FEDORA_MIRROR="https://download.fedoraproject.org/pub/fedora/linux/releases/${FEDORA_VERSION}/Everything/${ARCH}/os"
|
||||||
|
|
||||||
log "Fetching boot artifacts (Fedora ${FEDORA_VERSION} ${ARCH})..."
|
log "Preparing boot artifacts (Fedora ${FEDORA_VERSION} ${ARCH})..."
|
||||||
download "https://boot.ipxe.org/undionly.kpxe" "$TFTPDIR/undionly.kpxe" "iPXE BIOS"
|
copy_if_missing "/usr/share/ipxe/undionly.kpxe" "$TFTPDIR/undionly.kpxe" "iPXE BIOS"
|
||||||
download "https://boot.ipxe.org/ipxe.efi" "$TFTPDIR/ipxe.efi" "iPXE UEFI x86_64"
|
|
||||||
download "https://boot.ipxe.org/arm64-efi/snponly.efi" "$TFTPDIR/ipxe-arm64.efi" "iPXE UEFI arm64"
|
# UEFI x86_64: two-stage PXE boot
|
||||||
download "${FEDORA_MIRROR}/images/pxeboot/vmlinuz" "$HTTPDIR/vmlinuz" "Fedora kernel"
|
# Stage 1: tiny PXE loader stub (<20KB) fits in constrained TFTP buffers
|
||||||
download "${FEDORA_MIRROR}/images/pxeboot/initrd.img" "$HTTPDIR/initrd.img" "Fedora initrd"
|
# Stage 2: full iPXE binary downloaded via UEFI PXE protocol (no size limit)
|
||||||
|
PXELOADER_SRC="$(cd "$(dirname "$0")" && pwd)/pxeloader.c"
|
||||||
|
[[ -f "$PXELOADER_SRC" ]] || PXELOADER_SRC="$(dirname "${BASH_SOURCE[0]}")/pxeloader.c"
|
||||||
|
build_pxeloader "$PXELOADER_SRC" "$TFTPDIR/ipxe.efi" "PXE loader stub (stage 1)"
|
||||||
|
copy_if_missing "/usr/share/ipxe/ipxe-snponly-x86_64.efi" "$TFTPDIR/ipxe-real.efi" "iPXE UEFI x86_64 (stage 2)"
|
||||||
|
|
||||||
|
copy_if_missing "/usr/share/ipxe/arm64-efi/snponly.efi" "$TFTPDIR/ipxe-arm64.efi" "iPXE UEFI arm64"
|
||||||
|
download "${FEDORA_MIRROR}/images/pxeboot/vmlinuz" "$HTTPDIR/vmlinuz" "Fedora kernel"
|
||||||
|
download "${FEDORA_MIRROR}/images/pxeboot/initrd.img" "$HTTPDIR/initrd.img" "Fedora initrd"
|
||||||
|
|
||||||
|
# Symlink iPXE binaries into HTTP dir (UEFI HTTP Boot downloads via HTTP, not TFTP)
|
||||||
|
for f in "$TFTPDIR"/*.efi; do
|
||||||
|
ln -sf "$f" "$HTTPDIR/$(basename "$f")" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
# ──── Generate discovery kickstart ────────────────────────────────
|
# ──── Generate discovery kickstart ────────────────────────────────
|
||||||
# Boots Fedora installer env, collects hardware info, POSTs to bastion, powers off.
|
# Boots Fedora installer env, collects hardware info, POSTs to bastion, powers off.
|
||||||
@@ -492,18 +565,23 @@ def print_install_started(mac, hostname):
|
|||||||
# ── HTTP Handler ──────────────────────────────────────────────────
|
# ── HTTP Handler ──────────────────────────────────────────────────
|
||||||
|
|
||||||
class BastionHandler(SimpleHTTPRequestHandler):
|
class BastionHandler(SimpleHTTPRequestHandler):
|
||||||
|
protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, directory=HTTP_DIR, **kwargs)
|
super().__init__(*args, directory=HTTP_DIR, **kwargs)
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
"""Suppress default HTTP access logs — we have our own output."""
|
"""Log HTTP requests to help debug boot issues."""
|
||||||
pass
|
print(f" HTTP: {self.client_address[0]} {self.command} {self.path}", flush=True)
|
||||||
|
|
||||||
def send_text(self, code, text, content_type="text/plain"):
|
def send_text(self, code, text, content_type="text/plain"):
|
||||||
|
data = text.encode()
|
||||||
self.send_response(code)
|
self.send_response(code)
|
||||||
self.send_header("Content-Type", content_type)
|
self.send_header("Content-Type", content_type)
|
||||||
|
self.send_header("Content-Length", str(len(data)))
|
||||||
|
self.send_header("Connection", "close")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(text.encode())
|
self.wfile.write(data)
|
||||||
|
|
||||||
def send_json(self, code, data):
|
def send_json(self, code, data):
|
||||||
self.send_text(code, json.dumps(data, indent=2), "application/json")
|
self.send_text(code, json.dumps(data, indent=2), "application/json")
|
||||||
@@ -548,7 +626,7 @@ echo Collecting hardware info...
|
|||||||
echo =============================================
|
echo =============================================
|
||||||
echo
|
echo
|
||||||
|
|
||||||
kernel http://{SERVER_IP}:{HTTP_PORT}/vmlinuz inst.ks=http://{SERVER_IP}:{HTTP_PORT}/discover.ks inst.text
|
kernel http://{SERVER_IP}:{HTTP_PORT}/vmlinuz inst.ks=http://{SERVER_IP}:{HTTP_PORT}/discover.ks inst.stage2={FEDORA_MIRROR} inst.text
|
||||||
initrd http://{SERVER_IP}:{HTTP_PORT}/initrd.img
|
initrd http://{SERVER_IP}:{HTTP_PORT}/initrd.img
|
||||||
boot
|
boot
|
||||||
"""
|
"""
|
||||||
@@ -574,6 +652,19 @@ boot
|
|||||||
self.send_json(200, load_state())
|
self.send_json(200, load_state())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# ── iPXE EFI binaries (for UEFI HTTP Boot) ──
|
||||||
|
if parsed.path in ("/ipxe.efi", "/ipxe-real.efi", "/ipxe-arm64.efi"):
|
||||||
|
tftp_dir = os.path.join(os.path.dirname(HTTP_DIR), "tftp")
|
||||||
|
fpath = os.path.join(tftp_dir, parsed.path.lstrip("/"))
|
||||||
|
if os.path.isfile(fpath):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/efi")
|
||||||
|
self.send_header("Content-Length", str(os.path.getsize(fpath)))
|
||||||
|
self.end_headers()
|
||||||
|
with open(fpath, "rb") as f:
|
||||||
|
self.wfile.write(f.read())
|
||||||
|
return
|
||||||
|
|
||||||
# ── Static files (vmlinuz, initrd, discover.ks, etc.) ──
|
# ── Static files (vmlinuz, initrd, discover.ks, etc.) ──
|
||||||
super().do_GET()
|
super().do_GET()
|
||||||
|
|
||||||
@@ -671,12 +762,14 @@ port=0
|
|||||||
|
|
||||||
# Listen on the right interface
|
# Listen on the right interface
|
||||||
interface=${IFACE}
|
interface=${IFACE}
|
||||||
bind-interfaces
|
bind-dynamic
|
||||||
|
|
||||||
$(if [[ "$DHCP_MODE" == "full" ]]; then
|
$(if [[ "$DHCP_MODE" == "full" ]]; then
|
||||||
cat << FULL_DHCP
|
cat << FULL_DHCP
|
||||||
# Full DHCP mode — bastion is the only DHCP server on this network
|
# Full DHCP mode — bastion is the only DHCP server on this network
|
||||||
dhcp-range=${DHCP_RANGE_START},${DHCP_RANGE_END},255.255.255.0,12h
|
dhcp-range=${DHCP_RANGE_START},${DHCP_RANGE_END},255.255.255.0,12h
|
||||||
|
dhcp-option=3,${GATEWAY}
|
||||||
|
dhcp-option=6,${GATEWAY}
|
||||||
FULL_DHCP
|
FULL_DHCP
|
||||||
else
|
else
|
||||||
cat << PROXY_DHCP
|
cat << PROXY_DHCP
|
||||||
@@ -688,17 +781,29 @@ fi)
|
|||||||
# TFTP for initial PXE boot
|
# TFTP for initial PXE boot
|
||||||
enable-tftp
|
enable-tftp
|
||||||
tftp-root=${TFTPDIR}
|
tftp-root=${TFTPDIR}
|
||||||
|
tftp-no-blocksize
|
||||||
|
|
||||||
# Detect client architecture
|
# Detect client architecture — PXE (TFTP) clients
|
||||||
dhcp-match=set:bios,option:client-arch,0
|
dhcp-match=set:bios,option:client-arch,0
|
||||||
dhcp-match=set:efi-x86_64,option:client-arch,7
|
dhcp-match=set:efi-x86_64,option:client-arch,7
|
||||||
dhcp-match=set:efi-x86_64,option:client-arch,9
|
dhcp-match=set:efi-x86_64,option:client-arch,9
|
||||||
dhcp-match=set:efi-arm64,option:client-arch,11
|
dhcp-match=set:efi-arm64,option:client-arch,11
|
||||||
|
|
||||||
|
# Detect client architecture — UEFI HTTP Boot clients (no TFTP size limit)
|
||||||
|
dhcp-match=set:httpboot-x86_64,option:client-arch,16
|
||||||
|
dhcp-match=set:httpboot-arm64,option:client-arch,20
|
||||||
|
|
||||||
# Detect iPXE clients (already chainloaded)
|
# Detect iPXE clients (already chainloaded)
|
||||||
dhcp-userclass=set:ipxe,iPXE
|
dhcp-userclass=set:ipxe,iPXE
|
||||||
|
|
||||||
# First PXE boot → serve iPXE binary via TFTP
|
# UEFI HTTP Boot → serve full iPXE EFI via HTTP (no TFTP size limit)
|
||||||
|
dhcp-boot=tag:httpboot-x86_64,http://${SERVER_IP}:${HTTP_PORT}/ipxe-real.efi
|
||||||
|
dhcp-boot=tag:httpboot-arm64,http://${SERVER_IP}:${HTTP_PORT}/ipxe-arm64.efi
|
||||||
|
# Echo vendor class back to HTTP Boot clients (required by UEFI HTTP Boot spec)
|
||||||
|
dhcp-option-force=tag:httpboot-x86_64,60,HTTPClient
|
||||||
|
dhcp-option-force=tag:httpboot-arm64,60,HTTPClient
|
||||||
|
|
||||||
|
# First PXE boot → serve iPXE binary via TFTP (BIOS and UEFI fallback)
|
||||||
dhcp-boot=tag:bios,tag:!ipxe,undionly.kpxe
|
dhcp-boot=tag:bios,tag:!ipxe,undionly.kpxe
|
||||||
dhcp-boot=tag:efi-x86_64,tag:!ipxe,ipxe.efi
|
dhcp-boot=tag:efi-x86_64,tag:!ipxe,ipxe.efi
|
||||||
dhcp-boot=tag:efi-arm64,tag:!ipxe,ipxe-arm64.efi
|
dhcp-boot=tag:efi-arm64,tag:!ipxe,ipxe-arm64.efi
|
||||||
@@ -706,17 +811,27 @@ dhcp-boot=tag:efi-arm64,tag:!ipxe,ipxe-arm64.efi
|
|||||||
# iPXE clients → chain to boot script via HTTP
|
# iPXE clients → chain to boot script via HTTP
|
||||||
dhcp-boot=tag:ipxe,http://${SERVER_IP}:${HTTP_PORT}/boot.ipxe
|
dhcp-boot=tag:ipxe,http://${SERVER_IP}:${HTTP_PORT}/boot.ipxe
|
||||||
|
|
||||||
|
# PXE service directives (needed for proxy DHCP to respond properly)
|
||||||
|
pxe-service=tag:!ipxe,x86PC,"PXE Boot",undionly.kpxe
|
||||||
|
pxe-service=tag:!ipxe,X86-64_EFI,"PXE Boot",ipxe.efi
|
||||||
|
pxe-service=tag:!ipxe,BC_EFI,"PXE Boot",ipxe.efi
|
||||||
|
pxe-service=tag:!ipxe,ARM64_EFI,"PXE Boot",ipxe-arm64.efi
|
||||||
|
|
||||||
# Verbose logging
|
# Verbose logging
|
||||||
log-dhcp
|
log-dhcp
|
||||||
DNSMASQ
|
DNSMASQ
|
||||||
|
|
||||||
# ──── Open firewall ──────────────────────────────────────────────
|
# ──── Open firewall ──────────────────────────────────────────────
|
||||||
if command -v firewall-cmd >/dev/null && firewall-cmd --state >/dev/null 2>&1; then
|
if command -v firewall-cmd >/dev/null && firewall-cmd --state >/dev/null 2>&1; then
|
||||||
log "Opening firewall ports (DHCP, TFTP, HTTP:${HTTP_PORT})..."
|
# Detect the zone for our interface (may differ from default zone)
|
||||||
firewall-cmd --quiet --add-service=dhcp
|
FW_ZONE="$(firewall-cmd --get-zone-of-interface="${IFACE}" 2>/dev/null || echo "")"
|
||||||
firewall-cmd --quiet --add-service=tftp
|
FW_ZONE_FLAG=""
|
||||||
firewall-cmd --quiet --add-port=${HTTP_PORT}/tcp
|
[[ -n "$FW_ZONE" ]] && FW_ZONE_FLAG="--zone=${FW_ZONE}"
|
||||||
firewall-cmd --quiet --add-port=4011/udp 2>/dev/null || true
|
log "Opening firewall ports (DHCP, TFTP, HTTP:${HTTP_PORT})${FW_ZONE:+ in zone ${FW_ZONE}}..."
|
||||||
|
firewall-cmd --quiet ${FW_ZONE_FLAG} --add-service=dhcp
|
||||||
|
firewall-cmd --quiet ${FW_ZONE_FLAG} --add-service=tftp
|
||||||
|
firewall-cmd --quiet ${FW_ZONE_FLAG} --add-port=${HTTP_PORT}/tcp
|
||||||
|
firewall-cmd --quiet ${FW_ZONE_FLAG} --add-port=4011/udp 2>/dev/null || true
|
||||||
FW_OPENED=true
|
FW_OPENED=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
136
pxeloader.c
Normal file
136
pxeloader.c
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Tiny UEFI PXE Loader — downloads and starts a larger EFI binary via TFTP.
|
||||||
|
* Bypasses PXE ROM TFTP buffer limits and firmware LoadImage restrictions.
|
||||||
|
* All debug output removed to minimize binary size (~20KB target).
|
||||||
|
*/
|
||||||
|
#include <efi.h>
|
||||||
|
#include <efilib.h>
|
||||||
|
#include <efipxebc.h>
|
||||||
|
|
||||||
|
#define PAYLOAD "ipxe-real.efi"
|
||||||
|
|
||||||
|
static EFI_GUID PxeGuid = EFI_PXE_BASE_CODE_PROTOCOL_GUID;
|
||||||
|
static EFI_GUID LipGuid = LOADED_IMAGE_PROTOCOL;
|
||||||
|
|
||||||
|
EFI_STATUS EFIAPI
|
||||||
|
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
|
||||||
|
{
|
||||||
|
EFI_STATUS s;
|
||||||
|
EFI_PXE_BASE_CODE_PROTOCOL *Pxe = NULL;
|
||||||
|
EFI_HANDLE *H = NULL;
|
||||||
|
UINTN HC = 0;
|
||||||
|
EFI_IP_ADDRESS Srv;
|
||||||
|
|
||||||
|
InitializeLib(ImageHandle, SystemTable);
|
||||||
|
|
||||||
|
/* Find active PXE protocol */
|
||||||
|
s = uefi_call_wrapper(BS->LocateHandleBuffer, 5,
|
||||||
|
ByProtocol, &PxeGuid, NULL, &HC, &H);
|
||||||
|
if (EFI_ERROR(s)) return s;
|
||||||
|
|
||||||
|
EFI_HANDLE PxeHandle = NULL;
|
||||||
|
for (UINTN i = 0; i < HC; i++) {
|
||||||
|
uefi_call_wrapper(BS->HandleProtocol, 3, H[i], &PxeGuid, (VOID**)&Pxe);
|
||||||
|
if (Pxe && Pxe->Mode->Started) { PxeHandle = H[i]; break; }
|
||||||
|
Pxe = NULL;
|
||||||
|
}
|
||||||
|
if (!Pxe) return EFI_NOT_FOUND;
|
||||||
|
|
||||||
|
/* Get TFTP server IP from proxy DHCP */
|
||||||
|
ZeroMem(&Srv, sizeof(Srv));
|
||||||
|
if (Pxe->Mode->PxeReplyReceived)
|
||||||
|
CopyMem(&Srv.v4, &Pxe->Mode->PxeReply.Dhcpv4.BootpSiAddr, 4);
|
||||||
|
if (!Srv.v4.Addr[0] && Pxe->Mode->ProxyOfferReceived)
|
||||||
|
CopyMem(&Srv.v4, &Pxe->Mode->ProxyOffer.Dhcpv4.BootpSiAddr, 4);
|
||||||
|
if (!Srv.v4.Addr[0])
|
||||||
|
CopyMem(&Srv.v4, &Pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4);
|
||||||
|
|
||||||
|
/* Get file size */
|
||||||
|
UINT64 fsz = 0;
|
||||||
|
s = uefi_call_wrapper(Pxe->Mtftp, 10, Pxe,
|
||||||
|
EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE,
|
||||||
|
NULL, FALSE, &fsz, NULL, &Srv, (UINT8*)PAYLOAD, NULL, FALSE);
|
||||||
|
if (EFI_ERROR(s)) return s;
|
||||||
|
|
||||||
|
/* Download */
|
||||||
|
UINTN pg = ((UINTN)fsz + 4095) / 4096;
|
||||||
|
EFI_PHYSICAL_ADDRESS ba;
|
||||||
|
s = uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiBootServicesData, pg, &ba);
|
||||||
|
if (EFI_ERROR(s)) return s;
|
||||||
|
VOID *buf = (VOID*)(UINTN)ba;
|
||||||
|
|
||||||
|
s = uefi_call_wrapper(Pxe->Mtftp, 10, Pxe,
|
||||||
|
EFI_PXE_BASE_CODE_TFTP_READ_FILE,
|
||||||
|
buf, FALSE, &fsz, NULL, &Srv, (UINT8*)PAYLOAD, NULL, FALSE);
|
||||||
|
if (EFI_ERROR(s)) return s;
|
||||||
|
|
||||||
|
/* Stop PXE so iPXE can claim the SNP protocol on this handle */
|
||||||
|
uefi_call_wrapper(Pxe->Stop, 1, Pxe);
|
||||||
|
|
||||||
|
/* Parse PE32+ */
|
||||||
|
UINT8 *pe = buf;
|
||||||
|
UINT32 po = *(UINT32*)(pe + 0x3c);
|
||||||
|
UINT8 *op = pe + po + 0x18;
|
||||||
|
UINT32 erva = *(UINT32*)(op + 0x10);
|
||||||
|
UINT64 ibase = *(UINT64*)(op + 0x18);
|
||||||
|
UINT32 isz = *(UINT32*)(op + 0x38);
|
||||||
|
UINT32 hsz = *(UINT32*)(op + 0x3c);
|
||||||
|
UINT16 ns = *(UINT16*)(pe + po + 0x06);
|
||||||
|
UINT16 ohs = *(UINT16*)(pe + po + 0x14);
|
||||||
|
UINT32 ndd = *(UINT32*)(op + 0x6c);
|
||||||
|
UINT32 rrva = 0, rsz2 = 0;
|
||||||
|
if (ndd > 5) { rrva = *(UINT32*)(op+0x70+40); rsz2 = *(UINT32*)(op+0x70+44); }
|
||||||
|
|
||||||
|
/* Load image into executable memory */
|
||||||
|
UINTN ip = (isz + 4095) / 4096;
|
||||||
|
EFI_PHYSICAL_ADDRESS ia;
|
||||||
|
s = uefi_call_wrapper(BS->AllocatePages, 4, AllocateAnyPages, EfiBootServicesCode, ip, &ia);
|
||||||
|
if (EFI_ERROR(s)) return s;
|
||||||
|
UINT8 *img = (UINT8*)(UINTN)ia;
|
||||||
|
ZeroMem(img, isz);
|
||||||
|
CopyMem(img, pe, hsz);
|
||||||
|
|
||||||
|
UINT8 *st = pe + po + 0x18 + ohs;
|
||||||
|
for (UINT16 i = 0; i < ns; i++) {
|
||||||
|
UINT8 *sec = st + i*40;
|
||||||
|
UINT32 va=*(UINT32*)(sec+0x0c), rs=*(UINT32*)(sec+0x10);
|
||||||
|
UINT32 ro=*(UINT32*)(sec+0x14), vs=*(UINT32*)(sec+0x08);
|
||||||
|
UINT32 cs = rs < vs ? rs : vs;
|
||||||
|
if (rs && ro+cs <= (UINT32)fsz) CopyMem(img+va, pe+ro, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Relocate */
|
||||||
|
INT64 d = (INT64)((UINT64)(UINTN)img - ibase);
|
||||||
|
if (d && rrva && rsz2) {
|
||||||
|
UINT8 *r = img+rrva, *re = r+rsz2;
|
||||||
|
while (r < re) {
|
||||||
|
UINT32 prv=*(UINT32*)r, bsz=*(UINT32*)(r+4);
|
||||||
|
if (!bsz) break;
|
||||||
|
UINT16 *e = (UINT16*)(r+8);
|
||||||
|
for (UINTN j = 0; j < (bsz-8)/2; j++)
|
||||||
|
if ((e[j]>>12)==10) *(UINT64*)(img+prv+(e[j]&0xFFF)) += d;
|
||||||
|
r += bsz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Install EFI_LOADED_IMAGE_PROTOCOL */
|
||||||
|
EFI_LOADED_IMAGE lip;
|
||||||
|
ZeroMem(&lip, sizeof(lip));
|
||||||
|
lip.Revision = EFI_LOADED_IMAGE_PROTOCOL_REVISION;
|
||||||
|
lip.ParentHandle = ImageHandle;
|
||||||
|
lip.DeviceHandle = PxeHandle;
|
||||||
|
lip.SystemTable = SystemTable;
|
||||||
|
lip.ImageBase = img;
|
||||||
|
lip.ImageSize = isz;
|
||||||
|
lip.ImageCodeType = EfiBootServicesCode;
|
||||||
|
lip.ImageDataType = EfiBootServicesData;
|
||||||
|
|
||||||
|
EFI_HANDLE nh = NULL;
|
||||||
|
s = uefi_call_wrapper(BS->InstallProtocolInterface, 4,
|
||||||
|
&nh, &LipGuid, EFI_NATIVE_INTERFACE, &lip);
|
||||||
|
if (EFI_ERROR(s)) return s;
|
||||||
|
|
||||||
|
/* Start iPXE */
|
||||||
|
typedef EFI_STATUS (EFIAPI *ENTRY)(EFI_HANDLE, EFI_SYSTEM_TABLE*);
|
||||||
|
return ((ENTRY)(img + erva))(nh, SystemTable);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user