略解
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以外の環境変数でデバイスを一意に取ってきても良いが、私の環境では他に使えそうな変数はなかった。 ↩