背景
年末年始にオライリーの『ハイパフォーマンスブラウザネットワーキング』を読んでいたら、 WebRTC の STUN, TURN, ICE とは何だったのか腑に落ちたので理解を整理する。
WebRTC とは
Web ブラウザにおける Real Time Communication を実現する仕様および、それを実現するためにブラウザに実装された API 群のこと。
通信プロトコルとして通常の TCP の代わりに UDP を用いており、それ故に STUN, TURN, ICE といったプロトコル/実装を必要とする。
通信プロトコルの話
TCP とは
通常 Web 上における HTTP 通信は TCP を用いて行われる。
TCP (Transmission Control Protocol) とは、クライアントとサーバーが通信を開始する際、安全に接続を確立するために定めた手順のこと。
(接続を開始する時だけでなく、通信中にネットワークの状態に関わらずデータを欠落のない状態で送り届けるための手順や、ネットワークの状態が不安定な時にデータを送りすぎてネットワークを破壊しないような安全弁、また通信を安全に終了する手順なども定めてある。)
初めにクライアントからサーバーのパブリックIPアドレス当てに通信開始の合図を送り、サーバーはそれを受け取って「受け取りましたよ」のサインを送る。それをクライアントが受信した時点でクライアントからのデータ送信が可能になる。
またサーバーから「受け取りましたよ」のサインを受け取ったクライアントは同様に「受け取りましたよのサインを受け取りましたよ」のサインを送り、サーバーはそれを受け取った時点でデータ送信を開始する。
データの送信中も、インターネットの世界では大量のパケットを小分けにして、多数のネットワーク中継点を経由して相手にデータを送り届けるため、途中でデータが欠落したり、順序どおりに届かなかったりする。そのため全てのパケットにシーケンス番号を振り、データ受信側ではシーケンス番号通りにデータが届いているかどうかをチェックしながらデータを復元する。
UDP とは
UDP (User Datagram Protocol) も通信プロトコルの1つであり、 WebRTC において TCP の代わりに用いられる。
上記に見た通り、 TCP は「データを欠損なく順序通りに通信相手へ届けること」を重視しており、そのため多くの手順を踏むことになる。従って通信にかかる時間が長くかかってしまうという課題がある。
UDP はデータに欠損や順序の乱れがあることを許容することで、通信にかかる時間を短縮しようとした。
UDP は1つのパケットごとに情報が完結するようにデータを送信し、受け取り手は受け取ったパケットの前後の情報の順序が逆だったり欠落していたりしても問題ない前提でデータを利用する。
例えば動画データの1場面を切り取った画像を次々に送信した場合、受け取り手側は届いた画像を次々に画面に表示していくような利用を考えてみる。ある場面のデータが欠落していても、一瞬のちにすぐ次の場面のデータが表示されればそこまで致命的な影響は無いし、1フレームだけ順序が逆になったとしても大勢として順序通りであればユーザーは気付かないだろう(多分)。
このような発想で TCP を用いるよりも素早い通信を行うためのプロトコルとして考案・実装されたのが UDP である。
しかし、今まで事実上ほぼ全ての HTTP 通信に TCP が使われていた以上、 UDP を用いた通信に対応していない部分がネットワーク上に存在する可能性がある。その代表例が NAT である。
NAT
NAT が生まれた背景
インターネットという巨大な1つのネットワークにおけるユニークな所在はIPアドレスによって表されるが、その形式がかつて 32bit で表現されていた(IPv4)ため、最大で42億9496万7296個までの所在しか表すことができなかった。
インターネットの発達と浸透によってインターネットに接続しているコンピュータの数がそれを上回りそうになるという「IPアドレス枯渇問題」が持ち上がり、その後IPアドレスは128bitのデータで表現されるようになった(IPv6)。
NAT とは
NAT はIPアドレス枯渇問題に際して暫定措置として生まれた仕組みである。
インターネット上に NAT という中継点を置き、1つのパブリックIPアドレスを振る。その NAT の背後にローカルなネットワークを構築し、その内部のノードが持つそれぞれのローカルIPアドレスにポート番号を割り当てる。 NAT はローカルIPアドレスとポート番号のペアを保持しておき、外部から自分に向けられたデータを受け取るとポート番号に応じてローカルIPアドレスに宛先を変換してルーティングする。
NAT を通した UDP 通信の問題点
この NAT を経由して UDP による双方向通信を行う場合、いくつかの問題が発生する。第一の問題は NAT の背後にあるクライアント/サーバーが自身のパブリックIPアドレスを把握できないという点だ。
そのため、内部ネットワークから外部の通信相手にデータを送信した際、自身のローカルIPアドレスを送信元情報として記載してしまうことになるが、それを受け取った相手がローカルIPアドレス当てに通信を送り返しても、当然通信は届かない。
第二の問題は NAT の外側から内側のノードへデータを送信した場合、ルーティングするためには内部のローカルIPアドレスとポート番号のペアを持っていなければならないが、 NAT が外部から初めてデータを受け取った時点ではそのような情報は保持していないという点だ。
NAT がポート番号に対応するローカルIPアドレスの情報を持っていない場合、その受け取ったパケットは単純に破棄されるため、通信はそこで遮断されてしまう。
STUN プロトコル
これらの問題に対処するため最初に考案されたプロトコルが STUN (Session Traversal Utilities for NAT) だ。
まずパブリックIPアドレスを持つサーバー(STUNサーバー)を置く。 NAT の背後にあるクライアントアプリケーションは UDP 通信を始める前に STUN サーバーへ通信する(バインディングリクエスト)。 NAT を中継する時点で送信元情報は NAT のパブリックIPアドレスに書き換わるため、 STUN サーバーはそのパブリックIPアドレスを知ることができる。
STUN サーバーは受け取ったパブリックIPアドレスとポート番号をそのままクライアントアプリケーションに返す。こうすることで、クライアントアプリケーションは自身が接続する NAT のパブリックIPアドレスとポート番号を知ることができ、それを本来の UDP 通信の相手に知らせることができるようになる。
また、クライアントアプリケーションから STUN サーバーへの通信が NAT を通る際に、 NAT 上にローカルIPアドレスとポート番号のペアが作成される。このことで NAT 外部から内部クライアントアプリケーションへの通信が通るようになる、という寸法だ。
さらに NAT 上のローカルIPアドレスとポート番号のペアは、しばらく利用されない場合に NAT が自動的に破棄してしまう(NATのタイムアウト)問題があるが、 STUN プロトコルにはキープアライブのメカニズムも定義されており、定期的に通信することでタイムアウトを防ぐことができる。
TURN プロトコル
上記の STUN プロトコルによる解決には、実は不十分なところがある。 NAT の設定やファイアウォールの振る舞いによっては UDP 通信が完全に遮断されてしまうこともある。
そういった場合に最終手段として利用されるのが TURN プロトコルだ。パブリックIPアドレス上に TURN サーバーを置き、 UDP 通信を行うピア同士は直接的には TURN サーバーにデータを送信し、 TURN サーバーは受け取ったデータをそのままもう一方へとリレーする、という仕組みである。
もちろん、この方法はもはや P2P 通信ではなく、維持のコストも高い。通信が TURN サーバーの可用性に依存してしまうし、ピア間の全てのデータフローを扱えるだけの容量が必要になる。従って最終手段として利用するに留めたい。
Google は Google Talk というサービスで UDP 通信をプロダクション投入したことがあり、その際に STUN プロトコルだけでは通信できずやむを得ず TURN プロトコルを利用することになった割合を計測した。結果、全体の約8%が TURN サーバーによるリレーを必要としたということなので、信頼性の高いサービスを提供したい場合は TURN サーバーの構築・運用を検討するべきだ。
ICE プロトコル
以上のように、 UDP による双方向通信を確立するためには様々な障害と回避策があり、その手順と実装は複雑になる。その手順は、まず P2P の直接通信を試し、必要に応じて STUN サーバーと通信し、それでも通信が確立しない場合は TURN サーバーを利用する、というものになる。このような手順およびメソッド群をまとめたものが ICE (Interactive Connectivity Establishment) プロトコルだ。
実際に WebRTC による通信をサービスに利用しようとした場合、 STUN, TURN, ICE を実装した既存プラットフォームの API ないしはサードパーティライブラリを用いることになるだろう。
これらのプロトコルの役割を理解した今なら、迅速にセットアップを初められることでしょう!
まとめ
よく分かっていなかった用語について理解できただけでなく、ついでに TCP や NAT といった周辺知識も得ることができてお得感のある年末年始でした(小並感)。
普段よく分からずブラウザの提供する API にインターフェース通りの値を投げ込むだけで動作するアプリケーションを実装できてしまうけど、たまに余裕のある時期にこういう根本的な勉強をすることも大事だなと思いました(小並感2)。
ちなみに聞いた話によると STUN/TURN サーバーは node.js でサードパーティライブラリを利用すればわりと簡単に構築できるらしいので、機会があれば触ってみたいと思う。おしまい。