概要#
BitTorrent ダウンロードグループ内で他のノードを見つけるために、クライアントはトラッカーに自己宣言リクエストを送信します。このリクエストは HTTP プロトコルを使用し、info_hash、key、peer_id、port、downloaded、left、uploaded、compact などのいくつかのパラメータを含みます。トラッカーはクライアントにノード(ホストとポート)のセットとその他の情報を返します。このリクエストと応答は非常に短いです。TCP プロトコルを使用しているため、リクエストを送信する前に接続を開き、リクエストが完了した後に接続を閉じる必要があり、これにより追加のオーバーヘッドが発生します。
システムオーバーヘッド#
HTTP プロトコルを使用すると、大きなシステムオーバーヘッドが発生します。なぜなら、イーサネット層、IP 層、TCP 層、HTTP 層のすべてでオーバーヘッドがあり、リクエストと応答が 50 ノードを含む場合、約 10 パケットを使用し、合計で約 1206 バイトを消費します。UDP ベースのプロトコルを使用することで、このオーバーヘッドを大幅に削減できます。本稿で提案するプロトコルは、わずか 4 パケットと約 618 バイトで、トラフィックを 50% 削減します。クライアントにとっては毎時 1KB の節約は重要ではありませんが、百万ノードにサービスを提供するトラッカーにとっては、トラフィックを 50% 削減することは非常に重要です。さらに、UDP ベースのバイナリプロトコルは、複雑なパーサーや接続処理を必要とせず、トラッカーのコードの複雑性を低減し、パフォーマンスを向上させます。
UDP 接続 / 偽装攻撃#
理想的な状況では、通信を完了するのに 2 パケットだけが必要です。UDP プロトコルを使用する場合、UDP プロトコルはコネクションレスであるため、ソースアドレスの偽造が発生する可能性があります。したがって、トラッカーは偽装行為が発生しないように対策を講じる必要があります。このような事態を避けるために、トラッカー(クライアントと他のピア間のデータ共有を調整するためのサーバープログラム)はいくつかの措置を講じます。
クライアントがトラッカーにリクエストを送信すると、トラッカーはランダムな数(connection_id)を生成し、それをクライアントに送信します。クライアントが再度トラッカーにリクエストを送信する際には、この connection_id を持参する必要があります。これにより、トラッカーはリクエストの出所が正当であるかを検証できます。クライアントがソースアドレスを偽造した場合、トラッカーから返された connection_id を受け取ることはできず、リクエストはトラッカーによって拒否されます。
connection_id がクライアントによって推測されないようにするために、トラッカーは TCP ハンドシェイクや syn-cookie のような方法を採用し、サーバー側で connection_id を保存し、特定の条件下でのみクライアントに返すことができます。1 つの connection_id は複数のリクエストに使用でき、クライアントは connection_id を受け取った後 1 分以内にそれを使用できます。トラッカーは connection_id を発行した後 2 分以内にそれを受け入れ、クライアントからのリクエストがこの connection_id と一致するかを検証する必要があります。
タイムアウト#
UDP はデータパケットの信頼性のある伝送を保証しないプロトコルであり、これは伝送中にパケットが失われた場合、UDP は自動的に再送信しないことを意味します。代わりに、UDP を使用するアプリケーションは、失われたパケットを処理し、必要に応じて再送信する責任があります。
失われたパケットを処理するために、クライアントはリクエストを送信した後、サーバーの応答を待つ必要があります。15 * 2 ^ n 秒後に応答が受信できない場合、クライアントはリクエストを再送信する必要があります。再送信の間隔は 15 秒から始まり、各試行ごとに倍増し、最大 8 回試行します。つまり、3840 秒です。
接続 ID が期限切れの場合、リクエストを再送信する前に新しい接続 ID を再リクエストする必要があることに注意してください。これにより、リクエストが正しく認証され、サーバーによって処理されることが保証されます。
サンプル#
通常のブロードキャスト:
t = 0: connect request
t = 1: connect response
t = 2: announce request
t = 3: announce response
接続タイムアウト:
t = 0: connect request
t = 15: connect request
t = 45: connect request
t = 105: connect request
etc
ブロードキャストタイムアウト:
t = 0:
t = 0: connect request
t = 1: connect response
t = 2: announce request
t = 17: announce request
t = 47: announce request
t = 107: connect request (because connection ID expired)
t = 227: connect request
etc
複数リクエスト:
t = 0: connect request
t = 1: connect response
t = 2: announce request
t = 3: announce response
t = 4: announce request
t = 5: announce response
t = 60: announce request
t = 61: announce response
t = 62: connect request
t = 63: connect response
t = 64: announce request
t = 64: scrape request
t = 64: scrape request
t = 64: announce request
t = 65: announce response
t = 66: announce response
t = 67: scrape response
t = 68: scrape response
UDP トラッカープロトコル#
すべての数値(整数または浮動小数点数)は、送信時にネットワークバイトオーダー(ビッグエンディアン)でエンコードされるべきです。また、各データパケットが確定したサイズを持つことを期待すべきではありません。将来的に新機能を追加することでデータパケットのサイズが増加する可能性があるためです。
接続#
アナウンスまたはスクレイピングを行う前に、接続 ID を取得する必要があります。
- ランダムなトランザクション ID を選択します。
- 接続リクエスト構造を埋めます。
- パケットを送信します。
アナウンスまたはスクレイピング操作を行う前に、接続 ID を取得するプロセスは次のとおりです。
- ランダムにトランザクション ID を生成し、ユニークな識別子として使用します。
- 接続リクエスト構造体を埋めます。
- リクエストデータパケットを送信します。
接続リクエスト:
Offset Size Name Value
0 64-bit integer protocol_id 0x41727101980 // magic constant
8 32-bit integer action 0 // connect
12 32-bit integer transaction_id
16
- データパケットを受信します。
- その長さが少なくとも 16 バイトであるかを確認します。
- 受信したデータパケット内のトランザクション ID が、以前に送信したリクエストで選択した ID と一致するかを確認します。
- データパケット内の操作が「connect」であるかを確認します。
- データパケット内の接続 ID をローカルに保存し、後続の操作に使用します。
接続応答:
Offset Size Name Value
0 32-bit integer action 0 // connect
4 32-bit integer transaction_id
8 64-bit integer connection_id
16
アナウンス#
- システムからランダムにトランザクション ID を選択します。
- アナウンスリクエスト構造を埋めます。
- データパケットを送信します。
IPv4 アナウンスリクエスト:
Offset Size Name Value
0 64-bit integer connection_id
8 32-bit integer action 1 // announce
12 32-bit integer transaction_id
16 20-byte string info_hash
36 20-byte string peer_id
56 64-bit integer downloaded
64 64-bit integer left
72 64-bit integer uploaded
80 32-bit integer event 0 // 0: none; 1: completed; 2: started; 3: stopped
84 32-bit integer IP address 0 // default
88 32-bit integer key
92 32-bit integer num_want -1 // default
96 16-bit integer port
98
- データパケットを受信します。
- データパケットの長さが少なくとも 20 バイトであるかを確認します。
- データパケット内のトランザクション ID が、以前に選択したトランザクション ID と等しいかを確認し、受信したのが以前に送信したアナウンスリクエストの応答であることを確認します。
- データパケット内の操作が「announce」であるかを確認します。
- 時間間隔のチェックを行います。前回のアナウンスリクエストからの時間が一定の時間間隔(interval 秒)に満たない場合、アナウンスリクエストを行わず、そうでない場合はアナウンスリクエストを続行するか、イベントがトリガーされるのを待って再度リクエストします。
ほとんどのトラッカーは、特定の状況でのみ IP アドレスフィールドを考慮します。
IPv4 アナウンスリクエスト:
Offset Size Name Value
0 32-bit integer action 1 // announce
4 32-bit integer transaction_id
8 32-bit integer interval
12 32-bit integer leechers
16 32-bit integer seeders
20 + 6 * n 32-bit integer IP address
24 + 6 * n 16-bit integer TCP port
20 + 6 * N
IPv6#
IPv6 と IPv4 のプロトコル構造の違いと、使用時の適合方法について説明します。IPv6 と IPv4 のメッセージ形式は基本的に同じですが、応答メッセージでは <IP アドレス、TCP ポート> のステップサイズが 6 バイトから 18 バイトに変わります。また、リクエストメッセージ内の IP アドレスフィールドは依然として 32 ビット幅であり、IPv6 には使用できず、常に 0 に設定されるべきです。
さらに、この段落では適合の方法についても言及しています。UDP パケットのアドレスファミリに基づいて使用する形式を決定します。つまり、IPv4 アドレスからのパケットは IPv4 形式を使用し、IPv6 アドレスからのパケットは IPv6 形式を使用します。最後に、ホスト名を IPv4 および IPv6 に解決し、両方のトランスミッションで同じキーを使用するクライアントに対して、トラッカーが両方のアナウンスを正確にマッチさせ、正確な統計を実現できるようにする必要があります。
スクレイピング#
最大で約 74 個のトレントファイルの情報を同時に取得できます。このプロトコルでは完全なデータスクレイピングを行うことはできません。
- ランダムにトランザクション ID を選択します。
- スクレイピングリクエスト構造を埋めます。
- データパケットを送信します。
スクレイピングリクエスト:
Offset Size Name Value
0 64-bit integer connection_id
8 32-bit integer action 2 // scrape
12 32-bit integer transaction_id
16 + 20 * n 20-byte string info_hash
16 + 20 * N
- データパケットを受信します。
- データパケットの長さが少なくとも 8 バイトであるかを確認します。
- このデータパケット内のトランザクション ID が、以前に選択したものと同じであるかを確認します。
- データパケット内の操作が「scrape」であるかを確認します。
スクレイピング応答:
Offset Size Name Value
0 32-bit integer action 2 // scrape
4 32-bit integer transaction_id
8 + 12 * n 32-bit integer seeders
12 + 12 * n 32-bit integer completed
16 + 12 * n 32-bit integer leechers
8 + 12 * N
トラッカーがエラーに遭遇した場合、エラーデータパケットを送信する可能性があります。
- データパケットを受信します。
- データパケットの長さが少なくとも 8 バイトであるかを確認します。
- このデータパケット内のトランザクション ID が、以前に選択したものと同じであるかを確認します。
エラー#
エラー応答:
Offset Size Name Value
0 32-bit integer action 3 // error
4 32-bit integer transaction_id
8 string message
既存のインスタンス#
IMFile、Azureus、libtorrent、opentracker、XBT Client および XBT Tracker はこのプロトコルをサポートしています。
プラグイン#
プロトコルの互換性を維持するために、一般的にプロトコルには拡張ビットやバージョンフィールドを含めません。クライアントとトラッカーもデータパケットのサイズを仮定すべきではありません。これにより、互換性を損なうことなく追加のフィールドを追加できます。言い換えれば、固定データパケットの長さと形式を避けることで、プロトコルはより容易に拡張および更新でき、後方互換性を維持できます。
まとめ#
UDP トラッカープロトコルは、ピアツーピアファイル共有プロトコル BitTorrent におけるトラッカー通信プロトコルです。これは UDP プロトコルに基づいており、HTTP プロトコルに比べてデータをより迅速に転送でき、さらに優れた拡張性と効率性を持っています。
UDP トラッカープロトコルを使用することで、BitTorrent クライアントはトラッカーにリクエストを送信し、トレントに接続している他のユーザーのリストを取得できます。これらのユーザーは、ダウンローダーがファイルブロックを提供するのを助け、ダウンロード速度を向上させることができます。さらに、トラッカーは特定のトレントに関する統計情報(アップロードおよびダウンロード速度、残り時間など)を提供することもできます。
UDP トラッカープロトコルには多くの利点がありますが、その本質的な無状態性のために、場合によってはリクエストや応答が失われるなどの問題が発生することがあります。これらの問題を解決するために、多くの BitTorrent クライアントは HTTP トラッカープロトコルもサポートしており、信頼性を向上させるために 2 つの異なるトラッカー通信プロトコルを同時に使用しています。