NetworkBoot - h-sendai/RaspberryPi GitHub Wiki

Raspyberry Pi 3でネットワークブートするためのメモ

情報のありか

Raspberry Pi 3では、SDカードなしでネットワークブートすることが可能です。 Raspberry Pi 2ではSDカードなしのネットワークブートはできないようです。 上の最後にあげたページにあるように SDカードにFAT32領域を作成し、そこに SDカードにbootcode.binというファイルをおくだけで ネットワークブートすることができます。

Raspberry Pi 4ではeepromが新設されそのなかにfirmwareが入っていて そこでブート順番が決まります。ネットワークブートするには firmwareを書き換える必要があります。 Raspberry Pi財団のPi Bootlader Configuration にその方法が書いてあります。サーバー側設定は下記のとおりでOKです。

動作

  • PXE、tftpでブートファイルシステムを取得
  • ルートファイルシステムはNFSで取得

調べておくこと

Raspyberry Piのシリアル番号。わからなくてもOKだが複数の Raspberry PiをネットワークブートするときにはNFSで供給される ルートファイルシステムをわけておいたほうがよいかと思う (sshホストキーは各Raspberry Piで別のものにする必要が あるが、これがルートファイルシステムにあるから)。

/proc/cpuinfoのSerialの欄を 見ると書いてある。

% grep Serial /proc/cpuinfo
Serial      : 0000000012345678

(下6桁はRaspberry Pi EthernetのMACアドレスベンダー部と同一 ようだ)。

Raspberry Pi 3の準備

デフォルトでは USBブートモードが無効になっているので有効にする。

RaspbianなどをSDカードに書いてSDカードから起動できるようにしておく。 /boot/config.txtの最後に

program_usb_boot_mode=1

を追加し再起動する。再起動後

% sudo vcgencmd otp_dump | grep 17:
17:3020000a

となればUSBブートモードが有効になっている。 一度やればOKなのでconfig.txtに追加した行は削除しておく。 この状態でSDカードがささっていればSDカードからブートする。

Raspberry Pi 3側の準備はこれで終了。停止させてSDカードを抜いておく。

Raspberry Pi 2ではconfig.txtの書き換えを行ってリブートしても vcgencmd opt_dumpで上のように 3020000aに変わることはありませんので この作業は必要ありません。

Raspberry Pi 4の準備

Raspberry Pi 4ではEEPROMが付き、そのなかにブートローダーが 入っている。ブート順はブートローダー内で指定されているため ネットワークブートするためにはブートローダーを書き換える必要が ある。RasPi4-Bootloader に書き換え方法を まとめておいた。

PXEサーバー、tftpサーバー、NFSサーバーの準備

dnsmasqでPXE、tftpサーバーを構築する。dnsmasqはDNS, DHCP, TFTP を提供する。

Scinentific Linux 6, 7ではlibvirtdが動いている場合がある。 libvirtdが起動していると自動的にlibvirtd用にdnsmasqが起動する。 libvirtd用dnsmasqは独立な設定ファイルを利用するので特に 気にする必要はない。 たとえばKVMでnatネットワークとIsolated networkを利用している 場合、pgrep -af dnsmasqの出力は次のようになっており独立した dnsmasqプロセスがあることがわかる。

2236 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/isolate_net1.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
2237 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/isolate_net1.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
2336 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
2337 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
3252 /usr/sbin/dnsmasq -k

PID 3252が/etc/dnsmasq.confあるいは/etc/dnsmasq.d/*.confを使うdnsmasqプロセス。

と思っていたらCentOS 7, 8でlibvirtdが動いていると/etc/dnsmasq.d/name.conf に書いた設定を読んでくれないようだ(libvirtdのほうのdnsmasqがポート53と67で 起動しているような気がする。systemctl stop libvirtd、systemctl disable libvirtdして systemctl start dnsmasqするとOKだが共存の方法はあるのか? (メモ: https://kernhack.hatenablog.com/entry/2015/05/05/133203 )

AlmaLinux 8ではlibvirtdが動いていると全てのNICでsysctlの net.ipv4.conf.<NIC>.forwardingの値が1になっていて Raspberry Pi側でデフォルトルートの設定がなされていると Raspberry Piが発した外へのパケットを送信してしまう(NATしてないので 返答はないんだが)。libvirtdをdisableして、リブートすると net.ipv4.conf.<NIC>.forwardingの値は0になる。

dnsmasqの設定ファイルは/etc/dnsmasq.conf。あるいは/etc/dnsmasq.d/においた*.confファイル。 Raspberry Piのネットワークブートに使う設定ファイルはSL6、7とでpxe-serviceの行が違っている。

SL 7, CentOS 8用

port=0
bogus-priv
interface=enp2s0
dhcp-range=192.168.10.50,192.168.10.150,255.255.255.0,12h
pxe-service=0,"Raspberry Pi Boot"
enable-tftp
tftp-root=/var/tftpboot
log-dhcp
dhcp-host=00:01:02:03:04:05,192.168.10.10,infinite
dhcp-option=3

SL 6用

port=0
bogus-priv
interface=enp2s0
dhcp-range=192.168.10.50,192.168.10.150,255.255.255.0,12h
pxe-service=0,"Raspberry Pi Boot",dummy
enable-tftp
tftp-root=/var/tftpboot
log-dhcp
  • port=0でDNS機能を停止。
  • bogus-privでプライベートIPアドレスの逆引きにはno such domainと応答 (今回はDNS機能を停止しているのであまり関係ない)。
  • NICが2枚ついているPCを使い、片方が組織内LANに接続していたのでinterface=enp2s0 でdnsmasqが応答するNICをプライベートネットワークに限定するために指定。
  • dhcp-rangeは チュートリアル では最後にproxyを付けているが、これを付けていると他にDHCPサーバーが必要 なようなのではずした。12hは有効期限12時間。
  • pxe-serviceの行はSL 6に付属のdnsmasqでは3個のパラメータを指定するように なっているが、SL 7に付属のものは2個指定すればよいように変更されている。 SL 6と7とで違うのはこの行だけ。
  • enable-tftpでtftpを動作させる。
  • tftp-rootでtftpのルートディレクトリを指定。
  • log-dhcpでdhcpのログをとる。ログはsudo journalctlで読める。
  • dhcp-hostで固定IPアドレスを割り当てる。MACアドレス,IPアドレス,DHCPリース時間の順に書く。
  • dnsmasq dhcpのデフォルトではdnsmasqが動いているホストを デフォルトルートとする、という情報がdhcpクライアントに対して送られている。 この情報を送らないようにするためにdhcp-option=3を指定している。

dnsmasqを起動:

(SL 7, CentOS 8)
systemctl start dnsmasq
リブート後に自動起動するには
systemctl enable dnsmasq

(SL 6)
service dnsmasq start
リブート後に自動起動するには
chkconfig dnsmasq on

(注)

interface=enp2s0と設定してもlsof -Piでみると

dnsmasq   1815 dnsmasq    4u  IPv4  35872      0t0  UDP *:67

*:67となっていて全NICでlistenしているようだが、 interface=以外で指定したNICを tcpdump -i eth100 port 68 or port 67してみるとrequestがきても 応答していない。ソースを見てみるとSO_BINDTODEVICEをsetsockopt() している。自分でtcpサーバーを作って

+    char *if_name = "exp0";
+    socklen_t optlen = 4;
+    if (setsockopt(listenfd, SOL_SOCKET, SO_BINDTODEVICE, if_name, optlen)) {
+        warn("setsockopt SO_BINDTODIVCE");
+        return -1;
+    }
+
     if (listen(listenfd, 10) < 0) {
         warn("listen");
         return -1;

してlisten()するとlsof -PiではTCP *:1234 (LISTEN)となっているが exp0以外のNICに到着したパケットはRESETが返っている。ということで lsof -PiUDP *:67となっていてもinterface=をちゃんと指定 しておけば組織内LAN用NICがあるPCでもだいじょうぶ。 (注おわり)

TFTPサーバー用コンテンツ。/var/tftpboot/12345678 (12345678はRaspberry Piの シリアル番号)とディレクトリを作って/boot以下のファイルをコピー。ただし bootcode.binはTFTPサーバールートディレクトリ直下に置く(RPi 4から不要に なったがRPi 3およびそれ以前では必要):

/var/tftpboot/bootcode.bin
             /12345678/COPYING.linux
                      /LICENCE.broadcom
                      /LICENSE.oracle
                      /bcm2708-rpi-0-w.dtb
                      :
                      /cmdline.txt
                      /overrays/README
                               /adau1977-adc.dtbo
                               :
                      /start.elf
                      :
                      /vmlinuz

NFSサーバーの設定。サーバーのインストール:

yum install nfs-utils

設定ファイルは/etc/exports:

/home/nfs 192.168.10.0/24(rw,no_root_squash)

nfsサーバーの起動は

(SL 7, CentOS 8)
systemctl start nfs-server
(自動起動するなら)
systemctl enable nfs-server

(SL 6)
service start nfs
(自動起動するなら)
chkconfig nfs on

/home/nfsの下にRaspberry Piのシリアル番号のディレクトリを作る。 シリアル番号が12345678だったら

mkdir /home/nfs/12345678

ここにRaspberry Piのルートパーティションをコピーする。 SDカードからコピーするか イメージファイルをマウントしてコピーするかする。イメージファイルは kpartxを使うとマウント可能になる (kpartxはEL 7ならkpartx rpmパッケージに入っている):

kpartx -a /home/username/2017-11-29-raspbian-stretch-lite.img
mount /dev/mapper/loop0p2 /mnt
/mntにイメージファイルのルートパーティションが現れる
コピーし終わったら
umount /mnt
kpartx -d /home/username/2017-11-29-raspbian-stretch-lite.img

fstab (上の例だと/home/nfs/12345678/etc/fstab)を下のようにprocから 始まる行のみにする。

proc  /proc proc  defaults  0  0

/var/tftpboot/12345678/cmdline.txtの編集:

console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.10.204:/home/nfs/12345678,vers=4.1,proto=tcp rw ip=dhcp rootwait elevator=deadline

nfsroot=でNFSのバージョンをvers=4.1と指定している。起動しない場合は vers=3としてNFS v3を使うように設定する(2020-05-27-raspios-buster-lite-armhf.img ではvers=4.1で正常に起動できた)。 NFSサーバー側がCentOS 8の場合は、CentOS 8からはNFS over UDP のサポートがなくなったのでnfsroot=にproto=tcpを入れておく 必要がある。 console=serial0,115200でシリアルコンソールにもメッセージがでるようにしておくと 端末エミュレータのスクロールバックで起動メッセージを全部読めるので便利。 シリアルコンソールにするにはconsole=serial0,115200のほかに /var/tftpboot/12345678/config.txtに

enable_uart=1

を入れておく必要がある。

これでRaspberry Piの電源をいれると起動する。起動しない場合はサーバー側で tcpdumpするなりしてパケットがきているかどうか見るなどする。

起動時にresize2fs_once.serviceでエラーがでる(SDカードに書いて起動すると 最初の起動でSDカードいっぱいにパーティションを拡張するサービス)が

systemctl disable resize2fs_once.service

で出なくなる。

起動時のログ

シリアルコンソールを有効化しておいたときのネットワークブート時の ブート時のログ

/var/tftpboot/bootcode.bin

sed -i -e "s/BOOT_UART=0/BOOT_UART=1/" bootcode.bin

によりearly stage UARTのログも出るようにしたときのログ。

パッケージの更新、インストール

  • NFSサーバー側でセットできれば楽かもしれない(Todo)。

  • プライベートネットワークでもproxyサーバーがあれば、環境変数http_proxyを

    export http_proxy='http://192.168.10.1:80/'

のように設定すればapt-get updateなどが可能(DNSはproxyサーバー側で引くので Raspi側で引けなくてもよい)。

トラブル

Raspberry Pi 3

Plusが付かないRaspberry Pi 3でネットワークブートを試みると、 ネットワークスイッチによってブートできたり、できなかったりする。 起動できないというのは、RPi 3の電源をいれるとネットワークスイッチの LEDが点灯しリンクアップしているが、その後RPi 3側からパケットがひとつ も出ない、という状態である(dnsmasaqサーバーでtcpdumpでパケットキャプチャ して確認した)。SDカードから起動するとこれらのスイッチを使った場合も ふつうにネットワーク接続できている。

ネットワークブートできたスイッチ:

ネットワークブートできなかったスイッチ:

RPi 3+、4ではRPi 3でネットワークブートできなかったスイッチでも正常に ネットワークブートできる。

組織内DHCPサーバーがすでにある場合

IPアドレス、ネットマスクなどを割り当ててくれるDHCPがすでにあるLAN内でも dnsmasq.confの

dhcp-range=192.168.10.255,proxy

とすると、IPアドレス、ネットマスクなどは既存のDHCPから供給、ブートファイルを もってくるtftpサーバーの情報はdnsmasqが動いている計算機から供給するということが できるようです。

固定IPアドレスを割り当てる

MACアドレス毎に固定したIPアドレスを割り当てるにはdnsmasq.confに dhcp-host行を追加する。

dhcp-host=00:01:02:03:04:05,192.168.10.40,myhost

dhcp-host行は必要なぶん、複数指定できる。試してみたらたしかにこれで 固定IPアドレスとして送られる。が、ときどきブートしないようなんだが。 使用RaspiはRPi 3 Model B (プラスなし)。

mmc0: Timeout waiting for hardware cmd interrupt.というログが定期的にでる

次のようなログが定期的にでることがある。

mmc0: Timeout waiting for hardware cmd interrupt.
mmc0: sdhci: ============ SDHCI REGISTER DUMP ===========
mmc0: sdhci: Sys addr:  0x00000000 | Version:  0x00001002
mmc0: sdhci: Blk size:  0x00000000 | Blk cnt:  0x00000000
mmc0: sdhci: Argument:  0x80000c08 | Trn mode: 0x00000000
mmc0: sdhci: Present:   0x1fff0001 | Host ctl: 0x00000001
mmc0: sdhci: Power:     0x0000000f | Blk gap:  0x00000080
mmc0: sdhci: Wake-up:   0x00000000 | Clock:    0x0000f447
mmc0: sdhci: Timeout:   0x00000000 | Int stat: 0x00000000
mmc0: sdhci: Int enab:  0x00ff1003 | Sig enab: 0x00ff1003
mmc0: sdhci: ACmd stat: 0x00000000 | Slot int: 0x00000000
mmc0: sdhci: Caps:      0x45ee6432 | Caps_1:   0x0000a525
mmc0: sdhci: Cmd:       0x0000341a | Max curr: 0x00080008
mmc0: sdhci: Resp[0]:   0x00000000 | Resp[1]:  0x00000000
mmc0: sdhci: Resp[2]:   0x00000000 | Resp[3]:  0x00000000
mmc0: sdhci: Host ctl2: 0x00000000
mmc0: sdhci: ADMA Err:  0x00000000 | ADMA Ptr: 0x00000000
mmc0: sdhci: ============================================

https://github.com/raspberrypi/linux/issues/3092#issuecomment-589981998 にあるように/boot/config.txt (に相当するNFSサーバー側のファイル)に

# /boot/config.txt
dtparam=sd_poll_once=on

と書くと出なくなる。

bootcode.binでearly stage UART (Pre Raspberry Pi 4B)

Raspberry Piブートモード解説文書にRPi 4B以前のRPiでearly stage UARTを 有効にする方法が書いてある。

strings bootcode.bin | grep BOOT_UART
BOOT_UART=0

となることを確認して、bootcode.binをsedで(!)書き換える:

sed -i -e "s/BOOT_UART=0/BOOT_UART=1/" bootcode.bin

RPi 4Bの場合はfirmwareがEEPROMに載っているのでEEPROM上の 設定ファイルを書き換える。 RasPi4-Bootloaderを参照。

⚠️ **GitHub.com Fallback** ⚠️