電脳世界のケーキ屋さん

考えの甘い甘党エンジニアがいろいろ書くブログ

Linux PXE 実習(DHCP設定ファイルを読み解く)

はじめに

以前の投稿で PXE用サーバを構築したが, その際 dhcpd の設定についてあまり理解していなかったので, これを機に理解していこうという次第.

調べたら思ったよりキツかった.
コンフィグについては改良の余地あり.
DHCPあなどるなかれ...

amaneku.hatenadiary.com

件のコンフィグ

まずは結論から

#~~~~~~~~~~~~~~~~~~~~~~~~
# /etc/dhcp/dhcpd.conf
#~~~~~~~~~~~~~~~~~~~~~~~~
# オプション93番について 'architecture-type' と定義, データは 16ビットの符号無整数とする
option architecture-type code 93 = unsigned integer 16;`

# オプション空間 'pxelinux' を定義
option space pxelinux;

# オプション208番を pxelinux オプション空間内で magic として定義, データは文字列型
option pxelinux.magic      code 208 = string;

# 以下同様に configfile, pathprefix, reboottime を定義する
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;

# 自身(DHCPサーバ)のアドレスを定義
local-address 192.168.4.28;

# サブネット 192.168.4.0/24 のスコープに関する宣言
subnet 192.168.4.0 netmask 255.255.255.0 {
  allow booting;
  allow bootp;

  # オプション1番でサブネットマスクを渡すことを明示
  option subnet-mask 255.255.255.0;

  # BOOTPデータとして, TFTPサーバのアドレスを定義
  next-server 192.168.4.28;

  # このサブネットでリースするアドレスの範囲を定義
  range 192.168.4.33 192.168.4.64;

  # デフォルトのリース時間と, 最大リース時間の定義
  default-lease-time 3600;
  max-lease-time 21600;

  # オプション空間 "pxelinux" をサイトローカルオプションとして利用することを宣言
  site-option-space "pxelinux";

  # pxelinux.magic に値を設定(実はなくても良い)
  option pxelinux.magic f1:00:74:7e;

  # クライアントが dhcp-parameter-request-list を付加していた場合の対応
  if exists dhcp-parameter-request-list {
    # クライアントへの返信時に強制的にオプション d0,d1,d2,d3 を含める
    # ※d0, d1, d2, d3 はオプション番号を16進数表記したもの(208~211)
    option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list,d0,d1,d2,d3);
  }

  # architecture-type が 00:00 だった場合の対応(以下同様)
  if option architecture-type = 00:00 {
    # pxelinux.pathprefix に値を設定
    option pxelinux.pathprefix "/bios/";
    # filename に値を設定
    filename "bios/lpxelinux.0";
  } elsif option architecture-type = 00:09 {
    # EFIx64
    filename "efi64/syslinux.efi";
    option pxelinux.pathprefix "/efi64/";
  } elsif option architecture-type = 00:07 {
    # EFIx64
    filename "efi64/syslinux.efi";
    option pxelinux.pathprefix "/efi64/";
  }
}

前提知識

まず 1行目から6行目までの option で始まるディレクティブについて.
これについては man dhcp-options でマニュアルが読める.

この辺りを把握するには DHCP のオプションについて理解する必要がある(あった) ここがもの凄く話が長いので閲覧注意.

DHCPのオプション

DHCPは一般的にはネットワークに接続されたクライアントに対して, 利用可能なIPアドレスをリースするためのシステムではあるが, この際に オプション を用いて様々な追加の情報のやり取りができる.

PXENBPファイルを指定していたり, サブネット長を指定しているのがまさにそれである.
(クライアントのIPやNextサーバの指定はメッセージ本体に含まれる)

オプションはそれを識別するオプション番号と, オプションデータのサイズ, オプションデータ本体のセットで構成される.
例えばオプション番号 1 はサブネットマスクをクライアントに指定するためのオプションだが, RFC2132 を見ると以下のようなフォーマットであることが分かる.

The code for the subnet mask option is 1, and its length is 4 octets.

 Code   Len        Subnet Mask
+-----+-----+-----+-----+-----+-----+
|  1  |  4  |  m1 |  m2 |  m3 |  m4 |
+-----+-----+-----+-----+-----+-----+

Code というのがオプション番号(以後そのように)となっており, 1オクテットの範囲で 0 ~255 の値を取る.
Len がその後に続く実データのサイズ(オクテット数)を示している.

前半のオプション番号については、用途についてRFCで定められている.
RFC3942 ではオプション番号のうち, 224 から 254 までがサイトローカルオプション番号として予約されており, 要するにユーザが自由に定義して使って良いオプションとなっている.
(製品とかアプリケーションには組み込むんじゃねーぞと注記がある. ちゃんと守られていないのか若干愚痴っぽい)

具体的にどのオプション番号にどんな意味があるのかは, IANAが公開している情報を見るのが分かりやすい模様.
RFCだと点在していて追っていくのが辛い.

BOOTP and DHCP options - IANA

オプションのカスタマイズ(DHCPHello World!)

オプションについては既存で定義されているものが幾つかあることは先に述べた.
これについてサイトローカルで独自の意味に書き換えることができる.

例えば, オプション1番は本来サブネットマスクを指定するものだが, 以下のように設定することで, そこに任意の文字列を入れて遊ぶことができる.

option local-hello code 1 = text;

local-address 192.168.4.28;

subnet 192.168.4.0 netmask 255.255.255.0{
  range 192.168.4.33 192.168.4.64;
  option local-hello "Hello World!";
}

Offer をパケットキャプチャすると以下のようになっており, ちゃんとオプション1番に値でハローワールドできていることが確認できる.

Frame 2: 342 bytes on wire (2736 bits), 342 bytes captured (2736 bits)
Ethernet II, Src: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx), Dst: RealtekU_fb:17:a9 (52:54:00:xx:xx:xx)
Internet Protocol Version 4, Src: 192.168.4.28, Dst: 192.168.4.36
User Datagram Protocol, Src Port: 67, Dst Port: 68
Dynamic Host Configuration Protocol (Offer)
    Message type: Boot Reply (2)
    Hardware type: Ethernet (0x01)
    Hardware address length: 6
    Hops: 0
    Transaction ID: 0x39de4e39
    Seconds elapsed: 0
    Bootp flags: 0x0000 (Unicast)
    Client IP address: 0.0.0.0
    Your (client) IP address: 192.168.4.36
    Next server IP address: 0.0.0.0
    Relay agent IP address: 0.0.0.0
    Client MAC address: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx)
    Client hardware address padding: 00000000000000000000
    Server host name not given
    Boot file name not given
    Magic cookie: DHCP
    Option: (53) DHCP Message Type (Offer)
    Option: (54) DHCP Server Identifier (192.168.4.28)
    Option: (51) IP Address Lease Time
    Option: (1) Subnet Mask                         ※サブネット用のオプションに...
        Length: 12
        Value: 48656c6c6f20576f726c6421             ※Hello World!(16進数ASCIIなので適宜翻訳してください)
        [Expert Info (Error/Protocol): length isn't 4]
    Option: (255) End
    Padding: 000000000000000000000000000000000000000000000000…

もちろん既存のオプションでこのようなことをしても余り幸せにはなれないが, ISC-DHCP がオプション名を定義していないものや, 前述のサイトローカルオプションを利用するためにはこの手法を使う必要がある.

冒頭に示したコンフィグの1行目はつまりオプション93番を arcitecture-type として宣言しているのである.
右辺はオプションの型を示すもので, この場合は符号無16ビット整数値.

option architecture-type code 93 = unsigned integer 16;`

なお, dhcpd.conf で整数値を参照する場合は16進数表記で8ビット毎にコロンで区切る必要がある.

if option architecture-type = 00:00 {  ※つまり 0 だったらという意味

この辺りは man dhcp-eval のマニュアルを見るのが良い.

オプション空間について

ISC-DHCP ではオプションをグループにまとめることができる.
これに用いるのが option space ディレクティブになる.
ここでオプション空間名, 言い換えればグループ名を定義する.

以降はその空間名をプレフィックスにしてオプションを定義すると, そのオプションをオプション空間に所属させることができる.

option space pxelinux;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;

一体これを何に使うのかというと, マニュアルを見ると2つの用法がある模様.

  • site-option-space によってサイトローカルオプションとして利用
  • vendor-option-space によってベンダー固有オプションとして利用

vendor-option-space については横道なので割愛.
site-option-space で, 定義したオプション空間を指定することで, そのスコープのサイトローカルオプションとして, 定義したオプション空間が利用される.
つまり, オプション空間に所属する各種オプションが設定された状態になる.

恐らく, このサブネット,クラスならこのオプション空間を...といった使い道をするのものだと思われる.
実は今回の dhcpd.conf についてはオプション空間は利用する必要がない.

あと site-option-space で指定したオプション空間に, 224より小さい番号のオプションがいると起動時に警告が出る.
今回の PXE で用いるオプションは224以下なので, 個別に名前を宣言してあげる方がベターなようである.

WARNING: site-local option codes less than 224 have been deprecated by RFC3942.
  You have options listed in site local space pxelinux that number as low as 209.
  Please investigate if these should be declared as regular options rather than site-local options, or migrated up past 224.

クライアントへのオプションの送信(request-listについて)

if exists dhcp-parameter-request-list ... という記載に関しての説明.

定義の通りにいけば, 以下のように記述すれば, PXEに関するオプションがクライアントに渡されると予想されるが, 実際はそうならない(ケースが多い) これは オプション55の dhcp-parameter-request-list(以後長いので request-list) というオプションが関わっている.

option space pxelinux;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;

local-address 192.168.4.28;

subnet 192.168.4.0 netmask 255.255.255.0{
  next-server 192.168.4.28;

  ※以下のオプションは想定通りにクライアントに渡されない場合がある
  site-option-space "pxelinux";
  option pxelinux.configfile = "/bios/pxelinux.cfg/default";
  option pxelinux.pathprefix = "/bios/";
  option pxelinux.reboottime = 00:00:00:10;
  range 192.168.4.33 192.168.4.64;
}

request-list はクライアントから, サーバに対して 「このオプションのデータを下さい」という要求を示すオプションである.
ISC-DHCPサーバは, 受信したパケットに request-list が含まれていると, マニュアル上は以下の動きをする.

  • request-list で要求されたオプションを返す
  • 該当スコープで有効なオプションがないものについては無視する

ここで重要なのが, request-list に含まれておらず, サーバ側で設定されているオプションはクライアントに送信されない点にある.
例えばサーバで PXEブートに必要な情報を用意していても, クライアントがリクエストしていなければ渡さない動作を取ることになる.

具体的な動作例を示す.


先のコンフィグについて, サーバクライアント間のやり取りについてパケットキャプチャを取ると以下のようになっている.

クライアント=>サーバの DISCOVERパケット
オプション列に 55 の request-list がない状態である.

Frame 1: 342 bytes on wire (2736 bits), 342 bytes captured (2736 bits)
Ethernet II, Src: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx), Dst: Broadcast (ff:ff:ff:ff:ff:ff)
Internet Protocol Version 4, Src: 0.0.0.0, Dst: 255.255.255.255
User Datagram Protocol, Src Port: 68, Dst Port: 67
Dynamic Host Configuration Protocol (Discover)
    Message type: Boot Request (1)
    Hardware type: Ethernet (0x01)
    Hardware address length: 6
    Hops: 0
    Transaction ID: 0x6f5b3f25
    Seconds elapsed: 0
    Bootp flags: 0x0000 (Unicast)
    Client IP address: 0.0.0.0
    Your (client) IP address: 0.0.0.0
    Next server IP address: 0.0.0.0
    Relay agent IP address: 0.0.0.0
    Client MAC address: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx)
    Client hardware address padding: 00000000000000000000
    Server host name not given
    Boot file name not given
    Magic cookie: DHCP
    Option: (53) DHCP Message Type (Discover)
    Option: (50) Requested IP Address (192.168.4.36)
    Option: (61) Client identifier
    Option: (255) End
    Padding: 000000000000000000000000000000000000000000000000…

これに対するサーバからの Offer は以下のような内容になる.

サーバ=>クライアントの OFFERパケット
209~211の設定で定義したオプションが含まれていることが確認できる.

Frame 2: 346 bytes on wire (2768 bits), 346 bytes captured (2768 bits)
Ethernet II, Src: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx), Dst: RealtekU_fb:17:a9 (52:54:00:xx:xx:xx)
Internet Protocol Version 4, Src: 192.168.4.28, Dst: 192.168.4.36
User Datagram Protocol, Src Port: 67, Dst Port: 68
Dynamic Host Configuration Protocol (Offer)
    Message type: Boot Reply (2)
    Hardware type: Ethernet (0x01)
    Hardware address length: 6
    Hops: 0
    Transaction ID: 0x6f5b3f25
    Seconds elapsed: 0
    Bootp flags: 0x0000 (Unicast)
    Client IP address: 0.0.0.0
    Your (client) IP address: 192.168.4.36
    Next server IP address: 192.168.4.28
    Relay agent IP address: 0.0.0.0
    Client MAC address: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx)
    Client hardware address padding: 00000000000000000000
    Server host name not given
    Boot file name not given
    Magic cookie: DHCP
    Option: (53) DHCP Message Type (Offer)
    Option: (54) DHCP Server Identifier (192.168.4.28)
    Option: (51) IP Address Lease Time
    Option: (1) Subnet Mask (255.255.255.0)
    Option: (209) PXE Configuration file  ※
    Option: (210) PXE Path Prefix         ※
    Option: (211) Reboot Time             ※
    Option: (255) End

次にrequest-listが含まれている場合を確認する.

クライアント=>サーバの DISCOVERパケット(request-list有)
55番のオプションデータが付与されていることが確認できる.
中身は説明した通り, 「この番号のオプションを下さい」というものである.
このリストの中身は dhclient のデフォルトのリクエスト内容になる.

Frame 1: 342 bytes on wire (2736 bits), 342 bytes captured (2736 bits)
Ethernet II, Src: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx), Dst: Broadcast (ff:ff:ff:ff:ff:ff)
Internet Protocol Version 4, Src: 0.0.0.0, Dst: 255.255.255.255
User Datagram Protocol, Src Port: 68, Dst Port: 67
Dynamic Host Configuration Protocol (Discover)
    Message type: Boot Request (1)
    Hardware type: Ethernet (0x01)
    Hardware address length: 6
    Hops: 0
    Transaction ID: 0x8600fa35
    Seconds elapsed: 0
    Bootp flags: 0x0000 (Unicast)
    Client IP address: 0.0.0.0
    Your (client) IP address: 0.0.0.0
    Next server IP address: 0.0.0.0
    Relay agent IP address: 0.0.0.0
    Client MAC address: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx)
    Client hardware address padding: 00000000000000000000
    Server host name not given
    Boot file name not given
    Magic cookie: DHCP
    Option: (53) DHCP Message Type (Discover)
    Option: (50) Requested IP Address (192.168.4.36)
    Option: (55) Parameter Request List                         ※
        Length: 7
        Parameter Request List Item: (1) Subnet Mask            ※
        Parameter Request List Item: (28) Broadcast Address     ※
        Parameter Request List Item: (2) Time Offset            ※
        Parameter Request List Item: (3) Router                 ※
        Parameter Request List Item: (15) Domain Name           ※
        Parameter Request List Item: (6) Domain Name Server     ※
        Parameter Request List Item: (12) Host Name             ※
    Option: (61) Client identifier
    Option: (255) End
    Padding: 00000000000000000000000000000000

これ対するサーバからの Offer は以下のようなものになる.

サーバ=>クライアントの OFFERパケット(request-list有) 付加されると想定していたオプションがないことが見て取れる.
クライアントからは他にもドメイン名やゲートウェイのリクエストがあったが, 今回の設定ではスコープに情報が定義されていないのでサーバは無視した形となっている.

Frame 2: 342 bytes on wire (2736 bits), 342 bytes captured (2736 bits)
Ethernet II, Src: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx), Dst: RealtekU_fb:17:a9 (52:54:00:xx:xx:xx)
Internet Protocol Version 4, Src: 192.168.4.28, Dst: 192.168.4.36
User Datagram Protocol, Src Port: 67, Dst Port: 68
Dynamic Host Configuration Protocol (Offer)
    Message type: Boot Reply (2)
    Hardware type: Ethernet (0x01)
    Hardware address length: 6
    Hops: 0
    Transaction ID: 0x8600fa35
    Seconds elapsed: 0
    Bootp flags: 0x0000 (Unicast)
    Client IP address: 0.0.0.0
    Your (client) IP address: 192.168.4.36
    Next server IP address: 192.168.4.28
    Relay agent IP address: 0.0.0.0
    Client MAC address: RealtekU_xx:xx:xx (52:54:00:xx:xx:xx)
    Client hardware address padding: 00000000000000000000
    Server host name not given
    Boot file name not given
    Magic cookie: DHCP
    Option: (53) DHCP Message Type (Offer)
    Option: (54) DHCP Server Identifier (192.168.4.28)
    Option: (51) IP Address Lease Time
    Option: (1) Subnet Mask (255.255.255.0)
    Option: (255) End
    Padding: 000000000000000000000000000000000000000000000000…

かくかくしかじかなので, クライアントが request-list を付けてきていた場合は, サーバ側が必要と判断するオプションは明示的に渡す必要がある.

ISC-DHCP では dhcp-parameter-request-list をスコープ内で定義した場合は, そのオプションを強制的に返す動作を取る. (man dhcp-optionsより) なので, dhcp-parameter-request-list に渡したいオプションを付加させてやれば良い.

それが以下の部分の記述の意味となる.

  if exists dhcp-parameter-request-list {
    # Always send the PXELINUX options (specified in hexadecimal)
    option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list, d1, d2, d3);
  }

concat(option dhcp-parameter-request-list, d1, d2, d3) が実際にオプションを連結している箇所である.
d1, d2, d3 というのは 16進表記によるオプション番号を表わしており, それぞれ 209,210,211 になる.
これで各オプションの値を示すことになるらしい. へぇ~(マニュアル内にはどこを探しても見つからなかった)

ちなみに d1 = "/bios/pxelinux.cfg/default"; とかできるかなぁと思ったけどできなかった残念.

おまけ

vendor-option-space について

オプション番号43番の vendor specific の中にベンダーが独自のデータを挿入できる設定.
例えば以下のようなコンフィグを作ると, 43番オプションの中に, 1番オプションとして "Hello World" の値が入ったデータがクライアントに返される.
この時サブネットマスクのオプションは汚染されない.

オプションの中にオプションを入れ子にできるイメージ.

option space local;
option local.hello code 1 = text;

local-address 192.168.4.28;

subnet 192.168.4.0 netmask 255.255.255.0{
  next-server 192.168.4.28;

  vendor-option-space local;
  option local.hello = "Hello World";
  range 192.168.4.33 192.168.4.64;
}

ベンダーが製品とかで使う時はこれ使ってね!と記載があるが, どうもあまり守ってくれてないらしい.
どうも解釈の仕方がマチマチで, vendor-specific ではなく site-local を使っているケースが多い模様.

dhclient の使いかた

request-list を付加しないようにするには, 設定ファイルに空の request ; ディレクティブを記述しておく.

また, デフォルトだと2回目以降のIPの取得でアドレスの再利用のために DISCOVER を送らずにいきなり REQUEST を送る挙動を取る.
これを回避したい場合は事前に RELEASE を行う必要がある.
これは -r オプションで実施可能.

使っていたコマンドは以下の感じ.

DISCOVERYの実施

# dhclient -d -cf dhclient.conf

アドレスのリリース

# dhclient -d -cf dhclient.conf -r

dhclient.conf

interface "ens3" {
  # request-list を付加したくない時は以下のように何も付けていない request ディレクティブを記述する
  # request ;

  # request ディレクティブがない場合は以下のように記述したのと同義
  # request subnet-mask, broadcast-address, time-offset, routers, domain-name, domain-name-servers, host-name;
}

208オプションについて

オプション番号 208 は "PXELINUX Magic" として予約されている.
これは古い PXELINUX(3.55以前) が DHCPオプションを認識するために必要なオプションである.
現行の PXELINUX を使うのであれば不要.

SYSLINUX Wiki より

Option 208 pxelinux.magic
Earlier versions of PXELINUX required this option to be set to F1:00:74:7E (241.0.116.126) for PXELINUX to be able to recognize any special DHCP options whatsoever. As of PXELINUX 3.55, this option is deprecated and is no longer required.

参考文献

IANA bootp dhcp parameters
man dhcpd.conf
man dhcp-options
Debian wiki(man dhcp-options)日本語訳 man dhcp-eval
SYSLINUX WIKI > PXELINUX

おわりに

結局 PXE やりたいなら dnsmasq とかの方がいいんじゃないかと
しかし勉強のためにあえて茨の道を進むのもエンジニアとしてのロマンか(ドMか)

Linux PXE 実習(Fedoraインストーラの起動)

はじめに

前回投稿した PXE 関連記事の続き.
構築した PXE 環境を使って Fedoraインストーラの起動を行えるようにする.

amaneku.hatenadiary.com

手順

  1. Fedora のインストールイメージをダウンロードする
  2. イメージは Standard ISO image for x86_64 のDVDイメージを利用するものと仮定
# mkdir /var/lib/tftpboot/image/fedora/
# cd $_
# wget https:// download.fedoraproject.org/pub/fedora/linux/releases/31/Server/x86_64/iso/Fedora-Server-dvd-x86_64-31-1.9.iso
↑URLに空白を挿入している
  1. ループバックマウントの実施
# cd /var/lib/tftpboot/image
# mkdir /var/lib/tftpboot/image/fedora/root
# mount -o loop,ro Fedora-Server-dvd-x86_64-31-1.9.iso root/
# df

fstab に登録する場合は下記のエントリを /etc/fstab に追記する.

#/etc/fstab 
/var/lib/tftpboot/image/fedora/fedora.iso /var/lib/tftpboot/image/fedora/root iso9660 loop,ro,auto,nofail 0 0
  1. pxelinux.cfg 設定ファイルの編集
  2. 対象ファイルは以下2つを想定
    • /var/lib/tftpboot/bios/pxelinux.cfg/default
    • /var/lib/tftpboot/efi64/pxelinux.cfg/default

pxelinux.cfg/default

DEFAULT menu.c32  # LegacyBIOSの場合(どっちかを記載)
UI menu.c32       # EFI64の場合(どっちかを記載)

LABEL Fedora31-local
 MENU LABEL Installer-Fedora31(Local)
 KERNEL http://<PXE IPアドレス>/image/fedora/root/images/pxeboot/vmlinuz
 APPEND initrd=http://<PXE IPアドレス>/image/fedora/root/images/pxeboot/initrd.img ip=dhcp inst.repo=http://<PXE IPアドレス>/image/fedora/root/

メモ

FedoraインストーラPXE経由で起動させるためには, パッケージリポジトリを指定する必要がある.
これの簡単な方法は, DVDイメージ内のローカルリポジトリをクライアントに見せるか, Fedoraリポジトリミラーサイトを直接指定することになる.
(本末転倒だが, PXEクライアント側にFedoraのDVDイメージが挿さっていればそれを使うこともできる)

環境構築の際に HTTP サーバを動作させるようにしているので, 今回は PXEサーバが保持している DVDイメージを参照するように設定した.
容量をケチってループバックマウントしているが, 展開しても問題はない.

参考: RHEL7インストールガイド > 起動オプション

なおインターネットのミラーサイトを指定する場合は Fedora31 の場合は以下の通り.

https:// download.fedoraproject.org/pub/fedora/linux/releases/31/Server/x86_64/os/

vmlinuz や initramfs も配下に存在するので, 全てのリソースをミラーサイトから取ってきて動作させることもできる.
うちは回線の問題で厳しいのでローカルにダウンロードした次第である.

Kickstart とか余裕があれば勉強したい所存

Linux PXE 実習(環境導入編)

はじめに

年末年始仕事と長期休暇, 暇を持て余していたため, PXEの利用について勉強をした. 本稿はそのノートである.

本稿ではPXEの動作概要と, 基礎環境の構築について述べる.

概論

PXE(Preboot eXecution Environment) はネットワークブート用のブート環境の1つである.

実際には DHCP と TFTP を利用することで, OSのような高水準なプログラムが動作していない環境に対して NBP(Network Bootstrap Program)と呼ばれるブートストラッププログラムを注入する動作がメインと言える.

ぶっちゃけその後の, 注入された NBP がどう動作するかは PXE の関するところではない.
NBP は黙々と指定された手段で OS のプロビジョニングをしてもいいし, 追加でリソースをダウンロードして, ユーザに対して選択肢を示すようなことをしても良い.

動作の流れとしては, おおまかに以下の通り.

  1. PXEクライアントが DHCPサーバに対してアドレスを要求する
  2. PXEシステムのDHCPサーバがIPのリースに併せて, 以下の情報を提示
  3. PXEシステム用のTFTPサーバのアドレス
  4. TFTPサーバに対して何という名前のNBP実行ファイルを要求すれば良いか
  5. PXEクライアントが得た情報から NBP をダウンロードして実行する
  6. NBPが動作してごにょごにょ

そのため, PXEシステムを構築する場合は, NBP をクライアントに注入するまでの DHCP と TFTP を用いた根幹部分の構築に加えて, NBP にどういった挙動を行なわせるかを設定する必要がある.
PXEの構築よりこちらの設定の方が面倒.
また NBP の挙動によっては FTPサーバや HTTPサーバ, NFSサーバなども構築する必要がある.

今回は NBP として, SYSLINUXプロジェクトの PXELINUX を利用する.
なお, Windows なら WDSNBP.COM というものが NBP となる模様.

環境導入手順

インストールのおおまかな流れは以下のようになる.

  1. OS基本設定
  2. DHCPサーバ設定
  3. TFTPサーバ設定
  4. HTTPサーバ設定(オプション)
  5. syslinux関連ファイルの展開
  6. pxelinux.cfg 設定ファイルの作成(別稿記載)

サーバについて

本稿では PXEサーバとして以下の構成の Linux サーバを利用する.

  • KVMマシン(RAM 256MiB/2CPU/x86_64)
  • Fedora 31
  • syslinux 6.03
  • isc-dhcpd-4.3.6
  • tftp-hpa 5.2
  • Nginx 1.16.1
  • SELinux は permissive 又は disabled

ストレージについては NFS で準備しているため, ここでは言及しないが, ある程度余裕がある方が望ましい(OS領域を除いて8GBくらいあると試す分には問題ない).

前提として, PXEサーバのOSのインストールとネットワーク設定は完了しているものとする.
PXEクライアントとは同一セグメントで接続されているものと仮定し, DHCPリレーエージェントなどの介在は想定していない.

実はこの環境を Ansible で構築できるようにプレイブックを作成したのだが, それについては別に記載する(予定).

追加パッケージのインストール

PXE環境構築に必要なソフトウェアをインストールする.

# dnf update
# dnf install tftp-server dhcp-server nginx

Firewalldの設定

tftp,http,dhcp のポートへの受信を許可する.
Fedora だとデフォルトのゾーンは FedoraServer らしい

# firewall-cmd --add-service=tftp --zone=FedoraServer --permanent
# firewall-cmd --add-service=http --zone=FedoraServer --permanent
# firewall-cmd --add-service=dhcp --zone=FedoraServer --permanent
# firewall-cmd --reload

DHCPサーバの設定

Fedoraの isc-dhcpd の設定ファイルは /etc/dhcp/dhcpd.conf である.
以下設定と仮定して, 設定ファイルを追記する.

  • サブネットは 192.168.4.0/24
  • PXEサーバのアドレスは 192.168.4.28
  • リースするアドレスは 192.168.4.33 - 192.168.4.64
  • PXEクライアントのアーキテクチャによってNBPの指定を変更する
    • LegacyBIOS(x86_64) の場合は /bios/lpxelinux.0
    • UEFI64 の場合は /efi64/syslinux.efi

この設定ファイルでは PXEクライアントは LegacyBIOS か UEFI で動作する x86_64マシンだろうと想定している.
必要であれば architecture-type をごにょごにょすることで ARM プロセッサとかにも対応できる(はず)

dhcpd の設定ファイルはそれぞれの項目の末尾にセミコロンが必要なことに注意.
忘れていると当たり前だけどサービスの起動に失敗する.

一応 dhcpd -t -cf /etc/dhcp/dhcpd.conf で文法チェックをできる.

#~~~~~~~~~~~~~~~~~~~~~~~~
# /etc/dhcp/dhcpd.conf
#~~~~~~~~~~~~~~~~~~~~~~~~
option architecture-type code 93 = unsigned integer 16;
option space pxelinux;
option pxelinux.magic      code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;

local-address 192.168.4.28;

subnet 192.168.4.0 netmask 255.255.255.0 {
  allow booting;
  allow bootp;

  option subnet-mask 255.255.255.0;
  next-server 192.168.4.28;

  range 192.168.4.33 192.168.4.64;
  default-lease-time 3600;
  max-lease-time 21600;

  site-option-space "pxelinux";
  option pxelinux.magic f1:00:74:7e;
  if exists dhcp-parameter-request-list {
    option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list,d0,d1,d2,d3);
  }

  if option architecture-type = 00:00 {
    # Intel x86PC (legacy bios)
    option pxelinux.pathprefix "/bios/";
    filename "bios/lpxelinux.0";
  } elsif option architecture-type = 00:09 {
    # EFIx64
    filename "efi64/syslinux.efi";
    option pxelinux.pathprefix "/efi64/";
  } elsif option architecture-type = 00:07 {
    # EFIx64
    filename "efi64/syslinux.efi";
    option pxelinux.pathprefix "/efi64/";
  }
}

設定を投入したらサービスの起動と有効化を行う.

# systemctl enable dhcpd
# systemctl start dhcpd
# systemctl status dhcpd
  // Active(running) であることを確認

動作については journalctl で確認できる.

# journalctl -u dhcpd.service
Jan 04 13:03:34 vPXE-Stack dhcpd[14687]: DHCPREQUEST for 192.168.4.33 from 52:54:00:XX:XX:XX via ens4
Jan 04 13:03:34 vPXE-Stack dhcpd[14687]: DHCPACK on 192.168.4.33 to 52:54:00:XX:XX:XX via ens4
※MACは伏せ字にしている

TFTPサーバの設定

TFTPサーバはセキュアにしたいとかルートを変えたいとかの理由がなければ設定をする必要がない.
サービスを有効化すれば動き出すのでお手軽.

なおこの環境では /var/lib/tftpboot が TFTP サーバのルートである.

個人的にはここで適当なクライアントからファイルのダウンロードができることを確認しておくことを推奨する.
PXEクライアントだと何がどうなっているのか分かりづらい.

なお, FedoraServer の tftpd は systemd ソケットユニットを使って動作している.
例えば待ち受けるアドレスを指定したい場合はユニットファイルを編集することで実現できる.

# /etc/systemd/system/tftp.socket
[Unit]
Description=Tftp Server Activation Socket
After=network-online.target

[Socket]
ListenDatagram=192.168.4.28:69

[Install]
WantedBy=sockets.target
WantedBy=network-online.target

サービスについては tftp.socket を有効化しておく. tftp.service については Inactive な状態で問題ない.

# systemctl daemon-reload
# systemctl enable tftp.socket
# systemctl start tftp.socket

FedoraServer の tftpd はデフォルトでアクセスログを journal に流してくれるので, 動作確認がしやすい

# journalctl -u tftp.service
...
Feb 15 00:05:23 vPXE-Stack systemd[1]: Started Tftp Server.
Feb 15 00:07:14 vPXE-Stack in.tftpd[1063]: Client 192.168.4.3 finished /bios/lpxelinux.0

HTTPサーバの設定

今回はHTTPサーバとして Nginx を利用する.
必要最低限のコンテンツサーバとしての動作しかさせないので, Apache でも問題はない.

インストールができたら /etc/nginx/nginx.conf を編集し, var/lib/tftpboot をルートに動作するように設定する.

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
        worker_connections 1024;
}
http {
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        '$status $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  /var/log/nginx/access.log  main;
        server {
                listen       192.168.4.28:80;
                root         /var/lib/tftpboot/;
        }
}

Nginx の設定ファイルについては nginx -t -c /etc/nginx/nginx.conf で文法チェックを行える.
問題なければサービスを有効化しておく.

# systemctl enable nginx.service
# systemctl start nginx.service

SYSLINUX の展開

SYSLINUXプロジェクトのページから tarball をダウンロードする.
実は dnf リポジトリにもあるといえばあるのだが, ファイルが揃ってないので今回は利用しない.

https://wiki.syslinux.org/wiki/index.php

ダウンロードについては少し分かりづらいが kernel.org から出来る.

# cd /tmp/
# wget https://mirrors.edge.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.xz
# tar xvf syslinux-6.03.tar.xz

tarball の中のファイルを TFTP で取得できる場所にコピーする.

じつは TFTP のディレクトリ構造をどうするのがベストかというのはあまり決まっていない模様.
今回は x86_64 マシンについて LegacyBIOS と UEFI の両方に対応したいという要件が個人的にあったので, 以下のようなディレクトリ構成を生成している.

/var/lib/tftpboot/
 |--- bios/
 | |--- ldlinux.c32
 | |--- libutil.c32
 | |--- lpxelinux.0
 | |--- menu.c32
 | |--- pxelinux.cfg/
 |   |--- default
 |--- efi64/
 | |--- ldlinux.e64
 | |--- libutil.c32
 | |--- menu.c32
 | |--- syslinux.efi
 | |--- pxelinux.cfg/
 |   |--- default
 |--- image
   |--- OSイメージ周り(省略)

コマンドだと以下の通り.

# mkdir -p /var/lib/tftpboot/{bios,efi64}/pxelinux.cfg
# cp syslinux-6.03/bios/core/lpxelinux.0 /var/lib/tftpboot/bios/lpxelinux.0
# cp syslinux-6.03/bios/com32/menu/menu.c32 /var/lib/tftpboot/bios/menu.c32
# cp syslinux-6.03/bios/com32/libutil/libutil.c32 /var/lib/tftpboot/bios/libutil.c32
# cp syslinux-6.03/bios/com32/elflink/ldlinux/ldlinux.c32 /var/lib/tftpboot/bios/ldlinux.c32
# cp syslinux-6.03/efi64/efi/syslinux.efi /var/lib/tftpboot/efi64/syslinux.efi
# cp syslinux-6.03/efi64/efi/com32/menu/menu.c32 /var/lib/tftpboot/efi64/menu.c32
# cp syslinux-6.04/efi64/efi/com32/libutil/libutil.c32 /var/lib/tftpboot/efi64/libutil.c32
# cp syslinux-6.03/efi64/efi/com32/elflink/ldlinux/ldlinux.e64 /var/lib/tftpboot/efi64/ldlinux.e64
# touch /var/lib/tftpboot/{bios,efi64}/pxelinux.cfg/default

dhcpd.confoption pxelinux.pathprefix "/bios/"; という設定により, PXEクライアントが TFTP でファイルをリクエストする際にプレフィックスを付けてくれるようになる.
これで LegacyBIOS と UEFI で同じファイル名でもディレクトリを分けて別のファイルを利用できる.

SYSLINUX で使ったファイルについては以下の通り.

ファイル名 補足
pxelinux.0 PXELINUX の NBPファイル(Legacy BIOS用)
lpxelinux.0 pxelinux.0 の強化版(こなみ)
ldlinux.c32 pxelinux.0 の依存ファイル
menu.c32 メニュー画面を作るのに必要なファイル
libutil.c32 menu.c32 の依存ファイル
syslinux.efi UEFI64用のNBPファイル
ldlinux.e64 syslinux.efi の依存ファイル

NBPの設定(pxelinux.cfg/default の編集)

今回はデフォルトではPXEブート時にメニューを表示するように動作させる.
bios/pxelinux.cfg/default, efi64/pxelinux.cfg/default はそれぞれ以下のように記述する.
bios と efi64 で微妙に先頭の記述が異なるので注意すること.
そこ以外は基本的に同じ設定を使い回せる.

bios/pxelinux.cfg/default

DEFAULT menu.c32

LABEL Menu-01
 MENU LABEL Menu-01Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>

LABEL Menu-02
 MENU LABEL Menu-02Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>
...

efi64/pxelinux.cfg/default

DEFAULT menu.c32

LABEL Menu-01
 MENU LABEL Menu-01Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>

LABEL Menu-02
 MENU LABEL Menu-02Label
 KERNEL <vmlinuz-file path>
 APPEND <kernel-option>
...

MENU LABEL に設定した値が実際のメニューに表示される.

KERNEL に vmlinuz のようなカーネルファイルのパスを指定する.
指定の方法には幾つか方法があり, TFTPやFTP, HTTP などを使うことができる.

APPEND にはカーネル引数を指定する.
通常は初期RAMイメージ(initrdやinitramfs)の設定が必要になる.

具体的な pxelinux.cfg の設定については別記事に記載する.

備考

pxelinux.0 と lpxelinux.0 の違い

lpxelinux.0 は pxelinux.0 より新しいバイナリである.
SYSLINUX の 5.10 から実装されたらしい.

大きな相違点としては, ファイルの取得に HTTP や FTP などを使えることにある.
従来の pxelinux.0 では TFTP しか使えず, カーネルや初期RAMファイルが大きい場合, 転送に時間がかかる問題があり, その点を改善したものと言える.

そのため, NBP として lpxelinux.0 を指定した場合は, 以下のようにすることで HTTPを利用してカーネルと初期RAMファイルをダウンロードすることができる.

# /pxelinux.cfg/default
DEFAULT menu.c32

LABEL Fedora
 MENU LABEL Installer-Fedora
 KERNEL http://192.168.4.27/images/fedora/vmlinuz
 APPEND initrd=http://192.168.4.27/images/fedora/initrd.img ip=dhcp inst.repo=http://192.168.16.27/images/fedora/root/

なお, この TFTP しか使えない問題の解決については gPXE/iPXE を用いるという方法もある.
gPXE/iPXE は PXEクライアントで動作する PXEプログラム の強化版だと思って良い.
gPXE は開発が終了して iPXE が引き継ぎている形のようなので, 今後見るならば iPXE となるだろう.

PXEクライアントが iPXE で動作している場合は, lpxelinux.0 を使わず, pxelinux.0 でも HTTP や FTP を利用することができる.

iPXE が動いている場合は起動画面で iPXE... と分かりやすく出力されるはず.
困ったら lpxelinux.0 を渡す形で良いかと個人的には判断している.

initrd の中身

何らかの理由で初期RAMイメージの中身を見たくなることもあると思われる.
FedoraCentOS, RHEL の場合は dracut ユーティリティが入っていれば操作ができる.

2020年現在の初期RAMイメージは CPIO形式でアーカイブ化され, gz や xz 辺りで圧縮されたイメージとなっている.
なので解凍すればそのまま中身を確認することができる.
圧縮形式は file コマンドで判定可能.

このイメージの先頭に CPUマイクロコードを付与している場合もある(early-microcode).
その場合は, /usr/bin/dracut/skipcpio を通すことでCPIOアーカイブの部分のみを抽出できる.

lsinitrd を使うとその辺りを自動判別してくれる.
dracut のビルドオプションを最初に出力してくれるので, そこから圧縮形式やCPUマイクロコードの有無を確認できる.

# lsinitrd <initrdファイル> | head     ※Argumentを確認
Image: ../initrd.img: 71M
========================================================================
Version: dracut-049-27.git20181204.fc31.1

Arguments: --nomdadmconf --nolvmconf --xz --install '/.buildstamp' \
  --no-early-microcode --add 'fips' --add 'anaconda pollcdrom qemu qemu-net' \
  --force

dracut modules:
bash
systemd
...

// xzで圧縮されており, マイクロコードは含まれていない
# mkdir img
# cd img
# xzcat ../initrd.img | cpio -i -d

参考文献

wiki.syslinux.org wiki SYSLINUX > PXELINUX
www.pxe.net PXE Specification