Новости и статьи

Среда разработки встроенных систем или дистрибутивов на основе GNU/Linux. Здесь вы можете найти новости проекта, статьи и заметки, касающиеся разработки дистрибутива Radix cross Linux.

UEFI PXE Boot в виртуальном окружении

10 февраля 2025 г.

Установка PXE‑сервера для тестирования edk2/OvmfPkg в виртуальном окружении может оказаться не совсем тривиальной задачей. Например мы столкнулись с тем что TFTP‑сервер, работающий на одной виртуальной машине, оказался недоступным для других виртуальных машин. Решение данной проблемы находится в конфигурации брандмауэра. В Linux netfilter есть вспомогательные модули nf_conntrack_tftp и nf_nat_tftp, которые после загрузки позволяют отслеживать обратные TFTP‑соединения. Однако и здесь настройка может потребовать дополнительных затрат времени.

Qemu

Допустим мы работаем на персональной машине в сети с адресом 192.168.9.0/24, где установлены Qemu, Libvirt, Virtual Machine Manager и нам необходимо создать виртуальную сеть 192.168.49.0/24, в которой настроен PXE‑сервер для загрузки виртуальных машин подключаемых к этой сети.

tags

Создание такой сети, с использованием Libvirt, выглядит достаточно просто. Главным моментом здесь является то, что мы используем встроенный TFTP‑сервер и отказываемся от DHCP‑сервера.

Создание сети

Создадим XML‑файл pxe‑net.xml:

 <network>
   <name>pxe-net</name>
   <forward mode='nat'>
     <port start='1' end='65536'/>
   </forward>
   <bridge name='pxe0' stp='on' delay='0'/>
   <mac address='00:16:3e:69:d4:a6'/>
   <domain name='radix-linux.su'/>
   <ip address='192.168.49.1' netmask='255.255.255.0'>
     <tftp root='/srv/tftp'/>
   </ip>
 </network>

и выполним команду:

 virsh net-define --file pxe-net.xml

После этого в каталоге /etc/libvirt/qemu/networks/ будет создан файл описания сети pxe‑net.xml.

Для того чтобы данная сеть была доступна после каждой перезагрузки рабочей машины, выполним еще одну команду:

 virsh net-autostart --network pxe-net

Поднять сеть можно с помощью команды:

 virsh net-start --network pxe-net

Теперь остается разрешить Qemu использовать мост pxe0. Для этого в файл /etc/qemu/bridge.conf необходимо добавить строку:

 allow pxe0

Теперь сеть 192.168.49.0/24 готова к использованию.

Напомним, что теперь каталог /etc/tftp/ на рабочей машине будет доступен всем виртуальным машинам сети 192.168.49.0/24 и загрузить файл из этого каталога можно будет, например, с помощью команды:

 tftp 192.168.49.1 -c get pxelinux.0

При этом, роль TFTP‑сервера 192.168.49.1 будет выполнять Libvirt.

DHCP‑сервер

Поскольку в сети 192.168.49.0/24 не предусмотрен DHCP‑сервер, нам понадобится некая виртуальная машина с настроенным DHCP‑сервером. Ее можно создать с помощью Virtual Machine Manager или взять готовую. Рассмотрим настройку и запуск DHCP‑сервера на примере Slackware‑current.

Допустим, что мы имеем образ диска slackware‑20250205.qcow2, на котором установлена свежая версия дистрибутива. Приготовим скрипт запуска:

#!/bin/sh

qemu-system-x86_64 \
  -enable-kvm -bios OVMF.fd \
  -cpu host -smp 4 -m 8192 \
  -drive file=slackware-20250205.qcow2,media=disk,if=ide \
  -netdev bridge,id=pxe-net,br=pxe0,helper=/usr/libexec/qemu-bridge-helper \
  -device e1000,netdev=pxe-net,mac=00:16:3e:21:ac:4b \
  -display sdl

Здесь, файл OVMF.fd – это образ, полученный в результате сборки edk2 (OvmfPkgX64.dsc).

Итак, мы имеем Slackware‑машину с одним сетевым интерфейсом. Сразу после запуска этой машины, следует назначить статический IP‑адрес.

Для этого надо отредактировать файл /etc/rc.d/rc.inet1.conf добавив адрес 192.168.49.2 так, чтобы получилась запись:

# IPv4 config options for eth0:
IPADDRS[0]="192.168.49.2"

в начале файла.

Теперь можно приступать к настройке DHCP‑сервера. ISC DHCP‑сервер позволяет выбирать загрузочные конфигурации и образы в зависимости от архитектуры процессора клиентской машины, а также типа загрузки. Список доступных архитектур можно найти на странице Processor Architecture Types.

Например, тип 00:07 соответствует UEFI загрузчику с архитектурой x64. При обращении такого UEFI PXE загрузчика по IPv4 к DHCP‑серверу, в файле /var/state/dhcp/dhcp.leases будет оставлена запись, например, следующего содержания:

 lease 192.168.49.50 {
   starts 0 2025/02/09 06:49:13;
   ends 1 2025/02/10 01:49:13;
   cltt 0 2025/02/09 06:49:13;
   binding state active;
   next binding state free;
   rewind binding state free;
   hardware ethernet 00:16:3e:5f:bd:52;
   set vendor-class-identifier = "PXEClient:Arch:00007:UNDI:003001";
 }

Где, vendor‑class‑identifier показывает тип клиента "PXEClient" и архитектуру процессора "00:07".

Таким образом DHCP‑сервер может быть настроен на работу сразу с несколькими клиентами, имеющими различную архитектуру. В качестве примера, приведем выбор параметров загрузки в конфигурационном файле /etc/dhcpd.conf для архитектуры PowerPC:

  if option arch = 00:0e {
    # POWER OPAL v3
    option path-prefix "powerpc/";
    option conf-file "netboot.cfg";
  } else if option arch = 00:0c {
    # PowerPC Open Firmware
    filename "pxelinux.0";
  }

Итак, займемся конфигурированием DHCP‑сервера. Для этого надо отредактировать файл /etc/dhcpd.conf. В начале файла зададим общие настройки:

authoritative;
ddns-update-style none;

ignore bootp;

option domain-name-servers 192.168.49.1;

Напомним, что роль сервера 192.168.49.1 выполняет Libvirt, обслуживая сеть 192.168.49.0/24.

Далее опишем подсеть:

subnet 192.168.49.0 netmask 255.255.255.0 {
  option domain-name "radix-linux.su";
  option broadcast-address 192.168.49.255;
  option subnet-mask 255.255.255.0;
  option routers 192.168.49.1;

  # We reserve the range 192.168.49.1 to 192.168.49.49 for static IP addresses

  pool {
    # Known clients (i.e. configured with a 'host' statement)
    # that request an IP address via DHCP

    range 192.168.49.50 192.168.49.100;

    # Default lease is 1 week (604800 sec.)
    default-lease-time 604800;

    # Max lease is 4 weeks (2419200 sec.)
    max-lease-time 2419200;

    deny unknown clients;
  }

  pool {
    # Guests

    range 192.168.49.150 192.168.49.200;

    # Default lease is 8 hours (28800 sec.)
    default-lease-time 28800;

    # Max lease is 24 hours (86400 sec.)
    max-lease-time 86400;

    deny known clients;
  }
}

Здесь мы зарезервировали диапазон 192.168.49.1 – 192.168.49.48 под статические IP‑адреса, а блоки pool { } определяют диапазон адресов для известных нам клиентов [192.168.49.50 – 192.168.49.100] и диапазон гостевых машин [192.168.49.150 – 192.168.49.200]. Для гостевых машин мы выбрали более короткие интервалы жизни присвоенных IP‑адресов.

Теперь нам остается разделить клиентов на группы. Первая группа создана для машин, не требующих PXE‑загрузки:

group {
  # Non-PXE
  default-lease-time 68400;
  max-lease-time 172800;
}

Вторая группа предназначена для клиентов, которые будут обращаться к нашему серверу для получения загрузочных образов:

group {
  # PXEBoot
  default-lease-time 68400;
  max-lease-time 172800;

  allow bootp;

  next-server 192.168.49.1;

  use-host-decl-names on;

  if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
    filename "grubx64.efi";
  }

  host t47 {
    hardware ethernet 00:16:3e:5f:bd:52;
    fixed-address 192.168.49.7;
  }
}

Параметр next-server указывает адрес TFTP‑сервера, в то время как DHCP‑сервер работает на машине с адресом 192.168.49.2.

Блок:

  if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
    filename "grubx64.efi";
  }

предлагает PXE‑клиентам загрузчик grubx64.efi, расположенный на нашей рабочей машине в каталоге /srv/tftp/.

Подобных блоков может быть несколько, например, можно добавить блок для клиентов с идентификатором "Etherboot":

  else if substring (option vendor-class-identifier, 0, 9) = "Etherboot" {
    filename "bzImage";
  }

Кроме того, DHCP‑сервер может предлагать клиетнам фиксированные IP‑адреса по их MAC‑адресу. Пример такой выдачи описан в блоке t47 { }:

  host t47 {
    hardware ethernet 00:16:3e:5f:bd:52;
    fixed-address 192.168.49.7;
  }

То есть клиенту с MAC‑адресом 00:16:3e:5f:bd:52 DHCP‑сервер всегда будет предлагать IP‑адрес, равный IP=192.168.49.7.

Приведем итоговый конфигурационный файл /etc/dhcpd.conf целиком:

# dhcpd.conf
#
# Configuration file for ISC dhcpd (see 'man dhcpd.conf')
#

authoritative;
ddns-update-style none;

ignore bootp;

option domain-name-servers 192.168.49.1;

subnet 192.168.49.0 netmask 255.255.255.0 {
  option domain-name "radix-linux.su";
  option broadcast-address 192.168.49.255;
  option subnet-mask 255.255.255.0;
  option routers 192.168.49.1;

  # We reserve the range 192.168.49.1 to 192.168.49.49 for static IP addresses

  pool {
    # Known clients (i.e. configured with a 'host' statement)
    # that request an IP address via DHCP

    range 192.168.49.50 192.168.49.100;

    # Default lease is 1 week (604800 sec.)
    default-lease-time 604800;

    # Max lease is 4 weeks (2419200 sec.)
    max-lease-time 2419200;

    deny unknown clients;
  }

  pool {
    # Guests

    range 192.168.49.150 192.168.49.200;

    # Default lease is 8 hours (28800 sec.)
    default-lease-time 28800;

    # Max lease is 24 hours (86400 sec.)
    max-lease-time 86400;

    deny known clients;
  }
}

group {
  # Non-PXE
  default-lease-time 68400;
  max-lease-time 172800;
}

group {
  # PXEBoot
  default-lease-time 68400;
  max-lease-time 172800;

  allow bootp;

  next-server 192.168.49.1;

  use-host-decl-names on;

  if substring (option vendor-class-identifier, 0, 9) = "PXEClient" {
    filename "grubx64.efi";
  }

  host t47 {
    hardware ethernet 00:16:3e:5f:bd:52;
    fixed-address 192.168.49.7;
  }
}

Автозапуск

Запустить настроенный DHCP‑сервер в работу можно с помощью команды:

 /usr/sbin/dhcpd -q eth0

Для того чтобы DHCP‑сервер стартовал при каждой загрузке операционной ситемы, необходимо составить еще один файл /etc/rc.d/rc.dhcpd:

#!/bin/sh

DHCPD_OPTIONS="-q eth0"

[ -x /usr/sbin/dhcpd ] || exit 0

[ -f /etc/dhcpd.conf ] || exit 0

start() {
  echo -n "Starting dhcpd: /usr/sbin/dhcpd ${DHCPD_OPTIONS} "
  /usr/sbin/dhcpd ${DHCPD_OPTIONS}
  echo
}

stop() {
  echo -n "Shutting down dhcpd: "
  killall -TERM dhcpd
  echo
}

status() {
  PIDS=$(pidof dhcpd)
  if [ "${PIDS}" == "" ]; then
    echo "dhcpd is not running!"
  else
    echo "dhcpd is running at pid(s) ${PIDS}."
  fi
}

restart() {
  stop
  start
}

case "${1}" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    restart
    ;;
  status)
    status
    ;;
  *)
    echo "Usage: $0 {start|stop|status|restart}"
    ;;
esac

exit 0

и добавить в файл /etc/rc.d/rc.local следующие строки:

 route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.49.1

 if [ -x /etc/rc.d/rc.dhcpd ]; then
   /etc/rc.d/rc.dhcpd start
 fi

Теперь, если предоставить файлу /etc/rc.d/rc.dhcpd право исполнения:

 chmod a+x /etc/rc.d/rc.dhcpd

при каждом старте системы будет запускаться и DHCP‑сервер.

Отдельно следует напомнить, что Libvirt не предоставляет собственный DHCP‑сервер в сети 192.168.49.0/24 и не прокладывает маршруты. Поэтому мы должны вручную задать маршрут во внешнюю сеть:

 route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.49.1

что мы и сделали в начале файла /etc/rc.d/rc.local.

TFTP‑сервер

Содержимое каталога /srv/tftp/ на рабочей машине зависит от выбора операционной системы. Здесь можно создать множество подкаталогов для загрузки различных OS. Мы же рассмотрим самый простой случай, когда в каталоге /srv/tftp/ располагается лишь один инсталлятор системы Debian‑12.

Чтобы получить все необходимые для загрузки файлы, надо скачать архив netboot.tar.gz и распаковать его непосредственно в каталоге /srv/tftp/:

 cd /srv/tftp
 wget https://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/netboot/netboot.tar.gz
 tar xzvf netboot.tar.gz
 rm -f netboot.tar.gz

В результате в каталоге /srv/tftp/ будут расположены следующие файлы и подкаталги:

   /srv/tftp/
   ├── debian-installer/
   ├── ldlinux.c32        -> debian-installer/amd64/boot-screens/ldlinux.c32
   ├── netboot.tar.gz
   ├── pxelinux.0         -> debian-installer/amd64/pxelinux.0
   ├── pxelinux.cfg       -> debian-installer/amd64/pxelinux.cfg
   ├── splash.png         -> debian-installer/amd64/boot-screens/splash.png
   └── version.info

Для того чтобы иметь возможность загружать GRUB, нам необходимо дополнительно создать еще две символические ссылки:

 cd /srv/tftp
 ln -s debian-installer/amd64/grubx64.efi .
 ln -s debian-installer/amd64/grub .

Виртуальная машина

Итак, в сети 192.168.49.0/24 работает наш DHCP‑сервер по адресу 192.168.49.2 и мы можем приготовить свежую виртуальную машину для того, чтобы на ней инсталлировать систему Debian‑12. Создадим пустой образ диска disk0.qcow2:

 fallocate --length 64GiB disk0.qcow2

выберем подходящий образ OVMF.fd и создадим скрипт запуска машины run‑pxe‑net.sh:

#!/bin/sh

qemu-system-x86_64 \
  -enable-kvm -bios OVMF.fd \
  -cpu host -smp 4 -m 8192 \
  -drive format=raw,file=disk0.qcow2,media=disk,if=ide \
  -netdev bridge,id=pxe-net,br=pxe0,helper=/usr/libexec/qemu-bridge-helper \
  -device e1000,netdev=pxe-net,mac=00:16:3e:5f:bd:52 \
  -display sdl

после запуска машины по клавише Esc мы сможем перейти в меню Boot Manager:

Fig.1. OVMF Front Page.

и выбрать загрузку по сети "UEFI PXEv4 (MAC:00163E5FBD52)":

Fig.2. OVMF Boot Manager Menu.

Через некоторое время будет загружен GRUB, путь к которому мы указали в файле /etc/dhcpf.conf:

    filename "grubx64.efi";

и мы увидим следующее меню:

Fig.3. Debian GRUB Menu.

Проведя инсталляцию системы на диск disk0.qcow2, мы получим рабочую виртуальную машину в сети 192.168.49.0/24.

Fig.4. Debian MATE.

UEFI HTTP Boot

В рассмотренной выше конфигурации DHCP‑сервера предусматривается только PXE‑загрузка и только для машин с MAC‑адресами представленными в блоках:

  host t47 {
    hardware ethernet 00:16:3e:5f:bd:52;
    fixed-address 192.168.49.7;
  }

Если же образ OVMF.fd предусматривает еще и UEFI HTTP загрузку и, мы хотим разрешить всем машинам в сети загружаться по HTTP и PXE протоколам на выбор, то конфигурацию DHCP‑сервера /etc/dhcpd.conf можно существенно упростить:

# dhcpd.conf
#
# Configuration file for ISC dhcpd (see 'man dhcpd.conf')
#

option domain-name-servers 192.168.49.1;
option routers 192.168.49.1;

default-lease-time 14400;

ddns-update-style none;

subnet 192.168.49.0 netmask 255.255.255.0 {
  range dynamic-bootp 192.168.49.50 192.168.49.100;
  default-lease-time 14400;
  max-lease-time 172800;

  class "pxeclients" {
    match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
    next-server 192.168.49.1;
    filename "grubx64.efi";
  }

  class "httpclients" {
    match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";
    option vendor-class-identifier "HTTPClient";
    filename "http://boot.kx-home.su/openSUSE-Leap-15.4-NET-x86_64-Media.iso";
  }
}

Здесь, наряду с TFTP‑сервером, подразумевается наличие HTTP‑сервера. HTTP‑сервер может быть расположен в любой доступной сети поскольку мы указали путь:

 route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.49.1

в файле /etc/rc.d/rc.local сервера 192.168.49.2.

В случае использования NGINX, создание HTTP‑сервера выглядит просто:

    server {
        listen 80;
        server_name boot.kx-home.su;
        root /srv/nginx/vhosts/boot;

        error_log /var/log/nginx/boot.kx-home.su-error.log;
        access_log /var/log/nginx/boot.kx-home.su-access.log;

        location / {
          autoindex on;
        }
    }

Загрузочный образ openSUSE‑Leap‑15.4‑NET‑x86_64‑Media.iso можно найти на официальном сайте download.opensuse.org и скопировать его в каталог /srv/nginx/vhosts/boot/ на машине, где работает сервер NGINX.

Выполнив все настройки и создав новую виртуальную машину, мы сможем инсталлировать openSUSE Leap 15.4. Для этого надо всего лишь выбрать пункт "UEFI HTTPv4 (MAC:00163E5FBD52)" в меню Boot Manager.

Fig.5. openSUSE Installer.

Клиенты с архитектурой x64 осуществляющие загрузку по HTTP оставляют след в файле /var/state/dhcp/dhcpd.leases с идентификатором, равным:

  vendor-class-identifier = "HTTPClient:Arch:00016:UNDI:003001";

Здесь тип архитектуры 0x00:0x10 представлен в десятичном формате 00016.

Литература:


Следует отметить, что на сервере 192.168.49.2 можно настроить NFS, HTTP и другие сервисы, которые будут необходимы для запуска виртуальных машин с различной архитектурой CPU и, таким образом, построить виртуальное тестовое окружение для повседневной работы.

Enjoy.