スパニングツリープロトコルのインタラクティブツアー
An interactive tour of the spanning tree protocol (vincent.bernat.ch)
要約
この記事は、ネットワークループを防ぐために設計されたスパニングツリープロトコル(STP)の進化形であるRSTP(Rapid Spanning Tree Protocol)について、インタラクティブなデモンストレーションを交えながら解説します。ネットワークの基本的な仕組みから、ルートブリッジの選出、ポートの役割割り当て、トポロジー変更通知、そしてRSTPが現代のネットワークでなぜ重要なのかまでを網羅しています。Radia PerlmanによるSTPの詩的な説明も紹介されています。
全文翻訳
スパニングツリープロトコル(STP)のインタラクティブツアー
注意
この記事にはインタラクティブな例が含まれています。それらを視覚化して操作するには、JavaScriptを有効にする必要があります。
3日間のイベントのためにオフィススペースを借りていると想像してください。数台のEthernetスイッチを素早く設置し、床にケーブルをテープで固定して、全員がオンラインになれるようにします。残念ながら、コーヒーを取りに行くたびに、最も不器用な同僚のスタンがケーブルを抜いてしまいます。予備のケーブルを追加することもできますが、そうするとブロードキャストストームが発生します。Ethernetパケットがループして増殖し、何も通らなくなるのです。
そこで登場するのがスパニングツリープロトコル(STP)です。STPは、余分なケーブルのうち、ループのないツリー構造を残すのに十分な本数だけをブロックします。スタンが再びケーブルを抜いても、STPは1秒以内にツリーを再構築し、あなたの1人サポート担当であるブロビーがケーブルを再接続する時間を与えます。1 以下のダイアグラムは、ブラウザで実際のSTP実装を実行しているので、ぜひご自身で確認してみてください!
:demo
A1 @0,0 prio=4096
A2 @0,1
A3 @0,2
A4 @0,3
B1 @1,0 prio=8192
B2 @1,1
B3 @1,2
B4 @1,3
C1 @2,0 prio=8192
C2 @2,1
C3 @2,2
C4 @2,3
A1 -- A2 hazard=0
A2 -- A3 hazard=0
A3 -- A4 hazard=0
B1 -- B2
B2 -- B3
B3 -- B4
C1 -- C2 hazard=0
C2 -- C3 hazard=0
C3 -- C4 hazard=0
A1 -- B1 cost=10
B1 -- C1 cost=10
A4 -- B4 cost=20
B4 -- C4 cost=20
Leo @-0.3,0.7 proto=none icon=👦🏻
Mia @-0.3,1.3 proto=none icon=👧🏽
Joy @0.3,0.7 proto=none icon=👱🏻♀️
Roy @0.3,1.3 proto=none icon=👨🏾
A2 -- Leo hazard=0 A2:edge
A2 -- Mia hazard=0 A2:edge
A2 -- Joy hazard=0 A2:edge
A2 -- Roy hazard=0 A2:edge
Max @-0.3,1.7 proto=none icon=👨🏽
Zoe @-0.3,2.3 proto=none icon=👩🏾
Ada @0.3,1.7 proto=none icon=👵🏾
Amy @0.3,2.3 proto=none icon=👩🏼
A3 -- Max hazard=0 A3:edge
A3 -- Zoe hazard=0 A3:edge
A3 -- Ada hazard=0 A3:edge
A3 -- Amy hazard=0 A3:edge
Eli @0.7,0.7 proto=none icon=👦🏼
Jay @0.7,1.3 proto=none icon=👨🏻
Kai @1.3,0.7 proto=none icon=🧑🏽
Ben @1.3,1.3 proto=none icon=👱🏼
B2 -- Eli hazard=0.2 B2:edge
B2 -- Jay hazard=0.2 B2:edge
B2 -- Kai hazard=0.2 B2:edge
B2 -- Ben hazard=0.2 B2:edge
Ava @0.7,1.7 proto=none icon=👩🏻
Lea @0.7,2.3 proto=none icon=🧑🏾🦱
Ivy @1.3,1.7 proto=none icon=🧕🏽
Rex @1.3,2.3 proto=none icon=👴🏿
B3 -- Ava hazard=0.2 B3:edge
B3 -- Lea hazard=0.2 B3:edge
B3 -- Ivy hazard=0.2 B3:edge
B3 -- Rex hazard=0.2 B3:edge
Ana @1.7,0.7 proto=none icon=👩🏿
Eve @1.7,1.3 proto=none icon=👧🏼
Abe @2.3,0.7 proto=none icon=🧓🏿
Ian @2.3,1.3 proto=none icon=🧔🏾
C2 -- Ana hazard=0 C2:edge
C2 -- Eve hazard=0 C2:edge
C2 -- Abe hazard=0 C2:edge
C2 -- Ian hazard=0 C2:edge
Ned @1.7,1.7 proto=none icon=👨🏼🦳
Lou @1.7,2.3 proto=none icon=🧑🏿
Fay @2.3,1.7 proto=none icon=👧🏻
Sue @2.3,2.3 proto=none icon=👩🏽🦰
C3 -- Ned hazard=0 C3:edge
C3 -- Lou hazard=0 C3:edge
C3 -- Fay hazard=0 C3:edge
C3 -- Sue hazard=0 C3:edge
注記
この記事はビデオとしても利用可能ですが、インタラクティブなデモンストレーションを試すために、ここでは読み進めることをお勧めします。
基本#
80年代に設計されたスパニングツリープロトコルは、「ラピッド」バージョン(RSTP)と「VLAN対応」バリエーション(MSTP)へと進化しました。2 健全なネットワークエンジニアなら誰でも、BGP EVPN VXLANのようなより良い代替手段があることを知っています。しかし、どのスイッチもこれを話すため、古くからあるスパニングツリープロトコルは依然としてニッチな分野を埋めています。
ここではRSTPに焦点を当てます。これは2004年に元のプロトコルに取って代わりました。ネットワークループを排除するために、RSTPは複雑なステートマシンを実装しています。タイマー、リンク状態の変更、そしてブリッジがネイバーから受信するリンクローカル制御フレームが、その遷移を駆動します。これらのEthernetフレームはBridge Protocol Data Units(BPDU)です。以下のデモで、それらが動作している様子を観察してください。「開始」ボタンをクリックしてください。
:protocol rstp
:tx-hold 10
A1 @0,1
C11 @1,0 prio=4096 icon=🌳
C12 @1,2 prio=4096 icon=🌳
C21 @2,0 prio=4096 icon=🌳
C22 @2,2 prio=4096 icon=🌳
A2 @3,1
H1 @0,0.2 proto=none icon=💻
H2 @0,1.8 proto=none icon=🖨️
H3 @3,0.2 proto=none icon=📠
H4 @3,1.8 proto=none icon=📺
A1 -- C11
A1 -- C12
A2 -- C21
A2 -- C22
C11 -- C12
C11 -- C21
C11 -- C21
C11 -- C22
C12 -- C21
C12 -- C22
C21 -- C22
A1 -- H1 A1:edge
A1 -- H2 A1:edge
A2 -- H3 A2:edge
A2 -- H4 A2:edge
しばらくすると, トポロジーはツリーに収束します。ルートC11から、各ブリッジ3へのパスがあり、ループはありません。右上隅には、ツリーアイコン🌳と、この状態に達するまでにかかった時間が表示されます。リンクを切断して、プロトコルが1秒未満で代替パスを見つける様子を確認してください。シミュレーションを停止したり、ステップごとに進めたり、初期状態にリセットしたり、または「カタツムリ」モード🐌で遅くしたりできます。表示されているすべての情報について心配しないでください。後で説明します。
注記
スクロールしても、現在のシミュレーションは表示されたままなので、読みながら確認できます。この動作を無効にするには、このチェックボックスをオフにしてください。
すべての例はブラウザで実行され、MSTPD — RSTPのオープンソースのユーザー空間4実装 — によって駆動されています。5
歴史的間奏曲#
2014年にインターネット殿堂入りを果たしたRadia Perlmanは、DECで彼女が発明したSTPの祖先を、後に米国特許に収録されたこの詩で要約しました。
私が思うに、私は決して見ないだろう
木よりも美しいグラフを。
その重要な特性である木
ループフリーの接続性。
全てのLANにパケットが届くように
スパンしなければならない木。
まず、ルートを選ばなければならない。
IDによって、それは選出される。
ルートからの最小コストパスがたどられる。
ツリーでは、これらのパスが配置される。
私のような人々によってメッシュが作られる、
そしてブリッジはスパニングツリーを見つける。― Radia Perlman, Algorhyme。
ルートブリッジの選出#
ツリーを構築するために、RSTPはまず最も低いブリッジ識別子を持つブリッジをルートブリッジとして選出します。ブリッジ識別子は、優先度とMACアドレスを組み合わせたものです:8192.6e:2b:10:a0:5f:29。
以下の例では、S1とS2はそれぞれ4,096と8,192の優先度を持っています。S1がルートになります。S4は12,288の優先度を持ち、S3はデフォルトの優先度32,768を維持します6。S4がルートになります。S5とS6は特定の優先度を持たないため、最も低いMACアドレスを持つものが勝ち、S5がルートになります。
:protocol rstp
S1 @0,0 prio=4096
S2 @0,1 prio=8192
S1 -- S2
S3 @1,0
S4 @1,1 prio=12288
S3 -- S4
S5 @2,0
S6 @2,1
S5 -- S6
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
Root Identifier: 8192.02:00:00:01:00:01
Bridge Identifier: 8192.02:00:00:01:00:01
ブリッジがより良いルートブリッジを広告するBPDUを受信すると、その新しい情報をネイバーに伝播します。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
Root Identifier: 4096.02:00:00:00:00:00
Bridge Identifier: 8192.02:00:00:00:00:01
ポートへの役割の割り当て#
第二段階は、各ポートに役割を割り当てることです。RSTPは5つの役割を定義しており、それぞれが文字で表されます。
- ルート(R)、
- 指定(D)、
- 代替(A)、
- 無効(X)、または
- バックアップ(B)。8
各非ルートブリッジは、ルートへの最小コストパスを持つポートをルートポートとして選択します。明示的に設定しない限り、各ブリッジはリンクコストを速度から導き出します。1 Gbpsの場合は20,000です。同率の場合、最も低いポート識別子が勝ちます。
残りのポートは、送信するBPDUが受信するBPDUよりも「優れている」場合、指定ポートになります。そうでなければ、代替ポートになります。後で、ルートポートがダウンした場合、「最良の」代替ポートが新しいルートポートになります。最良のBPDUのタイブレーカーは以下の通りです。
- 最も低いルートブリッジ識別子、
- ルートへの最も低い累積コスト、
- 最も低いブリッジ識別子、および
- 最も低いポート識別子。
:protocol rstp
S1 @1,0 prio=4096 icon=🌳
S2 @0,1
S3 @2,1
S1 -- S2
S1 -- S3
S1 -- S3
S2 -- S3
上記の例では、収束後, S1は優先度が4,096であり、他のブリッジは優先度が32,768であるため、ルートブリッジです。ルートへの累積コストが0であるため、すべてのポートが指定ポートになります。
S2のS1に面するポートは、ルートへの累積コストが20,000対40,000と最も低いため、ルートポートになります。S3はS1に面する2つのポートを持っていますが、ポート識別子が最も低いものがルートポートになります(0x8000対0x8001)。もう一方の候補は、リモートポートが累積コスト0のより良いBPDUを送信するため、代替ポートになります。S2とS3間のセグメントでは、S2のポートが勝ちます。両方のブリッジがルートへの累積コスト(20,000)は同じですが、S2のブリッジ識別子の方が小さいです(32768.02:00:00:00:00:01対32768.02:00:00:00:00:02)。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
Root Identifier: 4096.02:00:00:00:00:00
Root Path Cost: 20000
Bridge Identifier: 32768.02:00:00:00:00:01
Port identifier: 0x8002
S1とS3間のアクティブリンクを切断すると、S3は「最良の」代替ポートをルートポートに昇格させます。セカンドリンクも無効にすると, S3は残りの代替ポートをルートポートとして選択します。しかし、S1とS2間のリンクを無効にすると, S2は代替ポートを持っていないため、新しいルートポートを選出するのに少し時間がかかります。
特定のイベントが発生しない限り、指定ポートは2秒ごとにBPDUを送信します9。ブリッジが3回の連続したhello期間中にネイバーからBPDUを受信しない場合、そのネイバーは死んだとみなし、ポート情報を削除します。
ポート状態遷移#
各ポートは3つの状態のいずれかを持つことができます。ダイアグラムは、各状態の背景色を表示します。
- 破棄(赤)、
- 学習(黄)、または
- 転送(緑)。
ルートポートは自動的に転送状態に遷移します。代替ポートは破棄状態のままです。指定ポートは、破棄状態から転送状態に遷移するために2つの選択肢があります。
- ポートがエッジポートである場合(設定によるか、リモートデバイスがいかなるSTPフレーバーも話さないため)、ブリッジはそのデバイスがプロトコルに参加せず、ループを作成できないと仮定します。この場合、指定ポートは直ちに転送状態に遷移します。
- それ以外の場合、ダウンストリームネイバーに提案を送信します。リモートブリッジが受信したBPDUが、他のポートに保存されているBPDUよりも「優れている」と同意した場合、受信ポートをルートポートとして選択し、同期プロセスを開始します。これにより、ループを回避するために、すべての非エッジ非同期指定ポートを破棄状態に遷移させます。その後、合意を返信します。合意を受信すると、ピア指定ポートは転送状態に遷移します10。
:protocol rstp
S1 @1,0 prio=4096 icon=🌳
S2 @1,1
S3 @0,2
S4 @2,2
S5 @0,3 prio=8192 icon=
S6 @2,3
H1 @0,1.2 proto=none icon=🖨️
H2 @2,1.2 proto=none icon=📠
H3 @2.5,1.3 proto=none icon=📺
H4 @2.5,2.3 proto=none icon=💻
S1 -- S2
S2 -- S3
S2 -- S4
S3 -- S5
S4 -- S6
S4 -- S3
S5 -- S6
S3 -- H1 S3:edge
S4 -- H2 S4:edge
S4 -- H3 S4:edge
S6 -- H4 S6:edge
上記のトポロジーでは、H1、H2、H3、H4はプロトコルに参加しないエンドデバイスです。それらに接続するポートをエッジポートとして設定するため、これらのポートは直ちに転送状態に移行します。
「ステップ」ボタンを使用してシミュレーションを進めます。時計が1秒進みます。再度ステップすると、S1とS2がお互いに提案を送信します。S2からの提案は次のとおりです。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
BPDU flags: 0x4e, Agreement, Port Role: Designated, Proposal
0... .... = Topology Change Acknowledgment: No
.1.. .... = Agreement: Yes
..0. .... = Forwarding: No
...0 .... = Learning: No
.... 11.. = Port Role: Designated (3)
.... ..1. = Proposal: Yes
.... ...0 = Topology Change: No
Root Identifier: 32768.02:00:00:00:00:01
Root Path Cost: 0
Bridge Identifier: 32768.02:00:00:00:00:01
Port identifier: 0x8001
S1はこれを無視します。自身のルート識別子がより低いためです。S2がS1から同様の提案を受信すると、S1をルートブリッジとして受け入れます。また、S1へのポートをルートポートとして選択し、同期プロセスを開始します。2つの指定ポートはすでに破棄状態なので、ここでは変更はありません。再度ステップすると、S2はS1に2つのBPDUを送信します。そのうちの1つでは、合意ビットが1で提案ビットが0です。また、S2がS1をルートブリッジとして受け入れ、ルートポートが転送状態になったことも示しています。このBPDUを受信すると、S1は自身の指定ポートを転送状態に遷移させます。この時点から、S1とS2間のリンクはユーザーデータを転送します。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
BPDU flags: 0x79, Agreement, Forwarding, Learning, Port Role: Root, Topology Change
0... .... = Topology Change Acknowledgment: No
.1.. .... = Agreement: Yes
..1. .... = Forwarding: Yes
...1 .... = Learning: Yes
.... 10.. = Port Role: Root (2)
.... ..0. = Proposal: No
.... ...1 = Topology Change: Yes
Root Identifier: 4096.02:00:00:00:00:00
Root Path Cost: 20000
Bridge Identifier: 32768.02:00:00:00:00:01
Port identifier: 0x8001
S5に何が起こったか見てみましょう。シミュレーションをリセットして2回ステップします。S5はS3とS6の両方とBPDUを交換します。S5はS3とS6よりも低いルート識別子を持っているため、ルートブリッジのままですが、S3とS6は提案を受け入れ、ルートポートを選択します。S3とS6は同期プロセスを開始します。H4へのS6のポートはエッジポートであるため、転送状態のままです。1ステップ進めます。S3とS6の両方がS5に合意を送信し、S5は両方の指定ポートを転送状態に遷移させます。しかし、S5とS3間のリンクはユーザーデータの転送を破棄し続けます!よく見ると、S3のS5へのポートは、ルートポートではなく指定ポートになっています。同じステップで、S3はS2から、S1をルートブリッジとするより良いBPDUを受信します。S2へのポートをルートポートとして選択し、S5へのポートを指定ポートにダウングレードします。これは破棄状態のままです。
次のステップで、状況は少し複雑になります。S3はS5に提案を送信します11:
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
BPDU flags: 0x4f, Agreement, Port Role: Designated, Proposal, Topology Change
0... .... = Topology Change Acknowledgment: No
.1.. .... = Agreement: Yes
..0. .... = Forwarding: No
...0 .... = Learning: No
.... 11.. = Port Role: Designated (3)
.... ..1. = Proposal: Yes
.... ...1 = Topology Change: Yes
Root Identifier: 4096.02:00:00:00:00:00
Root Path Cost: 40000
Bridge Identifier: 32768.02:00:00:00:00:02
Port identifier: 0x8002
S5はS1をルートブリッジとして選択し、S3へのポートをルートポートとして選択します。同期プロセスを開始しますが、S6への指定ポートは破棄状態に移行しません。なぜでしょうか?そのポートは指定ポートのままで、ネイバーのS6はすでにリンク上で合意を送信しているため、ポートは同期状態を維持します。
ここで、S6に何が起こったかを見るためにステップバックします。この時点で、S6はS5をルートブリッジだと考えています。1ステップ進めますと、S4がS6に新しい提案を送信します。S6は提案を受け入れ、S1をルートブリッジとして選択し、S4へのポートをルートポートとして選択します。S5に面するポートの役割が変更されます。ルートポートから指定ポートになります。ピアがリンク上で劣ったBPDUを広告し続けるため、このポートは論争状態になり、破棄状態に移行します。ルートポートは転送状態に遷移し、S4の指定ポートがすでに転送状態にあるため、リンクは直ちに転送を開始します。さらに1ステップ進めると, S5とS6は2つのBPDUを交換します。S5からのBPDUは、ブリッジ識別子が低いため、より優れています。S5のポートは指定ポートのままですが、S6は自身のポートを代替ポートにダウングレードします。
最後に、最初から巻き戻して、S1とS2間のリンクを切断し、トポロジーが安定するまでシミュレーションを実行し、シミュレーションを停止して、S1とS2間のリンクを復元します。最初のステップで、S1とS2は提案を交換します。S2はS5の代わりにS1をルートブリッジとして選択し、S1へのポートをルートポートとして選択します。以前のルートポートを指定ポートにダウングレードし、破棄状態に移行させます。もう一方の指定ポートは同期状態を維持し、転送状態を保ちます。次のステップで、S2はS1に合意を送信し、両者のリンクはユーザーデータの転送を開始します。また、S3に提案を送信しますが、S4には送信しません。代わりに、S4に通常のBPDUを送信します。S4は依然としてS1をルートブリッジとして選択し、S2へのポートをルートポートとして選択します。S3へのポート(以前のルートポート)を指定ポートにダウングレードし、ルートポートの変更により破棄状態に遷移させます。もう一方の代替ポート(S6へ)も指定ポートになり、破棄状態のままです。次のステップステップで、S4のS3へのポートは、S3から「より良い」BPDUを受信した後、代替ポートとして落ち着きます。
RSTPは、ブリッジ検出、ポート情報、ポートプロトコル移行、ポートロール選択、ポートロール遷移、ポート受信、ポート状態遷移、ポートタイマー、ポート送信、トポロジー変更など、小さなステートマシンに分割された巨大なステートマシンです。それらのいくつかはブリッジごと、いくつかはポートごとです。各ブリッジはインスタンスを実行します。時間、運用ポート状態の変更、および他のインスタンスから受信するBPDUが遷移を駆動します。イベント駆動型であるためRSTPはより効率的ですが、理解するのはより困難です。
[画像: Western Australian Government Railways class Msa Garratt articulated steam locomotive: elevation and plan drawing]
IEEE 802.1Q-2005、182ページから抽出されたポート情報ステートマシンのプレースホルダー。再版のIEEE承認待ちのため、これはWestern Australian Government Railways class Msa Garratt articulated steam locomotiveの設計図です。
トポロジー変更通知#
ブリッジはMACアドレステーブルを構築します。各ソースMACアドレスを、最後に受信したポートに関連付けます。Ethernetフレームを転送する際、このテーブルを参照して正しいポートを選択します12。リンクが失敗すると、あるポートを通じて到達可能だった接続された冷蔵庫が、別のポートを通じて到達可能になる場合があります。影響を受けたブリッジは、これらのエントリが間違っている可能性があるため、学習したMACアドレスをフラッシュする必要があります。
この目的のために、RSTPはフラッディングメカニズムを使用したトポロジー変更通知を実装しています。非エッジポートが転送状態に遷移すると、ブリッジはトポロジー変更(TC)ビットがセットされたBPDUを生成します。これをすべての非エッジ指定ポートとルートポートに送信します。また、これらのポートのMACアドレステーブルをフラッシュします。ブリッジがこのようなBPDUを受信すると、通知をすべての非エッジ指定ポートとルートポート(通知元を除く)に伝播します。また、これらのポートのMACアドレステーブルをフラッシュします。例では、TCビットが1にセットされたBPDUは赤い円で示されています。
:protocol rstp
S1 @1,0 prio=4096 icon=🌳
S2 @0,1
S3 @1,1
S4 @2,1
S5 @1,2
LPT @0.1,2 proto=none icon=🖨️
S1 -- S2
S1 -- S3
S1 -- S4
S2 -- S3
S2 -- S5
S4 -- S5
S5 -- LPT S5:edge
シミュレーションを開始し、数秒間待ってトポロジーが安定するのを待ちます。シミュレーションを停止し、S2とS5間のリンクを無効にします。S5はS4に面するポートをルートポートとして選択し、これは直ちに転送状態に遷移します。1ステップ進めますと、S5はTCビットが1にセットされたBPDUを発行します。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
BPDU flags: 0x79, Agreement, Forwarding, Learning, Port Role: Root, Topology Change
0... .... = Topology Change Acknowledgment: No
.1.. .... = Agreement: Yes
..1. .... = Forwarding: Yes
...1 .... = Learning: Yes
.... 10.. = Port Role: Root (2)
.... ..0. = Proposal: No
.... ...1 = Topology Change: Yes
Root Identifier: 4096.02:00:00:00:00:00
Root Path Cost: 40000
Bridge Identifier: 32768.02:00:00:00:00:04
Port identifier: 0x8002
S4はこのBPDUを受信します。S1に面するポートのMACアドレステーブルをフラッシュします。以前はLPTがこのポートを通じて到達可能でしたが、現在はS5を通じて到達可能です。1ステップ進めます。S4はTCビットが1にセットされたBPDUをS1に送信します。S1がこのBPDUを受信すると、S2とS3に面するポートのMACアドレステーブルをフラッシュします。1ステップ進めますと、S1はS2とS3に通知を送信します。さらに1ステップ進めますと、S2はS3に通知を送信しますが、S3はS2へのポートが代替ポートであるため何も行いません。LPTは依然としてS1へのポートを通じて到達可能であるため、S3はMACアドレステーブルをフラッシュしません。
さらにステップを進めると、定期的なBPDUの一部がTCビットを1のままにしていることがわかります。各ポートはhelloタイマープラス1秒のタイマーを実行します13。タイマーはポートが通知を送信したときに開始されます。それが期限切れになるまで、ポートは送信するすべてのBPDUでTCビットを1に設定します。TCビットのない定期的なBPDUもいくつか見られます。これらは通知を受信しただけでタイマーをアームしなかったポートから発信されたものです。
セキュリティ#
RSTPは、設定ミスや悪意のある攻撃者に対して脆弱です。RSTPを話さないブリッジはループを作成する可能性があります。攻撃者は、サービスを妨害したり、トラフィックをスパイしたり、変更したりするために、トポロジーに自身を挿入することができます。
これらの問題を軽減するために、エッジポートを特定する必要があります。エッジポートは、PCやプリンターなどのエンドデバイスに接続されます。このようなデバイスはBPDUを生成せず、ループを作成できません。RSTPは、2つの関連フラグを定義しています。
- trueの場合、AdminEdgeはポートをエッジポートとして初期化します。デフォルトはfalseです。
- trueの場合、AutoEdgeは、3秒間BPDUを受信しない場合にポートをエッジポートにすることができます。デフォルトはtrueです。
エッジポートがBPDUを受信した場合、これらのフラグの値に関係なく、非エッジポートに戻ります。
R0 @1.5,1.5 prio=8192
# AutoEdge=true, AdminEdge=false, bridge
S1 @3,1.58
R0 -- S1
# AutoEdge=true, AdminEdge=false, end device
H1 @2.84,2.18 icon=🖨️ proto=none
R0 -- H1
# AutoEdge=true, AdminEdge=true, bridge
S2 @2.18,2.84
R0 -- S2 R0:edge
# AutoEdge=true, AdminEdge=true, end device
H2 @1.58,3 icon=💻 proto=none
R0 -- H2 R0:edge
# AutoEdge=false, AdminEdge=true, bridge
S3 @0.68,2.76
R0 -- S3 R0:edge R0:no-auto-edge
# AutoEdge=false, AdminEdge=true, end device
H3 @0.24,2.32 icon=📠 proto=none
R0 -- H3 R0:edge R0:no-auto-edge
# AutoEdge=false, AdminEdge=false, bridge
S4 @0,1.42
R0 -- S4 R0:no-auto-edge
# AutoEdge=false, AdminEdge=false, end device
H4 @0.16,0.82 icon=📺 proto=none
R0 -- H4 R0:no-auto-edge
# Network port, bridge
S5 @0.82,0.16
R0 -- S5 R0:network S5:network
# Network port, end device
H5 @1.42,0 icon=☕ proto=none
R0 -- H5 R0:network
# AdminEdge=true, bpdu-guard=true, bridge
S6 @2.32,0.24
R0 -- S6 R0:bpdu-guard R0:edge
# AdminEdge=true, bpdu-guard=true, end device
H6 @2.76,0.68 icon=💡 proto=none
R0 -- H6 R0:bpdu-guard R0:edge
上記のトポロジーでは、S1、S2、S3、S4、S5、S6はブリッジとして機能し、H1、H2、H3、H4、H5、H6はエンドデバイスとして機能します。
- S1とH1は、特定の構成がないポートに接続されています:AutoEdgeはtrue、AdminEdgeはfalseです。
- S2とH2は、AdminEdgeがtrueのポートに接続されています。
- S3とH3は、AutoEdgeがfalseでAdminEdgeがtrueのポートに接続されています。
- S4とH4は、AutoEdgeがfalseのポートに接続されています。
シミュレーションを開始し、約20秒待つと、S1、S2、S3、S4、H1、H2、H3、H4へのリンクは最終的にユーザーデータを転送します。フラグはどれも関係ありません。
しかし、残りの2つのペアはどうでしょうか?S5とH5はネットワークポートに接続されています。このようなポートは、非標準機能であるブリッジアシュアランスを有効にします。ポートは、その役割に関係なくBPDUを送信します。3回の連続したhello期間中にBPDUを受信しない場合、破棄状態に遷移します。R0とS5間のリンクでは、他のリンクとは異なり、指定ポートのみがBPDUを送信するのに対し、両方向のBPDUが移動しているのが見えます。
S6とH6は、AdminEdgeがtrueでBPDUガードが有効になっているポートに接続されています。これは、BPDUを受信した場合にポートをシャットダウンする別の非標準機能です。
要約すると、ポートがエッジポートであることを期待する場合は、AdminEdgeをtrueに設定し、BPDUガードを有効にする必要があります。それ以外の場合は、ネットワークポートとして宣言してください。
今日のRSTPの必要性?#
今日RSTPの説得力のあるユースケースは、データセンターのアウトオブバンドネットワークです。数秒間の停止は許容できるためです。設定は最小限で済み、Cisco 2960Xのような安価なスイッチを使用できます14。ルートブリッジとして機能する2つのスイッチが必要で、各キャビネット内のOOBスイッチを接続するためにいくつかのループを構築します。このシンプルな設計は、各ループでの1回の障害に耐えます15。
:protocol rstp
:tx-hold 10
# Root bridges
R1 @0,1 prio=0
R2 @0,2 prio=4096
R1 -- R2 cost=200 R1:network R2:network
R1 -- R2 cost=200 R1:network R2:network
# First loop
C1 @1,0 icon=🗄️
C4 @2,0 icon=🗄️
C7 @3,0 icon=🗄️
C10 @4,0 icon=🗄️
C12 @5,0 icon=🗄️
C13 @5,3 icon=🗄️
C15 @4,3 icon=🗄️
C18 @3,3 icon=🗄️
C21 @2,3 icon=🗄️
C24 @1,3 icon=🗄️
R1 -- C1 R1:network C1:network
C1 -- C4 C1:network C4:network
C4 -- C7 C4:network C7:network
C7 -- C10 C7:network C10:network
C10 -- C12 C10:network C12:network
C12 -- C13 C12:network C13:network
C13 -- C15 C13:network C15:network
C15 -- C18 C15:network C18:network
C18 -- C21 C18:network C21:network
C21 -- C24 C21:network C24:network
C24 -- R2 C24:network R2:network
# Second loop
C2 @1,0.5 icon=🗄️
C5 @2,0.5 icon=🗄️
C8 @3,0.5 icon=🗄️
C11 @4,0.5 icon=🗄️
C14 @4,2.5 icon=🗄️
C17 @3,2.5 icon=🗄️
C20 @2,2.5 icon=🗄️
C23 @1,2.5 icon=🗄️
R1 -- C2 R1:network C2:network
C2 -- C5 C2:network C5:network
C5 -- C8 C5:network C8:network
C8 -- C11 C8:network C11:network
C11 -- C14 C11:network C14:network
C14 -- C17 C14:network C17:network
C17 -- C20 C17:network C20:network
C20 -- C23 C20:network C23:network
C23 -- R2 C23:network R2:network
# Third loop
C3 @1,1 icon=🗄️
C6 @2,1 icon=🗄️
C9 @3,1 icon=🗄️
C16 @3,2 icon=🗄️
C19 @2,2 icon=🗄️
C22 @1,2 icon=🗄️
R1 -- C3 R1:network C3:network
C3 -- C6 C3:network C6:network
C6 -- C9 C6:network C9:network
C9 -- C16 C9:network C16:network
C16 -- C19 C16:network C19:network
C19 -- C22 C19:network C22:network
C22 -- R2 C22:network R2:network
このトポロジーは約6秒で収束します。各ループは(二重障害の確率を減らし、帯域幅の共有を避けるために)約16ブリッジに保つべきです。設計は、あまり複雑さを追加せずに少し進化させることができます。ループごとに1つのVLAN、またはループごとに1つのブリッジドメインです。
ネットワークはどれくらいの大きさになれるか?#
デフォルト値が20である最大エイジ(maximum age)は、ブリッジからルートまでの最大距離を制御します。以下のトポロジーは、R1からのBPDUがS20を超えて到達するには大きすぎます16。
:protocol rstp
:tx-hold 10
:max-age 20
R1 @0,0 prio=4096 icon=🌳
R2 @0,5 prio=4096 icon=
S1 @1,0
S2 @2,0
S3 @3,0
S4 @4,0
S5 @5,0
S6 @6,0
S7 @6,1
S8 @5,1
S9 @4,1
S10 @3,1
S11 @2,1
S12 @1,1
S13 @1,2
S14 @2,2
S15 @3,2
S16 @4,2
S17 @5,2
S18 @6,2
S19 @6,3
S20 @5,3
S21 @4,3
S22 @3,3
S23 @2,3
S24 @1,3
S25 @1,4
S26 @2,4
S27 @3,4
S28 @4,4
S29 @5,4
S30 @6,4
S31 @6,5
S32 @5,5
S33 @4,5
S34 @3,5
S35 @2,5
S36 @1,5
R1 -- S1
S1 -- S2
S2 -- S3
S3 -- S4
S4 -- S5
S5 -- S6
S6 -- S7
S7 -- S8
S8 -- S9
S9 -- S10
S10 -- S11
S11 -- S12
S12 -- S13
S13 -- S14
S14 -- S15
S15 -- S16
S16 -- S17
S17 -- S18
S18 -- S19
S19 -- S20
S20 -- S21
S21 -- S22
S22 -- S23
S23 -- S24
S24 -- S25
S25 -- S26
S26 -- S27
S27 -- S28
S28 -- S29
S29 -- S30
S30 -- S31
S31 -- S32
S32 -- S33
S33 -- S34
S34 -- S35
S35 -- S36
S36 -- R2
R1 -- R2 cost=200 down
トポロジーが安定すると, ネットワークの一部はR1をルートと見なし、他の部分はR2をルートと見なします。境界では、S20がS21と同期を開始しようとして、その指定ポートを転送状態に移行させます。BPDUは次のようになります。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
BPDU flags: 0x4e, Agreement, Port Role: Designated, Proposal
Root Identifier: 4096.02:00:00:00:00:00
Root Path Cost: 400000
Bridge Identifier: 32768.02:00:00:00:00:15
Port identifier: 0x8002
Message Age: 20
Max Age: 20
メッセージエイジが最大エイジと等しいため、S21はこれを拒否します。一方、S21がS20に送信するBPDUは次のようになります。
Spanning Tree Protocol
Protocol Identifier: Spanning Tree Protocol (0x0000)
Protocol Version Identifier: Rapid Spanning Tree (2)
BPDU Type: Rapid/Multiple Spanning Tree (0x02)
BPDU flags: 0x7c, Agreement, Forwarding, Learning, Port Role: Designated
Root Identifier: 4096.02:00:00:00:00:01
Root Path Cost: 320000
Bridge Identifier: 32768.02:00:00:00:00:16
Port identifier: 0x8001
Message Age: 16
Max Age: 20
これはS20のルートポートを変更するには十分ではありません。なぜなら、S20はより低いルート識別子(4096.02:00:00:00:00:00対4096.02:00:00:00:00:01)を持っているからです。
R1とR2間のリンクを修正すると問題が解決します。パケットが運ぶ最大メッセージエイジは18になり、設定された最大エイジを下回ります。しかし、別のリンクが切断されるまでしか機能しません。もっともらしい修正策は、最大エイジを40に増やすことです17。
RSTPはどれくらい速いか?#
RSTPは通常、起動時に数秒で収束します。多くの場合、1秒未満でツリーを修復します。38ブリッジのトポロジーでさえ、収束に10秒未満しかかかりません18。ルートブリッジが利用できなくなった場合、一部のトポロジーは回復に少し時間がかかることがあります19。
:protocol rstp
R0 @1,0 prio=0
S1 @1,1 prio=4096
S2 @0,2 prio=8192
S3 @2,2
R0 -- S1
S1 -- S2
S2 -- S3
S3 -- S1
上記のトポロジーで、シミュレーションを開始し、収束を待って、停止ボタンを押し、R0とS1間のリンクを切断します。トポロジーはすでに最適ですが、RSTPは再び収束するのに苦労します。
まず、S1はルートポートを失います。R0に関する情報がなくなり、自身をルートブリッジとして選択します。S2とS3へのポートを転送状態の指定ポートとして維持します。1ステップ進めますと、ルート変更を知らせるために両方のS2とS3にBPDUを送信します。受信すると、S2は他のポートにこれより良いルートがないため、S1をルートとして受け入れます。S1へのポートをルートポートとして選択します。もう一方のポートは指定ポートのままです。両方のポートは転送を続けます。
S1からのBPDUを受信すると、S3は異なる動作をします。S2への代替ポートを通じて、S1よりも優れたルートであるR0を知っています。このポートをルートポートに昇格させ、S1に面するポートを指定ポートにダウングレードします。これには新しい合意が必要です。1ステップ進めますと、S3はR0をルートブリッジとする提案をS1に送信します。S1はR0をルートブリッジとして選択し、S3へのポートをルートポートに昇格させます。
同じステップで、S3はS2からS1をルートブリッジとするBPDUも受信します。したがって、S3にはR0をルートブリッジとするポートが残っていません。S1をルートブリッジとして選択し、S2へのポートをルートポートとして選択します。1ステップ進めますと、次のS1へのBPDUにはこの情報が含まれます。S1は再び自身をルートブリッジとして選択します。しかし、同じ波で、S1はR0をルートブリッジとする提案をS2に送信します。S1とS3はS1がルートブリッジであることに同意しますが、S2は現在R0だと信じています!順番に, S2は再びS3にR0がルートブリッジであると説得し、S3はS1に説得し、S1はS2に説得し、S2はS3に説得します。
これは永遠に続く可能性がありますが、そうはなりません。メッセージエイジが最大エイジを超えると、「R0がルートである」というBPDUは最終的に期限切れになります。上記の例では、11秒目で、S2はR0をルートとするBPDUをS3に送信しますが、メッセージエイジが最大値に達したためS3はこれをドロップします。運が良ければ、ポートが送信ホールドカウント(デフォルト値は毎秒6)を超えて新しいBPDUの送信を停止した場合、トポロジーはより速く収束することもできます。
MSTPについて#
MSTPはRSTPの「VLAN対応」バージョンです。複数のRSTPインスタンスを実行し、管理者が各VLANを特定のインスタンスにマッピングできるようにします。たとえば、VLAN 100から200を最初のインスタンスにマッピングし、300から400を2番目のインスタンスにマッピングできます。残りのVLANは、Internal Spanning Tree(IST)という特別なインスタンスにマッピングされます。MSTPは独自の複雑さを追加しますが、要点は、独立して動作する複数の論理トポロジーがあるということです。さらに深く掘り下げたい場合は、「MSTPチュートリアルパートI:リージョン内」を参照してください。
インタラクティブな例について#
インタラクティブな例は、WebAssemblyにコンパイルされ、emscriptenを使用してブラウザで直接MSTPDを実行します。C APIはLinuxカーネルと通信するコードを置き換え、ブリッジとポートを管理し、状態をJSONとしてエクスポートし、時間を決定論的に駆動します。JavaScriptラッパーが、よりユーザーフレンドリーにします。
import { loadMSTPD } from "./dist/mstpd.mjs";
const mstp = await loadMSTPD();
// Create 3 bridges
const a = mstp.createBridge("A", { priority: 4096 });
const b = mstp.createBridge("B", { priority: 8192 });
const c = mstp.createBridge("C");
// Each bridge has two ports
const a1 = a.addPort("a-b", { portno: 1 });
const a2 = a.addPort("a-c", { portno: 2 });
const b1 = b.addPort("b-a", { portno: 1 });
const b2 = b.addPort("b-c", { portno: 2 });
const c1 = c.addPort("c-a", { portno: 1 });
const c2 = c.addPort("c-b", { portno: 2 });
// Build a triangle topology
mstp.link(a1, b1);
mstp.link(a2, c1);
mstp.link(b2, c2);
// Enable all bridges and ports
for (const br of [a, b, c]) br.enable();
for (const p of [a1, a2, b1, b2, c1, c2]) p.enable();
// Execute 40 seconds' worth of wall clock and display the topology
mstp.step(40);
console.log("Topology:", mstp.topology());
数十個の単体テストがMSTPDの機能を調査し、この環境で正しく動作することを確認します。
$ node --test *.test.mjs
✔ two bridges: lower priority becomes root (41.657342ms)
✔ triangle loop: exactly one port blocks and all agree on the root (5.832ms)
✔ breaking the active link reconverges and restoring recovers (18.730753ms)
[…]
ℹ tests 40
ℹ pass 40
ℹ fail 0
[…]
ℹ duration_ms 396.190897
追加のJavaScriptコードは、トポロジー定義を含む特定の<pre>ブロックを探し、それらをインタラクティブなウィジェットに変換します。「編集」ボタンをクリックすると、定義を検査および変更できます。
トポロジーが収束したかどうかを判断するクールなトリックもあります。各ステップの後、シミュレーションメモリのスナップショットを保存し、シミュレーションを50秒間実行してトポロジーが安定しているか確認し、そのスナップショットを復元することで時間を巻き戻します。🕰️
完全なコードはGitHubにあります。結果には満足しています。単一のステップ中に起こっていることをすべて追跡するのは難しいかもしれませんが、前後にステップを進めることで役立ちます。将来のネットワーク機能に関するブログ記事でも同じアプローチを使用する予定です。
注記
Michael Lynchがこの記事の初稿をレビューしました。彼は、ブログ記事、ドキュメント、コミットメッセージ、チュートリアルのためのライティングをシャープにするための本「Refactoring English」の著者です。誤りはすべて私の責任です!
更新(2026-10)
この記事はHacker Newsで briefly 議論されました。