Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active April 1, 2024 00:49
Show Gist options
  • Star 85 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save voluntas/a1d39c2b2a4392956ff69732dc493e39 to your computer and use it in GitHub Desktop.
Save voluntas/a1d39c2b2a4392956ff69732dc493e39 to your computer and use it in GitHub Desktop.
WebRTC で利用されいる TURN プロトコルの解説

WebRTC で利用されいる TURN プロトコルの解説

日時

2021-01-29

@voluntas

バージョン

2021.2

url

https://voluntas.github.io/

image

この記事が良いと思ったらこの記事に Star をお願いします。

著者

株式会社時雨堂 で 1 から多くの商用環境で利用されている WebRTC SFU を開発している。 WebRTC スタックは暗号ライブラリの利用している部分以外はすべて自前実装。

概要

WebRTC は P2P というイメージをお持ちの方が多いと思います。 今回は P2P で繋げなかった場合に利用される TURN というプロトコルの話をします。

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

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

この資料で学べないこと

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

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

この資料で学べること

  • STUN プロトコル
  • TURN プロトコル

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

NAT

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

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

セキュリティ

WebRTC では利用されていません

今まで 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 Session Traversal Utilities for NAT (STUN) Long-Term Authentication for Traversal Using Relays around NAT (TURN)

NONCE

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

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

TURN の通信方式

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

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

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

TURN-UDP/UDP

RFC 8656 - Traversal Using Relays around NAT (TURN): Relay Extensions to Session Traversal Utilities for NAT (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 over TCP という残念な感じになってしまうことをここに報告しておきます。

TURN-TCP/TCP

WebRTC では使われていません

RFC 6062 - Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations

TURN-DTLS/*

WebRTC では使われていません

draft-petithuguenin-tram-stun-dtls-00 - Datagram Transport Layer Security (DTLS) as Transport for Session Traversal Utilities for NAT (STUN)

まとめ

  • TURN は複雑な動作をしている
  • TURN は通信の成功率を上げる仕組みなので必須

参考

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

RFC

ドラフト

広告

WebRTC SFU Sora

WebRTC SFU Sora

商用の WebRTC SFU です。価格は同時 100 接続でライセンス費用は 1 年で 84 万円です。 3 ヶ月ライセンスも用意してあります。製品のサポート料金込みです。

複数人数での会議や、 1000 人への配信、一対一の面談など様々な用途に利用可能です。

パッケージで提供しますので、自社で運用が可能です。 AWS だろうが GCP だろうが、オンプレだろうがなんでも好きな環境で動かすことができます。

サーバさえあれば Nginx の設定と Let's Encrypt での証明書取得まで含めて 1 時間で可能です。 HTTPS が必須なのでその準備が必要ですがそれさえ対応できればすぐ確認可能です。

  • 大変多くのお客様に採用いただいております
  • とにかく 落ちないこと を目的に作っています
  • とにかく 繋がること を目的に作っています
  • とにかく 手間がかからないこと を目的に作っています
  • 最新ブラウザのアップデートに追従しています
  • シグナリングサーバ内蔵ですので別途立てる必要はありません
  • TURN サーバ内蔵ですので別途立てる必要ありません
  • 日本語によるサポート対応しています
  • フルスクラッチによる自前実装なのですべて把握しています
  • 1:1 の双方向に対応しています
  • 1:1000 の片方向に対応しています
  • 3:1000 といった配信者が複数の片方向にも対応しています
  • 4K 30fps の配信も可能です
  • スポットライトという機能を利用することで 200 人以上の会議に対応しています
  • 録画機能があります
  • Chrome / Firefox / Edge / Safari といった主要ブラウザ全てに対応しています
  • Apache 2.0 ライセンスで JavaScript と iOS と Android 、Unity のクライアント SDK を公開しています
  • Apache 2.0 ライセンスで React Native 向け WebRTC ライブラリを公開しています
  • 既存システムとの連携を重視しており、Web フック機能を利用して簡単に連携が可能です
    • 認証や、クライアントの接続切断などもすべて HTTP での通知を既存のシステムに送ることができます
  • Apache 2.0 ライセンスで Sora 専用負荷試験ツールをオープンソースとして公開しています
  • Apache 2.0 ライセンスで Sora 専用録画合成ツールをオープンソースとして公開しています

興味のある方はお気軽に sora at shiguredo.jp までお問い合わせください。

紹介や検討資料も公開しております。

WebRTC Native Client Momo

WebRTC Native Client Momo

GitHub にオープンソースで公開している WebRTC のネイティブクライアントです。 Linux と macOS と Windows で動作します。

  • OpenMomo プロジェクト
  • ライセンスは Apache License 2.0 です
  • 多くの端末のハードウェアエンコーダに対応しています
    • Raspberry Pi Zero という非力デバイスでも H.264 ハードウェアエンコーダを利用し 720p@30 で配信可能です
    • Jetson Nano ではハードウェアエンコーダを利用することで 4K@30 で配信可能です
    • macOS でもハードウェアエンコーダを利用して動作します
  • SDL (Simple DyanmicMedia Layer) を利用することで受信した音声と映像を表示する機能を持っています

WebRTC Signaling Server Ayame

WebRTC Signaling Server Ayame

GitHub にオープンソースで公開している WebRTC のシグナリングサーバです。 Linux と macOS と Windows で動作します。

Ayame Labo

Ayame Labo

Ayame を時雨堂がサービスとして提供しています。

  • 無料で使えます
  • サインアップしないでも使えます
    • ただし TURN や認証機能などは利用できません
  • GitHub アカウントを持っていればすぐに利用できます
  • TURN の UDP / TCP / TLS を提供します
  • ルームに認証をかけられます
    • シグナリングキーを提供します
  • 認証ログを確認できます

Sora Labo

Sora Labo

商用製品の WebRTC SFU Sora を時雨堂が検証目的で無料で使えるサービスを提供しています。

  • 無料で利用可能です
  • 商用目的では使えません
  • すぐに試せるサンプルも用意してあります
  • 法人、アカデミックでの利用は申請必須です
  • GitHub アカウントを持っていればすぐに利用できます
  • TURN の UDP / TCP / TLS を提供します
  • チャネルに認証をかけられます
    • シグナリングキーを提供します
  • 同時接続数制限があります
  • 認証ログを確認できます
  • 統計情報を確認できます
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment