P2Pファイル交換ソフトを作ってみる(2)

第一回:http://d.hatena.ne.jp/susumu/20041205#p1
前回から3週間近く経過してしまいました。とにかく続きです。出来るところから、少しずつ設計を考えていきます。
開発の流れですが、まず1対1のP2Pを実装し、次に多対多のP2Pを考えていきます。使用する言語はJavaで、お手軽楽チンネットワークプログラミングと行きたいと思います。

提供するサービス

ユーザーに提供するサービスを挙げます。

  • 1対1のP2Pファイル交換
    • 自分の所有しているファイルを相手に送信する
    • 相手が所有しているファイルを自分へ転送する
      • ファイル名のリストを要求する
  • ユーザー認証
    • 相手のファイルを閲覧する権利(無制限にOK,許可されればOK,駄目)
    • 相手のファイルを受信する権利(無制限にOK,許可されればOK,駄目)
    • 自分のファイルを相手に送信する権利(無制限にOK,許可されればOK,駄目)
  • ちょっとしたセキュリティ
    • 認証中にやりとりするデータが外に漏れたら嫌
ファイル交換

当たり前ですが、ファイル交換機能を組み込みます。
ファイル交換機能は、ファイルを相手に送る機能と、相手からファイルを受け取る機能の両方を含みます。相手にファイルを送る場合、ファイル名+ファイルデータを相手に送信します。また、相手からファイルを受け取る場合は、欲しいファイル名を相手に送信します。相手が公開しているファイルを知るために、ファイル名のリストを相手から受け取る必要があります。

ユーザー認証

ユーザー認証機能を組み込みます。
なぜユーザー認証機能が必要かというと、今回作成するプログラムは、匿名ファイル共有ではないことと、ファイルを強制的に送りつける機能があるためです。もしユーザー認証機能がないと、自分の知らない相手からファイルをがんがん送りつけられてしまうことになり、これは軽く泣けます。

セキュリティ

通信経路上を暗号化します。
ユーザー認証を行うときに、認証データを外に漏らしたくないためと、パケットの解析をされたくないためです。

各ノード(ピア)の機能

各ノードの機能について考えます。

通信について

各ノードは、サーバー機能およびクライアント機能の両方を有します。
ノードは起動後、サーバースレッドを開始させ、ServerSocketを用いてある定められたポート番号で待ちます。相手ノードからサーバー機能にアクセスされた場合、相手ノードへのサービスを開始します。このとき、1対1のP2Pなので、相手ノードとのコネクションが切れるまでは、他からのアクセスは受け付けません。また、サーバーがコネクションを受け付けたとき、折り返し、自身のクライアント機能を用いて、相手のサーバーに接続します。
また、ノードは、ユーザからの命令を処理します。特に通信を必要とする場合、クライアント機能を用いて実現されます。クライアント機能は、上記の命令およびデータをサーバーに送る機能から成り立ちます。
サーバー機能およびクライアント機能は、常時接続されており、セッションが保たれています。プログラムの簡単化のため、命令の発行はクライアント機能からしか行えなず、命令の解釈はサーバーからしか行えないようにします。ですから、ピアAとピアBが通信を開始するときは、A→BというチャネルとB→Aというチャネルの二つのチャネルが成立することになります。

セキュリティについて

通信を暗号化することで、ちょっとしたセキュリティをがんばります。具体的にはSSLを用いる。これで、通信の安全化を行う。
SSLを用いるときは、自己証明(?)を使います。ですから、これ自身を用いて認証することは出来ず、あくまで通信の暗号化のためだけに用います。

ユーザー認証

ユーザー認証は、ID,ID+パスフレーズ、IP+パスフレーズ、IPのみのいずれかで行います。これは、ユーザー認証が複数あるのは、認証の問題と言うよりも、ピア管理を効率化するためのものです。
IDのみで認証する場合、たとえばguestのようなものを実現します。またID+パスフレーズの場合だと、ある特定の個人やグループに対して認証を行えます。IP+パスフレーズは、自分ひとりで使う場合に、家のマシンと外のマシンとかで使えます。IPのみも同様だけど、IP偽装が怖いのであまり使いたくはありませんが、ある特殊な場面においては便利かもです。

通信プロトコル

通信は、サーバ接続時、要求発行+データ通信時の2つに分けられます。それぞれ、プロトコルを決めます。

サーバー接続時

サーバ接続時は、こうなります。

 Client >>- P2PSpecific(32) AuthTYPE(1) DATA(64) ->> Server
 Client <<- P2PSpecific(32) YorN(1)              <<- Server

P2PSpecific(32)は、32バイトの固定的なバイナリ列。このバイナリ列が違うと、Serverからはリジェクトされます。自分がP2Pソフトであるという証明書のようなものを想定しいます。SSLも自己証明は行えるけど、自己証明じゃ何の証明にもならんから、もう一回自己証明をするってなことです。趣味の問題で、あんまり意味はありません。
AuthTYPE(1)は、1バイトで、ユーザー認証の種類を表す。1バイトも要らないけどね。
DATA(64)は、64バイトで認証のためのデータ。AuthTYPEによって、内容は変わってきます。
YorN(1)は、認証成功か失敗かを表すだけ。成功していたら、そのままセッションは保たれて、失敗してたら、セッションはサーバー側から閉じられる。

データ通信時

次に、データ通信時の話。

 Client >>- P2PSpecific(32) ReqTYPE(1) DATA(n) ->> Server
 Client <<- P2PSpecific(32) YorN(1)            <<- Server
 if( YorN(1) == yes ){
     switch( ReqTYPE ){
     case ReqGetFileList:
         Client <<- FileNum(4) [ FileNameLen(1) FileName(n) ] <<- Server
     case ReqGetFile:
         Client <<- DataLen(8) FileData(n) <<- Server
     case ReqPutFile:
         Client >>- DataLen(8) FileData(n) ->> Server
     }
 }

ReqTYPE(1)は、サーバーに対する要求の種類を表します。
DATA(n)は、要求の種類に応じたデータです。ReqGetFile、ReqPutFileについてファイル名の指定や、ファイル情報に使用されます。
こんなところで、今回は疲れたので終了します。
次回は、実装です。ソースコードを貼り付けるだけという凄く簡単な何か。