Android
QR コードを機器間での秘密情報の輸送に利用する試み
QR コードのもうひとつの利点
QR コードはインターネットアドレスなど所定のテキストデータを簡単に機器に取り込むための手段として広く利用されています。印刷物に限らずさまざまな媒体で扱えるのも便利ですね。
先日、公開鍵暗号を使う Android アプリを試作していた折に、自分の端末 A で生成した秘密鍵を手持ちの別の端末 B へ安全に輸送する手段の検討が必要になりました。やり方はいろいろありそうですが、よりシンプルな方法をとあれこれ考えている内に、机上に無造作に置いたチラシの QR コードにふと目が留まりました。
QR コードからのデータ取り込みは通常光学的に行われます。このことは電子機器の視覚を利用したデータ通信と考えることができるでしょう。第三者による情報窃取の可能性がしばしば問題となるネットワーク通信とは異なり、「目」を使っての情報伝達には物理的に介入することが難しいため、デジタルデータを画像で表現する QR コードは秘匿性の求められる局面での通信手段としても有用と考えられます。
今回の話題はふたつの端末の間でのデータ授受なので、送り側の端末で QR コードの生成と画面表示を行い、受け側の端末でそれを読み取ればよさそうです。情報の受け渡しさえできればコードを残しておく必要はないので QR コードは常に動的に生成し使い捨てとすればよいでしょう。
留意すべき点
一件の QR コードに格納できるデータの量には上限があります。QR コードの開発元である株式会社デンソーウェーブ様のサイトの記事から概要を示す表を以下に引用します。
| コードの大きさ | 21セル×21セル〜177セル×177セル(4セル /辺毎に増加) | |
| 情報の種類及び
情報量(混在も可) QRコードの情報量とバージョンについて |
数字 英数字 8ビットバイト(バイナリ) 漢字 |
最大7,089文字 最大4,296文字 最大2,953文字 最大1,817文字 |
|
誤り訂正能力 (データ復元機能) 誤り訂正能力について |
レベルL レベルM レベルQ レベルH |
コードワードの約 7% が復元可能 コードワードの約 15% が復元可能 コードワードの約 25% が復元可能 コードワードの約 30% が復元可能 |
| コード連結機能 | 最大16分割 (細長いエリアなどへの印刷) | |
下の QR コードには半角英数記号 1675 文字を格納しています。これを一般的なサイズ・解像度の PC のモニタで表示した状態であればおそらく多くの携帯端末の QR コードリーダーで読み取ることが可能でしょう。しかし、スマートフォンの小さな画面にこれを表示した状態だと読み取りに失敗するリーダーが出てくるかもしれません。また、一般に情報の密度が高くなるほど読み取りにより多くの時間がかかるため、たとえ成功しても別のストレスが残る可能性もあります。
そういった事情を考え合わせると、今回のような目的で QR コードを利用する場合は、一件のコードに強引に多くのデータを押し込むのではなく、複数のコードに分けて利用することを前提に余裕を持ってデータを扱うほうが賢明でしょう。
実は上の表にも記述があるように、QR コードには一件のデータを最大 16 件のコードに分割して格納することの可能な「コード連結」という仕様があります。次の記事には分割 QR コードの実例が掲載されています。 「IT4206,QRコードの連結機能に対応していますか。」 - 株式会社エイポック様 公式サイトより -
残念ながらすべてのリーダー・ライブラリがこの機能に対応しているわけではなく、現在手元で使っているメジャーな ZXing のライブラリも未対応のようです。しかし、今回はリーダーだけではなくライターも自作することが前提なので、両者間で整合性のとれる内容で独自の分割プロトコルを用意すれば事足りると判断しました。また、二台の端末を操作しながら複数のコードを順番に処理していくのは想像するだけで非常に面倒なので、両者の連携にネットワーク通信を併用し、リーダーが正しくコードを読み取ったらライターが自動的に次のコードを表示することにしました。
ちなみに、先日の記事で操作性の良い QR コードリーダーを自作するために行った取り組みを紹介しましたが、そのきっかけは今回の一連の話題にありました。リーダーを自分で実装すれば上記のような細かい取り回しも柔軟に実現できるわけですね。QR コードの読み取りが主目的ではないアプリへ補助機能としてリーダー処理を組み込むことにはちょっと新鮮な印象があります。
試作と実験
そんなわけで次のような QR コードライターアプリと QR コードリーダーアプリを作ってみました。
デモ動画
動作の様子です。奥の端末で QR コードライター、手前の端末でリーダーを動かしています。
リソース
今回試作したソフトウェア一式を公開します。興味のある方はお試し下さい。
QR コードライター・リーダーのソースコード
MyQRCodeReaderEx - github
QR コードライターのビルドずみ apk
http://dsas.blog.klab.org/data/qr_secret/QR_writer.apk
QR コードリーダーのビルドずみ apk
http://dsas.blog.klab.org/data/qr_secret/MyQRCodeReaderEx.apk
(tanabe)
「ZXing QR コードスキャナー」の内部処理を追う
このライブラリの優秀さはスマートフォンでバーコード/QR コードを処理する際の実質標準の座にある状況からも裏打ちされていますが、ZXing スキャナの「使いやすさ」は本ライブラリの性能のみに依るものではなくアプリの実装に大きく支えられています。ZXing スキャナのソースコードは公開されているので、それに学べば本家と同等の使いやすさを備えた QR コードリーダーの自作が可能となるはずですね。操作性の良いリーダーを自作できるのであれば QR コード読み取り機能をアプリへ組み込む際にわざわざ ZXing スキャナをインテントで呼び出す必要はなく、独自の処理を柔軟に組み込むこともできるでしょう。また、上記ライブラリを知り尽くした ZXing 謹製スキャナのコードはライブラリの性能を最大限に引き出すための最良のお手本となりそうです。zxing Multi-format 1D/2D barcode image processing library with clients for Android, Java
ZXing スキャナのソースコードを正面から分析した情報はほとんど見当たらないようですが、手元での調査を通じて得られた情報と、そのエッセンスを小さくまとめた形で試作した自作リーダーのリソース一式を公開します。なお、この記事では ZXing 2.1 Release 中の zxing-2.1/android/ 配下のソースコードを対象としています。
注目すべき要素
なぜ ZXing スキャナは QR コードリーダーとして使いやすいのでしょう?その要素こそが実装を調べる上で注目すべきポイントとなるでしょう。特長をみっつピックアップしてみます。
- コードの認識が早い
ターゲットにレンズを向けてじっとしていればすぐに認識が完了する - 対象とするコードをファインダ枠内に留める以外の操作が不要
フォーカスを合わせたり画面をタップするといった操作をする必要がない - コード識別パターン検出位置を示すポイントが描画される
プレビュー表示にコード識別パターンを発見した位置を示すポイント (ResultPoint) がリアルタイムで描画されるため端末の位置・角度を加減し易い
ソースコードの追跡とまとめ
コード読みの途中で迷子にならないための道具としてホワイトボード代わりにエクセルシートを使いました。あくまでも作業用なので決して見た目の良いものではありませんが記録としてそのまま掲載します。
コードリーディング時のメモ
以下、ソースコードから得られた情報を整理してみます。
- プレビュー表示中に裏側で走っている太い処理の内容
※カメラには 2 秒ごとに autoFocus() が適用される- 現在プレビュー表示中のフレームイメージを取得
- 取得したイメージをバックグラウンドスレッドへ渡す
- バックグラウンドスレッドはイメージを ZXing ライブラリ処理に渡す
- ライブラリはコードの検出過程で QR コード画像の識別パターンである可能性のある箇所(ResultPoint)を見つけるとその座標を UI 側へ逐次通知する
- ResultPoint を受け取とった UI 側はそれを表示中のプレビュー画面に重ねて描画する
- ライブラリ側処理が今回のフレームイメージから QR コードを検出しなかった場合、1. からの処理が繰り返される
- 重要なクラスとその処理の概要
- com.google.zxing.client.android- CaptureActivity
QR コードスキャナーの Activity クラス - CaptureActivityHandler
CaptureActivity のハンドラ。DecodeThread インスタンスを生成し DecodeHandler からライブラリ処理による QR コード認識結果を受け取る。コードが認識されなかった場合はこのクラスが CameraManager.requestPreviewFrame() を叩くことで、次のフレームイメージ取得〜QR コード認識という全体のループを継続させる - DecodeThread
プレビュー中のフレームイメージから QR コードを検出するための手続きを連続的に繰り返すバックグラウンドスレッド - DecodeHandler
DecodeThread のハンドラ。ZXing ライブラリの MultiFormatReader クラスの QR コード検出メソッドを呼び、認識結果を CaptureActivityHandler へ伝える - ViewfinderView
View の継承クラス。プレビュー画面の描画、ファインダ矩形上に捕捉したイメージについての識別パターン検出点(ResultPoint)の描画を onDraw() メソッド内で実施 - ViewfinderResultPointCallback
ZXing ライブラリ内に宣言のある「com.google.zxing.ResultPointCallback」インターフェイスの実装クラス。DecodeThread 経由でライブラリ側処理へ登録され、ライブラリが ResultPoint 認識時にこのクラスの foundPossibleResultPoint() メソッドをコールバックする。同メソッドは ViewfinderView クラスへ ResultPoint を伝達する
- CameraManager
カメラのオープン・クローズや PreviewCallback へのフレームイメージ取得指示など - PreviewCallback
Camera.PreviewCallback の実装クラス。onPreviewFrame() 発生時に DecodeHandler へキャプチャイメージを渡す - AutoFocusManager
バックグラウンドで 2 秒ごとにカメラに autoFocus() を適用 - CameraConfigurationManager
カメラまわりの解像度や各種パラメータの取得・設定
- CaptureActivity
- 使いやすさを支えているもの
- 「コードの認識が早い」
→ プレビュー表示中、コードを検出するまで絶えずフレームイメージの取得とバックグラウンドスレッドでの分析を繰り返している - 「対象とするコードをファインダ枠内に留める以外の操作が不要」
→ カメラへのオートフォーカスの適用を効果的に利用している - 「コード識別パターン検出位置を示すポイントが描画される」
→ 検出位置を直ちに通知するためにライブラリの深部に用意されたコールバック機構と UI への描画処理を適切に組み合わせて使用している
- 「コードの認識が早い」
リーダーの試作
以上のように、全体像が見えてしまえば ZXing スキャナの操作性を支えているのは意外なほどシンプルなしくみであることがわかります。それを自作のコードに組み込むことは難しくなさそうですね。そこで ZXing スキャナの実装に倣いつつできるだけ短いコードでざっくりとリーダーを作ってみることにしました。認識結果はダイアログ表示のみとしています。動作確認は一部の環境でのみ行っており、不具合があれば適宜手を入れて下さい。
ソースコードの一覧と概要を以下に示します。
- MyActivity.java
本アプリの Activity クラス。implements SurfaceHolder.Callback, Handler.Callback, Camera.PreviewCallback - MyCameraConfigurationManager.java
ZXing スキャナの CameraConfigurationManager より。カメラパラメータの設定を決め打ちに - MyDecodeThread.java
ZXing スキャナの DecodeThreadより。対応コードフォーマットを決め打ちに - MyDecodeHandler.java
ZXing スキャナの DecodeHandlerより。コード認識成功時に MyActivity へ Bitmap は送らず検出したテキストの情報のみに - MyFinderView.java
ZXing スキャナの ViewfinderView および ViewfinderResultPointCallback より。オリジナルの ViewfinderView はプレビュー画面全体を覆っているが単純化のためここではファインダ矩形と一対一に変更。ResultPoint が 4 以上になるとファインダ内に「Warning!」と表示
ビルドずみの apk はここにあります
http://dsas.blog.klab.org/data/zxing_qr_scanner/MyQRCodeReader.apk
(tanabe)
Android の GCM をプライベートな目的に使う
自分の端末を遠隔操作
Android 界隈は依然にぎやかで次々に新しい製品が発売されています。そのため複数の端末を持っている人も少なくないでしょう。まだまだ使える端末を遊ばせておくのはもったいないので、これを外出中の自宅の監視カメラとして使うことにしました。 端末を室内の対象物に向けて固定しておき、出先や仕事場から GCM 経由で端末へメッセージを送出、それをトリガーにアプリが撮影したスナップを Dropbox 経由で確認します。
シンプルな実装の割に結構役に立っています。 アプリ本体とソースコードを以下で公開しています。
RemoteWand - github
処理の流れ
端末の登録
遠隔操作の対象とする端末上で「RemoteWand」を起動して「登録」ボタンを押下すると GCM サーバへ端末が登録されトリガー発信用のフォームを含む HTML ファイルの添付された自分あてのメールが生成されます。
トリガーの送出〜撮影
PC やスマホのウェブブラウザに上記のフォームをロードし登録時に指定したパスワードを添えてサブミットするとアプリケーションサーバから GCM サーバへ要求が送出され GCM サーバが当該端末へ所定のメッセージをプッシュします。端末にインストールずみの RemoteWand はこのメッセージをトリガーにカメラで静止画の撮影を行います。Dropbox の「カメラアップロード」機能は自動的に撮影画像のアップロードを行うためあらかじめ端末にインストールしておけばリモートで簡単に写真を確認できます。
(tanabe)
Android のプッシュ通知用コネクションに関するメモ(補記)
この記事の文中に、上記の持続接続が「プッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい」 と注釈を添えています。今回はその内容と関連する話題を控えます。DSAS 開発者の部屋 : 「Android のプッシュ通知用コネクションに関するメモ」
まとめ
端末と mtalk.google.com:5228 との間の接続はプッシュ通知以外に次の用途で使用される
- 他にもあるかも?
- 非公開の内部仕様につき今後変更される可能性も
Google Talk について
$ dig mtalk.google.com
; <<>> DiG 9.2.1 <<>> mtalk.google.com
:
;; ANSWER SECTION:
mtalk.google.com. 81547 IN CNAME mobile-gtalk.l.google.com.
mobile-gtalk.l.google.com. 291 IN A 74.125.31.188
:
Google Play ページからのプッシュインストールについて
mtalk.google.com:5228 に接続できない場合は?
端末上の com.google.process.gapps プロセスが 「mtalk.google.com:5228」 へコネクションを確立できない場合の所作を確認した
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:43695 173.194.35.15:80 TIME_WAIT -
tcp 0 0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
tcp 0 0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
tcp 0 1 ::ffff:10.10.0.10:34224 ::ffff:173.194.72.188:5228 SYN_SENT 256/com.google.proc
:
(時間の経過・・・)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:43695 173.194.35.15:80 TIME_WAIT -
tcp 0 0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
tcp 0 0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
:
(時間の経過・・・)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:43695 173.194.35.15:80 TIME_WAIT -
tcp 0 0 ::ffff:10.10.0.10:38835 ::ffff:74.125.235.130:443 ESTABLISHED 776/com.android.ven
tcp 0 0 ::ffff:10.10.0.10:46503 ::ffff:74.125.235.185:80 ESTABLISHED 881/berserker.andro
tcp 0 1 ::ffff:10.10.0.10:32648 ::ffff:173.194.72.188:5228 SYN_SENT 256/com.google.proc
:
Note: If your organization has a firewall that restricts the traffic
to or from the Internet, you'll need to configure it to allow
connectivity with GCM. The ports to open are: 5228, 5229, and 5230.
GCM typically only uses 5228, but it sometimes uses 5229 and 5230.
付録:端末上で拾ってみたパケットの様子
Google Talk
プッシュインストール
(tanabe)
Android のプッシュ通知用コネクションに関するメモ
Android のプッシュ通知機構(GCM, 旧 C2DM)は有用なしくみですが、オープンソースではないソフトウェア要素が関わっているためか内部仕様に近い情報をあまり見かけないのが残念です。手元での観察結果をもとにプッシュ通知で使用されるネットワークコネクションまわりの情報をいくつかまとめてみました。
まとめ
(通常は 5228 番ポートだが 5229, 5230 番ポートが使用される場合もある)
※[A] はプッシュ通知以外の用途にも利用されている様子でありそちらもおって調査したい
端末− Google サーバ間のコネクションについて
コネクションの存在確認
GCM に関する開発者向け情報へアクセスすると次の記述が見つかります。[GCM Architectural Overview | Android Developers] より
Note: If your organization has a firewall that restricts the traffic to or from the Internet, you'll need to configure it to allow connectivity with GCM. The ports to open are: 5228, 5229, and 5230. GCM typically only uses 5228, but it sometimes uses 5229 and 5230.GCM では端末と所定の外部サーバとの接続に通常 5228 番ポートを使うということですね。 さっそく Android 端末実機上で netstat コマンドを実行してみます。
※ここで使用しているのは root 化ずみの実験用端末であり、 各コマンドは Android オリジナルのものではなくBusyBox 版を利用しています。
pid=3547 のプロセスが 「173.194.72.188:5228」との間に TCP コネクションを張っていることがわかります。
# netstat -p
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 10.10.0.10:60272 74.125.235.111:80 ESTABLISHED 6337/berserker.andr
tcp 1 0 ::ffff:10.10.0.10:33375 ::ffff:74.125.235.145:443 CLOSE_WAIT 3547/com.google.pro
tcp 0 0 ::ffff:10.10.0.10:22 ::ffff:10.10.0.6:3667 ESTABLISHED 6385/dropbear
tcp 1 0 ::ffff:10.10.0.10:52412 ::ffff:74.125.235.180:443 CLOSE_WAIT 7318/com.google.and
tcp 0 0 ::ffff:10.10.0.10:22 ::ffff:10.10.0.6:3911 ESTABLISHED 6879/dropbear
tcp 0 0 ::ffff:10.10.0.10:44984 ::ffff:173.194.72.188:5228 ESTABLISHED 3547/com.google.pro
tcp 0 0 ::ffff:10.10.0.10:36091 ::ffff:74.125.235.122:80 ESTABLISHED 6337/berserker.andr
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node PID/Program name Path
unix 2 [ ] DGRAM 10725 3445/system_server /data/misc/wifi/sockets/wpa_ctrl_3445-307
unix 2 [ ] DGRAM 10727 3445/system_server /data/misc/wifi/sockets/wpa_ctrl_3445-308
unix 2 [ ] STREAM 1293 103/rild /dev/socket/rild-debug
unix 2 [ ] STREAM 1295 103/rild /dev/socket/rild
unix 4 [ ] DGRAM 10722 3762/wpa_supplicant /dev/socket/wpa_wlan0
unix 3 [ ] STREAM CONNECTED 17159 108/adbd
unix 3 [ ] STREAM CONNECTED 17158 108/adbd
:
当該コネクションを保持するプロセスについて
pid=3547 は「com.google.process.gapps」というプロセス。# ps | grep 3547 3547 app_1 0:05 com.google.process.gapps
「com.google.process.gapps」は「Google Services Framework」のプロセス名。
(関連記事)
"Fix for Google services Framework (process com.google.process.gapps) has stopped unexpectedly"
「Google services Framework」の実体は以下のシステムパッケージ。(※ソース未公開)
# ls -l /system/app/GoogleServicesFramework.apk -rw-r--r-- 1 root root 2867063 Feb 6 2012 /system/app/GoogleServicesFramework.apk
接続先サーバについて
「173.194.72.188」に該当するホスト名は「mtalk.google.com」。$ host -v mtalk.google.com Trying "mtalk.google.com" ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30177 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mtalk.google.com. IN A ;; ANSWER SECTION: mtalk.google.com. 85662 IN CNAME mobile-gtalk.l.google.com. mobile-gtalk.l.google.com. 66 IN A 173.194.72.188 Received 79 bytes from 10.10.0.18#53 in 0 ms※2012 年 8 月現在、「mtalk.google.com」の分散先として「173.194.72.188」「74.125.31.188」の 2 サーバの存在を確認
接続維持用の Keep-Alive パケットについて
tcpdump を走らせ 5228 ポートをめぐる応酬を監視。沈黙状態の 15 分ごとに端末から Keep-Alive パケットが送出される様子が見てとれます。# tcpdump -nl -s 256 port 5228 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on wlan0, link-type EN10MB (Ethernet), capture size 256 bytes 13:16:23.504179 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 78:103(25) ack 541win 11236 <nop,nop,timestamp 9585688 3638038533> 13:16:23.560095 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 541:566(25) ack 103 win 274 <nop,nop,timestamp 3638938605 9585688> 13:16:23.560168 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 566 win 11236 <nop,nop,timestamp 9585694 3638938605> 13:31:23.568391 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 103:128(25) ack 566 win 11236 <nop,nop,timestamp 9675694 3638938605> 13:31:23.624242 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 566:591(25) ack 128 win 274 <nop,nop,timestamp 3639838674 9675694> 13:31:23.624371 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 591 win 11236 <nop,nop,timestamp 9675700 3639838674> 13:46:23.637744 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 128:153(25) ack 591 win 11236 <nop,nop,timestamp 9765701 3639838674> 13:46:23.696340 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 591:616(25) ack 153 win 274 <nop,nop,timestamp 3640738749 9765701> 13:46:23.696469 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 616 win 11236 <nop,nop,timestamp 9765707 3640738749> 14:01:23.712443 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 153:178(25) ack 616 win 11236 <nop,nop,timestamp 9855709 3640738749> 14:01:23.770009 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 616:641(25) ack 178 win 274 <nop,nop,timestamp 3641638829 9855709> 14:01:23.770164 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 641 win 11236 <nop,nop,timestamp 9855715 3641638829> 14:16:23.783100 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 178:203(25) ack 641 win 11236 <nop,nop,timestamp 9945716 3641638829> 14:16:23.841258 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 641:666(25) ack 203 win 274 <nop,nop,timestamp 3642538904 9945716> 14:16:23.841349 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 666 win 11236 <nop,nop,timestamp 9945722 3642538904> 14:31:23.853398 IP 10.10.0.10.48304 > 173.194.72.188.5228: P 203:228(25) ack 666 win 11236 <nop,nop,timestamp 10035723 3642538904> 14:31:23.912470 IP 173.194.72.188.5228 > 10.10.0.10.48304: P 666:691(25) ack 228 win 274 <nop,nop,timestamp 3643438980 10035723> 14:31:23.912555 IP 10.10.0.10.48304 > 173.194.72.188.5228: . ack 691 win 11236 <nop,nop,timestamp 10035729 3643438980>
GCM, C2DM で通知を送ってみる
以下は自作アプリから C2DM, GCM の順に端末へプッシュ通知を送った時の様子です。
いずれも「173.194.72.188:5228」経由で通知が端末へ配信されていることがわかります。
11:49:49.268419 IP 173.194.72.188.5228 > 10.10.0.10.44984: P 974497881:974498015(134) ack 436376500 win 272 <nop,nop,timestamp 1641184578 3415344>
11:49:49.269687 IP 10.10.0.10.44984 > 173.194.72.188.5228: . ack 134 win 11236 <nop,nop,timestamp 3483319 1641184578>
11:49:59.146970 IP 173.194.72.188.5228 > 10.10.0.10.44984: P 134:262(128) ack 1 win 272 <nop,nop,timestamp 1641194459 3483319>
11:49:59.147103 IP 10.10.0.10.44984 > 173.194.72.188.5228: . ack 262 win 11236 <nop,nop,timestamp 3484307 1641194459>
(tanabe)
Android パッケージインストール処理のしくみを追う
まとめ
※PackageInstaller を起動した状態での関連プロセスの例
$ ps
USER PID PPID VSIZE RSS WCHAN PC NAME
root 1 0 268 180 c009b74c 0000875c S /init
root 36 1 812 244 c02181f4 afd0b45c S /system/bin/installd
root 33 1 60900 16628 c009b74c afd0b844 S zygote
system 62 33 126452 28368 ffffffff afd0b6fc S system_server
app_30 372 33 74876 18852 ffffffff afd0c51c S com.android.packageinstaller
:
※Package Manager Service と installd プロセスの連携用 UNIX ドメインソケット
$ ls -l /dev/socket/installd srw------- system system 2012-07-04 11:43 installd
- Android OS 上でパッケージインストールにもっとも深い関わりを持つシステムサービスは、system_server プロセス内で稼動する Package Manager Service と、単独のネイティブプロセスとして動作する installd デーモンである。両者はいずれもシステムブート時に動作を開始する。
- Package Manager Service は起動時に /data/system/packages.xml ファイルより現システム上のパッケージに関する情報を読み込んで実状と照合し、何らかの不整合があれば再インストール処理を含む自動復旧を試みた上で同ファイルへ最新の状態を反映する。
- Package Manager Service は起動時に /system/etc/permissions/platform.xml を参照し既定の Android パーミッションに関する情報をロードし、同ディレクトリ下の他の *.xml より当該端末がカバーする機能の情報をロードする。
- installd デーモンは UNIX ドメインソケット /dev/socket/installd 経由で Package Manager Service から処理要求を受信し、パッケージのインストール・アンインストールまわりの一連の手順のうち root 権限を要する処理を主に担当する。
- PackageInstaller は通常のパッケージを対話的にインストールするための Android 既定のアプリケーションである。ユーザからインストール指示を受けると PackageInstaller は InstallAppProgress アクティビティを呼び出し、InstallAppProgress は Package Manager Service の installPackage() API 経由で当該パッケージのインストールをシステムへ依頼する。
- Package Manager Service はパッケージインストール要求を受けると当該パッケージの情報を取得し以下を実施する
- インストール処理用のキューへパッケージ情報を追加〜順番待ち
- 当該パッケージの適切なインストールロケーションを判定
- 新規インストール/更新インストールの判別
- 所定のディレクトリへの apk ファイルのコピー
- 当該アプリの UID の決定
- installd デーモンへ処理を要求
- アプリケーションディレクトリの作成とパーミッション設定
- dex コードのキャッシュディレクトリへの切り出し
- 最新の状態を /data/system/packages.xml および packages.list へ反映
- インストール完了の旨をパッケージ名を添えてシステムへブロードキャスト
(新規の場合:Intent.ACTION_PACKAGE_ADDED
更新の場合:Intent.ACTION_PACKAGE_REPLACED) - 上記の PackageInstaller 経由でのインストール経路とは別に、Package Manager Service は内部クラス「AppDirObserver」による非対話式のインストール経路を用意している。 AppDirObserver は android.os.FileObserver の派生クラスであり、所定のディレクトリ直下に .apk の拡張子を持つファイルが配置/削除されると自動的にシステムへのインストール/アンインストールを行う機能を持つ。(※別経路でインストールが行われた場合に重複処理発生を抑制するための機構を備える) Package Manager Service は起動時に /system/app, /data/app, /data/app-private, /system/framwrorks, /vendor/app の各ディレクトリを対象に AppDirObserver クラスのインスタンスを生成する。なお、これらのディレクトリへの書き込みには特権が必要であるため一般のアプリケーションプロセスから直接 AppDirObserver の提供する機構を利用することはできない。
コード読み
パッケージインストール処理を構成する一連のソースコードはかなりのボリュームがありますが、コードリーディングの一助として注釈を添えた抜粋を以下に掲載します。コードはいずれも 2012-07-06 現在の内容です。
なお、オンラインソースへのリンクには便宜上 http://android.git.linaro.org/ を使用しています。
続きを読む
inotify で Android 上のファイル I/O を監視する
Android の Linux カーネルには inotify が含まれており、Android SDK には inotify を利用した FileObserver クラスが用意されています。
Android では一般のアプリケーションプロセスからアクセスできるファイル・ディレクトリが制限されるため、FileObserver を使い Android アプリの形でツールを用意しても今回の目的にはあまり役に立たないでしょう。こういう時には CUI のコマンドラインツールの方が何かと融通がききます。Android エミュレータ環境への adb shell 接続で得られる root 権限のコンソール上でツールを動かすことにしました。
Android の notify コマンドを使う
Android には inotify API を使ってファイル I/O をモニタするコマンドラインツール「notify」が含まれています。 [platform/system/core/toolbox/notify.c]
# notify Usage: notify [-m eventmask] [-c count] [-p] [-v verbosity] path [path ...]この notify コマンドを使って "/data/local/tmp" ディレクトリを監視しつつ別プロセスから以下のコマンドを実行した時の出力例を示します。
# pwd /data/local/tmp # echo aaa > test.txt # mv test.txt aaa.txt
# notify -c 10 /data/local/tmp /data/local/tmp: 00000100 00000000 "test.txt" ; 0x00000100:IN_CREATE /data/local/tmp: 00000020 00000000 "test.txt" ; 0x00000020:IN_OPEN /data/local/tmp: 00000002 00000000 "test.txt" ; 0x00000002:IN_MODIFY /data/local/tmp: 00000008 00000000 "test.txt" ; 0x00000008:IN_CLOSE_WRITE /data/local/tmp: 00000040 00000033 "test.txt" ; 0x00000040:IN_MOVED_FROM /data/local/tmp: 00000080 00000033 "aaa.txt" ; 0x00000080:IN_MOVED_TO※ I/O イベントの定義は [platform/bionic/libc/kernel/common/linux/inotify.h]
inotifywait コマンドを使う
Android の notify コマンドはコンパクトで手軽なツールですが、出力の読みにくさに加え、ディレクトリ配下を再帰的に監視できない点が不便です。改造したりツールを自作するのも面白そうではありますが、Linux 界隈には inotify-tools というパッケージがあることを知り、そこに含まれる inotifywait コマンドを利用することにしました。
すでに誰かが Android 上で動作する inotifywait をビルドし配布しているのではないかとネット上を探したところ見当たらなかったため手元でビルドを行いました。inotifywait 本体と NDK でのビルド用に手を加えたプロジェクト一式を以下に公開します。
md5sum [55BF0FD8365A4139D679CE0D6A3A07B3]
プロジェクト一式(※) - github
(※) 以下の著作物が含まれます。利用に際しては各ソフトウェアのライセンス規約を遵守して下さい。・ inotify-tools
著作権者:Rohan McGovern, Radu Voicilas
ライセンス:GPL version 2
サイト:https://github.com/rvoicilas/inotify-tools/wiki/
・ GNU glibc POSIX 正規表現関数群 (glibc-2.11.3 より)
著作権者:Free Software Foundation, Inc.
ライセンス:LGPL version 2.1 or later
サイト:http://www.gnu.org/software/libc/
inotifywait に /data /cache /system の各ディレクトリを再帰的に監視させた状態で、自作のアプリ「HelloApp」を起動した際の出力例を以下に示します。
# ./inotifywait -r -m /data /cache /system
Setting up watches. Beware: since -r was given, this may take a while!
Watches established.
/system/framework/ ACCESS framework-res.apk
/system/framework/ ACCESS framework-res.apk
/data/app/ OPEN com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ OPEN com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/dalvik-cache/ OPEN data@app@com.example.helloapp-1.apk@classes.dex
/data/dalvik-cache/ ACCESS data@app@com.example.helloapp-1.apk@classes.dex
/data/dalvik-cache/ ACCESS data@app@com.example.helloapp-1.apk@classes.dex
/data/app/ OPEN com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/system/framework/ ACCESS framework-res.apk
/data/app/ ACCESS com.example.helloapp-1.apk
/system/fonts/ OPEN DroidSans-Bold.ttf
/system/lib/hw/ OPEN gralloc.default.so
/system/lib/hw/ ACCESS gralloc.default.so
/system/lib/hw/ ACCESS gralloc.default.so
/system/framework/ ACCESS framework-res.apk
/system/framework/ ACCESS framework-res.apk
/data/app/ CLOSE_NOWRITE,CLOSE com.example.helloapp-1.apk
:
(tanabe)
Android アプリ「SundayPad」を公開しました
予定のない休日の午後に PC の前でごろりと横になって DVD を観たりあちこちのサイトを見て回ったりするのは楽しいのですが、ひとたび「のんびりモード」に浸るとキーボードに触るのが億劫になります。ウェブ検索用にちょっとキーワードを入力したいのだけれど今の体勢を崩すのはなんだか悔しい。こんな時スマホなら音声入力を手軽に使えるので便利です。でも外出先ならともかく休日の自宅でわざわざ端末の小さな画面を追いかけるのはあまり嬉しくない気もする。不精者はあれこれ悩みます。
ある時ふと思いました。スマホには得意で賢い音声認識だけをやらせて、その結果を簡単に PC 上で利用できるようにすれば何かと便利ではないか?
さっそく Google 音声認識と連携し認識結果文字列を送信する簡単な Android アプリと、それを受信して PC 上のアプリの所定のフィールドへその文字列を送出する Windows プログラムを作ってみました。期待通りそれはシンプルな割に便利なものになりましたが、しばらく使っている内に「PC のマウスカーソルを操作できればもっと便利になる」ということに気づき、タッチパッド機能と必要最小限のソフトウェアキーボードを加えることにしました。
同僚たちに試作版を見せるとなるほどと受けもよく、そこで出されたアイディアを含めて機能追加と改良を行いひと通り形になったのが Android アプリ「SundayPad」と、PC 用のエージェントプログラム「SpAgent」です。興味のある方はお試し下さい。
改訂履歴
v1.0.1 (2012-06-07)
- 要求元の Android 端末のアドレスが前回認証ずみのものと同一であれば PC 側での受け入れ確認ダイアログの表示をスキップ
- Windows 環境でのカーソルの細かい移動をなめらかに
インストール・ダウンロード
Android アプリ「SundayPad」 (2012-06-07 更新)
SundayPad - Google Play のページ
PC 用エージェントプログラム「SpAgent」
Windows 版 - SpAgent.zip (2012-06-07 更新)
Mac 版 - SpAgent.dmg (2012-06-07 更新)
動作環境
SundayPad は Android OS 2.1 以上に対応します。
バージョン 2.2 以上の環境をお勧めします。
SpAgent の動作は以下のそれぞれ単一のテスト環境においてのみ確認しています
Windows 版
- Windows 8 Consumer Preview 32 bit/ 64 bit (デスクトップ環境)
- Windows 7 Professional SP1 32 bit/ 64 bit
- Windows Vista Business SP2 32 bit/ 64 bit
- Windows XP Professional SP3 32 bit
- Windows XP Home SP3
- Windows 2000 SP4
Mac 版
- Mac OS X バージョン 10.6.8
Android アプリ「AppNetBlocker」を公開しました
AppNetBlocker は、所定のアプリから「完全なインターネットアクセス」の許可を除去するツールです。実行に root 権限は必要ありません。Android 1.6 以上の環境で動作します。興味のある方はご利用下さい。もちろん無料です。
(2011/12/26 追記)
本アプリは、現時点では安全面において不安要素の少なくない Android をめぐる状況において Android 利用者が自分自身を守るためにとり得る対策のひとつを形にしたものであり、他者の権利を脅かすことを目的とするものではありません。
もし、Android を今よりもさらに安全に利用することが可能となればより多くの利用者・開発者の利益につながることでしょう。本アプリはたとえ僅かでもその一助になればと手がけたものであり、開発の動機もそこにあります。
しかしながら、一部の方から本アプリと Android マーケット規約とのかねあいを懸念するご指摘がありました。その話題については判断の余地があるものと認識していますが、少なくとも利用者の不安を誘引することはまったく本意ではなく、マーケットでの配布という形態は一時中断することとします。
実験用のいわゆる「野良アプリ」として apk のダウンロードリンクを当面残しておきます。このリンクから端末へ直接インストールすることはできません。意図を理解される方のみ自己責任でご利用下さい。
[ AppNetBlocker.apk ]
md5sum [CC8104C9DDE44AD308F09FF22B551575]
AppNetBlocker とは?
Android 端末上のデータを狙うマルウェアの問題が取り沙汰されていることもあり、アプリに付与された「許可」の内容は何かと気になります。
特に、それがネットワークアプリやバナー広告を表示するアプリではなく、また、機能面でインターネットへのアクセスが必須とは考えにくい内容のアプリであるにもかかわらず「完全なインターネットアクセス」許可を持っている場合は悩ましいですね。
そんな時には AppNetBlocker が役に立つかもしれません。
続きを読む
AndroidからL2TP/IPsec CRT VPNに接続する
Android端末には、標準でVPN接続機能が搭載されています。
その中でもL2TP/IPsec方式では、x509証明書による認証がサポートされていて、端末ごとに個別の証明書を発行するなどの方法でセキュリティの高いVPN接続を行うことができます。
KLabでは、社内用の認証局を運用していて、社員が自分のユーザ名の入ったクライアント証明書を持てるようになっています。
管理用サイトへのブラウザでのアクセスのほか、社内の無線アクセスポイントもWPA2 Enterprise EAP-TLS認証に対応させて、クライアント証明書による強力な認証を活用しています。
もちろん、万が一の秘密鍵流出の際には、認証局側でCRLにより証明書を失効させることができるよう整備しています。
本記事では、クライアント証明書を使ったAndroid端末からのL2TP/IPsec VPN接続を試してみたいと思います。
Android 2.2系までの端末では・・・
残念ながら、Android 2.2系までの端末では、組み込まれている racoon(ipsec-tools)に問題があり、x509証明書による認証が実質的に使えない状態です。理由は以下のとおりです。
IPsecでは、IKEの認証フェーズ1にて、お互いのIDを交換します。
このIDには、「ID_IPV4_ADDR」・「ID_FQDN」などいくつかの種類があり、x509証明書を認証に使う場合には、証明書の識別名を使う「ID_DER_ASN1_DN」というIDタイプを使うことができます。
本記事のように、接続元IPアドレスを固定せずx509証明書を使う環境では、ID_DER_ASN1_DNタイプのIDを使うのがベストですが、Android 2.2系のracoonでは、「L2TP/IPsec CRT VPN」を選んでいても常にID_IPV4_ADDRタイプのIDがサーバに通知されてしまいます。
x509証明書による認証で、ID_IPV4_ADDR等をIDに使うことは可能ですが、この場合は使用する証明書のsubjectAltName拡張等のフィールドに「IP:192.168.0.1」等の値を含めておく必要があります。
そしてモバイル端末では、キャリアから動的に割り当てられるIPアドレスを使いますから、事前にIPアドレス等を記載した証明書を用意することも難しいです。
このため、Android 2.2までのOSを搭載した端末では、証明書によるIPsecは実質使えないのです。
この問題は、バグレポートされていて、Android 2.3では改善されています。 そのため、以降はAndroid 2.3系のOSを搭載した端末を前提とします。
認証に使う証明書とCA
認証に使うクライアント証明書・サーバ証明書、発行するCAには、Android端末の実装上、いくつかの制約があるため注意が必要です。
- サーバ証明書のコモンネームには、サーバのFQDNを記載する
一般のWebサーバ用の証明書と同様に、コモンネームに記載されたFQDNと、端末の設定画面に入力した接続先サーバ名は一致する必要があります。
- サーバ証明書は、シングルルートCAから発行する
サーバ証明書やクライアント証明書の発行元は、いわゆるオレオレ認証局を使うことができますが、自己署名しているルートCAから直接発行する必要があります。
Android端末のIPsecの処理では、端末ビルトインの証明書ストアは使われず、設定画面で指定したクライアント証明書とCA証明書だけが認証に使われます。ですから、サーバ証明書の検証に必要なルート証明書は、CA証明書としてインポートしてあげれば良いことになります。
しかし、Android端末のCertInstallerでは、複数の証明書をまとめてインポートできない仕様らしく、PEMフォーマットの証明書を複数連結したファイルをインポートしようとしても、「CA証明書1件」と表示されて、中間証明書を含めてインポートすることができません。
IPsecの認証プロトコル中でやり取りできる証明書も各方向1枚だけのため、インポートしたルート証明書から直接サーバ証明書を発行するしか手がありません。
(IKEのプロコトル仕様では、複数の証明書ペイロードを送ることができます。
実際にstrongSwanに改造を加えて実験してみましたが、なんとracoon側が1つ目の証明書ペイロード以外を無視する実装になっていたため失敗しました・・・)
- クライアント証明書については、自由度は高い
クライアント証明書の検証は、サーバ側の仕事ですので、サーバに必要な中間証明書をインストールすればどのような証明書でも使用することができます。
openswanやstrongswanでは、クライアントのID_DER_ASN1_DNタイプのIDにワイルドカードを指定できるため、証明書の識別名についても自由度があります。
「rightid = "C=JP, ST=Tokyo, O=KLab Inc., CN=*」と設定すれば、各ユーザに個別に発行している「C=JP, ST=Tokyo, O=KLab Inc., CN=USERNAME」のような識別名の証明書をすべて使えるようになります。
端末への証明書・秘密鍵のインポート方法
インポートするクライアント証明書と秘密鍵は 「.p12」拡張子のPKCS#12形式、CA証明書は「.crt」拡張子のPEM形式もしくはDER形式にして、SDカードのルートかdownloadディレクトリに置きます。
Android端末のCertInstallerは「.p12」または「.crt」の拡張子の付いたファイルのみインポートしようとしますので、必ず所定の拡張子にする必要があります。
インポートは、設定の「現在地情報とセキュリティ」メニューの「SDカードからインストール」から行えます。
それぞれ読み込みできると証明書名を聞かれますが、これは後ほどVPN設定を行う際、証明書を選ぶメニューでの名前になりますので、わかりやすい名前を設定しておきます。
Android端末では、インポート済みの証明書の一覧表示や削除のインタフェースが充実していないので、証明書名には「シリアル番号+コモンネーム」等、証明書の個体を識別しやすい名前にするとよいでしょう。
サーバサイドの準備
Android端末からのVPN接続を受け入れるサーバ側の構築を行います。
使用するソフトウェアは、以下のとおりです。
- openswan-2.6.36
- xl2tpd-1.3.0 (後述のチューニングのためにパッチ適用あり)
- ppp-2.4.5
それぞれ、ビルドオプションに特別なものを指定する必要はありません。
カーネルのESPサポートは、linux-3.0.4の標準のものを使用しています。
また、LinuxでのIPsec実装は、openswanのほかにstrongswanもありますが、strongswanではVPN切断後の再接続が上手くいかないという問題が発生したため、openswanを使用します。
Android端末からのL2TP/IPsec接続では、VPN切断時にサーバ側のSAをうまく削除してくれないという問題があるようで、サーバ側にlifetime時間までSAが残ってしまいます。strongswanを使用するとSAが残っている間は再接続(2本目のSA)が出来ず通信不能になってしまいました。
openswanの設定ファイル(ipsec.conf)は以下のとおりです。
config setup
interfaces = %defaultroute
syslog = auth.error
plutodebug = "control"
uniqueids = no
nat_traversal = yes
conn Android-L2TP-IPsec
auto = add
type = transport
keyexchange = ike
auth = esp
authby = rsasig
pfs = no
keyingtries = 1
ikelifetime = 8h
keylife = 8h
rekeymargin = 10m
left = %defaultroute
leftid = "C=JP, ST=Tokyo, O=KLab inc., (略)"
leftrsasigkey = %cert
leftcert = Android_IPsec_Server.pem
leftupdown = "/bin/true"
leftprotoport = 17/1701
right = %any
rightca = "C=JP, ST=Tokyo, O=KLab Inc., (略)"
rightid = "C=JP, ST=Tokyo, O=KLab Inc., (略)"
rightrsasigkey = %cert
rightprotoport = 17/%any
読み込む証明書の指定(leftcert)、各ID等は環境に合わせて調整する必要があります。
サーバ証明書を/etc/ipsec.d/certsに、秘密鍵を/etc/ipsec.d/private、クライアント証明書の検証に必要な中間CA証明書を/etc/ipsec.d/cacertsにそれぞれ設置します。
ファイル名は、OpenSSL流のハッシュ形式にしなくても自動的に読み込んでくれます。
また、秘密鍵の復号化に必要なパスワードは/etc/ipsec.secretsに以下のように記述します。
(leftcertに指定しているAndroid_IPsec_Server.pemに対応する秘密鍵が、/etc/ipsec.d/private以下にAndroid_IPsec_Server.keyの名前で保存されている前提です。)
: RSA Android_IPsec_Server.key "hogefuga"
xl2tpd側の設定ファイル(xl2tpd.conf)とpppオプション(ppp-options)は以下のとおりです。
トンネルに割り当てるIPアドレス(local ipやip range)もお好みで選んでください。
[global] listen-addr = 0.0.0.0 port = 1701 debug network = no debug state = no debug tunnel = no [lns default] pppoptfile = /etc/xl2tpd/ppp-options hostname = gw1 exclusive = No local ip = 10.100.254.1 ip range = 10.100.254.128 - 10.100.254.254 length bit = yes require chap = yes refuse pap = yes require authentication = yes
pppoptfileでは、認証可否の設定の他、Android端末に通知するDNSサーバを設定します。
auth refuse-pap require-chap ms-dns 10.100.254.1
上記設定では、CHAP認証を行う設定になっています。許可するユーザ名・パスワードは/etc/ppp/chap-secretsに記述します。
USERNAME * "PASSWORD" *
PAP/CHAPともにrefuseとすると、L2TP上では認証しない設定にすることができますが、Android端末ではユーザ名・パスワードの入力が必須になっているため、ダミーのパスワードを入力する必要があります。
Android側からのVPN接続を行うと、端末からのすべての通信がトンネルを経由してサーバへルーティングされます。
サーバ側では、トンネルごとにPPPインタフェースが作られますので、適宜インターネットや社内のシステムに対してルーティング・アクセス許可してあげれば通信可能になります。
名前解決に使われるDNSサーバはpppoptfileのms-dnsで指定したサーバが使用されます。
Android端末サイドのVPN設定
Android端末に、VPN接続のプロファイルを作ります。
設定の「無線とネットワーク」から「VPN設定」とたどります。
「VPNの追加」をタップして「L2TP/IPsec CRP VPNを追加」を選びます。
各項目を以下のように設定して保存すると、プロファイルが作られます。
- VPN名: 任意の名前を設定します
- VPNサーバの設定: 接続するサーバのFQDNを指定します
- L2TPセキュリティ保護を設定: チェックを外したままにします
- 証明書を設定する:インポートしたクライアント証明書を選びます
- CA証明書を設定する:インポートしたCA証明書を選びます
- DNS検索ドメイン: 社内のドメイン名等を任意で設定します
保存後、VPN欄に現れたプロファイルをタップして、サーバのchap-secretsに記述したユーザ名・パスワードを入力し接続します。
画面上部に「VPN [プロファイル名] が接続されました」と出ればVPN接続は完了です。
切断する場合には、プロファイルを再度タップすれば切断されます。
VPN接続中に通信できなくなる問題
筆者の環境では、長時間接続していると、突然通信不能になる症状が発生しました。
調査してみたところ、キャリアあるいは電波状況によっては、不定期的にパケロスする状況が発生するようで、L2TPサーバ側で定期的に送っているHelloメッセージがロスし最大再送回数に到達してしまったためにトンネルが閉じられているという状態でした。
Android端末側から、L2TPトンネルの切断を検知できるようなメッセージを送出できると良いのですが、端末標準の設定インタフェースでは実現できないため、サーバサイドで出来る限りの調整を行なってみます。
今回使用したL2TPサーバ(xl2tpd)では、HelloメッセージによるKeepaliveの間隔(60秒)、コントロールメッセージの再送間隔(1秒)・最大再送回数(5回)がソースコード中にハードコードされています。
この再送条件では、電波状況等により通信品質が変化するモバイル環境には厳しすぎますので、設定ファイルからパラメータを変更できるようにするパッチを書いてみました。
xl2tpd-1.3.0-add-ctrl-retrans-opt.patch(2012/02/17:コメントにて、1.3.1では上記パッチが当たらないという連絡をいただきましたのでdiffを取り直しました: xl2tpd-1.3.1-add-ctrl-retrans-opt.patch )
このパッチを適用すると、設定ファイルのglobalセクションに以下の3つのパラメータを設定できるようになります。
- hello delay (Helloメッセージの送信間隔)
- ctrl retrans max (最大再送回数)
- ctrl retrans delay (再送回数)
各パラメータをそれぞれ、Hello送信間隔を5分(hello delay = 300)、コントロールパケットの再送間隔を10秒(ctrl retrans delay = 10)で最大再送回数を18回(ctrl retrans max = 18)とすれば、最大3分程度通信不能な時間が発生してもトンネルを閉じられずにすみます。
長すぎる値を設定すると、本当に端末がオフラインになってしまっていても、長時間サーバ側にトンネルが残り続けてしまうため注意が必要です。
このパッチによるパラメータの変更は、あくまで切断される条件の緩和ですので、確実にVPNが切れなくなるわけではありませんが、多少の効果はあると思います。
エンコードされた AndroidManifest.xml を読む
1. データ例
(A) テストアプリ「MyApp」用に記述した生の AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.klab.sample.myapp"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="4" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MyApp"
android:label="@string/app_name"
android:launchMode="singleTask"
android:excludeFromRecents="false"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
(B) パッケージ後の MyApp.apk に含まれる エンコードずみ AndroidManifest.xml のダンプ
(C) 試作コードにより上記 (B) に含まれる情報を XML ツリー形式に再構成したもの
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
package="jp.klab.sample.myapp">
<uses-sdk android:minSdkVersion="4" />
<application android:label="@0x7F050001" android:icon="@0x7F020000">
<activity
android:label="@0x7F050001"
android:name=".MyApp"
android:excludeFromRecents="false"
android:launchMode="2"
android:configChanges="0x000000A0">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
- エンコードされたデータには元の XML 記述中の名前空間情報・要素名・属性名・属性値・タグの階層情報等が含まれており、属性値として指定されたリソース ID や定数に対するシンボル名は値に解決された状態で保持される
- エンコードの目的はサイズ圧縮ではなく端末においてより簡単にデータをパース可能とすることにある ("This is designed to express everything in an XML document, in a form that is much easier to parse on the device.")
Android 上のアプリから SSL クライアント認証の必要なサーバへアクセスする方法
それから半年、本家の Android 標準ブラウザは現在も未対応のままですが、「SandroB」のようにクライアント認証に独自対応したブラウザも登場し始めています。こうした動きは Android ユーザとしてとても喜ばしいことで、対応環境の拡大や機能面の向上など今後の進化が大いに期待されます。
一方で、たとえば KLab の「VPN-Warp」のような SSL-VPN システム経由での利用が想定されるアプリケーションは Web ブラウザばかりではありません。私の場合、出先から自宅 PC へのアクセスには VNC を VPN-Warp+stone 経由で使っています。VNC ビューワそのものは SSL に対応していませんが、通信に stone を介在させることで所定のアプリ本体が SSL まわりの実装を持っていなくてもそういう使い方が可能となるわけですね。もちろん、人によってはここで言う「所定のアプリ」に、「普段使い慣れているブラウザ」が該当する場合もあるでしょう。
冒頭の記事を書いた時点では Android 用 stone は何かと面倒な CUI ベースでしたが、現在は通常の Android アプリとしてマーケットで公開しています。社内からの声もあり、今回はこのアプリ版に固有の Android キーストアへ証明書をインストールする機能に関する話題に触れながら、stone for Android でクライアント証明書を利用する手順等を紹介します。 続きを読む
root 化ずみ端末に対応した Android アプリを書く方法
一部の開発者向け製品以外では root 権限を取得するための公式の手段は提供されていないので、その方法を自分で探索するにせよ誰かが開拓した手順をトレースするにせよすべては自己責任であるわけですが、一方で root 権限を必要とするアプリはマーケットにもあれこれ出回っており、つまりはこの件に関する安全性と利便性のトレードオフに満足できない向きが世界中に大勢いるということでしょう。
その状況自体もいろいろ興味ぶかいのですが、ところで、その「root 権限を利用するアプリ」というものはどうやって書けばいいのでしょう?Google 公式の開発者向け資料は言うに及ばず、その他のリソースにも今のところほとんどこの話題に関する情報は見当たりません。そこで今回は、実際に手元のアプリを root 権限での実行に対応させる試みを通じて得た情報やノウハウを紹介したいと思います。
続きを読む
Ant とテキストエディタではじめる Android
皆様、新年あけましておめでとうございます。今年もよろしくお願いいたします。
さて、The Open Handset Alliance が発表した Android SDK ですが、既に Eclipse + Eclipse Plugin を使用してお試しになられた方もいらっしゃるかと思 います。
私は通常の開発業務で Eclipse を使っているのですが、plugin を入れすぎたせ いか動作が重いので、もっと軽い環境で開発できたらと思ってました。という 訳で Emacsと か vi などのテキストエディタを使って Android のアプリケーション を開発できる方法をご紹介します。
続きを読む


