はじめに
前回: BatfishでL2 Topologyを出せるかどうか調べてみる の補足的な記事です。前回は最も基本的な構成を、ということで 1-switch, 2-segment で試してみました。今回はそれをちょっと拡張して 2-switch, 2-segment です。以下の要素を足します。
- Trunk port
- Link Aggregation (Port channel)
- HSRP
構成
使用している Batfish container は前回と同じです。
- config 等は引き続き GitHub に置いてあります。
- 今回スイッチの増設は単純に config copy でやっているので間違えてるところがあるかもしれない……おかしいところを見つけたら教えてください。
- Layer1 topology file が必要なのは前回わかっているので最初から置きます。以下の Sample 3-5 ですべて同じ L1 topology です。
Sample3
Sample1 の構成を 2-switch に変更しています。VLAN100 で HSRP, スイッチ間リンクに Port Channel, VLAN Trunk を使います。
Sample4
Sample2 の構成を 2-switch に変更しています。追加点は sample3 と同様。同じ IP サブネットを使っている点に注意してください。
Sample5
Sample4 をちょっとイジワルな形にしているものです (異なる VLAN ID を使ってひとつの L2 segment をつくる)。まあ、通常こういう造りにあえてしようとは思わないでしょうけど、いちおう造りとしてはやれるので。
- 同一 IP サブネットを使用 (L3/L2 の構造自体は sample4 と同様)
- 同一 L2 セグメントを異なる VLAN ID で接続
- このためスイッチ間リンクは trunk ではなく access です。
これは Batfish が複数スイッチにまたがる L2 domain をどのくらいちゃんと見ているのかというのを確認したくて設定しています。(環境内ですべての VLAN ID が一意に同じセグメントを指しているみたいな仮定を持ってしまうと読めない構成です。)
検証
Sample3: 複数スイッチでの L2 Segment
まず Layer1 topology がちゃんと渡せているかを確認。物理リンクは 8-hosts + 2-interlinks なので片方向ずつ合計 20 本になりますが、スイッチ間リンクは物理インタフェースじゃなくて LAG (Port channel) で扱われているため (8 + 1) * 2 = 18 edges になっています。
>>> ans = bfq.edges(edgeType='layer1')
>>> ans.answer().frame()
status: TRYINGTOASSIGN
.... no task information
status: ASSIGNED
.... 2019-10-28 11:41:18.702000+09:00 Reading layer-1 topology 1 / 1.
status: TERMINATEDNORMALLY
.... 2019-10-28 11:41:18.702000+09:00 Reading layer-1 topology 1 / 1.
Interface Remote_Interface
0 host13[eth0] switch2[GigabitEthernet1/0/3]
1 host14[eth0] switch2[GigabitEthernet1/0/4]
2 host22[eth0] switch1[GigabitEthernet1/0/6]
3 switch2[GigabitEthernet1/0/4] host14[eth0]
4 host23[eth0] switch2[GigabitEthernet1/0/7]
5 switch2[GigabitEthernet1/0/8] host24[eth0]
6 switch1[GigabitEthernet1/0/6] host22[eth0]
7 host21[eth0] switch1[GigabitEthernet1/0/5]
8 switch2[GigabitEthernet1/0/7] host23[eth0]
9 host11[eth0] switch1[GigabitEthernet1/0/1]
10 host12[eth0] switch1[GigabitEthernet1/0/2]
11 host24[eth0] switch2[GigabitEthernet1/0/8]
12 switch1[GigabitEthernet1/0/5] host21[eth0]
13 switch2[Port-Channel1] switch1[Port-Channel1]
14 switch1[Port-Channel1] switch2[Port-Channel1]
15 switch2[GigabitEthernet1/0/3] host13[eth0]
16 switch1[GigabitEthernet1/0/1] host11[eth0]
17 switch1[GigabitEthernet1/0/2] host12[eth0]
>>>
つづいて L3 topology を見てみます。
>>> ans = bfq.edges(edgeType='layer3')
>>> ans.answer().frame()
status: TRYINGTOASSIGN
.... no task information
status: ASSIGNED
.... 2019-10-28 11:45:57.383000+09:00 Begin job.
status: TERMINATEDNORMALLY
.... 2019-10-28 11:45:57.383000+09:00 Begin job.
Interface IPs Remote_Interface Remote_IPs
0 host21[eth0] ['192.168.2.101'] host23[eth0] ['192.168.2.103']
1 host21[eth0] ['192.168.2.101'] host24[eth0] ['192.168.2.104']
2 host24[eth0] ['192.168.2.104'] host23[eth0] ['192.168.2.103']
3 switch1[Vlan100] ['192.168.1.2'] switch2[Vlan100] ['192.168.1.3']
4 host21[eth0] ['192.168.2.101'] host22[eth0] ['192.168.2.102']
5 switch1[Vlan100] ['192.168.1.2'] host13[eth0] ['192.168.1.103']
# 省略
37 host24[eth0] ['192.168.2.104'] host22[eth0] ['192.168.2.102']
38 switch2[Vlan100] ['192.168.1.3'] switch1[Vlan100] ['192.168.1.2']
39 host22[eth0] ['192.168.2.102'] host23[eth0] ['192.168.2.103']
40 host22[eth0] ['192.168.2.102'] host24[eth0] ['192.168.2.104']
41 host24[eth0] ['192.168.2.104'] host21[eth0] ['192.168.2.101']
>>>
VLAN 100 が 4-hosts + 2-SVI なので 6 * 5 = 30 edges, VLAN 200 が 4-hosts なので 4 * 3 = 12 edges, 合わせて 42 edges ということですね。HSRP (VIP) は edges の計算には含まれないみたいです。
>>> df = ans.answer().frame()
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 11:49:34.377000+09:00 Begin job.
>>> df.loc[list(map(lambda d: d.hostname=='host11', df.Interface.values))]
Interface IPs Remote_Interface Remote_IPs
7 host11[eth0] ['192.168.1.101'] host12[eth0] ['192.168.1.102']
10 host11[eth0] ['192.168.1.101'] host13[eth0] ['192.168.1.103']
14 host11[eth0] ['192.168.1.101'] switch1[Vlan100] ['192.168.1.2']
17 host11[eth0] ['192.168.1.101'] switch2[Vlan100] ['192.168.1.3']
18 host11[eth0] ['192.168.1.101'] host14[eth0] ['192.168.1.104']
>>>
(補足) Batfish 機能確認
Port-channel: 一部省略していますがちゃんと channel として property がとれています。
>>> ifprops = bfq.interfaceProperties(nodes="switch1", interfaces="Port-channel1", properties='.*').answer()
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 12:12:20.374000+09:00 Begin job.
>>> pprint.pprint(ifprops.rows)
[{'Access_VLAN': None,
...
'Allowed_VLANs': '100,200',
'Auto_State_VLAN': True,
'Bandwidth': 2000000000.0,
'Blacklisted': False,
'Channel_Group': None,
'Channel_Group_Members': ['GigabitEthernet1/0/23', 'GigabitEthernet1/0/24'],
'DHCP_Relay_Addresses': [],
'Declared_Names': ['Port-channel1'],
'Description': None,
'Encapsulation_VLAN': None,
...
'Interface': {'hostname': 'switch1', 'interface': 'Port-Channel1'},
...
'Switchport': True,
'Switchport_Mode': 'TRUNK',
'Switchport_Trunk_Encapsulation': 'DOT1Q',
...
}]
>>>
HSRP: いまの batfish で HSRP 情報を扱うようなクエリがない……? まず ip owners query で VIP が出てこない。1
>>> bfq.ipOwners().answer()
status: TRYINGTOASSIGN
.... no task information
status: ASSIGNED
status: TERMINATEDNORMALLY
.... 2019-10-28 12:21:41.246000+09:00 Begin job.
Node VRF Interface IP Mask Active
0 host14 default eth0 192.168.1.104 24 True
1 host13 default eth0 192.168.1.103 24 True
2 host11 default eth0 192.168.1.101 24 True
3 host22 default eth0 192.168.2.102 24 True
4 host21 default eth0 192.168.2.101 24 True
5 host24 default eth0 192.168.2.104 24 True
6 switch1 default Vlan100 192.168.1.2 24 True
7 switch2 default Vlan100 192.168.1.3 24 True
8 host12 default eth0 192.168.1.102 24 True
9 host23 default eth0 192.168.2.103 24 True
>>>
interface properties query で確認してみると、HSRP Group は取れているけど VIP の情報が入ってきてないんだよねえ。
>>> ifprops = bfq.interfaceProperties(nodes="switch1", interfaces="Vlan100", properties='.*').answer()
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 12:17:32.803000+09:00 Begin job.
>>> pprint.pprint(ifprops.rows)
[{
...
'All_Prefixes': ['192.168.1.2/24'],
...
'Declared_Names': ['Vlan100'],
...
'HSRP_Groups': ['100'],
'HSRP_Version': None,
'Incoming_Filter_Name': None,
'Interface': {'hostname': 'switch1', 'interface': 'Vlan100'},
...
'Primary_Address': '192.168.1.2/24',
'Primary_Network': '192.168.1.0/24',
...
'VRRP_Groups': [],
'Zone_Name': None}]
>>>
Sample4: 複数スイッチでの IP Subnet 重複
Layer1 topology は同様に出るので Layer3 topology を確認。VLAN 100/200 それぞれ 4-hosts + 2-SVI なので 6 * 5 * 2 = 60 edges になります。
>>> ans = bfq.edges(edgeType='layer3')
>>> ans.answer().frame()
status: BLOCKED
.... no task information
status: BLOCKED
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 12:40:30.090000+09:00 Begin job.
Interface IPs Remote_Interface Remote_IPs
0 switch1[Vlan100] ['192.168.1.2'] switch2[Vlan100] ['192.168.1.3']
1 switch1[Vlan200] ['192.168.1.2'] switch2[Vlan200] ['192.168.1.3']
2 switch2[Vlan200] ['192.168.1.3'] switch1[Vlan200] ['192.168.1.2']
3 switch1[Vlan200] ['192.168.1.2'] host23[eth0] ['192.168.1.103']
4 switch2[Vlan200] ['192.168.1.3'] host22[eth0] ['192.168.1.102']
# 省略
55 switch2[Vlan100] ['192.168.1.3'] switch1[Vlan100] ['192.168.1.2']
56 host23[eth0] ['192.168.1.103'] switch1[Vlan200] ['192.168.1.2']
57 host22[eth0] ['192.168.1.102'] switch1[Vlan200] ['192.168.1.2']
58 host23[eth0] ['192.168.1.103'] switch2[Vlan200] ['192.168.1.3']
59 host22[eth0] ['192.168.1.102'] switch2[Vlan200] ['192.168.1.3']
>>>
各セグメントのノードをピックアップして確認。おなじ IP サブネットを使用していますがちゃんと L2 segment で分割できています。
>>> df = ans.answer().frame()
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 12:41:49.258000+09:00 Begin job.
>>> df.loc[list(map(lambda d: d.hostname=='host11', df.Interface.values))]
Interface IPs Remote_Interface Remote_IPs
18 host11[eth0] ['192.168.1.101'] switch1[Vlan100] ['192.168.1.2']
21 host11[eth0] ['192.168.1.101'] switch2[Vlan100] ['192.168.1.3']
39 host11[eth0] ['192.168.1.101'] host12[eth0] ['192.168.1.102']
41 host11[eth0] ['192.168.1.101'] host13[eth0] ['192.168.1.103']
45 host11[eth0] ['192.168.1.101'] host14[eth0] ['192.168.1.104']
>>> df.loc[list(map(lambda d: d.hostname=='host21', df.Interface.values))]
Interface IPs Remote_Interface Remote_IPs
13 host21[eth0] ['192.168.1.101'] host23[eth0] ['192.168.1.103']
47 host21[eth0] ['192.168.1.101'] host24[eth0] ['192.168.1.104']
48 host21[eth0] ['192.168.1.101'] host22[eth0] ['192.168.1.102']
51 host21[eth0] ['192.168.1.101'] switch1[Vlan200] ['192.168.1.2']
52 host21[eth0] ['192.168.1.101'] switch2[Vlan200] ['192.168.1.3']
>>>
Sample5: 同一 L2 segment 異 VLAN ID
L1 topology データとしては同じなのですが sample5 では port channel を使用していないので確認してみます。
>>> ans = bfq.edges(edgeType='layer1')
>>> ans.answer().frame()
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 12:45:30.873000+09:00 Reading layer-1 topology 1 / 1.
Interface Remote_Interface
0 host13[eth0] switch2[GigabitEthernet1/0/3]
1 host14[eth0] switch2[GigabitEthernet1/0/4]
2 host22[eth0] switch1[GigabitEthernet1/0/6]
3 switch2[GigabitEthernet1/0/4] host14[eth0]
4 host23[eth0] switch2[GigabitEthernet1/0/7]
5 switch2[GigabitEthernet1/0/23] switch1[GigabitEthernet1/0/23]
6 switch2[GigabitEthernet1/0/8] host24[eth0]
7 switch1[GigabitEthernet1/0/6] host22[eth0]
8 host21[eth0] switch1[GigabitEthernet1/0/5]
9 switch2[GigabitEthernet1/0/24] switch1[GigabitEthernet1/0/24]
10 switch2[GigabitEthernet1/0/7] host23[eth0]
11 switch1[GigabitEthernet1/0/23] switch2[GigabitEthernet1/0/23]
12 switch1[GigabitEthernet1/0/24] switch2[GigabitEthernet1/0/24]
13 host11[eth0] switch1[GigabitEthernet1/0/1]
14 host12[eth0] switch1[GigabitEthernet1/0/2]
15 host24[eth0] switch2[GigabitEthernet1/0/8]
16 switch1[GigabitEthernet1/0/5] host21[eth0]
17 switch2[GigabitEthernet1/0/3] host13[eth0]
18 switch1[GigabitEthernet1/0/1] host11[eth0]
19 switch1[GigabitEthernet1/0/2] host12[eth0]
>>>
ちゃんとスイッチ間リンクが物理インタフェース Gi1/0/23-24 で表示されています。((8-hosts + 2-interlinks) * 2 = 20 edges)
L3 topology についても sample4 と同等になりました。異なる VLAN ID でひとつの L2 セグメントになっていることは、host11 の edge には switch1/VLAN100, switch2/VLAN200 が、host21 の edge には switch1/VLAN200, switch2/VLAN300 があることからも確認できます。
>>> df.loc[list(map(lambda d: d.hostname=='host11', df.Interface.values))]
Interface IPs Remote_Interface Remote_IPs
17 host11[eth0] ['192.168.1.101'] switch1[Vlan100] ['192.168.1.2']
38 host11[eth0] ['192.168.1.101'] host12[eth0] ['192.168.1.102']
40 host11[eth0] ['192.168.1.101'] host13[eth0] ['192.168.1.103']
46 host11[eth0] ['192.168.1.101'] host14[eth0] ['192.168.1.104']
47 host11[eth0] ['192.168.1.101'] switch2[Vlan200] ['192.168.1.3']
>>> df.loc[list(map(lambda d: d.hostname=='host21', df.Interface.values))]
Interface IPs Remote_Interface Remote_IPs
12 host21[eth0] ['192.168.1.101'] host23[eth0] ['192.168.1.103']
22 host21[eth0] ['192.168.1.101'] switch2[Vlan300] ['192.168.1.3']
49 host21[eth0] ['192.168.1.101'] host24[eth0] ['192.168.1.104']
50 host21[eth0] ['192.168.1.101'] host22[eth0] ['192.168.1.102']
54 host21[eth0] ['192.168.1.101'] switch1[Vlan200] ['192.168.1.2']
>>>
まとめ
前回 1-switch 2-segment で確認したモノを 2-switch に拡張してみました。Trunk/Access や LAG (Port channel) についてもちゃんと扱えていますね。主要な L2 (topology) 設定についてはおおむね parse できているようです。
edges
ではあまり問題にはならないですが、HSRP (VRRP) についてはどういう扱いになっているのかもうちょっと確認した方が良さそうですね……。
L2 Topology をどう出すか
L2 Topology: L2 segment がどのスイッチのどのポートを通ってどのノードと接続されているか、というのはどう求めれば良いのでしょうか?
まず、前提として
- NW 機器コンフィグは与えられる
- L1 topology はどうにかして取得できる
- L1 config がないと L2 topology の計算ができないので。とはいえ L1 topology をちゃんと作るのもそれはそれで面倒なんですが
とします。そのうえで各 L2 segment 間の接続を見ていくのがベタだけどひとつの方法でしょう。 Switched VLAN properties query では VLAN がどのインタフェースにマッピングされているかを求めることができます。(下記は sample4 での実行例)
>>> svprops = bfq.switchedVlanProperties(nodes="switch1").answer()
status: TRYINGTOASSIGN
.... no task information
status: TERMINATEDNORMALLY
.... 2019-10-28 14:15:33.307000+09:00 Begin job.
>>> pprint.pprint(svprops.rows)
[{'Interfaces': [{'hostname': 'switch1', 'interface': 'Vlan1'}],
'Node': {'id': 'node-switch1', 'name': 'switch1'},
'VLAN_ID': 1,
'VXLAN_VNI': None},
{'Interfaces': [{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/1'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/2'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/23'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/24'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/3'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/4'},
{'hostname': 'switch1', 'interface': 'Port-Channel1'},
{'hostname': 'switch1', 'interface': 'Vlan100'}],
'Node': {'id': 'node-switch1', 'name': 'switch1'},
'VLAN_ID': 100,
'VXLAN_VNI': None},
{'Interfaces': [{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/23'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/24'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/5'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/6'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/7'},
{'hostname': 'switch1', 'interface': 'GigabitEthernet1/0/8'},
{'hostname': 'switch1', 'interface': 'Port-Channel1'},
{'hostname': 'switch1', 'interface': 'Vlan200'}],
'Node': {'id': 'node-switch1', 'name': 'switch1'},
'VLAN_ID': 200,
'VXLAN_VNI': None}]
>>>
これらの情報を元に、各スイッチ/セグメント (VLAN) ごとにポート対応、隣接スイッチとの接続情報対応をみて L2 segment の範囲とかを洗い出せばよいはず。
ただ、これまで見てきたように Batfish は L3 node (IP addr) 間の隣接関係を L1/L2 情報に基づいて計算できています。つまり、こうした L2 トポロジ情報を内部的にはもっているはずなんですよね。でもそれをひっぱり出す口がない。あるスイッチの VLAN/Port の対応、各ポートについて対向のポートの VLAN 設定を突き合わせていって L2 トポロジを作るって実装はまあできるとは思うし、手動でやるよりはやりやすくはなっているでしょう。だけど、batfish の内部で恐らくやってるであろう処理を外で作り直すであろうことと、そもそもそういった面倒な計算をお任せできるツールとして期待しているというのがあって、なんかもうちょっといい方法ないのかなあとぐるぐる考えてしまいますね……。
-
Issueあった (2019/10/28時点では Open) : IPOwners should include HSRP IPs · Issue #4593 · batfish/batfish ↩