socat便利 && 「ファイル」奥深い…。
OSC2014にお邪魔
twitterを漠然と眺めていて「遠くて明星大学行けない」という文字列を受信。明星大学近くね? 何やってるんだろ?
オープンソースカンファレンス2014 Tokyo/Spring
へええ。展示は登録も不要。休みを無駄にしないためにも、覗いてみようかな。
オープンソースの文化祭
…というサブタイトルが付いているのを今知ったが、土曜日のキャンパスの静けさと空気のおいしさとは対照的に、まさに文化祭の盛り上がりであった。展示には企業とコミュニティがあり、企業はサーバ系、コミュニティは組み込みが多い印象。企業で組み込みやってる自分はコミュニティとの間をとりもつべく反省するように。
学生さんが声をかけてくれた。「パケットアートやってるんです」。パケットの中身に応じてアニメーションさせるアート作品とのこと。TCPのフラグに応じてキャラクタを変えたりしているそうで、パケット擬人化みたいな感じ? 学生さんらしくていいじゃないですか。
「リアルタイムでやってるの?」と聞いたら「ファイル読み込みなんですよ、リアルタイムはやりたいけどやり方がわからない…<小声>raw socket??</小声>」とのこと。わからないとおっしゃりつつraw socketに着眼とは、お目が高い。ずいぶん前にTCPの異常系再現のためにraw socketでTCP書いたけど、それはなかなか面倒であった…。
OSSは真似して勉強するのにも使えるけど、組み合わせて目的を実現するのにも使える。そこで。
tcpdumpで書いたファイルを読む
パケットをキャプチャするtcpdumpには-wオプションがあり、pcap形式でファイルに保存できるので、このファイルを読めばよいじゃん。と思って、tcpdump -w hoge.pcap で書いているファイルをtcpdump -r hoge.pcapで読ませたところ、途中で終了してしまう。読み込みが書き込みに追いついてファイルの終端に達すると、read()が0を返してEOFとなり終了していると思われる。
tail -fの実装ってどうなってるの?
ファイルの終端に達しても終わらない魔法、それがtail -f。ログファイルを表示する時などでおなじみ。tail -f <ファイル名>とすると、ファイルを読み込んで終端まで表示したらじっと待ち、書き込みファイルに更新があると、また読み込んで表示が更新される便利機能。
tail -fってどうやって実現しているのだろう? ググると、こちらのサイトを発見。結構面倒なことしてる。tail -Fに至ってはポーリング&再open()。kqueue()ってBSDにはあるけどLinuxであるUbuntuでman kqueueしても知らんと言われる。
Ubuntuでman tailとすると、GNU coreutilsを使っているとのこと。ソースを持ってきてtail.cを見ると、tail_forever()という関数がある。よくわからんが、ファイルサイズとか日付を見てるっぽい。どちらにせよ面倒。
パイプとソケット
ところが。tcpdump -w – | tcpdump -r – として、標準出力に書いて、標準入力から読み込むと、右側のtcpdumpの読み込みは終わらない。パイプ経由で読み込むと、Broken Pipeにならない限り、read()がブロックして止まっているように見える、と思われる。動画のストリーミングが、通信状態が悪くなって、クルクル止まっているのと同じですな。
TCPのソケットだってそうだ。相手がデータをwrite()してくれないのに自分がread()すると、read()がブロックして止まる。相手がshutdown()とかclose()すると、read()が0を返して、EOF。
パイプでできそうと思ったが、今度は読み込みがリアルタイムに表示されない。バッファリングされていて、左の書き込みデータがある程度溜まらないと、右の読み込み側に渡らない。これは、tcpdumpに-Uオプションを付けて、パケット単位の書き込みで解決。ついでに、sshでログインしているNICをtcpdumpして表示すると、ハウリング(?)を起こすから、パケット取得用別NIC(eth1)を用意し(*1)、tcpdump -i eth1 -U -w – | tcpdump -r – として、pingにもキビキビ反応。
ファイルでブロックさせるには
パケットアートのプログラムはファイル読み込みなので、これは修正したくないだろう。でも、tcpdump -wで書くファイルを読むと確実にEOFに達する。そこで、socat。たまたま別目的で注目していた、便利コマンド。ある入力を、ある出力に転送できる。入出力の形式は、ファイルからソケットまで、何でもありだ。
socatには、実に様々なオプションがある。何ができるか分からないくらい、何でもできる。マニュアルを隅々まで読み込むと、入出力の対象に「名前付きパイプ」なるものがある。これ、聞いたことはあってもなかなか使わないのでは。パイプって、|で使うとファイル名付かないから、ファイル名指定で読み込みできないと思っていたけど、名前付き、ならできそうな気がするじゃないですか。試しにやってみよう。
今日の1行
1 |
# tcpdump -U -i eth1 -w - | socat -u STDIN PIPE:/tmp/pipe |
これで、tcpdumpの出力を名前付きパイプにして、ファイル読み込みの既存の仕組みはそのままに、ファイル終端に来てもEOFで終了せずに、キャプチャファイルをリアルタイムに読み込みできるはず。
この1行を実行しつつ、tcpdump -r /tmp/pipeで祈るように読み込むと…何故かパーミッションエラー(そこかい)。tcpdumpは確かにrootで実行しているけど、/tmp/pipeは644で所有者以外でも読み込めそうなのになぁ。socatには作成するファイルのパーミッションや所有者を設定するオプションもあるが、うまく動かないので、
1 |
# chown <読み込むユーザー名>:<読み込むグループ名> /tmp/pipe |
で所有者を無理矢理変えるか、読み込み側を
1 |
$ cat /tmp/pipe | tcpdump -r - |
としたら、うまく行った。とりあえず、万歳!
(一応特殊性0なfread()で名前付きパイプを読み込んでみたけど、ちゃんとブロックした)
課題
ストリーミング
アート表示が速くて、ファイル読み込みが終端に達すると、read()がブロックするので、表示機能とファイル読み込みが同一スレッドだと、表示が固まる(バッファアンダーフロー)。逆に、表示が遅いのにパケットがどんどん到達すると、表示がリアルタイムでなくなって行き、キャプチャしたパケットのバッファがあふれる(バッファオーバーフロー)。このあたりの動作はプログラム全体の構造や性能に依存する。依存しないようにするには、動画のストリーミングのような同期機構がきっと必要。
スマホ
デモはChrome上で行われていたように見えたが、iOSやAndroidの実装もあったようだ。リアルタイムキャプチャはスマホでは(jailbreakやroot化をしない限り)できないはずなので、どこかのホストでtcpdump→socat→TCPのソケットに渡して、スマホでTCPのソケットを読み込むようにすれば、キャプチャしているホストの様子がどこからでも見られることになる。
1 |
# tcpdump -U -i eth1 -w - | socat -u STDIN TCP-LISTEN:1234 |
などとできそうだが、全世界に丸見えだし、出力先をOPENSSL-LISTENなどとしても、スマホ側のSSLは普通はHTTPとセットだろうし、SSLの証明書管理も必要だし、認証しなければ結局全世界に丸見え…ああ危険、要注意(*2)。
本当の課題
ここまで書いて、パケットアートで検索すると、コンテストが開催されている模様。なになに、キャプチャファイルはプライバシー保護のためMAWIのガイドライン準拠? そうかー教育も含めてここが大事、確かに! …ということはtcpdumpの生キャプチャは使えない…がーん。うすうす気づいていた(*1)や(*2)は、ここへの伏線だったのか…。
tcpdumpの生キャプチャからプライバシーデータを削除するtcpdprivもしばらく更新されていないようで、FreeBSD9やUbuntu12.04ではコンパイルできなかたよ…。別の課題を発見。
長くなったけど、最後に所感
- オープンソースを見て、触って、勉強する絶好の機会になった。ソースがなきゃ、こんなことわからなかった。
- UNIXのファイルアクセスによる抽象化は、非常に便利だが、ファイルとストリーミング(的なパイプやソケット)では、扱いが微妙に異なる模様。
- でも、ファイルにしておけば、socatのような便利なオープンソースで、TCPなどで飛ばして場所を意識しない仮想化なんかも簡単にできる(実際やったらできちゃった)。
- MAWIって先進的だったんですね…。
- 現実はきっと若さで乗り切れる! (わるいことはしないでねっ)
そんなことをしなくても
名前付きパイプにはmkfifoコマンドがあるって? おおお、使わなすぎて知らんかった…確かにできた。まぁTCPで飛ばすにはsocatが必要なので、同じ仕組みで名前付きパイプもTCPも使えるsocatが便利、ということで…。