【実務・中級編】 VTPアドバタイズメントのメッセージ種別とトリガー条件 – インフラ・物理&L2/L3ネットワーク基礎実践ガイド

やあ、現場の最前線でパケットと戦うエンジニア諸君。今日もネットワークの深淵へようこそ。

「VTP(VLAN Trunking Protocol)なんて、もう過去のレガシー技術だろう?」
もし君がそう思っているなら、少し立ち止まってほしい。確かに、現代のエンタープライズネットワークでは「VTP Transparentモード」や「VTP Version 3」による静的な運用、あるいはVXLANによるオーバーレイネットワークが主流になりつつある。しかし、VTPが内包する「分散ノード間における状態同期(State Synchronization)のパラダイム」は、現代の分散データベースやWeb APIのレプリケーション、イベント駆動型アーキテクチャの設計思想と驚くほど共通している。

今回は、数々の大規模ネットワーク障害を現場で収めてきた私から、VTPの心臓部である「3つのアドバタイズメントメッセージ」のメカニズムと、それらがトリガーされる極めて精緻な条件について、パケットレベルの挙動を交えて徹底的に解説しよう。

この挙動を理解することは、単なるCisco CLIの暗記ではない。L2マルチキャストを用いた「状態の一貫性(Consistency)」を保証するための、プロトコルデザインの真髄を学ぶことなのだ。

—

1. VTP同期のコア:リビジョン番号(Configuration Revision)の魔力

VTPの挙動を支配するのは、「リビジョン番号(Configuration Revision Number)」という32ビットの符号なし整数だ。

すべてのスイッチは、自身のデータベースにこのリビジョン番号を保持している。VLANの追加・削除・名前変更などの変更が発生するたびに、このリビジョン番号は 「1」ずつカウントアップ される。そして、VTPドメイン内のスイッチ群は、常に「より高いリビジョン番号を持つアドバタイズメント」を受け取ると、自身のVLANデータベースをその内容で上書き(同期)する。

これが、かつて多くのインフラエンジニアにトラウマを植え付けた「VTP Bombing(古いスイッチをネットワークに繋いだ瞬間、高いリビジョン番号のせいで本番のVLAN情報が全消去される惨劇)」の原因だ。この動的な同期システムを制御するために、以下の3つのメッセージタイプが定義されている。

—

2. 三種の神器:VTPメッセージフォーマットとその役割

VTPメッセージは、IEEE 802.3 イーサネットフレームのペイロードにカプセル化され、宛先MACアドレス 01:00:0c:cc:cc:cc(Cisco独自の共有L2マルチキャストアドレス)に向けて送出される。

メッセージタイプは、主に以下の3種類に大別される。

+-------------------------------------------------------------+
|                     Ethernet II / 802.3                     |
|  Dst MAC: 01:00:0c:cc:cc:cc | Src MAC: Switch MAC           |
+-------------------------------------------------------------+
|                     LLC (SNAP) Header                       |
|  OUI: 00:00:0c (Cisco) | PID: 2003 (VTP)                    |
+-------------------------------------------------------------+
|                         VTP Header                          |
|  Version | Message Type | Domain Name Length | Domain Name  |
+-------------------------------------------------------------+
|                        VTP Payload                          |
|  (Summary / Subset / Request depending on Message Type)     |
+-------------------------------------------------------------+

① サマリーアドバタイズメント (Summary Advertisement)

Message Type: 0x01

ドメイン内の全スイッチに対して、「現在のドメイン名」と「最新のリビジョン番号」を定期的に、または状態変更時に通知するためのメッセージだ。
このパケット自体には、具体的なVLAN情報(VLAN IDやVLAN名など)は含まれていない。あくまで「我がドメインの最新ステートは、リビジョン X である」というメタデータのみを宣言する。

  • 主な内包パラメーター:
  • VTP Version: VTPのバージョン(1, 2, または 3)
  • Management Domain Name: ゼロパディングされた最大32バイトのドメイン名
  • Configuration Revision Number: 現在のリビジョン番号
  • Updater Identity: 最後にリビジョンを更新したスイッチのIPアドレス(デバッグ時に極めて重要)
  • Update Timestamp: 最終更新日時(YY-MM-DD HH:MM:SS)
  • MD5 Digest: ドメイン名、パスワード、リビジョン等から計算されたハッシュ値。認証と整合性検証に使用される。

② サブセットアドバタイズメント (Subset Advertisement)

Message Type: 0x02

サマリーアドバタイズメントに続いて、または要求に応じて送信される、具体的なVLAN情報の本体である。
VLANの追加、削除、サスペンド、名前変更、MTUサイズ、802.10インデックスなどの詳細な構成パラメータが、このメッセージ内にカプセル化されて運ばれる。VLANの数が多い場合、複数のサブセットアドバタイズメントに分割(フラグメント)されて送信される。

  • 主な内包パラメーター:
  • Configuration Revision Number: サマリーと一致するリビジョン番号
  • VLAN Info Field: 各VLANの情報ブロック。以下の要素を含む。
  • VLAN Info Length
  • Status(Active / Suspended)
  • VLAN ID(12ビット)
  • VLAN Name Length & VLAN Name

③ アドバタイズメントリクエスト (Advertisement Request)

Message Type: 0x03

※実務において「サードパーティリクエスト」と俗称されることもあるが、公式なRFC/Cisco仕様上の名称は Advertisement Request である。
これは、クライアント(またはサーバー)スイッチが、「自分よりも新しい、または失われたVLAN情報を今すぐ送ってくれ」とドメイン内に要求するためのメッセージだ。

  • 主な内包パラメーター:
  • Management Domain Name
  • Start Value: 要求する最小のVLAN ID(通常は 1 から開始してすべての情報を求める)

—

3. メッセージの送信トリガーとシーケンス

これらのメッセージは、お互いに無秩序に飛び交っているわけではない。極めて厳密なトリガー条件に基づいて動作している。

トリガー条件の整理

| メッセージ種別 | 送信モード | 送信トリガー条件 |
| :— | :— | :— |
| Summary Advertisement | Server / Client | 1. 5分間隔(300秒)の定期ポーリング送信
2. VLANの追加・削除・変更が自機(Server)で行われた瞬間(即時) |
| Subset Advertisement | Server / Client | 1. 自機でVLAN構成変更が発生した瞬間(Summaryに追従)
2. 他機から「Advertisement Request」を受信したとき |
| Advertisement Request | Server / Client | 1. スイッチが再起動(Reload)したとき
2. VTPドメイン名が変更されたとき
3. 自機より「高いリビジョン番号」のSummaryを受信したが、手元にそのVLAN詳細がないとき |

通信フロー(シーケンス)

スイッチの起動時、および管理者がVLANを追加した際のシーケンスを見てみよう。

シーンA:新設スイッチ(Client)がネットワークに参加したとき

[ New Client Switch ]                               [ Core Server Switch ]
         |                                                    |
         |=== 1. Boot Up (No VLAN Database) ==================|
         |                                                    |
         |------ (0x03) Advertisement Request --------------->| "最新情報をくれ!"
         |                                                    |
         |<----- (0x01) Summary Advertisement ----------------| "最新はRev 12、ハッシュはこれだ"
         |                                                    |
         |<----- (0x02) Subset Advertisement -----------------| "Rev 12のVLAN詳細は VLAN10,20,30だ"
         |                                                    |
         |=== 2. Apply VLAN DB & Update Rev to 12 ============|

シーンB:Server上で管理者が VLAN 99 を新規作成したとき

[ Core Server Switch ]                              [ Edge Client Switch ]
         |                                                    |
         |=== 1. Conf t -> vlan 99 (Revision: 12 -> 13) =====|
         |                                                    |
         |------ (0x01) Summary Advertisement (Rev 13) ------>| "リビジョンが13に上がったぞ"
         |------ (0x02) Subset Advertisement (VLAN 99 info) ->| "変更内容はVLAN 99の追加だ"
         |                                                    |
         |                                                    |=== 2. Sync OK (Update Rev to 13)

—

4. Python (Scapy) によるVTPパケット解析シミュレーション

インフラの自動化やセキュリティ監査において、パケットの構造をプログラムレベルで理解することは大きな強みになる。
以下は、Pythonの強力なパケット操作ライブラリ Scapy を用いて、ネットワーク上を流れるVTPメッセージ(Summary Advertisement)を模したパケットを解析・ダンプするスクリプトの例だ。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
vtp_parser.py
イーサネット上を流れるVTP Summary Advertisementパケットを解析するシミュレーター。
※実務でのトラブルシューティングや、パケットキャプチャの自動解析スクリプトの基礎として利用可能。
"""

from scapy.all import *
import struct

def parse_vtp_packet(packet):
    # 宛先MACがVTPマルチキャストアドレス(01:00:0c:cc:cc:cc)か確認
    if packet.dst == "01:00:0c:cc:cc:cc":
        print(f"\n[+] VTP Packet Captured! Src MAC: {packet.src}")
        
        # LLC/SNAPヘッダーをスキップし、VTPペイロードを取得
        # 通常、SNAPのPIDが 0x2003 の場合にVTPとなる
        payload = bytes(packet.payload)
        
        if len(payload) < 4:
            return

        # VTPヘッダーのパース
        # Offset 0: Version (1 byte)
        # Offset 1: Type (1 byte) -> 0x01: Summary, 0x02: Subset, 0x03: Request
        # Offset 2: Domain Name Length (1 byte)
        vtp_version, msg_type, domain_len = struct.unpack("!BBB", payload[:3])
        
        msg_type_str = {
            1: "Summary Advertisement",
            2: "Subset Advertisement",
            3: "Advertisement Request"
        }.get(msg_type, "Unknown Type")

        print(f"    VTP Version : {vtp_version}")
        print(f"    Message Type: 0x{msg_type:02x} ({msg_type_str})")
        print(f"    Domain Len  : {domain_len}")

        # Summary Advertisement の詳細解析
        if msg_type == 1 and len(payload) >= 36:
            # ドメイン名の抽出(最大32バイト、domain_len分読み込む)
            domain_name = payload[4:4+domain_len].decode('utf-8', errors='ignore')
            print(f"    Domain Name : {domain_name}")
            
            # リビジョン番号(4バイト)を 40バイト目付近(ドメイン名領域32バイトの後ろ)から抽出
            # VTP規格では、ドメイン名領域は常に32バイト固定で確保される(ゼロパディング)
            revision = struct.unpack("!I", payload[36:40])[0]
            print(f"    Revision No : {revision}")
            
            # アップデータIP(4バイト)
            updater_ip = ".".join(map(str, payload[40:44]))
            print(f"    Updater IP  : {updater_ip}")
            
            # タイムスタンプ(12バイト)
            timestamp = payload[44:56].decode('utf-8', errors='ignore')
            print(f"    Timestamp   : 19{timestamp[0:2]}-{timestamp[2:4]}-{timestamp[4:6]} {timestamp[6:8]}:{timestamp[8:10]}:{timestamp[10:12]} (UTC)")

if __name__ == "__main__":
    print("Starting VTP Packet Sniffer...")
    # 実際のネットワークインターフェース(例: eth0)でリッスンする場合
    # sniff(iface="eth0", prn=parse_vtp_packet, store=0)
    
    # テスト用の疑似パケットを作成して流す
    # 01 01 08 (Ver1, Summary, Len 8) + 'Internal' (8 chars + 24 padding) + Rev 105 (0x00000069) + IP 10.1.1.254 + Timestamp '231124120000'
    dummy_vtp_payload = b'\x01\x01\x08' + b'Internal' + b'\x00'*24 + b'\x00\x00\x00\x69' + b'\x0a\x01\x01\xfe' + b'231124120000'
    test_pkt = Ether(dst="01:00:0c:cc:cc:cc", src="00:11:22:33:44:55") / dummy_vtp_payload
    
    parse_vtp_packet(test_pkt)

—

5. 現場を救う実務的な Tips とデバッグ手順

さて、ここからは実務の話をしよう。もし君が「VTPの同期がおかしい」という現場に遭遇したら、以下のステップでトラブルシューティングを行ってほしい。

① show vtp status の出力を精緻に読み解く

まずはCiscoスイッチのコンソールで、以下のコマンドを実行する。

Switch# show vtp status
VTP Version capable             : 1 to 3
VTP version running             : 2
VTP Domain Name                 : Enterprise-Core
VTP Pruning Mode                : Disabled
VTP Traps Generation            : Enabled
Device ID                       : 547f.ee32.1a00

Feature VLAN:
--------------
VTP Operating Mode              : Server
Maximum VLANs supported locally : 1005
Number of existing VLANs        : 15
Configuration Revision          : 45          <-- [注目] ドメイン内で最も高いリビジョンと一致しているか?
MD5 digest                      : 0x5C 0x8A 0xF2 0x11 0x4B 0x7E 0x93 0xA2 <-- [注目] 隣接と一致しているか?

デバッグのチェックポイント:

1. MD5 Digest の不一致:
もしドメイン内で MD5 digest が異なっているスイッチがある場合、VTPパスワードが異なっているか、ドメイン名の大文字小文字が間違っている。VTPはパスワードをMD5のソルトとして使用するため、1文字でも違えばメッセージは無視(Discard)される。
2. Configuration Revision が 0 から動かない:
そのスイッチは Transparent モード になっていないか? あるいは、トランクポート(Trunk Link)が正常にアップしていない可能性がある。VTPアドバタイズメントは トランクポート上でのみカプセル化されて送受信される。アクセスポートでは一切流れない。

② 安全に新しい(または中古の)スイッチを導入する手順

冒頭で述べた「VTP Bombing」を防ぐため、検証用スイッチや他拠点の使い回しスイッチを既存ネットワークに接続する際は、必ず以下の手順を徹底すること。

! 1. ネットワークに接続する前に、コンソール接続する
! 2. VTPモードを一時的に "Transparent" に変更する
!    これにより、Configuration Revision が強制的に「0」にリセットされる。
Switch(config)# vtp mode transparent

! 3. 目的のVTPモード(Clientなど)に戻す
Switch(config)# vtp mode client

! 4. ドメイン名とパスワードを設定
Switch(config)# vtp domain Enterprise-Core
Switch(config)# vtp password SecretPassword123

! 5. リビジョンが「0」であることを確認してから、トランクポートを接続する
Switch# show vtp status | include Revision
Configuration Revision          : 0

この「一度 transparent に挟む」という泥臭いワンステップが、ネットワーク全体の崩壊を防ぐ最大の防御策なのだ。

—

6. まとめ:状態同期プロトコルとしてのVTPから学ぶこと

VTPの「サマリー」「サブセット」「リクエスト」という3つの協調動作は、分散システムにおける「ステートセーブと差分同期」の古典的かつ完成されたモデルである。

  • サマリー(メタデータ)で変更の有無を高速に検知し、
  • リクエスト(要求)によって必要なノードだけが、
  • サブセット(実データ)を要求して同期する。

この美しい無駄のないステートマシンの挙動は、Web APIのキャッシュコントロール(ETag と If-None-Match の関係)や、Gitの fetch / pull の仕組みにも脈々と受け継がれている。

プロトコルの表層的なコマンドだけでなく、その裏側でパケットがどのようにトリガーされ、どのフィールドが同期をコントロールしているのか。それを知ることこそが、真のインフラアーキテクトへの道なのだ。

また次のパケットの深淵でお会いしよう。Keep Sniffing!

コメント

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