岩瀬 義昌

[翻訳] WhatsAppはWebRTCを使ってる?キャプチャから解析してみた! – 原題:What’s up with WhatsApp and WebRTC? –

本記事は、webrtcHacksに英語で掲載されている記事を、webrtcHacks様の許可を得た上で、翻訳&掲載する記事となります。修正・更新・コメント等がございましたら、webrtchacksまでお願いいたします。

This article originally appeared in English at webrtcHacks and has been translated with webrtcHack’s permission for posting to html5experts.jp in Japanese. Please visit webrtchacks for edits, updates, and comments.

イントロダクション

このブログ(webrtchacks)にある昔の記事の1つは、Amazon MaydayをWiresharkで分析して、内部でWebRTCを使ってるか確かめよう、というものだった。当時はWebRTCが出始めの段階だったので、大規模なWebRTC利用例は、WebRTCコミュニティにとって重要なマイルストーンだったんだ。

最近では、Philipp Hancke(Fippoとして知られる)がいくつかの素晴らしい記事を出していて、Google hangoutsや、MozillaのHelloを分析している。これらの分析は、WebRTCが大規模にデプロイして上手くいくかってのを実証してくれている。また、デベロッパやアーキテクト向けには、どうやったらWebRTCのサービスを作れるか、という点についての貴重な洞察になる。

こういう記事は最高だから、もっと欲しいのは当たり前だよね。

ということで、嬉しいことにもう1つが記事できたよ!WebRTCの事実に基づく情報を広めるために、GoogleのWebRTCチームが&yet(Fippoの雇用主)に、詳細なリバースエンジニアリングを加えつつ、分析レポートを書くようお願いしたんだ。Philippは承諾してくれて、分かったことなどをwebrtcHacksで書いてくれることになった!もちろん、この分析はかなり時間がかかる。分析は大変だけど、多くの人に見てもらうことはもっと大変なので、webrtcHacksではいつもの公平で非営利な感じで手伝うことにしたよ。

じゃあ、FippoのWhatsApp音声アプリの分析を見てみよう。

{ “導入の執筆者” : “chad” }

Philipp Hancke deconstructs WhatsApp to search for WebRTC

Philipp Hancke deconstructs WhatsApp to search for WebRTC

TechCrunchあたりで噂になった後、WhatsAppはAndroid向けに音声通話をローンチした。これはWebRTC業界で一気に話題になって、Tsahi Levent-Leviも加わって、 この話題についての議論が白熱した。残念なことに、ThahiのブログBlogGeek.Meのコメント欄は小さすぎたので、webRTCHacksで記事として書くことにしたんだ。

ある時、いくつかのモバイルサービス向けの分析をしようと思っていたのと、Google WebRTCチームのサポートもあって、WhatsAppのアプリを複数のシナリオでとったWiresharkのキャプチャを見るのに、数日間の時間を使うことができた。

最初は、単にキャプチャをして中を見ようとしたんだけど(将来的にこれも記事で説明するかも)、面白い情報がもっとたくさんあって、学ぶべきことが多そうだってことがわかってきた。だから、15ページのレポートを書いたし、それはここから入手できる。かなり長いパケットの中の話で、エンジニア以外にはつまらないだろうから、この記事で要約してみるよ。

要約

WhatsAppはPJSIPライブラリを、VoIP実装のために使ってる。キャプチャを見ると、DTLSなんかなくて、SDESを使ってるように見える。(詳しくはVictorのこの記事を見てね)STUNは使われてるんだけど、STUNのバインディングリクエストにはICE関連の属性がない。RTPとRTCPは同じポートで多重化されてる。

Audioコーデックは完全には分からない。サンプリングレートは16kHzで、コーデックの帯域はだいたい20kbit/毎秒ぐらいだ。これはミュート時もかわらない。

stringsツールを使って、バイナリを観察してみたら、PJSIPといくつかの文字列が出てきて、その文字列から、webrtc.orgの音声エンジンにあるいくつかの要素(アコースティックエコーキャンセラ、AECM、ゲイン制御、ノイズ抑制、ハイパスフィルタとか)を利用してることがわかった。

WebRTCとの比較

機能WebRTC/RTCWebの仕様WhatsApp
SEDSSDES使わないたぶんSDES
ICERFC 5245ICE無し、STUNの接続確認だけ
TURN利用最後の手段同じ仕組み(後述)
音声コーデックOpusかG.711謎、16kHzで20kbpsのビットレート

リレーからP2Pへのスイッチ

いろいろと見つけた中で最も感動的だったのは、最初は中継ノードを使って呼を確立して、後でP2Pにスイッチするっていう最適化だ。 これによって、呼が確立するまでの時間が早くできる。また、将来的にマルチパーティのVoIPへの可能性も広げてくれる。中継サーバは、バイナリを見ると「conf bridge」って呼ばれてるようだ。

最初のセッションを説明してみよう(PDFに詳細なものが書いてあるから、気になったらそっち見てね):

  1. セッションは、TURN ALLOCATEリクエストを8つ異なるサーバに送るところからスタートする(パケット #70)。これは、普通のSTUN属性(見落としがちな)を使ってない。
  2. レスポンスを受け取ったら、シグナリングサーバと情報交換をはじめる。これは、集めたリレー候補をシグナリングサーバに送って、他方のピアへ候補を送ってるんだろう。
  3. パケット(#132)は、TURNサーバ群の1つに何かを送ってることを示してる。これは実はRTCPパケットで、その後にはRTPが続く。Wiresharkの「decode as」って機能を使って見つけられる。これはWebRTCだと普通じゃなくて、ミスリーディングな感じだ。だって、標準的なTURNの機能(send/data indication)を使ってなくて、代わりに生のRTPを使ってるからだ。
  4. パケット#146は他方のピアから受信する最初のRTPを示してる。だいたい3秒ぐらいの間、中継サーバを通して流れてきてる。
  5. その一方で、#294のパケットは相手ピアのグローバルIPアドレスにSTUNを送ってる。Wiresharkのフィルタ((ip.addr eq 172.16.42.124 and ip.addr eq 83.209.197.82) and (udp.port eq 45395 and udp.port eq 35574))を使えば分かる。
  6. そのレスポンスが#300のパケットだ。
  7. で、また面白いことが起こってる。クライアントはRTPストリームの行き先を、#298と#305でスイッチするんだ。RTPとしてデコードすると、RTPのシーケンス番号を確認できる。このスクリーンショットを見てみて:
switch

switch

これで、全てをRTPとしてデコードできてるなら(Wiresharkのデフォルトだと難しいから、多少の工夫は必要)、フィルタをrtp.ssrc == 0x0088a82dに変えれて、もっと詳しく見れるはずだ。

ここでの意図は、確実につながる接続を試して(同じ根拠を別の記事であるminimal viable SDPに書いた)、それからTURNサーバの負荷を下げるために、P2P接続へスイッチするんだ。

マジか!これは賢いね。ユーザーからしたらつながるまでに時間を短くできる。もっかい繰り返すけど、UXを向上させるためのハックだ!

定量化するのは難しい。大規模に実験すれば(標準的なアプローチと、今回のアプローチを両方ね)、答えられるようになるだろうね。

WebRTCへのレッスン

WebRTCでは、だいたい似たようなことをできるけど、今のところは小さい。iceTransports: 'relay'って書いて呼を確立できて、これを使えば、hostとserver-reflexiveを使った候補をスキップできるってわけだ。また、中継サーバを使えば、接続できる確率も上げられる。

このアプローチには、TURNのpermissionの仕組みのため、RTTの観点からいくつか欠点がある。基本的には、TURNのリレー候補を集めるときは、以下のように動く。(実際にはChromeとFirefoxでちょっと違うんだけど)

  1. Chromeはまず認証なしでAllocationを作ろうとする
  2. TURNサーバは、「認証が必要だよ」って答える
  3. Chromeは、認証付きでもう一度トライする
  4. TURNサーバはChromeには、ICE候補のアドレスとポート番号を伝える
  5. Chromeはonicecandidateコールバックを使って、JavaScriptレイヤにもらった候補を伝える。ここまでで、RTTは2回分だ。
  6. リモート候補を処理したら、Turnで実際にトラフィックを中継する前に、ChromeはTURNにpermissionを作るよう依頼する。これはココで書かれてるセキュリティの仕組みだ。
  7. STUNバインディングリクエストを中継アドレスに送れるようになる。このとき、TURNのsend/data indicationを使える。
  8. ICE候補にお互いで同意できたら、Chromeは、オーバヘッドを減らすためにTURNチャネル(相手ピア向かいの)を作る。

上のWebRTCのアプローチと、Whatsappのそれを比べると、ラウンドトリップの回数がかなり削減できる!

これがUser Experienceを向上させるためのハックだ!

もし、まずリレー候補だけから開始できるなら、通信相手同士で互いのIPアドレスが分からない状態なので、中継サーバ経由で接続確立して、受信側が呼を受けるって決める前にDTLSのハンドシェイクも済ませられる。これは、トランスポートウォームアップとして知られていて、メディアが実際に流通しはじめる前に、ユーザーが感じる時間を減らせる。

リレー候補が確立できれば、SecConfiguration(昔はupdateiceとして知られていたやつ。いまは実装されてない)を利用して、中継サーバが持ってる制約を取り外しにかかる。で、createOfferをもう一回呼んで、このときiceRestartフラグをtrueにしてね、ICEを再開(リスタート)するわけだ。これでICEの処理がはじまるから、P2Pが接続がつながるかどうか試せる。

updateIceが実装されてないにもかかわらず、今日でもリレー接続からP2P接続へスイッチできる。ChromeならICE再開は動く。なので今欠けている唯一の要素は、iceTransprots 'relay'で、この動作としては単にリレー候補を作るだけだ。で、リレーじゃない候補は、Javascriptで全部落としちゃう。

そうすれば、WhatsAppみたいな動作がWebRTCでもできる。私のお気に入りのsdp修正のサンプルをみれば、実装も簡単だと思う。リレーからP2Pへのスイッチは動くし、コードもあるよ。

ICE再開は今は非効率だけど、実際のメディアのスイッチ(これが実際難しいところ)はシームレスに起こるよ。

私見だけど…

WhatsappのSTUNとRTPの使い方は、ちょっと時代遅れな気もする。議論の余地はたくさんあるんだけど、STUNの使い方は分かりやすいし、 中継接続からP2P接続へリレーするのに使われてる。だけど、ICEは同じことができるし、もっと確実なやり方だ。カスタムTURN機能を使って、生のRTPを会議のブリッジ間で使えば、TURNチャネルのオーバヘッドを何バイトか節約できるけど、無視できるオーバヘッドだ。

完璧な前方秘匿性を持ってるDTLS-SRTPを使わないってのも、プライバシの観点から大いに議論の余地がある。SEDSは欠点を持ってることが知られていて、鍵がばれたら後からでも解読できちゃう。(その鍵はちなみに、シグナリングサーバ経由で転送されるやつだよ)シグナリングでの情報交換は、テキストメッセージに使われるのと同じ方法で保護されてる点に注意。

UXの観点からいえば、P2Pにおける通話中呼のブロッキングが、これまでのシナリオで考慮済みってことを示してる。エコーキャンセルはかなり問題になるけど。webrtc.orgのエコーキャンセラはかなり上手くできていて、バイナリにも組み込まれている。awesomeチョコレートっていう見返りがあれば、WhatsAppのチームがもっと助けてくれるかも。。。

{“著者”: “Philipp Hancke“} {“訳者”: “岩瀬 義昌”}

de:code 2017
Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Canvas Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Node.js PhoneGap Polymer React Safari SkyWay TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket