略解
USB-OTG以外の方法で端末へのアクセスが可能な状況を想定.
-
nmcli dev
で出てくるデバイス一覧をメモっておく -
(大既出)
/boot/config.txt
と/boot/cmdline.txt
に 追 記 (sudo
)
/boot/config.txt
:dtoverlay=dwc2
/boot/cmdline.txt
:...rootwait modules-load=dwc2,g_ether ...
-
再起動
-
nmcli dev
でusbとおぼしきデバイスが追加されているのを確認する(典型的にはusb0
、以降<usb_dev>
と記述) -
sudo nmtui
>接続の編集
><追加>
>Ethernet
で新しいEthernet接続を作成-
プロファイル名
: 適当に -
デバイス
:<usb_dev>
-
Ethernet
: いじらない -
802.1X Security
: いじらない -
IPv4 設定
:<ローカルにリンク>
-
IPv6 設定
:<無効>
-
自動的に接続する
: チェックをつける(デフォルト) -
全ユーザに使用可能
: お好みで
で<OK>
-
-
sudo nmcli con mod USB-OTG connection.autoconnect-priority 0
を実行 -
udevadm info /sys/class/net/usb0
でID_PATH
の値を確認(典型的にはplatform-fe980000.usb
、以降<IDパス>
と記述) -
/etc/udev/rules.d/85-usb-otg.rules
を作成(sudo
)SUBSYSTEM=="net", ACTION=="add|change|move", ENV{DEVTYPE}=="gadget",ENV{ID_PATH}=="<IDパス>", ENV{NM_UNMANAGED}=""
-
シャットダウン
-
USB-OTGする母機に電源を接続
-
ip addr show usb0
でIPv4アドレス169.254.~.~
が振られていることを確認 -
母機と端末で
netcat
やssh
などでトランスポート層の通信ができることを確認
成功したら成功
動機
Raspberry Pi 4B(raspi)をサーバ用途に使っていて、かつ有線環境がない場合、必然的にWi-Fi経由のssh
でアクセスすることになる。これではWi-Fi環境が変化する(例: 転居・帰省)と直ちに世界と断絶した文鎮と化し、緑ランプが付かないことを祈りながら電源ケーブルを抜くという危険な"shutdown
"を行わざるを得ない1。ここからWi-Fi
を回復するには別のPCにmicro SDを刺し、ext4領域をマウントして設定をいじるというアクロバティックな操作が必要になる。
実はRaspberry Pi 4BにはUSB Ether Gadgetという、電源ケーブルを通信に兼ねることができ、適当なPCにこれをぶっ刺すとそれがEthernetとして振る舞うという機能がある。世間では専らUSB On-The-Go(USB-OTG)の名で通っているのだが、ともかくこの通信路をraspiとPCの双方でいい感じに認識してもらえれば、ここからssh
し放題というわけで、無事上述の問題は解決できる。
USB-OTGの導入方法はあちこちに転がっている(略解の2の部分)。しかし今回Raspberry Pi OSの基盤とするDebianのバージョンが12 (bookworm)に上がるのに伴って、ネットワーク管理がdhcpcd
からNetworkManager
に移管されて、既出の方法では正しく認識してもらえなくなった。
今回記述するのは上述のOSアップデートに伴うUSB-OTG設定についての、専ら正攻法による解決策である。略解は上で述べた通り。以下に詳解(機序)を述べる。
詳解
ブートオプションの編集
- 略解の該当箇所 : 2
この部分はRaspberry Pi USB-OTG
とかで調べればいくらでも湧いてくるが、一応注意を述べておくと、絶対に呪文を間違えないこと。ブートオプションでやらかすということはOSの立ち上げでしくるということなので、ほぼ確実に起動が途中で停止してしまう。それ則ち緑ランプお祈り電源ぶっこ抜きなので、なるべく避けたい2。複数の記事をチェックして整合性をとった上で、タイポがないか確認するべき。特に、無闇な空白の挿入は引数の意図しない解釈に繋がり危険なので、自分の判断で書き換えてはいけない。また/boot/cmdline.txt
の追記についてはちょこちょこ明示しない記事が散見されるが、すでに入っている引数のうちrootwait
の直後に、modules-load=dwc2,g_ether
を両側空白つきで挿入するという操作である。
NetworkManager
の設定
- 略解の該当箇所 : 1, 4, 5, 6
上記の操作の後再起動すると、ip a
とかnmcli
とかで見えるインタフェース/デバイスにUSB系統のものが増えているはずである(典型的にはusb0
の名が与えられる)。かつてdhcpcd
の頃には自動でIPを割り当てて起動までしてくれたのだが、NetworkManager
では自分で設定をいじる必要がある。とはいっても、このレイヤではただのEthernetと代わりがなく、設定もEthernetの設定と大差ない。せいぜい迷うのはDHCPサーバがいないのをどう補うかぐらいである。
nmcli
でうだうだやってもいいが、面倒なのでnmtui
を使った方が話が早いだろう。設定の書き換えには管理者権限が必要なのでsudo nmtui
とやるとGUI擬きが起動される。ここでの操作はざっくり十字キーで移動、スペースキーで選択/決定である。あとは書いてある通りに接続の編集
> <追加>
> Ethernet
と選んでいって設定詳細の画面に入る。
プロファイル名
、デバイス
、Ethernet
、802.1X Security
、自動的に接続する
、
全ユーザに使用可能
は自明なので省略しよう。問題はIPv4 設定
とIPv6 設定
である。正直ここについてはベストプラクティスがまだ見通せていないのだが、Ethernet(仮想的な)で2端末を繋いでいて、しかもどちらもハブではないという状況を考えると、リンクローカルアドレス、各々がめいめいにランダムなアドレスを宣言するのが良いと考えた。DHCPがいないのだから動的アドレスは無理だし、共有アドレスはむしろraspiのネットワークアクセスを母機に共有する形になるから不本意である。他に芽があるとしたら静的アドレス設定だが、あまり理解していないので放棄(知見のある方は編集リクエストからどうぞ)。一旦<ローカルにリンク>
を選択することにした3。またIPv6アドレスがあっても大して嬉しくないので<無効>
とした。
略解では省略したが、IPv4 設定
とIPv6 設定
の<表示する>
詳細設定を開き、このネットワークはデフォルトのルートには使用しない
にチェックを入れておくと良いだろう。リンクローカルアドレスを選んだ以上デフォルトゲートウェイは生えてこないが、今後設定を変えたときに通信が吸われたりしたら困るので、安全弁としていじっておく。
これでほとんど設定は終わりだが、nmtui
からは弄れない部分が一箇所あり、それはconnection.autoconnect-priority
なる、自動接続の優先度を決める数値である。理由は知らないがEthernetの初期設定だとここに-999
が入っており(これはnmcli con show <プロファイル名>
で確認できる)、このままだとconnection.autoconnect
が有効(はい
/Yes
)になっていても起動時の自動接続が発生しない。sudo nmcli con mod <プロファイル名> connection.autoconnect-priority 0
としてやると修正できる。ただこれではWi-Fiの優先度の初期値0とダブってしまい、実際に確認したことはないがWi-Fiが起動されないとかの不具合を起こしそうなので、同様のコマンドでWi-Fiの優先度を上げておくといいだろう。
udev
の設定
- 略解の該当箇所 : 7, 8
上記の操作後、nmcli con up/down <接続名>
でUSB-OTGを立てたり止めたりできるようになり,その上で起動時の自動接続を行うよう設定したが、実はこの段階で再起動しても自動接続は全く行われない。試みられることもない。
この状況でjournalctl -u NetworkManager -b
としてNetworkManager
のログを見ると、usb0
は
manager: (usb0): new Ethernet device (/org/freedesktop/NetworkManager/Devices/3)
みたいな行が1行あるきりで、以降全く触られていないという結果が出るはずである。つまりNetworkManager
はusb0
を認識はしているが、何らかの力が働いてその管理を放棄している。
ここで仮定するのは、NetworkManager
を制止しているのはデバイス管理のサブシステムだろうということだ。というのもusb0
がそこから生えてくるからである4。Raspberry Pi OSではデバイスの管理はudev
が担当しているので、その周りを漁ってみると/usr/lib/udev/rules.d/
なるフォルダがあり、ここを覗いてみると、
$ ls /usr/lib/udev/rules.d/
10-local-rpi.rules 60-persistent-input.rules 70-touchpad.rules 77-mm-longcheer-port-types.rules 80-net-setup-link.rules
10-vc.rules 60-persistent-storage-dm.rules 70-uaccess.rules 77-mm-mtk-port-types.rules 80-noobs.rules
15-i2c-modprobe.rules 60-persistent-storage-tape.rules 71-seat.rules 77-mm-nokia-port-types.rules 80-udisks2.rules
40-usb_modeswitch.rules 60-persistent-storage.rules 73-seat-late.rules 77-mm-qcom-soc.rules 81-net-dhcp.rules
50-firmware.rules 60-persistent-v4l.rules 73-special-net-names.rules 77-mm-qdl-device-blacklist.rules 84-nm-drivers.rules
50-udev-default.rules 60-rpi.gpio-common.rules 75-net-description.rules 77-mm-quectel-port-types.rules 85-hwclock.rules
55-dm.rules 60-sensor.rules 75-probe_mtd.rules 77-mm-sierra.rules 85-nm-unmanaged.rules
60-autosuspend.rules 60-serial.rules 77-mm-broadmobi-port-types.rules 77-mm-simtech-port-types.rules 90-alsa-restore.rules
60-block.rules 60-tpm-udev.rules 77-mm-cinterion-port-types.rules 77-mm-telit-port-types.rules 90-console-setup.rules
60-cdrom_id.rules 60-triggerhappy.rules 77-mm-dell-port-types.rules 77-mm-tplink-port-types.rules 90-nm-thunderbolt.rules
60-dma-heap.rules 64-btrfs.rules 77-mm-dlink-port-types.rules 77-mm-ublox-port-types.rules 90-pi-bluetooth.rules
60-drm.rules 69-libmtp.rules 77-mm-ericsson-mbm.rules 77-mm-x22x-port-types.rules 92-libccid.rules
60-evdev.rules 70-camera.rules 77-mm-fibocom-port-types.rules 77-mm-zte-port-types.rules 95-dm-notify.rules
60-fido-id.rules 70-joystick.rules 77-mm-foxconn-port-types.rules 78-sound-card.rules 96-e2scrub.rules
60-flashrom.rules 70-memory.rules 77-mm-gosuncn-port-types.rules 80-debian-compat.rules 97-hid2hci.rules
60-infiniband.rules 70-microbit.rules 77-mm-haier-port-types.rules 80-drivers.rules 99-nfs.rules
60-input-id.rules 70-mouse.rules 77-mm-huawei-net-port-types.rules 80-ifupdown.rules 99-systemd.rules
60-persistent-alsa.rules 70-power-switch.rules 77-mm-linktop-port-types.rules 80-mm-candidate.rules
ここを睨むと、85-nm-unmanaged.rules
なるいかにも関係がありそうなファイルがある(というのも、nm
はnmcli
やnmtui
のように、NetworkManager
の略語として頻出である。)。その中身は以下の通り。
# Do not modify this file, it will get overwritten on updates.
# To override or extend the rules place a file in /etc/udev/rules.d
SUBSYSTEM!="net", GOTO="nm_unmanaged_end"
ACTION!="add|change|move", GOTO="nm_unmanaged_end"
# VirtualBox host networking. Out-of-tree driver that looks like an ordinary
# Ethernet. No parent device (lives in /virtual/), no support for ethtool
# to identify the driver, MAC address defaults to 08:00:27:, but can be
# changed. Interface name will have to do, it's always vboxnet*.
ENV{INTERFACE}=="vboxnet[0-9]*", ENV{NM_UNMANAGED}="1"
# VMWare host networking. Out-of-tree driver that looks like an ordinary
# Ethernet. No parent device (lives in /virtual/), no support for
# ethtool to identify the driver. They have their own MAC prefix that
# can not be changed.
ATTR{address}=="00:50:56:*", ENV{INTERFACE}=="vmnet[0-9]*", ENV{NM_UNMANAGED}="1"
# Parallels Workstation host networking. Out-of-tree driver that looks like
# an ordinary Ethernet. No parent device (lives in /virtual/), no support for
# ethtool to identify the driver and the interface name is too generic.
# However, they have their own MAC prefix that can not be changed.
ATTR{address}=="00:1c:42:*", ENV{INTERFACE}=="vnic[0-9]*", ENV{NM_UNMANAGED}="1"
# Virtual Ethernet device pair. Often used to communicate with a peer interface
# in another net namespace and managed by libvirt, Docker or the like.
# Generally we don't want to mess with those. One exception would be the
# full system containers, like LXC or LXD. LXC containers run via libvirt
# don't use udev, so this doesn't apply. LXD does, though. To deal with the
# LXD situation, let's treat the devices called eth* as regular ethernet.
ENV{ID_NET_DRIVER}=="veth", ENV{INTERFACE}!="eth[0-9]*", ENV{NM_UNMANAGED}="1"
# USB gadget device. Unmanage by default, since whatever created it
# might want to set it up itself (e.g. activate an ipv4.method=shared
# connection).
ENV{DEVTYPE}=="gadget", ENV{NM_UNMANAGED}="1"
LABEL="nm_unmanaged_end"
……ENV{DEVTYPE}=="gadget", ENV{NM_UNMANAGED}="1"
なる行がある。udev
ルールを参照すると、これは環境変数DEVTYPE
が"gadget"
であったときに、環境変数NM_UNMANAGED
を1に設定するという意味で、NM_UNMANAGED
とはその名の通りNetworkManager
が該当デバイスの管理を放棄するフラグになっている。今設定しているのはUSB Ether Gadgetだから、当然このフィルターに引っかかるわけだ。
というわけでこのフラグをへし折ってやればめでたく自動接続が成るわけで、このファイルを書き換えても達成できるのだが、このファイルはudev
パッケージが管理しているのでそれはよろしくない。最上部にも
# Do not modify this file, it will get overwritten on updates.
# To override or extend the rules place a file in /etc/udev/rules.d
とご丁寧に書いてくれているので、/etc/udev/rules.d/
下に適当にファイルを作って上書きすれば良いが、ここで注意が必要なのはudev
ルールファイルはどのフォルダにいるかに関係なく、辞書順に評価されるという点である。迂闊に10-usb-otg.rules
としたり、85-ether-gadget.rules
としたりすると、これらは辞書順で85-nm-unmanaged.rules
より前に来るから、せっかくフラグを折った後に立て直されるという虚無が発生する.かといって他のルールとの干渉も避けたいので、略解では85-usb-otg.rules
としたわけである。
さてルールファイルの中身だが、
SUBSYSTEM=="net", ACTION=="add|change|move", ENV{DEVTYPE}=="gadget",ENV{ID_PATH}=="<IDパス>", ENV{NM_UNMANAGED}=""
これは85-nm-unmanaged.rules
でNM_UNMANAGED
に1
が入る条件をまず持ってきておいて、DEVTYPE
が"gadget"
かつID_PATH
が<ID_PATH>
である時だけNM_UNMANAGED
を空にしておくという挙動をする。これで<ID_PATH>
に対応するGadget機器はNetworkManager
の管理下に入るようになる。
それではusb0
に対応する<ID_PATH>
をどう調べるかというと、udevadm info <デバイスパス>
で対応するデバイスの環境変数を調べられるということ、ネットワークインターフェースが/sys/class/net/
下にあることを使えば、udevadm info /sys/class/net/<インターフェース名>
とすれば求まる5。これを使えばNetworkManager
の制止が解かれ、無事自動接続に成功するはずである。
終わりに
systemd
に連座するサブシステムは、大体適切な設計のもとに適切な拡張が可能な形になっており、扱いやすい。場当たり的に凌ごうとする(今回であれば、起動時にusb0
をアクティベートするサービスを入れる、とか)前に、設定集を当たることをオススメする。
-
Raspberry Pi 5で漸く電源ボタンが付き、SD破壊の危険からは逃れられたが、Wi-Fiに自動で繋がってくれたりはしない。 ↩
-
SDが傷んでもあまり気にしない、所詮使い捨てのOSだというのなら気にせずバンバンやらかすといい。実際そういう壊していい台があった方が検証とかし易くていいよね。 ↩
-
このままだとraspiのIPがわからず接続ができないと思われるが、
mdns
が効いていれば<ホスト名>.local
で接続できる。 ↩ -
実際には
journalctl
を見つめたりudevadm trigger --dry-run
してみたりといった試行錯誤が埋もれている。 ↩ -
無論、
ID_PATH
以外の環境変数でデバイスを一意に取ってきても良いが、私の環境では他に使えそうな変数はなかった。 ↩