2013年12月2日

Raspberry PiにVFATをマウントする - いろいろ問題が…

Raspberry Pi(RPi)を車載して、低価格(~$50)のドライブレコーダ(車載カメラ+ストレージ)に記録された画像データを帰宅後ガラージュに停車したら自動的にWiFiで我が家のサーバに転送するプロジェクトを進めている。

セキュアなWiFiの接続(確実に自宅サーバだけに接続する)だのカメラのモード(撮影と転送)の切り替えだのカメラの電源のコントロールだのいろいろ問題はあるが、まずはRPiに接続したカメラのデータをWiFiでサーバに転送できるところから実装し始めたが、大まかには動作し始めたものの、カメラをRPiに接続するところからRPi特有と思われる問題いろいろが見つかった。

使用したドライブレコーダ

参考のために、使用したカメラ(ドライブレコーダ)と使用している動作モードを紹介する。
  • モデル: BlackView(当然のことながら中国製)
  • 解像度: 1280x720 30fps
  • 記録: MJPEG(motion JPEG) 約1GB/10min.
  • メディア: 32GB micro SD(最大5時間記録)
  • 電源・転送: USB2.0
  • GPS機能なし(よって時刻合わせは手動)
USBでPCに接続すると撮影モードからデータ転送モードになり、PCからはマスストレージに見える。最初に見つかった問題は、USBのデータ転送が約900MBを超えると必ずコケること。RPiだけでなくUbubtu、Win7などでも同じ症状を見せたのでこのカメラ特有の問題と思われる。これはファイルあたりの記録時間を5分(5、10、15分から選択、5分≒500MB)に制限することで回避。

ストレージクラスデバイスをマウントする

RPiの標準インスタレーションではマスストレージクラスのデバイスしか認識しない。iPhoneやディジタルカメラはイメージングクラスデバイスなので、USBレベルでは認識されても「/dev/sdX」としては認識されない。LinuxでこれらのイメージングデバイスをマウントするにはGVFSを使うが、ここで使ったカメラはストレージクラスなので取り敢えず問題なし。
 
ストレージクラスのカメラは、例えば「/dev/sda1」というブロックデバイスとしてして現れるので単純にマウントできるが、そのままでは自動的にはマウントされない。自動的にマウントするにはいろいろ方法があるようだが、成功したのはusbmountを使う方法。automountではできなかった。usbmountは/dev/sdXNデバイスを自動的に/media/usbMにマウントする(例えば/dev/sda1 on /media/usb0)が、N(パーティション番号)はともかく、XとMは再現性が保証されないので不確定性が残るが、マウント時に起動されるスクリプトが/var/run/usbmount/の下にデバイスのベンダとモデルから生成されたシンボリックリンク(この場合は「Syntek_USB_MSDC_1」)がマウントされたデバイスのルートを指すように作成するので、こちらでアクセスする限り不確定性は実用上問題ない。
 

タイムゾーンが違う!!

最悪にして最大の(同じこと!?)の問題は、RPiのカーネルに含まれるVFATFSがメディアのタイムスタンプをUTCと解釈してしまうこと。おかげでファイルの日付がタイムゾーンの分だけずれてしまう。VFATFSはtz=UTCのマウントオプションを受け付けるはずだが、実際には機能していないようだ。他にもいろいろやってみたが全て不発。複数のUSBマスストレージデバイスをRPiにマウントして同じ現象が発生し、他のLinux(例えばUbuntu)では発生しないので、RPiカーネル固有の問題だと思う。以下のカーネルバージョンでこの現象が確認されている。
  • 3.6.11+ #474 PREEMPT Thu Jun 13 17:14:42 BST 2013
  • 3.6.11+ #538 PREEMPT Fri Aug 30 20:42:08 BST 2013
  • 3.10.19+ #600 PREEMPT Sat Nov 16 20:34:43 GMT 2013
カーネルを自分でビルドするとかモジュールでパッチするとかはやりたくないので、アプリケーションレベルでの回避策を考える。

ローカルで回避

RPiにログインしてローカルでVFATFSのタイムゾーンの問題を回避するにはTZ環境変数を使う。例えば
$ ls -l /var/run/usbmount/Syntek_USB_MSDC_1
 とする代わりに
$ env TZ=UTC ls -l /var/run/usbmount/Syntek_USB_MSDC_1
 とする。ぎこちないが仕方ない。

リモートで回避

そもそもカメラのファイルシステムのタイムゾーンが間違っていると何故困るかと言うと、構築中のシステムではカメラの「RECxxxxx.AVI(xxxxxはシーケンス番号)」という名前のMJPEG録画ファイルをサーバに転送して、最終的に録画開始時刻を基にした「20131201_07:01:23.avi」のようなファイル名のMPEGファイルに変換して保存したいからだ。残念ながら

2013年9月10日

AndroidでCalDAVカレンダを使う

NOOKというAndroidのタブレットの環境を整備している。今回はカレンダ。

我が家のカレンダシステムはThunderbird/Lightningを基にしている。ぐぐるなどの外部サービスに生殺与奪権を与えるのは嫌なので、サーバは自前で、汎用性のあるCalDAVを使っている。CalDAVはWebDAVというHTTP1.1上のプロトコルの上で動作するプロトコル上のカレンダ同期専用プロトコル。WebDAVの上では、CalDAV以外にもXMarksのブラウザの同期プロトコルなどが走る。
 
色々調べた結果、Androidでは個々のカレンダクライアントは直接外部CalDAV(あるいは別のプロトコルの)サーバと交信するのではなく、Android内部の共通カレンダデータベースをアクセスするらしいことが分かった。そうなると、カレンダ(CalDAV)同期用クライアントとカレンダ表示クライアントが別になり、自由度が増す。

と言うことでまた色々調べたが、 CalDAV同期用クライアントで使えそうなICSSyncという有料アプリが見つかった。現在まだお試し期間中だが、必要にして十分な機能をソツなく実現している印象だ。CalDAVサーバとAndroid内部のデータベースを双方向に同期してくれる。

我が家内部の家族メンバ別のカレンダとMozillaの休日カレンダを設定し、同期が実現したのでこれでうまくいくはず、と思ったが、そうは問屋がおろさない。NOOKにデフォルトでインストールされているカレンダが同期したデータを安定に表示しない。そこで代替カレンダを探したのだが、Lightningのようなシンプルでスケジュール管理のできるカレンダはあまりない。結局、ぐぐるのカレンダがこちらの要求に一番合っているのでこれをインストール。
 
しかし、ここでまた伏兵。ぐぐるカレンダは予定の表示は問題なく動作するのだが、 新規予定を作成しようとしたり、既存の予定を編集しようとしたりすると「LOADING...」を表示して固まってしまう。と言っても、致命的ではなく、「戻る」の左矢印をタップすれば抜け出せるが、カレンダの予定が読み出し専用なことに変わりはない。まぁ、とりあえず新規作成と変更はThunderbird/Lightningで行い、ぐぐるがアプリをアップデートするまで待つことにする。

PPTPD VPNサーバを走らせる

最近ハマっているVPNプロジェクトの中でPPTPサーバを走らせてみたが、巷の情報と実際が食い違ったり、よそ様の情報では不明瞭だった事柄が見つかったので書いてみる。
 

その1:PPPD

LinuxのPPTPやIPSecのようなVPNプロトコルの実装は、プロトコル独自の通信レイヤと、複数の異なるVPNプロトコルに共通の仮想ネットワークインタフェースレイヤが明確に分離していて、この共通部分はPPPD(Point-to-Point Protocol)というデーモンで実現される。PPPDはPPTPD(サーバ側)/PPTP(クライアント側)やIPSecといった「プロトコルプロセッサ」から起動され、ユーザが直接起動することはない。
 
ところで、実際にPPTPDを走らせる前にいろいろなサイトで情報を事前チェックしてみると、PPTPDが立ち上がるとすぐにPPPDが起動され仮想ネットワークインタフェースとしてppp+が作成されるようなことが書いてあったにもかかわらず、実際にPPTPDを走らせてみるとPPPDは起動されずppp+も作成されない。さては何かまずい設定か、といろいろなサイトと/etc/pptpd.conf/etc/ppp/options.pptpdの比較をしてみるが、問題は見つからない。PPTPDを起動するときにstraceで追っかけるが、PPPDの起動に失敗した形跡はなく、むしろPPPDなんか起動するつもりがなさそうな形跡だけが目につく。しかも、PPTPDはPPPDなしでそ知らぬ顔でデーモンとしてふんぞり返っている。
 
こういうミステリアスな状態にすっかり混乱し、LinuxQuestions.orgに質問を投げたりもしてみたが有益な情報は得られなかった。
 
ふと思い立って、PPTPDだけが走りPPPDのない状態でクライアントから接続を試みたら、ビックリ仰天!他にも問題があってこの時点でのVPN接続は最終的には失敗するも、サーバ側では接続の試行中にPPPDが走り、ppp+もIPアドレスこそ与えられていないが作成されているではないか!
 
PPTPD(そしてIPSecも)では、サーバデーモン起動時にはPPPD(およびppp+)は起動されず、クライアントから接続要求があったときにオンデマンドで動的に起動・作成されることが分かった。
 

その2:パケットフィルタリング

これは完全に私のチョンボだが、PPTPで開放しなければならないのはGREプロトコル(47)とTCPのPPTPポート(1723)。UDPを使ったOpenVPNのつもりでUDPを開いていたら最初のLCPのハンドシェークで失敗していた。
 

その3:options.pptpd

PPPDに様々なオプションを渡すために、/etc/pptpd.confに「option /etc/ppp/options.pptpd」というコマンドを書くようになっており、そのコメントには「指定省略時には/etc/ppp/optionsが使われる」と書いてある。これは正しくない、あるいは少なくとも正確ではない。PPPDには、「option」で指定したオプションファイルに加え、常に/etc/ppp/optionsのオプションファイルが渡される/etc/ppp/optionsはPPPDの共通オプションファイルで、VPNのプロトコルやサーバかクライアントかに関わらず常にPPPDに渡されるから、内容は真に共通な最小限にするか、または/etc/ppp/optionsが存在しないほうがよい。インストール時のデフォルトの/etc/ppp/optionsの内容は「Lock」の一行のみ。
 

この、PPPDがプロトコルやクライアント・サーバを区別しない(と言うより区別できない)問題はip-up.localip-down.localにも共通するから、PPPDを使う複数のVPNプロトコルやクライアントとサーバを同時に走らせるシステムでは適切な対応が必要。具体的には、これらのスクリプトは以下のような引数で起動されるので、(必要なら)それらで区別する。
  • PPTP(クライアント): インタフェース名(ppp+) pty名(pts+) ボーレート(38400) ローカル(クライアント側)エンドポイントアドレス リモート(サーバ側)エンドポイントアドレス
  • PPTPD(サーバ): インタフェース名(ppp+) pty名(pts+) ボーレート(38400) ローカル(サーバ側)エンドポイントアドレス リモート(クライアント側)エンドポイントアドレス クライアントの物理IPアドレス
PPTPDは6番目の引数としてクライアントの物理IPアドレス(正確にはVPNが走る下位ネットワークのIPアドレス)が追加される。インタフェース名は動的に割り当てられるので、これをプロトコルやクライアント/サーバの識別に使ってはいけない。
 
IPsecはどうなるのかは解析していない。
 

その4:ppp+

上でPPPDはクライアントから接続要求があったときにオンデマンドで起動されると書いたが、これは最初に接続するクライアントのみ。PPTPDは複数のクライアントを受け付けるが、二番目以降のクライアントは最初に起動されたPPPDが面倒をみるので、新たなPPPDプロセスは起動されず、したがってppp+仮想ネットワークインタフェースも「Point-to-Point」の名に反し一つだけで複数のクライアントと接続する。P-t-Pと言うことでOpenVPNのtunインタフェースのイメージを抱いていたが、むしろtapに近い。なおクライアントのエンドポイントアドレスは各クライアント個別に/etc/pptpd.confの「remoteip」で指定されたレンジの先頭からわりあてらていくようだ。ifconfigで見たときのppp+には、最初に接続したのクライアントのエンドポイントアドレスだけがリモート側に表示される。

iPhoneをVPNでLANにつなぐ

このところVPNに少々ハマっている。

数年前から、まず日本に置いた録画サーバをファイルバックアップサーバとして使うときのセキュリティのためにOpenVPNを開通させており、次いで職場のWSと自宅のLANの間にやはりOpenVPNを開通させて重宝している。また、ラップトップから自宅LANへのOpenVPNも構築し、旅行先から自宅の全ネットワークソースを使えている。

最近、NOOKというAndroidのタブレットとAmazonのKindle Paperwhiteを二台づつ買った。Kindleの方は電子本リーダ専用ということで我が家のVPNとは直接関係ないが、二台のうち一台は日本のキンドルストアから日本の本を買うため専用ということで、アマゾンの国別サービス制限を回避するために外部VPNを使っている(こちらの記事参照)。NOOKの方は少なくとも一台は配偶者の仕事用ということで、仕事先から我が家のLANにアクセスできるようにOpenVPNを開通させた。

となると、完璧主義者の小生としては、残りのモバイル機器である家族3人が使っているiPhoneも何とかVPNで我が家のLANに接続させたくなる。実を言うと、接続できたからと言って何ができるようになると言うあてはない。とにかく、まず接続してみたいのだ。

と言うことで仕事が暇になったのをよいことにあれこれやってみた。

使ったのは
・Linuxサーバ:Fedora 13
・iPhone 4S:iOS 6.1.3
・ネットワーク:WiFi(3G/4Gはまだ試していない)

iPhoneはネイティブでPPTP、IPSec、L2TPのVPNをサポートしている(私にはIPSecとL2TPの区別がよくつかない)いるが、結論としてこれらのいづれもダメで、やはりiPhoneもOpenVPNを採用することにした。
 

PPTP

少々の紆余曲折はあったが、Linuxのファイアウォール上でPPTPDを動かし、外部のLinuxクライアントから暗号付きでVPNを構築する異に成功した。

ところがiPhoneで同じように開通させようとしてもエラーになるばかりで、結局暗号化をすべて禁止しないとダメだということが分かった。VPNの目的が中国のようにサイトのアクセス規制のある国から禁止されているサイトをアクセスするためで、VPNの先がパブリックのインターネットなら暗号化は不要だが、ここで目的としているようにプライベートなLANをアクセスするためには暗号化されていないのは論外と言うことで却下。
 

IPSec/L2TP

こちらのVPNの比較記事を読み、試すまでもなくやめた。iPhoneのネイティブサポートに対する期待はあったが、PPTPのような結末では時間の無駄と判断し、性能と安全性にすぐれ(我が家で)実績のあるOpenVPNを進めることにした。
 

OpenVPN

iTunesからOpenVPNのアプリをダウンロードしてインストールしたら、基本的な設定はAndroidの場合と変わるところはない。*.confファイルから*.ovpnファイルを作成するところも同じ(こちらのスクリプトを使う)。
 
*.ovpnファイルの取り込みはさらに簡単で、ファイルを(本文ではなく)添付したメイルをiPhoneに送ってやって、そのファイルを開くとOpenVPNのアプリが起動してインポートが始まる。

さあ、iPhoneからもOpenVPNが開通した。何をさせよう?

2013年8月23日

ファイルシステムを跨いでのファイルの移動にはmvを使ってはいけない

mv(1)は賢い。同じファイルシステム内のファイルの移動ならrename(2)を使ってディレクトリエントリだけを変更し、ファイルシステムを跨いだ移動ならまずcp(1)と同じ動作でファイルをコピーしてからrm(1)と同じ動作で元のファイルを削除する。便利だ。

しかし便利さに甘えてはいけない。

失敗例は、btrfsの一つのサブボリュームから別のサブボリュームにファイルを移動させようとしたとき。mvで移動を開始したがどうも遅い。簡単な計測をしてみると5MB/s程度の速度しか出ていない。SOHO向けのNASで、だ。

straceで調べてみると、ファイルをコピーする前に必ずrenameを実行して本来の移動だけですまないかどうか試して、ダメならコピーを実行する、と言うことを全てのファイルの移動に毎回行っている。一回にかかる時間は僅かかも知れないが、移動したいファイルは50万個ぐらいあるから無視できない。おまけに、コピーの実行には32KBという小さなバッファで毎回read(2)write(2)を呼んでいる。遅いわけだ。多分cpとかtar(1)で実際にファイルをコピーして元のファイルを削除した方が速かったと思う。

今回はbtrfsのサブボリューム間の移動だった。btrfsのサブボリュームはストレージプールは共有するが個別のファイルシステムとして動作する。だから、コピーによる移動は同じストレージプールにコピーして削除と言うバカ臭いことになる。

おまけにmvのコピーによるファイルの移動は、個々のファイルのアトリビュートは全ファイルをコピーし終わるまで確定しないようだ。

今回はストレージの空き容量容量が十分あったので全ファイルを一旦コピーしてからコピー元の全ファイルを削除と言う方法も取れたはずだが、空き容量に余裕がないときはファイルをスキャンしながら一つづつコピーしては削除と言う動作をするスクリプトでも書くか。

2013年8月13日

VMWare PlayerでCDからアプリケーションをインストールする

9年間使ってきたDellのデスクトップが操作できなくなった。このデスクトップは、我が家の家計簿や写真の加工、CADでの簡単な設計など私の仕事のかなりの部分をこなしている。数年前からは、仕事部屋に置かれた物理画面・キーボードに向かうことはなくなり、専ら居間のラップトップからターミナルサービスを通して使っている。国外旅行中もVPNで操作するなど、未だに現役のワークホースだった。

正確には、ネットワークが使えなくなった。このデスクトップにはネットーワークカード(NIC)が2台装着してあり、両方共動作しないので、ハードウェアの不具合ではなくWindows XPのネットワークスタックのどこかが吹っ飛んだらしい。HDDを含めて、購入後9年間ほぼ24/7で稼働してきたハードウェアはまったく故障なしに動作している。大したもんだ。

さて、このPCがないと日常生活が困る。特にQuickenを使った金銭の出し入れの記帳ができないのは致命的だから早急に何とかしなければならない。

しかしながら、もうこのPCはハードウェアがいつ壊れてもおかしくないし、Windows XPも来年でサポート終了ということなので、2年ほど前から考えてきたハードウェアとソフトウェアの双方のアップグレードを実行することにした。

旧マシン
  • Pentium 4 (2 threads) 3.0GHz
  • 4GB DDR
  • SATA 1.5Gb/s 180GB
  • Windows XP Pro 32bit
新マシン
  • Core i5 (4 cores) 3.2GHz
  • 8GB DDR3
  • SATA 6Gb/s 1TBx2 (RAID1)
  • Windows 7 Pro 64bit

OSは「仕事に使えるOS」であるWindows 7は当然としても、この際だから64bit版を選んだ。32bitから64bitへ移行するので、今まで使ってきたWindows XP 32bit版で動いてきたアプリケーションの一部が動作しないことが予想され、そして予想通りいくつかのアプリケーションはインストールできなかった。また、ネットワーク経由でいろいろなことをするので、必然的にProバージョンを選んだ(実は4年ほど前に構築した配偶者のPCは深く考えることなしにVista Home Premiumを選び、結果的に大失敗だったという苦い経験がある)。

「予想していた」ということは「対策を立てていた」ということで、VMWare Playerをつかって64bitのWindows 7上で32bitのWindows XPを走らせてみると見事成功!ここに32bitでしか動かないアプリケーションをインストールすれば、少々使いにくさはあるが新マシンで旧来のアプリケーションが使えるようになるはず。

VMWare Playerには、CD/DVDのようなリムーバブルの物理ドライブやISOメディアイメージファイルを仮想ゲストマシンのリムーバブルドライブ(E:やF:のドライブ)としてマップする機能があるので、これを使えばCDなどから仮想ゲストマシンにアプリケーションがインストールできる。

と思ったが、できない。一時ファイルを作れないというエラーが発生してインストーラが終了してしまう。


 この「C:\Documents and Settings\hiro\LocalSettings\Temp」というフォルダは隠しフォルダながら存在しており、手でファイルを作成できる。隠し属性を「見える」に変更しても変わらない。私はWindowsのACLとかにはまったく疎いのでどうしたらよいか分からない。

しばらく悩んだが、 解決方法は意外と単純だった。

インストール用のCDの内容をそっくり(ISOイメージではなくファイルレベルで)ローカルディスクにコピーして、そこでインストーラを走らせると難なくインストールできた。

複数のPCに同じアプリケーションをインストールするためにネットワークドライブにISOイメージをファイルに展開してもよいが、その時はネットワークドライブのファイルシステムがケースインセンシティブ(大文字小文字を区別しない)であることを確認すべき。アプリケーションのインストーラはケースの違うファイル名のファイルをコピーしようとすることはよくある(実ファイルは「abc.xyz」なのに「ABC.XYZ」をコピーしようとする)。ISOファイルシステムは規格上ケースインセンシティブなので問題はないが、これを別ファイルシステムに展開するとケースの判断はそのファイルシステムに依存するから。

また一つ障害をクリア!

Windows 7のスタートメニューをカスタマイズする

Windows XP、Vistaまではスタートメニューを右クリックするとWindows Explorerが現在のユーザのローカルスタートメニューのフォルダを開き、ここにサブフォルダやショートカットを好きなように作ってスタートメニューをカスタマイズできたが、Windows 7からはこれができなくなった。何でも「一部のユーザ」に多大な混乱を招いたからと言う理由らしいが、こういう「一部の」馬鹿ユーザのヘマのとばっちりを受けるのは、すべては自己責任と自覚している賢明ユーザだ。

Windows 7ではユーザに直接スタートメニューを編集させない代わりに「ピン」と言う機能を提供し、プログラムファイルのアイコンを右クリックして「このブログラムをスタートメニューに表示したい」と言うことができることになっているが、必ずしもすべてのファイルが「ピン」できるのではなさそうだし、メニューの階層のどこに表示されるようになるのかなど、何が起こるのかがさっぱり予測がつかない。

この「何が起こるのか予測できない」はWindowsに限ったことではなく、近頃のデスクトップのOSの馬鹿げた傾向のように思える。どうもOSの作者たちはユーザが何をしたいのかはユーザ自身よりも自分たちの方がよく知っており、それを先回りして実現することが「使い易さ」だという幻想を抱いているらしい。だから「コンテクスト何とか」という、ユーザにとっては不安定で何が起こるのか予測できないUIができ上がるのだ。

考え違いも甚だしい。PCを仕事で使っているユーザは自分が何をしたいのか100%知っており、100%自分でコントロールしたいのだ。

話を戻して、「ピン」は本当に使いにくい。Vistaまではどんなファイルでもタスクバーにドラッグすれば「クイックローンチャ」が作成でき、しかもそこから起動されるタスクのタブとは別だったから、例えばブラウザを別ウィンドウで立ち上げなんてことがすぐできたが、「7」のタスクバーのピン機能はアプリケーションに限られ、しかもタスクタブと重畳されるのでとても使いにくい。Windows NT → Windows XPまでは世代ごとに使い易くなったのに、Vista以降はどんどん使いにくくなってきている。いったい何を考えているのか?

前置きが長くなった。

Windows 7の「All Programs」は、「C:\ProgramData\Microsoft\Windows\Start Menu\Programs」 というフォルダそのものである。ここに手作業でフォルダやショートカットを作ったら、ちゃんとスタートメニューの「All Programs」に反映された。パスからして、全ユーザに共通のスタートメニューを扱うようだ。


ついでに、ユーザ固有のクィックメニュー(「start」をクリックすると最初に現れるプログラムリスト)の「ピン留め」されたプログラムは、「C:\Users\xxx\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\StartMenu」フォルダの中のショートカット。「Roaming」だの「Internet Explorer」だの意味不明のパスエレメントがあるが、気にしない。

当然のことながら、フォルダの中身をいじってメニューをカスタマイズするときはくれぐれも慎重に。私のやらかした失敗は、「Administrative Tools」というフォルダ(中身は実行プログラムファイルへのショートカット) を捨てたら、スタートメニューの右側の「Administrative Tools」の中身も消えてしまったというアセリもの。実はスタートメニュー右側の「Administrative Tools」は実質的には「C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools」フォルダへのショートカットに過ぎないようだ。捨てたフォルダとその中身は幸いゴミ箱の中にまだそっくりそのまま残っていたので、復活して事なきを得た。

2013年8月4日

マザーボードの交換

ファイアウォールに使っているサーバが死にかけた。

突然フリーズするようになり、電源再投入すると再起動してしばらく順調に稼働するものの、数時間後にはまたフリーズする。/var/log/messageを見ても何ら情報はなく、ただただフリーズしているので、4年前に中古で買ったマザーボードの寿命と考え、新品と交換することにした。それまではwebpowerというネットワーク電源スイッチでpingで監視して自動電源再投入でしのぐ。

新しいマザーボードは近所のFry'sで一番安かったASUS製。
  • CPU: Celeron Dual Core 2.7GHz
  • Memory: DDR3 2GB x2 (=4GB)
  • Slots: PCIe x1, PCIX x1, 32bit PCI x2
  • PS/2: Mouse, Keyboard (separate)
  • VGA
  • 6Gb SATA x1, 3Gb SATA x5
古いマザーボードはPentium 4 (2.8GHz シングルスレッド)+ 2GBだったから、かなりのスペック向上である。メモリは本当は2GBで十分なのだが、今どき1GBのDDR3は売っていないので2GBのチップを使い、スピードを多少稼ぐためデュアルチャンネルメモリを選択で x2になった。32bit PCIは、手持ちのCaller ID用のモデムカードと二番目のNIC(ファイアウォールなのでLANとWANの両方が必要なのだ!)に必要。HDMIだとかSPI/Fだとかのマルチメディアポートもついているが、サーバには全く関係ない。PS/2のマウスとキーボードが付いているのは、10年前から使っているPS/2用KVMスイッチが使えるので助かる。HDDは当然稼働中のものを使う。

マザーボードの交換は神経を使う。稼働中のインスタレーションとの互換性が心配なのは以下の点。
  • SATAコントローラ
  • ビデオチップ
  • NIC(イーサネット)

いきなり稼働中のマザーボードを交換するようなせっかちはせずに、まずは裸の状態でテストして問題点を解決してから交換、と言う手順で行う。テストには別のマザーボード用に作ってあった(稼働することが証明されている)RAID1のインスタレーション(交換先と同じFedora 13)を使う。

ブートしない!

最初の躓きは全くブートしないこと。BIOSにも到達せず、何も動かない。「さてはCPUの装着時にピンを曲げたか?」と何度も点検するが異常なし。最終的には(当然のことながら)自分のチョンボと判明。電源スイッチ代わりにATX電源コネクタの緑と黒をショートしていたのが誤りで、フロントパネル用コネクタのピンを使うことで解決。

 電源コネクタの方法は他のマザーボードでは上手く行っていたのに、このマザーボードでは電源は入りCPUファンなども動き始めるのにそれ以上進まなかった。違いと言えば、他のマザーボードでは緑と黒のショートはモメンタリで電源投入後はショートを外しても投入されたままだったのにこのマザーボードではショートを外すと電源が切れてしまったこと。まぁインチキはいけませんね。

ルートデバイスが見つからない!

BIOSが走り、HDDを繋ぐとLinuxがブートし始める!SATAの問題はなさそう(SATAでトラブったことはないが…)。

ところがブートの段階で「ルートデバイスが見つからない!」というエラーで固まってしまう。ブートの進行状況のバーが伸び行く時にAlt+DでブートRCファイルの進行状況メッセージが表示されるはずなのだがそれができない。「さてはビデオの互換性の問題か?」とオンボードのVGAの代わりにPCIにVGAカードを挿してみるが同じこと。

仕方がないので、もう一度BIOSの設定をくまなくチェックする。試しにSATAの設定をデフォルトのIDEからAHDIに変えたらあっさり動いた。二つ目クリア!

NICが動かない

デスクトップが立ち上がり、ターミナルウィンドウを開いてifconfigを実行してみるが、ループデバイス(127.0.0.1)しか動いていない。SATAとビデオの問題とは違い、こちらは予想していたとおり。

まず、lspciを実行してNICデバイスがカーネルに認識されていることを確認。これはOK。

次に、/etc/udev/rules.d/70-persistent-net-rulesの日付をチェックすると更新されており、このファイルを内容を見ると、eth2eth3という新しいデバイスが追加されている(このマザーボードには、オンボードとPCI32スロットの二つのNICがついている)。これらが新しいマザーボードで使えるNICなので、これらのエントリを基に新しいマザーボード用のネットワークスクリプトファイルを作る。

RedHat/Fedora/CentOS系のディストロでは、ネットワークデバイスは/etc/sysconfig/network-scripts/ifcfg-***(「***」はネットワークデバイス名、「eth0」「eth1」など)というファイルに記述されているので、先ほどの/etc/udev/rules.d/70-persistent-net-rulesの情報とlspciの出力(NICのチップなどのコメントのみ)でifcfg-eth2ifcfg-eth3のファイルを作成し、「ifup eth2」を実行してみるとネットワークがつながった。

もう一度整理すると
  • /etc/udev/rules.d/70-persistent-net-rules
  • /etc/sysconfig/network-scripts/ifcfg-***
の二つのファイルの内容が一致するように
  • デバイス名(eth0eth1、…)
  • MACアドレス
を新しいハードウェアに合わせて修正してやる。

eth0eth1を修正したら、ブート時からこれらのインタフェースが使えるようになった。

h@spice:~$ cat /etc/udev/rules.d/70-persistent-net-rules
(一部省略)
# PCI device 0x1011:0x0019 (tulip)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:c0:f0:4c:e6:78", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"
 
# PCI device 0x10ec:0x8168 (r8169)
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="60:a4:4c:b5:26:48", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"


h@spice:~$ cat /etc/sysconfig/network-scripts/ifcfg-eth0
# Realtek Semiconductor Co., Ltd. RTL8111/8168B PCI Express Gigabit Ethernet controller (rev 09)
DEVICE=eth0
BOOTPROTO=none
HWADDR=60:a4:4c:b5:26:48
TYPE=Ethernet

(一部省略)

h@spice:~$  cat /etc/sysconfig/network-scripts/ifcfg-eth1
# Digital Equipment Corporation DECchip 21142/43 (rev 41)
DEVICE=eth1
HWADDR=00:c0:f0:4c:e6:78

TYPE=Ethernet
(一部省略)

h@spice:~$


明示的に「NAME=」が示されていないときは、eth0から順に名づけられるようだ。

さぁ、これで主要な問題は解決済だ。起動して24時間を過ぎて、今のところ新たな問題は見つかっていない。上手く行ったようだ。

2013年7月31日

AndroidでOpenVPNとVNCクライアントを走らせる

Barnes & NobleのNOOKというAndroidタブレットを買った。もともと同社のe-Readerとして開発・発売されたが、B&N社が電子本事業のビジネスモデルの見直しにあたって、Androidの一切の制約を外してGoogle Playの使えるフルのオープンな比較的安値($149)のAndoidタブレットになったので買ってみた。ちょうど配偶者が仕事先にタブレットを持って行きたがっていたので好機だという事情もあった。

配偶者の期待する主なタブレットの用途は現場での簡単な報告書作りだが、複雑なことは自宅でPCでじっくり行うとしても、表計算とかプレゼンテーションも将来的にはできるにこしたことはない。Android Market Placeを探すと数は少ないもののフルフィーチャーのオフィスツールスイートが売られているが、それらに飛びつく前に先ずはタブレットからVPNで自宅のLANに繋いでVCNでPCを操作できるようにしてみた。こうすれば外から自宅のPC上で編集や転送などのファイルの操作ができ、将来的にもインフラとして役立つと考えたから。ファイル転送に関してはDropBoxなどの商用クラウドを使う手もあるが、何時の間にかUIが変わっていたりとか、プライベートなファイルの運命を100%他人に委ねることは個人的にあまり好きではないので、商用クラウドも使ってはいるが限定的。こちらも半プロだからバックアップとかセキュリティとかにはかなりの労力を使っていて、今のところうまく行っている。

VPN経由のVCNによるLAN上のPCアクセスには次の4段階のネットワーク操作が必要。
  1. タブレットを現場のWiFiに接続
  2. ダイナミックDNSでタブレットの現在のグローバルIPアドレスからの接続を自宅のファイアウォールに許可させる
  3. ファイアウォールとの間でVPNを接続
  4. ターゲットのPCにVCNでアクセス
1)は言うまでもない。タブレットのWiFi機能を使う。

2)は我が家独特のセキュリティ強化のための動的なIPパケットフィルタリング。ここでは詳しくは触れない。

3)は商用サービスではなく、我が家でいろいろな外部ホストからのアクセスで実績・経験のあるOpenVPNを使って、ファイアウォールをVPNサーバに仕立てた。

4)のVNCで自宅のPCのデスクトップが操作できる。

OpenVPN

Android用OpenVPNのappは数種類出回っているが、ここではrootアクセスが不要なOpenVPN Connectを選ぶ。OpenVPN ConnectはP-t-P接続のtunインタフェースのクライアント専用で、*.ovpnというファイルから設定をインポートする。*.ovpnファイルは、OpenVPNの*.conf設定ファイルとほぼ同じだが、PEM形式の鍵や証明のデータをXML風のタグで内蔵している。

OpenVPNの鍵や証明データの生成方法は別項を参照。

クライアント側の*.confファイルタをブレット用にカスタマイズし、nook-vpn.confと名付ける。カスタマイズは基本的に以下の数ヶ所だけ。
  • サーバのアドレス(ドメイン名可)とポート番号
  • 鍵や証明ファイルのパス(相対パス可)

もしTLSを使う時は、
  • サーバ側:「tls-auth ta.key 0
  • クライアント側:「tls-client」「tls-auth ta.key 1
の行が存在しなければならない。実はこれがよく分からない。iOS(iPhone)のクライアントを使ったときはこれがあるとダメだった。もし接続がTLSエラーで拒否されるようなら、多少セキュリティが落ちるがサーバとクライアントの両方で「tls-auth」をコメントアウトすればよい。

もしTLSを使うなら、「方向」を合わせなければならない。通常はサーバ側は「0」すなわち入力側、クライアント側は「1」すなわち出力側とする。このとき、後で述べるようにclient.confからクライアントにインストールするclient.ovpnを生成とき、TLS(ta.key)を組み込むのと同時に
key-direction 1
と言う行を追加してクライアント側の方向を示さないとハンドシェークができない。

tunインタフェースしかサポートしていないので、複数のAndroidクライアントをサポートするにはクライアントの数だけ別のポート番号でサーバを動かさねばならず、少々面倒。さらに、ドキュメントでは不明な以下の問題がある。
  • サーバの*.confファイルに設定するVPNエンドポイントアドレスのネットマスクは最低でも255.255.255.248(/25)
  • サーバ側のVPNエンドポイントアドレスはccd/*の指定に関わらず常に一番小さいアドレス、すなわち上記で指定したネットアドレス+1
例えば、nook-server.conf
server 10.0.0.32 255.255.255.248
を指定すると
  • サーバは必ず10.0.0.33
  • クライアントは10.0.0.34~10.0.0.39のいずれか
になる。クライアント側も固定アドレスの方がうれしいので、ccd/nook-vpn
# Force client and server addresses
ifconfig-push 10.0.0.34 10.0.0.33
のように指定してやる。こちらのサイトではサーバとクライアントのアドレスが上記とは逆になるような設定を紹介しているが、我が家では動作しなかった。ただしこのサイトにリストされているtunのエンドポイントアドレスの組み合わせは守らねばならない。

 *.ovpnファイルは*.confファイルに鍵や証明データを取り込んで生成する。一々手でやるのは面倒なのでスクリプトを書いたので使ってください。
[root@gateway ~]# ls -F
conf2ovpn.sh*
[root@gateway ~]# cd /etc/openvpn/nook-server-keys
[root@gateway ~]# ls -F
01.pem  ca.key        nook-vpn.conf  nook-vpn.key   server.key
02.pem  conf2ovpn.sh* nook-vpn.crt  
server.crt     ta.key
ca.crt  dh1024.pem    nook-vpn.csr   server.csr

[root@gateway/etc/openvpn/nook-server-keys]# ./conf2ovpn.sh nook-vpn.conf >nook-vpn.ovpn

生成した*.ovpnファイルは、(マイクロ)SDカードにコピーし、タブレットに挿入する。タブレット上でOpenVPN Connectを起動し、メニュー(右上の八卦見のような「三」のようなアイコン)を開き、「SDカードからインポート」を選ぶ。デフォルトでは/sdcardディレクトリに飛ぶが、NOOKの場合/sdcardは内部SDカードに使用済みで挿入した外部SDカードは/mnt/ext_sdcardにマウントされる(Chromoでも同じことが起きたので、NOOK限ったことでないようだ)ので、ディレクトリを移動してから*.ovpnファイルを選択する。

と言うより、*.ovpnファイルをメイルの添付ファイルとしてタブレット自身に送り、添付ファイルを保存すると「SDカードからインポート」ですぐ見えるのでそこでインポートすればよい。またはタブレットをUSBでホストに接続して、一時的にUSBドライブとして見せて、そこで見えるタブレット側のディレクトリ(例えば「Download」)にコピーしてもよい。
 
後はサーバ側でOpenVPNを起動し、タブレット側のUIで簡単な設定をするとVPNが開通する。

ここで露見したのは、クライアント(Android)側がすべてのネットワークトラフィックをVPN経由にしてしまうらしいこと。つまり、まるっきりLANの中に入ったようになる。このままでは外のインターネットに接続できないので、サーバのiptablesのNATでVPNからの接続も外のインターネットに行けるようにする。すべてのトラフィックがVPNサーバ経由になるのでパフォーマンスが低下するが、クライアントのオプションを探しても他に解決手段が見つからなかった。

だから、OpenVPNに接続するのは必要な時だけにして、使い終わったら切断するように心がけなければならない。したがって、「Boot時に自動的に接続」のオプションは選んではいけない。

NOOK以外のタブレットによっては(例えばChromo)、tun.koカーネルモジュールがないと騒ぐが、慌てずPlayStoreに行って「TUN.KO Installer」をダウンロードして実行するとインストールされるはず、と思ったが、カーネルモジュールをインストールするためにはこのデバイスを「root」つまりスーパユーザの特権を得なければならず、rootのやり方はデバイスによってさまざまらしいので面倒くさくなってやめてしまった。

VNC

VNCもいくつかのappが出回ってるが、bVNC Freeを使って好結果を得た。こちらはOpenVPNと違ってインストールにあたっての特別なトリックは必要ないが、以下の二点で多少の時間を費やした。
 

DNS

OpenVPNの設定が悪いのか、タブレットからVPN(LAN内)のDNSが使えないので、VCNでアクセスするPCのIPアドレスを直接入力する必要があった。我が家のLAN上のホストは固定アドレスのDHCPにしているので大きな問題ではない。
 

日本語入力

タブレット側で日本語キーボードを使って入力したテキストは、VNCサーバ(PC)が受け付けない。VNC経由でリモートホストの日本語入力は、タブレット側ではデフォルトの英語キーボードのままにして、リモートのPC側の日本語入力機能を使い、タブレット上のローカルの日本語入力は日本語キーボードと使い分けが必要。タブレットの仮想キーボードでしか試していないが、近々入手する予定のBluetoothの外部キーボードでも同じと期待される。

VPNとVNCで最低限のことができるようになった。これから、AndroidのオフィススイートだとかDropBoxだとかのツールを少しづつ加えていくつもりだ。

2013年7月5日

DNSをオーバライドする(Windows)

我が家のインターネットファイアウォールはOpenVPNのサーバとして機能する。普段は仕事先のUbuntuのワークステーションから定常的にtunで接続しており、仕事先から家庭内のLANをアクセスしている。更に、WindowsのラップトップにもOpenVPNのクライアントをインストールしてあり、こちらはWindowsの仕様上tapインタフェースだが、旅行中に滞在先のホテルから家庭内のLANをアクセスするのに重宝している。例えば、旅行中に撮影した写真をラップトップにコピーすると、Genieというバックアップデーモンが自動的に我が家のバックアップサーバにバックアップを作成してくれるし、旅行中に使った金の記録もLAN上の家計簿用マシンに直接記録できる。

今フィリピンを旅行中で、最初のホテルではVPNで快適に家庭内LANをアクセスできていたのだが、二軒目のホテルでは最初の1時間ぐらいはよかったものの、突然動作がおかしくなってしまった。さてはWindowsお得意の要リブート状態かとリブートしてみたが復活しない。調べてみると、DNSに問題が発生していた。このホテルのネットワークはDOCOMO interTouchとやらなのだが、こいつのDNSサーバが特異な動作をしていることが分かった。

そもそも、VPNとは既存のTCP・UDPレイヤの上にIPをトンネリングさせる技術だが、通常DNSはベースつまりUDPを提供しているレイヤのDNSに優先権があり、ベースのDNSが解決できないとVPN上のDNSサーバに解決を委ねるようになっているらしい。だからウチのVPN-LANの場合、外からは見えないLAN内のドメインはベースすなわちホテルのLANのDNSサーバには解決できず、VPN上の我が家の内部DNSサーバにお鉢が廻ってLAN上のプライベートドメインが解決できるはずなのだ。

ところが、このDOCOMO interTouchのDNSサーバは、自分が解決できないドメインをすべて自分自身に解決してしまうようになっているらしい。つまり、ありとあらゆる、出鱈目なドメインを含むグローバルインターネット上に見えないドメインはニセのホストに解決されてしまう。理由はよくわからないが、多分マルウェアによる不正なリダイレクトを防ぐためではないかと想像する。この結果、VPN上の我が家のDNSサーバの出番はなくなり、結果的にVPNで接続された我が家のLAN上のドメインは実質解決できなくなってしまう。最初の1時間ほどが上手く行っていたのは、多分前日に宿泊したホテルで正常に解決できていたものがキャッシュに残っていたのだと思う。

このDOCOMO interTouchのDNSサーバの余計な振舞いを阻止するには、Windowsクライアント上に固定のhostsファイルを置くしかない。hostsファイルの在り処はUnix/Linuxなら/etc/hostsだが、Windowsでは%SystemRoot%\system32\drivers\etc\hostsで、通常はC:\Windows\system32\drivers\etc\hostsとなる。Windowsのhostsファイルの文法はUnix/Llinuxのそれとは異なり、「IPアドレス ホスト名」である。

取り敢えず必要なLAN上のホストをhostsファイルに書き込んでみたところ上手く行った。

今後LAN上のDNSに変更があるたびに一々手でWindowsクライアントのhostsファイルをアップデートするわけにもいかないので、cronで定期的に自動アップデートを実行、すなわち我が家のプライベートDNSの解決データをキャッシュするスクリプトを書こうと思ったが、このWindowsラップトップにインストールしてあるCygwinのsshdがdllファイルの一つが足りなくて動作しないことが分かった。Cygwinは個別のパッケージを管理するツールがないようで、setup.exeですべてをインストールし直し(あるいはすべてをアップデート)することになり、ホテルの低速インターネットで明日の朝までに完了するかどうかは分からない。

2013年6月21日

超簡単! GITでローカルファイル管理(RCSから移行)

ホームプロジェクトのソースコードの管理には永らくRCSを使ってきた。もう20年以上になる。とっくに時代遅れのツールだということは分かっていたが、オンリーワンのデベロッパーでコンカレンシーは要求されないので…と言い訳をしつつ、ソースコードだけでなくサーバの各種設定ファイルなどもすべてRCS上にある。

Androidのカーネルの仕事をやっていたときは当然GITで、その使いよさには同意していたものの、「アップストリーム」「多人数」というイメージが先行して何となくホームプロジェクトとは別物としてきた。

ここに来て、本業のLinuxカーネルの開発の仕事がパッチ指向になってきて、パッチファイルを生成しなければならなくなり、ローカルにRCSで管理して自作ツールでRCSからパッチファイルを生成するようにしてきたが、どうも使い辛さだけが目立つようになったので思い切ってローカルのファイル管理にもGITを使うことにした。

新規プロジェクト

新規プロジェクトでGITリポジトリの作成は至って簡単。
h@spice:~$ mkdir projects/my_project 
h@spice:~$ cd projects/my_project
h@spice:~/projects/my_project$ git init
これだけ。.gitという隠しディレクトリが作られる。

もし既に管理対象のファイルがあればaddコマンドでステージングしてcommitコマンドでコミットする。
h@spice:~/projects/my_project$ git add .
(addingのメッセージ)
(「git status」でファイルの状況を確認し、不要なファイルがあれば「git rm -r --cached .svn」のようにしてステージングから削除)
h@spice:~/projects/my_project$ git commit

旧来のプロジェクト

すでに存在するプロジェクトのファイルをヒストリを保持したままRCSからGITに変換するのはちと面倒。いくつかの方法があるようだが、こちらのサイトでCVSからGITへの移行を紹介しているのでパクらせていただき、RCSから一旦その発展形であるCVSに変換してからGITに変換する。

ここでは「tv」というディレクトリの下のプロジェクトのRCSファイルを同じレベルの「tv.cvs」を経由して「tv.git」に変換する。

まず、tv.cvstv.gitの準備。
h@spice:~/projects$ mkdir tv.cvs tv.git 
h@spice:~/projects$ cvs -d `pwd`/tv.cvs init
h@spice:~/projects$ (cd tv.git; git init)

次に、tv.cvstvと同じサブディレクトリ構造を作り、tvのRCSファイル(RCS/*,v)をtv.cvsに「本物ファイル」としてコピーする。「RCS」のディレクトリレベルを削除するところに注意。
h@spice:~/projects$ (cd tv; find -type d -exec mkdir -p ../tv.cvs/{} \;) 
h@spice:~/projects$ for i in `(cd tv; find -name '*,v')`; do cp tv/$i tv.cvs/`echo $i | sed 's|/RCS/|/|'`; done

cvs2gitに各種オプションを与えるファイルをローカルにコピーし、必要箇所を変更する。キモはCVSの在り処を示す「r'test-data/main-cvsrepos'」の部分。
h@spice:~/projects$ cd tv.git
h@spice:~/projects/tv.git$ cp /usr/share/doc/cvs2git-2.3.0/cvs2git-example.options cvs2git.optionsh@spice:~/projects/tv.git$ vi cvs2git.options
(編集して変更)
h@spice:~/projects/tv.git$ diff -c /usr/share/doc/cvs2git-2.3.0/cvs2git-example.options cvs2git.options 
*** /usr/share/doc/cvs2git-2.3.0/cvs2git-example.options    Mon Nov 15 21:48:54 2010
--- cvs2git.options    Thu Jun 20 14:41:31 2013
***************
*** 552,558 ****
      # The filesystem path to the part of the CVS repository (*not* a
      # CVS working copy) that should be converted.  This may be a
      # subdirectory (i.e., a module) within a larger CVS repository.
!     r'test-data/main-cvsrepos',
 
      # A list of symbol transformations that can be used to rename
      # symbols in this project.
--- 555,562 ----
      # The filesystem path to the part of the CVS repository (*not* a
      # CVS working copy) that should be converted.  This may be a
      # subdirectory (i.e., a module) within a larger CVS repository.
!     ##r'test-data/main-cvsrepos',
!     r'/home/h/projects/tv.cvs',
 
      # A list of symbol transformations that can be used to rename
      # symbols in this project.

cvs2gitを実行し、GITにインポートする。
h@spice:~/projects/tv.git$ cvs2git --options=cvs2git.options
h@spice:~/projects/tv.git$ cat cvs2svn-tmp/git-blob.dat cvs2svn-tmp/git-dump.dat | git fast-import

この段階で「git status」で見ると全てのファイルが「deleted」状態でステージされているので、アンステージしてやる。
h@spice:~/projects/tv.git$ git reset HEAD .
h@spice:~/projects/tv.git$ checkout -- .

最後に余計なCVSROOTディレクトリを削除すればでき上がり。一時ファイル置き場のcvs2svn-tmp/とかオプションファイルのcvs2git.optionsも残っているが、これらはGITの管理下にはないので単純にrmで削除すればよい。
h@spice:~/projects/tv.git$ git rm -r CVSROOT/
h@spice:~/projects/tv.git$ git commit

RCSは死んだか?

そうは思わない。CVSやGITはプロジェクトを単位とし対象となる全てのファイルがリポジトリにあることをて想定しているが、例えば/etcの下の各種設定ファイルはほとんど他と独立しており、そういう概念とは無縁だ。そう言うことで、今後はプロジェクトファイルは順次GITに変換していくつもりだが、設定ファイルにはRCSを使い続けると思う。

2013年6月15日

Raspberry PiのGPIOで7セグメントLEDとLCDを使う

Raspberry Piで、NTPでコントロールされたディジタル時計とNCIDサーバからのフィードを受けてCallerID(日本で言う「ナンバーディスプレイ」)の表示できるガジェットを作るというプロジェクトを考えている。ファミリールームのAVラックの上、テレビの前に置いて、普段は時計、電話がかかってきたら発信元を表示させる構想。色々考えたが、時計表示には大型(1.5")の7セグメントLED、CallerID部分は16x2のLCDを使う予定。

まだ構想段階だから何もできていないが、ソフトウェアのリソースを調べた結果を書いておく。

GPIO全般

こちらのサイトに詳細がある。

LEDのドライバ

こちらのサイトにPerlで書いた例がある。Device::BCM2835が必要。下記GPIOドライバと共存できるかどうかはやってみないと分からないが、できるだけコンパイラは使いたくないのでPerlでいけるなら行きたい。

LCDのドライバ

WiringPiというプロジェクトがGPIO全般を扱うライブラリを提供している。LCDはこちら

WiringPiからダウンロードできるソースコートはセルフ開発しか考慮しておらずコンパイラの速度やソースコード管理に問題があるので、クロス開発できるようにするパッチを作った。ダウンロードはこちら
  • 環境変数「RPiCC」にクロスGCCのフルパスをセットしておく
  • buildを編集して以下の変数をカスタマイズする
    • RPi : 最終的にライブラリをインストールするRaspberry Piのホスト名とログインできるユーザ名(root)
    • INSTALL_DIR : 開発ホスト上に作成するインストールイメージのルートディレクトリ(../wiringPi-install
  • おまけとしてbuildスクリプトをそのまま使うMakefileも作った。
LCDのドライバをもう一つ見つけた。こちらはPerlのモジュールDevice::BCM2835:LCD。 上記LEDドライバと同じ系統のようなので、多分こちらを使うことになるだろう。
 

明るさセンサ

ガジェットが家族に受け入れられるためには「プロっぽい」仕様が必要。CdSセンサで周囲の明るさを計測してLEDとLCDのバックライトの明るさを自動調節する機能も計画の一部。Raspberry PiはArduinoと違いアナログポートがなくすべてのGPIOはディジタル入出力しかできないが、こちらのサイトでGPIOのピンの1本を入力と出力に切り替えながら一種の積分型オームメータを実現する方法を紹介している。

Perlのモジュールはパッケージとしてインストールできればよいのだが、apt-cache searchで探してもBCM2835関係は見当たらない。仕方ないのでcpanでインストールするが、ネイティブでコンパイラが走るので数時間かかる。

2013年6月13日

ISOイメージからブート可能なUSBメモリを作る

OSのインストールは、CDやDVDのような12cmの光ディスクを使うのがここ十数年の習わしだった。ベンダが物理メディアを提供していた時代は、一度マスタを起こせば安価に製造・送付できるからだ。

しかし物理メディアを持ち運ぶ「スニーカーネット」が歴史的事実になった現在、このやり方は便利とは言えない。うちの6台のサーバやデスクトップに装着されている光ディスクドライブは何年も使っていない。PCの生涯で数回しかないイベントのために光ディスクドライブを装着しておくのは無駄だし、そもそもダウンロードしたISOイメージを光ディスクに焼くのは手間がかかる。おまけに、書込み可能な光ディスクは寿命があるようで、うちの5年以上前に買ったストックは読出しエラーが頻発するようになった。

SSDのファームウェアのアップデートの際もISOイメージを光ディスクに焼いてそこからブートするように指示されていたが、もう少し便利な方法はないかと思って探したら、UNetBootinというツールを見つけた。ISOイメージをUSBフラッシュメモリに書き込んでブート可能なドライブを作る。Windowsはもちろん、Linux版もあるという優れもの。

USBフラッシュメモリなら書き換えて何度でも使えるし、今時の製品なら容量の心配もない。ポピュラーなOSのインストールイメージを直接選択してダウンロード込みでワンクリックで済ますこともできるし、もちろん別途自分で用意(ダウンロード)したイメージも書き込める。中身はよくわからないが、GRUBのようなブートローダを使って間接的にISOイメージのブートファイルに飛ぶのではないかと想像している。

2013年6月4日

SSDは使えるか?

ファイアウォールとして使っているサーバのHDDが壊れてからしばらくになる。このHDDはRAID1の2台のHDDの片割れなので、全く動作しなくなっていても一応ファイアウォールは動作を続けているが、いつ残りのHDDも壊れるかわからない。もし完全に死ぬと我が家のすべてのインターネットの接続がなくなってしまうので非常に痛い。さっさと新しいHDDを追加してこちらで解説したようにRAIDを修復すればよいのだが、なんとなく躊躇させる事情があった。

その「事情」を延々と書いても仕方ないので本稿に関係するところのみにすると、要するに「最近SSDがだんだん使われてきてているようだが、信頼性と消費電力の点からメカニカルなHDDより有利そうなので、RAIDの修復ではなくSSDに新規インストールしたシステムに置き換えてみっか」ということで、例によってAmazon.comを探し、一番価格対性能比がよさそうなCrucialのV4シリーズの32GB(CT032V4SSD2)を買ってみた。

このファイアウォールという特殊な使われ方のサーバは、パフォーマンスとか記憶容量とかはあまり問題ではない。メイルやウェブサーバのインターネットへのゲートウェイとして動作しているとは言え、インターネットの接続は実測でせいぜい40Mbpsで、ファイアウォール自身はセキュリティのために当然外に見せるファイルとかは持っていないので、4~5年前に中古で買ったSuperMicroのPentium4 3GHz+2GBのマザーボード(ただしオンボードのGBEが2台ついている)に40GBのHDDの半分も使っていないというごく控えめのシステムであり、32GBのSDDで十分なのである。

さて、SSDは使えるか?

結論から言うと

大変失望した。

その顛末をちょっと書く。

システムをそれまでのHDDx2のRAIDから非RAIDのSSDに置き換える方針は決まっていたのだが、いきなりSDDにインストールした新しいシステムを動かすのはちと無謀な感じがした。たかがファイアウォールとは言え、セキュリティを確保するためのパケットフィルタからメイルのフィルタ、自分で作ったいろいろなツールが動いている。それらを検証しながら新しいシステムに再構築しなければならないので、別のマザーボードを使ってOSをインストールし、稼働中のシステムからいろいろなツールを少しづつ転送した。更に、やはりSSDという未知のデバイスなので、念には念を入れるため(と言う言い訳をして、実際にはさぼりながら)エージングと称して立ち上げたまま半年ほどほったらかしにしてあった。

そうこうしているうちに、別のバックアップ専用サーバのマザーボードが寿命を迎え、全く動作しなくなってしまったので、SSDのインストール・検証用に使っていたマザーボード(iCore2 8GB) を転用することになり、したがって「検証段階」を終えて一刻も早くSSDでファイアウォールシステムの更新をしなければならなくなった。

そこで最終テストを実行…したら、何と、SSDからブートしない

「え?そんなはずが…」と調べてみると、どうもgrubの管理する最新のカーネルイメージファイルでSSDの読出しエラーが起こっているようで、 一世代前のカーネルはちゃんと起動した。S.M.A.R.Tで見てみると、セクタの読み出しエラーが何万回か発生していて、エラー回数が増加している。前回のセクタ読出しエラーの修復のワザを使って不良セクタをリマップしようとしたが上手くいかない。どうもSSDの壊れ方のモードはHDDと違うようだ。

買ってから半年少々のSSDなので保証で交換してもらえるかと思い、Crucialのテックサポートに電話をしてみると、ファームウェアのアップデートを勧められた。まぁ、そう簡単に返品だの交換だのは受け付けないと思っていたから、まずはファームウェアのアップデートを試みることにした。当然のことながらSSD上のファイルは全てバックアップする。

 まず、検証用システムをWindowsをインストールした別のHDDでブートし、指示されたウェブページからアップデータのZIPファイルをダウンロードする。SSDはSATAポートに繋いであるがシステムの動作とは関係ないのでアップデータは自由に操作できるはず。と・こ・ろ・が、アップデータは「デバイスが見つかりません」を繰り返すのだ。アップデータの説明にUSBがどうのこうのと書かれていたので、SSDをUSB/SATAのアダプタ2種類に繋いでみたがこれもだめ。

仕方ない、またCrucialのテックサポートに電話したら、何とダウンロードしたアップデータのプログラムはブート可能なUSBドライブを作るためで、そのドライブでシステムをブートしてそこに含まれているアップデータ本体でSSDのファームウェアをアップデートするという。アップデータの説明をもう2回読んでみたが、ブート用のUSBメモリを用意しろなどとはどこにも書かれていない。まぁよく考えてみれば、一般のユーザはSSDをインストールしたままアップデートを実行しなければならないので、外部のブートドライブから起動しなければならないことは当然といえば当然で、こちらが気を聞かせ過ぎたという感じもある(しかしアップデート結果SSD上のデータは全て失われるからSSDを使えるようにするためにはOSから何からを再インストールしなければならない)。文法的に間違いだらけのアップデータのメッセージからしてきっとチュウゴクの下請けが作っているのだろうと想像し、ドキュメントの品質のことは諦めてUSBのブートデバイスを作って再挑戦してみる。

BIOSの認識できるUSBドライブの形式でFAT32ではなくFAT16にしなければならないとかで少々躓いたが、とにかくDOSがブートし、指定されたバッチファイルを走らせると、何やらよさそうなプログレスメッセージ。25%...50%...75%...100%...DONE! で喜んだのもつかの間

SELFTEST FAIL

CT032V4SSD2(32GB SSD)が
2.1MBの訳のわからないドライブに化けた
が…。何だこりゃ?
 
もう一度アップデータを走らせてみると、もうSSDを認識しない。別のHDDからLinuxを起動してS.M.A.R.TでSSDを見ると、何とこれが正体不明のモデル番号の2.1MB(GBではない、めが・ばいと)のHDDに化けているではないか!ファームウェアのアップデートに失敗し壊れたようだ。

はっきり言ってもうこのSSDを使うつもりはないが、一応Crucialのテックサポートに電話してみたところ割とすんなりと動作品に交換してもらえることになったので、交換でき次第中古品として売り飛ばす予定。

結論:SSDはまだ時期尚早の感がある。少なくとも事前にファームウェアのアップデートを施し、十分なエージングをして動作に納得がいってからでないと怖くて使えない。6年前のラップトップのHDDをSSDに置き換えるというプランもあるが、もうCrucialのSSDはご免だ

動作しないRAIDディスクの交換


前回は不良セクタが発見されたRAIDディスクドライブを修復したが、今回は全く動作しなくなってしまったHDDを新しいドライブに交換する。RAID1の場合を示すが、RAID5やRAID6のレベルでも基本的に同じこと。少し古いが、こちらのサイトこちらのサイトが参考になる。
 


まず、/proc/mdstatでRAIDの状態を確認。
h@spice:~$ cat /proc/mdstat
Personalities : [raid1] [raid6] [raid5] [raid4]
md1 : a
ctive raid1 sda2[0]      38398908 blocks super 1.1 [2/1] [U_]
      bitmap: 1/1 pages [4KB], 65536KB chunk

md0 : active raid1 sda3[0]
      204788 blocks super 1.0 [2/1] [U_]
    
unused devices: <none>
/dev/sdbが全く見えていない。すでにmdadmが警告のメイルを送ってきているはず。
 

新しいHDDを用意

ペアになる稼働中のHDDと同じモデルが安心できるが、別にこだわる必要はない。ペアになるHDDより少容量でなければよい。5年ほど前までは、例えば同じ「120GB」を公称するHDDでもメーカにより、あるいは同じメーカの同じモデル名でもリビジョンにより実際の容量が微妙に異なり、運悪く稼働中のHDDより少容量のHDDを手に入れてしまうと使えなかったが、今は業界の申し合わせにより、例えば公称「2TB」のHDDはどのメーカ、どのモデル、どのリビジョンでもすべて同一の容量のはずなので心配事が一つ減った。
 

新しいHDDのパーティション

普段使っているfdiskでいちいち手でやるよりも、sfdiskで稼働中のHDDのパーティションをそっくりコピーする方が失敗がない。/dev/sdaから/dev/sdbにパーティションをコピーする。
h@spice:~$ sudo sfdisk -d /dev/sda | sudo sfdisk /dev/sdb
新しいディスクと古いディスクのジオメトリの違いで「I don't like this partition.」と拒絶されたら、--forceオプションを/dev/sdb側につけて強制的にパーティション情報をコピーする。この時点でパーティションのタイプなどもそっくりコピーされる。

注意:2TB以上の領域を使う場合は、2^32以上の512Bセクタを使うのでMBRのパーティションでは対応できず、従ってsfdiskも使えず、GPTを使う必要がある。この辺はご自分で工夫してください。
 

RAIDに新しいパーティションを加える

パーティションが整ったら、新しいパーティションをRAIDに加えてやれば、RAIDが勝手にリシンクを始める。リシンクは同時に一つのRAIDしか実行されないので、小さなサイズのパーティションから先に加えると先にリシンクが完了するので気持ちがよい。
h@spice:~$ sudo mdadm /dev/md0 --add /dev/sdb3
h@spice:~$ sudo mdadm /dev/md1 --add /dev/sdb2

h@spice:~@ sudo mkswap -U xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /dev/sdb1
h@spice:~$ sudo swapon -a


ブートローダの処理

以上のように、RAIDの修復そのものはさして難しくないが、ブートローダはRAIDの範疇ではなく、新しいHDDにはブートローダが書き込まれておらず、もし以前から動いている方のHDDが全く立ち上がらなくなるとブートできなくなってしまうのでこの手当てが必要。
 
ここではgrubを使っている 。個々のgrubコマンドやgrubの文法は「info grub」を熟読して精通しておくことを勧める。要点は「info grub」の「3.2 Installing GRUB natively」を参照。注意すべきことは、grubでのナンバリングは全てゼロベース、すなわち、
  • (hd0)/dev/sda
  • (hd0,0)/dev/sda1
となること。
ここで行うgrubの作業は以下の二つ
  1. 最初のHDDからのブートが失敗したときの代替HDDを指定する
  2. 代替HDD(新しいHDD)にブートローダを書き込む
上記の/proc/mdstatの出力をもう一度見てほしい。/dev/sda3と/dev/sdb3からなる/dev/md0/bootにマウントされていて、ここにブート用カーネルイメージとRAMディスクがある。

ブートパーティションなどの扱いはディストリビューションにより微妙に異なる。ここではFedoraを使っている。
 
代替HDDの指定のために、/boot/grub/grub.confを編集する。「title ~」で始まるブロックがブート可能なオプション。
(これより前省略)
#boot=/dev/sda
default=0
timeout=0
splashimage=(hd0,2)/grub/splash.xpm.gz
hiddenmenu
title Fedora (2.6.34.9-69.fc13.i686.PAE)
        root (hd0,2)
        kernel /vmlinuz-2.6.34.9-69.fc13.i686.PAE ro root=/dev/mapper/vg_mill2-LogVol00 rd_MD_UUID=d1a49db9:b462d70c:4d734507:a1a49339 rd_LVM_LV=vg_mill2/LogVol00 rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYTABLE=us rhgb quiet
        initrd /initramfs-2.6.34.9-69.fc13.i686.PAE.img

title Fedora (2.6.34.8-68.fc13.i686.PAE)
        root (hd0,2)
        kernel /vmlinuz-2.6.34.8-68.fc13.i686.PAE ro root=/dev/mapper/vg_mill2-LogVol00 rd_MD_UUID=d1a49db9:b462d70c:4d734507:a1a49339 rd_LVM_LV=vg_mill2/LogVol00 rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYTABLE=us rhgb quiet
        initrd /initramfs-2.6.34.8-68.fc13.i686.PAE.img

(これより後省略)
黄色の部分(hd0,2)すなわち/dev/sda3(からなる/dev/md0)に最新のブートファイル(カーネルイメージとRAMディスク)があることを示している。

代替用ブートパーティションを指定する。 水色の部分が追加された行。
(これより前省略)
#boot=/dev/sda

default=0
fallback=1

timeout=0
splashimage=(hd0,2)/grub/splash.xpm.gz
hiddenmenu

title Fedora (2.6.34.9-69.fc13.i686.PAE)
        root (hd1,2)
        kernel /vmlinuz-2.6.34.9-69.fc13.i686.PAE ro root=/dev/mapper/vg_mill2-LogVol00 rd_MD_UUID=d1a49db9:b462d70c:4d734507:a1a49339 rd_LVM_LV=vg_mill2/LogVol00 rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYTABLE=us rhgb quiet
        initrd /initramfs-2.6.34.9-69.fc13.i686.PAE.img

title Fedora (2.6.34.9-69.fc13.i686.PAE)
        root (hd0,2)
        kernel /vmlinuz-2.6.34.9-69.fc13.i686.PAE ro root=/dev/mapper/vg_mill2-LogVol00 rd_MD_UUID=d1a49db9:b462d70c:4d734507:a1a49339 rd_LVM_LV=vg_mill2/LogVol00 rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYTABLE=us rhgb quiet
        initrd /initramfs-2.6.34.9-69.fc13.i686.PAE.img

title Fedora (2.6.34.8-68.fc13.i686.PAE)
        root (hd0,2)
        kernel /vmlinuz-2.6.34.8-68.fc13.i686.PAE ro root=/dev/mapper/vg_mill2-LogVol00 rd_MD_UUID=d1a49db9:b462d70c:4d734507:a1a49339 rd_LVM_LV=vg_mill2/LogVol00 rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 SYSFONT=latarcyrheb-sun16 KEYTABLE=us rhgb quiet
        initrd /initramfs-2.6.34.8-68.fc13.i686.PAE.img

(これより後省略)
fallback=1」で「default=0」すなわち/dev/sdaからのブートに失敗した時は/dev/sdbを試すことを意味し、「title ~」は黄色の部分をコピーしたもので、「root (hd1,2)」でその時のブートファイルは/dev/sdb3(からなる/dev/md0、ただし多分デグレードしたRAID)にあることを示す。もし3台以上のHDDのRAIDでブートできる場合は「fallback=1 2」のように代替ドライブを列挙し、「title ~」の部分をコピーと(hd*,3)の変更で増やしてやる。

grub.confの追加が終わったら、grubを起動して/dev/sdbにブートローダを書き込む。

注意すべきは、普段はBIOSのデフォルトでは最初に見つかったブート可能なHDDすなわち/dev/sda、grubの表現では(hd0)からブートするが、もしこのHDDが死んでしまうと 物理的に2番目のHDDが/dev/sdaまたは(hd0)にマップされること。

だから以下の作業では、まず「device (hd0) /dev/sdb」で物理的に2番目のHDDを一時的に(hd0)にマップし、その後のコマンド実行のときにこのHDDが実際に2番目の物理デバイスからブートするときはブート可能な最初のHDDであることをシミュレートする。
h@spice:~$ sudo grub
Probing devices to guess BIOS drives. This may take a long time.


    GNU GRUB  version 0.97  (640K lower / 3072K upper memory)

 [ Minimal BASH-like line editing is supported.  For the first word, TAB
   lists possible command completions.  Anywhere else TAB lists the possible
   completions of a device/filename.]

grub> device (hd0) /dev/sdb
grub> root (hd0,2)
 Filesystem type is ext2fs, partition type 0xfd
grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... no
 Checking if "/grub/stage1" exists... yes
 Checking if "/grub/stage2" exists... yes
 Checking if "/grub/e2fs_stage1_5" exists... yes
 Running "embed /grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
succeeded
 Running "install /grub/stage1 (hd0) (hd0)1+15 p (hd0,2)/grub/stage2 /grub/grub.conf"... succeeded
Done.
grub> quit
root (hd0,2)」で/dev/sda3(現在は/dev/sdbとして稼働中)にブートファイルがあることを示し、「setup (hd0)」で現在/dev/sdbとして稼働中のHDDにブートローダを書き込む。

全ての作業が終わったら、念のため一旦シャットダウンし、/dev/sda側のHDDのケーブルを外して/dev/sdb側(2番目の物理のHDD)だけで正常にシステムが起動することを確認して終わり。
 

応用

上記の方法を使って、RAIDのHDDをそっくり入れ替えることもできる。
 
うちのファイアウォールに使っているPCのマザーボードはPATAx2とSATAx2の両方が使えるのだが、導入時になぜかPATAしか使えないと勘違いしてPATAx2のRAID1を構築してしまった。その後PATAのHDDの一台が死に、代替品を入れようとしたがPATAのHDDはもう入手困難。いっそのこと新品のSATAのHDD2台にそっくり入れ替える試みをし、成功した。

以下の手順。
  1. PATAのHDD1台で動作している状態(/proc/mdstatが「U_」を表示する)。
  2. 新しいSATAのHDD2台を接続。
  3. SATAHDDのプライマリ側(/dev/sdb)を上記の手順でRAIDに加え、ブートローダもインストールする。
  4. PATAのHDDを外す。
  5. すでにRAIDに加えたSATAのプライマリ側HDDが/dev/sdaになり、片肺飛行のRAIDとして動作している。
  6. セカンダリ側のSATAHDDを上記の手順でRAIDに加え、ブートローダもインストールして完了。