LGTM

Looks Good To Me

ものづくりの理想郷 を読んだ

著者の山本さん から頂きました.ありがとうございます.

この本は,平和酒造 という酒蔵が抱えていた組織的な問題をどうやって解決したか,蔵元の視点で書かれている.斜陽の日本酒業界にあって,昔はうまくいっていた

  • 蔵(職人たち) が酒を造り,蔵元(経営者) が売るという分業制
  • トップダウンで意思決定される硬直した組織

が時代に合わなくなって「さあどうしよう」という苦悩と,「こうすればうまくいった.お酒も売れた」という成功体験が書いてある.

日本酒業界に携わる人はもちろん,日本酒好きの人にオススメの一冊.蔵元視点の本なんてあまり見ないし,バックグラウンドを知って飲む酒は旨いはず.

ものづくりの理想郷――日本酒業界で今起こっていること

ものづくりの理想郷――日本酒業界で今起こっていること

平和酒造

ここ数年いろんな賞を取ったり,メディアに取り上げられることが多くて注目していた.

ここまで認知されるようになったのは ホントこの数年だと思う.「なにをしたんだ」というのがずっと疑問で,期待して読んでたんだけどマーケティング / 広報など「売る」ところについてはあまり触れられてなかった.残念.

ただ,「やりたいことをやるために まず認知度を上げる必要があるから,回り道でも名刺代わりになるブランドを持て」というくだりは,なるほどなーと思った.

杜氏ってエンジニアっぽい

「職人気質だった杜氏に,部下をマネージメントし 育成することの重要性をわかってもらうまで5年かかった」「杜氏の持つ技術をマニュアル化する必要があった」「酒造のプロセスを計測 / 数値化し,カンバン方式を取り入れた」という描写がある.

本では経営者側の視点だけれど,エンジニアなら「わかるわー,杜氏の気持ちわかるわー」とニヤニヤして読めると思う.

僕はエンジニアで,最近まで大きな企業で働いていた.「苦労して得たノウハウを共有するモチベーションが湧かない」という気持ちはよくわかる.IT 業界だと,今でこそ「情報発信することが自分の市場価値を高める」という空気感があるけれど,これは「情報の鮮度が長続きしないから,隠しとく意味ないよね」という条件と,OSS のような考え方があればこそ.

杜氏のように腕一本で渡っていける業種で,同業者は職人っぽいやりかたでそれなりにうまくやっている中「自分がマネージメントすれば組織が成熟して,そうすると自分も成長できて市場価値が高まる」とか「情報共有すれば回りまわって自分を助けてくれる」という発想にはなかなか至らない.経営者が懇願しても,行動に移すのに疑問はあったはず.

これまでいろんな葛藤があったんだろうなあと思うと,お会いしたことはないが,もしいつかご一緒できたら,この方とはたぶん旨い酒が飲めると思うな.

SpamAssassin が何をしてるか

スパムフィルターSpamAssassin に変えてから,DNS クエリーが増えた.

「なにこれ」と思ってだいぶ放置してたんだけど,あらためて何をしているか調べた.

ざっくり言うと

SpamAssassin は多数の外部ブラックリストサービスに依存しており,DNS クエリーを使って問い合わせる.スパマー側のMTA 構成やメール本文にもよるが,かなりクエリーを吐く.手元のメールでは 1 通を評価するのに50~70 クエリーほど.

SpamAssassin って?

Perl で書かれたアンチスパムソフトウェア.Wikipedia 読めばだいたい分かると思う.

$ spamc -R < path_to_a_spammail
...
Content analysis details:   (4.1 points, 4.0 required)

 pts rule name              description
---- ---------------------- --------------------------------------------------
 1.0 RCVD_IN_CSS            RBL: Received via a relay in Spamhaus CSS
                            [123.57.40.62 listed in zen.spamhaus.org]
 0.0 T_URIBL_SEM_FRESH_10   Contains a domain registered less than 10 days
                            ago
                            [URIs: ncsoft1.com]
 0.0 T_URIBL_SEM_FRESH_15   Contains a domain registered less than 15 days
                            ago
                            [URIs: ncsoft1.com]
 0.0 HTML_MESSAGE           BODY: HTML included in message
 0.8 BAYES_50               BODY: Bayes spam probability is 40 to 60%
                            [score: 0.5158]
 1.4 RCVD_IN_BRBL_LASTEXT   RBL: RCVD_IN_BRBL_LASTEXT
                            [123.57.40.62 listed in bb.barracudacentral.org]
 0.8 RDNS_NONE              Delivered to internal network by a host with no rDNS

こんな感じで,いろんなルールに基づいてスコアを加算し 閾値を超えたらスパムと判定する.

実際には,Postfix だとコンテンツフィルターに組み込む とか,procmail などでフィルターとしてspamc をくぐらせる.

何をしてるか

  1. spamc (クライアント) は,spamd (サーバー) に PROCESSREPORT などのコマンドを送る.その後メールヘッダ + 本文も送る
  2. spamd は設定やルールに基づいてメールをスコアリングし,スパム判定
  3. spamd はspamc に,実行結果を送る.コマンドにより実行結果の前にリターンコードを送る場合がある

ルール定義は/usr/share/spamassassin/ などにあり,ルールにマッチした場合に加算されるスコア設定も同じ場所にある. ルール実装はPerl module になっている場合も.

Rules

$ grep -hr loadplugin /etc/spamassassin

これで ロードしているプラグインが一覧できるので,動かしてみたり,コード読んだりして調べた.

Mail::SpamAssassin::Plugin::URIDNSBL

メール本文に含まれるURI について,下のようなURI DNSBL サービスにDNS プロトコルで問い合わせる. ブラックリストに含まれていたら加点.

  • multi.surbl.org
  • multi.uribl.com
  • dob.sibl.support-intelligence.net
  • urired.spameatingmonkey.net
  • uribl.spameatingmonkey.net
  • fresh.spameatingmonkey.net
  • fresh15.spameatingmonkey.net
  • fresh10.spameatingmonkey.net
  • dbl.spamhaus.org

どれもRFC5782 に基づいて実装されている.

DNS Blacklists and Whitelists (RFC5782)

かんぺきに脇道にそれるが,RFC5782 がおもしろいので簡単に紹介する.

たとえば doms.example.net というDNSBL サービスがあって,そこでは invalid.edu がブラックリスト入りしているなら

invalid.edu.doms.example.net    A      127.0.0.2
invalid.edu.doms.example.net    TXT    "Host name used in phish"

という2つのレコードが登録される.

  • A が返すアドレスは何でもいい
  • TXT の中身はブラックリスト入りした理由
  • A はMUST だが,TXT はSHOULD

ここまではギリギリわかるとしても,multiple sublists encode のムリヤリ感がすごい.

multi.surbl.org などがそうで,一発のクエリ

$ drill example.com.multi.surbl.org

で,6 ブラックリストの検索結果を返せるよう

2 = comes from SC
4 = comes from WS
8 = comes from PH
16 = comes from MW
32 = comes from AB
64 = comes from JP

のようなビットマスクを定義しておいて,IPv4 アドレスなら末尾1オクテットにエンコードする.

example.com.multi.surbl.org    A      127.0.0.4

だったら「WS (sa-blacklist web sites) に登録されてるんだな」とわかるし

example.com.multi.surbl.org    A      127.0.0.6

だったら「SC (SpamCop web sites) と WS (sa-blacklist web sites) に登録されている」とわかる.

「クライアントがデコードしないと意味を読み取れないなんて,強引なやつだ」と思うかもしれないが

  • A レコードが存在 → どれかのブラックリストに登録されていることがわかる
  • さらに興味があれば,ビットマスクをデコードしてリストを特定できる
  • UDP なので通信コスト低
  • リゾルバーが適度にキャッシュしてくれる

これはこれで良くできている.ちなみにRFC5782 はInformational.

Mail::SpamAssassin::Plugin::Hashcash

X-Hashcash ヘッダーがついていたらHashcash に基づいて評価. パスすれば減点.

Mail::SpamAssassin::Plugin::SPF

SPF に基づいて評価. パスすれば減点,失敗すれば加点.

Mail::SpamAssassin::Plugin::Pyzor

Pyzor を使って評価.Pyzor サーバー/クライアント間の通信はUDP. スパムデータベースに含まれていたら加点.

Pyzor クライアントがインストールされてなければ,テストされない.

Mail::SpamAssassin::Plugin::Razor2

Razor を使って評価.Razor サーバー/クライアント間の通信はHTTP. スパムデータベースに含まれていたら加点.

Razor クライアントがインストールされてなければ,テストされない.

Mail::SpamAssassin::Plugin::SpamCop

SpamCop へのレポート用プラグイン.スコアリングには関係ない.

spamd を-l オプションで起動しておき,

$ spamc -C report < path_to_a_spammail

のようにしてレポートする.

Mail::SpamAssassin::Plugin::AutoLearnThreshold

スコアが高い(かなりスパムっぽい) or 低い(かなりハムっぽい) メールを,ベイズデータベースに自動登録する.

通常はsa-learn コマンドなどを介して学習させる.

Mail::SpamAssassin::Plugin::WhiteListSubject

whitelist_subject, blacklist_subject が設定されていた場合,Subject とマッチすればそれぞれ減点,加点.

Mail::SpamAssassin::Plugin::MIMEHeader

MIME ヘッダーを扱えるようにするためのプラグイン.スコアリングには関係ない.

Mail::SpamAssassin::Plugin::ReplaceTags

正規表現パターンにタグを付け,タグでパターンを呼び出せるようにするプラグイン.スコアリングには関係ない.

Mail::SpamAssassin::Plugin::Check

テストの呼び出し元.スコアリングには直接関係ない.

Mail::SpamAssassin::Plugin::HTTPSMismatch

本文がHTML で,リンク先ホスト名とアンカーテキスト内のホスト名が異なる場合,加点.

<a href="http://foo.example.com">http://bar.example.com</a>

のようなやつ.

Mail::SpamAssassin::Plugin::URIDetail

本文に含まれるURI をパターンマッチにより評価できるようにするプラグイン.ルールを定義しないのでスコアリングには直接関係ない.

Mail::SpamAssassin::Plugin::Bayes

ベイジアンフィルターを実現するためのプラグインベイズスコアは9段階に分類され,高いほうがよりスパムと判定されやすい. (0~1%: 減点,99~100%:加点)

  • 0~1 %
  • 1~5 %
  • 5~20 %
  • 20~40 %
  • 40~60 %
  • 60~80 %
  • 80~95 %
  • 95~99 %
  • 99~100 %

sa-learn コマンドを使って学習させる.

Mail::SpamAssassin::Plugin::BodyEval

本文の内容により評価する.

  • text/html パートとmultipart/alternative パートの差分が大きければ加点
  • text/html パートとmultipart/alternative パートの単語数比が閾値を超えたら加点

Mail::SpamAssassin::Plugin::DNSEval

メールを送信してきたMTA のアドレスについて,下のようなRBL サービスに問い合わせる.

  • bl.score.senderscore.com
  • zen.spamhaus.org
  • psbl.surriel.com
  • list.dnswl.org
  • bl.spameatingmonkey.net
  • bb.barracudacentral.org
  • bl.spamcop.net
  • sa-trusted.bondedsender.org
  • sa-accredit.habeas.com
  • dnsbl.sorbs.net
  • iadb.isipp.com

Envelope From のドメイン部分について,RHSBL サービスに問い合わせる.

  • rhsbl.ahbl.org

ブラックリストに含まれていたら加点.

また Envelope From のドメインが A かMX レコードを持っているかどうかを調べ,持ってなければ加点.

問い合わせはすべてDNS プロトコルを使う.

Mail::SpamAssassin::Plugin::HTMLEval

本文のHTML にスパムっぽい点がないか評価し,該当すれば加点.

  • 短すぎるコメントがある
  • embedded オブジェクトがある
  • 閉じタグが多すぎる
  • フォントがでかい
  • フォントのコントラストが低い (背景にまぎれている)
  • フォントが文字ではない
  • action がmailto: なフォームがある
  • サイズの小さいimage タグがある
  • 難読化している部分がある
  • bgsound タグがある
  • 不正なタグがある
  • HTML が短すぎる

など.

Mail::SpamAssassin::Plugin::HeaderEval

ヘッダーにスパムっぽい点がないか評価し,該当すれば加点.

  • To: がない
  • Date: がずれている
  • Subject: に,To: のローカルパートが含まれる
  • Subject: が大文字
  • 宛先に,似たアドレスが並んでいる

など.

Mail::SpamAssassin::Plugin::MIMEEval

MIME ヘッダーにスパムっぽい点がないか評価し,該当すれば加点.

  • 添付ファイルがある
  • 外国語のメール (よく受信する言語は設定で決めておく)
  • multipart のほとんどがtext/html

など.

Mail::SpamAssassin::Plugin::RelayEval

MTA のスパマーっぽさを評価し,該当すれば加点.

  • Received: にparse できない行がある
  • HELO にIP アドレスが使われ,Received: を名前解決したリスト中に該当アドレスがない
  • HELO にIP アドレスが使われ,Received: にIP アドレスのまま載っている
  • Received: が偽装されている
  • HELO の逆引きがない

など.

Mail::SpamAssassin::Plugin::URIEval

本文中のURI を評価し,スパムっぽいリンクがあれば加点.

  • アンカーテキストはFQDNで,リンクはIP アドレス
  • URI が8192 文字を超える

など.

Mail::SpamAssassin::Plugin::WLBLEval

送信元,送信先アドレスがブラックリストにあれば加点し,ホワイトリストにあれば減点.

ブラックリストホワイトリストblacklist_to, whitelist_to, more_spam_to, all_spam_to などで設定する.

Mail::SpamAssassin::Plugin::VBounce

bounce しているメッセージでも,Received: にホワイトリストに含まれるホスト名があれば減点.

ホワイトリストwhitelist_bounce_relays で設定する.

Mail::SpamAssassin::Plugin::ImageInfo

添付画像のスパムっぽさを評価し,該当すれば加点.

  • 画像数
  • 画像フォーマット
  • 画像サイズ

など.

Mail::SpamAssassin::Plugin::DKIM

DKIM に基づいて評価.DKIM-Signature: がついていて,invalid なら加点.

Mail::SpamAssassin::Plugin::FreeMail

メール中にフリーメールのアドレスが含まれていた場合,スパムっぽければ加点.

  • Envelope From がフリーメールアドレス
  • Reply-To: がフリーメールアドレス
  • Subject: にフリーメールアドレスが含まれる
  • Reply-To:, From:, body に異なるフリーメールアドレスが含まれる

だいぶ古い3.3.2 で試しました

Debian wheezy に入っていたものをそのまま使ったが,最新の3.4.0 にすれば結構うれしいことありそう.

ブラックリストサービスへの問い合わせ,DNS でいいの?

DNS ゾーンの乗っ取りやキャッシュへの毒入れが流行っているので心配になるが,「多数のサービスを串刺し検索して使っているから大丈夫」って判断なのかな? よくわからない.

スパムは止まってる?

閾値の調整 + デフォルト設定 + ベイズデータベース学習で,ほぼ判定できている. false positive に振るか,false negative に振るかは好みかな.

required_score      4

いまはこんな閾値.時代に合わないルールもあるので,ブラックリスト系とベイジアンフィルターの重みを上げてもいいかもしれない.

Yosemite でSCR3310-NTTCom をつかう

e-Tax などでおなじみの公的個人認証サービス(JPKI) に必要なICカードリーダーのうち,鉄板だという「SCR3310-NTTCom」が生産終了になるらしい ので1つ買ってみた.

ざっくり言うと

OSX10.10 (Yosemite) で使うには

これでOK.JPKI 利用者クライアント はそのまま使える.

メーカー製のドライバーには触れるな

メーカーサイト に純正ドライバーがあるが,Yosemite では動かない.

動かないばかりか 標準のCCID クラスドライバーを削除して,アンインストール後も元に戻してくれない.やってしまったらバックアップ等から /usr/libexec/SmartCardServices/drivers/ifd-ccid.bundle を戻すしかない.

必要なドライバー のインストール

Apple サポートサイト

You must obtain the driver (Tokend) installer necessary to support the type of smart card in use from one of several open source or commercial third-party providers. For more information on tokend installers, smart cards, and smart card software updates, or for discussion and support, see the SmartCard Services project on the Mac OS Forge website.

(http://support.apple.com/kb/PH19083?viewlocale=en_US)

にあるとおり,必要なドライバー をインストールする.インストールしておけば住民基本台帳カードを挿したときにJPKI.tokend が自動で起動する.

スマートカードに対応したtokend を入れとかないと カード内のトークンをsecurityd に渡してくれず,使えない.

ちなみに,JPKI.tokend なしでJPKI利用者ソフト.app の "動作確認" 機能を使ってみるとこんな感じになる.

SCardEstablishContext OK
SCardListReaders OK
    Reader01:NTT Communications Corp. SCR3310-NTTCom USB SmartCard Reader
SCardGetStatusChange OK
    SCARD_READERSTATE->dwEventState : 0x00000022
        SCARD_STATE_PRESENT
        SCARD_STATE_CHANGED
SCardConnect OK
SCardStatus NG
    return :SCARD_E_INSUFFICIENT_BUFFER(0x80100008)
SCardDisconnect OK
SCardReleaseContext OK

SCR3310-NTTCom を買ってきて,何も考えずにYosemite に接続したらこの状態のはず.

JPKI 利用者クライアント はそのまま使える

Java ですから.

まったくどうでもいいが,「公的個人認証サービス」を英語では「Japanese PKI」と言うらしい.だれも気にしないと思って大きく出たな!

やってみよう NETCONF

知ったかぶりしない NETCONF に続く, NETCONF エントリーです.

今回はNETCONF の実装をいくつか試す.

前のエントリーでは「NETCONF 便利そうだけど, データモデル*1 が共通じゃないので使うのつらそう. 特にいろんな種類のものがある場合は」というようなことを書いた.

一方で「できる範囲でいいから物理ルーター/スイッチを自動管理したい」ニーズはあるので, 今回は「現状どんなことができるの?」を確認しようと思う.

たくさん試したわけではないので, 他に便利アプリがあるかもしれない.
オススメ情報があったらぜひ教えてください.

管理対象 (Netconf Server)

今回はJuniper Firefly Perimeter (junos-vsr-12.1X46-D-10-domestic) を使ってみる.

A Ruby gem for NETCONF

実装されている機能

  • Extensible protocol transport framework for SSH and non-SSH

  • NETCONF Standard RPCs

    • get-config, edit-config
    • lock, unlock
    • validate, discard-changes
  • Flexible RPC mechanism

    • Netconf::RPC::Builder to metaprogram RPCs
    • Vendor extension framework for custom RPCs

( 引用: https://github.com/Juniper/net-netconf )

  • RPC mechanism はモジュールによって抽象化されているが, 実装はJuniper 用のみ
    • ユーザーが拡張してCisco に対応することは可能っぽい
    • “netconf” っていう名前のgem なんだけど, たとえばCisco 対応PR 書いたら 受け付けてくれんのかな…

使用例

インストール
$ gem install netconf
設定取得

password は環境変数に入ってるとして,

require 'net/netconf'

Netconf::SSH.new(target: '192.168.0.74', username: 'codeout', password: ENV['PASSWORD']) do |device|
  config = device.rpc.get_config {|x|  # 以下の <rpc> message を送信
    x.configuration {
      x.system {
        x.services
      }
    }
  }
  
  # <rpc>
  #   <get-config>
  #     <source>
  #       <running/>
  #     </source>
  #     <filter type="subtree">
  #       <configuration>
  #         <system>
  #           <services/>
  #         </system>
  #       </configuration>
  #     </filter>
  #   </get-config>
  # </rpc>
  
  puts config  # 以下の設定が取得できる (Nokogiri::XML::Element オブジェクト)
  
  # <data>
  #   <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1413833291"
  #     junos:commit-localtime="2014-10-20 19:28:11 UTC" junos:commit-user="codeout">
  #     <system>
  #       <services>
  #         <ssh>
  #         </ssh>
  #         <netconf>
  #           <ssh>
  #           </ssh>
  #         </netconf>
  #         <web-management>
  #           <http>
  #             <interface>ge-0/0/0.0</interface>
  #           </http>
  #         </web-management>
  #       </services>
  #     </system>
  #   </configuration>
  # </data>
  
  puts config.xpath('//services/netconf').to_xml  # xpath を用いてフィルターできる
  
  # <netconf>
  #   <ssh>
  #   </ssh>
  # </netconf>
end
  • 動く
  • パラメーター(<filter>) は, JUNOS 設定の構造をXML で表現したもの
  • 出力も JUNOS 構造そのままのXML

使うには JUNOS 設定の構造を知っていなければならない.

状態取得
require 'net/netconf'

Netconf::SSH.new(target: '192.168.0.74', username: 'codeout', password: ENV['PASSWORD']) do |device|
  puts device.rpc.get_firmware_information  # 以下の <rpc> message を送信
  
  # <rpc>
  #   <get-firmware-information/>
  # </rpc>
  
  # 以下の状態が取得できる
  
  # <firmware-information xmlns="http://xml.juniper.net/junos/12.1X46/junos-chassis">
  #   <chassis junos:style="firmware">
  #     <chassis-module>
  #       <name>FPC</name>
  #       <firmware>
  #         <type>O/S</type>
  #         <firmware-version>Version 12.1X46-D10 by builder on 2013-12-05 15:05:41 UTC</firmware-version>
  #       </firmware>
  #     </chassis-module>
  #     <chassis-module>
  #       <name>FWDD</name>
  #       <firmware>
  #         <type>O/S</type>
  #         <firmware-version>Version 12.1X46-D10 by builder on 2013-12-05 15:05:41 UTC</firmware-version>
  #       </firmware>
  #     </chassis-module>
  #   </chassis>
  # </firmware-information>
  
  puts device.rpc.get_interface_information(interface_name: 'ge-0/0/0', terse: true)
  # 以下の <rpc> message を送信
  
  # <rpc>
  #   <get-interface-information>
  #     <interface-name>ge-0/0/0</interface-name>
  #     <terse/>
  #   </get-interface-information>
  # </rpc>
  
  # 以下の状態が取得できる
  
  # <interface-information xmlns="http://xml.juniper.net/junos/12.1X46/junos-interface" junos:style="terse">
  #   <physical-interface>
  #     <name>
  #       ge-0/0/0
  #     </name>
  #     <admin-status>
  #       up
  #     </admin-status>
  #     <oper-status>
  #       up
  #     </oper-status>
  #     <logical-interface>
  #       <name>
  #         ge-0/0/0.0
  #       </name>
  #       <admin-status>
  #         up
  #       </admin-status>
  #       <oper-status>
  #         up
  #       </oper-status>
  #       <filter-information>
  #       </filter-information>
  #       <address-family>
  #         <address-family-name>
  #           inet
  #         </address-family-name>
  #         <interface-address>
  #           <ifa-local junos:emit="emit">
  #             192.168.0.74/24
  #           </ifa-local>
  #         </interface-address>
  #       </address-family>
  #     </logical-interface>
  #   </physical-interface>
  # </interface-information>
end
  • 動く
  • パラメーターは, JUNOS XML API そのまま
  • 出力は JUNOS 仕様のXML

  • method-missing のしくみを使って 動的にXML を生成

    • JUNOS XML API に対応するメソッドがいちいち存在するわけではない
    • ヘルパー的な便利動作は少ない
2014-11-04 追記

show コマンドが分かっていれば | display xml rpcXML を調べることができる」と教えてもらいました.

例:

> show chassis firmware | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X46/junos">
    <rpc>                            #
        <get-firmware-information>   # ここを使う
        </get-firmware-information>  #
    </rpc>                           #
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>
設定変更
require 'net/netconf'

Netconf::SSH.new(target: '192.168.0.74', username: 'codeout', password: ENV['PASSWORD']) do |device|
  puts device.rpc.lock(:candidate)  # 以下の <rpc> message を送信
  
  # <rpc>
  #   <lock>
  #     <target>
  #       <candidate/>
  #     </target>
  #   </lock>
  # </rpc>
  
  # 結果
  
  # <ok/>
  
  puts device.rpc.edit_config {|x|  # 以下の <rpc> message を送信
    x.configuration {
      x.interfaces {
        x.interface {
          x.name 'ge-0/0/0'
          x.description 'foo'
        }
      }
    }
  }
  
  # <rpc>
  # <edit-config>
  #    <target><candidate/></target>
  #    <config><configuration><interfaces><interface><name>ge-0/0/0</name><description>foo</description></interface></interfaces></configuration></config>
  # </edit-config>
  # </rpc>
  
  # 結果
  
  # <ok/>
  
  puts device.rpc.validate(:candidate)  # 以下の <rpc> message を送信
  
  # <rpc>
  #   <validate>
  #     <source>
  #       <candidate/>
  #     </source>
  #   </validate>
  # </rpc>
  
  # 結果
  
  # <commit-results>
  # </commit-results>
  
  puts device.rpc.commit  # 以下の <rpc> message を送信
  
  # <rpc>
  #   <commit/>
  # </rpc>
  
  # 結果
  
  # <ok/>
  
  puts device.rpc.unlock :candidate  # 以下の <rpc> message を送信
  
  # <rpc>
  #   <unlock>
  #     <target>
  #       <candidate/>
  #     </target>
  #   </unlock>
  # </rpc>
  
  # 結果
  
  # <ok/>
end
  • 動く
  • パラメーターは, JUNOS 設定の構造をXML で表現したもの
  • 変更対象を指定するためのキーを知っておく必要がある
    • たとえば, <interface> を特定するためのキーは<name>

想像だけでXML を作るのは難しい. 戦略としては次のような感じになると思う.

  1. <get-config> して設定を取得
  2. キーを推測
    • <get-xnm-information> しておけばXSD が手に入る -> 2. は不要
  3. 変更したい部分をXML で記述

結局, これは何?

  • NETCONF のwrapper

    • NETCONF の 3+1 レイヤー*2のうち
      • Transport レイヤー(SSH/TELNET) を抽象化してくれる
      • Operations レイヤー, Content レイヤーは抽象化してくれない
        • 入出力とも デバイス種別 + メーカー依存
        • 現時点ではどうしようもないので, ユーザーが抽象化するしかない -> 面倒くさい!!
    • <get-config>, <edit-config>, <validate>, <commit>, <lock>, <unlock> 用のヘルパーメソッド
    • Nokogiri によるXML DOM 読み書き
    • ちゃんと動く
  • perl, php, go 実装もある

  • CLI をparse するより安全そう

    • CLI 出力とは違って, 構造化されているし エラー捕捉が簡単
    • NETCONF 対応デバイスだけであれば, データモデルが違っても頑張れる気がする
    • 「NETCONF 非対応デバイスとの共存期間」問題は残る

Netopeer

  • https://code.google.com/p/netopeer/
  • New BSD License
  • アーキテクチャ
    • netopeer-cli / gui
    • netopeer-agent
    • netopeer-server

    の3つから成る. IAサーバーなどでnetopeer-agent + netopeer-server を常駐させ, netopeer-cli / gui からのSSHを終端する. その上でnetopeer-cli / gui (NETCONF クライアント) からnetopeer-server(NETCONF サーバー) 経由でIA サーバーを管理する.

f:id:codeout:20141030200429p:plain

( 引用: https://code.google.com/p/netopeer/ )

管理対象として 現時点ではLinux をターゲットにしているようだけれど「NETCONF に準拠していれば ルーターでもある程度動くんでは?」という淡い期待のもと, Netopeer-GUI を試してみた.

実装されている機能

  • persistent connection to multiple NETCONF servers via backend process
  • presentation of state and configuration information from datastores using YIN (/Yang) models
  • creation of configuration with advisor of possible information in current context
  • asynchronous NETCONF notifications using HTTP websockets
  • calling model defined RPC methods

( 引用: https://code.google.com/p/netopeer/ )

  • “persistant connection” をどうやって実現しているかというと, mod_netconf というapache module が常駐してSSH を終端
  • GUI 本体はphp + symfony + DB

これが動いたらすごいですよね!

GitHub wiki を見てもらえると, ワクワク感が伝わるかもしれません.

使用例

インストール

Debian jessie にインストールした.

をソースからビルドする.

ビルド用スクリプトにパッチしたり, Firefly 向けにNETCONF capability の名前空間を変えたりする必要があったが, 今回のポイントではないので省略.

バイスを登録

Firefly は, SSH ポート(22/tcp) もしくはNETCONF ポート(830/tcp) で登録できる.

f:id:codeout:20141030200446p:plain

きちんとcapability の交換ができている.

f:id:codeout:20141030200500p:plain

それ以上, ぜんぜん動かない…

データモデルに差があるとはいえ「初手では動かないだろうけど, 送るXML のテンプレート + XML を読むparser に手を入れたら動くだろう」くらいに思ってた… が, かなりつらい.

なにがつらいかというと, NETCONF の3+1 レイヤーのうち

           Layer                 Example
      +-------------+      +-----------------+      +----------------+
  (4) |   Content   |      |  Configuration  |      |  Notification  |
      |             |      |      data       |      |      data      |
      +-------------+      +-----------------+      +----------------+
             |                       |                      |          YANG
----------------------------------------------------------------------------
             |                       |                      |       NETCONF
      +-------------+      +-----------------+              |
  (3) | Operations  |      |  <edit-config>  |              |
      |             |      |                 |              |
      +-------------+      +-----------------+              |
             |                       |                      |
      +-------------+      +-----------------+      +----------------+
  (2) |  Messages   |      |     <rpc>,      |      | <notification> |
      |             |      |   <rpc-reply>   |      |                |
      +-------------+      +-----------------+      +----------------+
             |                       |                      |
      +-------------+      +-----------------------------------------+
  (1) |   Secure    |      |  SSH, TLS, BEEP/TLS, SOAP/HTTP/TLS, ... |
      |  Transport  |      |                                         |
      +-------------+      +-----------------------------------------+

Transport + Messages レイヤーはapache module, Operations + Content レイヤーはsymfony アプリとして実装されているが,

  • XML parser のふるまいを変えるのに apache module とsymfony 両方の面倒をみる必要があって, 時には情報の橋渡しをしてあげないとダメだったり…
  • NETCONF に使うXML 構造が, symfony 上でテンプレート化されていない…

予想どおりだけど, できたのは<hello> の交換までだった. 「デバイス種別+メーカーに応じてXML を出し分ける」とか 現時点では厳しそう.

まとめ

Juniper Firefly をコントロールするため, いくつかの実装を試してみた.
(ちなみにNETCONF WG のサイト から 他の実装も探せる)

上位レイヤー(Operations + Content) のモデル化が進んでいない現状では, 下位レイヤー(Transport + Messages) を薄くwrap するライブラリーを使うのが良いと思う. 上位レイヤーは, 自分たちが必要な範囲内で, 自分たちで抽象化する.

「なんだNETCONF 単体じゃあできないのか」というガッカリ感が先行するけれど, CLI を相手にするのも面倒だし.

リッチなアプリケーションはレールから外れたときに痛い目を見るし, ネットワークデバイス側は融通が効かない分 レールに乗せるための制約が大きすぎるとも感じた.

一方で, Netopeer のようなアプローチも “将来的には” 成功する可能性があると思っている. Netopeer 本来の使いかたであれば, netopeer-server が管理サーバー内で上位レイヤーの抽象化を担ってくれる. たとえば クライアント - サーバー間のAPI を厳密に規定する代わりに, サーバー内でnetopeer-server API を作ったほうがうまくいく場合もあると思う.

MBaaS のようにSDK を配ってAPI を呼んでもらうやりかたも一般的になってきたし, PyEZ みたいなやつで抽象化レイヤーを作れるかもしれない.

少しずつではあるが, NETCONF データのモデル化が進んできている ことにも注目したい. モデルが標準化されれば, 状況は変わってくると期待している.

*1:バイスが持つ設定や状態の表現方法のこと

*2:詳細は前のエントリー 見てください

知ったかぶりしない NETCONF

最近ネットワーク関連のカンファレンスなどで 再び注目され始めたNETCONF について調べてみた.

NETCONF はネットワークデバイスの設定や状態を取得/更新するための管理用プロトコルで, 特にネットワーク全体を統合管理できるという特徴を持つ. RFC で標準化されている.

背景

NETCONF のはじまりは結構古くて, 2001 年くらいから「こんなのあったらいいよね」という議論があったっぽい. ネットワーク運用者にとって 当時の大きな問題は「CLI + SNMP によるネットワーク管理はめんどい」ということ.

2002 年のIAB Network Management Workshop でも議論され, その結果がInformational なRFC としてまとめられている.

RFC3535: Overview of the 2002 IAB Network Management Workshop

  • IAB: Internet Architecture Board
    • ワークショップにはIAB のメンバーに加え, 各分野のエキスパートが参加
    • アプリエンジニアにとっては, アジャイルマニフェストと その土台となった議論をイメージしてもらうといいかもしれない
  • 当時 管理用プロトコルとして広く実装されていたのは, CLISNMP
    • CLI には問題があった
    • SNMP にも問題があった
      • 仕様上はデバイスの設定変更も可能だが, 実装が普及しなかった
      • ほとんど監視とエラー通知に使われていた

そこで議論された「こんなのあったらいいよね」はまとめると14項目あって,

  1. 運用者が簡単に使えること
  2. 設定値, 状態, 統計情報を区別して扱えること
  3. 設定値, 状態, 統計情報を個別に取得できること. デバイス間で比較できること
  4. ネットワーク全体を統合的に扱えること
  5. 複数デバイスにまたがる設定について, トランザクション処理できること
  6. ある設定が投入されたとき, 不要な変更, 不要な状態遷移をしないこと
  7. 設定のセーブ/リストアができること
  8. 設定のバリデーションができること
  9. 設定データの標準的なスキーマがあること
  10. 設定はテキストとして扱えること
  11. ロールベースのアクセスコントロールが効くこと
  12. デバイス間でACL (Access Control List) の比較/検証ができること
  13. 複数バージョンの設定を投入できること. どのバージョンも個別に有効化できること
  14. データ志向のアクセスコントロール / タスク志向のアクセスコントロール, 両方をサポートすること
    • SNMP: データ志向 (どんなデータにアクセスできるか)
    • CLI: タスク志向 (どんな操作を許可するか)

こんな要件をスタートとしてIETF で議論され, 2006 年にRFC4741~4744 として標準化された. 当時 SOAP とかWSDL がバズっていたようで, NETCONF もその影響を受けている.

私が初めてNETCONF を知ったのは2008 年だったが, マーケットが小さかったのか 周辺仕様が定まってなかったのか, 実装を目にすることはあまりなかった.

ようやく見えてきた形

その後も継続的に議論されていたが

  • ネットワークデバイスからの通知
  • RPC 向け部分ロック
  • デフォルト値の扱い
  • トランスポート
  • データモデル

なども含めて標準化が進んできたのが2011 年. アクセスコントロールモデルの標準化が2012 年. 現在は

  • ネットワークデバイスからの通知に デバイス側からのSSH セッションを使う (Reverse SSH)
  • やっぱREST だよ (RESTCONF)
  • 初期設定の自動投入 (Zero Touch Provisioning)

このような標準化のための議論が進んでいる.

最近になってNETCONF が再注目されているのは, SDN / NFV がバズっていることと関係があると思っている. 解決する問題やユースケースについてNETCONF とは一致しないものの, 自動化という意味合いで方向性は一緒. SDN がデプロイされ始めてネットワークの自動化が一般的になり, 既存の資産について「こいつも自動化したいんだけど」という強い要望が背景にあるんだろうなと考えている.

NETCONF とは

NETCONF はRFC4741 として2006年に標準化された. (NETCONF1.0) その後デバイス操作の拡張やロックのしくみ, エラーハンドリングなどを含めて2011年にRFC6241 としてまとめられている. (NETCONF1.1)

NETCONF のベースはXML-RPC で, オーケストレーター(client) がネットワークデバイス(server) を制御する.

大まかに言えばNETCONF は

  1. Operations レイヤー
  2. Messages レイヤー
  3. Secure Transport レイヤー

の3層構造で, 上にはContent レイヤーと呼ばれる層がある.

           Layer                 Example
      +-------------+      +-----------------+      +----------------+
  (4) |   Content   |      |  Configuration  |      |  Notification  |
      |             |      |      data       |      |      data      |
      +-------------+      +-----------------+      +----------------+
             |                       |                      |          YANG
----------------------------------------------------------------------------
             |                       |                      |       NETCONF
      +-------------+      +-----------------+              |
  (3) | Operations  |      |  <edit-config>  |              |
      |             |      |                 |              |
      +-------------+      +-----------------+              |
             |                       |                      |
      +-------------+      +-----------------+      +----------------+
  (2) |  Messages   |      |     <rpc>,      |      | <notification> |
      |             |      |   <rpc-reply>   |      |                |
      +-------------+      +-----------------+      +----------------+
             |                       |                      |
      +-------------+      +-----------------------------------------+
  (1) |   Secure    |      |  SSH, TLS, BEEP/TLS, SOAP/HTTP/TLS, ... |
      |  Transport  |      |                                         |
      +-------------+      +-----------------------------------------+

(4) Content レイヤー

  • 設定, 状態, 通知などのデータ
    • 「YANG」と呼ばれるNETCONF のためのデータモデルに従って格納する
      • RFC6020
  • NETCONF の範疇ではない
  • オーケストレーター上のアプリケーションはこのデータにアクセスするため,
    • データ構造がデバイス種別やメーカーに依存しない
    • データ構造が明確

    であることが望ましい

たとえば, デバイス上のインターフェイス設定データは次のような構造になっている.

container interfaces {
  list interface {
    key "name";
    leaf name { type string; }
    leaf description { type string; }
    leaf type {
      type identityref { base interface-type; }
      mandatory true;
    }

    leaf enabled {
      type boolean;
      default "true";
    }

    leaf link-up-down-trap-enable {
      if-feature if-mib;
      type enumeration {
        enum enabled { value 1; }
        enum disabled { value 2; }
      }
    }
  }
}
データストア

設定データは「データストア」に保存される. データストアは3種類ある.

名前 概要
<running> 現在アクティブな設定を保存
<candidate> アクティブでない設定を保存
<startup> 起動後にロードされる設定を保存

<running> を変更すれば動作を変えることができるが, デバイスを再起動すると その変更が失われる. 変更を保存するには<running> を<startup> にコピーすればよい.

(3) Operations レイヤー

XMLベースで, デバイス操作方法やデータストアへのアクセス方法, パラメーターを規定している.

操作 概要
<get-config> 設定データの一部または全部を取得する
<edit-config> 設定データを一部または全部を変更する
<copy-config> 設定データを入れ換える (<candidate> を <running> に, など)
<delete-config> 設定データを全削除する (<candidate> を削除, など)
<lock> 設定データをロックする
<get> デバイスの状態を取得する
<unlock> 設定データをアンロックする
<close-session> NETCONF セッションを終了する
<kill-session> NETCONF セッションを強制終了する

なお, NETCONF server / client が各種Capability をサポートすることにより 機能拡張できる.

操作 概要
<commit> <candidate> を<running> にコピーする
<confirmed> パラメーターが指定された場合, Confirmed <commit> 動作をする.
(再度<confirmed>パラメーターなしで<commit>が実行されない限り, 10分後に設定が巻き戻る)
<discard-changes> <commit> 前の<candidate> を削除する
<cancel-commit> Confirmed <commit> をキャンセルする
<validate> 設定データを検証する
<get-schema> NETCONF サーバーからXML スキーマを取得する (RFC6022)

(2) Messages レイヤー

  • RPC とNotification のしくみを規定
    • <rpc> message と, それに対する<rpc-reply>
    • ステータス<rpc-error> と<ok>

Ethernet0/0 のMTU を1500 にする例:

<rpc message-id="101"
     xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <edit-config>
    <target>
      <running/>
    </target>
    <config>
      <top xmlns="http://example.com/schema/1.2/config">
        <interface>
          <name>Ethernet0/0</name>
          <mtu>1500</mtu>
        </interface>
      </top>
    </config>
  </edit-config>
</rpc>

成功した場合:

<rpc-reply message-id="101"
     xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <ok/>
</rpc-reply>

(1) Secure Transport レイヤー

  • 様々なトランスポートプロトコルが利用可能だが, SSH が主流
  • 現在はNETCONF1.1 over TLS の仕様策定が進んでいる

NFV / SDN との比較

NETCONF はOpenFlow などSDN と比較される場合があるけれども, 解決する問題が違う. ネットワークデバイスを制御するプロトコルであって, トラフィックを自由に扱えるわけではない. もちろん設定を変えることでデバイスのふるまいを変えて 間接的にトラフィックを制御することは可能だが, トラフィック制御の機能はデバイスの仕様次第だし, リアルタイムに動的制御するには向かないアーキテクチャ.

逆に言えばNETCONF はシンプルで, ハードウェア依存が小さそう. 「OpenFlow スイッチみたいなデバイスじゃなくても 自動化できるかも」という期待が持てる. もうひとつ「デバイスを設定するプロトコルを統一したい(SDN 用のHWも含めて)」という要求にはNETCONF がマッチすると思う.

一方でSDN がすごいのは, OpenFlow で言う「Flow Table によって動的にトラフィックが制御できる」点であって, たとえば「DDoS を受けたらトラフィックを絞る」みたいなリアルタイム制御が実現可能なこと. 「デプロイされるアプリに応じて必要なサービスVLAN を作る」ような自動プロビジョニング的ニーズもあるが, そのためだけに「ハードウェアはOpenFlow スイッチに限る」という制約を課せる事業者は多くないんじゃないかと思う.

半分期待を込めて言えば 自動設定程度のことはハードウェアによらず実現できるべきなので, NETCONF の良い実装が出てきて, ネットワーク設計やハードウェア選定の幅が広がって欲しい.

それから, NFV は「ネットワーク機能の仮想化 = 汎用サーバ上でのネットワーク機能具現化」だから「ネットワークハードウェアを抽象化」しようとするNETCONF とはちょうど反対のコンセプトに見える. NFV とSDN が補完関係にあるように, NETCONF もSDN を補完して「Flow コントローラーだけど, ついでにハードウェアルーターまで面倒みるよ」みたいな動きをするかもしれない.

所感

いまどき XML?

メッセージエンコーディングには流行があるので「JSON やMessagePack じゃないとダメ」とは思わないが, エンコーディングを変更できる余地が残っていると良かったかも.

  • 設定が馬鹿デカいとXML はオーバーヘッド大きそう
  • JSON で読み書きできたら, web 界隈の知見が生かせたかもしれない

と思う.

SSH 以外は使えないの?

メッセージングだけのためにSSH はオーバーヘッド大きいので, せめてTLS, できればWebSocket を使いたい.

  • NETCONF1.0 over TLS は5539 になっているが, NETCONF1.1 がまだ
  • NETCONF over WebSocket はI-D (Internet Draft. 標準化前の状態)

で, 結局やりたかったことは実現できそうなの?

RFC3535 の14コの夢のうち, 半分ほどは定義されている.

2 . 設定値, 状態, 統計情報を区別して扱えること
3 . 設定値, 状態, 統計情報を個別に取得できること. デバイス間で比較できること
7 . 設定のセーブ/リストアができること
8 . 設定のバリデーションができること
10 . 設定はテキストとして扱えること
11 . ロールベースのアクセスコントロールが効くこと
13 . 複数バージョンの設定を投入できること. どのバージョンも個別に有効化できること
14 . データ志向のアクセスコントロール / タスク志向のアクセスコントロール, 両方をサポートすること

複数デバイスをまとめて 1つのネットワークとして扱う, 下記オーケストレーション機能については実装次第.

4 . ネットワーク全体を統合的に扱えること
5 . 複数デバイスにまたがる設定について, トランザクション処理できること
12 . デバイス間でACL (Access Control List) の比較/検証ができること

NETCONF client アプリで頑張らないといけない部分だが, まだ問題があるように見える.

9. 設定データの標準的なスキーマがあること

現時点で標準的なスキーマがないので, 設定の構造やパラメーター名はデバイス種別+メーカー依存. NETCONF client が差分を吸収しないといけない. さらにはNETCONF 向けXML スキーマを公開していないメーカーもあるので, CLI やリファレンスマニュアルから想像しつつXML を作らないといけないが... 厳しそう.

Juniper のようにNETCONF ライブラリをOSS で出してもらうとか, Brocade のようにYANG モデルを公開してもらうとか, 何かしら情報がないと実装できないよね.

YANG によるモデル化は進んでるの?

  • 調査できた範囲では, まだまだModule が足りない. おそらくinterface + IP のみ
    • Routing Protocol もまだ
  • GitHub
  • YANG が柔軟すぎて, メーカー依存が増える予感

「運用者が簡単に使える」ってどういうこと?

SIer やソフトウェア開発者がいなくても, 運用者が使える」と説明しているスライド もあるが, 「運用者でもライブラリを使えば簡単にコーディングできる」であって欲しい.

rancid / exscript で自動化してるけど,NETCONF 使うべき?

比較的ちゃんと動くし,ぜひ検討するべきだと思う. 正しくエラーを捕捉できるし, リターンコードの検証が正確になる.

  • デバイス個別にCLI コマンドを出し分けていると思うが, XML を出し分けるようにすればいい
  • YANG モデル化が進んだとしてもXML スキーマは共通にならず, デバイス種別+メーカー依存が残ると思われる
    • 本気でやるなら YANG モデルをもう一段wrap して自分たちのモデルを作り, デバイス+メーカー差分を吸収するようにすれば 扱いやすくなるはず

最後に

長く書いてしまいましたが, NETCONF には期待しています.

さて, 次回は やってみよう NETCONF です.

emacs 24.4 をインストールしてみる

2014/03 月に出た24.3 以来, ひさしぶりのupdate. さっそくインストールしてみた.

  • 見やすくなったブラウザー
  • 自動インデント
  • 矩形選択 -> コピー&ペースト
  • 重複行の削除

などの便利機能が使える.

OSX 10.10

$ brew update
$ brew install emacs --cocoa

/usr/local/Cellar/emacs/24.4/Emacs.app ができるので, /Applications にコピー.

いろいろ下位互換が失われてるっぽい

apel が読みこめない

Wrong type argument: sequencep, 769

apel 依存のlisp もずいぶん無くなったし, もうメンテナンスされていないようなのでapel を捨てることにした. さしあたって使えなくなるのは

これも古いlisp にmonkey patch して だましだまし使っていたもの.

(OSX 10.5~) フォント バックエンドの変更

普段使っていたこんな設定が動かない.

(set-face-attribute 'default nil :family "Ricty")
(set-fontset-font t 'japanese-jisx0208 (font-spec :family "MS Gothic"))
(set-fontset-font t 'katakana-jisx0201 (font-spec :family "MS Gothic"))
(set-fontset-font t 'japanese-jisx0212 (font-spec :family "MS Gothic"))

ボールドなRicty フォントは読みづらいので困る.

f:id:codeout:20141023125709p:plain

(左: Ricty, 右: MS ゴシック)

新機能

見やすくなったブラウザー (eww - Emacs Web Wowser)

eww (左) とw3m (右).

f:id:codeout:20141023125724p:plain

  • 画像やフォームコンポーネントが表示されていて かなり見やすい
  • もちろんJavaScript は動作しない
  • w3m と比べると まだまだ機能が少ない
    • 基本的な用途には十分そう
    • ただ, emacs 上でスクレイピングすることが多いのでw3m-filter-delete-regions などの便利function 待ち

速くて ちょっと使うには良さそうだが, 普段はw3m を使うと思う.

自動インデント (electric-indent-mode)

デフォルトでelectric-indent-mode がenable になったので, 突然インデントされ始めて「なにこれ!」って思う人いるはず.

  • 自動でやられると困るMajor mode では,個別にdisable する

      (add-hook 'markdown-mode-hook '(lambda ()
        (electric-indent-local-mode -1)
        ...
    

markdown-mode + 自動インデントは本当ひどい.

f:id:codeout:20141023125725g:plain

  • electric-indent-mode 内でも, RET の代わりにC-j で自動インデントせず改行できる
    • 使い分け,私にはムリだった...

矩形選択 -> コピー&ペースト (rectangle-mark-mode)

rect.el が改良され, 矩形選択 -> コピー & ペースト しやすくなった.

C-x SPC -> リージョン選択したら

  1. M-w or C-w でコピー
  2. C-y でペースト

キーバインドが統一されて 便利になった!

重複行の削除 (delete-duplicate-lines)

M-x delete-duplicate-lines で重複行が削除できるようになった. バッファ全体 / リージョン選択後 どちらも使える.

その他の機能 / 変更点

GNU Emacs NEWS – history of user-visible changes からどうぞ.

通信内容からiPhone アプリの振る舞いを推測する

先日iPhone で撮った動画をメールで送ろうとして,送信エラーで送れなかった.ところがMessages.app だとちゃんと送れる.

「くそーなんだこれ.Messages なにやってんのかまったく分からん!」と思って,このアプリの振る舞いを調べてみた.

写真アプリの仕様で たぶん「特定サイズ以上のファイルはメールで送れない」が,Messages はそんな制限なくて「S3 経由で動画を送受信している」のを知った話.

通信内容を見る

Xcode を使うと iPhoneバイス上の通信を見ることができる.

Messages.app (iPhone) --> Messages.app (MBP)

上のように動画を送信したときのパケットシーケンスはだいたいこんな感じ.

f:id:codeout:20141012000809p:plain

パケット数で言うと90s で30,000 pkts を超えるが,コツをつかめばわりとパケットダンプが読める.

ところどころ ? になっているのは,通信がTLS で暗号化されていて「なにか通信している」ことしか分からない箇所.まあでも

  • 通信先アドレス / ホスト名 / ポート番号
  • 通信先サービス概要
  • 送信 / 受信のパケット数,バイト数
  • どちらから通信を開始しているか
  • ARP / NDP のしくみ
  • TCP のしくみ
  • DNS のしくみ
  • STUN やTURN のようなNAT 関連のしくみ
  • 暗号化のしくみ

が分かると なんとなく想像することができる.

2つのMessages アプリがやっていること

推測混じりで書くと

送信側 (iPhone)
  1. AppleAPI サーバー に「添付ファイルをアップロードしたい」と伝える
  2. API サーバーは添付ファイルのアップロード先 (ここでは たぶんAmazon S3 のエンドポイント) を返す
  3. 添付ファイルアップロード
  4. メッセージ本文 + 添付ファイルURL をAPI サーバーに送る
  5. “送信完了” サウンド再生
受信側 (MBP)
  1. プッシュ通知を受け取る
  2. API サーバーからメッセージをダウンロード
  3. 添付ファイルがあるので,S3 からダウンロード
  4. API サーバーに「受信完了 + メッセージを開いた」通知
  5. メッセージを表示

こういう動きをしていると思われる.

おもしろいのは,

  • iPhonexxx-content.icloud.com 宛てに通信しているのに,MBP はいきなり xxx-content.icloud.com.akadns.net と通信している
    • AppleAPI サーバーもGSLB のようなサービスを被せているっぽいが,CNAME 解決をしない
    • OS かアプリがキャッシュしていたか,なにか別の方法でホスト名を通知したか...–> よくわからなかった
  • iPhone からの添付ファイルアップロードがhttps (TLSv1.2) なのに,MBP へのダウンロードはhttp
    • 認証情報を渡さないと “403 Forbidden”
    • iPhone / MBP の仕様の違いなのか,アップロード / ダウンロードの違いなのか...–> 調べてない

まとめ

Messages 便利.S3 経由で デカいファイルを扱えるようにできてる.

  • 今まで SMS クライアント + テキストチャットアプリだと思ってた
  • SMS ですらなかった.普通にIP

ちなみに,送ろうとしてた動画は30MB もあった :)
メール送信がエラーしてよかった.

おまけ

iPhoneバイス上の通信を見る方法

iOS5 からRVI (Remote Virtual Interface) をサポートしているので,USB 接続されたOSX 端末に仮想インターフェイスを作れば そいつ経由でパケットをTap できる.

$ rvictl -s <iPhone のUDID>
# 仮想インターフェイス rvi0 (link type: PKTAP) ができる
  • UDID はiTunes などで確認できる
    • USB 接続し,概要タブ上の “シリアル番号” をクリック
  • iPhone 上のインターフェイスは区別できない
    • 全通信をTap し,混ざったものをrvi0 で見れるだけ
  • 付属のtcpdump でファイル出力すると,pcap-ng フォーマットになる

あとは例えば

$ tcpdump -i rvi0 -s 0 -w iphone.pcapng

のようにすればいい.

終わったら

$ rvictl -x <iPhone のUDID>

か,USB ケーブルを抜けば 仮想インターフェイス rvi0 が消える.

Messages.app (OSX) のデータ確認方法

sqlite データベースとして ~/Library/Messages/ に保存されているので,簡単に開ける.

$ sqlite3 ~/Library/Messages/chat.db
SQLite version 3.7.13 2012-07-17 17:46:21
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
_SqliteDatabaseProperties  chat_message_join
attachment                 handle
chat                       message
chat_handle_join           message_attachment_join

ダウンロードした添付ファイルも ~/Library/Messages/Attachments/ にある.