【実務・中級編】 スパニングツリープロトコル(STP:IEEE 802.1D)の基本動作 – インフラ・物理&L2/L3ネットワーク基礎実践ガイド

はじめに:なぜ現代のエンジニアもSTPの呪縛から逃れられないのか

Web APIの設計やモダンなクラウドインフラの構築に日々明け暮れている皆さん、こんにちは。アプリケーション層やコンテナオーケストレーションの抽象化された世界に生きていると、「物理やL2のレイヤーなんて、クラウドベンダーやデータセンターのプロバイダがよしなにやってくれているはずだ」と思いがちですよね。

しかし、ひとたびオンプレミスのサーバーラックと向き合ったり、ハイブリッドクラウドのエッジでトラブルシューティングを行ったりする時、あるいはAWSのDirect ConnectやVPC間の冗長構成で奇妙な通信断に遭遇した時、最後にあなたを救うのは、古臭く見えて実は極めてシビアに作り込まれたレイヤー2の知識です。特に、今日のテーマであるスパニングツリープロトコル(STP:IEEE 802.1D)は、ネットワークの「ループ」という致命的な病からL2セグメントを守る最後の砦です。

今回は、パケットがスイッチ間をどのように駆け巡り、どのようにして「あえて通信路を塞ぐ(ブロッキングする)」という苦渋の決断を下しているのか、現場のシニアエンジニアの視点から泥臭く、かつ深く解説していきます。

—

1. イーサネットの原罪:なぜL2ループはネットワークを殺すのか

IPパケットを運ぶルータの世界には、TTL(Time to Live)という素晴らしい寿命の概念があります。しかし、イーサネットのフレーム(L2)には、そんな気の利いたフィールドはありません。もしスイッチとスイッチを冗長化のために二重に接続し、STPのようなループ防止機構を有効にしなかった場合、何が起こるでしょうか。

1. ブロードキャストストーム(Broadcast Storm)
ARPリクエストなどのブロードキャストフレームがスイッチ間を無限に回り始めます。リンク帯域は一瞬で100%に張り付き、スイッチのCPUは限界を超え、他の正当なトラフィックが一切処理できなくなります。
2. MACアドレステーブルのフラッピング(MAC Table Flapping)
同じMACアドレスが、ポートAからもポートBからも見えている状態になり、スイッチのMACアドレステーブルが激しく書き換わります。結果として、ユニキャスト通信すらまともにルーティングできなくなります。

これを防ぐ唯一の機構がSTPです。物理的な冗長性を保ちつつ、論理的なループを断ち切るために、STPはネットワーク全体を1本の木構造(Spanning Tree)に変形させます。

—

2. STPの選出アルゴリズム:王様と部下を決める厳格なルール

STPが動くセグメント(ブリッジドメイン)では、すべてのスイッチ(ブリッジ)が協力して、以下の3つのステップで「どのポートを生かし、どのポートを塞ぐか」を決定します。

ステップ1:ルートブリッジ(Root Bridge)の選出

ネットワーク全体の「頭脳」となるボスを1台決めます。

  • 判定基準: ブリッジID(Bridge ID)が最も小さいスイッチ。
  • ブリッジIDの構成: 2バイトの「プライオリティ(通常デフォルトは32768)」+ 6バイトの「MACアドレス」。
  • 現場のTips: ネットワークのコアとなるスイッチのプライオリティを手動で 4096 や 8192 などに小さく設定し、意図したスイッチを確実にルートブリッジに固定するのがインフラ運用の鉄則です。これを怠ると、一番安価で古いスイッチが偶然ルートブリッジになり、トラフィックが変な経路を流れる原因になります。

ステップ2:ルートポート(Root Port)の選出

ルートブリッジ以外のすべてのスイッチ(非ルートブリッジ)は、ルートブリッジへの「最短パス」を1つだけ選びます。その最短パスが接続されている自スイッチのポートがルートポート(RP)になります。

  • 判定基準:

1. ルートパスコスト(Path Cost)が最小のポート。リンクの帯域幅(1Gbpsなら4、10Gbpsなら2など)に基づいて累積されます。
2. 隣接する上流スイッチのブリッジIDが小さいこと。
3. 隣接する上流スイッチのポートIDが小さいこと。

ステップ3:指定ポート(Designated Port)とブロッキングポートの選出

セグメント(スイッチ間を結ぶセグメント)ごとに、ルートブリッジへ向かうトラフィックの「出口」となる指定ポート(DP)を1つ選びます。

  • ルートブリッジ上のすべてのポートは、常に「指定ポート」になります。
  • 2台のスイッチ間のセグメントにおいて、ルートパスコストが低い方のポートが指定ポートになり、敗北した側のポートが非指定ポート(Non-Designated Port / ブロッキング状態)になります。

—

3. ステートの遷移と通信フロー:ポートが立ち上がるまでのドラマ

スイッチの電源を入れた直後から、ポートがトラフィックを転送できるようになるまでには、いくつかの状態(State)を経由します。オリジナルのIEEE 802.1Dでは、以下のステップを踏みます。

[Disabled / Blocking] 
       ↓ (20秒: Max Age)
   [Listening] 
       ↓ (15秒: Forward Delay)
   [Learning]  
       ↓ (15秒: Forward Delay)
   [Forwarding]

1. Blocking(ブロッキング): データフレームの送受信は行わず、BPDU(Bridge Protocol Data Unit)の受信のみを行います。ループを防ぐための待機状態です。
2. Listening(リスニング): ルートブリッジやトポロジを決定するため、BPDUを送受信し始めますが、まだMACアドレスの学習は行いません。
3. Learning(ラーニング): トポロジが安定してきたと判断し、フレームの送信元MACアドレスをMACアドレステーブルに学習し始めますが、まだデータフレームの転送は行いません。
4. Forwarding(フォワーディング): 通常のデータフレームの送受信(転送)が完全に許可された状態です。

> 実務での教訓(タイマーのジレンマ)
>
> 従来の802.1Dでは、ポートがBlockingからForwardingに移行するまでに合計で約30秒〜50秒かかります。「サーバーを接続したのに、リンクアップしても数テンポ通信できない!」という現場の焦りの原因の多くは、このSTPの収束(Convergence)待ちにあります。この課題を解決するために現代ではRSTP(Rapid Spanning Tree Protocol: IEEE 802.1w)が標準となっており、数秒で収束します。しかし、ベースとなるロジックは802.1Dと変わりません。

—

4. 実設定とデバッグ:Cisco IOSとLinux環境での実例

理論を学んだところで、実際の機器やシミュレーション環境でどのように設定・確認するかを見ていきましょう。

シスコ(Cisco IOS)スイッチでの設定例

エンタープライズの現場で最も一般的なCisco Catalystスイッチにおける、STP(ここではRSTP/PVST+を前提とした基本設定)の構成例です。

! 1. ルートブリッジを確実にこのスイッチにするためのプライオリティ変更
spanning-tree vlan 1 priority 4096

! 2. サーバーや端末が接続されるエッジポートに対して、STPのリスニング/ラーニング待ちをバイパスする機能(PortFast)を有効化
interface GigabitEthernet0/1
 description Connected to Web Server API Node 01
 switchport mode access
 spanning-tree portfast
 spanning-tree bpduguard enable
! ↑ サーバー側から不審なBPDU(スイッチの偽装など)を受信した際、ポートを即座にシャットダウンしてネットワークを守る重要設定です

Linuxブリッジ(L2仮想ネットワーク)でのSTP確認

Dockerのブリッジネットワークや、KVMなどの仮想化基盤のLinuxホスト上でも、内部的にSTPが動作しているケースがあります。現在のアクティブな状態をコマンドで確認してみましょう。

# インストールされているブリッジユーティリティを使って確認
$ brctl show

# 実行結果のイメージ
bridge name     bridge id           STP enabled     interfaces
br-api-net      8000.0242ac120002   yes             veth1a2b3c4
                                                    veth4d5e6f7

もしPythonやスクリプトからネットワークの状態を監視・制御するようなツールを自作する場合、直接L2フレームを叩くことは稀ですが、NetconfやRESTCONF、あるいはSSH経由でCLIに出力されるステータスをパースして監視することになります。以下は、設定値やステータスのJSONレスポンスを模したデータ構造をPythonでハンドリングするイメージです。

import json

# スイッチのSTPステータスを模したJSONデータ
stp_status_json = """
{
    "bridge_id": "4096.aabb.cc00.1100",
    "is_root_bridge": true,
    "ports": [
        {
            "interface": "GigabitEthernet0/1",
            "role": "Designated",
            "state": "Forwarding",
            "cost": 4
        },
        {
            "interface": "GigabitEthernet0/2",
            "role": "Root",
            "state": "Forwarding",
            "cost": 4
        }
    ]
}
"""

def analyze_stp_health(json_data):
    """
    STPのステータスを解析し、異常なブロッキングや
    意図しないルートブリッジの移動がないかをチェックするロジック
    """
    data = json.loads(json_data)
    print(f"[*] 現在のブリッジID: {data['bridge_id']}")
    print(f"[*] このデバイスはルートか?: {data['is_root_bridge']}")
    
    for port in data["ports"]:
        print(f" - ポート {port['interface']}: 役割=[{port['role']}], 状態=[{port['state']}]")
        if port["state"] != "Forwarding" and port["state"] != "Blocking":
            print(f"   [警告] ポート {port['interface']} が不安定な状態({port['state']})にあります!")

if __name__ == "__main__":
    analyze_stp_health(stp_status_json)

—

5. 現場のトラブルシューティング:STPが引き起こす「あるある」障害

最後に、現場のネットワークエンジニアが夜中に呼び出される原因となる、STPにまつわる代表的なトラフィックの罠と対処法を紹介します。

1. 意図しないループバック(TCNの嵐)

  • 現象: ネットワークのどこかで物理ケーブルの片端と他端が同じスイッチに刺さる(ループバック)といったミスが起きると、STPのトポロジ変更通知(TCN: Topology Change Notification)がネットワーク全体に流れ、MACアドレステーブルがクリアされ続けます。
  • 対策: LoopGuard や BPDUGuard を適切に設定し、エッジポートや不審なリンクをあらかじめ保護しておきます。

2. 片方向リンク障害(Uni-Directional Link Failure)

  • 現象: 光ファイバーの送受信(Tx/Rx)の片側だけが破損し、BPDUが「届かない」のに「送れる」状態になると、STPは「トポロジが変わった」と誤認してブロッキングポートをフォワーディングに変更してしまい、結果的に大ループが発生します。
  • 対策: CiscoのUDLD(Uni-Directional Link Detection)や、標準規格であるIEEE 802.3ah(Link OAM)を活用して、物理層の単方向障害を検知したら即座にポートを落とせるようにします。

—

おわりに

スパニングツリープロトコルは、派手なアプリケーションの裏側に隠れた、地味でクラシックなプロトコルに見えるかもしれません。しかし、パケットの挙動を根本から理解しているインフラエンジニアと、そうでないエンジニアの差は、障害時の初動において決定的な違いを生みます。

「なぜ今、このポートがトラフィックを通していないのか?」
その疑問に直面した時、脳内でBPDUが飛び交い、ルートブリッジが選出されるプロセスが鮮明に思い浮かべば、あなたも立派なネットワークスペシャリストです。レイヤー2の足元を固め、堅牢なシステムインフラを構築していきましょう。

コメント

タイトルとURLをコピーしました