NetworkBoot - h-sendai/RaspberryPi GitHub Wiki
Raspyberry Pi 3でネットワークブートするためのメモ
- Raspberry Piブートモード解説文書
- Raspberry Pi財団のチュートリアル: Network Boot your Raspberry Pi
- Raspberry Pi 3でPXEネットワークブート
- Raspberry Piで簡単にネットワークブートができるようになったよ、という話
- Known Problems
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アドレスベンダー部と同一 ようだ)。
デフォルトでは 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ではEEPROMが付き、そのなかにブートローダーが 入っている。ブート順はブートローダー内で指定されているため ネットワークブートするためにはブートローダーを書き換える必要が ある。RasPi4-Bootloader に書き換え方法を まとめておいた。
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 -Pi
でUDP *: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側で引けなくてもよい)。
Plusが付かないRaspberry Pi 3でネットワークブートを試みると、 ネットワークスイッチによってブートできたり、できなかったりする。 起動できないというのは、RPi 3の電源をいれるとネットワークスイッチの LEDが点灯しリンクアップしているが、その後RPi 3側からパケットがひとつ も出ない、という状態である(dnsmasaqサーバーでtcpdumpでパケットキャプチャ して確認した)。SDカードから起動するとこれらのスイッチを使った場合も ふつうにネットワーク接続できている。
ネットワークブートできたスイッチ:
- CentreCom GS 908TPL 「2010年7月現在」と書いてある。
- CentreCom GS 908TPL v2 「2022年8月現在」と書いてある。
- NetGear GS105
ネットワークブートできなかったスイッチ:
- CentreCom GS 908S-TP 「2014年9月現在」と書いてある。
- CentreCom GS 908XL V2 「2017年4月現在」と書いてある。
RPi 3+、4ではRPi 3でネットワークブートできなかったスイッチでも正常に ネットワークブートできる。
IPアドレス、ネットマスクなどを割り当ててくれるDHCPがすでにあるLAN内でも dnsmasq.confの
dhcp-range=192.168.10.255,proxy
とすると、IPアドレス、ネットマスクなどは既存のDHCPから供給、ブートファイルを もってくるtftpサーバーの情報はdnsmasqが動いている計算機から供給するということが できるようです。
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: 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
と書くと出なくなる。
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を参照。