先日、Buffaloの無線ルータ WZR-450HP にDisplayLink社のUSBグラフィックスアダプターを接続して、PCモニタにshellを表示することに成功したのでそれまでの道のりを紹介しようと思う。 実験記録みたいな感じなので、単にアダプタを接続するためのHowToではないことに注意。とにかく語りたいだけ(笑)
この記事の内容は、先日弊社で開催のFULLER エンジニアミートアップで発表した未完成のスライドおよびkosen10sLT #03で発表したスライド(下記)と同一だが、内容を時間の都合で多少端折っていたので、この記事では発表でしゃべらなかった細かい所も詳細に語りたいと思う。
www.slideshare.net
事前におことわりしておくが、私は電波法を侵さないよう細心の注意を払いながら、要は電波を飛ばさないようにしてこのハックを行っている。 改造後も内蔵のPHYは使わず、外付けのUSB Wi-Fiドングル(単体で技適認定を受けた製品)をルータに接続してWi-Fiを利用している。アンテナをぶっ挿している事もあるが、その場合はすべて演出もしくは無改造のルータであることに注意頂きたい。
TOC
- Introduction ルータとLinuxとOpenWrt
- 1. 前例を調べる
- 2. OpenWrtをビルドする
- 3. 悪戦苦闘・一旦諦める
- 4. 再開
- 5. Linuxを学ぶ
- 6. 成功
- 7. 今後
Introduction 1. ルータとLinuxとOpenWrt
家電量販店に並ぶ無線ルータ。あなたは、ルータの中身に興味を持ったことがあるだろうか? 実は、あれらはほとんどが組み込みLinuxで動いている。
私はRaspberry Piが発売された時に初めて、世の中にRISC CPUの Linuxが浸透していくのを感じたのだが、 実はそれより遥か昔から組み込みLinuxマシンは一般家庭に浸透していた。それがルータである。
「昔からMIPS含め組み込みLinuxは一般的だから」というツッコミもあると思うが、私に言わせればそれはギークの世界だけの話である。 極端な話、昨日初めてパソコンを買ったような人でも多少の手助けですぐ組み込みLinuxの世界に入れるようになったのは、 Raspberry Piが出てからである。x86とのアーキテクチャの違いをほとんど意識しなくてもよくなったのが大きい。
話を戻す。ルータでLinuxが動いているとわかれば、次に興味が湧いてくるのは「ルータを好きなようにいじって遊ぶことができるか」 ということだ。PS3のような極めて特殊な環境下でも、その上で動くLinuxさえ手に入れば後は遊び放題であるはずだ。
答えはYES。ルータには幸い、OpenWrtをはじめとするディストリビューションが存在する。 ルータは本来ユーザによる改造を意図しないハードだが、製造側でデバッグや修理ができないと色々と困ることから どのようなルータであっても必ずシリアルポートが用意してある。スルーホールやピンヘッダがなくても、プローブを押し当てるランドは絶対にある。 シリアルポートは最も低レベルなアクセスができるため、ブートローダが起きてからLinuxのブートが完全に起動するまで、 すべてのログを読みだすことができる。
また同時にほぼすべてのルータが、LAN越しのファームウェア書き込みの手段を提供している。改造対策のあるなしはあるにしても、 tftpでバイナリを投げればホイホイとファームウェアをFlashに書き込んでくれる。 たったこれだけで、超安くてハイパーお手軽な組み込みLinuxマシンが手に入るのだ。
費用対効果で言えば、Raspberry Pi Zeroが発売されるまではこれに勝るLinuxマシンは無かったし、 今現在も入手性やNICの多さでは負けていない。
私は、ルータ芸人を自称するほどにはこの世界に心酔している。
初めて改造ファーム(DD-WRT)をルータに書き込んだのは中3の夏だが、 ルータハックもとい「ルータ芸」を行うようになったのは高専4年、つまり2年ほど前である。 当初はルータに付いているUSBコネクタにはDACやUSBメモリ・マウス・キーボードなどそれらしいハードを繋いで遊んでいたのだが、ある日
「ルータにPCのディスプレイを接続し、スタンドアロンなLinuxマシンとして使えないか」
と思い、実行に移すことにした。
1. 前例を調べる
前例を調べると、同様の事例が3つあった。それぞれ、
- キーイベントの獲得から画面出力までひたすらコードを書いて力技で解決したもの
- カーネルの機能だけで済ませようとしたもののうまくいったかよくわからないもの
- BuffaloのNASにDebianを入れXまで動作させたもの
といった顔ぶれで、NASの例に関しては本当に驚いた。 ただ、ルータに限ればどちらもしっくり来るとは言えなかった。
ただどれも共通していたのは、DisplayLink社のUSBアダプタ(IC)を使っていることだった。さらに調べてみるとLinuxのソースツリーにDL社のICを駆動するドライバ udlfb がマージされていることも判明した。
直後にFULLER株式会社に入社しアメリカに行く機会を得たため、アメリカにて$15で販売されていたDL-165搭載のアダプタを購入した。
2. OpenWrtをビルドする
日本に帰ってきて早速とりかかった。
まずは前例に従いudlfbの導入を行った。Ubuntu TrustyのVMを用意しOpenWrtのBuildrootをインストール、make kernel_menuconfig
でDisplayLink関連のconfigをModuleにした。
ちなみに、OpenWrt謹製のMakefileでは、追加のKernel Moduleはファームに格納されない場合があるので注意が必要である。私はOpenWrtでSysupgradeを使ってファームを上書きした後、scpでkoファイルを転送した。
scp ./ko/* root@192.168.1.1:/lib/modules/3.xx.xx/
modprobeでkoをロードしてみたら、
画面が緑一色になった。ここまでは事前情報として得ていたので何ら心配はない。正常に認識していれば画面は緑一色になる。
udlfbがDLシリーズのICを正常に初期化すると、/dev/fb0
(Framebuffer, ここに絵を描くことによって画面にそれが描画される。いわばキャンバスのようなもの)が生成され、dmesgにEDID情報などが出力される。
実際、dmesgやFramebufferの所在を確認してみたらどれも正常だった。でもshellが表示されないのはおかしい。
よく考えたら、Raspberry Piは起動した瞬間からshellが表示される。あれは何なんだろう?と思い調べてみたところ、Framebuffer用のコンソールであるfbcon
(FrameBuffer CONsole) があれであることが判明した。本当に何も入っていないのがルータのLinuxである。
ここでfbconを導入する。もう一度ビルド。
今度は画面が真っ暗になった。(写真は撮り忘れた)
3. 悪戦苦闘・一旦諦める
画面の電源は切れないので正常に認識してはいるようだが、どういうわけか何ら反応がない。キーボードをつないで色々押してもなしのつぶてである。ここから設定を少しずつ変えつつ何度となくカーネルのビルドを繰り返したものの、全く変化はなかった。
どうしてうまく動かないんだ…
dmesgは正常、echo 1 > /sys/class/graphics/fb0/blank
や、busyboxのアプレットに追加したfbset
を使うなどして画面が変化することも確認した。しかし文字は出ない。
キーボードのKernel Moduleを忘れたか?いやそれも入っているし/dev/input
にもデバイスファイルがある。vtconsoleもある。
結局何度か徹夜したものの全く進展がなく疲れたので、このネタはお蔵入りとなった。
4. 再開
お蔵入りになって2ヶ月経った2016年1月、会社でエンジニアミートアップを行うとの連絡があった。弊社CTOが「ルータの発表聞かせて」と私に言ったところでお蔵入りになったこのネタを思い出した。
今やるしか無い…
そして死闘が始まった。
OpenWrtはtrunkのバージョンが上がっていたので、新しいLinux 4.xの方がコードも新しいだろうと思いtrunkを更新した。2ヶ月前はChaos Chalmerがリリースされてそんなに経っていなかったのに、trunkはいつのまにかDesignated Driverという次世代のコードネームに変わっていた。
画面が真っ暗になるところまで再現した。さてここからどうするか… 結局、前回と同じところを試行錯誤しても全く進展はなかった。
ここで再びRasPiを思い出す。RasPiでもPCでも「起動した瞬間に画面に文字が出て、キーを押すと反応する」ことが当たり前すぎて気づかなかったのだが、
何でPCを起動したら画面にshellが出て、何でキーを押したら画面に反映されるんだろう
という、根本的な事を今まで考えていなかった事に気づいた。 つまり、RasPiでは「入出力を司る何か」があると気づいた。
RasPiのLinuxにはこの「何か」を窓口に待ち受けるshellが最初からいて、キーイベントが何らかのルートでこの「何か」を通り、さらにそれに対してshellが受け答えているのではないか…加えて、この「何か」を通じる入出力を傍受してPCモニタに出力しているプログラムがRasPiでは動いているのではないか…という所まで考察が進んだところで、Linuxの動作原理について調べることにした。
5. Linuxを学ぶ
Linuxにおいて入出力といえば…なんだろう…と考え始めて浮かぶのにそう時間はかからなかった。
- Arduinoにプログラムを送信したり通信したりするときは
/dev/ttyUSB*
を使う - シリアルコンソール一般は
/dev/ttyS*
で通信できる - UbuntuでCtrl+Alt+F1を押すと以下の様に
tty1
と表示される
どうやらTTYに大きなヒントがありそうだと感じ、調べてみた。すると以下のサイトがヒットした。
上記サイトによると、TTYは入出力の窓口として機能していて、SSHやシリアルコンソールなどはtty・ptyを通してshellとやりとりしているらしい。
さらに別サイトによると、かつてUNIXサーバにVT100などの端末をつないでいた時代、端末とサーバは長いシリアルケーブルで接続していて、サーバのシリアルから入力はttyへの入力、シリアルへの出力はttyへの出力として表されていた。現在でもその形はそのまま残り、ttyは物理的なシリアルポートの入出力・ptyは仮想的なポートの入出力(SSHなど)を担っているとのことだった。
VT100は伝説として知っていたが、TTYとシリアルの関係は全く知らず、もやもやしていたものが一気に具体化していった。
前節で言った「何か」はどうやらTTYらしい。ということは、fbconはttyを読み書きしているらしいことは確実である。調べたところ、fbconのREADMEに以下のような記述があった。
fbcon=map:<0123>
This is an interesting option. It tells which driver gets mapped to which console. ...
tty | 1 2 3 4 5 6 7 8 9 ... fb | 0 1 2 3 0 1 2 3 0 ...
READMEによると、fb0は標準でtty1と紐付けられているらしいことが判明した。 ここで、上記のTTYの解説記事にて書かれていた、「ttyにcatすると、そのttyに紐付けられた端末に文字が出る」というのを試してみた。
echo "Hello World!" > /dev/tty1
すると…
うおおおおおおおおおおおおおおおおおおお!!!!!!!!!!!!
やっと!!!!!!!!文字が!!!!!!!!!!!!!!
6. 成功
色々なものが動作していない可能性を考えていたが、文字が表示されたことを考えるとゴールは近いと確信した。あと一息だ!!
ttyに書き込まれた文字はfbconへ出力された。この書き込まれた文字を受け取り、応答を返すshellがいないので調べてみると、/etc/inittab
がこのあたりの動作を定義していることがわかった。
inittabには、「カーネルが起きた後initがどうすればよいか」や、「TTYへの着信があった際にどんなプロセスを呼べばよいか」が書いてある。前者についてはお馴染みのrcSスクリプトの実行が関係していて、後者はgettyに代表されるような、シリアルポートの初期化を行い、着信のあったTTYへログインスクリプトを実行させるプログラムを呼び出す設定である。
なるほど、じゃあtty1に対して/bin/sh --login
を割り当てればいいじゃないか。
tty1::askfirst:/bin/sh --login
と書き加えて再起動した…すると…!!
キターーーーーーーーーーー!!!!!!!!!!!111111111
すばらしい!!長い間この瞬間を待ちわびてきた。苦労の末に、ついにBuffaloのルータはMIPS Linuxのスタンドアロンマシンになったのである。
tmuxだって動くぞ!
どうすればshellの表示にこぎつけるか判明したので、modules.dの中に新たにudlfbやfbconを書き込み、起動とともに自動でディスプレイにbashが表示されるようになった。
7. 今後
今後やりたいことはわんさかあるのだが、メモリ容量の限られたルータにおいて(ZRAMやSwapは使うにせよ)Xを動かすのは荷が重い。しかしながらウインドウ表示の夢は全く諦めていない。考えていることを雑にまとめると以下のようになる。
- Xなしでウインドウマネージャとして振る舞えるソフトを使う(例: Qt)
- Wayland / Weston にアプローチする
- USB 3.0に対応したルータで、3.0対応の最新DLチップを使う(mesaにも期待できる)
- DirectFB
ちなみに、今回の成果を元にルータでプレゼンを行うためのコードをPythonで書き、実際にプレゼンを行った。このことについては後日別記事で書くことにする。
Photo by: id:marin72_com