LGTM

Looks Good To Me

ステルスWiFi AP を駆逐した

macos がステルスWiFi AP に接続したときに脅してくるこれ,しばらく意味が分からなくて放置していた.

たぶんこういうことかな,というのを教えてもらって「なるほど,良くないかもしれん」と思い駆逐した.

ステルスAP が接続リストにいるリスク

アクティブにProbe Request を投げるため,接続可能なSSID の一部が漏れるから.せっかくMac Randomization してもSSID がリストで漏れており,個体識別されるリスクが高まる.ステルスじゃなくしたら漏れないかというと そうではないので「漏れやすさ」の問題.

ステルスAP が設定されているmacos 10.12.2 とIOS 10.2 の動き

ちゃんと調べたわけではありません.もし違っていたらご指摘ください.あれこれ操作したときの振る舞いを1時間くらい観察しました.

  • ステルスなSSID を探して,すべてのSSID についてProbe Request を投げまくる
  • ステルスAP に接続中のときも,そのSSID を含めて投げまくる

  • 違うAP がそのSSID を偽装したら

    • 正規のステルスAP がいない環境で,偽のステルスAP (SSID 同じ,認証なし) をつくる
    • 自動でそいつに接続することはなかった

ステルスAP が設定されていないmacos 10.12.2 とIOS 10.2 の動き

  • WiFi をON にした瞬間はActive Scan する = Probe Request を投げる
  • 放っておくとIOS 10.2 はActive Scan する
    • 端末がSleep しているのと関係あるかも (くわしく調べてない)
    • macos はそんな動きをしない (こちらはSleep していない)
  • それ以外SSID を投げていない.

かなりProbe Request が減った.

ステルスをやめる

そもそもステルスにしていたのは視界に入れたくなかったから.ほとんどメンテ不要なやつは消えていてほしい.とはいうものの,どこに持ち込むかわからない端末のほうのセキュリティリスクを下げたい.ステルスをやめることでAP のセキュリティリスクはさほど上がらない.

a/b/g/n/ac など広く802.11 を受けられる端末で

sudo tcpdump -I -ien0 -e -s0 type mgt subtype probe-req

を実行しつつ,一台ずつWiFI off/on してヘンなSSID を投げなくなるまで設定を削除した.

IOS はAP が存在しないと消せないので面倒だが,めちゃくちゃ古いSSID を探していて「おお…こいつ…こんなになっても〇〇を覚えてるのか…」って感傷的になれるのでオススメです.

BGP Community を透過する事業者がどれくらいいるか調べた

11月末にInternet Week 2016 というイベントで,「BGP Community の基本設計」について発表してきた.内容については 発表スライド をご覧いただければ良いかな と思うが,壇上から挙手アンケートを取ってみてびっくりした.

「顧客から受信したBGP Community を消さずに透過しているかた,どのくらいいますか?」の結果が 1%ほどだった.

該当するけど手を上げられなかった方もいると思うし,母数として全員がAS 運用者じゃなかったかもしれない.が,「えっ! 少なすぎる!」と思った.世の中そんなもんなのかな,と思ってざっと調査した内容を書いておく.

結果から書いておくと「割合的にはまあそんなもん」だった.

BGP Community は API なんですよ

BGP Community は「便利なタグ」だったり「他社ネットワーク内での自社経路のふるまいを定義できるノブ」だったりする.たとえば

  • 経路がネットワークに入ってきたのはどの国か,顧客に伝えるタグ
  • 他社ネットワーク内でLocal Preference を下げるためのノブ

のように,BGP プロトコルの範囲のなかで付加価値をつけることができる.「HTTP(S) でやれば?」という考えも頭をよぎるが,IP レイヤーの操作をさらに上のレイヤーに依存するのは危険かもしれないし,レイヤーをまたぎすぎている感がある.「HTTP(S) でもできる」が理想だけれど,IP ならではの柔軟性と拡張性を得るためにもBGP プロトコルでやりたいところ.

上のように AS65000 がBGP Community に乗せて便利情報を提供してくれている場合,AS65001 の立場からすると「自社に不利益がある」「AS65000 の利用規約に反する」ような場合を除いてAS65002 に透過しない理由はないはず.逆もしかりで,AS65002 が「AS65000 内での経路のふるまいを制御したい」と考えているとしてAS65001 がそれを遮断する理由はないように思う.

が,アンケート結果では「でも止めてる」だし,観測範囲内でも止めてる事業者がほとんどに見える.

特にAS65002 がAS65001 の顧客だった場合,情報を透過するほうがAS65001 サービスの付加価値につながりそうだし,ぜんぶ透過しなくても工夫してやればいいのに…と思う.

「将来,トランジット事業者(上の絵でいえばAS65000) を変えるかもしれない」という懸念はあるが,トランジット事業者が似たAPI を提供してさえすればAS65001 で変換するのがよさそう.他社のサービスや製品を組み合わせてうまいこと抽象化することは,サービスを作る側の手腕の見せ所かな と思う.

調査結果

AS間の関係を transit / peer / customer / unknown に分類し「X から受信したBGP Community をY に透過する事業者数」を数えた.調査の方法はあとで.

たとえば transit からcustomer に向かう矢印は93 だが,「transit から受信したBGP Community をcustomer に透過するAS は93 個あった」という意味.4バイトAS は除外してカウントしている.

BGPlay コレクターの配置的にすべてのAS をカバーしていないかもしれないが,フルルートの1/11 ほどのサンプル数.

  • customer から受信した経路を透過する事業者が比較的多そう
    • 送信先によって透過 / 不透過を区別してそう
    • 特にtransit 方向にはフィルターしてそう
  • 実際に使われている2バイトAS 約6万個に対して,透過しているAS はおよそ1% 程度しかない

ということがわかる.unknown が多いのは,参照したAS間の関係データベースが良くなかったかもしれない.

調査方法

BGPlayThe CAIDA UCSD as-relationships - 20161201 のデータを使った.

2バイトAS のすべてについて

  1. そのAS がoriginate している適当な1 prefix を選ぶ
  2. BGPlay でBGP Community が透過しているかどうか調べ,AS_PATH 上の各AS について transit / peer / customer のどれからどれに送信する際に透過するかをマークする
  3. AS 間の関係についてはCAIDA のas-relationships を参照する

仮定として

  • BGPlay コレクターはcustomer 相当と考える
  • BGP Community のGlobal Administrator がAS_PATH 上に現れるもののみをカウント
    • 現れない場合,BGP Community がどこでつけられたか判別できない
    • そのBGP Community は,AS_PATH 上のGlobal Administrator より右でつけられたものと考える

たとえば

  • AS_PATH: 65000 65001 65002 65003
  • BGP Community: 65002:100

の場合

  • Community は65002 か65003 でつけられたと仮定
    • → 65001 はCommunity を透過する
  • 65001 から見て65002 は transit で,65000 は customer
    • → 65001 は transit から受信したBGP Community を customer に透過する
  • 65000 から見て65001 は customer で,BGPlay コレクターも customer
    • → 65000 は customer から受信したBGP Community をcustomer に透過する

経路数でいえばフルルート68万経路に対して6万経路分,1/11 サンプルを調査した.BGPlay コレクターの配置から「透過しているんだけど観測できない」AS があることにも注意.

なお,今回は割合を概算したかったので 4バイトAS は除外してカウントしている.

Internet Week 2016 発表スライド

発表スライドをリンクしておきます.

  • AS 運用者向け
  • BGP Community 設計のコツ
  • API と思って設計しよう

のような内容です.よければご覧ください.

https://speakerdeck.com/codeout/bgp-communityfalse-ji-ben-she-ji

xlogin でコマンド自動投入したあと,手動制御にもどす

xlogin -s に渡すexpect スクリプトを書けばよい.

設定投入は自動でやって,温かみのある commit を打ちたい場合などにつかう.( そんなのcommit まで自動でやれよ,という話ではある)

jlogin の例

log_user 1

# 既存のrun_commands を参考に
proc run { prompt command } {
    # 補完を有効にしておく
    send "set cli complete-on-space on\r"
    expect -re $prompt {}

    # handle escaped ;s in commands, and ;; and ^;
    regsub -all {([^\\]);;} $command "\\1;\u002;" esccommand
    regsub {^;} $esccommand "\u002;" command
    set sep "\\1\u001"
    regsub -all {([^\\])\;} $command "$sep" esccommand
    set sep "\u001"
    set commands [split $esccommand $sep]
    set num_commands [llength $commands]
    for {set i 0} {$i < $num_commands} { incr i } {
        send "[lindex $commands $i]\r"
        expect {
            -re "^\[^\n\r *]*$prompt $" {}
            -re "^\[^\n\r]*$prompt." { exp_continue }
            -re "(\r\n|\n)" { exp_continue }
        }
    }
}


# 自動で入力したいコマンド群
set cmd_text "configure
set interfaces ge-0/0/0 description foo
show | compare
commit check
"

set command [join [split $cmd_text \n] \;]
run $prompt $command
interact

コマンド群を自動で send したあと,interact を呼べばいい.

実行例

configure という名前で保存してあるとして,

codeout $ jlogin -s configure router1
router1
set cli complete-on-space on
Enabling complete-on-space

codeout> configure
Entering configuration mode
The configuration has been changed but not committed

[edit]
codeout# set interfaces ge-0/0/0 description foo

[edit]
codeout# show | compare
[edit interfaces ge-0/0/0]
+   description foo;

[edit]
codeout# commit check
configuration check succeeds

[edit]
codeout#

[edit]
codeout# <-- ここで手動制御にもどる

複数台に対して同じことをやりたい場合,

codeout $ jlogin -s configure router1 router2 ...

のようにまとめて呼ぶ.

ほかのclogin などで試してないが,同じような処理でいけるはず.

MRAI とBGP Withdrawn とパケットロス

BGP には,無駄な経路計算を減らすためのMRAI Minimum Route Avertisement Interval) というしくみがあります.BGP Update を送信する前に一定時間待ち,経路変化をバッファリングできる機能です.バッファ中にさらに経路変化があった場合,BGP Update を二度送信しなくてすみます.

(MRAI の概要については前のエントリーをご覧ください)

codeout.hatenablog.com

一方で,MRAI の実装によりリスクも存在します.経路変化を下手にバッファすることで,たとえばルーティングループが発生し,パケットロスする可能性があります.本エントリーでは どのような場合にパケットロスが発生しうるか,さらにそれを緩和するために何ができるかについて考えてみます.

十分に検討できてないかもですし,いろいろご意見いただるとうれしいです.

目次

経路を加えるBGP Update と経路を消すBGP Update

タイトルには便宜的にBGP Withdrawn と書いてしまいましたが,「経路を消すためのBGP Update メッセージ」の意味で使っています.経路を加える場合も消す場合もBGP Update メッセージをつかいますが,ここでは次のように呼び分けることにします.

  • BGP Update
    • 経路の増加や変更を伝えるためにつかう.NLRI フィールドやPath Attributes フィールドをつけて送る
  • BGP Withdrawn
    • 経路の削除を伝えるためにつかう.Withdrawn Routes Length フィールドやWithdrawn Routes フィールドをつけて送る

対称的な2 つのUpdate メッセージですが,BGP Withdrawn については注意して送信しないとマズい場合があります.

BGP Withdrawn に注目するのはなぜか

BGP Withdrawn はループの原因になりえるから

BGP での経路コンバージェンスを考えるとき,BGP Withdrawn はBGP Update より弱い側面があります.BGP のしくみ上,BGP Withdrawn をトリガーにルーティングループが起こる可能性があるからです.

なるべくシンプルな例で説明しますが,たとえば 4x ルーターが次のように接続されていて BGP ピアが張られているとします.

初期状態:

  • 右のほうで10.0.0.0/20 がオリジネートされていて
  • 左のほうで10.0.0.0/24 がオリジネートされている

10.0.0.0/24(左) の経路が消えるとどうなるか.

  1. 左からBGP Withdrawn が伝搬し,10.0.0.1 のNextHop が右向きに切り替わる (page 3)
  2. さらにBGP Withdrawn が伝搬し,次のルーターでもNextHop 右向きに切り替わる (page 4)

page 3 でNextHop が互いに向かい合っている = パケットはループ(ピンポン) する ことに注意してください.BGP のしくみ上これを避けることが難しいため,なるべくこの期間を短くして

  • 一旦パケットロスするが,TCP で再送されるまでにループが解消されている
  • あるいは IP TTL が0 になるまでにループが解消されている

ようにすることが理想的です.

一方 BGP Update の場合はこのようなループが発生する可能性が少ないと考えられます.

初期状態:

  • 右のほうで10.0.0.0/20 がオリジネートされている

10.0.0.0/24(左) の経路が出現するとどうなるか.

  1. 左からBGP Update が伝搬し,10.0.0.1 のNextHop が左向きに切り替わる (page 7)
  2. さらにBGP Update が伝搬し,次のルーターでもNextHop 左向きに切り替わる (page 8)

どの状態においてもBGP Withdrawn の時のようなループは発生しません.Best Path (パケット転送に使われる最も強い経路) だけを伝搬させるBGP のしくみ上 このような動きになります.

もちろん,さらに複雑な構成でBGP Update をトリガーとしてループが起こることはありますが,Indirect Nexthop がループしていたり,経路フィルターなど他の要因とのコンボであることがほとんどです.

BGP Withdrawn は伝搬に時間がかかるから

BGP Withdrawn を送信する際には「過渡期 = BGP コンバージェンスタイム を短くする必要がある」という話をしました.しかしながら,BGP Update と比較してかなり長いことが分かっています.

一例として,JANOG38 で@maz_zzz さんがおもしろいデータを紹介していました.

f:id:codeout:20160810014452p:plain
色々なトラヒック制御の利点と欠点

これはRIPE のRIS Routing Beacon を日本のISP で観測したデータですが,BGP Update(左) の伝搬時間に対し,BGP Withdrawn(右) はおよそ3 倍も長くなっています.

BGP 運用者にとって,これは感覚的に納得できるデータです.というのは「BGP Withdrawn ってそんなもん」だからですね.*1

シンプルな例で説明してみます.

  • AS65000~AS65005 が上のようにBGP ピアを張っている
  • 各AS の関係は ピア / トランジットが入り混じっている
  • AS65004(左下) の経路がAS65003(右中) でどのように見えるかを考える

AS = 通信事業者と考えてください.AS 間の関係については@yuyarin さんのスライド(page 21~31 あたり) が分かりやすいです.

AS65004(左下) が持つ経路は,矢印の流れに沿ったBGP Update によって伝搬し,AS65003(右中) では3 Path から聞こえてきます.BGP では複数Path の経路をまとめて1 つとして扱うため,3 Path 中1 つでもBGP Update を受信すれば「伝搬した」と考えることができます.

一方 BGP Withdrawn の場合も同じ矢印の流れに沿って伝搬しますが,

「経路が消える」とは3 Path すべてが消えることを意味するため,BGP Withdrawn の伝搬時間は「最も伝搬時間の遅いPath」に律速されてしまいます.

こうしてみると,BGP Update に比べてWithdrawn の伝搬に時間がかかることが原理的に理解できると思います.

メモ: BGP Update のコンバージェンスタイム

一般に BGP コンバージェンスタイム は「定常状態に落ち着くまでの時間」を指すので,上記のような最短の1 Path のBGP Update 伝搬時間とは異なります.しかしながら最強のPath が最速で伝搬することが少なくないため,やはりBGP コンバージェンスタイムもBGP Withdrawn >> BGP Update になりがちです.

MRAI + BGP Withdrawn の危険性

さて,すでに長くなりましたがBGP Withdrawn に注目する理由について説明しました.

  1. BGP Withdrawn トリガーでルーティングループが発生することを,原理上避けられない場合がある
  2. 時間にして100s のオーダーになる可能性がある

という2点です.特に2 . について,MRAI は不安定な時間が長くなる原因のひとつなのでは? と考えています.

前のエントリー で調査した Juniper vSRX / Cisco IOS-XRv / Quagga の例でいえばvSRX パターンのMRAI 実装(out-delay) がまずいです.BGP Withdrawn とのコンボにより「RIB からすでに消えているのに,N 秒間 (= MRAI タイマー値) はBGP Withdrawn を出さない」という動きをするからです.ちょうど「N 秒間は経路オリジネート = discard し続ける」ような動作です.

BGP Neighbor ルーターではRIB から消えず,上のようなルーティングループ状態を維持してしまう.運悪くMRAI ルーターが直列する場合,N 秒は積算されます.実際のインターネットでは 5.5万AS が網状に接続されており,周辺パケットを吸い込んで捨てるブラックホールが点々と動き回るように見えるはずです.

実際のインターネットで起こりうるか

単純なモデルではMRAI + BGP Withdrawn がパケットロスの原因になりそうですが,実際のインターネットで起こりうるかについて,いくつかの例で考えてみましょう.

未使用の/24 を一時的に広告し BGPlay などで観測すると良いかもしれません.なにが起こるか推測しやすくなります.

例1: DDoS 対策サービスを使っている

BGP でトラフィックを引き込むタイプのDDoS 対策サービスを使っている場合.

DDoS トラフィックを一旦べつのAS に引き込んでおいて,DDoS だけをフィルターしてオリジンAS に戻します.引き込む際にはBGP で細かい経路が広告されますが,IPv4 の慣例的では インターネットに伝搬する最小サイズ = /24 であるため,DDoS 被害ホストを含む/24 を広告することが多いです.

さて,DDoS 自体が止まって トラフィックを引き込んでいた/24 広告を止めるとどうなるか.

実線の矢印 → (青/赤) はNextHop = パケット転送の向きと思ってください.さらに「どの経路をもとにパケット転送するか」を矢印の色で表現します.矢印(青) は10.0.0.0/24 がBest Path であり,その方向にパケット転送することを示します.

仮にAS65000 にout-delay 的MRAI が設定されているとすると

  1. AS65001(DDoS 対策サービスプロバイダー) からBGP Withdrawn が伝搬し,AS65000 RIB から/24 が消える → NextHop が下向きに切り替わる (page 11)
  2. ループした状態で N 秒(= MRAI タイマー値) 維持 (page 12)

例2: 経路広告によるトラフィックエンジニアリング

2事業者からトランジットを買っているAS65004 が,トラフィックエンジニアリングのため細かい経路を片方に広告する場合.

重要経路を片寄せしたいとか,エンドユーザーの意向でAS65003 には広告したくない などの想定.仮にAS65001 にout-delay 的MRAI が設定されているとします.

ここから,/24 の経路広告が止まるとどうなるか.

  1. AS65004からBGP Withdrawn が伝搬し,AS65002 RIB から/24 が消える → が,NextHop は変化なし (page 22)
  2. さらにBGP Withdrawn が伝搬し,AS65003 のみNexthop が上向きに切り替わる (page 23)
  3. さらにBGP Withdrawn が伝搬し,AS65001 でNexthop が下向きに切り替わる (page 24)
  4. ループした状態で N 秒(= MRAI タイマー値) 維持 (page 25)

AS65002-AS65004 間リンクが切れた場合でも似たような動きになるはずです.

でも,BGP Withdrawn 自体レアなのでは?

はい.正常時あまりBGP Withdrawn を送ることがないかもしれません.日本で観測されるBGP メッセージのうち,Withdrawn は10% に満たない程度です.短期間で経路広告/停止しない場合はさほど気にする必要ないはずです.

いっぽう短期間に経路広告/停止する場合,

  • 絶妙な位置にMRAI ルーターがあると,最大数分間通信が止まる
  • 遠くにあるMRAI ルーターでも,その周辺の通信を止めている (遠いので影響が見えにくいだけ)

という可能性を念頭に置いておくと良いでしょう.

パケットロスを緩和するために

例1: BGP Withdrawn の影響をあらかじめキャンセルする

/24 からトラフィックを抜くのは厳しいので,基本的にはルーティングループを発生させずにBGP Withdrawn を伝搬させる戦略になります.

場合により有効な手段があります.たとえば例1: DDoS 対策サービスを使っている ケースでは

  1. トラフィックを引き込んでいる/24 より強い/24 をオリジンAS から広告する (page 15)
  2. DDoS 対策サービスを止める → 経路を上書きしているためループは発生しない (page 16)
  3. 上書きしていた/24 も止める → オリジンが同じためループは発生しない (page 17~18)

のように,BGP Withdrawn の悪影響をあらかじめキャンセルすることができます.ただし,DDoS 対策サービスが,トラフィックを引き込む/24 をあらかじめ弱めに広告できる ことが前提です.

例2: BGP Withdrawn の影響を局所化する

「このあたりでいつもループする」という事実を把握していない限り,多くの場合は対策が困難です.しかしながらBGP Withdrawn がルーティングループを生んでいると考えられるなら,それを局所化することは可能です.

Withdraw するべき経路のBGP Update を,あらかじめ日本国内 / アジア圏内 / 特定AS内に制限しておけばよい.たとえばオリジンAS が海外拠点を持たない場合は,DDoS 対策やトラフィックエンジニアリング向け経路は日本だけで十分です.

ただし,経路の伝搬範囲を制御するオプションを,トランジットISP が提供している ことが前提です.多くないと思いますが,そのようなISP は国内にもいます.今後増えていくことを期待しています.

まとめ

いくつかのMRAI 実装のうち,JUNOS のout-delay 的な実装とBGP Withdrawn の相性が悪く ルーティングループを生む場合があることを説明しました.経験的には,このようなループは30秒~数分間の通信断の原因になることがあります.

トランジットISP がMRAI を設定する際には 思わぬ悪影響がないかを検討してもらいたいですし,ユーザー側としては「MRAI ルーターがインターネット上にある」前提に立って,ルーティングループを検知し,キャンセルする策について検討しておくとよいでしょう.

*1:AS接続構成に依存するので常に3 倍とは限らないが,だいたい数倍遅い

MRAI の代表的なふるまい

BGP には古くからMRAI (Minimum Route Advertisement Interval) という機能がある.

イマイチ理解していなかったが,動作を確認しておく必要があり いくつかの実装を調査した.

ざっくり言うと

vSRX IOS-XRv Quagga
新しいprefix の追加時 MRAI 待つ MRAI 待つ(*2) MRAI 待たない
最後の経路削除時(*1) MRAI 待つ MRAI 待たない MRAI 待たない
経路切替時 Best Path になってからMRAI 待つ 前回のUpdate からMRAI 待つ MRAI 周期でのみUpdate
  • (*1) prefix ごとの,最後の経路(path)
  • (*2) 5% 程度,待たないpacket があったため要調査

このようにさまざまな実装がある.

以下目次:

MRAI (Minimum Route Advertisement Interval) とは

簡単にいえば「BGP Update Message の送る頻度を,prefix ごとに制限する」機能のこと.多くのルーターではBGP neighbor 単位で設定できる.

インターネットでは,各事業者のネットワーク拡張や障害などによるBGP 経路の変化が日常的に起こっているが,短時間に大量に変化したり,長時間経路がフラップし続けるとヤバい場合がある.たとえば,末端のちいさなルーターで経路計算が追いつかず,通信に影響が出たりする.

BGP は受け取った経路をもとに各ルーターがベストパスを計算し,ピアに伝達する (BGP Update を送る) プロトコルのため,ピアから大量のBGP Update を受け取った場合は力の限り計算し,力の限り 別ピアに送りつける.

MRAI はこの動作を緩和するしくみで,力の限り計算するが,結果を送りつけるときには 一定期間の間をあけて やさしく送ることができる.

多くの実装で「期間」を設定することができるが,「何と何のあいだの期間か」は実装による. だいたい共通するのは,

  • BGP neighbor 単位で設定できる
  • 単位は秒
  • ピア & prefix (BGP 経路の宛先情報) のペアごとにタイマーを持つ

おおまかには,MRAI は経路計算結果をバッファしておいて 一定時間待ってフラッシュするしくみと捉えておけばいいと思う.同じprefix について何度も経路計算したとしてもフラッシュ時には1 Update になるため,結果的にBGP Update の数を抑えることができる.

MRAI にはデメリットもある点に注意.バッファすることでBGP Convergence が遅くなる場合もあり,インターネットを少しずつ不安定にしてしまうかもしれない.これについてはまた別のエントリーで書く.

MRAI の標準化

この機能は10年以上も前から実装があるが,2008 年にInternet Draft が書かれている.最新のI-D もExpire していて 現在でも標準はない.各社独自実装している状況.

最新のI-D によると

The Minimum Route Advertisement Interval (MRAI) timer is specified in RFC4271 [BGP]. This timer acts to rate-limit updates, on a per- destination basis. [BGP] suggests values of 30s and 5s for this interval for eBGP and iBGP respectively. The MRAI must also be applied to withdrawals according to RFC4271, a change from the earlier RFC1771.

Withdrawn にも適用せよ,とのこと.

MRAI の動作

  • Juniper vSRX 15.1X49
  • Cisco IOS-XRv 5.2.2
  • quagga 0.99.23

のふるまいを調べてみた.各々設定は

Juniper vSRX
protocols bgp {
  group foo {
    out-delay 30;
  }
}
  • default: 0s
Cisco IOS-XRv
router bgp 65000
 neighbor 192.168.0.75
  advertisement-interval 30
  • default: 0s (iBGP), 30s (eBGP)
Quagga
router bgp 65000
 neighbor 192.168.0.75 advertisement-interval 30
  • default: 5s (iBGP), 30s (eBGP)

こんな感じ.

経路の追加 / 削除時

まず,ベストパス選択がない場合の動作.RIB にない経路を入れ,消す.

左から重複のない新たな経路20000 prefix をBGP Update として入れ,経路処理して右に送り出すまでの時間を見る.十分時間経過したあとにBGP Update (Withdraw) を入れつつ同様に観測する.

経路の追加: Juniper vSRX (MRAI なし)

横軸に時刻(s),BGP Update Message (パケット) を受信したタイミングを青レーンに描画,BGP Update Message (パケット) を送信したタイミングを黒レーンに描画した.

Best Path Selection がないとはいえ結構な遅延があるが,Update 受信 → 経路処理 → Update 送信 までInterval をおかず処理していることがわかる.

経路の追加: Juniper vSRX (MRAI: 30s)

グラフのスケールが違ってて申し訳ないが,経路処理の遅延に加えて30s のInterval をおいていることがわかる.

経路の削除: Juniper vSRX (MRAI なし)

追加と同様に,MRAI がなければInterval なし.

経路の削除: Juniper vSRX (MRAI: 30s)

追加と同様に,Withdraw 時にも30s のInterval が乗る.

経路の追加: Cisco IOS-XRv (MRAI なし)

次にIOS-XRv の結果.Interval は見られない.

Juniper xSRX に比べてBGP Update 処理遅延が小さいことも興味深い.
(同じような処理プロセスだが爆速なのか,並列化など手続き自体を工夫しているのか調べてない)

経路の追加: Cisco IOS-XRv (MRAI: 30s)

Interval を待たず 何か一部お漏らししているように見えるが,(青い四角の下の黒い線) たぶん30s 待ちたいんだろうなあと汲み取った.Interval なくUpdate を送ってしまっているのは,全体300 packet 中の20 packet.

経路の削除: Cisco IOS-XRv (MRAI なし)

Interval は見られない.

経路の削除: Cisco IOS-XRv (MRAI: 30s)

興味深いのは,Interval が見られない点.

送信したBGP Update (黒い線全体) は60 packet ほど.経路追加時には20 packet ほどInterval を待たないBGP Update があったが,必要になればあとで詳しく調べる予定.

経路の追加: Quagga (MRAI: 30s)

次にQuagga のふるまい.一見Interval をおいているように見えるが,よくみると4s に満たない程度なので,これはBGP Update 処理遅延と考えるのが妥当.

経路の削除: Quagga (MRAI: 30s)

Cisco IOS-XRv のようなふるまいで,Interval がない.

ここまでのまとめ: MRAI の動作 (経路の追加 / 削除時)

長くなったので一旦まとめ.

vSRX IOS-XRv Quagga
新しいprefix の追加時 MRAI 待つ MRAI 待つ(*2) MRAI 待たない
最後の経路削除時(*1) MRAI 待つ MRAI 待たない MRAI 待たない
  • (*1) prefix ごとの,最後の経路(path)
  • (*2) 5% 程度待たないpacket があったため要調査

MRAI の動作 (続き)

経路切替時

次にBest Path Selection が走る場合.

同じAS と2 ピア張り,連続的にBest Path Selection が実行されるよう強弱 *1 をつけて同じprefix のBGP Update を送る.

経路の切替: Juniper vSRX (MRAI: 30s)

下のグラフは,

  • 横軸: 時刻(s)
  • 左からBGP Update を受信した時刻 = 正の値をプロット
  • 右へBGP Update を送信した時刻 = 負の値をプロット
  • 線が長い = 強い経路
  • 線の色が同じ = 同じ経路 *2

のように描画したもの.

MRAI: 30s に対して10s ごとに強弱のBGP Update を入れた場合:

40s ごとに強弱のBGP Update を入れた場合:

Juniper vSRX は

  • 送信するべき経路がBest Path になってから,30s (MRAI) 経過して初めてBGP Update を送信
  • 送信するべき経路のBest Path が<30s の間に変わり続ける場合,BGP Update は送信されない

のようにふるまうことがわかる.

経路の切替: Cisco IOS-XRv (MRAI: 30s)

MRAI: 30s に対して10s ごとに強弱のBGP Update を入れた場合:


(60s すぎのBGP Update 受信(青) と送信(オレンジ) が重複している部分,送信のほうが若干早い)

40s ごとに強弱のBGP Update を入れた場合:

Cisco IOS-XRv は

  • 前回BGP Update を送信してから,30s (MRAI) を経過して初めてBGP Update を送信
  • 30s 待つ間にBest Path が変わっていれば30s ですぐ送信するし,変わっていなければ以降Best Path が変わるまで送信しない
    • 変わった時点ですぐ送信

のようにふるまうことがわかる.

経路の切替: Quagga (MRAI: 30s)

MRAI: 30s に対して10s ごとに強弱のBGP Update を入れた場合:

40s ごとに強弱のBGP Update を入れた場合:

Quagga は

  • 30s (MRAI) ごとのサイクルでのみBGP Update を送信する
  • 30s おきにBest Path が変わっていれば送信するし,変わってなければ送信しない

のようにふるまうことがわかる.

ここまでの3実装のふるまい(経路切替時)について,あるprefix に限定した動作であることに注意.prefix が異なれば送信タイミングは独立している.

2016/07/23 追記

コード読んだところQuagga はprefix ごとのタイマーを持たない.BGP neighbor 単位.prefix が違っても 一定のサイクルでまとめてUpdate 送信する.

まとめ

全体のまとめ.

vSRX IOS-XRv Quagga
新しいprefix の追加時 MRAI 待つ MRAI 待つ(*2) MRAI 待たない
最後の経路削除時(*1) MRAI 待つ MRAI 待たない MRAI 待たない
経路切替時 Best Path になってからMRAI 待つ 前回のUpdate からMRAI 待つ MRAI 周期でのみUpdate
  • (*1) prefix ごとの,最後の経路(path)
  • (*2) 5% 程度,待たないpacket があったため要調査

見てわかるように,実装によりふるまいがけっこう違う.

目的によってMRAI がハマるとき / ハマらないときがある気がするけど,「このベンダーのこのバージョンでは…」といった具体的な仕様はさておき,多種多様な実装があることを知っておけばひとまず十分.MRAI を検討するときに

  • 守りたいものはなにか
  • MRAI で自衛できるものか
  • 使っているルーターのMRAI はどのように動くか.守りたいものを守れるか
  • MRAI を入れる → Convergence Time が伸びて困ることはないか
  • 周辺のAS はどのようにふるまうか

などを考えればよいかな,と思う.あとデフォルトが0s でない装置もあるので知らず知らずのうちに使っているかもしれない.

*1:強弱はMED で制御した

*2:BGP Community でマークした

unicast route の変更により,flowspec validation は走るか

flowspec は forwarding に関するルールをルーター間で伝え合うプロトコルで,「他者から受け取ったルールを適用する前にvalidation しなさい」と仕様で決まっている.

では,validation するのはいつだろう?

  1. flowspec route を受け取ったとき
  2. flowspec route に関連するunicast route に変更があったとき

1 . は当然として,2 . のタイミングでvalidation が走るかどうかで振る舞いが大きく変わりそう.

RFC5575 によれば

A flow specification received from an external autonomous system will need to be validated against unicast routing before being accepted.

と書かれているものの,「いつvalidate するか」については細かく規定されていない.関連I-D にもない.

いつものように実装にまかされてるやつなので,Juniper vSRX, Cisco IOS-XRv で試してみた.

環境

testing network

eBGP ピアを2 ルーター間で張り,

  1. unicast route を1つずつ広告する
  2. destination prefix が同じflowspec route も1つずつ広告する
  3. ピアを張ったまま,1 . をwithdraw して2 . の経路をvalidate し直すかを見る

結果

Juniper vSRX, Cisco IOS-XRv とも unicast route の変更時にもvalidation が走る

妥当な動きというか,直観的でよかった.そうでなければ「validation fail するはずなのに動いてる」「ebgp ピアがflap したらflowspec 動作が変わる」「soft clear したら動作が変わる」「flowspec route を止めて出し直したら動作が変わる」みたいになる.

以下,参考まで.

設定

Juniper vSRX

routing-options {
    static {
        route 10.0.1.0/24 discard;  # deactivate したり activate したりする
    }
    autonomous-system 64600;
    flow {
        route flow1 {
            match destination 10.0.1.0/24;
            then discard;
        }
    }
}
protocols {
    bgp {
        group ebgp {
            family inet {
                unicast;
                flow;
            }
            export redistribute-static;
            peer-as 64601;
            neighbor 192.168.0.74;
        }
    }
}
policy-options {
    policy-statement redistribute-static {
        from protocol static;
        then accept;
    }
}

Cisco IOS-XRv

class-map type traffic match-all flow1
 match destination-address ipv4 10.0.2.0 255.255.255.0  !
 end-class-map
!
policy-map type pbr drop1
 class type traffic flow1
  drop
 !
 class type traffic class-default
 !
 end-policy-map
!
route-policy permit-all
  pass
end-policy
!
router static
 address-family ipv4 unicast
  10.0.2.0/24 Null0   ! no したり入れたりする
 !
!
router bgp 64601
 address-family ipv4 unicast
  redistribute static
 !
 address-family ipv4 flowspec
 !
 neighbor-group ebgp
  remote-as 64600
  address-family ipv4 unicast
   route-policy permit-all in
   route-policy permit-all out
   soft-reconfiguration inbound always
  !
  address-family ipv4 flowspec
   route-policy permit-all in
   route-policy permit-all out
   soft-reconfiguration inbound always
  !
 !
 neighbor 192.168.0.67
  use neighbor-group ebgp
 !
!
flowspec
 address-family ipv4
  service-policy type pbr drop1
 !
!
end

unicast route があるとき

koji@vsrx> show route table inetflow.0 detail

inetflow.0: 2 destinations, 2 routes (2 active, 0 holddown, 0 hidden)
...
10.0.2/24,*/term:2 (1 entry, 1 announced)
        *BGP    Preference: 170/-101
                Next hop type: Fictitious
                Address: 0x8f3df04
                Next-hop reference count: 2
                State: <Active Ext>
                Local AS: 64600 Peer AS: 64601
                Age: 10:38
                Task: BGP_64601.192.168.0.74+179
                Announcement bits (1): 0-Flow
                AS path: 64601 I
                Communities: traffic-rate:64601:0
                Accepted

                # Accept されている

                Validation state: Accept, Originator: 192.168.0.74
                Via: 10.0.2.0/24, Active
                Localpref: 100
                Router ID: 172.16.0.3
RP/0/0/CPU0:ios#sh bgp ipv4 flowspec Dest:10.0.1.0/24
Thu May 22 10:52:24.962 UTC
BGP routing table entry for Dest:10.0.1.0/24/40
Versions:
  Process           bRIB/RIB  SendTblVer
  Speaker                  6           6
Last Modified: May 22 10:46:16.486 for 00:06:08
Paths: (1 available, best #1)
  Not advertised to any peer
  Path #1: Received by speaker 0
  Not advertised to any peer
  64600, (received & used)
    0.0.0.0 from 192.168.0.67 (172.16.0.2)

      ! best になっている

      Origin IGP, localpref 100, valid, external, best, group-best
      Received Path ID 0, Local Path ID 1, version 6
      Extended community: FLOWSPEC Traffic-rate:0,0

unicast route がないとき

koji@vsrx> show route table inetflow.0 detail all
...
10.0.2/24,*/term:N/A (1 entry, 0 announced)
         BGP                 /-101
                Next hop type: Fictitious
                Address: 0x8f3df04
                Next-hop reference count: 2
                State: <Hidden Ext>
                Local AS: 64600 Peer AS: 64601
                Age: 17:41
                Task: BGP_64601.192.168.0.74+179
                AS path: 64601 I
                Communities: traffic-rate:64601:0

                # Reject されている

                Validation state: Reject, Originator: 192.168.0.74
                Via: 0.0.0.0/0, Active
                Localpref: 100
                Router ID: 172.16.0.3
RP/0/0/CPU0:ios#sh ip bgp ipv4 flowspec Dest:10.0.1.0/24
Thu May 22 10:58:36.526 UTC
BGP routing table entry for Dest:10.0.1.0/24/40
Versions:
  Process           bRIB/RIB  SendTblVer
  Speaker                  7           7
Last Modified: May 22 10:54:16.486 for 00:04:20
Paths: (1 available, no best path)
  Not advertised to any peer
  Path #1: Received by speaker 0
  Not advertised to any peer
  64600, (received & used)
    0.0.0.0 from 192.168.0.67 (172.16.0.2)

      ! invalid になっている

      Origin IGP, localpref 100, valid, external, invalid flowspec-path
      Received Path ID 0, Local Path ID 0, version 0
      Extended community: FLOWSPEC Traffic-rate:0,0

mention-bot を試した

Facebook が公開しているPull Request (PR) レビュー支援ツール
PR を作ったとき,最適なレビューアーを見つけてきてPR 上でメンションしてくれる.

github.com

試したけど,うまくいかなかった.

ざっくり言うと

セキュリティ上の懸念があって断念した.

  1. 「ボットアカウントはGitHub の2FA をオフにしなさい」と書いてある
    • GitHub API にはないが Web UI にはある機能を使っているから
    • 自動Web ログイン と 2FA の併用がむずかしい
  2. このボット機能のためだけに,1 の状態で動くエンドポイントを持ちたくない
    • PR 作成時にWebHook でトリガーしてもらうのにエンドポイント必要
    • 得られる価値に対してリスク高い

やりたかったこと

PR レビューがコスト高になることがある

Private Repo でGitHub フローを回しており,日常的にPR レビューしている. チーム内のさまざまな事情により,ふと気づくとレビューにすごい時間を使っていることがある.

個人がレビューを嫌って押し付けあうのは避けたいが,チーム全体として効率よくレビューしたい.

日本人だし「〇〇さんレビューお願いします」って言えない

〇〇さんにお願いするのが最適な場合でも「お願いします」ってなかなか言えない.日本人だからね… 急ぎの中ちょっと待っちゃったりして,状況をすこしずつ悪くしてしまう.

また,PR がぽんっと出た瞬間「あいつが見るだろう」みたいな考えがよぎる. たぶんみんな同じことやってて そもそもの着手が遅くなり,ちょっとずつ他メンバーのタスクをブロックしちゃう.

これやめたい.

試したこと

レビューアーをランダムに決めるやつ とかもあるが, やりたいことの半分しか実現できないので mention-bot に興味を持った.

ぱっと見「WebHook トリガーにしないといけないの面倒…」と思ったが, 「PR 作ったあと チャット経由で手動キックすれば動くだろう」と,Hubot スクリプトを書いてみた.

ところが…GitHub API トークンではなくID / パスワードでログインしないと動かないことがわかった.

mention-bot の動き

ここ に書いてあるとおりだが,

  1. WebHook でPR の情報を受け取る.またはPR の情報を渡して手動で呼ぶ
  2. PR 内で,どのファイル / 行にdiff があるか探す
  3. 削除/変更があった行について,git blame でmaster ブランチでの最終更新者を探す
    • いじる行の最終更新者がレビューアーに向く という前提
  4. PR 全体で3 の頻出ランキングを作る
  5. いじるファイル内の変更しない行についても 同様にgit blame ベースのmaster 最終更新者ランキングをつくる
    • いじる行でなくても,同じファイル内の最終更新者がレビューアーに向く という前提
  6. 5 を4 のケツにくっつける
  7. 6 からPR 作成者を除いたトップ N人について,指定のコールバックを呼ぶ

問題は,git blame に相当するGitHub API がないこと. Web UI にはあるのでスクレイピングして代用しているが,これが原因でボットアカウントを2FA にできない. 場合によってはAPI も使うため, さらにトークンが必要.

あきらめた

とても便利そうなんだけれど,セキュリティ的にイマイチ という判断をした.
今回Private Repo なのでやめたが,GitHub Enterprise で社内に閉じてるとか, Public Repo であれば敷居が下がったかもしれない.

git blame をローカルでやればいいだけかもしれない.

Public Repo なら認証不要*1 なので,

var mentionBot = require('./run-mention-bot.js');
var GitHubApi = require('github');

var github = new GitHubApi({ version: '3.0.0' });

var config = {
  maxReviewers: 5,
  findPotentialReviewers: true,
  fileBlacklist: [],
  userBlacklist: [],
  requiredOrgs: []
}

mentionBot
  .guessOwnersForPullRequest(
    'https://github.com/fbsamples/bot-testing',
    98,
    'mkonicek',
    'master',
    false,
    null,
    config,
    github
  )
  .then(function(users) {
    console.log(users);
  })
  .catch(function(err) {
    console.error(err);
  });

こういう感じのが動く.

特定Organization 内からのみレビューアー候補を選びたいときは,API トークンが必要なことがある.

それでもちょっと試したいひと向け

Private Repo でちょっとだけ試す方法.
ES6 のtranspile 環境をつくるのが面倒なので,mention-bot レポジトリ内にスクリプトを置く作戦.

git clone https://github.com/facebook/mention-bot.git
cd mention-bot
npm install
npm install github --save

Web UI 認証をやめて,認証済みCookie をつかう.

# githubAuthCookies.js を書き換え

module.exports = '_octo=GHx.x.xxxxxxxxxx.xxxxxxxxxx; logged_in=yes; dotcom_user=codeout; _gat=1; _gh_sess=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; _ga=GAx.x.xxxxxxxxxx.xxxxxxxxxx; tz=Asia%2FTokyo; user_session=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

私はChrome から抜いた.

Cookie

最後にスクリプト本体.

# mention.js

var mentionBot = require('./run-mention-bot.js');
var GitHubApi = require('github');

var github = new GitHubApi({ version: '3.0.0' });
// Organization 内からのみレビューアー候補を選びたいときのみ必要
github.authenticate({
  type: 'oauth',
  token: '...token...'
});

var config = {
  maxReviewers: 5,
  findPotentialReviewers: true,
  fileBlacklist: [],
  userBlacklist: [],
  requiredOrgs: []  // レビューアー候補がprivate メンバーだと動かない
}

mentionBot
  .guessOwnersForPullRequest(
    'https://github.com/user/private_repo',
    1,          // PR Number
    'codeout',  // PR 作成者
    'master',   // blame するブランチ
    false,  // Private Repo でも false, null でOK
    null,   // Organization 縛りにするときは true, 'organization'
    config,
    github
  )
  .then(function(users) {
    console.log(users);
  })
  .catch(function(err) {
    console.error(err);
  });

実行結果

mention-bot $ time node mention.js
[ 'codeout', 'foo', 'bar', 'baz' ]
node mention.js  0.89s user 0.23s system 11% cpu 9.385 total

Organization 縛りをつけるとAPI コールが増えて遅い.

mention-bot $ time node mention.js
[ 'codeout', 'foo', 'bar' ]
node mention.js  1.01s user 0.25s system 6% cpu 18.059 total

*1:PR 上でメンションしない場合