らくがきちょう

なんとなく ~所属組織/団体とは無関係であり、個人の見解です~

はじめての LLDP

LLDP の基本についてメモしておきます。

LLDP とは

LLDP (Link Layer Discovery Protocol) とは「隣接検出プロトコル」と呼ばれたりしますが、「接続された隣通しの機器で、お互いの情報を交換するプロトコル」のことです。 簡単に言うと「LLDP を使えば、お隣の機器情報が分かります」ということです。 LLDP を使って隣接する機器を検出 (発見) していくことで、最終的にはネットワーク全体のトポロジーを検出することも可能です。 LLDP は IEEE 802.3AB で標準化されており、ネットワーク機器に限らず LinuxWindows でも対応している為、幅広い機器との間で利用することが可能です。

レイヤー2 で動作するプロトコルですので、IP アドレスが割り当てられていない区間であっても「LLDP フレームで相手が見えていれば、物理的な問題の可能性は低い」とトラブルシューティング時のヒントにすることも可能です。 LLDP は殆どの実装系で送信間隔が「120 秒」等の長めの値となっており、しかも一度に送信するデータ量 (フレーム長) も短い為、有効化しておいても技術的な問題が発生するケースは少ないと考えられます。 但し、「隣接関係を検出する」という性質上、「キャリア機器とユーザ機器」等、情報交換したくない機器間で有効化してしまうとセキュリティ面での事故にも繋がる為、不必要なインターフェイスでは無効化しておく等、注意が必要です。

また昨今では SDN 等で機器が LLDP でトポロジーを学習し、自動的に設定も済ませる… といった用途でも利用されています。

LLDP と CDP の比較表

LLDP と似た隣接検出プロトコルに CDP が挙げられます。 以下に LLDP と CDP を比較してみます。

項目 LLDP CDP
規格 IEEE 802.3AB Cisco 独自
動作レイヤー レイヤー2 レイヤー2
アドバタイズ方式 マルチキャスト マルチキャスト
アドバタイズ用 MAC アドレス 01:80:c2:00:00:0e 01:00:0c:cc:cc:cc

CDP は Cisco 独自規格ではあるものの、VMware vSphere の vSwitch でも CDP に対応している等、Cisco 以外でも対応している製品は存在します。

LLDP は標準規格

前述した通り、LLDP は IEEE 802.1AB で定義されています。 つまり (特定メーカーの規格では無く)「標準規格」です。

LLDP のフレームフォーマット

LLDP フレームをイーサネットフレームとして全体を俯瞰すると以下のような構造になっています。

f:id:sig9:20191002193820p:plain

宛先 MAC アドレス

LLDP はレイヤー 2 で動作しますが、宛先 MAC アドレスは常に 01:80:c2:00:00:0e という Multicast MAC Address 宛に送信されます。 Wikipedia の Link Layer Discovery Protocol ページ には Destination MAC として以下の記載があります。

01:80:c2:00:00:0e, or 01:80:c2:00:00:03, or 01:80:c2:00:00:00

ですが、これらの MAC アドレスは各々以下の用途で利用されているはずです。 従って LLDP で利用可能なのは下記 1. のアドレスのみ、と思われます。

No. MAC アドレス 用途
1 01:80:c2:00:00:0e LLDP
2 01:80:c2:00:00:03 802.1X EAPOL
3 01:80:c2:00:00:00 BPDU (STP/RSTP/MSTP)

タイプ (Ethertype)

LLDP フレームはタイプが常に「0x88cc」になります。 この値を意識するケースは少ないかも知れませんが、tcpdump で LLDP フレームをキャプチャしたい場合は (http や ssh のように)「lldp」を指定することは出来ず、「Ethertype == 0x88cc」のフレームを指定することで結果的に LLDP フレームだけをキャプチャすることが出来ます (LLD フレームのキャプチャ方法は後述)。 尚、Wireshark の Display Filter には「lldp」というキーワードが利用可能です。

データ (TLV)

LLDP フレームのデータは TLV で構成されます。 「TLV」という用語自体は LLDP に特化したものでは無く、いわゆる「下記 3 つを組み合わせたもの」を意味します。

  1. Type (種類)
  2. Length (長さ)
  3. Value (値)

実際の LLDP フレームはイーサフレームのデータ部分に LLDP を表現する TLV を含んだものになります。

f:id:sig9:20191002193834p:plain

LLDP TLV の分類

TLV は以下、いずれかに分類されます。

No. Category TLV type
1 TLV 0 ~ 8
2 Organizationally specific TLV 127 (固定)

TLV type (7bit) は十進数で 0 ~ 127 の値を取りえますが、TLV type 0 ~ 8 は値に応じて TLV 名・用途が決められています。 9 ~ 126 は予約されており、現状では使われていないようです。 ベンダー毎の拡張用途には TLV type 127 が利用されます。 0 ~ 3 は必須 TLV で、LLDP フレームに必ず含まれている必要があります。 また、TLV type 0 の「End of LLDPDU」は必須/オプション関係無く、全ての TLV より後に (=TLV の最後に) 付与されている必要があります。

TLV type TLV name 必須か?
0 End of LLDPDU 必須
1 Chassis ID 必須
2 Port ID 必須
3 Time To Live 必須
4 Port description オプション
5 System name オプション
6 System description オプション
7 System capabilities オプション
8 Management address オプション
9 ~ 126 (Reserved) -
127 オプション

代表的な TLV の意味

ここでは代表的な TLV の意味を説明します。 パケットの例は主に Wireshark のサイトにある SampleCaptures/lldpmed_civicloc.pcap というファイルから抜粋させて頂きました。

TLV type 1 : Chassis ID

シャーシ (筐体 ≒ 機器) を一意に表現します。 「何をもって一意とみなすか?」は Chassis Id Subtype で宣言されます。 下記の例では Chassis Id Subtype が「4」になっていますが、これは「MAC アドレス」を意味し、Chassis Id に MAC アドレスが格納されていることが分かります。

Chassis Subtype = MAC address, Id: 00:13:21:57:ca:40
    0000 001. .... .... = TLV Type: Chassis Id (1)
    .... ...0 0000 0111 = TLV Length: 7
    Chassis Id Subtype: MAC address (4)
    Chassis Id: HewlettP_57:ca:40 (00:13:21:57:ca:40)
TLV type 2 : Port ID

LLDP フレームを送信したポートの情報を表現します。 Port Id Subtype の値で「何をキーにするのか?」を宣言します。 下記でh Port Id Subtype が「7」になっていますが、これは「Locally assigned」という意味だそうです。

Port Subtype = Locally assigned, Id: 1
    0000 010. .... .... = TLV Type: Port Id (2)
    .... ...0 0000 0010 = TLV Length: 2
    Port Id Subtype: Locally assigned (7)
    Port Id: 1

下記は Port Id Subtype が「1」となっていますが、これは Interface alias を意味し、続く Port Id に「Gi1/0/10」という値が入っていることが分かります。

Port Subtype = Interface alias, Id: Gi2/0/10
    0000 010. .... .... = TLV Type: Port Id (2)
    .... ...0 0000 1001 = TLV Length: 9
    Port Id Subtype: Interface alias (1)
    Port Id: Gi1/0/10
TLV type 3 : Time To Live

TTL (Tile To Live) は、この LLDP フレームを受信した機器が「何秒間、該当 LLDP フレームに含まれた情報を保持するか?」を定義します。 IEEE 802.3AB 上には「既定の値」は記載が無いようですが、Cisco 機器では「デフォルト 120 秒」というものが多いようです。 また、同じく IEEE 802.3AB を読む限り、仮に LLDP を有効 → 無効へ設定変更した場合 (いわゆる「LLDP の Shutdown 動作」)、機器は TTL = 0 の LLDP フレームを送信することにより、相手側機器の LLDP データベースをクリアする、という振る舞いをするようです (※ 実機では未検証)。

Time To Live = 120 sec
    0000 011. .... .... = TLV Type: Time to Live (3)
    .... ...0 0000 0010 = TLV Length: 2
    Seconds: 120
TLV type 4 : Port Description

LLDP 送信ポートに設定された Description を送信します。 下記の例では「1」と書かれているだけなので、見てもあまり面白くありません…

Port Description = 1
    0000 100. .... .... = TLV Type: Port Description (4)
    .... ...0 0000 0001 = TLV Length: 1
    Port Description: 1

下記の例では「LLDP-TEST」という Description が表示されていることが分かります。

Port Description = LLDP-TEST
    0000 100. .... .... = TLV Type: Port Description (4)
    .... ...0 0000 1001 = TLV Length: 9
    Port Description: LLDP-TEST
TLV type 5 : System Name

ホスト名を送信します。

System Name = ProCurve Switch 2600-8-PWR
    0000 101. .... .... = TLV Type: System Name (5)
    .... ...0 0001 1010 = TLV Length: 26
    System Name: ProCurve Switch 2600-8-PWR
TLV type 6 : System Description

システムに関する情報を送信します。 一般的には OS バージョン等を送信する実装が多いようです。 以下の例では ProCurve の OS バージョン等を送信しています。

System Description = ProCurve J8762A Switch 2600-8-PWR, revision H.08.89, ROM H.08.5X (/sw/code/build/fish(ts_08_5))
    0000 110. .... .... = TLV Type: System Description (6)
    .... ...0 0101 1111 = TLV Length: 95
    System Description: ProCurve J8762A Switch 2600-8-PWR, revision H.08.89, ROM H.08.5X (/sw/code/build/fish(ts_08_5))

以下の例では Cisco IOS の OS バージョンを送信しているのが分かります。

System Description = Cisco IOS Software, C3750 Software (C3750-IPBASEK9-M), Version 12.2(46)SE, RELEASE SOFTWARE (fc2)\nCopyright (c) 1986-2008 by Cisco Systems, Inc.\nCompiled Thu 21-Aug-08 15:43 by nachen
    0000 110. .... .... = TLV Type: System Description (6)
    .... ...0 1011 0111 = TLV Length: 183
    System Description: Cisco IOS Software, C3750 Software (C3750-IPBASEK9-M), Version 12.2(46)SE, RELEASE SOFTWARE (fc2)\nCopyright (c) 1986-2008 by Cisco Systems, Inc.\nCompiled Thu 21-Aug-08 15:43 by nachen
TLV type 7 : System Capabilities

自身が「どういった機能を持った機器なのか?」という情報を送信します。 具体的には以下 8 つのフラグを送信します。 同時に複数のフラグを立てて送信することも可能です。 「そんな機器が存在するのか!?」は別にして、理論上は全てのフラグを立てた LLDP フレームを送信することも可能なはずです。

  1. Other
  2. Repeater
  3. Bridge
  4. WLAN Access Point
  5. Router
  6. Telephone
  7. DOCSIS cable device
  8. Station only

以下の例では Capabilities が 0x0014 ですが、これは「Bridge」と「Router」のフラグ (bit) が立っていることを意味します。 但し、Enabled Capabilities は「0x0004」となっており、これは「Bridge」だけが有効なことを意味する為、総合すると以下だ、ということが分かります。

  • この機器は Bridge 機能と Router 機能に対応している
  • しかし、現在は Bridge 機能のみを有効化している
Capabilities
    0000 111. .... .... = TLV Type: System Capabilities (7)
    .... ...0 0000 0100 = TLV Length: 4
    Capabilities: 0x0014
        .... .... .... ...0 = Other: Not capable
        .... .... .... ..0. = Repeater: Not capable
        .... .... .... .1.. = Bridge: Capable
        .... .... .... 0... = WLAN access point: Not capable
        .... .... ...1 .... = Router: Capable
        .... .... ..0. .... = Telephone: Not capable
        .... .... .0.. .... = DOCSIS cable device: Not capable
        .... .... 0... .... = Station only: Not capable
    Enabled Capabilities: 0x0004
        .... .... .... ...0 = Other: Not capable
        .... .... .... ..0. = Repeater: Not capable
        .... .... .... .1.. = Bridge: Capable
        .... .... .... 0... = WLAN access point: Not capable
        .... .... ...0 .... = Router: Not capable
        .... .... ..0. .... = Telephone: Not capable
        .... .... .0.. .... = DOCSIS cable device: Not capable
        .... .... 0... .... = Station only: Not capable
TLV type 8 : Management Address

機器の管理アドレスを送信します。 下記の例では「15.255.122.148」です。

Management Address
    0001 000. .... .... = TLV Type: Management Address (8)
    .... ...0 0000 1100 = TLV Length: 12
    Address String Length: 5
    Address Subtype: IPv4 (1)
    Management Address: 15.255.122.148
    Interface Subtype: ifIndex (2)
    Interface Number: 0
    OID String Length: 0
TLV type 0 : End of LLDPDU

LLDP TLV の終わりを意味します。 TLV には順序の意味は無く、「必須 TLV だけが含まれていれば良い」ようですが、TLV type 0 の End of LLDPDU だけは最後に来る必要があります。

End of LLDPDU
    0000 000. .... .... = TLV Type: End of LLDPDU (0)
    .... ...0 0000 0000 = TLV Length: 0

TLV のフォーマット

TLV は以下のような構造をしています。

通常の TLV

TLV type 0 ~ 8 の、通常の TLV は以下のような構造をとります。

f:id:sig9:20191002195236p:plain

Organizationally specific TLV

前述の通り、ベンダー独自の TLV は TLV type 127 決め打ちになります。 通常の TLV と違い、Value 部分が OUI (Organizationally unique identifier) や Organizationally defined subtype、Organizationally defined information string で構成されます。

f:id:sig9:20191002195246p:plain

実際の LLDP フレーム

Wireshark のサイトに LLDP パケットをキャプチャしたサンプルが置いてあります。 ここでは Wireshark のサイトにあるサンプルのうち、SampleCaptures/lldpmed_civicloc.pcap という LLDP フレームを Wireshark で開いてみます。 ざっくり全体を見てみると LLDP TLV は以下の 4 パートに分かれていることが分かります。

  1. 標準 TLV (必須)
  2. 標準 TLV (オプション)
  3. Organizationally specific TLV (オプション)
  4. End of LLDPDU (必須)

f:id:sig9:20191002205525p:plain

尚、LLDP TLV 部分を十六進数でダンプすると以下のようになります。

0000   02 07 04 00 13 21 57 ca 40 04 02 07 31 06 02 00   .....!W.@...1...
0010   78 08 01 31 0a 1a 50 72 6f 43 75 72 76 65 20 53   x..1..ProCurve S
0020   77 69 74 63 68 20 32 36 30 30 2d 38 2d 50 57 52   witch 2600-8-PWR
0030   0c 5f 50 72 6f 43 75 72 76 65 20 4a 38 37 36 32   ._ProCurve J8762
0040   41 20 53 77 69 74 63 68 20 32 36 30 30 2d 38 2d   A Switch 2600-8-
0050   50 57 52 2c 20 72 65 76 69 73 69 6f 6e 20 48 2e   PWR, revision H.
0060   30 38 2e 38 39 2c 20 52 4f 4d 20 48 2e 30 38 2e   08.89, ROM H.08.
0070   35 58 20 28 2f 73 77 2f 63 6f 64 65 2f 62 75 69   5X (/sw/code/bui
0080   6c 64 2f 66 69 73 68 28 74 73 5f 30 38 5f 35 29   ld/fish(ts_08_5)
0090   29 0e 04 00 14 00 04 10 0c 05 01 0f ff 7a 94 02   )............z..
00a0   00 00 00 00 00 fe 09 00 12 0f 01 03 6c 00 00 10   ............l...
00b0   fe 07 00 12 bb 01 00 0f 04 fe 08 00 12 bb 02 01   ................
00c0   40 65 ae fe 2e 00 12 bb 03 02 28 02 55 53 01 02   @e........(.US..
00d0   43 41 03 09 52 6f 73 65 76 69 6c 6c 65 06 09 46   CA..Roseville..F
00e0   6f 6f 74 68 69 6c 6c 73 13 04 38 30 30 30 1a 03   oothills..8000..
00f0   52 33 4c fe 07 00 12 bb 04 03 00 41 00 00         R3L........A..

Python + scapy で LLDP フレームを送信する

Python 3.x + scapy で LLDP フレームを送信してみます。

サンプルスクリプト

サンプルスクリプトは以下の通りです。

#!/usr/bin/env python

from scapy.all import *
import sys
import time

args = sys.argv

# TLV type1 : Chassis Type (MAC Address = 00:11:22:33:44:55)
chassis = bytearray((0x02, 0x07, 0x04, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55))

# TLV type2 : Port Subtype (Gi2/0/10)
portId = bytearray((0x04, 0x09, 0x01, 0x47, 0x69, 0x32, 0x2f, 0x30, 0x2f, 0x31, 0x30))

# TLV type3 : Time To Live (120seconds)
ttl = bytearray((0x06,0x02, 0x00,0x78))

# TLV type4 : Port Description (LLDP-TEST)
portDesc = bytearray((0x08, 0x09, 0x4c, 0x4c, 0x44, 0x50, 0x2d, 0x54, 0x45, 0x53, 0x54))

# TLV type5 : System Name
sysName = bytearray(7)
sysName[0:2] = (0x0a,0x05)
sysName[2:] = str.encode('TEST!', 'utf-8')

# TLV type6 : System Description
sysDesc = bytearray(12)
sysDesc[0:2] = (0x0c,0x0a)
sysDesc[2:] = str.encode('Python 3.x', 'utf-8')

# TLV type7 : Capabilities (Bridge & Router)
capabilities = bytearray((0x0e, 0x04, 0x00, 0x14, 0x00, 0x04))

# TLV type8 : Management Address (192.168.1.1)
mgmtAddr = bytearray((0x10, 0x0c, 0x05, 0x01, 0xc0, 0xa8, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00))

# TLV type127 : IEEE - Port VLAN ID
vlan = bytearray((0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, 0x00, 0x01))

# TLV type127 : Ieee 802.3 - MAC/PHY Configuration/Status
macPhy = bytearray((0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0x6c, 0x00, 0x00, 0x10))

# TLV type127 : Ieee 802.3 - Power via MDI
power = bytearray((0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02, 0x01, 0x00, 0x00))

# TLV type127 : Ieee 802.3 - Link Aggregation
linkAgr = bytearray((0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00))

# TLV type127 : Ieee 802.3 - Maximum Frame Size
maxFrameSize = bytearray((0xfe, 0x06, 0x00, 0x12, 0x0f, 0x04, 0x05, 0xf2))

# TLV type0 : End of LLDPDU
end = bytearray((0x00, 0x00))

# Create frame
payload = bytes(chassis + portId + ttl + sysName + sysDesc + capabilities + mgmtAddr + vlan + macPhy + power + linkAgr + maxFrameSize + end)
mac_lldp_multicast = '01:80:c2:00:00:0e'
eth = Ether(src='00:11:22:33:44:55', dst=mac_lldp_multicast, type=0x88cc)
frame = eth / Raw(load=bytes(payload)) / Padding(b'\x00\x00\x00\x00')

# Send frame
sendp(frame, iface=args[1])

実行例

実行例は以下の通りです。 実行時は LLDP フレームを送信したいインターフェイス名を指定します。 尚、今回は Python 3.7.3 + scapy 2.4.3 で動作確認しました。

# ./send-lldp.py ens192
.
Sent 1 packets.

Python から送信した LLDP フレームのキャプチャ結果

サンプルスクリプトから送信した LLDP フレームをキャプチャすると Wireshark では以下のように表示されました。 「Ieee 802.3 - Link Aggregation」が黄色く表示されていますが、これは Deprecated な TLV を指定している為です。

f:id:sig9:20191002231112p:plain

Link Layer Discovery Protocol
    Chassis Subtype = MAC address, Id: 00:11:22:33:44:55
    Port Subtype = Interface alias, Id: Gi2/0/10
    Time To Live = 120 sec
    System Name = TEST!
    System Description = Python 3.x
    Capabilities
    Management Address
    IEEE - Port VLAN ID
    Ieee 802.3 - MAC/PHY Configuration/Status
    Ieee 802.3 - Power Via MDI
    Ieee 802.3 - Link Aggregation
    Ieee 802.3 - Maximum Frame Size
    End of LLDPDU

tcpdump で LLDP フレームをキャプチャする

tcpdump を使って LLDP フレームをキャプチャする場合、キャプチャフィルタに lldp と指定してもエラーになります。

# tcpdump -i ens192 -w lldp-frame.pcap lldp
tcpdump: syntax error

LLDP フレームはイーサタイプが 0x88cc なので、ether proto 0x88cc を指定することでキャプチャ出来ます。

tcpdump -i ens192 -w lldp-frame.pcap ether proto 0x88cc