Zopfcode

かつてない好奇心をあなたに。

めちゃくちゃ頑張って 22 年前の化石ルーターを令和最新ファームにした

本記事は "Kernel/VM 探検隊@東京 No18" で発表したプレゼンのブログ版です。

speakerdeck.com

本記事では電波法に基づく技術基準適合証明を受けた無線ルーターに自作のファームウェアをインストールする箇所がある。この状態で電波を放射した場合、技適が無効つまり違法となる可能性がある。本記事の全工程において電波の放射は一切行っていない。また、同様の遊びを嗜む場合は自己責任において行って欲しい。電波法を守って楽しいルーター改造ライフ!

購入

2024 年のある日、私はある古い無線ルーターを購入した。大昔に発売された、最初期の Linux 搭載無線ルーター WRT54GS である。無線ルーターの機能を増強できる代替ファームウェアとして有名な OpenWrt は、この機種の兄弟である WRT54G から生まれた。

「青いルーターで有名な WRT54G シリーズを手に入れて、最新の OpenWrt を動かしてやりたいよなぁ…」

そう考えながらヤフオクを見ていたら、OpenWrt に適した機種である WRT54GS v1 のきれいな個体を発見した。

OpenWrt が動く最低要件: flash memory と DRAM

「OpenWrt に適した機種」とはどういうものだろうか。

OpenWrt の動作可否を分けるのは SoC や中身の CPU ではなく、ズバリ flash(フラッシュメモリ)と DRAM の容量である。Flash には Linux kernel と user space のソフトウェア(ルートファイルシステム)が全て入る。実行時に DRAM にそれらがコピーされ*1、CPU があれこれ作業する。計算や DRAM や I/O が遅い分には待てばいいが、記憶容量の不足はどうしようもない。よって、これら 2 つの十分な容量が動作の絶対条件となる。

限界ポーティングに基づく個人的な知見から言えば、本記事執筆時点での OpenWrt (master*2​) は flash 8 MB と DRAM 32 MB があればギリギリ動かせる。Flash が 8 MB あれば、最低限の構成 (defconfig) にプラスで Web UI である LuCI を入れても案外収まるし*3、DRAM が 32 MB あれば起動直後の free コマンドで 10 MB ほどが available と表示される*4

現代の OpenWrt がギリギリ動く flash と DRAM の容量を示した図。

"8/32" を満たす機種を探すと、残念ながら WRT54"G" だといずれのリビジョンもこれを満たさない。時期も見た目も SoC も同じで、わずかに高いスペックを持つ WRT54"GS" の初期リビジョンは、ぴったり 8/32 を満たしている。

WRT54"G" のリビジョン一覧。最もリッチな構成の初期リビジョンでも 4/16 しかない。
出典: https://en.wikipedia.org/wiki/Linksys_WRT54G_series

WRT54"GS" のリビジョン一覧。v3 までは 8/32 を満たす。
出典: 上に同じ

かくして私は、WRT54GS の初期リビジョンを狙って購入することにした。

入手成功

そもそも Linksys の製品を中古市場で見かける機会が少ないので、購入は困難だろうと予想していた。と思いきや、買うと決心した日に v1 の個体がヤフオクに転がっていたのを発見した。こんな古い製品のために争う相手もいないことから、出品価格そのままであっさり入手に成功した。

ヤフオクの商品ページ。箱と PC カード付き。

商品画像のシリアルナンバーの箇所を拡大したところ。CGN1 から始まるので v1.0 と同定できる。

届いた品を確認すると、ビックカメラ有楽町店で平成 17 年(2005年)に購入した証明シールが付属していた。外箱こそひどく擦れていたが、本体は使用歴がほとんどないのか 20 年以上の経過を感じさせないほど状態が良かった。

届いた本体。非常に状態が良い。

ビックカメラ有楽町店で平成 17 年 4 月に購入されたことを示す証明シール。

予想以上の美品を手に入れて上々のスタートを切りつつ、OpenWrt のビルドへと駒を進めた。

ファームウェアはビルドするだけ、と思いきや

WRT54GS はとうに OpenWrt のサポート範囲を外れているため、公式のビルドは 22.03.7 を最後に途絶えているが、実はソースコードからは drop されていない。つまり、OpenWrt を Git で clone して機種の設定だけ行えば、すぐにファームウェアがビルドできる。私はまず手始めに OpenWrt の stable release である 24.10.1 をビルドした。

OpenWrt をどういう構成でビルドするか設定する menuconfig のスクリーンショット。WRT54GS 向けに設定している。

ビルドは難なく終わり、昔懐かしい雰囲気の漂う設定画面を眺めながらファームウェアをインストールした。

設定画面のスクリーンショット。往時を思わせる風合いがある。

ファームウェアアップグレード画面のスクリーンショット。

再起動してすぐ異変に気がついた。Ethernet がリンクアップせず、SSH で接続できない。シリアルコンソールで ifconfigdmesg を確認したところ、どうやら Ethernet PHY*5 の初期化に失敗して Linux に認識されていないようだった。

シリアルコンソールでログインして ifconfig -a を実行したところ。Interface が lo (loopback) しかない。

以下は OpenWrt 24.10.1 のカーネルログの一部である。libphy: PHY fixed-0:00 not found から明らかにエラーっぽいメッセージが並んでいて、PHY が登録できなかった旨が読み取れる。

[    3.729778] adm6996: adm6996_gpio: ADM6996L model PHY found.
[    3.763375] b44 ssb0:1: could not find PHY at 30, use fixed one
[    3.775831] libphy: PHY fixed-0:00 not found
[    3.780449] b44 ssb0:1: could not attach PHY at 0
[    3.790291] b44 ssb0:1: Cannot register PHY, aborting

問題切り分けの第一歩として、OpenWrt 公式ビルドが提供されていた最後のバージョン 22.03.7 をインストールしたところ、問題なく eth0 が表示された。さらにバージョンを詰めていくと、23.05.5 では動くが直後のリリース 24.10.0 では動かないことがわかった。以下は 23.05.5 のカーネルログの一部で、fixed-0:00 なる PHY が mdio_bus で見つかり attach されたと表記されている。

[    3.820714] adm6996: adm6996_gpio: ADM6996L model PHY found.
[    3.850127] b44 ssb0:1: could not find PHY at 30, use fixed one
[    3.862767] bus=mdio_bus name=fixed-0:00 driver=mdio_bus dev=(ptrval)
[    3.870028] Generic PHY fixed-0:00: attached PHY driver (mii_bus:phy_addr=fixed-0:00, irq=POLL)
[    3.879142] b44 ssb0:1 eth0: Broadcom 44xx/47xx 10/100 PCI ethernet driver 00:0f:66:ce:c0:e3

この時点で私は問題の原因が Linux kernel 内部にあると悟り、最終的な PR 提出も視野に入れつつ OpenWrt の master にパッチを追加するプランへと作戦を変更した。

問題が発生した箇所を Git で特定

原理からいえば、OpenWrt のリポジトリで 24.10.0 から 23.05.5 まで遡っていけば、いつかは問題のコミットにたどり着くだろう。しかし、何万ものコミットをひとつひとつ遡るのは時間がかかりすぎる。そこで、「問題を生じさせたコミットを高速に見つける機能」である git bisect の世話になることにした。Git bisect を使えば、あるコミット2つの間を二分探索で反復横跳びしながら動作可否をマークして、問題が生じた最初のコミットを素早く発見できる。

今回の場合、タグ v23.05.5 では動いたが、v24.10.0 では動かなかった。両バージョンの共通祖先から*6 v24.10.1 までの間で二分探索すれば原因が見つかると予測し、Git bisect を開始した。

休憩しつつ2日かけて bisect した結果、Linux kernel が 5.15 から 6.1 に切り替わったところで PHY を認識しなくなることを突き止めた。

まだ問題は解決していないが、真因に着実に迫れているのでここで少しガッツポーズをした。そして、何が悪いのかをバージョン間の差分から見つけていくステップに入った。

根本原因探しの旅

PHY 初期化周辺のログを眺めると、"could not find PHY at ..." や "could not attach PHY at ..." など特徴的な文字列が並んでいる。これらのログを出している行をヒントに問題の箇所を文字列検索で発見し、考察に入っていくのがカーネルデバッグの定石である。今回も、正常なバージョンと異常なバージョンで実行パスが分かれる箇所を文字列をヒントにして発見できた。

この時点で、原因について以下のようにいくつかの予想を立てた。

  1. 暗示的に操作されていた何らかの GPIO*7 が操作されなくなり PHY がリセットを抜けなくなった
  2. PHY を登録するカーネル内部の API について、シグネチャは変わっていないが呼ばれるコンテキストや意図が変化した
  3. もともと偶然合致していた初期化の順番が前後して、依存関係の根もとから順に初期化されなくなった*8

仮説ベースで修正を加えながら調査したため困難を極めたが、最終的に2と3が実際に原因であると判明した。修正は PR にまとめて GitHub で提出し、現在レビューを通過してマージを待っている。

github.com

追記: 上記 PR を見て反応していただいた LKML 上のメンテナーの方々によって、より簡潔な修正が示されているため、OpenWrt 側の参照する Linux のバージョンが更新された暁には上記のパッチは不要になると見ている。

修正 & 動作成功

これでついに、ようやく、令和最新版の OpenWrt を約 22 年前のルーターである WRT54GS で動かせた。問題解決を経て達成感もひとしおである。

Web UI があったほうが見た目にわかりやすいので、数 MB 残された flash に LuCI を追加して動かしたところで記念写真。Flash 残り 1.88 MB、DRAM 残り 9.0 MB という限界極まるフィニッシュとなった。

LuCI のトップ画面を背に誇らしく動作する WRT54G。

おまけ: MAP-E でネットに出よう

せっかくなので、このルーターに IPv6 の世界を見せてやりたい。発売当時のファームでは IPv6 を知らなかったこのルーターも、令和最新 OpenWrt を手にした今、MAP-E や DS-Lite が自由に扱える。やってやろうじゃないか。

複雑な日本のインターネットで OpenWrt に VNE へ接続させると設定が大変になりがちだが、Linksys の最新 Wi-Fi ルーターに MAP-E させた時の知見が頭にあったため難なく接続できた。この手順について興味があれば以下の記事を参照してほしい。

www.zopfco.de

LuCI を見ると、wan6 インターフェースに有効な IPv6 アドレスが表示され、WANMAP インターフェースに有効な IPv4 アドレスが表示されている。実際に WRT54GS 経由で適当に YouTube を開いたところ、微かに遅れは感じるが思ったより高速にページが表示され、動画の閲覧も問題なくできた。

MAP-E による接続が確立した時点での LuCI のスクリーンショット。

スピードテストも実行してみよう。日頃 7 Gbps 出る回線に WRT54G を接続したら、一体どれくらいのスピードが出るのか!?その結果は!?!?

7.7 Mbps でした

P.S. 2026-01-05 改訂。

*1:XIP = eXecute In Place といって、バイト単位で読める NOR flash など条件を満たせば、バイナリを DRAM にコピーせず flash から直接実行できる SoC もある。その場合 DRAM 容量の下限が下がる。

*2:最も先端の開発バージョンのこと。main とも呼ばれる。

*3:Flash 4 MB の場合、ルートファイルシステムをほぼ無にしつつ、printk の無効化など明らかに消したらダメそうなオプションを消してまでカーネルをダイエットしなければ収まらない。

*4:DRAM 16 MB の場合、どう頑張っても init に達する前後で OOM Killer が発生する上、kill できるプロセスがない場合は kernel panic に陥り世にも珍しいエラーメッセージを目にすることになる。

*5:Ethernet の物理層を実装するパーツ。SoC とつながる MDIO というバスと、RJ45 端子にやってくる物理層の信号とを相互に変換する。

*6:メジャーリリースでバージョンが切られた時点で master から分岐しメンテナンス状態になるため、リリースタグと master は共通祖先以降異なる歴史を歩む。OpenWrt のバージョニングについて詳細はこちらを参照。

*7:電圧の高低だけを出力できる基本的なデジタル I/O。

*8:すべての初期化を手続きで書いている古式ゆかしいコードで、かつ現代では一般的な EPROBE_DEFER(依存先が初期化されていない等の理由で初期化を順延したいとカーネルに伝えるための errno)が見当たらないため、初期化の順番が入れ替わることを想定していないのではと推測した。