Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
WebRTCの裏側

WebRTCの裏側

シグナリングと TURN/STUN のプロトコル解説

作:@voluntas
バージョン:0.2.0
url:https://voluntas.github.io/

2015 年 2 月 5,6 日に行われた WebRTC Conference Japan 向けの発表資料です。

概要

WebRTC は P2P というイメージをお持ちの方が多いと思います。 今回は P2P で繋げる前の話と、P2P で繋げなかった場合の話をします。

なので、地味です。気をつけてください、眠くなる可能性が多々あります。

目的は単純で、本気で WebRTC を使う場合は繋がることが重要になります。そして、トラブルシューティングをする人向けの話です。

実際に運用している場合に何かしら問題があったときにヒントになるような知識を共有できればと思います。

自己紹介

株式会社時雨堂

社員数が片手で足りる小さな小さな零細企業から来ました。

Erlang/OTP

主に Erlang/OTP という隙間産業向け言語でお仕事をしています。

せっかく、この機会を頂いたので宣伝しておきます。

ほとんど聞いたことが無い言語だと思いますので、 Erlang/OTP が採用されている有名なサービスを紹介しておきます。

  • ニコニコ生放送
    • 誰もが知ってる有名サービス
  • LINE
    • ユーザ数 5 億名を超えるメッセージツール
    • メッセージ配送に使用されているようです
  • WhatsApp
    • ユーザ数 7 億名を超えるメッセージツール
    • Facebook が 2.5 兆円くらいで買収しました
  • League of legends
    • ユーザ数 7000 万人を超えるオンラインゲーム
    • 同時接続数 750 万人以上

Erlang/OTP を使って落ちてはいけないサーバをメインに開発しています。

専門分野は暗号(使う側)やら認証やら課金あたりです。

WebRTC 関連

WebRTC 関連では WebRTC 向けのシグナリングサーバや TURN/STUN サーバを開発しています。

本日話さないこと

  • WebRTC API について
  • SDP について
  • NAT の詳細について
  • ICE について

NAT について詳細にお話ししないのは、NAT の話をしていたらキリが無いからです。

本日話すこと

  • シグナリングサーバ
  • STUN サーバ
  • TURN サーバ

割合的には TURN サーバが多いです。シグナリングは概要だけで終わらせます。

STUN は元々 WebRTC がとてもシンプルな使い方しかしていないので、話すことがあまりありません。

TURN は元々が複雑な上に、いろいろなパターンがあるため、がっつり説明します。

シグナリングサーバ

シグナリングサーバは通信相手の情報を取得するためのサーバです。

WebRTC で P2P で通信をするにしても、何かしら相手の情報を知らない限りは通信ができません。

どこにいるかわからない相手とは通信出来ないからです。

それを自動化する事で、 相手の情報 の断片、たとえばユーザ名を伝える事でそれ以外の必要な情報のやりとりをしてくれるのがシグナリングサーバです。

一番基本的なところで行くとユーザ名伝える事で、その相手を探し出してくれる仕組みがあります。

相手がオンラインなのかオフラインなのかどうかを知るには、たとえば口頭できいたり、チャットできいたりする方法があります。

ただ、毎回それをやっていたらキリがありませんね。そのため WebRTC の準備ができているユーザー一覧を取得するという仕組みも必要になってきます。

通信方式

シグナリングサーバとクライアントの通信方式は自由です。WebRTC の仕様によって縛られていません。

一番シンプルな WebSocket でも問題ありません。もちろん XHR を使ったポーリングでもいいですし、XMPP でもかまいません。昔ながらの SIP でもとくに問題ありません。

最近であれば XHR と WebSocket 両方をうまくつかった Socket.IO を使うのが一番シンプルだと思います。

基本的にはリアルタイムなメッセージ通信を行える土管型のサービスを使えばシグナリングはできます。

NAT

さて、 STUN や TURN の話をするまえに、簡単に NAT に触れていきたいと思います。

NAT (Network Address Translation) は日本語だとネットワークアドレス変換と訳されます。

ちなみに、今の世の中では NAT の IP アドレスだけではなく ポート番号の変換も行うため NAPT (Network Address Port Translation) が実際の動作としては正しいです。今回の発表では NAT と呼びますが NAPT も含まれると思って聞いてください。

NAT をイメージする場合、ご自宅で Wifi を繋いだときに全ての端末にグローバル IP が付いていない事を考えてみてください。ただ、 ISP から割り振られる IP は一つですよね。となると何かしらアドレス変換が起きていると考えるのが普通です。

内部は A なのに外から見ると B に見えます。外から A に送るためにどうするべきなのか。これが NAT 越えとして解説していきます。

NAT は細かい話をしていくとキリが無いので、ざっくりと理解で十分です。何が課題なのか、何を解決するのかの方が重要だと思っています。

STUN

Simple Traversal of User Datagram Protocol Through Network Address Translators

STUN は外のプロトコル(ここでは WebRTC で使用されるプロトコル全般)が NAT 越えを実現するためのプロトコルです。

WebRTC でも使用されています。

STUN 自体は UDP でも TCP でも使用可能なプロトコルです。ただ、基本的には UDP で使用されます。

WebRTC の STUN は今のところ UDP が使用されています。もしかすると今後は TCP が使われていくのかもしれません。

内容

WebRTC で実際に使われる STUN リクエストは Binding-Request と呼ばれる形式だけが使われます。

STUN では属性と呼ばれる Key/Value のペア (HTTP のヘッダーみたいなものです) が色々リクエストに付与されるのですが、それは一切使われていません。

サーバは Binding-Request に対して成功レスポンスを返します。レスポンスには送信 IP アドレスと送信ポート番号を、 XOR-MAPPED-ADDRESS 属性の中に含めて送り返します。

送信 IP アドレスやポートと言いましたが、これらの値は実際に STUN パケットを投げてきたクライアントのアドレスである可能性はとても低いです。

なぜなら、リクエストが送られる間に NAT が存在する可能性はとても高いからです。自分が置かれている状況を理解するための仕組みが STUN です。自分が知ってるアドレスと同じアドレスが帰ってきたら NAT が存在していない可能性が高いと判断できます。

このように STUN は自分がどのような状況にいるのかを手助けするプロトコルです。

NAT を超えるためのプロトコルというよりは、超えなければいけないかどうかを判断するプロトコルと認識して貰えれば良いです。

どう判断したかどうかはプロトコル上からは認識することはできません。

セキュリティ

今まで STUN は TLS が使われていませんでしたが、最近になって STUN over DTLS の RFC が公開されました。もしかすると今後は WebRTC でも DTLS が可能になるかもしれません。

スキーマは stuns と定義されました。

RFC 7350 - DTLS as Transport for STUN

TURN

TURN は実際に NAT 越えを実現するプロトコルです。 STUN が判断とするなら TURN は実現するプロトコルです。

実現方法はとても単純でグローバルに存在するサーバーを経由してパケットを代理で送って貰うという仕組みです。

プロトコル

TURN は STUN と同じポートで送られてくる前提で設計されています。そのため規定されているポート番号も一緒です。

ただし、内容はかなり複雑です。TURN の基本的な流れを紹介しておきます。

  1. 認証
  2. 代理でパケットを受け取る代理人を立てる
  3. 代理人のパーミッションを設定
  4. 代理人の更新
  5. 代理人に送信メッセージを送る

基本的には NAT 越えできないクライアントのための代理を TURN サーバの中に立てるのがメインです。ただ、立てるだけだとアタックされてしまうので、 IP レベルで制限する事ができます。

さらに、野ざらしにしておくとアタックを受けやすいので、時間制限付きです。

後はその代理人に対してメッセージを送ったり、代理人からメッセージが届いたりします。

誤解されがちな事

TURN で良く誤解されることがいくつかあります。

  • 二人で通信をする場合は土管は 1 つだけ
  • 暗号化された通信を盗み見ることができる
  • TCP/UDP だと結局 UDP なので NAT が超えられない

二人で通信をする場合は土管は一つだけ

これは NAT 環境に左右されるのですが二人ともが TURN を使う前提の場合は間違っています。一人一つの土管を用意する必要があります。

それぞれが送るのも受けるのも自分の土管です。

あくまで代理人がそれぞれ立ち、代理人同士が会話するためです。土管といっても代理人までの土管になるため、二人で通信をする場合はそれぞれの代理人が立てられるため土管は二つ存在することになります。

暗号化された通信を盗み見ることができる

TURN は全ての通信を代理人として送る事ができるため、全てのデータが読み取れるのでは?と思われることがあります。実際読み取れることはできますが、通信の中身が暗号化されている場合は当たり前ですが読むことはできません。

WebRTC のデータ通信は全て DTLS によって保護されています。

TCP/UDP だと結局 UDP なので NAT が超えられない

結論から言えば超えられます。ただし、皆さんが想定されている動作とは違います。 こちらは実際 TURN-TCP/UDP で解説します。

TURN の認証

TURN サーバは STUN と違って認証が必須です。 Username と Credential を使用します。ただし少し違うのが、二つ認証方式があることです。ちなみに認証の概念自体は TURN というよりは STUN のものです。

ただ STUN は認証があまり使われてきていませんでした。リソースを確保してしまう TURN では必須のため、ほとんど TURN のための認証方式となってしまっています。

  • short-term
  • long-term

TURN ではこの二つの認証方式が定義されています。 WebRTC で使われるのは後者の long-term 認証です。

short-term と long-term 認証はメッセージに対して HMAC 計算してその値を MESSAGE-INTEGRITY 属性として送ります。 ただし、 HMAC を計算する際の Key の扱いが short-term と long-term では異なります。

short-term 認証では普通に Credential を使用します。long-term 認証では USERNAME 属性と REALM 属性と Credential の 3 つを使います。つまり REALM 属性が必須となります。 USERNAME 属性はそもそもないと誰のメッセージなのかがわかりません。

注意点として、この認証方式はあくまで、サーバ側に設定がゆだねられています。サーバが short-term 認証と設定しているのに、クライアントが long-term 認証で送ってきた場合、認証は常に失敗します。

サーバ側はメッセージを受け取った際、 MESSAGE-INTEGRITY 属性の存在を確認し、無ければ 401 (認証失敗) を返します。 存在した場合は USERNAME 属性を使って TURN サーバ側で保存しているパスワードを確認し、 HMAC の値を計算します。

ちなみに、 TURN のデータのやりとり以外のメッセージは全て認証されることが前提となっています。

RFC 7376 - Problems with STUN Long-Term Authentication for TURN

NONCE

short-term や long-term とは関係なく NONCE という仕組みも存在します。これはサーバが返した値をそのまま送ってきて貰うという仕組みです。チャレンジ方式の一種ですね。

サーバが NONCE を送ってきた場合はクライアントは NONCE を付けて返さなければならない (MUST) と定義されています。

TURN の通信方式

TURN は通信方式がとても重要になります。 TURN を使う時点で P2P が行えないと判断された状況だからです。

TURN は自分が TCP で TURN サーバまで送って、その後は UDP といったプロトコルが変わる動作も存在します。

このあたりの動作を一通り紹介していきます。

TURN-UDP/UDP

RFC 5766 - Traversal Using Relays around NAT (TURN): Relay Extensions to STUN

まずは王道の受け取りも送信も UDP モードです。

TURN サーバはグローバルに存在することがほとんどで、さらに通信の制限を受けていることもほとんどありません。そのため相手クライアントよりは UDP で繋がる可能性はとても高いです。

繋がらないのはクライアント環境がそもそも UDP で抜けられないとかの場合ですね。

この通信方式はとてもシンプルではありますが、 UDP である以上、 相手がいなくなった 事に気づけません。

そのためタイマーがふんだんに使われています。使われる TURN プロトコルを紹介していきます。

Allocate-Request

TURN では Allocate という概念が存在します。これは代理人を立てる処理です。

つまり TURN サーバの中に、自分の代わりにパケットを受け取ったり送ったりしてくれる人を作る作業です。

実装よりな話をすると動的に UDP ソケットを作っています。つまり、相手からメッセージを受け取ったり自分が送ったりするソケットを動的に生成するリクエストです。

ただ、これそのまま立ててしまうとセキュリティ的にも不安ですし、 UDP で作られた以上その役目がいつ終わるのかわかりません。 TCP であれば接続が切れたタイミングでソケットを閉じたりもできますが、 UDP ではできません。

代理人を確保したのはいいけれど、色々不安ですね。そのためこの代理人には契約期限が存在します。

つまり、一定時間たつとこの代理人は使えなくなるという仕組みです。さらにパーミッションも存在します。

特定の IP アドレスから以外のリクエストは受け付けたとしても破棄するという仕組みです。

Refresh-Request

代理人の契約期限を更新するのがこの仕組みです。 Allocate-Request で確保した代理人に対して、継続更新を依頼します。依頼と言っても基本的に拒否する仕組みはないので、このリクエストが送られてきたタイミングで、まだ契約期限が残っていれば、自動的に契約が更新されます。

この契約期限ですが、基本的には 800 秒程度です。LIFETIME という属性で指定が可能です。

ただし、あまりにも長い場合はサーバはそのクライアントが希望している契約期限の長さ無視して、サーバ側で勝手に決めることも可能です。

もちろんサーバはクライアントにレスポンス時に契約期限の値を提示します。

CreatePermission-Request

代理人に対して想定外の相手から接続があると困るので、それを制限するのがこの処理です。

IP アドレスをパーミッションにインストールします。そうすることで、代理人は特定の相手からのリクエスト以外を無視するようになります。

このパーミッションは複数指定することができます。また、このパーミッションにも期限があります。そのため定期的にパーミッションの更新をする必要があります。この LIFETIME の値は 300 秒と仕様により固定されています。

Send/Data と Channel

さて、代理人を立てたらあとはメッセージを送信したり受信したりします。そのためには TURN の仕組みの中で別のプロトコルをいれる、カプセル化を行う必要があります。

そのための技術がなんと、二つあります。 WebRTC では両方使用している事を確認しています。

なぜ二つあるのかは、後ほど説明します。

Send-Indication/Data-Indication

Send-Indication と Data-Indication というメッセージがあります。これはレスポンスが存在しない一方通行のメッセージです。 今まで紹介してきたメッセージには全てレスポンスが存在しました。

  • Request はレスポンスが必要
  • Indication はレスポンスが不要

メッセージを送信したら後は知らない。という仕組みです。

Send と Data でどう違うのかというと、 Send は クライアントが代理人に送信を依頼するメッセージ です。 Data は 代理人が受信したメッセージをクライアントに送るメッセージ です。

つまり送信と受信で別のメッセージが使用されます。

WebRTC ではまず最初にこれらのメッセージを使用してやりとりが行われます。

Channel

チャネルという概念があります。これは TURN パケットフォーマットから逸脱するパケットを送れるようになる仕組みです。

クライアントは事前にチャネル ID というのをサーバに ChannelBind-Request を使って送ります。 TURN サーバはこのリクエストを受け取ったタイミングでチャネル ID を記録します。

この際、チャネル ID と送り先 IP/Port を結びつけます。

その後パケットのやりとりにはチャネル ID を使用します。送信したいパケットの先頭にチャネル ID を付与して送ります。つまりカプセリングというよりは、完全にヘッダーをちょろっと付けただけのバイナリを使って送り合うようになります。

クライアントからサーバへ
クライアントはチャネル ID を付与して代理人にメッセージを送ります。代理人はメッセージを受け取ったらチャネル ID から相手先を探しだし、チャネル ID を外してメッセージを送ります。
サーバからクライアントへ
代理人は相手からメッセージを受け取ったら、チャネル ID を付与してクライアントに送ります

なぜ二種類必要なのか

理由は Allocate したタイミングでメッセージを受信してもよい といった仕組み上の問題が一つあります。

チャネルを使うためには ChannelBind-Request を成功させる必要があります。ただし、その前に代理人はメッセージを受け取る可能性があります。

そのため、チャネルが確立していない間の代理人が受け取ったメッセージをクライアントに送信する場合はすべて Data-Indication を使う必要があります。

また、 Send/Data は常に宛先を含める必要があるためパケット的に少し重くなります。そのため軽量なチャネルが用意されています。

  • チャネルが確立されていない間のためのものが Send/Data
  • Send/Data をより軽く使えるようにしたのがチャネル

一応合理的には考えられています。実装大変でしたけど。

TURN-TCP/UDP

TURN では代理人までの通信を UDP ではなく TCP を使うという仕組みが存在します。外に UDP が出られない場合に有効な仕組みです。

ただし、 WebRTC では後述する相手クライアントまでの通信を TCP で行うという仕組みは今のところ対応していません。

つまり相手クライアントまでの通信は UDP で行います。ここで出てくる疑問は、結局 UDP だったら繋がらない気がする ... です。

そこで、思い出して欲しいのが ... 土管は二つ作られるという事です。

この UDP というのは 相手の代理人までの通信が UDP になる ということです。

もう既にお気づきの方もいらっしゃいますが、続けます。 まだ気づけない場合は図が無いとわかりにくいので、図をざっくりと。

クライアント -> TCP -> [代理人(TURN サーバー) -> UDP -> 代理人(TURN サーバー)] -> TCP -> クライアント

[ ] でくくった範囲は同一サーバということを思い出してみてください。 UDP で何ら問題ないことがわかりますね。つまり実際は TCP -> [UDP] -> TCP という TCP で繋いでいるだけです。

これは UDP ですらありません。さらにクライアントから TCP で繋いできています。NAT 越えが達成できる確率がかなり高いです。 WebRTC では TURN-TCP/UDP 今のところ最後の頼みの綱です。良くあるのがこれを 80 番ポートに設定していたりするところです。

凄く良く見える TURN-TCP/UDP ですが、仕組み上かなり複雑なことをしています。

TCP ではコントロールのための接続とデータを送り合うための接続が別になっています。少しでも効率化をはかるためでしょう。

ConnectionBind-Request

クライアントから代理人にデータを送るときは ConnectionBind-Request を使ってデータを送信するための仕組みを作ります。

ConnectionAttempt-Indication

代理人が相手からデータを受け取った場合は、一端コントロールコネクションに対して、一方通行のメッセージを送ります。

その後クライアントは ConnectionBind を使ってそのメッセージ用のデータ接続を作成します。これでやっと代理にから送られてきたメッセージを受け取れるようになります。

この一連の手続きが終わるまでのメッセージは 代理人が全てのメッセージを保持し続ける ことになっています。実装つらかったです。

TURN-TLS/UDP

TCP/UDP の TCP を TLS にして暗号がりがりにしたバージョンです。 WebRTC では TURN を使うとき turn というスキーマを設定しますが、 TLS を使う場合は turns を使用します。

443 ポート使って抜け道を作りたいときとかに有用かもしれません。そもそも WebRTC のデータは DTLS で暗号化されているので、 DTLS over TLS という残念な感じになってしまうことをここに報告しておきます。

TURN-TCP/TCP

この話はおまけです

RFC 6062 - TURN Extensions for TCP Allocations

カンファレンス後に公開されます

TURN-DTLS/*

この話はおまけです

DTLS as Transport for TURN

カンファレンス後に公開されます

TURN-TCP/INTERNAL

この話はおまけです

カンファレンス後に公開されます

まとめ

  • TURN は複雑な動作をしている
  • TURN はできる限り通信の成功率を上げる以外には不要
  • TURN はまだまだ改良の余地がある世界
  • TURN は今後も新しい技術がつぎ込まれていく
  • WebRTC とは当面は付き合いがありそう

最後に、サービスを紹介させてください。

Twiilo という SMS やら VoIP やらをやっていて、かなり市場を持っている会社があります。 日本だと KDDI が代理店?としてサービスをやっているようです。

その Twilio が最近 STUN/TURN のサービスを始めました。

Global Network Traversal Service - Twilio

これはなかなか面白い仕組みです。こんなサービスが日本でもでてくるといいなと思っています。

また、NTT コミニュケーションズ様も、国内初で TURN のサービスを始められました。

ニュース 2015年1月27日:WebRTCプラットフォーム「SkyWay」にTURN機能を追加し国内初のトライアル提供を開始 | NTT Com 企業情報

この TURN サーバ、作りました。

参考

TURN/STUN サーバ開発時によく見てる資料を箇条書きにしました

RFC

ドラフト

宣伝

WebRTC SFU を 1 から開発/販売しています。興味のある方は是非

WebRTC SFU Sora

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment