> ## Content Index
> Fetch the complete content index at: https://www.pinglabz.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# BPDU Guard Lab: Catch a Rogue Bridge on an Access Port
- URL: https://www.pinglabz.com/bpdu-guard-rogue-bridge-lab/
- Published: 2026-10-21T19:11:31.000Z
- Updated: 2026-10-21T19:11:31.000Z
- Description: Built in my home lab: a Linux bridge sends BPDUs, BPDU Guard err-disables the Cisco access port, and shutdown/no shutdown restores it. Storm control stays untested.
- Author: Jaime
- Tags: STP, Switching, Layer 2, Network Security, Labs, Troubleshooting

BPDU Guard err-disables an access port the moment a BPDU arrives. Built in my home lab on three IOL-L2 switches, this lab proves it end to end: a Linux bridge with STP turned on sent an 802.1D BPDU into a PortFast access port, the switch logged `%SPANTREE-2-BLOCK_BPDUGUARD` and put the port in err-disabled, and `shutdown`/`no shutdown` brought it back once the BPDUs stopped.

The useful proof is the chain: a captured BPDU, a matching switch log, an err-disabled interface, and restored connectivity after repair. There is no broadcast flood and no physical loop in this test. Storm control was also on the plan, but both lab images rejected the commands, so this post treats it as untested. For the command options themselves, see the [BPDU Guard configuration reference](https://www.pinglabz.com/bpdu-guard-configuration-cisco/).

## Lab topology: three IOL-L2 switches and a Linux bridge

I used three IOL-L2 switches and two Alpine containers: IOL-L2 17.18.2 and Alpine 3.24.2 under Containerlab. Use your own licensed Cisco images.

![Lab diagram: BPDU Guard test built in my home lab on three IOL-L2 17.18.2 switches and two Alpine Linux nodes in access VLAN 147, laid out as SW3, SW1 (root), SW2, a Linux bridge BR and host H1. When BR starts sending BPDUs, BPDU Guard on SW2 Ethernet0/2 err-disables the port and pings fail; after the BPDUs stop, shut/no shut restores 5/5 pings with Guard still configured. Storm-control commands were rejected on both IOL-L2 and IOSv-L2, so storm control is shown as untested.](https://storage.ghost.io/c/ff/28/ff28db4a-11e1-4835-b928-158974cf96c1/content/images/2026/10/bpdu-guard-lab-rogue-bridge.png)

```text
SW3 Et0/1 ── Et0/2 SW1 Et0/1 ── Et0/1 SW2 Et0/2 ── eth1 BR eth2 ── eth1 H1
10.47.147.3        10.47.147.1             protected edge                 10.47.147.100
                  STP root              VLAN 147 throughout
```

Every switch link shown is an access link in VLAN 147\. SW1 is the root; SW3 gives H1 a second reachable destination across the tree. BR has no lab IP address. Its `eth0` management interface stays outside the bridge.

Deploy the topology with `containerlab deploy -t <topology-file>`, then open each switch CLI from the Containerlab VM using your usual console or SSH access. The steps below build the baseline used for every check in this post.

On all three switches, create VLAN 147 and select [Rapid PVST+](https://www.pinglabz.com/configure-rapid-pvst-cisco/):

```text
vlan 147
 name BPDU-LAB
exit
spanning-tree mode rapid-pvst
```

Set SW1 to `spanning-tree vlan 147 priority 4096`. SW2 and SW3 use priority 32768\. Put SW1 Ethernet0/1 and Ethernet0/2, SW2 Ethernet0/1 and Ethernet0/2, and SW3 Ethernet0/1 into access VLAN 147:

```text
switchport mode access
switchport access vlan 147
no shutdown
```

On SW1, give `interface Vlan147` address `10.47.147.1 255.255.255.0`; on SW3 use `10.47.147.3 255.255.255.0`. Enable both SVIs with `no shutdown`.

## Baseline: a PortFast edge port forwarding

In the VM shell, set `BR` and `H1` to the Containerlab container names of the bridge and host nodes, then make the Alpine bridge with its STP process initially off:

```sh
BR=<bridge-container>
H1=<host-container>
docker exec "$BR" sh -c '
 apk add --no-cache iproute2 tcpdump
 ip link add br0 type bridge stp_state 0
 ip link set eth1 master br0
 ip link set eth2 master br0
 ip link set eth1 up
 ip link set eth2 up
 ip link set br0 up
'
docker exec "$H1" sh -c '
 ip addr add 10.47.147.100/24 dev eth1
 ip link set eth1 up
'
```

On SW2, configure the host-facing interface:

```text
interface Ethernet0/2
 description HOST-EDGE-VIA-BR
 switchport mode access
 switchport access vlan 147
 spanning-tree portfast
 no shutdown
```

[PortFast](https://www.pinglabz.com/portfast-configuration-cisco-switches/) is appropriate here only while the attached segment behaves as an edge. Do not copy it onto the inter-switch links. Check the root and edge state with `show spanning-tree vlan 147` and `show spanning-tree interface Ethernet0/2 detail`.

Observed excerpt from the converged SW2 baseline:

```text
Et0/1               Root FWD 100       128.2    P2p 
Et0/2               Desg FWD 100       128.3    P2p Edge 
```

From the VM shell, ping both SVIs:

```sh
docker exec "$H1" ping -c 5 -W 1 10.47.147.1
docker exec "$H1" ping -c 5 -W 1 10.47.147.3
```

Both returned five replies from five requests. This establishes forwarding before the fault.

## Enable BPDU Guard and turn on STP on the rogue bridge

At the SW2 CLI:

```text
configure terminal
service timestamps log datetime msec
logging buffered 64000
interface Ethernet0/2
 spanning-tree bpduguard enable
end
show spanning-tree interface Ethernet0/2 detail
show errdisable recovery
```

The interface detail reported `Bpdu guard is enabled`. Automatic recovery for `bpduguard` was disabled in this run (see [errdisable recovery](https://www.pinglabz.com/errdisable-recovery-cisco/) for the timed option, which I did not test). Per-interface BPDU Guard and the global PortFast BPDU Guard default have different scope: the interface command works with or without PortFast, while the global `spanning-tree portfast bpduguard default` only covers ports in PortFast operational state. This walkthrough uses the explicit interface command. Cisco explains both in its [PortFast and BPDU Guard guide](https://www.cisco.com/c/en/us/support/docs/lan-switching/spanning-tree-protocol/10586-65.html?ref=pinglabz.com).

Start a bounded capture from the VM shell, then enable STP on BR:

```sh
docker exec -d "$BR" sh -c '
 timeout -s INT 18 tcpdump -U -i eth1 -s 0 -w /tmp/bpduguard.pcap \
 ether dst 01:80:c2:00:00:00 > /tmp/capture.log 2>&1
'
docker exec "$BR" ip link set br0 type bridge stp_state 1
```

BR now emits IEEE STP BPDUs. A second cable is unnecessary. This is a controlled way to introduce the rogue-bridge signal without creating a live loop.

## Verify the err-disable: syslog, interface status and the BPDU capture

After several seconds, inspect SW2:

```text
show interfaces status err-disabled
show interfaces Ethernet0/2
show logging | include BPDUGUARD|ERR_DISABLE
```

The settled-state output showed:

```text
Et0/2        HOST-EDGE-VI err-disabled bpduguard
Ethernet0/2 is down, line protocol is down (err-disabled) 
```

The first trigger produced these log lines:

```text
*Oct 21 18:31:47.507: %SPANTREE-2-BLOCK_BPDUGUARD: Received BPDU from bridge aac1.ab35.e69e on port Et0/2 with BPDU Guard enabled. Disabling port.
*Oct 21 18:31:47.507: %PM-4-ERR_DISABLE: bpduguard error detected on Et0/2, putting Et0/2 in err-disable state
```

Read the capture after its 18-second timeout:

```sh
docker exec "$BR" tcpdump -nn -e -vv -r /tmp/bpduguard.pcap
```

The capture contains an 802.1D configuration BPDU at 18:31:47.506723 (UTC, like the switch log), with bridge ID `8000.aa:c1:ab:35:e6:9e.8001`. Its bridge MAC matches the switch log. The Ethernet source address belongs to the transmitting bridge port and differs from that bridge ID; compare the BPDU payload when checking the match. (For the field itself, see [how a BPDU carries the bridge ID](https://www.pinglabz.com/how-stp-works-root-bridge-bpdus/).)

H1 lost all five pings to SW1 in the first fault check. Ping loss alone would not prove BPDU Guard: enabling Linux STP can also delay bridge forwarding. The switch's err-disable reason and matching BPDU capture establish the cause.

One timing detail matters: our first status query arrived just before the first BPDU. It briefly showed the port up, followed by the guard log. Repeating the same trigger and checking after a delay produced the settled err-disabled output above. A single early query can miss the transition.

## Recover the err-disabled port with shutdown/no shutdown

For this test, return BR to its original STP-disabled transit state from the VM shell:

```sh
docker exec "$BR" ip link set br0 type bridge stp_state 0
```

In a real incident, remove or correct the unexpected bridge before restoring the port. Here we keep the transit bridge solely to verify the original host path again; switching off its STP process is not a general recommendation for network design.

At the SW2 CLI:

```text
configure terminal
interface Ethernet0/2
 shutdown
 no shutdown
end
show spanning-tree interface Ethernet0/2 detail
show interfaces status
```

BPDU Guard remained configured. H1 again received five replies from five requests to both SW1 and SW3\. The recovery used manual `shutdown` / `no shutdown`; timed automatic recovery was not tested.

BPDU Guard is the guard that err-disables. [Root Guard and Loop Guard](https://www.pinglabz.com/root-guard-loop-guard-cisco-configuration/) block the port instead of err-disabling it.

💡

**Read the err-disable reason before you bounce the port** [PingLabz Pro](https://www.pinglabz.com/membership/?plz%5Fplacement=pricing:bpdu-guard-rogue-bridge-lab:mid:v1) unlocks all 68 browser labs, 500 CCNA practice questions and two timed mock exams for $59/year or $7.99/month.

## Storm control: rejected on IOL-L2 and IOSv-L2, so untested here

The existing [storm-control article](https://www.pinglabz.com/storm-control-configuration/) already says its virtual IOL-L2 nodes reject storm control. This lab reproduced that parser limitation on IOL-L2 17.18.2:

```text
SW2(config-if)#
storm-control broadcast level 1.00 0.50
                ^
% Invalid input detected at '^' marker.
```

This is the exact parser output. Percentage, pps and bps forms, action commands, and storm-control verification commands were also rejected. The parser rejected these commands on this image, so storm control is untested here. A parser rejection does not measure broadcast suppression.

The IOSv-L2 fallback (`vrnetlab/cisco_vios:L2-20200929`, reporting experimental IOS 15.2) rejected the same configuration and show commands with the same `% Invalid input detected` error. That IOSv-L2 run was only a parser comparison; I did not repeat the BPDU Guard test on it. Storm-control enforcement, rate thresholds and storm counters are **untested**. No flood-rate or suppression numbers are claimed.

Do not generalize “no physical ASIC” into a guarantee about every virtual platform. Cisco describes [Catalyst 9000v](https://developer.cisco.com/docs/modeling-labs/cat-9000v/?ref=pinglabz.com) as a beta image with software dataplane emulation. That description alone does not verify storm-control enforcement on a particular release.

BPDU Guard gave us a useful, repeatable access-port protection test on this virtual image. The evidence supports that result; it leaves storm-control enforcement for a separately verified platform. For the rest of the access-edge controls, work through the [Layer 2 hardening checklist](https://www.pinglabz.com/layer-2-attack-surface-hardening/), and for the sister err-disable lab, try the [port security vs MAC flooding lab](https://www.pinglabz.com/cisco-port-security-mac-flooding-lab/).