🏅

パケットキャプチャでSTPを学ぶ(8) Port Priority変更でRoot Port選定

2022/07/02に公開

前回のおさらい

Linux(WSL)に作ったBridge(L2スイッチ)でSTPを有効にしてパケットキャプチャして現物のパケットを見ながら遊んでました。Port Cost、Bridge IDを変更することで目的のSTPトポロジに収束させることも確認できました。

https://zenn.dev/takai404/articles/d4818eb9ecb6e0

今回は以下のRoot Port選定基準のうち、ポートIDを変更することで目的のSTPトポロジに収束させてみることにします。と言いたいのですが、それができないのでPort IDを変更して遊んでみます。

  1. ルートパスコストが最小
  2. Bridge IDが最小
  3. ポートIDが最小

(各ポートが受信したBPDUを見て、条件1が同位の場合は条件2で、条件2が同位の場合は条件3で決定します)

ケーブルの追加

このネットワーク構成で、条件3のポートIDを変更してSTPトポロジ(Root Port)を変更してみようと思ったのですが、Root Portの選定にPort IDが関与するのは、Bridge IDが同じ場合のみです。
今回の構成では1台のBridgeが、同一Bridge IDのBPDUを受け取る箇所、もっと端的にいうとBridge間にケーブルが2本引かれている箇所はありません。
幸いにも仮想環境で実験しているので、Bridgeのポートが空いているかとかLANケーブルが手元にあるかを考える必要はないです。core1とcore2の間にケーブル増やしちゃいましょう。

ip link add core1_e3 type veth peer name core2_e3
ip link set core1_e3 master core1
ip link set core2_e3 master core2
ip link set core1_e3 up
ip link set core2_e3 up

blocking portの確認もしておきます。新規に追加したcore2_e3がBlockingになっていますね。

# bridge link  | sed -ne 's/[^ ]* \([^@]*\)@.*\(state [^ ]*\).*/\1\t\2/p' | sort
access1_e0      state forwarding
access1_e1      state forwarding
access1_e2      state forwarding
access2_e0      state blocking
access2_e1      state forwarding
access2_e2      state forwarding
access3_e0      state forwarding
access3_e1      state forwarding
access3_e2      state forwarding
access4_e0      state blocking
access4_e1      state forwarding
access4_e2      state forwarding
core1_e0        state forwarding
core1_e1        state forwarding
core1_e2        state forwarding
core1_e3        state forwarding
core2_e0        state forwarding
core2_e1        state forwarding
core2_e2        state forwarding
core2_e3        state blocking

図はこちら。core1とcore2の間にケーブルが2本あり、片方(e3)がblockingです。

パケットキャプチャ確認

BPDUは上位から下位に対して送るので、core2でBPDUを受信しているはずです。(Blocking Portはパケットの転送を無効にしているだけでリンクダウンではないので、BPDUを受信しています)

coreのe0, e3でキャプチャを取得します。

# tcpdump -i core2_e0 -U -w - | /mnt/c/Program\ Files/Wireshark/wireshark.exe -k
-i - &
# tcpdump -i core2_e3 -U -w - | /mnt/c/Program\ Files/Wireshark/wireshark.exe -k
-i - &

core2_e0

core2_e3

core2_e0はポートID 0x8001、core2_e3は0x8004を取得しています。

ポートIDは2バイト(16ビット)です。
標準STPでは上位8ビットがPort Priority、下位8ビットがPort Numberです。
Port Priorityは8ビットなので0~255の256通りの値を利用可能ですが、0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240を使うことが多いです。

core2_e0, core2_e3ともにPort Priorityは0x80(128)になっています。
Port Numberは1から数え始めてるので、e0が0x01、e3は0x04なのでしょう。[1]

bridge linkコマンド

パケットキャプチャを見る限り、Port Priorityは0x80です。0x00と0xFFのちょうど真ん中なので、デフォルト値としては適切だと思います。

問題はip linkコマンドの表示です。これらのBPDUを送出しているcore1の設定を見ると、「priority 32」って表示されるんですよね。

# bridge link show dev core1_e0
14: core1_e0@core2_e0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 s
tate forwarding priority 32 cost 2
# bridge link show dev core1_e3
32: core1_e3@core2_e3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 s
tate forwarding priority 32 cost 2

man bridgeでマニュアルを読んでみるとbridge link setでSTPのPort Priorityを設定できると書いてある[2]ので、さっきの32はPort Priorityのことだと思われるんですが、なぜ設定値が32で、実際に送られる値は128なのか?

   priority PRIO
          the STP port priority. The priority value is an unsigned 8-bit quantity (number between 0 and 255).
          This metric is used in the designated port an droot port selection algorithms.

値を変更して試してみましょう。

# bridge link show dev core1_e3
32: core1_e3@core2_e3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 s
tate forwarding priority 32 cost 2
# bridge link set dev core1_e3 priority 16
# bridge link show dev core1_e3
32: core1_e3@core2_e3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master core1 s
tate forwarding priority 16 cost 2

32の半分の16にセットしました。bridge link showコマンドの出力も16になりました。

パケットキャプチャをみても0x80の半分の0x40になっています。ついでにBPDUのフラグにTopology Changeフラグが立ったので、トポロジにも変更が起きているみたいです。

# bridge link  | sed -ne 's/[^ ]* \([^@]*\)@.*\(state [^ ]*\).*/\1\t\2/p' | sort
access1_e0      state forwarding
access1_e1      state forwarding
access1_e2      state forwarding
access2_e0      state blocking
access2_e1      state forwarding
access2_e2      state forwarding
access3_e0      state forwarding
access3_e1      state forwarding
access3_e2      state forwarding
access4_e0      state blocking
access4_e1      state forwarding
access4_e2      state forwarding
core1_e0        state forwarding
core1_e1        state forwarding
core1_e2        state forwarding
core1_e3        state forwarding
core2_e0        state blocking
core2_e1        state forwarding
core2_e2        state forwarding
core2_e3        state forwarding

Blocking Portがcore2_e3からcore2_e0に移動しました。


core2のRoot Portが移動したのでやりたかったことは確認できました。
忘れないうちに書いておくと、ポイントは次の通りです。

項目 Port Cost Port Priority
Root Port選定時の優先度 最初にチェックされる Root Path Cost(=Port Costの積算値)とBridge IDが同位の場合チェックされる
設定箇所 BPDUを受信するBridge BPDUを送信するBridge
設定変更の効果(影響) STP木構造の下流すべてに及ぶ BPDUを直接受信する1台だけ

bridge linkコマンドの謎

16を設定すると0x40(10進数64)になってしまう件をもうちょっと調査してみました。
bridge link setコマンドでセットした値とBPDUの関係は以下の通りです。
ちょっとバグっぽい感じですね。4ビット左シフトなら、(標準STPではなく)RSTPのPriority設定を意識しているともとれるのですが、実際には2ビット左シフトされてBPDUに格納されていますね。
bridge link setで設定するときはBPDUにセットしたい値を4で割って(2ビット左シフト右シフトして)指定するとよさそうです。(128にしたいときは4で割った答えである32をセットする)

bridge link setコマンドの設定値 送信されたBPDUのPort ID上8ビット 同 10進数表現 同 2進数表現
0 0x00 0 0000 0000
1 0x04 4 0000 0100
16 0x40 64 0100 0000
32 0x80 128 1000 0000
63 0xfc 252 1111 1100

これを超える数は設定エラーでした。

# bridge link set dev core1_e3 priority 64
RTNETLINK answers: Numerical result out of range
# bridge link set dev core1_e3 priority 255
RTNETLINK answers: Numerical result out of range

遊び終わったので最後にPriority値戻しとケーブル削除しておきました。

bridge link set dev core1_e3 priority 32
ip link del core1_e3

Kernel source

カーネルのソースコードをざっと追ってみたのですが、おそらくここのコードが原因かなという気がします。

br_stp_if.c
/* Port id is composed of priority and port number.
 * NB: some bits of priority are dropped to
 *     make room for more ports.
 */
static inline port_id br_make_port_id(__u8 priority, __u16 port_no)
{
	return ((u16)priority << BR_PORT_BITS)
		| (port_no & ((1<<BR_PORT_BITS)-1));
}

priorityをBR_PORT_BITSビット分だけ左シフトしていて、BR_PORT_BITSはここで10であると定義されています。

br_private.h
#define BR_PORT_BITS	10

一方で、IEEEの8021Q-2018を見ると以下のように16ビットのポートIDのうち、上位4ビットがPriorityと書いてあるので、16-4=12ビット左シフトすべきではないかと。(もしくは下位4ビットをゼロクリアしてから8ビット左シフト)


14.2.7 Encoding of Port Identifiers
A Port Identifier is a 16-bit unsigned integer. If two Port Identifiers are numerically compared, the lesser number denotes the port of better priority. The most significant 4 bits of a Port Identifier is a settable priority component that permits the relative priority of ports on the same Bridge to be managed (13.27.46), and can be modified independently for the CIST and each MSTI. The less significant 12 bits is the Port Number expressed as an unsigned binary number, and is same for the CIST and all MSTIs. The value 0 is not used as a Port Number.

The Port Identifier for the CIST is encoded in two octets, comprising both the CIST’s priority component and the Port Number used for all spanning trees. The 4-bit priority component for each MSTI is encoded as a binary number, in the MSTI Configuration Message for that MSTI. Each SPT uses the same Port Identifier
as the CIST, so SPT Port Identifiers are not separately encoded.

NOTE—IEEE Std 802.1D, 1998 Edition [B16], and prior revisions specified a priority component of 8 bits and a Port Number of 8 bits. To maintain management compatibility with prior implementations the priority component is still considered to be an 8-bit value, but its values are restricted to those where the least significant 4 bits are zero (and hence ignored in encoding).


参考:動作確認を行った環境について

# cat /etc/issue
Ubuntu 22.04 LTS \n \l

#  uname -r
5.10.16.3-microsoft-standard-WSL2
# dpkg -l iproute2
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name           Version         Architecture Description
+++-==============-===============-============-================================
====
ii  iproute2       5.15.0-1ubuntu2 amd64        networking and traffic control t
ools
脚注
  1. Linux Bridgeの実装では特にアルファベット順ソートとかはしていないみたいなので、例えばポート(veth)をe3, e2, e1, e0の順に作るとe3のNumberは0x01、e0のNumberは0x04になります。 ↩︎

  2. 「designated port an droot port」はたぶん「designated port and root port」の誤記かと思われる。 ↩︎

Discussion