Batfish を使ってネットワーク構成を可視化してみよう・改

## はじめに

[Batfish を使ってネットワーク構成を可視化してみよう \(1\)](https://qiita.com/corestate55/items/8a39af553785fd77c20a)～(4) では  [Netoviz](https://github.com/corestate55/netoviz) の紹介がてら、Batfish で抽出した L3 (BGP/OSPF/L3) トポロジの可視化を試してみました。これに対していくつか機能追加をしたのでその紹介をします。

## 追加機能サマリ

その1 : OSPF 応用 (複数 OSPF Area 構成) 対応

* [最初に挙げた記事](https://qiita.com/corestate55/items/8a39af553785fd77c20a) の中では  [pybatfish の example config](https://github.com/batfish/pybatfish/tree/master/jupyter_notebooks/networks/example) を使用していました。このネットワークではひとつの AS にひとつの OSPF Area (Area 0) だけがあるという構成になっており、複数 OSPF エリアがある状況を対象に含めていませんでした。そのため OSPF Area のレイヤはあるけど、ほかのレイヤとの依存関係定義のためだけに使われていて、トポロジとしてはあまり意味がありませんでした。
* 今回、複数の OSPF エリアがある状況を想定したターゲットネットワークを設定することで、OSPF エリアのトポロジについても取り扱えるようにしています。

その2 : BGP 応用 (Route Reflector, Confederation) 対応

* 同様に、当初ターゲットのネットワークはごく基本的な eBGP/iBGP peer だけがあり、大きな環境でよく使用されているであろう BGP confederation や route reflector (RR) は含まれていませんでした。今回はそれらの機能を含めた構成を対象とし、batfish の対応状況および可能な範囲での構成可視化を検討しました。
* まあ結論から行くと confederation については batfish は対応していない・RR については iBGP として参照される RR client が iBGP config に基づいて把握される、というところなのであまりたいそうな話ではないですが。

その3 : BGP AS 外のルータ情報の補足

* 通信事業者としては、自分の AS 内 (自分の管理範囲) の外にある bgp peer のパラメータ (bgp peer を張るためのパラメータ) はわかっても peer router のコンフィグまでは入手できません。対向 AS から先の範囲はブラックボックスになりますが、運用上は対向ルータまでが図に含まれることは珍しくないはずです。自 AS 内のルータのうち、eBGP で外部と接続する部分のパラメータを基に、対向側の BGP peer パラメータを描きだし、BGP レイヤで eBGP peer router の情報を埋められるようにしてあります。

## 分析対象のネットワーク

構成図 はこんな感じになります。

今回ターゲットにするネットワークは [GNS3](https://www.gns3.com/) を使って動作確認をしました。ネットワーク全体の動作確認のために外部 AS にあたる部分も作っていますが (sample1a)、Batfish の機能確認の際にはそれらを省いています (sample1b)。設定ファイルは github にあります。

* [sample1a](https://github.com/corestate55/batfish-test-topology/tree/4be400bc0d500f48314c8e90ed462e0481b31480/l3/sample1a/configs)
  * ルーティング設定の動作確認用 (外部 AS のコンフィグ入り)
* [sample1b](https://github.com/corestate55/batfish-test-topology/tree/4be400bc0d500f48314c8e90ed462e0481b31480/l3/sample1b/configs)
  * 実際に batfish で分析するデータ (外部 AS のコンフィグ抜き)

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/a94c6ebf-a504-bf93-87e6-c911e9addcbe.png)


アドレスや AS 番号は適当に振っていますが、public/private ASN まとめて連番にしないほうがわかりやすかったですね……。

## その1 : OSPF 応用 (複数 OSPF Area 構成) 対応

OSPF 応用として OSPF エリア間の接続 (トポロジ) を作ってみました。…といっても、そもそも OSPF エリアだけを抜き出して図を作ったことがないんですよね。なのでまず、エリア間トポロジというので何がどう表現できればよいのかというところから考えます。先に OSPF の基本をおさらい。

* Area 0 がバックボーンエリアになる
* 標準エリア (バックボーンエリア以外の通常のエリア) は必ずバックボーンエリアに接続して経路を交換する : 非バックボーンエリア間で直接経路交換しない
  * OSPFエリアは 2 階層になる。
  * 注意: いまのところ virtual-link は考慮していません
* エリアはルータのインタフェース単位で設定できる : ひとつのルータが複数のエリアにまたがる → エリア境界ルータ (ABR: Area Border Router)

シンプルな下図のような接続でエリア間接続を考えてみましょう。エリア間をつないでいるのは ABR です。この状況では ABR をリンクに、リンク端点 (termination point) を ABR の各エリア接点インタフェースとするのがよさそうに見えます。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/315d7f59-afaf-4f68-5ca1-27094674d98b.png)

ただ、問題になるのは ABR が複数のエリアを接続できるという点です。今回対象としている構成のように1つの ABR が3つのエリアをつなぐケースを考えてみましょう。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/f1e0f44e-3b28-4496-a063-dfeee2084c02.png)

エリア 0 / 10 / 100 をひとつの ABR がつないでいます。最初のケースに倣って ABR をリンクとしてとらえてみると真ん中のようになりますが、こうなると Area0 側は複数のリンクを受け入れる状態になります。これは point-to-multipoint のリンクとしてとらえられます。  [RFC 8345 Sec.4.4.5](https://tools.ietf.org/html/rfc8345#section-4.4.5) には multipoint link についての記述があります。


> 4.4.5.  Cardinality and Directionality of Links
> The topology data model includes links that are point-to-point and unidirectional. It does not directly support multipoint and bidirectional links. Although this may appear as a limitation, the decision to do so keeps the data model simple and generic, and it allows it to be very easily subjected to applications that make use of graph algorithms. Bidirectional connections can be represented through pairs of unidirectional links. Multipoint networks can be represented through pseudonodes (similar to IS-IS, for example). By introducing hierarchies of nodes with nodes at one level mapping onto a set of other nodes at another level and by introducing new links for nodes at that level, topologies with connections representing non-point-to-point communication patterns can be represented.

[DeepL 翻訳してみる](https://www.deepl.com/home)とこうなります (強調はこっちで入れたもの)
> トポロジーデータモデルには、ポイントツーポイントおよび一方向性のリンクが含まれます。多点リンクと双方向リンクは直接サポートしていません。これは制限のように見えるかもしれませんが、そうすることでデータモデルはシンプルで汎用的なものになり、グラフアルゴリズムを利用するアプリケーションに非常に簡単に適用できるようになります。双方向接続は、単方向リンクのペアを通して表現することができます。 **多点ネットワークは、（例えばIS-ISに類似した）擬似ノードを介して表現することができる。** あるレベルのノードが別のレベルの他のノードの集合にマッピングされたノードの階層を導入し、そのレベルのノードに新しいリンクを導入することで、非点間通信パターンを表す接続を持つトポロジを表現することができる。

ちょっと話がそれますが…ここで IS-IS の話が出てきます。OSPF と同じく IGP として使われるプロトコルなので探すとこういう資料がありました。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/0e10fd2d-a9a4-6a88-885f-1367320be4b2.png)
[IS\-ISはじめの一歩 0から1へ :: janog44](https://www.janog.gr.jp/meeting/janog44/program/isis)


IS-IS ちゃんと理解しないまま書いちゃいますが、少なくとも multipoint 接続をどう考えるかにあたって "pseudo-node" がどんなものを指すのかはこの図で分かるかと。こうした接続は L2 segment としていつも登場するし、これまでの L2 segment (switch/hub) をどうモデル化するかというところでやってきている話なので復習ではあるのですが。

話を戻しましょう。OSPF エリア間接続として、複数のエリアをつなぐ ABR をどうモデル化するか。まあもう上の図に描いてしまってますが、図右のように、ABR を複数エリアをつなぐハブのように扱うことにしました。なんか一回りして最初(図左)のものと同等ですね。なので term-point のうち ABR 側のものはそのまま ABR のインタフェース名とします。エリア側の term-point ですが明示的にこれだと決めるのは難しいので便宜上の term-point を設定するだけにしています。[^link]

[^link]: 特に今回 area0 なんかは core01-02 間のリンクにしかないわけで、エリアをひとつのノードとして、エリアの ABR port を ABR ノードにもっていってしまうと、エリアノードには何もないんですよね。Term-point や support (レイヤ間の関係性) まで含めたモデルについて、今作ったモデルが本当に良いかどうかはまだ検討の余地があると思います。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/8871f212-4fe6-ea4e-1a67-657210a550e8.png)

## その2 : BGP 応用 (Route Reflector, Confederation) 対応

今回 "BGP 応用" として見ているのは次のふたつです。

* BGP Confederation
  * AS65530 の中を AS65531/AS65532 のふたつの Sub AS に分割しています。
* Route Reflector
  * AS65531 にあるルータは iBGP でフルメッシュの peer を張る必要がありますが、数が増えて煩雑になるので、border01-core01-core02 の三角形のところのみ通常の iBGP フルメッシュとし、border11/12 は core01/02 に RR Client としてぶら下がる 2 階層の BGP peer 構造とします。

それぞれが Batfish でどう扱われるのかを見てみます。今回 [BGP の情報を取り出すのに使っている](https://github.com/corestate55/netomox-examples/blob/develop/model_defs/bf_l3trial/exec_l3queries.py)のは  [bgpPeerConfiguration](https://pybatfish.readthedocs.io/en/latest/notebooks/configProperties.html#BGP-Peer-Configuration),  [bgpProcessConfiguration](https://pybatfish.readthedocs.io/en/latest/notebooks/configProperties.html#BGP-Process-Configuration),  [bgpEdges](https://pybatfish.readthedocs.io/en/latest/notebooks/routingProtocols.html#BGP-Edges) あとルータの経路情報として  [routes](https://pybatfish.readthedocs.io/en/latest/notebooks/routingTables.html#Routes) になります。

### Confederation

まず confederation については、基本的に Sub AS Number のほうしか扱われません。たとえば下記は  [core01 の BGP config](https://github.com/corestate55/batfish-test-topology/blob/4be400bc0d500f48314c8e90ed462e0481b31480/l3/sample1b/configs/core01_i2_startup-config.cfg) です。ここでは `router` セクションで Sub ASN を, `bgp confederation identifier` で Global ASN を指定しています。こうした config に対して各 batfish query から取得されるデータに含まれる AS 番号 (`Local_AS`, `Remote_AS` など) は `router bgp` で指定した番号 (Sub ASN) になります。`bgpProcessConfiguration` の結果には `Confederation_ID`, `Confederation_Members` という列がありますが、いまのところなにも値が入りませんでいた。

```

# core01
router bgp 65531
 bgp router-id 10.0.0.2
 bgp cluster-id 10.0.0.2
 bgp log-neighbor-changes
 bgp confederation identifier 65530
...
!
```

結果として、BGP AS トポロジについては AS65530 がなく、AS65530 内の Sub AS (65531, 65532) が Global AS であるかのようなトポロジになっています (下図)。実体としては AS65530 { AS65531, AS65532 } という包含関係があるので、Global ASN 間のつながりとその内部 (Sub ASN - Global ASN) みたいなかたちで 2 つのレイヤに分けて……なども考えられると思っていたのですが、そうした構造をとるためのデータが取得できないので、やるなら別途 (static に) そうした構成情報を定義する必要がありますね。

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/6bc419cf-ba59-ac4a-53e4-ff92ea1e3f79.png)

### Route Reflector

Route Reflector (RR) については対応されています。`bgpProcessConfiguration` の結果には `Route_Reflector` の列があり、RR (Server) になっているデバイスについてはここが `True` になります。また、`bgpPeerConfiguration` の結果には `Route_Reflector_Client`, `Cluster_ID` の列があります。`Route_Reflector_Client` 列は、RR client デバイスに対して `True` になり、`Cluster_ID` 列に RR Cluster ID が入ってきます。RR Server/Client の peer については通常の iBGP peer と同等の扱いのようです (`bgpEdges`, 下図)。

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/3ee984ec-8412-8be1-d9c4-a94cb3cfa2eb.png)

RR cluster をひとつのグループとして BGP process とその接続 (peering) 関係を分けるというのもできそうではありますが、今のところそこまで実装していません (必要以上にややこしくなりそうだったので)。今回、RR 構成は割とシンプルなので、redundant RR とかもうちょっと複雑な構成とかで試してみたほうが良い気はします……が、BGP 的な知識のほうの壁もありまだそこまではやれていません。

## その3 : BGP AS 外のルータ情報の補足

今回この機能については [Biglobe の滝口さん](https://speakerdeck.com/ttkgh) からコントリビュートを受けています。 [ネットワークプログラマビリティ勉強会](https://network-programmability.connpass.com/) つながりですね (多謝!!)。ちなみにこの機能を入れることで [前の記事](https://qiita.com/corestate55/items/10673ef74c33a24a0389) で主に取り上げていた  [pybatfish example](https://github.com/batfish/pybatfish/tree/master/jupyter_notebooks/networks/example) のコンフィグファイルにも eBGP の設定があることに気づきました。AS1 から AS4 への peer があるけど AS4 のコンフィグは入っていないのでこの機能を入れる前は可視化できていなかった。

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/aaefa749-8e58-5ced-8aa9-05d94123f4d8.png)

では何をしているのかをもうちょっと具体的に見てみます。まず、[前の記事](https://qiita.com/corestate55/items/10673ef74c33a24a0389)で書いた通り、コンフィグをいったん batfish に食わせて、必要なクエリをいくつか実行 → 結果を CSV に保存 → トポロジデータへ加工、という処理を行っています。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/f5348536-e255-e0fe-434d-dd9555fe512f.png)

はじめに、batfish にたいして複数のクエリを投げて各種情報を CSV に保存します  ([exec\_l3queries\.py](https://github.com/corestate55/netomox-examples/blob/develop/model_defs/bf_l3trial/exec_l3queries.py))。出力された CSV ファイル対して [ebgp\_peer\_data\.py](https://github.com/corestate55/netomox-examples/blob/develop/model_defs/bf_l3trial/ebgp_peer_data.py) を実行し、bgp peer の情報がコンフィグから取得できていないもデータについて、対になる bgp peer 情報を生成して追記しています。

あとはそれらの CSV のデータをつなぎ合わせてトポロジデータを作っていくのですが、bgp process (neighbor) とそれが peer をはる IP アドレス に注意が必要です。BGP process 間の関係性なので peer IP アドレスを termination point としてリンクを張るのが自然だろうというってとこはよいのですが、特に iBGP では ひとつの IP アドレスに対して複数の peer 設定が出てくるんですよね。ここで 1:N の関係がある。なので同じ IP アドレスに対して複数の peer がある場合には `:N` をつけて名前が重複しないようにしてあります。[^BGPP2MP]

[^BGPP2MP]: ひとつの term-point に対して複数のリンクが設定されても (BGP process にあるひとつの loopback interface に複数の peer link がつながっても) よいのでは、という話については、「その2: OSPF エリア間接続」のところで出た通り。P2MP なものについては pseudo node を置くというのを iBGP peer (BGP process topology) についてやると必要以上に複雑になりそうなんですよね。Loopback interface の uniqueness みたいなものの表現はいったんあきらめて、bgp proc topology として直観的に考えるであろう図として構成しています。

![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/1521ed91-0eb3-7ca8-fef6-7723739bdfc2.png)

この処理に対象として eBGP peer も含まれていて、eBGP についても 1IP 複数 peer になっていても大丈夫なはず…… 用意した sample1 のネットワークではそういう部分がないので明に表れていないですが。ロジックは全体的に整理して [layer\_bgp\_proc\.rb](https://github.com/corestate55/netomox-examples/blob/develop/model_defs/bf_l3trial/layer_bgp_proc.rb) のあたりです。

```ruby

def make_proc_node_tps(rec)
  tps = rec.ips_facing_neighbors # returns Array of BGPProcEdge
  tp_name_counter = TPNameCounter.new(tps)
  tps.map do |tp|
    tp_name = tp_name_counter.tp_name(tp)
    make_proc_node_tp(rec, tp, tp_name)
  end
end
```

## 暗黙の仮定の明文化

実際の構成情報を基に、システム構成をモデル化していく・システム構成のデータを起こしていく際、どうしても仮定・制約する条件が出てきます。そして対象とするネットワークの構成や機能を変えようとしたりするとそういう仮定が表に出てきて構成情報のモデル化が引っかかってしまいます。今回だと例えば下記のようなところ :

* プロトコルの基本仕様: OSPFエリアの階層構造など、仕様上どういう作りになるのかが規定されているもの
  * 今回だと ospf virtual-link を使うことは想定していません
* BGP peer, OSPF neighbor が multi-access な状態で構成されている想定を置いていない
  * peer/neighbor の[隣接関係がメッシュになるような状態](https://qiita.com/corestate55/items/8fa006d1e30f49da36f6#l3-topology)がちゃんとフォローできないと思われる。
* デバイスとしては可能だけど設計上制限しているもの
  * 1-Router : 1-OSPF process など、デバイス上で動かすプロセスの種類や数、対応関係
  * VRF など仮想インスタンスを使うことを想定していない
  * L3 P2P 接続でやる構成……BGP/OSPF process が multipoint で neighbor を持つことを想定していない
* インタフェース名のマッチングや種別の判定: 機器依存するインタフェース名ルールに依存

機能上可能なものすべてに対してとか、ましてやベンダ製品依存になるところまで合わせて汎用化していくのは至難の業です。そこまでやらないといけない必要性もないので、ある程度自分たちが使う範囲のもの・それに合った見方に基づいて構成情報を起こしていく (そのためのロジックを実装する) ことになります。その中で、どういう制限や仮定についてどういうモデル化・実装をしたのかを残しておくと、そこから外れたものがあった場合の対応がやりやすくなるでしょう。こうした点をあまり意識しないでいると「普通はこうするよね」と思って作ったロジックに対して、その普通だと思っていた条件が外れた時にどこを変えていかなければいけないのかを洗い出すのが大変です。やってみると、インタフェース名からポート種別を判断して……みたいなベンダ依存のことを暗黙的にやっているのに気づきます。こうしたところは注意しながら進めたほうが良いです。

## おわりに

外部 AS の peer 情報の補足についてフィードバックをもらったので、あわせて BGP 応用 (confederation, route-reflector) に対する Batfish の対応状況の調査、OSPF エリア間トポロジの作成などを追加してみました。個人的に一番引っかかったのは OSPF Area 間トポロジというをどう考えるかというところですね……。Area は 2 階層になるというのが最初からあるので、これまであえてちゃんと図に起こすというのを考えたことがなかった。"Area の termination-point" ってなんだろう? とか考えるとどうしたものやらという感じになります。

途中でも書いていますが、BGP/OSPF をを触ること自体久しぶりなので、そういうのを使うネットワークって通常はこうするよね、みたいなところがどうしても弱いし BGP や OSPF 自体ももうだいぶ忘れている……。そもそもの NW の設定がなんか変じゃないかとか、モデル化にあたってもっとこうすべき、みたいなことで気付いたことがあれば教えてください。