🌉

パケットキャプチャでSTPを学ぶ(3) Root Bridge選出

2022/06/24に公開

前回のおさらい

STP(Spanning Tree Protocol)のパケットキャプチャができる環境作りとパケットキャプチャ取得をしました。

https://zenn.dev/takai404/articles/1cd9bc85f53331

今回は取得したパケットキャプチャを実際に見ながらSTPのRoot Bridge選出の動きをみてみます。

環境

構成図は以下の通りです。

図中のポート、BridgeのMACアドレスは以下の通りです。

デバイス MACアドレス
access1_e0 52:65:55:a4:d6:45
access1_e1 d2:62:ef:5b:0d:85
access1_e2, access1 32:c8:70:0d:8a:30
access2_e0 ce:3f:e7:b8:99:02
access2_e1 ee:7a:38:59:e8:35
access2_e2, access2 3e:cc:33:fc:df:10
access3_e0 fe:53:e0:d3:ad:0a
access3_e1, access3 06:6e:04:36:2e:c5
access3_e2 0a:87:a3:48:e2:4a
access4_e0 5e:31:05:90:b1:0c
access4_e1 96:e6:22:86:7c:86
access4_e2, access4 42:bf:cd:48:42:30
core1_e0 c2:67:12:ab:9a:a1
core1_e1, core1 6a:78:dc:b8:0a:3d
core1_e2 ba:53:6c:3a:f2:a6
core2_e0, core2 26:4b:c9:95:0e:e0
core2_e1 8e:ec:ba:e3:d8:e2
core2_e2 32:8e:62:9d:2f:1f

STPの各種パラメータはデフォルト値です。

Root Bridgeとは

STPでは、Bridge同士がBPDU(Bridge Protocol Data Unit)というメッセージを交換しあって、その系の中で「誰が一番偉いか」を決めます。
この後、この最も偉いBridgeを頂点とした木構造(Tree構造)を作っていきます。

「偉さ」というのはBridge IDという数値で決めます。小さい方が「偉い」です。

Bridge IDはBridge PriorityとBridgeのMACアドレスをつなげたものです。
Bridge Priorityのデフォルト値は16進数 0x8000 (10進数 32768)です。

今回でいうとこうなります。

デバイス Bridge Priority MACアドレス Bridge ID 役割
access1 0x8000 32:c8:70:0d:8a:30 0x800032c8700d8a30
access2 0x8000 3e:cc:33:fc:df:10 0x80003ecc33fcdf10
access3 0x8000 06:6e:04:36:2e:c5 0x8000066e04362ec5 Root Bridge
access4 0x8000 42:bf:cd:48:42:30 0x800042bfcd484230
core1 0x8000 6a:78:dc:b8:0a:3d 0x80006a78dcb80a3d
core2 0x8000 26:4b:c9:95:0e:e0 0x8000264bc9950ee0

BPDUとは

こちらがBPDUパケットです。

フィールド 説明
Protocol Identifier 0です。標準STP、RSTP、MSTPどれでも0です。
Protocol Version Identifier 標準STPは0です。
BPDU Type Bridge情報をやり取りするのはConfiguration BPDUで、この他にTCN(Topology Change Notification)もあります。
BPDU flags STPトポロジに変化があったことをConfiguration BPDUで通知するときに使います
Root Identification (その時点で)Root Bridgeだと認識されているBridgeのIDが入ります。
- Priority 基本的には4096, 8192, 12288, 16384, 20480, 24576, 28672, 32768, 36864, 40960, 45056, 49152, 53248, 57344, 61440のどれか。
- System ID extention 標準STPでは0。
- System ID MACアドレス。なんでWiresharkで「Root Bridge System ID」と表記されているのかが謎なんですよね。まず事実としてこのフィールドに入るのはMACアドレスです。いくつかのドキュメントを漁ってみたのですが、このフィールドを「System ID」と呼称しているドキュメントが見つからない…
Root Path Cost これも新旧2つの形式があって分かりづらい… とりあえずRoot Bridgeから遠くなるほど数値が積算されて大きくなるものと認識してもらえればOKです。
Bridge Identifier BPDUを送信したBridgeのIDです。
Port Identifier 正直、このフィールドの値を使ってるケースを見たことない
Message Age Root Bridgeから出るときは0で、Bridgeを経由する度に積算されていきます。そうするとRoot Path Costとどう違うの?という疑問が出てきます。まぁとりあえずRoot Path Costの方が大事です。Message Ageに着目することはあんまりないかな。あと、何秒加算されるのか、というのがはっきりしないです。検証して確かめたいところ。
Max Age 下のForward Delayと合わせてSTPの収束時間を決める重要なパラメータ。デフォルト20秒。
Hello Time BPDUを出す間隔。デフォルト2秒。
Forward Delay デフォルト15秒。

Bridge PriorityがSTPの理解を難しく感じさせる要因の一つだと思うんですよね。Bridge PriorityとSystem ID Extentionの2つを合わせて2バイト(16ビット)のフィールドです。
16ビットということは0~65535の範囲の数値が入ると考えるのが自然で、当初はそういう風に設計されていたのですが、PVST+(Per-VLAN Spanning Tree Plus)、RSTP(Rapid Spanning Tree Protocol)ではPriorityは上位4ビットのみをPriorityとして使う、下位12ビットはVLAN IDを入れる、という形になりました。
Priorityが「0~15の整数」であれば分かりやすいのですが、上記の経緯があって「0~61440の範囲の4096の倍数」となります。[1]

4096の倍数を使うので、10進数よりも16進数の方が見やすいです。以下の表を見れは一目瞭然ですね。
ただ、ここからは標準STPじゃなくてPVST+、RSTPの話なのですが、「(広義の)Pritory=32868」みたいになることもあります。これは32768+100で、(狭義のPriority)32768+VLAN番号100から成り立っています。16進数で表現すると0x8064、これだと上位4ビットが1000(2進数)なのは分かりやすいのですが、下位12ビットの0x64を頭の中で瞬時に100(10進数)に変換してVLAN100と認識できる人は少ないので、やっぱり10進表記も16進表記も見づらいSTPのPriortyって分かりづらい…って思っちゃうんですよね。

2進数 10進数 16進数
0001 0000 0000 0000 4096 1000
0010 0000 0000 0000 8192 2000
0011 0000 0000 0000 12288 3000
0100 0000 0000 0000 16384 4000
0101 0000 0000 0000 20480 5000
0110 0000 0000 0000 24576 6000
0111 0000 0000 0000 28672 7000
1000 0000 0000 0000 32768 8000
1001 0000 0000 0000 36864 9000
1010 0000 0000 0000 40960 A000
1011 0000 0000 0000 45056 B000
1100 0000 0000 0000 49152 C000
1101 0000 0000 0000 53248 D000
1110 0000 0000 0000 57344 E000
1111 0000 0000 0000 61440 F000

STPで障害があった場合の途絶時間は基本的には(Forward Delay x 2)か(Forward Delay x 2 + Max Age)と思っておけば大丈夫です。デフォルトでは(15 x 2 = 30秒)か(15 x 2 + 20 = 50秒)です。

ところで、STPの各種タイマーの秒数はSTPについて書かれたドキュメントを見ていると必ず書かれているのですが、パケットキャプチャを見て初めて気づくこともあります。
以下の図の赤丸のところ、違和感ないですか?

真ん中のペインではHello Timerは2(秒)なのに、下のペインでは16進数表現で0x0200?
これが0x0002なら0x0002(16進数)=2(10進数)なので違和感ないです。
0x0200(16進数)=512(10進数)なのになぜ2秒になる?

答えを言うと、このフィールドは以下のように定義されています。

Timer Values shall be encoded in two octets, taken to represent an unsigned binary number multiplied by a unit of time of 1/256 of a second. This permits times in the range 0 to, but not including, 256 s to be represented.

(意訳)タイマー値は2バイトです。\frac{1}{256}を掛けると秒数になります。(0秒以上256秒未満)

先の例でいうと512 * \frac{1}{256} = 2 (秒)ですね。
まぁ正直、設定値(Max Age, Hello Time, Forward Delay)に小数点以下の数を使うことはないので、「2バイト目は基本的に0が入っている、1バイト目が秒数を表す」って考えた方が楽です。
ただ、Message Ageについては2バイト目が0ではない場合があります。

では、パケットを見ながらどのようにaccess3がRoot Bridgeに選出されるかを見てみましょう。
ここで紹介したキャプチャファイルはここに置いてあるので興味があれば見てください。

起動直後(t=0)

Bridge起動直後のaccess1のパケットキャプチャです。



Download access1.pcap

  • access1の3つのポートからBPDUが出ている(赤色)
    • Bridgeは最初は全ポートからBPDUを送出
    • BPDUのRoot IDには「その時点でBridgeが一番偉い(Root Bridgeである)と思っているIDが入る」
    • 他のBridgeの情報がまだないので「一番偉いBridge」=「自分」
  • access2からBPDUを受信してる(青色)
    • access2は自分自身(access2)が一番偉いと思っているので、BPDUのRoot IDにはaccess2のBridge IDを入れている
  • core1からBPDUを受信してる(緑色)
    • core1もBPDUにcore1のBridge IDを入れている

他のBridgeも同様に自分自身のBridgeIDをBPDUのRoot IDフィールドに入れたBPDUを出します。

t=0~2

次にaccess1とcore1のBPDUが時間とともにどう変化するのかを確認します。



Download access1_e1.pcap

  • t=0の時は先ほど説明した通り、access1とcore1がお互いにBPDUを送りあっています(赤色)
    • このとき、access1とcore1のBPDUのRoot IDを比較するとaccess1(0x8000 32:c8:~)の方がcore1(0x8000 6a:78:~)より低いです。これはaccess1の方が偉いということを示しています。
  • t=1(Bridge起動から1秒後)の時、access1が送出するBPDUには変化がないのですが、core1が送出するBPDUのRoot IDがcore1(0x8000 6a:78:~)からaccess3(0x8000 06:6e:~)に変わりました。
    • STPでは、Bridge間でBPDUをやり取りし、相手が自分より格上だと判断すると下っ端は黙り込みます。以降、偉い人だけが一方的にしゃべり続けます(人間社会みたい…)
    • t=0ではaccess1の方が偉かったので、access1はt=1でもBPDUを出しています。
    • t=0ではcore1はaccess1に負けたので、BPDUを出さない… つもりだったのですが、強力な後ろ盾(access3)ができたので、「やっぱ自分(の後ろ盾)の方が偉いよ」とaccess1に言っているのです(人間社会みたい…)
  • t=2以降はcore1だけがBPDUを送出し、access1は(core1との間では)沈黙しました。
    • access1はaccess1が知りうる最も偉いBridge IDよりも、core1の出してきたRoot IDの方が高いので、BPDUを出さなくなったのです。

core1

core1の動きに着目してみます。



Download core1.pcap

  • 最初はcore1自身のBridge ID(6a:78:~)をRoot IDと認識して周りにBPDUを送っています(赤色)
  • 3つの隣接BridgeからBPDUが送られてきます。それぞれのBPDUは06:6e:~、32:c8:~、26:4b:~です。全部、自分が認識していたRoot IDより偉いIDですが、最も偉いのは06:6e:~(access3)です(青色)
  • それ以降は06:6e:~(access3)をRoot IDフィールドにセットしたBPDUを送り続けます(緑色)

access3

最終的にはaccess3がこのネットワーク全体のRoot Bridgeとなるので、ある意味で分かりやすい動きをしています。



Download access3.pcap

  • 最初に06:6e:~をRoot IDとしており、最終的にこのRoot IDを最後まで送り続けます(赤色)
  • 途中で隣接Bridgeから自分と異なるRoot IDがセットされたBPDUが届きますが、自分の方が偉いので無視です(青色)

access4

これはcore1と似ています。



Download access4.pcap

  • 最初はaccess4自身のBridge ID(42:bf:~)をRoot IDと認識して周りにBPDUを送っています(赤色)
  • 2つの隣接BridgeからBPDUが送られてきます。それぞれのBPDUは06:6e:~、26:4b:~です。全部、自分が認識していたRoot IDより偉いIDですが、最も偉いのは06:6e:~(access3)です(青色)
  • それ以降は06:6e:~(access3)をRoot IDフィールドにセットしたBPDUを送り続けます(緑色)

core2



Download core2.pcap

  • 最初はcore2自身のBridge ID(26:4b:~)をRoot IDと認識して周りにBPDUを送っています(赤色)
  • 3つの隣接BridgeからBPDUが送られてきます。42:bf:~(access4)、6a:78:~(core1)、3e:cc:~(access2)全て自分より下っ端なのでこの時点では無視しています(青色)
  • 隣接BridgeからくるBPDUが弱いので、自分のBridge IDをRoot IDとして継続してBPDUを出しています。隣接Bridgeは自分に対してBPDUを送ってこなくなると思っています(緑色)
  • ところがcore1とaccess4はさっきとは別のRoot ID 06:6e:~をセットしてBPDUを送ってきました。これらのBridgeはaccess3からBPDUを受け取っていて、なおかつ26:4b:~(core2)よりも偉いので逆襲してきたわけです。(オレンジ色)
  • core2も06:6e:~(access3)をRoot IDフィールドにセットしたBPDUを送り始めました。Bridge起動から3秒経っています(ピンク色)

access2

翻弄される可哀そうなやつです。



Download access2.pcap

  • 最初はaccess2自身のBridge ID(3e:cc:~)をRoot IDと認識して周りにBPDUを送っています(赤色)
    • 自分が一番偉いと思っています。
  • 2つの隣接BridgeからBPDUが送られてきます。それぞれのBPDUは32:c8:~(access1)、26:4b:~(core2)です。どちらも自分が認識していたRoot IDより偉いIDですが、最も偉いのは26:4b:~(core2)です(青色)
    • 26:4b:~(core2)が一番偉いと思っているので、core2から受け取ったRoot ID=26:4b:~を自分のe0の先につながっているaccess1をはじめとする隣接装置にBPDUを送っています。(緑色)
    • 自分のボスはcore2、子分はaccess1だと思っています。
  • 下っ端だと思っていたaccess1が自分宛てにRoot ID=06:6e:~(access3)というBPDUを送って来ました。(オレンジ色)
    • 自分のボスはaccess1、子分はcore2だと思っています。
  • 今度は逆にaccess1様から頂戴したRoot ID=06:6e:~(access3)をcore2宛に送り始めます。(ピンク色)
  • さっきまではRoot ID=26:4b:~(core2)を送ってきていたcore2が、今度はRoot ID=06:6e:~(access3)を送ってきました。(水色)
    • access1もcore2もどちらも自分より偉いと悟りました。
  • これ以降はaccess2がBPDUを送信するのはe2だけです。e2はPCがつながっているので、向こうからBPDUが来ることはありません(茶色)

時系列での変化

時系列でどのBridgeがだれをRootと認識しているのかを図示しました。

最初は全Bridgeが自分をRootだと思っています。

access3とcore2がそれぞれ勢力圏を伸ばしました。access1は独立王国です。

access1とcore2もaccess3に吸収されました。access2はcore2(26:4b:~)をRoot Bridgeだと思っているのですが、実はcore2は既に陥落しています。

最終的にはaccess3が世界征服しました。

これで全Bridgeの共通認識として、もっとも優先度の高い(Bridge IDパラメータの数値が低い)Bridgeがaccess3であると分かり、access3がRoot Bridgeに選出されました。

まとめ

STPのパケットキャプチャを見ながらRoot Bridgeが選出されるところまでを確認しました。
説明上スルーしたのですが、STPのHelloは2秒おきじゃなかったかな?とか思いながら最初の方は1秒ちょっとでBPDUを出しているように見えるんですよね。
ただ、大まかな流れとしては教科書通りに、BridgeがBPDUを交換し合うところが見えました。
この次は各ポートの役割の決定、STPトポロジの決定ですね。

脚注
  1. 標準STPではPriorityに10、100、10000を使うように設計してもいいのですが、(ネットワーク屋からみると)分かりづくなるので、だいたい4096の倍数を使っていると思います。 ↩︎

Discussion