GoでP2Pファイル転送コマンドを作った


ftransとは

cs3238-tsuzu/ftransで公開/開発しているファイル転送のためのコマンドラインツールです。
File TRANSferringの略です。愚直です。

TL;DR

  • バイナリ1つで手軽かつP2Pでファイル転送するコマンドラインツールを作った
  • P2Pはproduction-readyなソフトウェアを作るのは大変だけど個人で使う分には結構使える

特徴

  • Pure Goで実装されている静的リンクのシングルバイナリファイル
    • バイナリをReleasesからダウンロードするだけで即利用可能
    • x86_64だけでなくx86、arm等でも動作する(はず。多分・・・)
  • P2Pを利用
    • 回線の帯域をフルに活用した高速な転送が可能

インストール

  • Releasesから適当なバイナリへのリンクをコピペして任意のコマンドでダウンロード
$ wget https://github.com/cs3238-tsuzu/ftrans/releases/download/...
$ chmod +x ftrans
$ sudo cp ./ftrans /usr/local/bin

go getでもインストールは可能ですがバイナリにバージョン番号等が埋め込まれなかったりプロトコルのバージョンが保証されないため開発以外の用途ではオススメできません。
自前ビルドをしたい場合はReadmeのFor Developersをご覧ください。

使い方

  • 送信側
$ ftrans send .vimrc ~/.ssh/id_rsa_hoge.pub hoge.mp4
2018/02/23 17:35:52 Your password: g5kb38
...
  • 受信側
$ ftrans recv -p g5kb38

もしくは

  • 受信側
$ ftrans recv
2018/02/23 17:35:52 Your password: tdZ5af
...
  • 送信側
$ ftrans send -p tdZ5af .vimrc ~/.ssh/id_rsa_hoge.pub hoge.mp4

これだけでファイルを転送できます。転送したファイルは受信側のカレントディレクトリに置かれます。
もちろん"-p"オプションで自由にパスワードを指定することもできます。

開発経緯

 最近、EC2やGCEなどのインターネット経由で接続するインスタンスや、Dockerなどの仮想マシン環境を利用する機会が増えています。こうした環境にdotfilesやSSH公開鍵などを転送する際に利用したい転送ツールがなかったためです。(今まではscpコマンド、Dropbox、gist等で対処していましたがそれぞれ使いづらいと思う箇所がありました。)
 似たようなものにmagic-wormholeというものがありますが、Pythonやその他のライブラリのインストールが必要だったりするので、即インストールして使える手軽さがありませんでした。
 また、ネットワークの勉強も兼ねてP2Pを自分で実装してみたいというのもありました。

なぜP2P?

 一番の理由はP2Pを実現するために最低限必要なシグナリングサーバとSTUNサーバが前者はただのWebサーバ、後者はGoogleが提供してくれているものがあるのでサーバ管理コストが低くて済みます。特にファイル転送のような広い帯域が必要なものを作ろうとすると尚更です。
 また、サーバクライアント型で実現する場合はサーバ側が常にネットワークに対してポート開放されています。一方で,
P2Pでは双方がインターネットに対してポート開放できない環境である可能性が高いです。私が今回利用したような一般的な手法を用いれば接続できる環境もありますが、できない環境もそれなりにあります。(例: ホワイトリスト方式、プロキシ環境、UDPが不可、symmetric NATなど)
 production-readyな製品を作るにはそのような環境でもなるべく接続できるようにするためにTURNサーバを設置したり色々な努力をする必要がありますが、個人プロジェクトでP2Pをしたい場合はある程度のところで諦めがつくので導入のハードルが低いです。
 

仕組み

ftransで利用するためのP2Pライブラリとしてgo-easyp2pを作成しました。
ftransはまず、WebSocketでシグナリングサーバに接続します。サーバ側はパスワードが一致した二つのクライアントのP2P接続用の情報(WebRTCで言うところのLocalDescription)を受信して反対側に転送しています。

go-easyp2pはWebRTCの接続方式を真似ています。

  • UDPでポートをListen
  • STUNサーバに接続し、パブリックIPアドレスとポートを取得
  • ネットワークインターフェースからIPアドレスを取得
  • TLS用のサーバ証明書およびクライアント証明書とパスワードを生成
  • 以上の情報からLocal Descriptionを生成し、シグナリングサーバ経由で送信
  • 通信相手から受け取ったDescriptionを利用して接続試行(UDPホールパンチング)
  • 接続が完了するとTLSの証明書とパスワードで接続を認証する

パケットの再送等を自分で実装するのは大変だったためµtp(Micro Transport Protocol)というBitTorrentが開発、利用しているものを利用していましたがdepcrecatedになっていたのでquic-goに移行しました。QUICはGoogleが主に開発しており、標準化も進んでいるUDPベースのHTTP通信プロトコルです。

今後の展望

  • シグナリングサーバとの通信にもE2EEを導入
  • ディレクトリ転送のサポート(なぜサポートしていない・・・)
  • TURNサーバのサポート
  • ライブラリ化し、GUIソフトやモバイル向けアプリの開発
  • なぜか接続確立が遅いので高速化
  • Go ReportをA+に・・・
  • 仲間内でゲームサーバを建てたりする時に使えそうなReverse Proxy over P2Pの構築

まとめなど

  • P2Pは曲者だが役に立つ(575ではない)
  • もう少し技術的な詳細はいずれどこかで・・・
  • 以前調べた時は見当たらなかったのですが気づいたらlibp2pというプロジェクトが誕生して開発が行われています。まだ開発途中で実用段階ではありませんが、このプロジェクトに貢献してみたいなぁ、と思っています