Single file EFI booting over PXE with Grub.
Introduction
I had an issue where I had no other computer and needed to netboot into linux. However, PXEBoot chaining was not an option, PXE over HTTP wouldn't work, and I was stuck. As such, booting into linux becomes difficult.
Linux supports something called UKIs, Unified Kernal Image. Most commonly, Linux is deployed via independent EFI programs, Grub or Systemd-boot. However, Linux can use a more minimal EFISTUB, which can be used to independently load Linux into memory. This can combine initramfs and the kernel, into a unified (kernel) image. Some examples are as such:
&&
For OFL: I was able to get it to boot and run easily. It's only 20mb. However, it lacked hardware support, and was unable to actually modify my system or mount any disks.
For Archboot: Its over 300mb. I was unable to get it to load into memory, over legacy PXE.
I tried making UKIs with ukify, one with VOID, and another with Alpine VIRT. Neither succesfully were able to control the TTY, and allow me to interact with it. I gave up on UKIs for this route, I think this edge case requires a more robust solution than what EFISTUB offers.
In this guide, we will be using Grub embedded with Linux. That isn't to say that UKIs can't work in this situation, and, I'm telling you about them specifically in the case that my solution doesn't work for you.
The anatomy of basal Linux
Linux, in its initial boot phase is comprised of two parts:
- initramfs
- A given kernel
The initramfs is a temporary file system (tempfs) used initially for Linux to initialize. Both are loaded into memory by an EFI program, be it grub, systemd-boot, or Linux's own EFISTUB. This is all Linux needs to be "Linux", run a shell, and use drivers to display to a tty; and its all we need to connect to grub to make a Linux we can deliver over the internet.
Android requirements
- A few gb free (maybe even 1gb max)
- Termux
- proot-distro support
- Access to your router's configuration
TFTP...
The Trivial File Tranfer Protocol is a UDP based FTP protocol, typically bounded to port 69. Created in 1981, It was developed within the context of other early network protocols like FTP. TFTP is simple, much simpler than FTP, and negotation is usually based around Block Size, Timeout Interval (because its over UDP), and Transfer Size. If a host and client enter negotiations (which doesn't always happen), and cannot agree on these 3 variables, the client/host may refuse to download files. This is an issue because PXE firmware can expect very specific behavior, and certain servers may be unable to accommodate. This is father complicated by reports of buggy PXE clients implantations in the firmware of various motherboards.
PXE clients use TFTP because of its simplicity to implement. Firmware developers do not have the luxuries afforded to userland developers. Thus, to serve a EFI image to a motherboard over legacy PXE Boot, you must host a TFTP server. However, android has very limited support for TFTP servers, though they do exist. We will be using tftpd via busybox, and I have not found any other server which works for this, even when compiled for android.
Hosting
In The Router:
To avoid the complications of hosting on android, one might prefer to host on the router.
RouterOS, for example, includes a builtin TFTP server, which can allow for rapid deployment. This can host your EFI files effectively, and requires no port forwarding features
From Android;
Assuming you cant host from your router for whatever reason, In order to host a TFTP server on Android: we must have a way to get around Linux's control of ports under 1023. As far as I understand, Option 66 does not allow you to specify the port.
The BusyBox server we're using is very minimal, and may not work on all otherboards. If this implementation does not work, you will have to find a different route. Many routers have more mature TFTP servers which actually run on port 69.
pkg install busybox;
busybox udpsvd \
-vE 0.0.0.0 8080 busybox tftpd -l -r ./dist/;
Port 8080 assumes you cant run it under SUDO for port 69. If your Android is rooted, you can simply put port 69 and skip the following section.
This allows you to serve ./dist on TFTP on port 8080. However, your firmware is looking on port 69.
Routing
You must be on the same network as your computer. Mobile providers will often prevent you from hosting servers.
If you're hosting on port 69 just skip to the DHCP section.
I only know if this works on RouterOS.
We must use DSTNAT to properly route packets from port 69 -> port 8080, and allow our phone to respond.
First, we must know your inet address.
ifconfig
Your address should look like the following: inet addr:192.168.88.15.
Next we need your public IP:
curl ip.me
> 24.90.250.149
The following NAT rules must be made:
| Port Forward | Network reroute | |
|---|---|---|
| Chain | DSTNAT | SRCNAT |
| SRC. Address | 192.168.88.00/24 | |
| DST. Address | Your Public Ip | |
| PROTOCOL | UDP | UDP |
| DST-PORT | 69 | |
| Action | DST-NAT | Masquerade |
| To Address | Your INET | |
| To Port | 8080 |
It works as such
┌───────┐
│Device │
│ │
└───────┘
▼ ▼
┌─────────┐
│ Router │
│ │
└─────────┘
│ │
┌───┘ └───┐
│ ▼
│ ┌──────────┐
│ │Public IP │
│ │ │
│ └──────────┘
│ ▼
│ ┌──────────┐
│ │ SRC-NAT │
│ │ │
│ └──────────┘
│ ▼
│ ┌─────────────────┐
│ │Return to router │
│ │ │
│ └─────────────────┘
└───┐ │
│ │
▼ ▼
┌────────┐
│Port 69 │
│ │
└────────┘
▼
┌──────────┐
│ DST-NAT │
│ │
└──────────┘
▼
┌─────────────────────────────┐
│Port 8080 INET 192.168.88.xx │
│ │
└─────────────────────────────┘
The first rule is a very general rule. Requests for port 69 go to port 8080 on your phone's INET. The second rule allows your router to communicate to your phone over your public IP address.
Its generally speaking, very difficult for your router's NAT rules to change routing over the bridge. By using your Public IP, you can bypass this limitation. A packet leaving your router and re-entering can be re-routed much easier.
Building
In order to actually use grub-mkstandalone you need a more proper debian enviorment. Termux has implemented chroot systems on android via proot-distro.
pkg install proot-distro
proot-distro install debian
proot-distro login debian
sudo dpkg --add-architecture amd64
sudo apt update
sudo apt install grub-common grub-pc-bin grub-efi-amd64-bin \
xorriso mtools build-essential nasm qemu-system-x86
The following grub.cfg script must be made:
echo "
# Loads the linux module
insmod linux
# loads linux into memory
linux (memdisk)/di-linux console=tty0
# loads the ramdisk
initrd (memdisk)/di-initrd.gz
# boots
boot" > grub.cfg
This is the most minimal grub you can possibly establish. But its all we need.
The final step is to pick your distribution. Most distributions Will not work. The distribution must be based around just the initramfs and kernel. The one that worked for me was Debian net-boot.
Debian releases their distributions in open parts, so you can fetch what you need.
wget -O net-debian-linux https://deb.debian.org/debian/dists/stable/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux
wget -O net-debian-initrd.gz https://deb.debian.org/debian/dists/stable/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz grub-mkstandalone \
-O x86_64-efi \
-o grub.efi \
--modules="normal linux memdisk tar insmod" \
"boot/grub/grub.cfg=grub.cfg" \
"di-linux=net-debian-linux" \
"di-initrd.gz=net-debian-initrd.gz"
This is produces a bootable grub.efi. You put this in ./dist folder.
DHCP
A DHCP is the name server of your router. It gives each device is own INET. It is the first server a computer talks to before it can establish connections, so it can pass on critical information. These options are numbered, and each one has a single byte value. There are ~130 defined DHCP values with distinct meanings according to IANA BOOTP-DHCP Parameters Registry. Critical information includes: 'Option 3: Router Address', 'Option 12: Hostname', etc.
In order to PXE Boot, we must assign 2 lesser used values. Option 66 and Option 67
- Option 66: "Server Name" - This is the IP address of the TFTP server which will provide the EFI image to boot from
- Option 67: "Bootfile-Name" - This is the name of the bootfile to be used.
If your android is not rooted, and you configured the DSTNAT properly, your Option 66 should be your Public IP Address. Otheriwse, if you are hosting on port 69, it should be your INET IP Address.
- Option 66: "Server Name" - This is the IP address of the TFTP server which will provide the EFI image to boot from
- Option 67: "Bootfile-Name" - This is the name of the bootfile to be used.
For an example:
- Option 66: 95.4.35.95
- Option 67: grub.efi
Testing
In order to ensure that your TFTP server is serving, and working at all:
busybox tftp -g -r grub.efi -l /dev/null $INET 8080
If it downloads, then, we know the server is working locally.
If you followed the routing rules correctly, the following should work fine:
busybox tftp -g -r grub.efi -l /dev/null $PUBLICIP
If it downloads, then, we know the server is working for your entire inet.
END
With this, you should be ready to install boot directly into debian's netboot without PXE.