P2PやNAT Traversal、その周辺要素や、実はそれらに多く絡んでいるXMPPの勉強がてら、色々読んでいてもいまいちイメージがつかめないということで、とりあえずXMPPの拡張仕様であるjingleの仕組みを使って、互いにNAT配下のサーバでも簡単にP2Pでファイル同期できるツールを作ってみました。現状はFedoraやCentOSの64bitのLinuxでのみ動作確認しています。
synciga(しんきーが)という名前で呼ぶことにします。
syncigaにできる事
syncigaによってできることは、サーバマシンやクライアントがNAT配下に位置していても、そのマシン上でsyncigaを立ち上げておけば、別の場所にいるNAT配下のマシンからP2Pの通信で、とあるディレクトリのファイルを簡単に同期(現状は同期対象のディレクトリにファイルの変更や作成が行われたら自動でファイルの転送をするだけで削除は行われない)することができます。
現状はディレクトリ配下の自動検知からファイル転送までは、一方向でしかできません。ただ、技術的(オプションでは実装済み)に双方向の送受信は可能なので、いずれは双方向の同期を実装してみようかと感がています。
P2Pに至るまでの認証やセッション管理はSSLにのせた上でGoogle Talkサーバを使うので、Gmailアカウントを持っている必要があります。また、アカウントを持っていなくても自前でjingleに対応したXMPPサーバとSTUNサーバを立てる事で、Google Talkサーバに依存しない仕組みも作る事ができます。
syncigaでやっている事
概要は以下の図のようになります。
このように、緑がサーバやクライアントマシンとして、それらがNAT配下に属していても、syncigaを立ち上げる事で、Google Talkサーバ(XMPP)に認証を行って、P2Pのセッション管理を依頼し、別のNAT配下(2段でも動作することを確認しました)のsyncigaからP2Pの通信でファイルを同期します。これによって、ファイル転送用にサーバを立てる必要がなく、自前のファイル同期を行う事ができます。Google Talkサーバへの通信はSSLを介して行います。
jingleやP2Pの実装には、Googleが提供しているlibjingleを使っています。NAT TraversalのプロトコルにはICEを使っていて、STUNサーバを介して(STUNが正常に動作しない場合はRELAY)UDPを使った疑似TCPのような処理を行っています。ICEはP2P間の最良の通信を行うために、とりうる通信の組み合わせを試みてパフォーマンスを計測するところも面白いです。
簡単なNAT Traversalの仕組み(UDPホールパンチング)はWikipediaにうまくまとまっていました。
NATの背後にある各ホストは、典型的には STUN や ICE を用いて通信相手との間に存在するNATの公開アドレスを取得する。この過程では、公開のアドレス空間にある第3の既知のサーバに一旦接続することでUDPポートマッピングと相手方のUDP状態を確立し、以後はパケットが違うホストから来ていても NATデバイスが状態を維持するだろうと期待して、直接的な通信に切り換える。通常、UDP状態は数十秒~数分以内に失効してクローズされてしまうので、UDPホールパンチングでは定期的な keep-alive パケットの送出が併用され、これによってNATが持つUDP状態マシンの寿命を延長する。
syncigaでファイル同期をしてみる
現状ではファイルの自動送信しか実装していませんが、おいおい双方向で同期できると良いなぁと思っています。使い方は、GitHubのREADME通りにsyncigaをビルドした後に、適当に自分のホームディレクトリのbin以下にsyncigaをコピーします。
ファイルの同期を受ける側でsyncigaを起動
現状は同期という観点では色々未実装なので、「ファイルの同期を受ける側」という前提で話を進めます。ファイルの同期を受ける側で以下のようにsyncigaを起動します。
[program lang=’bash’ escaped=’true’]
$ synciga --sync-dir=./syncer/ myaccount@gmail.com Directory: ./syncer/ Password: Connecting... OK Logging in... OK Logged in... OK Assigned FullJID myaccount@gmail.com/synciga******** Input below command on client synciga synciga --sync --remote-dir=./syncer/ myaccount@gmail.com myaccount@gmail.com/synciga********
[/program]
すると上記のようにログインが完了し、ユニークなIDであるFull JIDが得られます。また、「ファイルの同期を送る側」で実行するコマンドが上記のように表示されるので、それをもう一方のマシンで実行すれば良いです。上記のオプションでは、./syncer/というディレクトリに、もう一方のマシンからファイルが同期されてくる事を表しています。
ファイルの同期を行う側でsyncigaを起動
基本的には上記の表示のまま、syncigaを起動させれば良いです。
[program lang=’bash’ escaped=’true’]
synciga --sync --remote-dir=./syncer/ myaccount@gmail.com myaccount@gmail.com/synciga********
[/program]
ただし、この状態だとsyncigaを実行したカレントディレクトリのファイル作成・変更が、受信側に転送されるので、転送対象のディレクトリを指定したい場合は、上記のコマンドにsync-dirオプションを追記します。
[program lang=’bash’ escaped=’true’]
synciga --sync --remote-dir=./syncer/ --sync-dir=./test_sync/ myaccount@gmail.com myaccount@gmail.com/synciga********
[/program]
すると、以下のような出力と共に起動します。
[program lang=’bash’ escaped=’true’]
$ synciga --sync --remote-dir=./syncer/ --sync-dir=./test_sync/ myaccount@gmail.com myaccount@gmail.com/synciga******** Directory: ./test_sync/ Password: Connecting... OK Logging in... OK Logged in... OK Assigned FullJID myaccount@gmail.com/synciga******** Syncing directory ./test_sync/
[/program]
上記の出力と共に、./test_sync/ディレクトリが同期対象になります。この状態で例えば、適当に./test_sync/ディレクトリにファイルを作ったりすると、自動でそれが、もう一方に転送されます。例えば、以下のようなコマンドを実行します。
[program lang=’bash’ escaped=’true’]
echo hoge > ./test_sync/hoge
[/program]
そうすると、ファイル転送の出力と共に、
[program lang=’bash’ escaped=’true’]
Syncing file start: hoge Tunnel connected End of file Syncing file end: hoge
[/program]
相手側の./syncer/ディレクトリ以下に以下のようにファイルが転送されます。
[program lang=’bash’ escaped=’true’]
IncomingTunnel from myaccount@gmail.com/synciga********: recv:./syncer/hoge Tunnel connected Tunnel closed normally
[/program]
上記のようなsyncigaの出力と共に、
[program lang=’bash’ escaped=’true’]
$ ls -l ./syncer/ total 4 -rw-rw-r-- 1 matsumoto_r matsumoto_r 5 Apr 18 22:47 hoge
[/program]
このようにファイルが転送されてきます。NAT配下でもファイル転送用の中央サーバ無しで手軽にカジュアルにP2Pでファイル転送ができて良いですね!
まとめ
これはあくまで、今後の研究のための勉強から生まれたツールに過ぎないので、そこまできちんと作りこんでいません。ですが、それなりに便利だったので公開してみました。今後は、ファイルの転送だけでなく、ファイルの削除や双方向の同期処理を行ったり、ファイルの差分のみを転送できたりすると良いなぁと思っています。
そのあたりができてきたら、自分の専門であるWebサーバやHTTPの分散処理機能みたいなものを実装していきたいです。