UEFI and PXE boot setup on ubuntu 17.10, 18.04

UPDATE 2019:

While initial instructions worked fine for me, it seems that some equipment had issues with syslinux and its configuration. This old instructions have been superseeded with current ones.

Update 2019-11-02: since this post has quite a bit of hits, i cleaned up instructions a bit: there were some unnecessary things used below (e.g. delivery of signed efi file from bios dir) which distracts from important things. Those issues are updated and configuration seems more clean now. I have also removed “old” bios configuration to see which files should go where and added kernel configuration for fedora, both live and network installation media.

Why now changes?

There are actually two reasons:

  • I have a Dell Latitude 7440 and this laptop boots fine with old (syslinux) instructions. No issues here.
  • However Lenovo Thinkpad t440s does not (it loads syslinux.efi, but then stops). Dnsmasq logs show that the file load was stopped by the client.

In order to solve this, i have replaced syslinux with grub, which seems to work great.

Note: new instructions are here for grub and uefi. Bios systems have a different configuration and has to be set up in a different way to run grub. Since i do not particularly care about bios systems anymore, those instructions have been removed.

BOOT WITH GRUB (2019 update)

Purpose of this update is to use grub instead of syslinux (as initially used).

First, download following file from ubuntu archive. This file will be delivered by dnsmasq.

Additionally, to show menus and stuff, you need grub modules. Those you can get from an Ubuntu ISO and copy them to the server:

  • use ubuntu dvd and copy files from this location on the dvd -> /boot/grub/x86_64-efi/*
  • to the target location -> /srv/pxe/grub/x86_64-efi/

The whole goal is to have the following directory structure:

[root@hp:/srv/pxe]# tree
.
├── grub
│   ├── grub.cfg
│   ├── grubnetx64.efi.signed
│   └── x86_64-efi
│       ├── acpi.mod
│       ├── adler32.mod
│       ├── *********** a whole lot of grub modules ***********
│       └── zfscrypt.mod
└── mounts
    ├── arch
    ├── fedora
    ├── fedora-net
    └── ubuntu

Contents of /srv/pxe/grub/grub.cfg should look like this:

function load_video {
  insmod efi_gop
  insmod efi_uga
  insmod video_bochs
  insmod video_cirrus
  insmod all_video
}

load_video
set gfxpayload=keep
insmod gzio
insmod part_gpt
insmod ext2
insmod http

default vesamenu.c32

menuentry 'Ubuntu' {
        linux (tftp,192.168.43.1)/mounts/ubuntu/casper/vmlinuz root=/dev/nfs rw nfsroot=192.168.43.1:/srv/pxe/mounts/ubuntu ip=dhcp preseed/url=http://192.168.43.1/pxe/ubuntu/preseed/ubuntu.seed boot=casper netboot=nfs
        initrd (tftp,192.168.43.1)/mounts/ubuntu/casper/initrd
}

menuentry 'Arch' {
        linux (tftp,192.168.43.1)/mounts/arch/arch/boot/x86_64/vmlinuz archisobasedir=arch archiso_http_srv=http://192.168.43.1/pxe/arch/ ip=:::::eth0:dhcp -
        initrd (tftp,192.168.43.1)/mounts/arch/arch/boot/x86_64/archiso.img
}

menuentry 'Fedora Live' {
	linuxefi (tftp,192.168.43.1)/mounts/fedora/images/pxeboot/vmlinuz ip=dhcp root=live:http://192.168.43.1/pxe/fedora/LiveOS/squashfs.img ro rd.live.image
	initrdefi (tftp,192.168.43.1)/mounts/fedora/images/pxeboot/initrd.img
}

menuentry 'Fedora Network' {
        linuxefi (tftp,192.168.43.1)/mounts/fedora-net/images/pxeboot/vmlinuz ip=dhcp root=live:http://192.168.43.1/pxe/fedora-net/images/install.img ro
        initrdefi (tftp,192.168.43.1)/mounts/fedora-net/images/pxeboot/initrd.img
}

Contents of /etc/dnsmaq.conf should look like this:

#general settings
port=0
interface=eno2
bind-interfaces

#dhcp
dhcp-range=192.168.43.2,192.168.43.254,48h
dhcp-option=3,192.168.43.1

#nameserver
dhcp-option=6,192.168.40.1

#caching dns server to serve lan
server=192.168.40.1
domain-needed
bogus-priv
no-resolv
no-poll
domain=lan
local=/lan/
expand-hosts
cache-size=1000
no-negcache
dnssec

#pxe
enable-tftp
dhcp-boot=boot/bios/pxelinux.0,hp,192.168.43.1
tftp-root=/srv/pxe
tftp-mtu=1500

log-dhcp

dhcp-vendorclass=BIOS,PXEClient:Arch:00000
dhcp-vendorclass=UEFI32,PXEClient:Arch:00006
dhcp-vendorclass=UEFI,PXEClient:Arch:00007
dhcp-vendorclass=UEFI64,PXEClient:Arch:00009


#dhcp-boot=net:UEFI32,boot/uefi/syslinux/efi32/syslinux.efi,,192.168.43.1
#dhcp-boot=net:UEFI,boot/uefi/syslinux.efi,,192.168.43.1
#dhcp-boot=net:UEFI64,boot/uefi/syslinux.efi,,192.168.43.1

#dhcp-boot=net:BIOS,boot/grub/i386-pc/core.0
dhcp-boot=net:UEFI,grub/grubnetx64.efi.signed,,192.168.43.1
dhcp-boot=net:UEFI64,grub/grubnetx64.efi.signed,,192.168.43.1

Done.

BOOT WITH SYSLINUX (Old instructions)

This was done mostly by trial and error, but i managed to do a pxe boot for both old bios systems and new uefi. Not well documented task online, so here is something that could serve as a guide/tutorial. Hope i didn’t leave something out… Anyhow, good luck.

Prereq:

  • syslinux binaries – download from kernel.org (don’t install them, just download and extract proper files). These files are needed from the zip archive:
    • for bios
      • bios/com32/elflink/ldlinux.c32
      • bios/com32/libutil/libutil.c32
      • bios/com32/menu/menu.c32
      • bios/com32/menu/vesamenu.c32
      • bios/core/pxelinux.0
      • bios/core/lpxelinux.0
    • for uefi
      • efi64/efi/syslinux.efi
        • this file is needed as original and as copy (reason why)
        • copy this to bootx64.efi
        • copy this to bootx64c.efi
      • efi64/com32/elflink/ldlinux/ldlinux.e64
      • efi64/com32/libutil/libutil.c32
      • efi64/com32/menu/menu.c32
      • efi64/com32/menu/vesamenu.c32
  • dnsmasq
  • nginx
  • nfs server

Location used:

  • /srv/pxe

Let’s start with what is there at the finish line.

Directory tree looks like this:

Note: boot/bios/pxelinux.cfg/default and boot/uefi/pxelinux.cfg/default are the same files!

[root@hp:/srv/pxe]# tree
.
├── boot
│   ├── bios
│   │   ├── ldlinux.c32
│   │   ├── libutil.c32
│   │   ├── lpxelinux.0
│   │   ├── menu.c32
│   │   ├── pxelinux.0
│   │   ├── pxelinux.cfg
│   │   │   └── default
│   │   └── vesamenu.c32
│   └── uefi
│       ├── bootx64c.efi
│       ├── bootx64.efi
│       ├── ldlinux.e64
│       ├── libutil.c32
│       ├── menu.c32
│       ├── pxelinux.cfg
│       │   └── default
│       ├── syslinux.efi
│       ├── syslx64.cfg
│       └── vesamenu.c32
├── mounts
│   ├── arch
│   ├── fedora-gnome
│   ├── fedora-kde
│   ├── kubuntu
│   ├── neon
│   └── opensuse
└── syslinux-6.04-pre1.zip

16 directories, 26 files

Contents of the /srv/pxe/boot/bios/pxelinux.cfg/default should look like this:

DEFAULT menu.c32
#PROMPT 0
 
MENU TITLE PXE UEFI Boot Menu
#MENU AUTOBOOT Starting Local System in # seconds
 
PROMPT 0
TIMEOUT 0

LABEL Arch
 KERNEL http://192.168.29.99/pxe/arch/arch/boot/x86_64/vmlinuz
 APPEND initrd=http://192.168.29.99/pxe/arch/arch/boot/x86_64/archiso.img archisobasedir=arch archiso_http_srv=http://192.168.29.99/pxe/arch/ ip=:::::eth0:dhcp -
EOF

MENU SEPARATOR

LABEL Opensuse Tumbleweed
 KERNEL http://192.168.29.99/pxe/opensuse/boot/x86_64/loader/linux
 APPEND initrd=http://192.168.29.99/pxe/opensuse/boot/x86_64/loader/initrd showopts acpi=force
EOF

MENU SEPARATOR

LABEL Kubuntu
 KERNEL http://192.168.29.99/pxe/kubuntu/casper/vmlinuz.efi
 APPEND initrd=http://192.168.29.99/pxe/kubuntu/casper/initrd.lz preseed/url=http://192.168.29.99/pxe/kubuntu/preseed/kubuntu.seed boot=casper netboot=nfs nfsroot=192.168.29.99:/srv/pxe/mounts/kubuntu/
EOF

LABEL Neon
 KERNEL http://192.168.29.99/pxe/neon/casper/vmlinuz.efi
 APPEND initrd=http://192.168.29.99/pxe/neon/casper/initrd.lz boot=casper netboot=nfs nfsroot=192.168.29.99:/srv/pxe/mounts/neon/
EOF

Contents of /etc/dnsmasq.conf should look like this:

#general settings
port=0
interface=br0
bind-interfaces

#dhcp
dhcp-range=192.168.29.100,192.168.29.199,48h
dhcp-option=3,192.168.29.1

#nameserver
dhcp-option=6,192.168.29.90

#caching dns server to serve lan
listen-address=127.0.0.1,192.168.29.99

#pxe
enable-tftp
dhcp-boot=boot/bios/pxelinux.0,hp,192.168.29.99
tftp-root=/srv/pxe

log-dhcp

dhcp-vendorclass=BIOS,PXEClient:Arch:00000
dhcp-vendorclass=UEFI32,PXEClient:Arch:00006
dhcp-vendorclass=UEFI,PXEClient:Arch:00007
dhcp-vendorclass=UEFI64,PXEClient:Arch:00009

dhcp-boot=net:UEFI32,boot/uefi/syslinux.efi,,192.168.29.99
dhcp-boot=net:UEFI,boot/uefi/syslinux.efi,,192.168.29.99
dhcp-boot=net:UEFI64,boot/uefi/syslinux.efi,,192.168.29.99

Contents of /etc/nginx/sites-enabled/default should look like this:

server {
 listen 80 default_server;
 listen [::]:80 default_server;

server_name _;

location / {
 # First attempt to serve request as file, then
 # as directory, then fall back to displaying a 404.
 try_files $uri $uri/ =404;
 #autoindex on;
 #root /var/www/html;
 }

location /pxe {
 alias /srv/pxe/mounts;
 autoindex on;
 }
}

Nfs mountpoints in /etc/exports look like this:

/srv/pxe/mounts 192.168.29.1/24(ro,no_subtree_check,nohide)
/srv/pxe/mounts/arch 192.168.29.1/24(ro,no_subtree_check,nohide)
/srv/pxe/mounts/opensuse 192.168.29.1/24(ro,no_subtree_check,nohide)
/srv/pxe/mounts/fedora-kde 192.168.29.1/24(ro,no_subtree_check,nohide)
/srv/pxe/mounts/fedora-gnome 192.168.29.1/24(ro,no_subtree_check,nohide)
/srv/pxe/mounts/kubuntu 192.168.29.1/24(ro,no_subtree_check,nohide)
/srv/pxe/mounts/neon 192.168.29.1/24(ro,no_subtree_check,nohide)

Let’s start with the explanations.

First and foremost, the pxe server has to also be a dhcp server. For this we’re using dnsmasq which also includes tftp server. PXE relevant config in this case is marked with #pxe part of the file. So, tftp is trivial file transfer protocol and this is used to load syslinux.efi or pxelinux.0 during boot. Those two binaries load everything else afterwards. The boot process looks like this:

  1. Dhcp server will provide ip address to client and send the file (e.g. syslinux.efi loader) marked in the dnsmasq.conf using tftp protocol. Bios systems use pxelinux.0 loader and uefi systems use syslinux.efi. Dnsmasq will route the request to proper file:
    • In case of bios, dhcp-boot option will be used
    • In case of uefi, we have to route to proper architecture using dhcp-vendor and dhcp-boot options in dnsmasq.conf
    • Note: there is difference between efi, efi32 and efi64. My example here uses only efi64 because i have no interest in e.g. ia32 architecture that’s why only efi64 is linked in dnsmasq configuration.
  2. Once loader has been sent, the client searches for other files (i.e. ldlinux.c32 or libutil.c32). Those files are picked up from the same directory where the loader is found. Make sure that other files are coming from proper sources! e.g. libutil.c32 is not the same in bios and uefi versions! Because of this, syslinux.efi found in syslinux-6.04-pre1.zip has to be copied with two additional names bootx64.efi and bootx64c.efi. Maybe now it’s even outdated and not needed, but i left it there just in case. Once more, default file in pxelinux.cfg directory can be, and in my case is, the same file.
  3. Next, the menu gets shown. When option is selected, we have to load the kernel and initrd for that option into memory. Loading of those files can be done with different protocols, but i’ve found that tftp doesn’t work for uefi, but it works for bios. To address this, i’ve set up nginx as http server to send mentioned files to client. Would recommend this for generic setup.
  4. For some images, it could happen that only nfs is supported. Nfs shares then need to be exported, but usage of this is defined in /srv/pxe/boot/bios/pxelinux.cfg/default configuration

In order to use isos, you need to mount them to proper directory. E.g.:

mount /mnt/red/data/isos/linux/neon-useredition-20180104-1018-amd64.iso /srv/pxe/mounts/neon

Done!

Now restart another computer in the same network and choose pxe boot.