こんにちは! 2014年に連載した「WebRTCを使ってみよう!」シリーズのアップデート記事も番外編を含めて6回目となりました。2016年の最後として、実際の通信では欠かせないNAT越えと、企業ネットワークで使うために必要なFirewallを通過する方法について見ていきましょう。
NATを越えて
NATの役割
NAT(+IPマスカレード)は企業だけでなく、一般家庭でも使われています。ブロードバンドルーターやWiFiルーターでは、1つのグローバルIPアドレスを、複数のPCやデバイスで共有することができます。このとき、NATには2つの役割があります。
- インターネットにつながったグローバルなIPアドレスと、家庭内/社内のローカルなネットワークでのIPアドレスの変換
- 複数のPC/デバイスが同時に通信できるように、ポートマッピングによるポート変換
WebRTCでNAT越しに通信すること考えてみましょう。
ブラウザが知っている情報
- ローカルネットワークのIPアドレス
- 自分が使う(動的に割り振った)UDPポート
ブラウザでは分からない情報
- グローバルIPアドレス
- NATによってマッピングされた、外部に向けたUDPポート
例えば
次の架空の例を見てみましょう。二つのPCのブラウザでは、それぞれ自分のローカルIPアドレス、UDPは知ることができます。
- 左のPCのブラウザが分かること
- ローカルIPアドレス 192.168.10.11
- ローカルUDPポート 4001/UDP
- 右のPCのブラウザが分かること
- ローカルIPアドレス 10.2.100.51
- ローカルUDPポート 5001/UDP
しかし、NATの外側から見たグローバルの情報は自分では分かりません。
- 左のPCのブラウザが知らないこと
- グローバルIPアドレス 178.50.111.222
- グローバルUDPポート 51111/UDP
- 右のPCのブラウザが知らないこと
- グローバルIPアドレス 200.100.50.81
- グローバルUDPポート 52222/UDP
NATを越えてPeer-to-Peer通信を行うには、シグナリング処理でお互いに自分の知らないグローバルの情報を交換する必要があります。
STUNが教えてくれること
自分で知らない情報は、誰かから教えてもらうしかありません。それを可能にするのがSTUN(Session Traversal Utilities for NATs)です。
STUNの仕組みはシンプルです。NATで変換されたIPアドレス/ポートを、外側にいるSTUNサーバーに教えてもらいます。先ほどの例で言えば、このようになります。
自分のグローバル情報が分かったら、それをシグナリングサーバー経由で相手に渡します。
STUNサーバーから得られた情報は、シグナリングの過程でICE Candidateとして取得できます。これをシグナリングサーバーなどを経由して相手に送ることになります。(参考: シグナリングサーバーを動かそう、シグナリングを拡張して、複数人で通信してみよう )
首尾よくNATとポート変換を潜り抜ければ、Peer-to-Peerで通信を行うことができます。
STUNサーバーを使ってみよう
STUNサーバーを自分で動かすこともできますが、幸い公開されているSTUNサーバーがあります。まず、そちらを使ってみましょう。Googleが公開しているサーバーがよく利用されているようです。
- stun.l.google.com:19302
- stun1.l.google.com:19302
- stun2.l.google.com:19302
- stun3.l.google.com:19302
- stun4.l.google.com:19302
通常はどれか1つ指定すればOKですが、複数指定することもできます。それでは、実際に以前のソースの一部を修正してみましょう。
function prepareNewConnection(id) { let pc_config = {"iceServers":[ {"urls": "stun:stun.l.google.com:19302"}, {"urls": "stun:stun1.l.google.com:19302"}, {"urls": "stun:stun2.l.google.com:19302"} ]}; let peer = new RTCPeerConnection(pc_config); // ... 省略 ... }
さあ、これで接続を試してください。上手く行けば、例えば自宅と友人の家で通信が可能になっているはずです。
STUNでNATを越えられないとき
NATにはグローバルIPアドレスを共有するだけでなく、セキュリティ対策としての役割もあります。内部の端末を隠したり、通信できるポートを制限したり、一種の簡易Firewallとして利用されているケースもあります。その場合はFirewallの場合と同じく、次に説明するTURNを利用する必要があります。
またNATの構造によっては、接続先によって(今回の場合、STUNサーバーとPeer-to-Peerの通信相手)別のポートが割り当てられる Symmetric NAT という物があるようです。この場合もSTUNの仕組みでは通信することができません。やはりTURNの出番ということになります。
Firewallを越えたい
一般家庭のようにブロードバンドルーターなどでNATがある環境では、STUNを使えば通信が可能になります。
それに対して一般的な企業では、Firewallにより通信できるポートが制限されるケースが多いようです。STUNを使った場合でもUDPポートは動的に割り振られるままなので、通信をするためにはFirewallにとても大きな穴を空ける必要があります。それではきっとセキュリティ管理者に怒られてしまいます。
こんなケースに対応するのが、TURN(Traversal Using Relays around NAT)の仕組みです。先ほどのSTUNもTURNもどちらもWebRTCのために生まれたのではなく、以前からVoIPやネットワークゲームの世界で使われていたものです。
TURNの仕組み
TURNを使った通信では、TURNサーバが実際のストリームデータを受け渡す間に入ります。すべてのパケットをTURNサーバーがリレーすることになり、もはや厳密にはPeer-to-Peer通信ではなくなります。
ただしこの際、TURNサーバーではパケットをそのままリレーし、内容については一切変更はしません。
- 動画のデコード、エンコードは行わない
- データはそのまま受け渡し、両端のPeerで施された暗号は解除しない
そのため個人的には、アプリケーション的に見た場合にはPeer-to-Peerを維持していると考えていいと思っています。
TURNサーバーでは暗号化処理や動画のデコード/エンコーディングは行わないので、CPU負荷よりもネットワーク負荷が高くなりやすいです。
TURNサーバーを用意するには
それでは、実際にTURNサーバーを動かしてみましょう。2014年の記事ではrfc5766-turn-serverを使いましたが、今回はその後継であるcoturnを使います。coturnが生まれた背景は、こちらの記事に詳しく書かれています。
今回は、OSはUbuntu 16.04 LTSを使って導入してみました。 なるべくOSセットアップ直後の素の状態から自分でビルドしてみましょう。
自分でビルドする手順
まずは、依存ツールやライブラリを導入します。
sudo apt-get update sudo apt-get upgrade
sudo apt-get install gcc sudo apt-get install sqlite3 sudo apt-get install libssl-dev sudo apt-get install libevent-dev sudo apt-get install make
次にcoturnのソースを、こちらのサイトからダウンロードします。
私が試したときは4.5.0.4が最新でしたが、2016年12月現在は4.5.0.5が最新のようです。 利用したいバージョンのソースをダウンロードして、解凍してください。
wget http://turnserver.open-sys.org/downloads/v4.5.0.4/turnserver-4.5.0.4.tar.gz tar xvfz turnserver-4.5.0.4.tar.gz
それではビルドしてみましょう。
cd turnserver-4.5.0.4 ./configure make sudo make install
もし途中で依存ライブラリ不足でエラーが出た場合には、追加でインストールしてからビルドし直してみてください。ビルドに成功すると、Ubuntu16の場合は次の場所に関連ファイルがインストールされます。
- バイナリ → /usr/local/bin/turnserver
- 設定ファイル(ひな形) → /usr/local/etc/turnserver.conf.default
設定ファイルは適宜 turnserver.conf にコピーして、それを編集して利用してください。
※こちらの手順はcoturn 4.5.0.4 をビルドした際のものです。最新のものでは手順が変更されている可能性がありますので、もし何か見つけたらコメントいただけると嬉しいです。
coturn を動かすまで
coturnをTURNサーバーとして動かす際に、通信方法を2種類を使うことがことができます。
- TURN … 特定のUDPポートを利用する
- TURN over TCP … 特定のTCPポートを利用する
企業で使う場合は、後者を利用したいケースが多いと思います。今回は両方使えるように設定しましょう。
tunserver.conf の設定
まず、/usr/local/etc/turnserver.conf.default を /usr/local/etc/turnserver.conf にコピーし、それを編集します。たくさんの設定項目がありますが、TURNをWebRTCで使う際のポイントを見ておきましょう。
ポートの指定
# TURN listener port for UDP and TCP (Default: 3478).デフォルトでは3478/UDP を使いますが、それを変更して80/TCPを使うようにします。コメントアウトを外してポート番号80を指定してください。Note: actually, TLS & DTLS sessions can connect to the
"plain" TCP & UDP port(s), too - if allowed by configuration.
# listening-port=80
資格証明方法(クレデンシャル)
# Uncomment to use long-term credential mechanism.WebRTCで使うには、lt-cred-mech を有効にする必要があります。デフォルトではコメントアウトされているので、これを有効にしてください。ユーザアカウントは外部のDBを使うのが適切ですが、今回はシンプルに設定ファイルに直接記入しました。ご利用のシステムに合わせて、外部ファイルや外部DBをご利用ください。By default no credentials mechanism is used (any user allowed).
# lt-cred-mech
'Static' user accounts for long term credentials mechanism, only.
This option cannot be used with TURN REST API.
'Static' user accounts are NOT dynamically checked by the turnserver process,
so that they can NOT be changed while the turnserver is running.
#
user=username1:key1
user=username2:key2
OR:
user=username1:password1
user=username2:password2
user=user:password
同時にrealmも指定します。自分のサーバーのドメインに合わせて、適宜指定してください。
# The default realm to be used for the users when no explicitorigin/realm relationship was found in the database, or if the TURN
server is not using any database (just the commands-line settings
and the userdb file). Must be used with long-term credentials
mechanism or with TURN REST API.
# realm=coturn.yourdomain.com
通信プロトコルの指定(変更不要)
プロトコルの指定で no-tcp のコメントアウトを外すとTURN over TCPを利用しなくなります。今回はTCPを使いますので、コメントアウトのままにしておきます。もしUDPを使いたくない場合は no-udp のコメントアウトを外してください。Uncomment if no UDP client listener is desired.
By default UDP client listener is always started.
#
no-udp
Uncomment if no TCP client listener is desired.
By default TCP client listener is always started.
#
no-tcp
Uncomment if no TLS client listener is desired.
By default TLS client listener is always started.
#
no-tls
Uncomment if no DTLS client listener is desired.
By default DTLS client listener is always started.
#
no-dtls
以前試したときはブラウザでの実装の不十分でtlsやdtlsを利用できなかったのですが、今回試したところデフォルトのまま(有効のまま)で通信できました。もしうまく通信できない場合は、コメントアウトを消してtlsやdtlsを無効にして試してみてください。
coturnを起動しよう
それではcoturnを起動しましょう。今回は80ポートを利用しているため、環境によってはroot権限が必要となりますので、sudoを利用してください。
sudo /usr/local/bin/turnserver -o -v -c /usr/local/etc/turnserver.conf
ここで指定しているコマンドライン引数は次の意味です。
- -o .. デーモンとして起動
- -v .. verboseモード。ログを多く出す
- -c .. 設定ファイルのパスを指定
クライアントのソースを修正しよう
それでは準備したcoturnサーバーを使うように、にクライアント側のソースを修正しましょう。STUNで修正した部分と同じ個所になります。 coturnサーバーが coturn.yourdomain.com で、ポート80で動いていると仮定します。その情報をPeerConnectionに教えてあげましょう。
function prepareNewConnection(id) { let pc_config = {"iceServers":[ {"urls": "stun:coturn.yourdomain.com:80"}, {"urls":"turn:coturn.yourdomain.com:80?transport=udp", "username":"user", "credential":"password"}, {"urls":"turn:coturn.yourdomain.com:80?transport=tcp", "username":"user", "credential":"password"} ]}; let peer = new RTCPeerConnection(pc_config); // ... 省略 ... }
また、企業内から外部のシグナリングサーバーを利用するには、そちらも80/TCPや443/TCPを使う必要があるかもしれません。環境に応じてご準備ください。
ここまでできたら、無事Firewallを越えて通信ができるはずです。企業内のブラウザと、外のブラウザ(自宅のPCやアンドロイドのブラウザ)で試してみてください。
最後に
WebRTC入門も2016年中になんとか予定していた記事を書き終えることができました。書いている間にもどんどんブラウザがバージョンアップし、新しいAPIが使えるようになっています。最新情報はブラウザのリリースノートやブログをご覧いただくとよいと思います。
- WebRTC.org Chrome Report Notes
- Mozilla blog Advancing WebRTC
みなさんもWebRTCを活用した素敵なアプリケーションを作ってくださいね。ここまでお付き合いいただき、どうもありがとうございました!