DIALというネットワークプロトコル
家庭のネットワークの監視システムからDIALなるプロトコルが暴れていてアラートが飛んできたので調査しました。
目次
Open 目次
日頃の監視と増えた謎のリクエスト
自宅のシステムを管理する上で、ネットワーク監視は重要なものと言えます。 そんな監視システムが異常を検知したと言うのでパケットを見てみると、ある日を境に自宅ネットワークに何やら見慣れないものが大量に流れ始めていました。
M-SEARCH * HTTP/1.1
で始まるUDPパケットから見慣れたUPnP(Universal Plug and Play)の探索リクエストというのはわかるのですが、これが今まで以上に飛び交うようになっていたのです。
リクエストの中身には、urn:dial-multiscreen-org:service:dial:1
というリソース名(URN)を持っていることがわかります。
Universal Plug and Play - Wikipedia
オープンDIALプロトコル
このURNが示すものとは一体何なのか、日本語での解説は唯一、Amazonの開発者向けサイトに記載がありました。
DIALについて
Amazon Fire TVデバイスは、Whisperplayサービスを介してDIAL(Discovery-and-Launch)プロトコルをサポートします。DIALは、別のデバイスからセカンドスクリーンアプリを使用してFire TV対応アプリを検出し起動できるようにするオープンプロトコルです。そのためには、Fire TVとセカンドスクリーンデバイスが同じネットワークに存在する必要があります。
DIALは、キャスティングやミラーリングの機能を提供するAPIではありません。セカンドスクリーンデバイスのアプリがFire TVでアプリを見つけて起動できるようにするだけです。通常は、セカンドスクリーンアプリ(起動メッセージの送信側)と、対応するファーストスクリーンアプリであるFire TV対応アプリ(メッセージの受信側)の両方を実装します。
なるほど、Fire TVなどのスマートテレビデバイスにあるアプリケーションを検出して起動するためのオープンなプロトコルであるとのことです。 DIAL公式サイトでプロトコル仕様のPDFが提供されていました。
DIAL —DIscovery And Launch— | www.dial-multiscreen.org
DIALを喋る相手を突き止める
しかしながら、我が家にはFire TVはありません。一体どのデバイスがこのDIALを喋るようになってしまったのでしょうか。 仕様書を読みながら簡単に検出スクリプトを書いて実行してみます。
これを実行すると、標準出力には次のような内容が出力されました。
HTTP/1.1 200 OK
LOCATION: http://192.168.181.222:56790/dd.xml
CACHE-CONTROL: max-age=1800
EXT:
BOOTID.UPNP.ORG: 1
SERVER: Linux/2.6 UPnP/1.0 quick_ssdp/1.0
ST: urn:dial-multiscreen-org:service:dial:1
USN: uuid:82152303-4d0c-4cba-92e8-9614ee8aff70::urn:dial-multiscreen-org:service:dial:1
WAKEUP: MAC=96:14:ee:8a:ff:70;Timeout=120
仕様書と照らし合わせて読むと、HTTPプロトコルで http://192.168.181.222:56790/dd.xml
に DIALの Device Description があるとわかります。
このURLを開いてみると、次のようになっていました。
DIALを喋っている子は先日買い替えた75インチテレビのHISENSE-75A6Gでした! 振り返ってみると、ちょうどテレビを買い替えた日からUPnPのマルチキャストパケットが大量発生していました! インターネットテレビに対応したスマートTVを導入したのが今回が初めてなので、それによって急増したマルチキャストパケットが異常としてアラートに引っかかってしまったのです。
Amazon | ハイセンス 75V型 4Kチューナー内蔵 液晶 テレビ 75A6G ネット動画対応 ADSパネル 3年保証 2021年モデル | テレビ 通販
DIALでNetflixを操作する
DIALプロトコルはNetflixが策定しています。そしてHISENSE-75A6GにはNetflixアプリが搭載されています。 このNetflixがリファレンス実装をしていると想定し、仕様書に書かれている手順を用いてDIALでNetflixを起動してみるとします。
DIALでは先ほどのDevice DescriptionのレスポンスヘッダーにあったApplication-URL:をベースとして、末尾にアプリケーション名を追加したエンドポイントをApplication Resource URLと呼び、そこに対してリクエストを行います。 ここで用いるアプリケーション名は、DIAL Registryに登録されていると仕様書に書かれています。
Application Name/Prefix Registry - DIAL
早速Netflixのアプリケーション名をDIAL Registryで探したところ、Netflix
でした。
このNetflix
とApplication-URLのhttp://192.168.181.222:56789/apps/
を合わせたhttp://192.168.181.222:56789/apps/Netflix
がエンドポイントとなります。
このエンドポイントを例にとると、Netflixアプリに対して次のような操作が可能となっています。
GET /apps/Netflix
: Netflixの情報の取得POST /apps/Netflix
: Netflixの起動POST /apps/Netflix/run/hide
: Netflixの非表示DELETE /apps/Netflix/run
: Netflixの終了
これを踏まえて、DIALでアプリケーションを起動するためのフローは次のようになっています。
DIALでNetflixの状態を確認する
まず状態を確認するため、GETリクエストを送ってみます。
$ curl -s 'http://192.168.181.222:56789/apps/Netflix'
<?xml version="1.0" encoding="UTF-8"?>
<service xmlns="urn:dial-multiscreen-org:schemas:dial" dialVer="2.2">
<name>Netflix</name>
<options allowStop="true"/>
<state>stopped</state>
<additionalData>
</additionalData>
</service>
おお、ちゃんとそれらしきレスポンスが返っててきました。
DIALでNetflixを起動してみる
同じエンドポイントに対してPOSTリクエストを送ることで、テレビにはNetflixの画面が映し出され、起動することができました。
$ curl -XPOST -s -D - 'http://192.168.181.222:56789/apps/Netflix'
HTTP/1.1 201 Created
Content-Type: text/plain
Location: http://192.168.181.222:56789/apps/Netflix/run
Access-Control-Allow-Origin: (null)
レスポンスヘッダーに含まれるLocationが示すURLが、現在起動中のインスタンスに対するREST操作を受け付けるエンドポイントとなります。
DIALでNetflixを非表示にする
インスタンスのエンドポイントに/hide
を加えたURLに対してPOSTリクエストを行うと、現在起動中のDIALアプリケーションを非表示にしてバックグラウンド動作に切り替えられます。
$ curl -XPOST -s -D - 'http://192.168.181.222:56789/apps/Netflix/run/hide'
HTTP/1.1 503 Service Unavailable
Content-Type: text/plain
Content-Length: 50
Connection: close
Error 503: Service Unavailable
Service Unavailable
なぜか503エラーが返ってきますが、テレビで起動しているNetflixは非表示になり、起動する前の画面に戻りました。
DIALでNetflixを終了する
インスタンスのエンドポイントに対してDELETEリクエストを行うと、現在起動中のDIALアプリケーションを終了できます。
$ curl -XDELETE -D - 'http://192.168.181.222:56789/apps/Netflix/run'
HTTP/1.1 200 OK
Content-Type: text/plain
Access-Control-Allow-Origin: (null)
テレビで起動しているNetflixは終了し、成功レスポンスが返ってきました。
他のアプリも操作してみる
HISENSE-75A6GにはいくつかVODサービスが登録されているので、DIAL Registryに登録されているものを探してみました。
VODサービス名 | DIAL対応 | DIALアプリケーション名 |
---|---|---|
Netflix | ○ | Netflix |
YouTube | ○ | YouTube |
Prime Video | ○ | AmazonInstantVideo |
Hulu | × | n/a |
ABEMA | × | n/a |
U-NEXT | × | n/a |
dTV | × | n/a |
Paravi | × | n/a |
スカパー | × | n/a |
TSUTAYA TV | × | n/a |
DMM.com | × | n/a |
Rakuten TV | × | n/a |
DIAL RegistryにはHuluの名前はあるものの、日本のHuluは本家Huluとは別物の”名ばかりHulu”1なので、DIALには対応していないようです。
Netflixの例では特にリクエストヘッダーをつけなくてもcurl
でDIALリクエストを送ることができていました。
しかしYouTubeは、Netflixの例で示したようなシンプルなリクエストでは403エラーで弾かれてしまいます。
これを解決するにはDIALプロトコル仕様の6.6 CORS Requirements and CORS Access Control Policyを読み込み、開発者の意図を汲み取る必要があります。
6.6には次のように記されています。
Whenever an HTTP request is made against an Application Resource, the DIAL server should run the following checks:
- If the
ORIGIN
header is absent in the request, the CORS check is not applicable and the request is allowed.- If the
ORIGIN
header is present in the request:
- The
ORIGIN
header may indicate the https scheme. The full hostname of theORIGIN
header must match one of the domains authorized for the https scheme. Alternatively, all single-level subdomains within a specific authorized domain may be accepted if explicitly authorized by a DIAL application. The set of authorized domains is specific to each DIAL application.- Additional
ORIGIN
header schemes that are considered secure resources may also be accepted, such as thepackage
scheme. {: .second-lower-alpha }
リクエストにOriginヘッダーなければリクエストが許可され、Originヘッダーがある場合は、そのドメインでCORS判定を行うよう書かれています。 この仕様に従うと、Netflixのようにリクエストヘッダーをつけなくても、すなわちOriginヘッダーが無くてもリクエストは許可されるはずですが、YouTubeは403エラーが出ます。
ここからが肝心なところなのですが、この節はshouldで書かれています。
開発者の意図を汲み取り、きっとYouTubeの開発者はOriginヘッダーが無い場合にリクエストを許可する実装をあえてしなかったと推測する必要があります。
それができればあとは簡単で、Origin: https://www.youtube.com
をリクエストヘッダーにつけてあげればNetflixと同様のDIAL操作ができるようになります。
挙動を調べてみたところ、今回見つかったDIALアプリが許容するOriginヘッダーの値は次の通りでした。後に判明したことですが、後述するホワイトリストにいくつかのアプリの許容オリジンが書かれています。
- Netflix:
https://www.netflix.com
,package:
スキーマで始まる任意のURI - YouTube:
https://www.youtube.com
,package:
スキーマで始まる任意のURI - Amazon:
https://www.amazon.com
,package:
スキーマで始まる任意のURI
DIAL対応アプリと挙動
冒頭で紹介したAmazon開発者サイトの説明にあった通り、DIALは対応アプリの探索と起動をするプロトコルで、キャスティングやミラーリングの機能を提供するAPIではありません。 仕様を見ても挙動を確認しても、探索と起動しかできないことがわかりました。
そんなDIALの使いどころとは、いったいどこにあるんでしょうか。
単純な起動をするだけのこのプロトコルはどういった目的で使われるのか、その答えも単純で、スマホアプリなどが同じネットワークに存在する対応アプリを探索し起動までをするためです。 起動までできれば、あとはアプリの範囲内で自由にネットワーク通信を確立できるので、相互にTCP/IPで接続しあったりサービスのAPIを用いてクラウドを経由したりと思い思いの手段を用い、動画の再生をコントロールするようになります。
手持ちのデバイスでは、NetflixのiOSアプリとYouTubeのiOSアプリ、そしてyoutube.comにアクセスしているChromeやVivaldiなどのChromiumベースのブラウザが、このDIALによってDIAL対応アプリを探索し起動していました。
ChromeのDIALによる起動API
youtube.comにアクセスしているChromeでDIAL操作ができるということは、JavaScriptでDIALによって対応アプリの起動ができるということを意味しています。 調べてみると、Chromiumの実装は以下の箇所にDIAL関連のソースコードがありました。
media/router/discovery/dial/chromium/README.md at 94.0.4595.1 · chromium/chromium
このmedia/router下の階層のソースコードはChrome Media Routerというコンポーネントの実装で、Chromecastなどのデバイスとコンテンツのコントロールを行うブラウザAPIを提供しています。 この低いレイヤーのAPIを拡張機能なしでJavaScriptから扱うために、Cast Chrome Sender SDKが使えるとあります。
Web Sender API | Cast | Google Developers
早速Cast SDKリファレンスとインテグレーションガイドを読み、SDKの中身を紐解きながらDIALアプリの起動コードを実装してみました。DIALで起動させる肝となるのは、Cast Framework APIではなくBase APIを使い、DialRequestを正しくセットできるかという部分でした。
const sessionRequest = new chrome.cast.SessionRequest(
chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID
);
sessionRequest.dialRequest = new chrome.cast.DialRequest(dialAppName);
数十行のコードでブラウザから簡単に起動ができるコードが完成したかと思いきや、うまく動きません。実装は絶対に間違っていないのに。。
そこでさらに探っていくと、NetflixやYouTubeはホワイトリスト形式でドメイン判定があり、特定のオリジンでしかDIALによる起動ができないようになっていました。 ただ、Amazon Prime VideoはChrome 94相当のソースコードには判定コードが含まれていなかったので、Amazon Prime Videoを起動するサンプルを以下に用意しました。 対応デバイスが同一ネットワークにあれば、Chrome 94前後でAmazon Prime VideoのDIALアプリの起動を試すことができるはずです。
DIALによる起動後の操作連携
YouTubeは起動後、こちらはQUICでYouTubeのAPIサーバーと通信して操作があるたびにクラウドを経由して再生コントロールをする形になっていました。
Netflixはというと、TCP 9080ポートでHTTPサーバーをiOSアプリとテレビの両方で立ち上げ、立ち上がっていることをUPnPで確認しあい、次に示すフローで暗号化されたセッション情報を相互に送り合いながら動画の再生コントロールを行っていました。仕組みとしてはMDX(Multiple-Device Experience)2を使っていて、CTicketのペイロードにはCBOR3が使われるなど、なかなか面白い作りになっていました。
まとめ
異常なほどにUDPマルチキャストが飛び交っていたのは、テレビをHISENSE-75A6Gに買い替えたことによって、VivaldiがHISENSE-75A6Gを見つけてしまったことに起因していました。 ご家庭のネットワークで異常検知アラートがなったら調査する習慣をつけておくと、こういった問題の解決に迅速に対応できるのでアラート設定をお勧めします。
DIALの挙動を一通り確認することもでき、また新たな技術を知る良いきっかけとなりましたとさ。おしまい。