【Batfish】ネットワーク機器の検証がしたい

### はじめに

<バージョン>
Cisco IOS: 12.4(15)T1

　Batfishとは、実機に接続せずにネットワーク機器の検証を行えるツールです。
今回は、Ciscoルータの設定を読み込ませて、access-listの設定を確認してみます。

```text:Router.log
Router#show running-config 
～省略～
interface FastEthernet0/0
 ip address 192.168.0.1 255.255.255.0
 duplex auto
 speed auto
!
interface FastEthernet0/1
 ip address 192.168.1.1 255.255.255.0
 ip access-group 100 out
 duplex auto
 speed auto
!
interface FastEthernet1/0
 ip address 192.168.2.1 255.255.255.0
 ip access-group 110 out
 duplex auto
 speed auto
!
interface FastEthernet1/1
 no ip address
 duplex auto
 speed auto
 shutdown
!
interface Vlan1
 no ip address
 shutdown
!
～省略～
!
access-list 100 permit tcp 192.168.0.0 0.0.0.255 192.168.1.0 0.0.0.255 eq 22
access-list 100 deny tcp 192.168.0.0 0.0.0.255 192.168.1.0 0.0.0.255 eq www
access-list 100 permit tcp 192.168.0.0 0.0.0.255 192.168.1.0 0.0.0.255 eq 443
access-list 110 deny tcp 192.168.0.0 0.0.0.255 192.168.2.0 0.0.0.255 eq www
access-list 110 permit tcp 192.168.0.0 0.0.0.255 192.168.2.0 0.0.0.255 eq 443
access-list 120 deny ip any any
access-list 130 permit ip any any
!
～省略～
end
```

### 1. 事前準備

　まずはdockerコンテナ内にButfishを構築します。Docker イメージは[batfish/allinone](https://hub.docker.com/r/batfish/allinone)と[batfish/batfish](https://hub.docker.com/r/batfish/batfish/)の
2つがありますが、今回はJupyter Notebookで動作確認を行いたいので``allinone``の方を選択しました。

### 2. ディレクトリ構成

　Jupyter Notebook内のディレクトリ構成は以下のようにしました。一番上の階層が、デフォルトで
``/notebooks``になっており、``/notebooks/networks``配下にサンプル用のconfigが格納されています。
今回は、自分で用意したconfigを入れたいので、``/notebooks/my_network``を作成してCisco機器の
Config（Router.log）を格納しました。

```text
/notebooks/
　├ networks/    # デフォルトで存在
　└ my_network/  # 以下、新規追加
　 　└ configs/
　 　　　└ Router.log
```

### 3. 動作確認

#### 3-1. 初期設定

　初期設定の内容は、[Batfish documentation - Interacting with the Batfish service](https://pybatfish.readthedocs.io/en/latest/notebooks/interacting.html#)を参考にしました。
手順の通り、Jupyter Notebookに入力すればよいですが、``SNAPSHOT_PATH``はConfigを入れたpathを指定しましょう。

```text
import pandas as pd
from pybatfish.client.commands import *
from pybatfish.datamodel import *
from pybatfish.datamodel.answer import *
from pybatfish.datamodel.flow import *
from pybatfish.question import *
from pybatfish.question import bfq

NETWORK_NAME = "example_network"          # 任意
SNAPSHOT_NAME = "example_snapshot"        # 任意
SNAPSHOT_PATH = "/notebooks/my_network"   # Configを入れたpathを指定

load_questions()
bf_session.host = 'localhost'
bf_set_network(NETWORK_NAME)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
```

ここまで入れると、以下のようなerrorが出ることがあります。意味としては、
「configの読み取りには成功したが、一部読み取れない部分があった」ということなので、
どこにあるかを確認しましょう。

```text
Your snapshot was successfully initialized but Batfish failed to fully recognized some lines in one or more input files. 
```

以下のコマンドを入れると、Configで読み取れなった行がわかります。
結果を見ると、1行目の``Router#show running-config``が読み取れなかったようです。
Configに関する行ではなかったのでここではスルーします。

```text
bfq.initIssues().answer().frame()
```

![snapshot_error.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554812/20bb2cb8-a87a-4cba-9765-10c7a91a2b49.png)


#### 3-2. access-listの確認

　今回は以下の通信を確認します。access-listの確認をする場合は、通過するinterfaceも指定する必要があります。
通過するinterfaceがわからない場合は、intefaceやroutingの情報から抽出するのがよいと思います。

|id|送信元IP|宛先IP|通過interface(in)|通過interface(out)|宛先ポート番号|deny/permit|
| --- | --- | --- | --- | --- | --- | --- |
|1|192.168.0.2|192.168.1.2|Eth0/0|Eth0/1|80|deny|
|2|192.168.0.2|192.168.2.2|Eth0/0|Eth1/0|443|permit|

- 1. HTTP(80)の通信がdenyになることを確認する

　変数(flow/flow_in/flow_out)に必要な情報を記載し、結果を変数resultに格納します。

```text
flow=HeaderConstraints(srcIps='192.168.0.2',dstIps='192.168.1.2',dstPorts=["80"])
flow_in="@enter(router[FastEthernet0/0])"
flow_out="@out(FastEthernet0/1)"

result = bfq.testFilters(headers=flow, startLocation=flow_in, filters=flow_out).answer().frame()
```

変数resultの中身は以下です。想定通り、access-list 100でdenyになっていることがわかります。
また、通信に関係のないaccess-listは表示されていません。

![result1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554812/81acb57a-0410-a345-f089-0f168a0942ab.png)

ちなみに、``出力用変数.iloc[行番号]``(今回はresult.iloc[0])とすることで、表示結果が以下のように縦に見やすく表示されます。

```text
Node                                                                                               router
Filter_Name                                                                                           100
Flow            start=router interface=FastEthernet0/0 [192.168.0.2:49152->192.168.1.2:80 TCP length=512]
Action                                                                                               DENY
Line_Content                                  deny tcp 192.168.0.0 0.0.0.255 192.168.1.0 0.0.0.255 eq www
Trace                          - Matched line deny tcp 192.168.0.0 0.0.0.255 192.168.1.0 0.0.0.255 eq www
Name: 0, dtype: object
```

- 2. HTTPS(443)の通信がpermitになることを確認する

```text
flow=HeaderConstraints(srcIps='192.168.0.2',dstIps='192.168.2.2',dstPorts=["443"])
flow_in="@enter(router[FastEthernet0/0])"
flow_out="@out(FastEthernet1/0)"

result = bfq.testFilters(headers=flow, startLocation=flow_in, filters=flow_out).answer().frame()
```

変数resultの中身は以下です。想定通り、access-list 110でpermitになっていることがわかります。

![result2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554812/7675d247-a35a-d4ae-5518-0ddba011c72c.png)

result.iloc[0]の結果は以下です。

```text
Node                                                                                                router
Filter_Name                                                                                            110
Flow            start=router interface=FastEthernet0/0 [192.168.0.2:49152->192.168.2.2:443 TCP length=512]
Action                                                                                              PERMIT
Line_Content                                 permit tcp 192.168.0.0 0.0.0.255 192.168.2.0 0.0.0.255 eq 443
Trace                         - Matched line permit tcp 192.168.0.0 0.0.0.255 192.168.2.0 0.0.0.255 eq 443
Name: 0, dtype: object
```


### 参考（出力結果の表示設定）

　pandasのDataFrameをJupyter Notebookで見る際に結果の全てが見えない場合があるので、
以下の設定を適宜入れるようにして下さい。

- 出力する列の制限を解除する（デフォルト：20列）

```text
pd.set_option("display.max_columns", None)
```

- 出力する行の制限を解除する（デフォルト：60行）

```text
pd.set_option("display.max_rows", None)
```

- 1列の表示文字数の制限を解除する（デフォルト：50文字）

```text
pd.set_option("display.max_colwidth", None)
```

### まとめ

　今回紹介したCisco機器の検証以外にも、Batfishは様々なベンダー機器に対応しているので、
興味がある方はBatfishに読み込ませてみるとよいと思います。（リンクは以下）
[Batfish documentation - Supported Network Device and Operating System List](https://batfish.readthedocs.io/en/latest/supported_devices.html)
　なお、Batfishの結果はDataFrameで出力されるので、結果の抽出にはpandasの知識が必要です。

### 参考記事

[pandas 1.3.3 documentation - pandas.set_option](https://pandas.pydata.org/docs/reference/api/pandas.set_option.html)
[Batfish documentation - Interacting with the Batfish service](https://pybatfish.readthedocs.io/en/latest/notebooks/interacting.html#)
[Batfish documentation - Datamodel classes(HeaderConstraints)](https://batfish.readthedocs.io/en/latest/datamodel.html?highlight=HeaderConstraints#pybatfish.datamodel.flow.HeaderConstraints)
[Batfish documentation - Access-lists and firewall rules](https://batfish.readthedocs.io/en/latest/notebooks/filters.html)
[Batfish documentation - Analyzing ACLs and firewall rules with Batfish](https://batfish.readthedocs.io/en/latest/notebooks/linked/analyzing-acls-and-firewall-rules.html)
[Batfish documentation - Supported Network Device and Operating System List](https://batfish.readthedocs.io/en/latest/supported_devices.html)