HTML5Experts.jp

そして壁の向こうへ。 NAT/Firewallを越えて通信しよう―WebRTC入門2016

連載: WebRTC入門2016 (6)

こんにちは! 2014年に連載した「WebRTCを使ってみよう!」シリーズのアップデート記事も番外編を含めて6回目となりました。2016年の最後として、実際の通信では欠かせないNAT越えと、企業ネットワークで使うために必要なFirewallを通過する方法について見ていきましょう。

NATを越えて

NATの役割

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

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

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

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

例えば

次の架空の例を見てみましょう。二つのPCのブラウザでは、それぞれ自分のローカルIPアドレス、UDPは知ることができます。

しかし、NATの外側から見たグローバルの情報は自分では分かりません。

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が公開しているサーバーがよく利用されているようです。

通常はどれか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-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の場合は次の場所に関連ファイルがインストールされます。

設定ファイルは適宜 turnserver.conf にコピーして、それを編集して利用してください。

※こちらの手順はcoturn 4.5.0.4 をビルドした際のものです。最新のものでは手順が変更されている可能性がありますので、もし何か見つけたらコメントいただけると嬉しいです。

coturn を動かすまで

coturnをTURNサーバーとして動かす際に、通信方法を2種類を使うことがことができます。

企業で使う場合は、後者を利用したいケースが多いと思います。今回は両方使えるように設定しましょう。

tunserver.conf の設定

まず、/usr/local/etc/turnserver.conf.default を /usr/local/etc/turnserver.conf にコピーし、それを編集します。たくさんの設定項目がありますが、TURNをWebRTCで使う際のポイントを見ておきましょう。

ポートの指定

# 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

デフォルトでは3478/UDP を使いますが、それを変更して80/TCPを使うようにします。コメントアウトを外してポート番号80を指定してください。

資格証明方法(クレデンシャル)

# Uncomment to use long-term credential mechanism.

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

WebRTCで使うには、lt-cred-mech を有効にする必要があります。デフォルトではコメントアウトされているので、これを有効にしてください。ユーザアカウントは外部のDBを使うのが適切ですが、今回はシンプルに設定ファイルに直接記入しました。ご利用のシステムに合わせて、外部ファイルや外部DBをご利用ください。

同時にrealmも指定します。自分のサーバーのドメインに合わせて、適宜指定してください。

# The default realm to be used for the users when no explicit

origin/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

通信プロトコルの指定(変更不要)


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

プロトコルの指定で no-tcp のコメントアウトを外すとTURN over TCPを利用しなくなります。今回はTCPを使いますので、コメントアウトのままにしておきます。もしUDPを使いたくない場合は no-udp のコメントアウトを外してください。

以前試したときはブラウザでの実装の不十分でtlsやdtlsを利用できなかったのですが、今回試したところデフォルトのまま(有効のまま)で通信できました。もしうまく通信できない場合は、コメントアウトを消してtlsやdtlsを無効にして試してみてください。

coturnを起動しよう

それではcoturnを起動しましょう。今回は80ポートを利用しているため、環境によってはroot権限が必要となりますので、sudoを利用してください。

sudo /usr/local/bin/turnserver -o -v -c /usr/local/etc/turnserver.conf

ここで指定しているコマンドライン引数は次の意味です。

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

それでは準備した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を活用した素敵なアプリケーションを作ってくださいね。ここまでお付き合いいただき、どうもありがとうございました!