P2P技術とその応用
P2P技術とNAT穴あけについて解説。NATの種類やSTUN/TURNを用いたNAT越えの仕組みを紹介します。
一般的な動画ライブ配信は、通常 rtsp や hls プロトコルを用いて行われます。この方式では、すべてのトラフィックが中央サーバーから送出され、CDN(Content Delivery Network) を経由して各端末に配信されるという特徴があります。
この方式は多くの視聴者に適していますが、サーバーの帯域幅コストが高くなります。
コスト削減のため、P2P技術を活用した音声・動画ライブ配信が登場し、現在ではオンラインチャット(webRTC)やネットワークカメラなどの分野で広く利用されています。
では、P2P技術とは何でしょうか?また、この技術はどのように動画ライブ配信に応用されるのでしょうか?
対等ネットワーク(peer-to-peer、略称P2P)は、中央サーバーを持たず、ユーザー群(peers)が情報を交換するインターネットアーキテクチャです。従来のネットワーク伝送におけるノードを減らし、データ損失のリスクを低減する役割を果たします。中央サーバーを持つ集中型ネットワークシステムとは異なり、対等ネットワークでは各クライアントがノードであると同時にサーバーの機能も持ち、どのノードも他のノードを直接見つけることはできず、ユーザー群を通じて情報交換を行う必要があります。
Gitと同じような発想ですね。
では、相手のIPアドレスが分かれば直接接続できるのでしょうか?
NPTv6(Network Prefix Translation for IPv6) を設定していないIPv6ネットワークでは、相手のIPv6アドレスが分かれば直接接続できます。IPv6アドレスは通常グローバルに一意であるため、各デバイスはインターネット上でグローバルに到達可能なIPv6アドレスを直接持つことができます。
しかし、IPv4ネットワーク環境では、NAT(Network Address Translation) が存在するため、直接接続することはできません。
NAT技術は、IPv4アドレス枯渇問題を解決するために考案されました。IPv4アドレスは32ビットの2進数で、表現可能なアドレス数は2の32乗、すなわち 4294967296 個です。しかし、そのうちの一部はプライベートアドレス、ブロードキャストアドレス、マルチキャストアドレスなど特別な用途に予約されているため、実際に利用可能なIPv4アドレス数はこれより少なくなります。さらに、IPv4アドレスの割り当ては均一ではなく、地域や組織によって割り当て数に差があるため、IPv4アドレス枯渇問題が生じています。
NAT技術は、内部ネットワークのプライベートIPアドレスをパブリックIPアドレスに変換することで、複数の内部ホストが1つのパブリックIPアドレスを共有してインターネットにアクセスできるようにし、IPv4アドレス枯渇問題を解決します。また、NAT技術は内部ネットワークのIPアドレスを隠蔽し、外部ネットワークから内部ネットワークへの直接アクセスを防ぐため、ネットワークセキュリティを向上させることもできます。
NATには 3つのタイプがあります:
- スタティックNAT(静的NAT):静的な1対1のアドレス変換。マッピング関係を手動で設定する必要があり、ネットワークトポロジが変更されると設定の更新が必要となるため、管理と保守が困難です。通常は企業内部ネットワークで使用されます。
| 内線IP | 外線IP |
|---|---|
| 192.168.1.55 | 219.152.168.222 |
| 192.168.1.59 | 219.152.168.223 |
| 192.168.1.155 | 219.152.168.224 |
-
ダイナミックNAT(
Pooled NAT):動的アドレスプール変換。内部ネットワークのプライベートIPアドレスを変換する際に、アドレスプールからパブリックIPアドレスを選択し、対応関係にはリース期間の制限があります。 内線IPと外線IPのマッピングは1対1で、静的NATとの違いはマッピング関係が動的に変化することです。 -
NAPT(
Network Address Port Translation、ポートアドレス変換):特殊な動的NATの一種で、内部ホストのIPアドレスをパブリックIPアドレスにマッピングするだけでなく、内部ホストのポート番号をパブリックIPアドレスの異なるポート番号にマッピングすることで、複数の内部ホストが1つのパブリックIPアドレスを共有して外部ネットワークにアクセスできるようにします。
| 内線IP | 外線IP |
|---|---|
| 192.168.1.55:5555 | 219.152.168.222:9200 |
| 192.168.1.59:80 | 219.152.168.223:9201 |
| 192.168.1.155:4456 | 219.152.168.224:9202 |
家庭用ルーターは通常NAPTを使用し、複雑なネットワークを持つ大企業の内部ネットワークでは Static NAT や Dynamic NAT が使用されます。本記事で議論するNATは3番目のNAPTです。
3番目のNAPTはさらに、コーン型NATと対称型NAT(Symmetric NAT)に分類できます。コーン型NATはさらに Full Cone NAT、Address Restricted Cone NAT、Port Restricted Cone NAT に細分化され、それぞれ外部リクエストへの応答方法が異なります。
Full Cone NAT(フルコーン型)
内部アドレス(iAddr:iPort)が外部アドレス(eAddr:ePort)にマッピングされると、iAddr:iPort から送信されるすべてのデータパケットは eAddr:ePort を経由して外部へ送信されます。任意の外部ホストは、eAddr:ePort にデータパケットを送信することで、NATデバイス内部の iAddr:iPort ホストに到達できます。
Address Restricted Cone NAT(アドレス制限型)
フルコーンNATにIPアドレスの制限を加えたものです。内部アドレスと通信したことのある外部アドレスからのみメッセージを送信できます。
Port Restricted Cone NAT(ポート制限型)
アドレス制限型NATにポートの制限を加えたものです。特定の外部アドレスの特定のポートからのみメッセージを送信できます。
Symmetric NAT(対称型)
以下のコードを使用して、Chromeブラウザで現在のネットワークのNAPTタイプを検出できます:
// parseCandidate from https://github.com/fippo/sdpfunction parseCandidate(line) { var parts; // Parse both variants. if (line.indexOf('a=candidate:') === 0) { parts = line.substring(12).split(' '); } else { parts = line.substring(10).split(' '); }
var candidate = { foundation: parts[0], component: parts[1], protocol: parts[2].toLowerCase(), priority: parseInt(parts[3], 10), ip: parts[4], port: parseInt(parts[5], 10), // skip parts[6] == 'typ' type: parts[7] };
for (var i = 8; i < parts.length; i += 2) { switch (parts[i]) { case 'raddr': candidate.relatedAddress = parts[i + 1]; break; case 'rport': candidate.relatedPort = parseInt(parts[i + 1], 10); break; case 'tcptype': candidate.tcpType = parts[i + 1]; break; default: // Unknown extensions are silently ignored. break; } } return candidate;};
var candidates = {};var pc = new RTCPeerConnection({iceServers: [ {urls: 'stun:stun1.l.google.com:19302'}, {urls: 'stun:stun2.l.google.com:19302'}]});pc.createDataChannel("foo");pc.onicecandidate = function(e) { if (e.candidate && e.candidate.candidate.indexOf('srflx') !== -1) { var cand = parseCandidate(e.candidate.candidate); if (!candidates[cand.relatedPort]) candidates[cand.relatedPort] = []; candidates[cand.relatedPort].push(cand.port); } else if (!e.candidate) { if (Object.keys(candidates).length === 1) { var ports = candidates[Object.keys(candidates)[0]]; console.log(ports.length === 1 ? 'normal nat' : 'symmetric nat'); } }};pc.createOffer().then(offer => pc.setLocalDescription(offer))IPデータパケットがNATデバイス(ルーターなど)を通過する際、NATは送信元IPアドレスと宛先IPアドレスを書き換え、同一内部ネットワーク内の異なるホストがパブリックIPアドレスを共有できるようにします。
NATの仕組み
上の図のように、192.168.100.3 のホストが 209.131.36.158 の80番ポートにHTTPリクエストを送信すると、NATは保持しているマッピングテーブルに基づいて、該当ホストに対応する外部IPアドレスとポート(145.12.131.7:6282)を特定し、IPデータパケットのSourceフィールドをこのアドレスに書き換えて www.yahoo.com に送信します。Yahooサーバーがレスポンスを送信する際、Dest は 145.12.131.7:6282 となり、NATはそれを 192.168.100.3 の 3855 ポートに転送します。
しかし、外部のホストが 192.168.100.3 に能動的にアクセスしようとしても、内部ネットワークの 192.168.100.3 には到達できません。たとえそのホストがこちらのパブリックIPを知っていたとしても、能動的に接続を確立しようとすると、NATはそのアドレスとローカルネットワークアドレスとのマッピングを持たないため、該当データパケットはNATデバイスによって破棄されます。
NAT技術は非常に広く使用されていますが、いくつかの欠点もあります:
- NATデバイスは送受信するデータパケットの書き換え(IPアドレス変換、チェックサムの再計算)を行う必要があり、この操作によりネットワークデータの転送速度が低下します。
- NATデバイスのポートエージング問題により、接続中のデバイスが異常に切断される可能性があります。NATデバイスはポートマッピングテーブルを維持する必要がありますが、ハードウェアリソースには限りがあるため、一部のNATデバイスは定期的に一部の接続を切断します。
- 一部のネットワークプロトコルはNATデバイスを通過できず、2台のデバイス間の直接接続が困難になります。そのため、NAT穴あけ技術が登場しました。
NAT穴あけは、異なるプライベートネットワークに存在する2台のデバイス間で直接通信を確立するための技術です。NAT穴あけは通常UDPプロトコルを使用するため、UDP穴あけとも呼ばれます。以下がUDP穴あけの基本的な流れです:
- デバイスAとデバイスBはそれぞれ異なるプライベートネットワークにあり、直接通信はできません。
- デバイスAがデバイスBにUDPデータパケットを送信します。このパケットにはデバイスAのIPアドレスとポート番号が含まれています。
- デバイスBはこのパケットを受信すると、デバイスAのIPアドレスとポート番号を記録し、デバイスAにUDPデータパケットを送信します。このパケットにはデバイスBのIPアドレスとポート番号が含まれています。
- デバイスAはこのパケットを受信すると、デバイスBのIPアドレスとポート番号を記録します。
- これでデバイスAとデバイスBは互いのIPアドレスとポート番号を把握し、これらの情報を通じて直接UDP通信が可能になります。
Server S 18.181.0.31:5678 | | +----------------------+----------------------+ | | NAT A NAT B155.99.25.11:62000 138.76.29.7:31000 | | | | Client A Client B192.168.0.100:1234 10.1.1.3:1234では問題です。デバイスAがデバイスBにUDPデータパケットを送信するとき、デバイスAはどのようにしてデバイスBのIPアドレスとポート番号を知るのでしょうか?そのためには仲介サーバーが必要です。この仲介サーバーは、パブリックサーバーまたは STUNサーバー です。
STUN(Session Traversal Utilities for NAT)は、ネットワークアドレス変換(NAT)を透過するためのプロトコルであり、P2P通信における穴あけ操作に一般的に使用されます。P2P穴あけは、中間サーバーを介さずに、NAT配下にある2つのデバイスが直接通信できるようにすることを目的としています。

Googleが提供する STUNサーバー は、webRTCのデモでテストすることができます。
NATにはさまざまなタイプがあり、すべてのNATがNAT穴あけをサポートしているわけではありません。前述の Symmetric NAT では穴あけができません。UDP穴あけの成功率は約60%で、穴あけが失敗した場合は TURN サーバーを利用したリレーモードが使用されます。
Traversal Using Relays around NAT (TURN) は、TURNサーバーへの接続を開き、そのサーバーを介してすべての情報を中継することで、対称NATの制限を回避することを目的としています。TURN サーバーへの接続を作成し、すべてのピアにサーバーへパケットを送信するよう指示し、サーバーがそれを転送します。これには明らかにオーバーヘッドが伴うため、他に選択肢がない場合にのみ使用されます。

TURN サーバーを介した中継モードは、一般的に relay と呼ばれます。
参考資料