BatfishでNW障害時の影響分析を行う②

# 1. はじめに
前回の記事では、Configファイルをインプットに、ネットワークConfig解析ツール「Batfish」で正常時の確認を行いました。
[BatfishでNW障害時の影響分析を行う①](https://qiita.com/kitara/items/3969a9c7223ec2e91d29)

本記事では筐体障害、リンク障害時の確認と、正常時・障害時を比較した影響分析を行ってみたいと思います。

# 2. 筐体障害時の確認

### 2-1. 筐体障害発生
※前回の記事の「3. Batfishのセットアップ」まで終わっている前提とします。
正常時のスナップショット`test_snapshot`をフォークし、`iosvl2-0`筐体障害発生時のスナップショット`fail_access`を作成します。引数`deactivate_nodes`には障害機器のホスト名`iosvl2-0`を指定します。

```python:snippet1
FAIL_ACCESS_SNAPSHOT_NAME = "fail_access"
bf.fork_snapshot(BASE_SNAPSHOT_NAME,
                 FAIL_ACCESS_SNAPSHOT_NAME,
                 deactivate_nodes=["iosvl2-0"],
                 overwrite=True)
```

### 2-2. 通信経路確認
`bidirectionalTraceroute()`を使用し、`10.1.1.100`から`10.4.1.100`宛ての疎通性および通信経路を確認します。
正常時の確認と異なるのは、`answer()`の引数としてフォークしたスナップショットを明示指定している点です。

```python:snippet2
result_bdtr_dev_fail = bf.q.bidirectionalTraceroute(
    startLocation='@enter(csr1000v-0[GigabitEthernet2])',
    headers=HeaderConstraints(dstIps='10.4.1.100', srcIps='10.1.1.100')).answer(
    snapshot=FAIL_ACCESS_SNAPSHOT_NAME).frame()
result_bdtr_dev_fail.Forward_Traces[0][0]
```

`iosvl2-0`障害時は、`iosvl2-1`経由に切り替わるのが想定結果です。ただし今回は、`iosvl2-1`でACL許可設定漏れがあり、`10.1.1.100`から`10.4.1.100`宛ての通信が拒否される状態になっています。
行きの実行結果では想定通り`DENIED_IN`となり、`iosvl2-1`Vlan100に適用されているin方向ACL`acl_test`で拒否されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/4e647eaa-558d-9b72-8b78-189ee03a77f4.png)

戻りの実行結果も確認してみます。
```python:snippet3
result_bdtr_dev_fail.Reverse_Traces[0][0]
```
以下の通りエラーとなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/b519892d-100a-4070-8870-7abf7ff91ccd.png)

代わりに、`traceroute()`で`10.4.1.100`から`10.1.1.100`宛ての疎通性および通信経路を確認してみます。

```python:snippet4
result_bdtr_dev_fail2 = bf.q.traceroute(
    startLocation='@enter(iosvl2-3[GigabitEthernet0/2])',
    headers=HeaderConstraints(dstIps='10.1.1.100', srcIps='10.4.1.100')).answer(
    snapshot=FAIL_ACCESS_SNAPSHOT_NAME).frame()
result_bdtr_dev_fail2.Traces[0][0]
```
`traceroute()`は片方向の通信がNGでも結果を返してくれるので、想定通りの通信経路が表示されました。3.で`iosvl2-0`の代わりに`iosvl2-1`を経由していることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/5f8b2237-becc-1e84-5fb1-f153c344d6a9.png)


# 3. リンク障害時の確認
### 3-1. 単一リンク障害発生
正常時のスナップショット`test_snapshot`をフォークし、`iosvl2-0`の`GigabitEthernet0/2`リンク発生時のスナップショット`fail_link`を作成します。
筐体障害時の引数は`deactivate_nodes`でしたが、リンク障害は`deactivate_interfaces`を使います。

```python:snippet5
FAIL_LINK_SNAPSHOT_NAME = "fail_link"
bf.fork_snapshot(BASE_SNAPSHOT_NAME,
                 FAIL_LINK_SNAPSHOT_NAME,
                 deactivate_interfaces=[Interface(hostname="iosvl2-0", interface="GigabitEthernet0/2")],
                 overwrite=True)
```

### 3-2. 単一リンク障害時の通信経路確認
スナップショット名以外は筐体障害時と同じです。
```python:snippet6
result_bdtr_link_fail = bf.q.bidirectionalTraceroute(
    startLocation='@enter(csr1000v-0[GigabitEthernet2])',
    headers=HeaderConstraints(dstIps='10.4.1.100', srcIps='10.1.1.100')).answer(
    snapshot=FAIL_LINK_SNAPSHOT_NAME).frame()
result_bdtr_link_fail.Forward_Traces[0][0]
```
LAN側の単一リンク障害だけではVRRP切り替わりは発生しない設計のため、正常時と同様の結果となりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/d8837726-c438-a30c-c1f3-914452a249e4.png)

### 3-3. 多重リンク障害発生
正常時のスナップショット`test_snapshot`をフォークし、今度は`iosvl2-0`の`GigabitEthernet0/2, 1/0, 1/1`の多重リンク発生時のスナップショット`fail_multiple_link`を作成します。
```python:snippet7
FAIL_MULTIPLE_LINK_SNAPSHOT_NAME = "fail_multiple_link"
bf.fork_snapshot(BASE_SNAPSHOT_NAME,
                 FAIL_MULTIPLE_LINK_SNAPSHOT_NAME,
                 deactivate_interfaces=[Interface(hostname="iosvl2-0", interface="GigabitEthernet0/2"),
                                        Interface(hostname="iosvl2-0", interface="GigabitEthernet1/0"),
                                        Interface(hostname="iosvl2-0", interface="GigabitEthernet1/1")],
                 overwrite=True)
```

### 3-4. 多重リンク障害時の通信経路確認
こちらもスナップショット名以外は筐体障害時と同じです。
```python:snippet8
result_bdtr_mlink_fail = bf.q.bidirectionalTraceroute(
    startLocation='@enter(csr1000v-0[GigabitEthernet2])',
    headers=HeaderConstraints(dstIps='10.4.1.100', srcIps='10.1.1.100')).answer(
    snapshot=FAIL_MULTIPLE_LINK_SNAPSHOT_NAME).frame()
result_bdtr_mlink_fail.Forward_Traces[0][0]
```
LAN側IFがすべて塞がれてVRRP切り替わりが発生し、想定通り筐体障害時と同様の結果になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/e12ef07e-8eb8-48ee-667c-af4e7a8e8eee.png)


# 4. 正常時・障害時を比較した影響分析
最後に、正常時と筐体障害時を比較し、前者で許可されるが、後者ではドロップされる通信を出力したいと思います。
Questionは`differentialReachability()`を使います。[(参考) 通信経路確認で用いるQuestion](https://qiita.com/kitara/items/3969a9c7223ec2e91d29#%E5%8F%82%E8%80%83-%E9%80%9A%E4%BF%A1%E7%B5%8C%E8%B7%AF%E7%A2%BA%E8%AA%8D%E3%81%A7%E7%94%A8%E3%81%84%E3%82%8Bquestion)
結果を見やすくするため、引数`pathConstraints`で開始ノード、`headers`で対象通信、`maxTraces`でTrace出力結果数を絞っています。引数を指定しない場合、各機器から障害機器宛ての通信を含むすべての差分が出力されます。

```python:snippet9
result_diff_reachability = bf.q.differentialReachability(
    pathConstraints=PathConstraints(startLocation = f'@enter({"csr1000v-0"}[{"GigabitEthernet1"}])'),
    headers=HeaderConstraints(dstIps='10.4.1.100', srcIps='10.1.1.100'),
    maxTraces=1).answer(
    snapshot=FAIL_ACCESS_SNAPSHOT_NAME,
    reference_snapshot=BASE_SNAPSHOT_NAME)
result_diff_reachability.frame()
```

結果は以下の通りです。`Reference_Traces`(正常時)では`DELIVERED_TO_SUBNET`、`Snapshot_Traces`(筐体障害時)では`DENIED`として差分出力されていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/dbf62f03-3d34-45f8-b6cf-54150af65611.png)

送信元と宛先を逆にした場合、正常時・障害時ともに通信可能なため差分出力されませんでした。

```python:snippet10
    headers=HeaderConstraints(dstIps='10.1.1.100', srcIps='10.4.1.100'),
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/ea58c11c-fe52-f5df-3eb0-b34f0daf9347.png)


# 5. 最後に
今回は小規模なネットワークで、1パターンの通信経路のみで影響分析を実施しました。
一方で大規模ネットワークに対し、あらかじめ定義した筐体・リンク障害パターンで複数の通信経路確認も当然可能です。
Batfishのツールとしての性質上、複雑な設計、特殊な設定ではうまく解析できないケースもあるかもしれませんが、まずは「問題が見つかったらラッキー」くらいの感覚で使ってみるのも良いかも知れません。