Mac版Chromeで、Firewallが越えられない話

## はじめに
WebRTCでFirewall/NATを越えて通信するために、TURNと呼ばれる仕組みを使います。80/TCPや443/TCPしか通さないFirewall越しでも、TURN over TCP を使えば、[壁を越えて通信する](http://html5experts.jp/mganeko/5554/)ことができます。
これでめでたしめでたし、だと思っていたのですが、自社内でFirewallを越えられないケースがありました。その1つケースでどうやら原因が判明したので、ここにまとめておきます。

## TURN over TCP を利用するには
TURN over TCP が利用できるのは、今のところ Chrome だけのようです。Firefoxでは（私の知る限り）まだ利用できません。[こちらのNYのイベントの資料](http://www.slideshare.net/alexpiwi5/practical-webrtc-from-api-to-solution-webrtc-summit-2014-nyc)の22,23ページでも、Firefoxでは上手く動かないと記述されています。

TURN over TCP を使うためには、PeerConnectionを生成する際にその情報を指定します。

```js:
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);
  }
 
  //...省略...
}
```

これでブラウザとTURNサーバーの間は、80/TCPで通信してくれます。

## Chromeで動かしてみる
### 上手く行くケース (Windows + Chrome v39)
弊社には複数のオフィスがありますが、東京オフィス、四国オフィスの2カ所で確認できています。
![chrome_win_firewall_ok.png](https://qiita-image-store.s3.amazonaws.com/0/19403/d4d78fd3-4406-515d-c9ab-45374710c9ea.png)


### だめなケース (Mac OS X + Chrome v39)
こちらはなぜか東京オフィスでは上手く行きません。四国オフィスでは上手く行きます。
![chrome_mac_firewall_ng.png](https://qiita-image-store.s3.amazonaws.com/0/19403/ec700970-32c1-5ba8-14ec-f7d17ccb6f1a.png)
東京オフィスの4台のMac(OS X 10.8 と OS X 10.9)で試しましたが、いずれも上手く行きません。マシン単体の問題ではなく、ネットワーク構成に伴う問題なのでしょうか？

## chrome://net-internals を使う
途方に暮れていたところ、ネットワークに強いメンバーから、「chrome://net-internals　で見たらわかるかも」と言われました。試しに [chrome://net-internals](chrome://net-internals) を開いてみましたが、私にはさっぱり分かりません。なのでログを取得してその人に調べてもらいました。

### 通信ログの取得
chrome://net-internals　の詳細な使い方は省略しますが、ログの取得、表示の流れはこんな感じです。

* 事前に別のタブで chrome://net-internals/ を開いておく（Captureが開始される）
* WebRTCの通信を開始する
* 適当なところで、chrome://net-internals のCaptureを止める
* chrome://net-internals/#export で、通信内容をファイルに保存する
* chrome://net-internals/#import で、ファイルに保存した内容を読み込む
* chrome://net-internals/#events で、通信内容を表示する

### Macで上手く通信できないケース
通信のログ（event）はたくさんあるのですが、気になる箇所がありました。

```text:

23401: SOCKET
133.xxx.xxx.xxx:443 		// <--- TURN severのアドレス
Start Time: 2014-12-03 15:04:17.059

t=21885 [st=    0] +SOCKET_ALIVE  [dt=15199+]
                    --> source_dependency = 23397 (CONNECT_JOB)
t=21885 [st=    0]   +TCP_CONNECT  [dt=0]
                      --> address_list = ["10.xxx.xxx.xxx:8080","10.xxx.xxx.xxx:8080"]  // <-- proxy
t=21885 [st=    0]      TCP_CONNECT_ATTEMPT  [dt=0]
                        --> address = "10.xxx.xxx.xxx:8080"   // <-- proxyのアドレス
t=21885 [st=    0]   -TCP_CONNECT
                      --> source_address = "127.0.0.1:58931"  //  mac localhost??
t=21886 [st=    1]   +SOCKET_IN_USE  [dt=15198+]
                      --> source_dependency = 23396 (CONNECT_JOB)
t=21886 [st=    1]     +HTTP_TRANSACTION_TUNNEL_SEND_REQUEST  [dt=0]
t=21886 [st=    1]        HTTP_TRANSACTION_SEND_TUNNEL_HEADERS
                          --> CONNECT 133.xxx.xxx.xxx:443 HTTP/1.1		// <--- TURN severのアドレス
                              Host: 133.xxx.xxx.xxx:443				// <--- TURN severのアドレス
                              Proxy-Connection: keep-alive
t=21886 [st=    1]        HTTP_TRANSACTION_SEND_REQUEST_HEADERS
                          --> CONNECT 133.xxx.xxx.xxx:443 HTTP/1.1		// <--- TURN severのアドレス
                              Host: 133.xxx.xxx.xxx:443				// <--- TURN severのアドレス
                              Proxy-Connection: keep-alive
t=21886 [st=    1]        SOCKET_BYTES_SENT
                          --> byte_count = 97
t=21886 [st=    1]     -HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
t=21886 [st=    1]     +HTTP_TRANSACTION_TUNNEL_READ_HEADERS  [dt=32]
t=21886 [st=    1]       +HTTP_STREAM_PARSER_READ_HEADERS  [dt=32]
t=21918 [st=   33]          SOCKET_BYTES_RECEIVED
                            --> byte_count = 39
t=21918 [st=   33]       -HTTP_STREAM_PARSER_READ_HEADERS
t=21918 [st=   33]        HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS
                          --> HTTP/1.0 200 Connection established
t=21918 [st=   33]     -HTTP_TRANSACTION_TUNNEL_READ_HEADERS
t=21918 [st=   33]     +SOCKET_IN_USE  [dt=15166+]
                        --> source_dependency = 23389 (SOCKET)
t=37084 [st=15199] 

```
このイベントにはなぜか source_address = "127.0.0.1:58931" という箇所があります。どうやら自分のIPアドレスの様なのですが、なぜか他のマシンと通信できない 127.0.0.1 のアドレスが使われています。

### Windowsで上手く通信できるケース
対してWindowsでは、該当箇所はこのような内容です。

```text:
340538: SOCKET
133.xxx.xxx.xxx:443			// <--- TURN severのアドレス
Start Time: 2014-12-03 15:10:25.880

t=  450 [st=    0] +SOCKET_ALIVE  [dt=14435+]
                    --> source_dependency = 340534 (CONNECT_JOB)
t=  450 [st=    0]   +TCP_CONNECT  [dt=3]
                      --> address_list = ["10.xxx.xxx.xxx:8080","10.xxx.xxx.xxx:8080"]   // <--- proxy 
t=  450 [st=    0]      TCP_CONNECT_ATTEMPT  [dt=3]
                        --> address = "10.xxx.xxx.xxx:8080" // <--- proxyのアドレス
t=  453 [st=    3]   -TCP_CONNECT
                      --> source_address = "10.xxx.xxx.xxx:52885"    // <--- Windows PCのアドレス
t=  453 [st=    3]   +SOCKET_IN_USE  [dt=14432+]
                      --> source_dependency = 340533 (CONNECT_JOB)
t=  453 [st=    3]     +HTTP_TRANSACTION_TUNNEL_SEND_REQUEST  [dt=1]
t=  453 [st=    3]        HTTP_TRANSACTION_SEND_TUNNEL_HEADERS
                          --> CONNECT 133.xxx.xxx.xxx:443 HTTP/1.1  	// <--- TURN severのアドレス
                              Host: 133.xxx.xxx.xxx:443			// <--- TURN severのアドレス
                              Proxy-Connection: keep-alive
t=  453 [st=    3]        HTTP_TRANSACTION_SEND_REQUEST_HEADERS
                          --> CONNECT 133.xxx.xxx.xxx:443 HTTP/1.1	// <--- TURN severのアドレス
                              Host: 133.xxx.xxx.xxx:443		// <--- TURN severのアドレス
                              Proxy-Connection: keep-alive
t=  454 [st=    4]        SOCKET_BYTES_SENT
                          --> byte_count = 97
t=  454 [st=    4]     -HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
t=  454 [st=    4]     +HTTP_TRANSACTION_TUNNEL_READ_HEADERS  [dt=36]
t=  454 [st=    4]       +HTTP_STREAM_PARSER_READ_HEADERS  [dt=36]
t=  490 [st=   40]          SOCKET_BYTES_RECEIVED
                            --> byte_count = 39
t=  490 [st=   40]       -HTTP_STREAM_PARSER_READ_HEADERS
t=  490 [st=   40]        HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS
                          --> HTTP/1.0 200 Connection established
t=  490 [st=   40]     -HTTP_TRANSACTION_TUNNEL_READ_HEADERS
t=  490 [st=   40]     +SOCKET_IN_USE  [dt=14395+]
                        --> source_dependency = 340490 (SOCKET)
t=14885 [st=14435] 

```

問題のアドレスは、きちんと自分のPCの社内ネットワーク内でのIPアドレスになっています。四国オフィスで上手く通信できるMacも、この箇所はちゃんとIPアドレスが取得できていました。これはかなり怪しいですね。

## ChrominumeのIssue
「chrome issue "127.0.0.1" mac」で検索すると、ChrominumeのIssue [267101](https://code.google.com/p/chromium/issues/detail?id=267101)にそれらしい記述が見つかりました。
#### myIpAddress() reporting 127.0.0.1
* <b>What is the expected result?</b>
 * it should come back with the local ip address of 192.168.2.1 but for some reason on my mac it comes back with 127.0.0.1


自分のIPアドレスのはずが127.0.0.1 になってしまう、というあたり、臭いですね。
他にも mac や TURN 関連のIssueを探すと、どうやら Chrome Canary では修正が含まれているようです。

## Chrome Canary v41
早速Chrome Canaryをダウンロードして試してみます。（今回はv41でした）
すると、何とあっさりつながってしましました。この数か月悩んだのは何だったんだ...。

### Mac + Chrome Canaryでうまく行くケース 

![chrome_canary_mac_firewall_ok.png](https://qiita-image-store.s3.amazonaws.com/0/19403/c322ef8e-7773-0da8-d5cb-72c1738cf923.png)

現在のChrome安定版はv39なので、v41がリリースされるのは半年後ぐらいでしょうか。

### 通信イベントは
念のため chrome://net-internals　で、Chanaryの場合も見てみます。問題の箇所は、ちゃんと自分のIPアドレスが取得できていました。

```text:
3101: SOCKET
133.xxx.xxx.xxx:443		// <--- TURN sever
Start Time: 2014-12-08 14:52:09.842

t= 4726 [st=    0] +SOCKET_ALIVE  [dt=12781+]
                    --> source_dependency = 3097 (CONNECT_JOB)
t= 4726 [st=    0]   +TCP_CONNECT  [dt=1]
                      --> address_list = ["10.xxx.xxx.xxx:8080","10.xxx.xxx.xxx:8080"]	 // <-- proxy
t= 4726 [st=    0]      TCP_CONNECT_ATTEMPT  [dt=1]
                        --> address = "10.xxx.xxx.xxx:8080"				 // <-- proxy
t= 4727 [st=    1]   -TCP_CONNECT
                      --> source_address = "10.xxx.xxx.xxx:49903"			 // <--- mac IP address
t= 4727 [st=    1]   +SOCKET_IN_USE  [dt=12780+]
                      --> source_dependency = 3096 (CONNECT_JOB)
t= 4727 [st=    1]     +HTTP_TRANSACTION_TUNNEL_SEND_REQUEST  [dt=0]
t= 4727 [st=    1]        HTTP_TRANSACTION_SEND_TUNNEL_HEADERS
                          --> CONNECT 133.xxx.xxx.xxx:443 HTTP/1.1			// <--- TURN sever
                              Host: 133.xxx.xxx.xxx:443					// <--- TURN sever
                              Proxy-Connection: keep-alive
t= 4727 [st=    1]        HTTP_TRANSACTION_SEND_REQUEST_HEADERS
                          --> CONNECT 133.xxx.xxx.xxx:443 HTTP/1.1			// <--- TURN sever
                              Host: 133.xxx.xxx.xxx:443					// <--- TURN sever
                              Proxy-Connection: keep-alive
t= 4727 [st=    1]        SOCKET_BYTES_SENT
                          --> byte_count = 97
t= 4727 [st=    1]     -HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
t= 4727 [st=    1]     +HTTP_TRANSACTION_TUNNEL_READ_HEADERS  [dt=23]
t= 4727 [st=    1]       +HTTP_STREAM_PARSER_READ_HEADERS  [dt=23]
t= 4750 [st=   24]          SOCKET_BYTES_RECEIVED
                            --> byte_count = 39
t= 4750 [st=   24]       -HTTP_STREAM_PARSER_READ_HEADERS
t= 4750 [st=   24]        HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS
                          --> HTTP/1.0 200 Connection established
t= 4750 [st=   24]     -HTTP_TRANSACTION_TUNNEL_READ_HEADERS
t= 4750 [st=   24]     +SOCKET_IN_USE  [dt=12757+]
                        --> source_dependency = 3089 (SOCKET)
t= 4750 [st=   24]        SOCKET_BYTES_SENT
                          --> byte_count = 28
t= 4773 [st=   47]        SOCKET_BYTES_RECEIVED
                          --> byte_count = 112
```

めでたし、めでたし。
（※真の解決は、同じMacでも上手く行く場合とダメな場合の原因を切り分けることなのですが...。もし分かったらのまたの機会に）

## 終わりに
今回はMac OS Xの話でしたが、Linuxの場合はどうなんでしょうね？ こんな Issue [386221](https://code.google.com/p/chromium/issues/detail?id=386221)もあるので、同じようなことが発生してそうです。Linuxで試した方の情報もお待ちしています。