tjun月1日記

なんでもいいので毎月書きたい

WebRTCに使われるP2Pの技術

f:id:taka-jun:20180130154309p:plain(この記事は過去の記事の転載です)

この記事は、HTML5 Advent Calendar 2013 - Adventarの15日目になります。

WebRTCが少しずつ広まってきて、あまりこの辺りの情報を日本語では見たことがないので書きました。 間違っている箇所、表現がおかしい箇所がありましたら、@tjunまで教えていただけると助かります。 もっと詳しく知りたいという人は一番最後のリンクにあるページを見るとよいと思います。

はじめに

WebRTCのおさらい

WebRTC、聞いたことがあるという人も増えていると思います。 WebRTCとは何かを一言でいうと、ブラウザ間でリアルタイムなやりとりをするための仕組み、ということになると思います。

ポイントは、「ブラウザ間」です。 サーバを経由せずにブラウザ間で通信することで、ブラウザ同士のやりとりも高速になりますし(もちろんネットワークの環境によります)、サーバ側でそれぞれの接続を管理する必要もなくなります。 WebRTCのRTC=Real Time Communicationで、「リアルタイムな通信」の方に気を取られるとWebSocketと似たようなものかと誤解されることがありますが、WebSocketは基本的にブラウザとサーバ間の通信のものであるのに対し、WebRTCはブラウザ同士の通信がターゲットです。 だから、よくあるデモアプリケーションとしてはビデオチャットなどになります。

f:id:taka-jun:20180130154345p:plain

ブラウザ間通信を実現するためには

一般的なブラウザ-Webサーバ間の通信ではサーバがパブリックなIPアドレスを持っているので、そのIPアドレスのポート80番を宛先として通信が開始できます。 このとき、基本的にブラウザはどこかのNATの下のネットワークにいて、プライベートなIPアドレスを持つマシンで動いています。 サーバからブラウザへの通信は、サーバへの問い合わせへの返答であることをNATが判断できるので、サーバからブラウザへのデータの通信もNATを経由して実現できます。 その仕組みについてはここでは説明しません。 一方、ブラウザ-ブラウザ間で通信しようと思っても、どちらもどこかのNATの下のプライベートなIPアドレスを持つマシンで動いているので、そのままでは相手がどこにいるのか分かりません。 サーバ-ブラウザ間の場合は、片方がパブリックなIPアドレスを持っていたので通信が開始できましたが、お互いが異なるNATの下にいる場合には、NATを通してその先のマシンへ通信を開始することができません。

f:id:taka-jun:20180130154405p:plain

そこで、まず通信するブラウザでお互いに、さあこれから通信をしましょう、とコミュニケーションして、通信に必要なメタデータの交換などを行う必要があります。このことをシグナリング、といいます。 次に、2つのクライアントが異なるNATの下にいる場合やfirewallがある場合、それらを越えて通信する仕組みが必要です。 以下では、シグナリングと、NATを越える技術について説明します。

シグナリング

ブラウザからWebRTCで通信するためには、当たり前ですがまず通信する相手が必要です。 いわゆるチャットサービスなら、友人の誰がオンラインか知る必要があります。 あるいは、知らない人同士がゲームの対戦をするサービスなら、どこかで対戦相手のマッチングをする必要があります。 どのようにして通信相手を決めるかはアプリケーションによりますが、必ず通信相手を見つけるための仕組みがサービス側にも必要です。 したがって、WebRTCを使ったサービスには、マッチングのためのサーバが必ず必要となります。

そこで、多くの場合このサービス用のサーバを利用してシグナリングを行います。 シグナリングのやり方は、アプリケーションによって変わってくるため、WebRTCでは決められていません。例えばXMPPSIPを使ってもよいですし、WebSocketを使ってブラウザサーバ間の通信を独自に実装することもあります。サービスを作る側が自由に選択することができます。

f:id:taka-jun:20180130154421p:plain

通信相手が決まれば、サービスのサーバを介してそれぞれのクライアントがお互いの情報を交換して、P2Pの通信へと移行します。 ここで交換する情報とは、上にも書きましたが、だいたい次のようなものです。

  • セッションのコントールメッセージ。接続の開始や終了を相手に知らせる。
  • エラーメッセージ
  • 接続で使用するメディアのメタデータ。例えば、コーデックや帯域、メディアの種類など。
  • セキュアな接続を構築するための、鍵情報
  • ネットワーク情報。例えばホストのIPアドレスとポート番号など。

シグナリングの大まかな流れは以上です。 詳しくは、what is signaling - HTML5 Rocks などを参照してください。

ICEを使ったNATとFirewall越え

シグナリングを使えば、通信相手とのP2P通信のきっかけとなるやりとりができることが分かりました。 そこで次はどのようにしてブラウザ同士が通信するかについて説明します。

しかしながら、最初に書いたように基本的にそれぞれのクライアントはNATの下側に存在していて、クライアントから別のクライアント直接データを送ることはできません。 そこでWebRTCでは、ブラウザ間の通信を実現するためにICEというフレームワークを使います。

ICEでは、STUNサーバとTURNサーバという新たな役割を持つサーバが利用されます。 STUNサーバは、クライアントに外側のネットワークアドレスを教える役割をもち、そしてTURNサーバは直接(P2Pの)通信ができなかった場合に、通信を中継する役割を持ちます。

またICEではそれぞれのクライアントが接続に必要な情報として、自分のIPアドレスとポート番号の情報を、シグナリングを使って相手に送ります。この情報のことをCandidate(候補)といいます。それぞれのクライアントが、自身のCandidateから相手のCandidateに対して接続を試みて、お互いにベストな通信経路を探し出し、2つのブラウザの通信を確立します。

Candidateには、IPアドレスとポート番号の他に、優先度などの情報が含まれます。

以下でICEの流れを順に説明していきます。 以下でアドレスと書いているところは、IPアドレスとポート番号であると読み替えてください。

  • まずICEを開始したそれぞれのクライアントは、1つ目のCandidateとしてOSから取得できるデバイス自身のアドレスを送ります。もし複数のインターフェース(有線と無線、など)があれば、複数のCandidateとなります。相手側も同様にローカルのアドレスを送ってくるので、それぞれの自身のローカルアドレスと相手側のローカルアドレスを使って接続を試します。同じネットワークにいればこれで繋がることができて、ここで終了、となる場合もあります。

    f:id:taka-jun:20180130154441p:plain

ここでダメな場合、次へいきます。

  • 次にそれぞれのクライアントは、STUNサーバを利用して外側のアドレスを取得します。 クライアントがSTUNサーバに通信するとSTUNサーバはそのときの送信元であるアドレスとポート番号を教えてくれます。 STUNサーバからはそのとき通ったNATが送信元に見えるため、外側のアドレスとはつまり、NATの外側アドレスになります。 (もしSTUNサーバとクライアントの間にNATがなければクライアントのアドレスになります) このようにしてSTUNを使って取得したアドレスが2つめのCandidateとなり、相手へと送られます。 相手からも2つめのCandidateが送られてくれば、1つめのCandidate(C1)と2つめのCandidate(C2)を使って接続を試します。

    f:id:taka-jun:20180130154504p:plain

    このとき、NATでは内側からその(アドレスと)ポートを使って内側から外側(相手のC2)へ通信をした、ということが分かるため、そのポートに対して外側(相手のC2)から入ってきた通信は、さっきの通信に対する返答であると判断されて送信元であるクライアントへ届きます。 これがいわゆるNATのHole Panching(穴あけ)です。

    f:id:taka-jun:20180130154543p:plain

    ここで上のように通信ができるかどうかは、ネットワークの環境次第です。 複数(多段)のNATがある環境もありますし、NATはそれぞれフィルタリング特性やアドレス・ポート変換特性を持つので、つながるかどうかは実際に試してみないとわかりません。
    (以前はNATの区別をするためにフルコーン、シンメトリックなどの分類が使われてましたが、その分類は不十分なのでちゃんとポートマッピングとフィルタリングの特性を見ましょう、ということがRFC4787で書かれています。)

  • STUNを使っても接続ができなかった場合は、TURNサーバを使って通信します。 それぞれのクライアントはTURNサーバへ接続し、TURNサーバのアドレスと、確保されたポート番号を取得します。 このTURNサーバのアドレスとポート番号を3つめのCandidate(C3)として相手へ教えます。

    f:id:taka-jun:20180130154612p:plain

    そしてC1, C2, C3を使って、相手へと通信します。TURNサーバを経由して通信ができれば、その後のやりとりもTURNサーバ経由となります。

    f:id:taka-jun:20180130154644p:plain

以上のように順番に試していくことで、2つのクライアントでベストな経路を決定します。 このようにしてクライアント間の接続が確立されると、サービスのための通信が開始できます。

また、それぞれのクライアントがどのSTUN/TURNサーバを使うのか決める仕組みは、サービスの実装によります。 アプリケーションで固定のサーバを決めていて使ってもよいですし、最初のネゴシエーションの時にサービスサーバから割り当ててもよいです。 2つのクライアントは異なるSTUNサーバやTURNサーバを使っても動作します。

おわりに

WebRTCでクライアント同士がどのように接続するか見てきました。 WebRTCを利用するサービスを作る場合には、STUNやTURNサーバを使う必要があります。 サーバを自前で用意する場合にはもちろんですが、最近発表されたSkyWayのようなサービスを利用する場合にも、どのような仕組みでどのようなサーバが使われるのか、ある程度は知っておくとよいと思います。

今回の記事はいろいろと省略しています、本当はもうちょっといろいろな仕組みがあります。

参考にしたページ