接続 #
なぜWebRTCには接続用の専用サブシステムが必要なのでしょうか? #
現在導入されているほとんどのアプリケーションは、クライアント/サーバー接続を確立しています。クライアント/サーバー接続では、サーバーが安定した周知のトランスポートアドレスを持っている必要があります。クライアントはサーバーにコンタクトし、サーバーはそれに応答します。
WebRTCは、クライアント/サーバーモデルを使用せず、ピアツーピア(P2P)接続を確立します。P2P接続では、接続を作成するタスクが両方のピアに均等に分配されます。これは、WebRTCのトランスポートアドレス(IPとポート)は想定できず、セッション中に変更される可能性もあるためです。WebRTCはできる限りの情報を収集し、2つのWebRTCエージェント間の双方向通信を実現するために多大な努力をします。
しかし、ピアツーピアの接続を確立するのは難しいことです。これらのエージェントは、直接接続されていない異なるネットワークに存在する可能性があります。直接的な接続性が存在する場合でも、別の問題が発生することがあります。クライアントが異なるプロトコル (UDP <-> TCP) や、IPバージョン (IPv4 <-> IPv6) を利用している場合もあります。
このようにP2P接続の設定が難しいにもかかわらず、WebRTCには以下のような特徴があるため、従来のクライアント/サーバー技術よりも有利になります。
帯域幅コストの削減 #
メディアの通信はピア間で直接行われるため、メディアを中継するために別のサーバーを用意する必要がありません。
遅延の低減 #
通信は直接行われる方が速いです。ユーザーが全ての通信をサーバーを経由して行うと、通信速度が低下します。
安全なE2E通信 #
直接通信の方が安全です。ユーザーはサーバーを経由せずにデータを送信するので、ユーザーはサーバーがデータを解読しないことを信じる必要はありません。
どうやって使うの? #
上記のプロセスは、Interactive Connectivity Establishment (ICE)と呼ばれています。WebRTCよりも前のプロトコルです。
ICEは、2つのICE Agent間の通信に最適な方法を見つけようとするプロトコルです。各ICEエージェントは、到達可能な方法を公開しており、これを候補と呼びます。候補とは、基本的に、相手が到達できると考えられるエージェントのトランスポートアドレスです。ICEは、候補の中から最適な組み合わせを決定します。
ICEの実際のプロセスについては、本章の後半で詳しく説明します。WebRTC がネットワークにおけるどのような挙動を克服しようとしているかを理解すると、ICEが存在する理由を理解できます。
ネットワークの現実的な制約 #
ICEは、実世界のネットワークの制約を克服するためのものです。解決策を探る前に、実際の問題点について説明します。
同じネットワークにいない #
ほとんどの場合、相手の WebRTC エージェントは同じネットワーク内にいるとは限りません。典型的な通話は、通常、直接接続されていない異なるネットワークにある2つのWebRTC Agent間で行われます。
下の図は、公衆インターネットで接続された2つの異なるネットワークのグラフです。各ネットワークには2つのホストがあります。
同一ネットワーク内のホストは、非常に簡単に接続できます。 192.168.0.1
-> 192.168.0.2
の間の通信は簡単にできます。これらの2つのホストは、外部の助けを借りずにお互いに接続できます。
しかし、 ルーターB
を使っているホストは、 ルーターA
の後ろにあるものに直接アクセスする方法がありません。 ルーターA
の後ろにある 192.168.0.1
と ルーターB
の後ろにある同じIPの違いをどうやって見分けるのでしょうか?これらはプライベートIPです。 ルーターB
を使用しているホストは、 ルーターA
に直接トラフィックを送信できますが、リクエストはそこで終了します。ルーターA
はどのホストにメッセージを転送すべきか、どうやって知るのでしょうか?
プロトコルの制限 #
ネットワークによっては、UDP トラフィックを全く許可していなかったり、TCP を許可していない場合があります。ネットワークによっては、MTU(Maximum Transmission Unit)が非常に低い場合があります。このように、ネットワーク管理者が変更できる変数はたくさんあり、それが通信を困難にしています。
ファイアウォール/IDSルール #
また、「ディープ・パケット・インスペクション」やその他のインテリジェントなフィルタリングもあります。ネットワーク管理者の中には、すべてのパケットを処理しようとするソフトウェアを実行する人がいます。このようなソフトウェアはWebRTCを理解していないことが多く、WebRTCパケットをホワイトリストに載っていない任意のポートの不審なUDPパケットとして扱うなど、何をしていいかわからずブロックします。
NATマッピング #
NAT(Network Address Translation)マッピングは、WebRTCの接続性を実現する魔法です。これにより、WebRTCは全く異なるサブネットにいる2つのピアの通信を可能にし、上記の「同じネットワーク内にない」という問題に対処しています。新たな課題が生まれる一方で、そもそもNATマッピングがどのように機能するのかを説明しましょう。
NATマッピングは、リレーやプロキシ、サーバーを使用しません。ここでも、「エージェント1」と「エージェント2」がいて、それぞれ別のネットワークにいます。しかし、トラフィックは完全に通過しています。視覚的には次のようになります。
この通信を実現するために、NATマッピングを確立します。エージェント1は、ポート7000を使用して、エージェント2とのWebRTC接続を確立します。これにより、 192.168.0.1:7000
から 5.0.0.1:7000
へのバインディングが作成されます。これにより、エージェント2は、 5.0.0.1:7000
にパケットを送信することで、エージェント1に到達できるようになります。この例のようにNATマッピングを作成することは、ルータでポートフォワーディングを行うことの自動化版のようなものです。
NATマッピングの欠点は、マッピングの形式が一つではないこと(例:静的ポートフォワーディング)と、ネットワーク間で動作が一貫していないことです。ISPやハードウェアメーカーが異なる方法で行う場合もあります。場合によっては、ネットワーク管理者がこれを無効にしていることもあります。
ICEエージェントは、NATマッピングを作成したことと、そのマッピングの属性を確認できます。
これらの動作を説明したドキュメントは、RFC 4787です。
マッピングの作成 #
マッピングの作成は最も簡単な作業です。ネットワーク外のアドレスにパケットを送信すると、マッピングが作成されます。NAT マッピングは、NAT によって割り当てられた一時的なパブリック IP/Port に過ぎません。送信メッセージは、新たにマッピングされたアドレスを送信元アドレスとするように書き換えられます。マッピングにメッセージが送信されると、マッピングを作成したNAT内部のホストに自動的にルーティングされます。
マッピングの詳細については、ここからが複雑になります。
マッピング作成時の動作 #
マッピングの作成は3つのカテゴリーに分類されます。
エンドポイントに依存しないマッピング #
NAT内の送信者ごとに1つのマッピングが作成されます。2つのパケットを2つの異なるリモートアドレスに送信した場合、NATマッピングは再利用されます。両方のリモートホストには、同じソースIP/ポートが表示されます。リモートホストが応答すれば、同じローカルリスナーに送り返されます。
これは、最良のシナリオです。通話が機能するためには、少なくとも片側がこのタイプでなければならない。
アドレスに依存するマッピング #
新しいアドレスにパケットを送信するたびに、新しいマッピングが作成されます。2つのパケットを異なるホストに送信すると、2つのマッピングが作成されます。同じリモートホストに2つのパケットを送信し、宛先ポートが異なる場合、新しいマッピングは作成されません。
アドレスとポートに依存するマッピング #
リモートIPまたはポートが異なる場合、新しいマッピングが作成されます。同じリモートホストに2つのパケットを送信し、送信先のポートが異なる場合、新しいマッピングが作成されます。
マッピングフィルタリングの動作 #
マッピングのフィルタリングとは、マッピングの使用を許可する人に関するルールです。これらは3つの類似した分類に分類されます。
エンドポイントに依存しないフィルタリング #
誰でもマッピングを使用できる。マッピングを他の複数のピアと共有し、それらがすべてトラフィックを送信できます。
アドレスに依存したフィルタリング #
マッピングが作成されたホストのみがマッピングを使用できます。ホスト A
にパケットを送信すると、そのホストは好きなだけパケットを返してくることができます。ホストB
がそのマッピングにパケットを送ろうとしても、それは無視されます。
アドレスとポートに依存したフィルタリング #
マッピングが作成されたホストとポートだけがそのマッピングを使用できます。ホスト A:5000
にパケットを送信すると、必要なだけのパケットを返信できます。ホスト A:5001
がそのマッピングにパケットを送ろうとしても、無視されます。
マッピングの更新 #
マッピングが 5 分間使用されない場合、破棄することを推奨します。これは、ISP やハードウェアメーカーの判断によります。
STUN #
STUN (Session Traversal Utilities for NAT) は、NAT を利用するために作られたプロトコルです。これもWebRTC(とICE!)よりも前の技術です。このプロトコルはRFC 8489で定義されており、STUNのパケット構造も定義されています。STUNプロトコルはICE/TURNでも使用されています。
STUNが便利なのは、NATマッピングをプログラムで作成することができるからです。STUN以前は、NATマッピングを作成することはできましたが、そのIP/Portが何であるかはわかりませんでした。STUNでは、マッピングを作成できるだけでなく、詳細を知ることができるので、他の人とマッピングを共有して、作成したマッピング経由でトラフィックを送ることができます。
まずは、STUNの基本的な説明から始めましょう。その後、TURNとICEの使い方について説明します。今のところ、マッピングを作成するためのリクエスト/レスポンスフローについてだけ説明します。その後、他の人と共有するためにその詳細を得る方法について話します。これは、WebRTC PeerConnectionのICEのURLに stun:
サーバーがある場合に起こる処理です。簡単に言うと、STUNは、NATの外にあるSTUNサーバーに、観測された内容を報告してもらうことで、NATの後ろにいるエンドポイントが、どのようなマッピングが作成されたかを把握するのに役立ちます。
Protocol Structure #
STUNのパケットは以下のような構造になっています。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic Cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transaction ID (96 bits) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
STUNメッセージのタイプ #
各STUNパケットにはタイプがあります。今のところ、以下のものについてのみ関心があります。
- バインディング・リクエスト -
0x0001
- バインディングレスポンス -
0x0101
NATマッピングを作成するには、Binding Request
を行います。その後、サーバーは Binding Response
で応答します。
メッセージの長さ (Message Length) #
これはData
セクションの長さです。このセクションには Message Type
で定義された任意のデータが含まれます。
マジッククッキー (Magic Cookie) #
固定値 0x2112A442
をネットワークのバイトオーダーで表したもので、STUN トラフィックを他のプロトコルと区別するのに役立ちます。
トランザクションID (Transaction ID) #
リクエスト/レスポンスを一意に識別する96ビットの識別子です。リクエストとレスポンスを組むのに役立ちます。
データ (Data) #
データには、STUN属性のリストが含まれます。STUNアトリビュートは以下のような構造になっています。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value (variable) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
STUN Binding Request
は属性を使用しません。これは、STUN Binding Request
がヘッダーのみを含むことを意味します。
STUN Binding Response
は XOR-MAPPED-ADDRESS (0x0020)
という属性を使用します。この属性にはIP/Portが含まれる。これが、作成されるNATマッピングのIP/Portです!
NAT マッピングの作成 #
STUNを使ってNATマッピングを作成するには、リクエストを1回送るだけです。STUNサーバに STUN Binding Request
を送信します。STUNサーバは、 STUN Binding Response
を返信します。
この STUN Binding Response
にはマップされたアドレス
が含まれます。マップされたアドレス
は、STUNサーバがあなたをどのように見るかであり、あなたのNATマッピング
です。
マップされたアドレス
は、誰かにパケットを送ってもらいたいときに共有するものです。
マップされたアドレス
は、あなたの Public IP
や Server Reflexive Candidate
とも呼ばれています。
NAT の種類の決定 #
残念ながら、「マップされたアドレス」はすべてのケースで使えるわけではありません。 「アドレス依存」の場合、STUNサーバーだけが自分にトラフィックを送り返すことができます。もしあなたがこのアドレスを共有していて、他の相手がメッセージを送ろうとすると、そのメッセージはドロップされます。これでは他の人との通信には使えません。もし、STUNサーバーがあなたの代わりにパケットを相手に転送することができれば、「アドレス依存」のケースは実際に解決可能であることがわかるかもしれません。これは、以下のTURNを使った解決策につながります。
RFC 5780では、NATタイプを決定するためのテストを行う方法を定義しています。これは、直接接続が可能かどうかを事前に知ることができるので便利です。
TURN #
TURN(Traversal Using Relays around NAT)はRFC 8656で定義されており、直接接続ができない場合の解決策です。2つのNATタイプに互換性がない場合や、同じプロトコルを使用できない場合などに使用します。TURNは、プライバシー保護のためにも利用できます。すべての通信をTURN経由で行うことで、クライアントの実際のアドレスを見えなくできます。
TURNは専用のサーバーを使います。このサーバーは、クライアントのプロキシとして機能します。クライアントは、TURNサーバーに接続し、アロケーション
を作成します。アロケーションを作成することで、クライアントは一時的なIP/ポート/プロトコルを取得し、それを使ってクライアントにトラフィックを送り返すことができます。この新しいリスナーは中継トランスポートアドレス
として知られています。これは転送アドレスのようなもので、他の人がTURN
を介してあなたにトラフィックを送れるように、このアドレスを提供します。中継トランスポートアドレス
を渡した相手ごとに、自分との通信を許可するための新しいパーミッション
を作成する必要があります。
TURNを介してアウトバウンド・トラフィックを送信する際には、中継トランスポートアドレス
を介して送信されます。リモートピアがトラフィックを取得する際には、TURNサーバーから送られてきていることがわかります。
TURN ライフサイクル #
以下は、TURNのアロケーションを作成したいクライアントが行うべきことです。TURNを使っている相手との通信には、何の変更も必要ありません。相手はIP/Portを取得し、他のホストと同様に通信を行います。
アロケーション #
アロケーションは、TURNの中核をなすものです。アロケーション」とは、基本的に「TURNセッション」のことです。TURNのアロケーションを作成するには、TURNのサーバー・トランスポート・アドレス
(通常はポート3478
)と通信します。
アロケーションを作成する際には、以下の項目を指定する必要があります。
- Username/Password - TURNのアロケーションを作成するには認証が必要です。
- アロケーション・トランスポート - サーバー(
中継トランスポートアドレス
)と通信相手間のトランスポートプロトコルにはUDPまたはTCPを指定できます。 - 偶数ポート - 複数のアロケーションに対して連続したポートを要求できますが、WebRTCには関係ありません。
リクエストが成功すると、TURNサーバーから、Dataセクションに以下のSTUN属性を持つレスポンスが得られます。
XOR-MAPPED-ADDRESS
-TURN クライアント
のマップされたアドレス
です。誰かがデータを中継トランスポートアドレス
に送ると、ここに転送されます。RELAYED-ADDRESS
- これは他のクライアントに配るためのアドレスです。誰かがこのアドレスにパケットを送ると、TURNクライアントに中継されます。LIFETIME
- このTURNアロケーションが破棄されるまでの期間です。リフレッシュ
リクエストを送ることで、寿命を延長できます。
パーミッション #
リモートホストは、あなたがパーミッションを作成するまで、あなたの中継トランスポートアドレス
に送信できません。パーミッションを作成すると、TURNサーバーに「このIP/Portはインバウンドトラフィックの送信が許可されています」と伝えることになります。
リモートホストは、TURNサーバーに表示されているIP/Portを伝える必要があります。つまり、TURNサーバーにSTUN Binding Request
を送る必要があります。よくあるエラーケースは、リモートホストが別のサーバーにSTUN Binding Request
を送信してしまうことです。そして、このIPに対してパーミッションを作成するように要求してきます。
例えば、アドレスに依存したマッピング
の背後にあるホストに対するパーミッションを作成したいとします。別のTURNサーバーからマップされたアドレス
を生成すると、すべてのインバウンドトラフィックがドロップされます。異なるホストと通信するたびに新しいマッピングが生成されます。パーミッションはリフレッシュされないと5分後に失効します。
SendIndication/ChannelData #
これら2つのメッセージは、TURNクライアントがリモートピアにメッセージを送信するためのものです。
SendIndicationは自己完結型のメッセージです。その中には、送りたいデータと、誰に送るかが書かれています。これは、リモート・ピアにたくさんのメッセージを送信する場合には無駄になります。1,000通のメッセージを送信すると、相手のIPアドレスを1,000回繰り返すことになります。
ChannelDataでは、データを送ることはできますが、IPアドレスを繰り返すことはできません。IP/Portを持つChannelを作成します。その後、ChannelIdを使って送信すると、IP/Portはサーバー側で入力されます。たくさんのメッセージを送信する場合は、この方法が適しています。
Refreshing #
割り当てられたデータは自動的に破棄されます。TURNクライアントは、アロケーション作成時に指定したLIFETIME
よりも早くリフレッシュする必要があります。
TURNの使い方 #
TURNの使い方には2つの形態があります。通常は、片方のピアが「TURNクライアント」として動作し、もう片方が直接通信を行います。例えば、双方のクライアントがUDPをブロックしているネットワークにいるために、それぞれのTURNサーバーへの接続がTCP経由で行われるなど、双方にTURN Usageが存在する場合があります。
これらの図は、そのような場合を説明するのに役立ちます。
通信のための1TURNの割り当て #
コミュニケーションのための2つのTURNアロケーション #
ICE #
ICE(Interactive Connectivity Establishment)は、WebRTCが2つのAgentを接続する方法です。RFC 8445で定義されていますが、これもWebRTCよりも前の技術です。ICEは、接続性を確立するためのプロトコルです。ICEは、2つのピア間で可能なすべてのルートを決定し、接続を維持します。
これらのルートは候補ペア
と呼ばれ、ローカルとリモートのトランスポートアドレスのペアとなっています。ICEでは、ここでSTUNとTURNが活躍します。これらのアドレスは、ローカルIPアドレスにポートを加えたものや、NATマッピング
、中継トランスポートアドレス
などがあります。それぞれの側は、使用したいアドレスをすべて集めて交換し、接続を試みます。
2つのICEエージェントは、ICE pingパケット(正式には接続性チェックと呼ばれる)を使って通信し、接続性を確立します。接続が確立した後は、好きなものを送ることができます。通常のソケットと同じように使用できます。これらのチェックには STUN プロトコルを使用しています。
ICE エージェントの作成 #
ICE エージェントは、コントロールする
またはコントロールされる
のいずれかです。コントロールするエージェント
は、選択された候補者ペア
を決定します。通常、オファーを送信している相手がコントロール側になります。
それぞれの側はユーザーフラグメント
とパスワード
を持たなければならない。接続性のチェックを開始する前に、この2つの値を交換する必要があります。ユーザーフラグメント
はプレーンテキストで送信され、複数のICEセッションをデマックスするのに役立ちます。
パスワード
はMESSAGE-INTEGRITY
属性の生成に使用されます。各STUNパケットの最後には、パスワード
をキーにしてパケット全体をハッシュ化した属性があります。これは、パケットを認証し、改ざんされていないことを確認するために使用されます。
WebRTCでは、前章で説明したように、これらの値はすべて Session Description
を介して配布されます。
候補者の収集 #
次に、到達可能なすべてのアドレスを収集する必要があります。これらのアドレスを候補と呼びます。
ホスト #
ホスト候補は、ローカルインターフェイス上で直接リッスンするものです。これはUDPでもTCPでも構いません。
mDNS #
mDNS候補は、ホスト候補と似ていますが、IPアドレスが見えません。相手に自分のIPアドレスを知らせるのではなく、UUIDをホスト名として与えます。そして、マルチキャストのリスナーを設定し、誰かがあなたの公開したUUIDを要求してきたら応答します。
もしあなたがエージェントと同じネットワーク内にいれば、マルチキャストでお互いを見つけることができます。同じネットワーク内にいない場合は、(ネットワーク管理者がマルチキャストパケットの通過を許可するようにネットワークを明示的に設定していない限り)接続することができません。
これは、プライバシー保護のために役立ちます。ユーザーは、Host候補であれば、WebRTC経由であなたのローカルIPアドレスを(あなたに接続しようとしなくても)知ることができますが、mDNS候補では、ランダムなUUIDしか得られません。
Server Reflexive #
Server Reflexive 候補は、STUN サーバーに STUN Binding Request
を行うことで生成されます。
STUN Binding Response
を取得すると、XOR-MAPPED-ADDRESS
がサーバーリフレックス候補となる。
Peer Reflexive #
Peer Reflexive候補とは、自分が知らないアドレスからインバウンドリクエストを受け取った場合のことです。ICEは認証されたプロトコルであるため、そのトラフィックが有効であることがわかります。これは、リモートピアが、自分の知らないアドレスから 知らないアドレスから通信しているということです。
これは、ホスト候補
がサーバー反射候補
と通信している場合によく起こります。サブネットの外で通信しているため、新しいNATマッピング
が作成されました。接続性チェックは実際にはSTUNパケットであると言ったことを覚えていますか?STUNレスポンスのフォーマットでは、当然、相手は相手リフレックスアドレスを報告します。
リレー #
リレー候補は、TURNサーバーを使って生成されます。
TURNサーバーとの最初のハンドシェイクの後、RELAYED-ADDRESS
が与えられ、これがリレー候補となります。
接続性のチェック #
これで、リモートエージェントのユーザフラグメント
、パスワード
、候補者がわかりました。これで、接続を試みることができます。すべての候補者はお互いにペアになっています。つまり、片側3人の候補者がいる場合、9組の候補者がいることになります。
視覚的には次のようになります。
候補者選定 #
コントロールするエージェントとコントロールされるエージェントは、それぞれのペアでトラフィックの送信を開始します。これは、一方のエージェントがAddress Dependent Mapping
を使用している場合に必要となり、Peer Reflexive Candidate
が作成されます。
ネットワークトラフィックを確認した各候補ペア
は、有効な候補ペア
に昇格します。コントロールエージェントは、有効な候補者
のペアを1組選び、それを指名します。これがノミネートペア
となります。コントロール側とコントロールされる側のエージェントは、もう一回、双方向通信を試みます。これが成功すると,指名されたペア
は選択された候補者ペア
になります。このペアは、その後のセッションでも使用されます。
リスタート #
選択された候補ペア
が何らかの理由(NATマッピングの期限切れ、TURNサーバーのクラッシュなど)で動作しなくなった場合、ICEエージェントは失敗
状態になります。どちらのエージェントも再起動することで、すべてのプロセスをやり直すことができます。