HTML5Experts.jp

壁を越えろ!WebRTCでNAT/Firewallを越えて通信しよう

こんにちは!がねこまさしです。前回は複数人の同時通話まで実現しました。社内で使うには十分なレベルです。
しかし本格的な企業ユースとなると、まだまだ障害があります。会社と家、自社と別の会社さんなど、実際に通信しようとするとNATやFirewallといった壁が立ちはだかります。

NATを越えよう

NATの役割は

NAT(+IPマスカレード)は企業だけでなく、一般家庭でも使われています。ブロードバンドルーターやWiFiルーターでは、1つのグローバルIPアドレスを、複数のPCやデバイスで共有することができます。このとき、NATには2つの役割があります。

WebRTCでNAT越しに通信すること考えてみましょう。

ブラウザが知っている情報

ブラウザでは分からない情報

Peer-to-Peer通信を行うには、シグナリング処理でお互いに(ローカルネットワーク内の情報ではなく)インターネット側から見た情報を通知する必要があります。

ブラウザーが、インターネット側から見た情報を知るための仕組みが、STUNになります。

STUNの仕組みは

STUNの仕組みは意外とシンプルです。インターネット側にいる誰か(STUNサーバー)に、自分(ブラウザ)がどう見えるか教えてもらうだけです。

STUNは元々WebRTCのために作られた仕組みではなく、より汎用的なUDP通信の補助として生まれました。VoIPやネットワークゲームの世界でも使われているようです。

自分を外側から見た情報が分かったら、それをシグナリングサーバー経由で通信相手に渡します。

お互いの情報が伝わったら、そこを目掛けて通信を行います。間にNATが挟まりますが、ポートを直接マッピングしているのであくまでもPeer-to-Peerです。

STUNサーバーを動かそう

それでは実際にSTUNサーバーを動かしてみましょう。Linuxで動作するオープンソースのものがあるので、そちらを使います。
rfc5766-turn-server
このサーバはSTUNだけでなく、後程説明するTURNにも対応しています。
DownloadページからOSに合わせたgzファイルをダウンロードし、INSTALL手順に従ってビルド、インストールしてください。参考までに以前私が実施した手順を載せておきます。※ちょっと古いバージョンです。最新のものを取得してインストールしてください。

$ wget https://rfc5766-turn-server.googlecode.com/files/turnserver-3.2.1.4-CentOS6-x86_64.tar.gz
$ tar zxvf turnserver-3.2.1.4-CentOS6-x86_64.tar.gz
$ cd turnserver-3.2.1.4
$ ./install.sh
CentOSの場合、以下の場所に導入されました。
バイナリ →  /usr/bin/turnserver 
設定ファイル →  /etc/turnserver/*.conf

次に設定ファイル( /etc/turnserver/turnserver.conf )を見てみましょう。ポイントとなるのは次の箇所です。


STUN/TURNサーバーの接続待ポート番号。デフォルトは3478です。

コメントアウトを外して数値を指定すれば、任意のポートに変更できます。

#

TURN listener port for UDP and TCP (Default: 3478).

Note: actually, TLS & DTLS sessions can connect to the

"plain" TCP & UDP port(s), too - if allowed by configuration.

#

listening-port=3478

TCPとUDPの両方で待ち受けできますが、STUNはUDPのみが有効なので注意が必要です。※つまりUDPが通らない環境ではSTUNは使えません。


このサーバーはデフォルトでSTUN/TURNの両方をサポートしています。

STUNのみ使いたい場合は、stun-only のコメントアウトを外します。

#

Option to suppress TURN functionality, only STUN requests will be processed.

Run as STUN server only, all TURN requests will be ignored.

By default, this option is NOT set.

#

stun-only

STUNはPeer-to-Peer通信が始まればサーバーのCPU負荷、ネットワーク負荷はかかりません。それに対して後述するTURNでは特にネットワーク負荷がかかります。サーバーを借りていてネットワーク通信量で課金されるようなケースでは、stun-onlyを設定してTURNは無効にしておいた方が良いかもしれません。

設定がすんだらSTUNサーバーを起動してみましょう。画面にエラーが出なければ無事に起動成功です。エラーが出る場合は turnserver.conf を確認してみてください。
/usr/bin/turnserver -o -v -c /etc/turnserver/turnserver.conf

クライアントのソースを修正しよう

ここまでで準備したSTUNサーバーを使うように、クライアント側のソースを修正しましょう。前回のソースの一部を変更します。
STUNサーバーが stun.yourdomain.com で、デフォルトのポート3478で動いていると仮定します。その情報をPeerConnectionに教えてあげます。

function prepareNewConnection(id) {
  var pc_config = {"iceServers":[ {"url":"stun:stun.yourdomain.com:3478"} ]};
  var peer = null;
  try {
    peer = new webkitRTCPeerConnection(pc_config);
  } catch (e) {
    console.log("Failed to create PeerConnection, exception: " + e.message);
  }

//...省略... }

さあ、これで接続を試してください。上手く行けば、自宅と友人の家で通信が可能になっているはずです。

STUNでNATを越えられないとき

NATにはグローバルIPアドレスを共有するだけでなく、セキュリティ対策としての役割もあります。内部の端末を隠したり、通信できるポートを制限したり、一種の簡易Firewallとして利用されているケースもあります。その場合はFirewallの場合と同じく、次に説明するTURNを利用する必要があります。
また、NATの構造によっては、接続先によって(今回の場合、STUNサーバーとPeer-to-Peerの通信相手)別のポートが割り当てられる Symmetric NAT という物があります。この場合もSTUNの仕組みでは通信することができません。やはりTURNの出番ということになります。

Firewallを越えよう

一般家庭のようにブロードバンドルーターなどでNATがある環境では、STUNを使えば通信が可能になります。次は一般的な企業で使えるようにしましょう。
企業ではFirewallが設置されているケースがほとんどです。その場合、外部と通信できるポートも制限されます。STUNではUDPポートは動的に割り振られるままなので、Firewallにとても大きな穴を空けないと通信ができません。きっとセキュリティ管理者に怒られてしまいます。こんなケースに対応するのが、TURNの仕組みです。TURNもWebRTCのために生まれたのではなく、VoIPやネットワークゲームの世界で使われていたものです。

TURNの仕組みは

TURNを使った通信では、TURNサーバが実際のストリームデータを受け渡す間に入ります。すべてのパケットをTURNサーバーがリレーすることになり、もはやPeer-to-Peer通信ではなくなります。この際TURNサーバーでは動画のエンコーディングは行わないので、CPU負荷よりもネットワーク負荷が高くなりやすいです。

Firewallに穴を空けよう

TURNを使うには、Firewallに1つ穴を空ける必要があります。標準では3478/UDPを使うので、そのポートが通過可能になるように設定して(してもらって)ください。
もう1つ、シグナリングサーバーと通信するための穴も空ける必要があります。例えば9000番を使うのであれば、9000/TCPも同様に通過可能になるように設定して(してもらって)ください。
会社間で通信するのは、両方の会社でFirewallに穴を空ける必要があります。
※これを読んで「結局Firewallをいじるのかよー」とがっかりした人もいますよね? Firewallをいじらない方法もあるので、最後までお楽しみに。

TURNサーバーを動かそう

TURNサーバーは先ほどSTUNサーバーとしてインストールしたrfc5766-turn-server
がそのまま使えます。/etc/turnserver/turnserver.conf の設定を変更してTURNとして使える様にします。


STUN/TURNサーバーの接続待ポート番号。デフォルトは3478です。

コメントアウトを外して数値を指定すれば、任意のポートに変更できます。

#

TURN listener port for UDP and TCP (Default: 3478).

Note: actually, TLS & DTLS sessions can connect to the

"plain" TCP & UDP port(s), too - if allowed by configuration.

#

listening-port=3478

デフォルトのポートは3478ですが、自由に設定できます。


UDP/IPの他に、rfc5766-turn-serverではTLS/DTLSでの通信も可能です(デフォルトで有効になっています)

残念ながらブラウザー側の実装がまだ不十分なので、この機能は無効にしておく(コメントアウトを外す)ことをお勧めします

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は今回は使わない設定にしておきます。また先ほどのSTUNを動かす際に stun-only を設定した場合は、再びコメントアウトしてTURNも使える様にして下さい。

Option to suppress TURN functionality, only STUN requests will be processed.

Run as STUN server only, all TURN requests will be ignored.

By default, this option is NOT set.

#

stun-only


そして、WebRTCでTURNを使う際のキモはこちらの設定です。


認証の方法を選択します。次の3種類があります。

no-auth

st-cred-mech (short-term credential mechanism)

lt-cred-mech (long-term credential mechanism)

WebRTCでは lt-cred-mechを使用する必要がありますので、その行のコメントアウトを外します。

Uncomment to use long-term credential mechanism.

By default no credentials mechanism is used (any user allowed).

This option can be used with either flat file user database or

PostgreSQL DB or MySQL DB or Redis DB for user keys storage.

# lt-cred-mech

同時に、realmも設定が必要になります。お忘れなく。

#

Realm for long-term credentials mechanism and for TURN REST API.

# realm=turn.yourdomain.com

なかなか lt-cred-mech が必須だとは分からず、とても長い間悩んでしまいました。lt-cred-mech で使用するアカウント情報はPostgreSQL, MySQL, Redisなどで管理できますが、今回はシンプルにファイル管理にします。

ユーザーアカウントを定義するファイル名を指定します。

#

'Dynamic' user accounts database file name.

Only users for long-term mechanism can be stored in a flat file,

short-term mechanism will not work with option, the short-term

mechanism required PostgreSQL or MySQL or Redis database.

'Dynamic' long-term user accounts are dynamically checked by the turnserver process,

so that they can be changed while the turnserver is running.

#

Default file name is turnuserdb.conf.

# userdb=/etc/turnserver/turnuserdb.conf

/etc/turnserver/turnuserdb.conf を使うことにしたので、その内容も変更します。


This file can be used as user accounts storage for long-term credentials mechanism.

#

username1:key1

username2:key2

OR:

username1:password1

username2:password2

yourid:yourpassword

例としてユーザID: yourid 、パスワード: yourpassword としました。 ※実際にはもっと強度の高いパスワードにしてくださいね。

これで turnserverを再起動すれば、TURNでの通信が有効になります。
/usr/bin/turnserver -o -v -c /etc/turnserver/turnserver.conf
※ちなみに turnserverを安全に停止させる手段が分かりません。仕方がないので kill で殺しています…。

クライアントのソースを修正しよう

ここまでで準備したTURNサーバーを使うように、クライアント側のソースを修正しましょう。STUNで修正した部分と同じ個所になります。 STUN/TURNサーバーが turn.yourdomain.com で、デフォルトのポート3478で動いていると仮定します。その情報をPeerConnectionに教えてあげます。(STUNとTURNの両方を候補にすることができます)

function prepareNewConnection(id) {
  var pc_config = {"iceServers":[
   {"url":"stun:turn.yourdomain.com:3478"},
   {"url":"turn:turn.yourdomain.com:3478", "username":"yourid", "credential":"yourpassword"}
  ]};
  var peer = null;
  try {
    peer = new webkitRTCPeerConnection(pc_config);
  } catch (e) {
    console.log("Failed to create PeerConnection, exception: " + e.message);
  }

//...省略... }

さあ、これで接続を試してください。上手く行けば、会社と自宅、あるいは会社と友人の会社で通信が可能になります。

Firewallはそのままで

実際の企業ではセキュリティ上の制約や手続き上の問題で、Firewallに穴を空けるのが大変なことも多々あります。お客様の会社だったらなおさらですよね。そんな時のために、TURN over TCP という規格があり、rfc5766-turn-server と Chrome の両方ともサポートしてます。これを使えば、Firewallはそのままで、通信が可能になります。

TURNサーバを設定し直そう


一つのポート番号で、UDPとTCPの両方を待ち受けすることができます。

HTTPと同じ、80番を設定します。

#

TURN listener port for UDP and TCP (Default: 3478).

Note: actually, TLS & DTLS sessions can connect to the

"plain" TCP & UDP port(s), too - if allowed by configuration.

# listening-port=80

設定が終わったら、turnserverを再起動してください。

クライアントのソースを修正しよう

function prepareNewConnection(id) {
  var pc_config = {"iceServers":[
   {"url":"stun:turn.yourdomain.com:80"},
   {"url":"turn:turn.yourdomain.com:80?transport=udp", "username":"yourid", "credential":"yourpassword"},
   {"url":"turn:turn.yourdomain.com:80?transport=tcp", "username":"yourid", "credential":"yourpassword"}
  ]};
  var peer = null;
  try {
    peer = new webkitRTCPeerConnection(pc_config);
  } catch (e) {
    console.log("Failed to create PeerConnection, exception: " + e.message);
  }

//...省略... }

ここでは省略しますが、シグナリングサーバーも 80/TCP で動かす必要があります。サーバー側のNode.jsのポート番号と、クライアント側のsocket.ioのつなぎ先のポート番号を80番に変更してください。※Webサーバー、シグナリングサーバー、TURNサーバーの3つをすべて80/TCPで動かすので、サーバーを3つ別々に立てる必要があります。頑張ってください。

さあ、これで最後です。ブラウザをリロードしてください。きっと壁を越えて対話ができることと思います。

最後に

これまで全5回、WebRTCの使いかたを説明してきました。開発者向けにコードを一から書いてきましたが、世の中には便利なライブラリやサービスも数多くあります。

こちらを利用するのもありですね。日本でWebRTCが盛り上がって、企業ユースでも認知されるのを期待しています。
おつきあいいただき、どうもありがとうございました。