💣

パケットキャプチャでSTPを学ぶ(5) Bridge Priorityの変更

2022/06/27に公開

前回のおさらい

STP(Spanning Tree Protocol)構成をLinuxの中にLinux Bridgeで作成して、パケットキャプチャを取りながらSTPトポロジが完成するところまで確認しました。

https://zenn.dev/takai404/articles/1e5183570194ac

STPのパラメータ値はデフォルトを使っているので、若干いびつな形になっています。以下の図でpc1からpc2に行くのにかなり遠回りしています。


STPトポロジ設計について

上で述べたようにSTPをでデフォルトパラメータで構築するとMACアドレスに依存したトポロジに収束します。
仮想環境であれば作り直すたびにトポロジが変わってしまいます。物理環境であればそんなに変化はないと思いますが、機械である以上、ネットワークスイッチは故障します。MACアドレスは機器固有の値なので、故障交換するとトポロジが変わってしまうという非常に管理しづらいネットワークになります。

どのようなトポロジがいいのかというと、正解はなくて、こんな上下の(North-South)トラフィックが多い場合と

同レベルの(East-West)トラフィックが多い場合で最適解は異なってきます。

今回は後者のモデルで考えてみます。
サーバNICは一般的に2つのスイッチングハブに足を出してBondingすることが多いです。このトラフィックがわざわざコア間を通るとか、一番先頭で書いたみたいにaccess1とaccess2でパケットをやり取りしたいときにaccess3, access4を通るのとかは悪手ですよね。

トポロジの変え方

まずはRoot Bridgeを決めましょう。Root Bridgeに近い方がBlockingポートが少ないので、ネットワークの中心にいるBridgeをRoot Bridgeにしましょう。
core1をRoot Bridgeにすることにします。
Root Bridge選出基準ではBridge IDの数値が小さい方が優勢でした。
Bridge IDは上位がPriority、下位がMACアドレスなので、Priorityを小さい値に設定してあげることで優先的にRoot Bridgeになります。

Bridge Priority MACアドレス 備考
access1 0x8000(デフォルト) 32:c8:70:0d:8a:30
access2 0x8000(デフォルト) 3e:cc:33:fc:df:10
access3 0x8000(デフォルト) 06:6e:04:36:2e:c5
access4 0x8000(デフォルト) 42:bf:cd:48:42:30
core1 0x2000 6a:78:dc:b8:0a:3d Root Bridge
core2 0x4000 26:4b:c9:95:0e:e0

core2のPriorityの数値も小さくしているのは、core1が故障した際に代わりにRoot Bridgeになってもらうためです。

設定変更方法

Linux BridgeのSTPのBridge Priorityの変え方は次の通りです。

ip link set core1 type bridge priority 0x2000
ip link set core2 type bridge priority 0x4000

途絶時間

ところで、Bridge Priorityを変更したらネットワークは途絶するんでしょうか?
やってみましょう。

すべてのvethでパケットキャプチャを開始して、

ip -br link show type veth \
    | awk -F @ '{print $1}' \
    | xargs -t -P 0 -i tcpdump -i {} -w {}.pcap

pc1~pc4で相互にPingするところをログファイルに取得して

for i in `seq 4`; do
    for j in `seq $(($i+1)) 4`; do
        ip netns exec pc$i ping 10.0.0.$j > pc$i-pc$j.log &
    done;
done

Bridge Prioriyを変更してみます。

ip link set core1 type bridge priority 0x2000
ip link set core2 type bridge priority 0x4000

標準STPは途絶時間があるとするとだいたい30~50秒なので、Bridge Priorityを変更してから60秒くらい情報を取得してからパケットキャプチャとPingを終了します。

パケットキャプチャの終了はCtl-Cです。
ping終了は(ちょっと雑ですが)killallを使いましょう。

sleep 60; killall -INT ping; kill

パケットキャプチャファイルの結合もしておきます。

mergecap -w access1.pcap access1_e0.pcap access1_e1.pcap access1_e2.pcap
mergecap -w access2.pcap access2_e0.pcap access2_e1.pcap access2_e2.pcap
mergecap -w access3.pcap access3_e0.pcap access3_e1.pcap access3_e2.pcap
mergecap -w access4.pcap access4_e0.pcap access4_e1.pcap access4_e2.pcap
mergecap -w core1.pcap core1_e0.pcap core1_e1.pcap core1_e2.pcap
mergecap -w core2.pcap core2_e0.pcap core2_e1.pcap core2_e2.pcap

結果確認

Pridge Priorityの設定値確認

ip linkコマンドに-d (details)オプションを付けるとBridgeの詳細情報が出てきます。
横に長いので非常に見づらいですが、priority 8192(16進数0x2000)というのが見つかります。

# ip -d link show core1
7: core1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 6a:78:dc:b8:0a:3d brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
    bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 1 priority 8192 vlan_filtering 0 vlan_protocol 802.1Q bridge_id 2000.6a:78:dc:b8:a:3d designated_root 2000.6a:78:dc:b8:a:3d root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer    1.18 tcn_timer    0.00 topology_change_timer    0.00 gc_timer  237.40 vlan_default_pvid 1 vlan_stats_enabled 0 vlan_stats_per_port 0 group_fwd_mask 0 group_address 01:80:c2:00:00:00 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

-j (json)オプションを付けるとJSON形式で出てきます。そのままだと整形されていなくてとても読めたものじゃないので、pythonのjsonモジュールで見やすくします。
"priority": 8192,というのが見えて、うまく設定変更できているように見えます。

# ip -d -j link show core1 | python3 -m json.tool
[
    {
        "ifindex": 7,
        "ifname": "core1",
        "flags": [
            "BROADCAST",
            "MULTICAST",
            "UP",
            "LOWER_UP"
        ],
        "mtu": 1500,
        "qdisc": "noqueue",
        "operstate": "UP",
        "linkmode": "DEFAULT",
        "group": "default",
        "txqlen": 1000,
        "link_type": "ether",
        "address": "6a:78:dc:b8:0a:3d",
        "broadcast": "ff:ff:ff:ff:ff:ff",
        "promiscuity": 0,
        "min_mtu": 68,
        "max_mtu": 65535,
        "linkinfo": {
            "info_kind": "bridge",
            "info_data": {
                "forward_delay": 1500,
                "hello_time": 200,
                "max_age": 2000,
                "ageing_time": 30000,
                "stp_state": 1,
                "priority": 8192,
                "vlan_filtering": 0,
                "vlan_protocol": "802.1Q",
                "bridge_id": "2000.6a:78:dc:b8:a:3d",
                "root_id": "2000.6a:78:dc:b8:a:3d",
                "root_port": 0,
                "root_path_cost": 0,
                "topology_change": 0,
                "topology_change_detected": 0,
                "hello_timer": 1.35,
                "tcn_timer": 0.0,
                "topology_change_timer": 0.0,
                "gc_timer": 23.57,
                "vlan_default_pvid": 1,
                "vlan_stats_enabled": 0,
                "vlan_stats_per_port": 0,
                "group_fwd_mask": "0",
                "group_addr": "01:80:c2:00:00:00",
                "nf_call_iptables": 0,
                "nf_call_ip6tables": 0,
                "nf_call_arptables": 0
            }
        },
        "inet6_addr_gen_mode": "eui64",
        "num_tx_queues": 1,
        "num_rx_queues": 1,
        "gso_max_size": 65536,
        "gso_max_segs": 65535
    }
]

Pridge Priorityの動作確認

core1に隣接しているBridgeでどんなBPDUを受信しているのかを見てみます。
これもかなり長いのですが、linkinfo -> info_slave_data -> bridge_idにある2000.6a:78:dc:b8:a:3dというのがBPDUのBridge IDを示しているようです。

# ip -d -j link show access3_e1 | python3 -m json.tool
[
    {
        "ifindex": 17,
        "link": "core1_e2",
        "ifname": "access3_e1",
        "flags": [
            "BROADCAST",
            "MULTICAST",
            "UP",
            "LOWER_UP"
        ],
        "mtu": 1500,
        "qdisc": "noqueue",
        "master": "access3",
        "operstate": "UP",
        "linkmode": "DEFAULT",
        "group": "default",
        "txqlen": 1000,
        "link_type": "ether",
        "address": "06:6e:04:36:2e:c5",
        "broadcast": "ff:ff:ff:ff:ff:ff",
        "promiscuity": 1,
        "min_mtu": 68,
        "max_mtu": 65535,
        "linkinfo": {
            "info_kind": "veth",
            "info_slave_kind": "bridge",
            "info_slave_data": {
                "state": "forwarding",
                "priority": 32,
                "cost": 2,
                "hairpin": false,
                "guard": false,
                "root_block": false,
                "fastleave": false,
                "learning": true,
                "flood": true,
                "id": "0x8001",
                "no": "0x1",
                "designated_port": 32771,
                "designated_cost": 0,
                "bridge_id": "2000.6a:78:dc:b8:a:3d",
                "root_id": "2000.6a:78:dc:b8:a:3d",
                "hold_timer": 0.0,
                "message_age_timer": 19.09,
                "forward_delay_timer": 0.0,
                "topology_change_ack": 0,
                "config_pending": 0,
                "proxy_arp": false,
                "proxy_arp_wifi": false,
                "mcast_flood": true,
                "mcast_to_unicast": false,
                "neigh_suppress": false,
                "group_fwd_mask": "0",
                "group_fwd_mask_str": "0x0",
                "vlan_tunnel": false,
                "isolated": false
            }
        },
        "inet6_addr_gen_mode": "eui64",
        "num_tx_queues": 1,
        "num_rx_queues": 1,
        "gso_max_size": 65536,
        "gso_max_segs": 65535
    }
]

JSON形式だとbridge_idという名前だったのですが、標準の出力だとdesignated_bridgeという名前みたいですね。なんで名前が違うの…

# ip -d link show access3_e1
17: access3_e1@core1_e2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master access3 state UP mode DEFAULT group default qlen 1000
    link/ether 06:6e:04:36:2e:c5 brd ff:ff:ff:ff:ff:ff promiscuity 1 minmtu 68 maxmtu 65535
    veth
    bridge_slave state forwarding priority 32 cost 2 hairpin off guard off root_block off fastleave off learning on flood on port_id 0x8001 port_no 0x1 designated_port 32771 designated_cost 0 designated_bridge 2000.6a:78:dc:b8:a:3d designated_root 2000.6a:78:dc:b8:a:3d hold_timer    0.00 message_age_timer   19.74 forward_delay_timer    0.00 topology_change_ack 0 config_pending 0 proxy_arp off proxy_arp_wifi off mcast_fast_leave off mcast_flood on mcast_to_unicast off neigh_suppress off group_fwd_mask 0 group_fwd_mask_str 0x0 vlan_tunnel off isolated off addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

パケットキャプチャを見るのが一番いいですね。

(昔のRoot Bridgeだった)access3からPriority 32768(0x8000)のBPDUが出ていたが、途中からcore1がPriority 8192(0x2000)のBPDUを出しています。

トポロジの確認

Blocking Portを確認してみます。access2_e0とaccess4_e0がblockingになっていますね。

# bridge link | awk '{print $2,$9}' | sort
access1_e0@access2_e0: forwarding
access1_e1@core1_e1: forwarding
access1_e2@tunl0: forwarding
access2_e0@access1_e0: blocking
access2_e1@core2_e1: forwarding
access2_e2@tunl0: forwarding
access3_e0@access4_e0: forwarding
access3_e1@core1_e2: forwarding
access3_e2@tunl0: forwarding
access4_e0@access3_e0: blocking
access4_e1@core2_e2: forwarding
access4_e2@tunl0: forwarding
core1_e0@core2_e0: forwarding
core1_e1@access1_e1: forwarding
core1_e2@access3_e1: forwarding
core2_e0@core1_e0: forwarding
core2_e1@access2_e1: forwarding
core2_e2@access4_e1: forwarding

こっちがPriority変更前。

こっちが変更後です。

段数が少なくなりましたね。変更前は最多で6台のBridgeを経由するケースがありましたが、変更後は最多でも4台で行けるようになりました。
ただ、これはEast-West最適ではなくてNorth-South最適なトポロジですね(access1からaccess2に行くのにcoreを経由している)。
どうするかは後で考えることにしましょう。

ネットワーク途絶時間確認

PingはCtl-Cで終了すると最後のほうに統計を出力してくれますよね。ちなみにさきほどkillall -INTでPingを終了したのですが、-INTを付けないと統計は出力してくれないです。INTerruptシグナルを受けたPingは正常に最後に統計を出して終了しますが、killallはオプションを付けないとTERMinationシグナルを送ります。TERMを受け取ったPingはその場で強制終了させられます。

# grep 'transmitted' pc?-pc?.log
pc1-pc2.log:88 packets transmitted, 57 received, 35.2273% packet loss, time 90432ms
pc1-pc3.log:88 packets transmitted, 88 received, 0% packet loss, time 90432ms
pc1-pc4.log:88 packets transmitted, 55 received, 37.5% packet loss, time 90430ms
pc2-pc3.log:88 packets transmitted, 59 received, +7 errors, 32.9545% packet loss, time 90424ms
pc2-pc4.log:88 packets transmitted, 57 received, +6 errors, 35.2273% packet loss, time 90426ms
pc3-pc4.log:88 packets transmitted, 52 received, 40.9091% packet loss, time 90508ms

このtransmittedとreceivedの引き算をすることで大まかなネットワーク途絶時間を確認してみましょう。

# grep 'transmitted' pc?-pc?.log | awk -F '[: ]' '{print $1,($2-$5);}'
pc1-pc2.log 31
pc1-pc3.log 0
pc1-pc4.log 33
pc2-pc3.log 29
pc2-pc4.log 31
pc3-pc4.log 36

pc1とpc3の間だけ途絶なしで、そのほかは約30秒の途絶ですね。
どうしてですかね? Topology Change Notificationでforwarding portがlistening portになることとか、BridgeのFDB(Forwarding Data Base)あたりが関係しているような気はしますが、深掘りできていないです。気が向いたらじっくり考えてみることにしましょう。

取得したパケットキャプチャはこちら

まとめ

Priorityを変えることでSTPの収束トポロジを変えることができました。完全に思った通りのトポロジではないのでもう少し手直しが必要ですね。

Discussion