検証
ネットワーク管理者のためのGnutella入門

4.Gnutellaの通信メカニズム(1)

井口圭一/デジタルアドバンテージ
2000/09/15

 GnutellaNetシステムでは、Napsterとは違って、中央集中的なファイル名の管理を行なうサーバ コンピュータは存在しない。代わりに、それぞれのGnutellaプログラムが動作しているシステム自身が、クライアントだけでなく、同時にサーバの役割もはたすようになっている。そのため初めてGnutellaプログラムを起動したときには、どのホストに接続してよいかが分からないので、ファイルは何も表示されないし、検索もできない。

 GnutellaNetに接続するためには、まずは掲示板やチャット、メールなどを使って、誰かほかのGnutellaユーザーから、IPアドレスとポート番号を教えてもらう必要がある。この情報をプログラムに入力すると、それを基にして他のホストとの通信が開始され、それらのホストに関する情報を入手することができる。この結果、急速に多数のホストが見えるようになる。後はそれらのホスト情報を元にしてコマンドをやり取りし、ファイルを検索したり、ダウンロードしたりする。

 GnutellaNetの特徴は、中核となるサーバがいないという点にあるが、それではどのようにしてファイル検索などが行なわれているのであろうか。簡単に言うと、

自分の知っている他のホストにコマンドを投げかけ(依頼し)、その応答を待つ

というふうにして動作している。つまり、他のホストを見つける場合や、ファイル名検索を行なう場合、自分の知っている(1台から4台程度の)ホストに対して検索コマンドを送るのである。するとそのコマンドを受け取ったホストは、自分の持っているファイル一覧から検索して結果を返すとともに、さらにそのコマンドを他のホストへも中継している。そして、それらの結果も同じように中継されながら返ってくるのである。いってみれば、バケツリレー式(ねずみ講式?)にコマンドやその結果を送り、中継しているわけである。

 ただし単純にこの方法をインプリメントしてしまうと、どこまでもコマンドが送られて際限がない。そこで中継を止めるための歯止めとして、各コマンドが通過できる最大ホスト数が決められている。コマンドを1回中継することを「ホップ」するというが、デフォルトでは7ホップまでに制限している(つまり最大7回中継されることになる)。また、各ホストから接続するホスト数も(デフォルトでは)多くても4台に制限されているので、結局、1つのホストからは最大21844台(=4+16+64+256+1024+4096+16384台)のホストと通信できることになる。もっとも実際にはホストの大半は重複しているであろうから(必ずどこかにループ構造などがあるから)、本当にユニークなホストの台数はもっと少ないはずである。そこで、各コマンドにはユニークなID(GUID、Globally Unique IDentifier)を付けて、一度受けたコマンドは(GUIDが同じコマンドならば)、受け付けない(無視する)ようにしている。

 このようなコマンドの送信(および他ホストでの中継)は、ユーザーが検索を指示した場合には、常に行なわれている。そのため各ホストでは、自分のコマンドの送受信だけでなく、他のユーザーのコマンドの中継も行なうことになり、ネットワークのトラフィックはNapsterなどよりもかなり多くなっている(実際使ってみると分かるが、ほとんど常に何らかのパケットを送受信しているように見える)。Gnutellaでは一度検索した結果をキャッシュしておいて、ネットワーク全体のトラフィックを抑えるというようなことは行なわれていないが、これが逆に、GnutellaNetのダイナミズムを支えているともいえる。ユーザーはいつでも自由にGnutellaNetに参加したり離脱したりできるが、他のユーザーに影響を与えることはほとんどない。せいぜい、ダウンロードの途中で相手のマシンが止まってしまって、ダウンロードが失敗するくらいであろう。将来的には、何らかのキャッシュのような機構が組み込まれるかもしれないし、そこに大きなビジネス チャンスもあるようにも思えるが、少なくとも現状のGnutellaにはまだそのようなものは用意されていない。

Gnutellaプロトコルのコマンド

 さてそれでは、Gnutellaプロトコルについて詳しくみていくことにしよう。ただしすでに述べたように、Gnutellaの正式な(公式な)プロトコル仕様書というものは存在しない。あるのは、オリジナルのGnutellaプログラムのリバース エンジニアリングなどによって得られた、非公式なドキュメントだけである。以下では、それらのドキュメントや実際のプロトコルを解析した結果に基づいて説明する。

 Gnutellaプログラムでは、ユーザーが指定したアドレス番号やポート番号に基づいて、まずそのホストにTCPプロトコルで接続を開始する。そして、最初に

[送信]GNUTELLA CONNECT/0.4[LF][LF]   …… 将来は変わるかもしれない
[受信]GNUTELLA OK[LF][LF]

というオープニング メッセージをやり取りして、お互いの準備ができていることを確認している(最後の“/0.4”は、HTTPプロトコルのように、プロトコルのバージョンを表しているので、将来は変わるかもしれない)。このメッセージはASCIIテキスト形式で行なわれているが、これ以後やりとりされるメッセージは、すべてバイナリ形式になっている。次の表はコマンドのフォーマットを示したものである。

バイト位置 名前 意味
0〜15 GUID コマンドを識別するためのユニークなID番号
16 コマンド番号 コマンドを表す番号。例:Ping = 0x00、Pong = 0x01、など
17 TTL Time To Live。コマンドの有効生存時間
18 Hops コマンドのホップ回数(通過したホストの数)
19〜22 コマンド長 コマンドの長さを表すバイト数
23〜 データ コマンドの引数

Gnutellaのコマンド フォーマット

 GUID(Globally Unique IDentifier)は、一度処理したパケットを再度処理しないようにするためのIDである。もともとはWindowsプログラミングなどで使われる用語であり、いろいろなオブジェクトの一意性を確保するために使われる16bytes長のランダムな数値である。コマンドごとに異なるGUIDを付けることにより、どのホストから出されたどのコマンドであるかが分かるようになっている(たとえば同じ検索コマンドでも、GUIDが違えば、異なる検索コマンドと認識することができるし、同じ値ならば、どこかネットワーク上をループして再び届いたものである、と分かる)。コマンドを受け取った側のホストではこのGUIDをチェックして、もしすでに受け取ったことのあるコマンド(GUID)ならば無視し、そうでなければコマンドを処理して、さらにそのGUIDをしばらくの間内部に保持しておき、後で同じコマンドが重複して届くのを検出する。

 コマンド番号は、以下で説明するPingやPong、Queryなどを表す1byteのIDである。

 TTL(Time To Live)は、ネットワーク プロトコルなどでよく使われる用語であり、パケットの有効生存時間を表す(時間といっても、timeは秒とか分ではなく、「回数」という意味)。具体的には、パケットを1回中継するたびに1つずつ減らし、0になればそれ以上は中継せずにパケットは破棄される。TTLの初期値はデフォルトでは「7」なので([Config]オプションの[My TTL]で指定する)、7回は中継されるが、それより(ネットワーク的に)遠くのホストにはパケットは届かないことになる。

 それでは、TTLの初期値を7より大きくしたらより遠くのホストに届くようになるのかというと、そうではない。すでに述べたように、あまり大きくすると、GnutellaNet上の他のホストの負担も大きく増えるので、望ましくない。そこで、むやみにTTLを大きくしないように(もしくは何らかのミスでTTL値の大きなパケットが生成されてしまっても大丈夫なように)、一種の保護機構が用意されている。それが[Config]オプションの[Max TTL]である。この値よりもTTL値が大きなパケットを受け取った場合、TTLの値をMax TTL値に置き換えて中継操作を行なう。

 Hopsは、初期値が0で、パケットが中継されるたびに1ずつ増やされる。TTL値と逆であり、コマンドを発行したオリジナルのホストからのネットワーク的な距離(ホップ数)を表している(ただし、プログラム中では特に使われていない)。

 コマンド長とデータ部は、引数を持つコマンドなどに応じて、可変長のデータを付加するために使われる。

GnutellaNetへのログイン

 さてそれでは、各コマンドについて具体的にみていこう。まずはGnutellaNetへのログイン処理である。

GnutellaNetへのログイン
あるホストAがGnutellaNetへログインした場合の処理。まずPingコマンドを送ってほかのホストの情報(IPアドレス、ポート番号、ファイル情報など)を収集する。コマンドはお互いに接続されたホスト同士で順次中継されながら送られ、結果も同様に隣接するホスト同士で交換され、戻される。
  他のGnutellaホストへ接続する。
  Pingコマンドを送って、他のホストの情報を取得する。
  Pingコマンドを受け取ったホストは、TTLが1より大きく、かつ、まだ受け取ったことのないGUIDを持つコマンドならば、それをさらに別のホストへ中継する。
  受け取ったことのないGUIDを持つコマンドならば、Pong応答で、そのホストの情報や持っているファイルなどの情報を返す。
  中継されたPingコマンドを受け取った各ホストも、自ホストに関する情報をパケットの送信元へ送り返す。
  他のホストから受け取ったPong応答を、Pingコマンドの送信元へ返す。
  指定された数のホストの情報が得られなければ、どこか別のホストを見つけて以上のシーケンスを繰り返す。

 まずあるユーザーが、ホストA(図の左端)でGnutellaプログラムを起動して、ホストBのIPアドレスとポート番号の情報を入力したとする。すると以下のような流れを経て、他のサーバの情報を集める。

@ホストAは、ホストBに対して、TCPコネクションをオープンする。そしてお互いにオープニング メッセージをやり取りして、認証を行なう。
A他のノードを検索するために、Gnutellaプロトコルの「Ping」コマンドを送る。Pingコマンドには引数はなく、単にコマンド番号やGUID、TTL、Hopsしか含まれていない)。Pingコマンドを受け取ったホストは、送信側に「Pong(Pingへの応答)」コマンドで応答を返す。
B応答を返すと同時に、TTL値を1つ減らし、それが0でなければ、自分の接続しているホスト(C、D、E)へPingコマンドをそれぞれ中継する。ただし、すでに自分が受け取ったコマンドであれば(同じGUIDを持つパケットであれば)、応答を返したり、中継したりせずに、そのパケットを破棄する。
CPong応答には、そのホストのIPアドレス(この場合はホストBのIPアドレス)、ログオン接続コマンドを受け付けるためのTCPポート番号、公開しているファイルの総数、その総サイズ(Kbytes単位)の情報を格納しておく。これにより、Pongコマンドを送ってきたノードがアクティブであることが分かるとともに、どのくらいのファイルを公開しているかの情報が分かる。
DホストB以下の各ホスト(C、D、E)も同様にして、パケットをさらにほかのホストに中継するとともに、自分のホストに関する情報を上位のホスト(Pingコマンドを送ってきたホスト)へ送り返す。
EホストBは、他のホストから送られてきたPongコマンドを、元のホストAへ送り返す。もともとどこから送られきたコマンド パケットであるかは、GUIDを基にして、プログラムの内部で管理している。

 以上のようにしてログイン処理が行なわれ、ホストの一覧リストが得られるが、ホストの総数があらかじめ設定されている最低接続数(デフォルトでは4ホスト)よりも少なければ、その中から1台のホストを選んでFのように接続を行い、また@から繰り返す。

関連記事(Windows Server Insider)
  検証:ネットワーク管理者のためのNapster入門
 
  関連リンク
  Gnutellaのホームページ(Gnutella)
     

 INDEX
  [検証]ネットワーク管理者のためのGnutella入門
    1.Gnutellaとは何か?
    2.Gnutellaのインストールと実行 (1)
    3.Gnutellaのインストールと実行 (2)
  4.Gnutellaの通信メカニズム (1)
    5.Gnutellaの通信メカニズム (2)
 
 検証


Windows Server Insider フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Windows Server Insider 記事ランキング

本日 月間