🌲

パケットキャプチャでSTPを学ぶ(1) 環境構築

2022/06/20に公開

はじめに

STP(Spanning Tree Protocol)知ってますか?
最後に使ったのはいつですか?
ネットワークを学んでいると、「STPは冗長性を確保しつつL2ループを防ぐための仕組みですよ」くらいは出てくるのですが、現場では近年(少なくとも2022年では)あまり見かけなくなりました。
でも忘れたころに急に出くわすんですよね。

一度手を動かしてみて、自分の目で見て確かめると記憶に残りやすい、ということで試してみましょう。
用意するものはLinux 1台だけです。WSLでもOKです。

仮想スイッチングハブの実装

Linuxでは仮想スイッチングハブの実装として大きく分けて2つあります。

  • Linux Bridge
    • 昔からある実装
    • 特に何しなくても使える
    • STPは使えるけどRSTP(Rapid Spanning Tree Protocol)は使えない[1]
  • Open vSwitch
    • ここ10年くらい?で新しくできた実装
    • Open vSwitchのインストール作業が必要(aptで入る)
    • RSTPやLACP(Link Aggregation Control Protocol)のような「スイッチングハブらしい」機能が使える

まずは一番簡単な例ということでLinux Bridgeの実装で確認します。

構成図

構成図はこちらです。
1階の端末は2台のL2スイッチに収容、2階も同じ。各階のスイッチをコアL2スイッチで集約、みたいなイメージです。
図はdrawthe.netで描いています。YAMLはこちら。描き方はこちら

環境構築

環境構築のコマンド全文はこちら
# create bridge
ip link add core1 type bridge
ip link add core2 type bridge
ip link add access1 type bridge
ip link add access2 type bridge
ip link add access3 type bridge
ip link add access4 type bridge

# enable STP
ip link set core1 type bridge stp_state 1
ip link set core2 type bridge stp_state 1
ip link set access1 type bridge stp_state 1
ip link set access2 type bridge stp_state 1
ip link set access3 type bridge stp_state 1
ip link set access4 type bridge stp_state 1

# create network namspaces
ip netns add pc1
ip netns add pc2
ip netns add pc3
ip netns add pc4

# create cable(veth pairs)
ip link add core1_e0 type veth peer name core2_e0
ip link add core1_e1 type veth peer name access1_e1
ip link add core1_e2 type veth peer name access3_e1
ip link add core2_e1 type veth peer name access2_e1
ip link add core2_e2 type veth peer name access4_e1
ip link add access1_e0 type veth peer name access2_e0
ip link add access3_e0 type veth peer name access4_e0
ip link add access1_e2 type veth peer name eth0 netns pc1
ip link add access2_e2 type veth peer name eth0 netns pc2
ip link add access3_e2 type veth peer name eth0 netns pc3
ip link add access4_e2 type veth peer name eth0 netns pc4

# PC
ip netns exec pc1 ip addr add 10.0.0.1/24 dev eth0
ip netns exec pc2 ip addr add 10.0.0.2/24 dev eth0
ip netns exec pc3 ip addr add 10.0.0.3/24 dev eth0
ip netns exec pc4 ip addr add 10.0.0.4/24 dev eth0

ip netns exec pc1 ip link set lo up
ip netns exec pc1 ip link set eth0 up

ip netns exec pc2 ip link set lo up
ip netns exec pc2 ip link set eth0 up

ip netns exec pc3 ip link set lo up
ip netns exec pc3 ip link set eth0 up

ip netns exec pc4 ip link set lo up
ip netns exec pc4 ip link set eth0 up

# attach ports to bridges
ip link set core1_e0 master core1
ip link set core1_e1 master core1
ip link set core1_e2 master core1

ip link set core2_e0 master core2
ip link set core2_e1 master core2
ip link set core2_e2 master core2

ip link set access1_e1 master access1
ip link set access1_e0 master access1
ip link set access1_e2 master access1

ip link set access2_e1 master access2
ip link set access2_e0 master access2
ip link set access2_e2 master access2

ip link set access3_e1 master access3
ip link set access3_e0 master access3
ip link set access3_e2 master access3

ip link set access4_e1 master access4
ip link set access4_e0 master access4
ip link set access4_e2 master access4


# linkup
ip link set core1_e0 up
ip link set core1_e1 up
ip link set core1_e2 up
ip link set core1 up

ip link set core2_e0 up
ip link set core2_e1 up
ip link set core2_e2 up
ip link set core2 up

ip link set access1_e1 up
ip link set access1_e0 up
ip link set access1_e2 up
ip link set access1 up

ip link set access2_e1 up
ip link set access2_e0 up
ip link set access2_e2 up
ip link set access2 up

ip link set access3_e1 up
ip link set access3_e0 up
ip link set access3_e2 up
ip link set access3 up

ip link set access4_e1 up
ip link set access4_e0 up
ip link set access4_e2 up
ip link set access4 up

Bridgeの作成

以下のコマンドでcore1という名前のLinux Bridgeを作成します。access1等、他のBridgeも作成します。作成方法は先のコマンド全文を見てください。

ip link add core1 type bridge

STPの有効化

以下のコマンドでcore1 BridgeのSTPを有効にします。

ip link set core1 type bridge stp_state 1

PCの作成

PC(の代わりになるnetwork namespace)を作成をします。

ip netns add pc1

ケーブルの作成

仮想的なケーブルであるvEthernet Pairを作成します。
この時点ではcore1_e0という名前のポートの対向がcore2_e0という名前のポートであるケーブルを作っているだけです。どのBridgeにも所属していません。

ip link add core1_e0 type veth peer name core2_e0

PCの設定

PCのIP設定、リンクアップします。

ip netns exec pc1 ip addr add 10.0.0.1/24 dev eth0
ip netns exec pc1 ip link set lo up
ip netns exec pc1 ip link set eth0 up

Bridgeにポートを付ける

ip linkコマンドでcore1_e0ポートの親をcore1 Bridgeに設定します。まだリンクアップはしていません。

ip link set core1_e0 master core1

ひと呼吸

ここでひと呼吸して落ち着いてください。
今回作成したネットワークはL2レイヤで見るとループ構造になっています。設定がおかしかったり、実装が不十分だとL2ループが発生します。

この環境はすべてのオブジェクトを仮想的に1台のマシンの中でエミュレーションしています。L2ループが発生するとCPU使用率が跳ね上がります。

別のコンソールウィンドウを開いてtopコマンドを実行するか、定期的にuptimeを発行してCPU使用率を監視しましょう。

この環境ですべてのBridgeを止めるコマンドは以下のコマンドです。CPU使用率が跳ね上がって、ループが疑われたら以下のコマンド投入する心の準備をしておきましょう。

ip link set core1 down
ip link set core2 down
ip link set access1 down
ip link set access2 down
ip link set access3 down
ip link set access4 down

Bridgeリンクアップ

Bridgeの各ポートとBridge自身をリンクアップさせます。
CPU使用率が跳ね上がってきたらBridgeをリンクダウンさせましょう。

ip link set core1_e0 up
ip link set core1_e1 up
ip link set core1_e2 up
ip link set core1 up

状態確認

STP状態確認

bridge linkコマンドでSTPの状態が分かります。
出力が横長で読みづらいのですが、各ポートについて「state listening」とか「state forwarding」とかが出力されるはずです。
(CPU使用率が跳ね上がってきたらBridgeをリンクダウンさせましょう)

# bridge link show
187: core2_e0@core1_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core2 state blocking priority 32 cost 2
188: core1_e0@core2_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 state listening priority 32 cost 2
189: access1_e1@core1_e1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access1 state listening priority 32 cost 2
190: core1_e1@access1_e1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 state listening priority 32 cost 2
191: access3_e1@core1_e2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access3 state listening priority 32 cost 2
192: core1_e2@access3_e1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 state listening priority 32 cost 2
193: access2_e1@core2_e1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access2 state listening priority 32 cost 2
194: core2_e1@access2_e1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core2 state listening priority 32 cost 2
195: access4_e1@core2_e2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access4 state listening priority 32 cost 2
196: core2_e2@access4_e1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core2 state listening priority 32 cost 2
197: access2_e0@access1_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access2 state blocking priority 32 cost 2
198: access1_e0@access2_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access1 state listening priority 32 cost 2
199: access4_e0@access3_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access4 state listening priority 32 cost 2
200: access3_e0@access4_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master access3 state listening priority 32 cost 2
201: access1_e2@tunl0: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 master access1 state listening priority 32 cost 2
202: access2_e2@tunl0: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 master access2 state listening priority 32 cost 2
203: access3_e2@tunl0: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 master access3 state listening priority 32 cost 2
204: access4_e2@tunl0: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 master access4 state listening priority 32 cost 2

awkで必要な列だけ取り出してsortでアルファベット順に並べるとちょっと見やすくなります。

# bridge link show | awk -F '[@ ]' '{print $2"\t"$10}' | sort
access1_e0      listening
access1_e1      listening
access1_e2      listening
access2_e0      listening
access2_e1      listening
access2_e2      listening
access3_e0      listening
access3_e1      listening
access3_e2      listening
access4_e0      listening
access4_e1      listening
access4_e2      listening
core1_e0        listening
core1_e1        listening
core1_e2        listening
core2_e0        listening
core2_e1        listening
core2_e2        listening

疎通確認

pc1からpc2宛にpingしてみます。STPのトポロジ計算が終わるまではpingは成功しません。60秒間pingしても疎通できなければ何かがおかしいです。
(CPU使用率が跳ね上がってきたらBridgeをリンクダウンさせましょう)

# ip netns exec pc1 ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=2.08 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.044 ms
64 bytes from 10.0.0.2: icmp_seq=4 ttl=64 time=0.044 ms
^C
--- 10.0.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3117ms
rtt min/avg/max/mdev = 0.044/0.553/2.081/0.881 ms

最終状態確認

pingが通るようになったら、最終的にSTPがどの状態に収束したのかを確認しましょう。このトポロジだと、どこか2か所に「blocking」と出てくるはずです。

# bridge link show | awk -F '[@ ]' '{print $2"\t"$10}' | sort
access1_e0      forwarding
access1_e1      forwarding
access1_e2      forwarding
access2_e0      blocking
access2_e1      forwarding
access2_e2      forwarding
access3_e0      forwarding
access3_e1      forwarding
access3_e2      forwarding
access4_e0      forwarding
access4_e1      forwarding
access4_e2      forwarding
core1_e0        forwarding
core1_e1        forwarding
core1_e2        forwarding
core2_e0        blocking
core2_e1        forwarding
core2_e2        forwarding

access2_e0とcore2_e0がblocking portなので、以下の状態になっています。pc1からpc2にいくのにものすごい遠回りをしています。[2]

今回はSTPのパラメータを設定せずにすべてデフォルト値のまま動かしました。どのポートがblockingになるのかはランダムに決まります。[3]

パケットキャプチャ

まずはパケットキャプチャを取ってみましょう。

WSLの場合
tcpdump -U -i access1_e1 -w - | /mnt/c/Program\ Files/Wireshark/Wireshark.exe -k -i - &
WindowsからLinuxにノーパスワードSSHできる場合
start cmd /c ssh  LinuxのIPアドレス  tcpdump -U -i access1_e1 -w - ^| "c:\Program Files\Wireshark\Wireshark.exe" -k -i -

パケットがうまく取れました。

  • フレームが「IEEE 802.3」として認識されていますね。普段よく見るIP、TCPだとここは「Ethernet II」になってるんですよね。
  • Logical-Link Control(LLC)という見慣れないレイヤが入ってます。
  • Spanning Tree ProtocolのBPDU(Bridge Protocol Data Unit)フレームが見えます。
    • Root BridgeのID、各種タイマーの値が含まれていることも分かります。

BPDUのフレームの構造はこちらのサイトが詳しいです。ネットワークを学ぶ者なら必読のサイトです。

https://www.n-study.com/layer2switch/stp-bpdu/

後片付け

作成したオブジェクトは再起動すれば消えます。
再起動なしで消したいときは以下のコマンドを実行してください。

ip -br link show  type veth | awk -F '[@ ]' '{print $1}' | xargs -n 1 -t ip link delete
ip -br link show  type bridge | awk '{print $1}' | xargs -n 1 -t ip link delete
ip -a netns delete

この記事で作っている以外のvethやLinux Bridge、network namespaceがあるときは以下のコマンドです。

ip link delete access1_e0
ip link delete access1_e2
ip link delete access2_e2
ip link delete access3_e0
ip link delete access3_e2
ip link delete access4_e2
ip link delete core1_e0
ip link delete core1_e1
ip link delete core1_e2
ip link delete core2_e1
ip link delete core2_e2
ip link delete core1
ip link delete core2
ip link delete access1
ip link delete access2
ip link delete access3
ip link delete access4
ip netns delete pc1
ip netns delete pc2
ip netns delete pc3
ip netns delete pc4

参考:動作確認を行った環境について

# cat /etc/issue
Ubuntu 22.04 LTS \n \l

# uname -r
5.10.16.3-microsoft-standard-WSL2
# dpkg -l iproute2
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version         Architecture Description
+++-==============-===============-============-====================================
ii  iproute2       5.15.0-1ubuntu2 amd64        networking and traffic control tools

まとめ

WSLの中に仮想的にスイッチングハブを作ってSTPが動作すること、パケットキャプチャが取れることが分かりました。
BPDU交換でSTPトポロジを形成していく様子や、経路切り替わりするときのパケットの動きも今後見ていきたいと思います。

脚注
  1. 厳密にいうとmstpdというデーモンを入れれば使えるのですが、標準リポジトリにも存在していないのでちょっと手間がかかります。 ↩︎

  2. というか説明の都合上、この構成になるまで何回か作成・削除を繰り返したんですけどね。 ↩︎

  3. 正確に言うと、ip linkコマンドでBridgeが作成されるときにBridgeのMACアドレスがランダムに設定され、トポロジはMACアドレスによって決まります。 ↩︎

Discussion