Thicket data repository for the EEG
at main 6.0 kB view raw
1{ 2 "id": "https://www.tunbury.org/2025/05/06/freebsd-uefi", 3 "title": "iPXE boot for FreeBSD with an UEFI BIOS", 4 "link": "https://www.tunbury.org/2025/05/06/freebsd-uefi/", 5 "updated": "2025-05-06T12:00:00", 6 "published": "2025-05-06T12:00:00", 7 "summary": "I had assumed that booting FreeBSD over the network using iPXE would be pretty simple. There is even a freebsd.ipxe file included with Netboot.xyz. However, I quickly realised that most of the Internet wisdom on this process centred around legacy BIOS rather than UEFI. When booting with UEFI, the Netboot.xyz menu omits the FreeBSD option as it only supports legacy BIOS. Even in legacy mode, it uses memdisk from the Syslinux project rather than a FreeBSD loader.", 8 "content": "<p>I had assumed that booting FreeBSD over the network using iPXE would be pretty simple. There is even a <code>freebsd.ipxe</code> file included with Netboot.xyz. However, I quickly realised that most of the Internet wisdom on this process centred around legacy BIOS rather than UEFI. When booting with UEFI, the Netboot.xyz menu omits the FreeBSD option as it only supports legacy BIOS. Even in legacy mode, it uses <code>memdisk</code> from the Syslinux project rather than a FreeBSD loader.</p>\n\n<p>FreeBSD expects to use <code>loader.efi</code> to boot and to mount the root directory over NFS based upon the DHCP scope option <code>root-path</code>. I didn’t want to provide an NFS server just for this process, but even when I gave in and set one up, it still didn’t work. I’m pleased that, in the final configuration, I didn’t need an NFS server.</p>\n\n<p>Much of the frustration around doing this came from setting the <code>root-path</code> option. FreeBSD’s <code>loader.efi</code> sends its own DHCP request to the DHCP server, ignoring the options <code>set root-path</code> or <code>set dhcp.root-path</code> configured in iPXE.</p>\n\n<p>Many <code>dhcpd.conf</code> snippets suggest a block similar to below, but usually with the comment that it doesn’t work. Most authors proceed by setting <code>root-path</code> for the entire scope.</p>\n\n<div><div><pre><code>if exists user-class and option user-class = \"FreeBSD\" {\n option root-path \"your-path\";\n}\n</code></pre></div></div>\n\n<p>I used <code>dhcpdump -i br0</code> to examine the DHCP packets. This showed an ASCII BEL character (0x07) before <code>FreeBSD</code> in the <code>user-class</code> string.</p>\n\n<div><div><pre><code> TIME: 2025-05-07 08:51:03.811\n IP: 0.0.0.0 (2:0:0:0:0:22) &gt; 255.255.255.255 (ff:ff:ff:ff:ff:ff)\n OP: 1 (BOOTPREQUEST)\n HTYPE: 1 (Ethernet)\n HLEN: 6\n HOPS: 0\n XID: 00000001\n SECS: 0\n FLAGS: 0\nCIADDR: 0.0.0.0\nYIADDR: 0.0.0.0\nSIADDR: 0.0.0.0\nGIADDR: 0.0.0.0\nCHADDR: 02:00:00:00:00:22:00:00:00:00:00:00:00:00:00:00\n SNAME: .\n FNAME: .\nOPTION: 53 ( 1) DHCP message type 3 (DHCPREQUEST)\nOPTION: 50 ( 4) Request IP address x.y.z.250\nOPTION: 54 ( 4) Server identifier x.y.z.1\nOPTION: 51 ( 4) IP address leasetime 300 (5m)\nOPTION: 60 ( 9) Vendor class identifier PXEClient\nOPTION: 77 ( 8) User-class Identification 0746726565425344 .FreeBSD\nOPTION: 55 ( 7) Parameter Request List 17 (Root path)\n\t\t\t\t\t 12 (Host name)\n\t\t\t\t\t 16 (Swap server)\n\t\t\t\t\t 3 (Routers)\n\t\t\t\t\t 1 (Subnet mask)\n\t\t\t\t\t 26 (Interface MTU)\n\t\t\t\t\t 54 (Server identifier)\n</code></pre></div></div>\n\n<p>There is a <code>substring</code> command, so I was able to set the <code>root-path</code> like this successfully:</p>\n\n<div><div><pre><code>if exists user-class and substring ( option user-class, 1, 7 ) = \"FreeBSD\" {\n option root-path \"your-path\";\n}\n</code></pre></div></div>\n\n<p>The situation is further complicated as we are using a Ubiquiti Edge router. This requires the command to be encoded as a <code>subnet-parameters</code>, which is injected into <code>/opt/vyatta/etc/dhcpd.conf</code>.</p>\n\n<div><div><pre><code>set service dhcp-server shared-network-name lab subnet x.y.z.0/24 subnet-parameters 'if exists user-class and substring( option user-class, 1, 7 ) = &amp;quot;FreeBSD&amp;quot; { option root-path &amp;quot;tftp://x.y.z.240/freebsd14&amp;quot;;}'\n</code></pre></div></div>\n\n<p>The FreeBSD 14.2 installation <a href=\"https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/14.2/FreeBSD-14.2-RELEASE-amd64-disc1.iso\">ISO</a> contains the required <code>boot/loader.efi</code>, but we cannot use the extracted ISO as a root file system.</p>\n\n<p>Stage <code>loader.efi</code> on a TFTP server; in my case, the TFTP root is <code>/netbootxyz/config/menus</code>. The IPXE file only needs to contain the <code>chain</code> command.</p>\n\n<div><div><pre><code>#!ipxe\nchain loader.efi\n</code></pre></div></div>\n\n<p>Download <a href=\"https://mfsbsd.vx.sk/files/iso/14/amd64/mfsbsd-14.2-RELEASE-amd64.iso\">mfsBSD</a>, and extract the contents to a subfolder on the TFTP server. I went <code>freebsd14</code>. This ISO contains the kernel, <code>loader.conf</code> and the a minimal root file system, <code>mfsroot.gz</code>.</p>\n\n<p>With the content of mfsBSD ISO staged on the TFTP server and the modification to the DHCP scope options, the machine will boot into FreeBSD. Sign in with <code>root</code>/<code>mfsroot</code> and invoke <code>bsdinstall</code>.</p>\n\n<p>On real hardware, rather than QEMU, I found that I needed to explicitly set the serial console by adding these lines to the end of <code>boot/loader.conf</code>/</p>\n\n<div><div><pre><code># Serial console\nconsole=\"comconsole\"\ncomconsole_port=\"0x2f8\"\ncomconsole_speed=\"115200\"\n</code></pre></div></div>", 9 "content_type": "html", 10 "author": { 11 "name": "Mark Elvers", 12 "email": "mark.elvers@tunbury.org", 13 "uri": null 14 }, 15 "categories": [ 16 "FreeBSD,UEFI,iPXE", 17 "tunbury.org" 18 ], 19 "source": "https://www.tunbury.org/atom.xml" 20}