From 0607df28d249a616e72730ed30a8b61d583cae93 Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Fri, 21 Aug 2026 11:49:50 +0100 Subject: [PATCH 1/7] DOCS-2997: Write the guide for preparing an existing bridge Fill in the guide for nodes whose only uplink also carries the node's own IP address, where Calico cannot create the bridge itself. The guide is written around configuring the host to bring the bridge up correctly at boot. Moving a running IP address onto a bridge is the dangerous way to reach the same end state, so the page does not teach it. The page states the three properties Calico checks, and what it deliberately does not require. It covers why the bridge MAC address must be set explicitly and must not be the trunk's, the VLAN membership the bridge device needs in its own right, and two hazards that are not settings: a bridge named with the prefix Calico treats as its own, and the startup cost of spanning tree. Verification runs in two rounds, because the failures that matter most only appear when the first workload attaches. Co-Authored-By: Claude Opus 5 (1M context) --- .../networking/l2-bridge/byo-bridge.mdx | 274 +++++++++++++++++- 1 file changed, 258 insertions(+), 16 deletions(-) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 6ab1096304..60c46df5ee 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -6,66 +6,308 @@ description: Configure a Linux bridge on your nodes so Calico Enterprise can use :::note -L2 bridge networking is a tech preview feature. APIs and behavior may change before GA. +L2 bridge networking is a tech preview feature. +APIs and behavior may change before GA. ::: -Use this guide when the uplink that must carry your VLANs is also the one your node's IP address depends on. $[prodname] cannot take that interface over on its own, so you build the bridge and $[prodname] uses it. +Use this guide when the interface that must carry your VLANs is also the one your node's IP address depends on. +$[prodname] cannot take that interface over on its own, so you build the bridge and $[prodname] uses it. +For why, see [Who owns the host network configuration](about-l2-bridge.mdx#who-owns-the-host-network-configuration). -The goal is not to move a running address onto a bridge. It is to have the host bring the bridge up correctly at boot, so the node's address arrives in the right place the first time. +If $[prodname] can have an interface of its own, you do not need this guide. +Use [Connect workloads to an existing VLAN](connect-vlan.mdx) instead. + +:::note + +The goal is not to move a running IP address onto a bridge. +It is to have the host bring the bridge up correctly at boot, so the node's address arrives on the right device the first time. +Configure this the way you configure the rest of your host networking, and verify it survives a reboot. + +::: ## Before you begin -This procedure changes how your nodes get their IP addresses. Read this section before you start. +This procedure changes how your nodes obtain their IP addresses. +A mistake takes the node off the network, and you will not be able to fix it over the network. + +You need: + +- Root access to the node, and ownership of its network configuration. +- Out-of-band access to the node, such as a console, or a tested way to roll the configuration back. +- The name of the interface that will become the trunk. +- The VLAN your host's own traffic uses, or confirmation that it is untagged. +- The VLAN IDs your workloads will use. +- The node's IP address, prefix length, and default gateway. + +Work on one non-critical node first, confirm it, and only then apply the same configuration to the rest of the cluster. ## What you are building -Before and after, so the configuration further down makes sense. +Today, your node's IP address sits directly on a physical interface, and that interface carries the node's traffic. + +Afterwards, that interface is a port on a bridge and carries no address of its own. +The bridge carries your workload VLANs, and the node's address sits either on the bridge or on a VLAN device layered on top of it. +$[prodname] attaches workloads to the same bridge as access ports. ## What $[prodname] requires -Three settings on the bridge. $[prodname] checks all three and will not use a bridge that is missing any of them. +$[prodname] checks three properties of the bridge and refuses to use it if any are missing. +It does not set them for you, because your host network configuration owns them and would undo the change. + +| Requirement | Setting | Why | +| ----------- | ------- | --- | +| VLAN filtering enabled | `vlan_filtering=1` | Without it the bridge ignores VLAN tags entirely, so segments are not isolated from each other. | +| An explicitly set MAC address | Any address, set deliberately | Stops the bridge's address changing as workloads come and go. See below. | +| The 802.1Q tag protocol | `vlan_protocol=802.1Q` | $[prodname] programs 802.1Q VLAN membership and creates 802.1Q sub-devices. On an 802.1ad bridge none of it matches. | + +:::caution + +Enable VLAN filtering **before** you put the node's address on the bridge, or as part of the same configuration change. +Turning it on makes the kernel start enforcing VLAN membership immediately, and an address on a bridge with no VLAN membership stops receiving and sending traffic the moment filtering comes on. + +::: ### Why the MAC address must be pinned, and must not be the trunk's -Two separate failures, both avoided by the same setting. +A Linux bridge has no MAC address of its own. +By default the kernel borrows one from whichever attached port has the numerically lowest address, and it re-runs that choice every time a port is added or removed. + +On an L2 bridge the ports are workload interfaces, which appear and disappear constantly. +Left to itself, the bridge's address changes under your node, and if the node's IP address is on that bridge, its traffic stops being delivered. + +Setting an address explicitly stops the kernel from ever reconsidering it. + +Do not use the trunk interface's own MAC address for the bridge. +When two devices in the same path present the same address, some network card drivers reject the second programming attempt and the bridge is left non-functional. +Use a distinct address. + +A locally administered address is a good choice, because it cannot collide with a real card. +Set the second least significant bit of the first byte: `02:00:00:00:00:01` and `0a:1b:2c:3d:4e:5f` are both locally administered. ### What $[prodname] does not require -Just as useful as the required list, and it keeps the required list credible. +Just as useful as the list above, and it keeps that list credible. + +- **Enslaving the trunk.** + $[prodname] adds the trunk interface to the bridge itself. + The interface only has to exist and be named in the `Network`. +- **A particular default PVID.** + $[prodname] does not read or change the bridge's default PVID. +- **Removing an existing VLAN device.** + If you already have a VLAN sub-device on the bridge, $[prodname] reuses it rather than failing. +- **Bringing the bridge up.** + $[prodname] brings up a bridge that is administratively down. +- **Self VLAN memberships.** + $[prodname] never adds or removes the bridge device's own VLAN memberships, or their PVID and untagged flags. + They are yours to manage. ### Two hazards that are not settings -A naming collision that deletes your bridge, and an option that quietly slows workload startup. +**Do not name the bridge with a `calb-` prefix.** +$[prodname] treats that prefix as meaning it owns the bridge, and it deletes bridges it owns when they are no longer needed. +A bridge of your own named that way can be deleted from under you. +Nothing rejects the name when you create the `Network`, so this is on you to avoid. + +**Spanning Tree Protocol is allowed, but it costs workload startup time.** +Every new access port waits out a forwarding delay before it passes traffic, which delays every workload attaching to the bridge. +$[prodname] warns once and leaves STP alone on a bridge it did not create. +Turn it off unless you need it. ## Configure the host -Write this as persistent configuration. A change that does not survive a reboot is a trap. +Write this as persistent configuration, using whatever manages networking on your nodes. +A change made with `ip` commands alone disappears at the next reboot, and the node comes back with its address on an interface that is now a bridge port. + +The steps below describe what the configuration has to achieve. +The example that follows shows one way to express it. + +1. Create a bridge, with VLAN filtering enabled, the 802.1Q tag protocol, STP disabled, and an explicitly set MAC address that is not the trunk's. +2. Add the trunk interface to the bridge as a port, and give that port membership of every VLAN it must carry, including the VLAN your host traffic uses. +3. Give the bridge device itself membership of the VLAN your host traffic uses. + See [Self VLAN membership](#self-vlan-membership). +4. Remove the IP configuration from the physical interface, so it carries no address. +5. Put the node's IP address, prefix, and default gateway on the bridge or on a VLAN device above it, depending on whether your host traffic is tagged. + See [Where the host's address goes](#where-the-hosts-address-goes). +6. Apply the configuration and confirm the node is still reachable. +7. Reboot the node and confirm it comes back with the same addressing. ### Where the host's address goes -Two shapes, depending on whether your host's own traffic is tagged. +Two shapes, depending on your host's own traffic. + +**Untagged host traffic.** +Put the node's address directly on the bridge device. +The bridge port facing the trunk needs the host's traffic to arrive untagged, which means setting a native VLAN on the `Network` later so $[prodname] knows which VLAN untagged traffic belongs to. + +**Host traffic on its own VLAN.** +Create a VLAN device on top of the bridge for that VLAN — for example, VLAN 100 on `br-l2` gives `br-l2.100` — and put the node's address on the VLAN device. +The bridge itself carries no address. + +The distinction matters and is easy to invert. +A VLAN device **above** the bridge, holding the host's address, is the supported arrangement. +A VLAN device **below** the bridge, standing in for the trunk, is not supported. ### Self VLAN membership -The step that is easiest to miss, and the one most likely to take a node off the network. +For the host to receive traffic on a VLAN through the bridge, the bridge device needs membership of that VLAN in its own right. +This is separate from the membership you give the trunk port, and it is the step most likely to be missed. + +Without it, the bridge forwards that VLAN's traffic between its ports but never delivers any of it to the host, so a node whose address depends on that VLAN goes dark. + +$[prodname] does not add this membership and does not remove one you have added. + +Some configuration tools have no field for it. +Where that is the case, set it with a hook that runs when the bridge comes up: + +```bash +bridge vlan add dev br-l2 vid 100 self +``` + +Confirm it with `bridge vlan show`, where the bridge device appears in the list alongside its ports. ### An annotated example -A complete configuration with every line explained. +This example uses NetworkManager keyfiles, for a node whose management traffic is on VLAN 100 and whose workloads will use VLANs 10 and 20. +The trunk is `eno1`. + +```ini +# /etc/NetworkManager/system-connections/br-l2.nmconnection +[connection] +id=br-l2 +type=bridge +interface-name=br-l2 + +[bridge] +# Required. Without this the bridge ignores VLAN tags. +vlan-filtering=true +# Required. Calico programs 802.1Q membership and sub-devices. +vlan-protocol=802.1Q +# Not required, but STP delays every workload that attaches. +stp=false + +[ethernet] +# Required. A locally administered address, deliberately not eno1's. +cloned-mac-address=02:00:00:5e:00:64 + +[ipv4] +# The bridge carries no address of its own. The VLAN device above it does. +method=disabled + +[ipv6] +method=disabled +``` + +```ini +# /etc/NetworkManager/system-connections/br-l2-port-eno1.nmconnection +[connection] +id=br-l2-port-eno1 +type=ethernet +interface-name=eno1 +master=br-l2 +slave-type=bridge + +[bridge-port] +# The trunk carries every VLAN the bridge serves, including management. +vlans=10,20,100 +``` + +```ini +# /etc/NetworkManager/system-connections/br-l2.100.nmconnection +[connection] +id=br-l2.100 +type=vlan +interface-name=br-l2.100 + +[vlan] +# A VLAN device above the bridge. This is where the node's address lives. +parent=br-l2 +id=100 + +[ipv4] +method=manual +address1=10.10.100.11/24,10.10.100.1 +``` + +The bridge device's own membership of VLAN 100 is not expressible here, so add it with a dispatcher script: + +```bash +# /etc/NetworkManager/dispatcher.d/50-br-l2-self-vlan +#!/bin/sh +[ "$1" = "br-l2" ] && [ "$2" = "up" ] && bridge vlan add dev br-l2 vid 100 self +exit 0 +``` + +Make it executable and owned by root, or NetworkManager ignores it. + +:::caution + +Validate this in a lab before you apply it to a cluster you care about. +Host network configuration varies with distribution and with whatever manages it, and the failure mode is a node you cannot reach. + +::: ## Point Felix at the right L3 device -Once the node's address lives on the bridge, $[prodname] needs to be told where to find it. +Once the node's address is on the bridge or on a VLAN device above it, $[prodname] has to be told where to find it. +Otherwise it attaches its programs to the physical interface, which no longer has an address, and traffic between workloads on different nodes fails. + +1. Find the current value of `bpfDataIfacePattern` in the default [FelixConfiguration](../../reference/resources/felixconfig.mdx). +2. Add a pattern that matches the device holding the node's address. + For a node whose address is on `br-l2.100`, `br-l2.*` matches it. +3. Apply the change and confirm `calico-node` restarts cleanly. + +You do not need to add the trunk interface to this pattern. +$[prodname] attaches to the trunk as part of setting up the L2 network. ## Verify before handing the bridge over -Two rounds of checks. Some failures only appear when the first workload attaches. +Check in two rounds. +Some problems only appear when the first workload attaches, so the first round is not sufficient on its own. + +Before you create a `Network`: + +1. Confirm the node is reachable on its own address, and that its default route is intact. +2. Run `ip -br addr show` and confirm the address is on the device you intended and the physical interface has none. +3. Run `bridge link show` and confirm the trunk is a port of the bridge. +4. Run `bridge vlan show` and confirm the trunk port carries every VLAN you expect, and that the bridge device itself appears with the host VLAN. +5. Reboot the node. + Repeat steps 1 to 4. + +After you create a `Network` and attach the first workload: + +1. Confirm the node is still reachable. + A bridge MAC that was not pinned changes at this point. +2. Run `bridge link show` again and confirm the workload's interface joined the bridge. +3. Confirm the workload can reach its gateway. ## Reference the bridge from a Network +1. Set the bridge in the `Network`'s host configuration: + + ```yaml + bridge: + existingBridge: + name: br-l2 + ``` + +2. Name the trunk interface in the same host configuration entry. + Omitting it leaves the node unable to route workload traffic. +3. Continue with [Connect workloads to an existing VLAN](connect-vlan.mdx) to create the IP pools, the attachment definitions, and your first workload. + ## If $[prodname] will not use your bridge -What the refusal looks like, and where to look first. +$[prodname] fails closed. +If the bridge does not meet all three requirements, nothing is programmed and workloads on that `Network` stay administratively down rather than attaching to a bridge that cannot isolate them. + +The only signal is the Felix log in `calico-node`, which names the bridge, the `Network`, and the command that fixes it, and repeats every few seconds until you do. +There is no Kubernetes event and no status on the workload. + +See [Troubleshoot L2 network connectivity](troubleshoot.mdx). ## Additional resources + +- [About L2 bridge networking](about-l2-bridge.mdx) +- [Connect workloads to an existing VLAN](connect-vlan.mdx) +- [L2 bridge support and limitations](../../reference/l2-bridge-support.mdx) +- [Network resource](../../reference/resources/network.mdx) From d21b9b5ed0b55a3ae03a31d246c5506553cec7ac Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Fri, 21 Aug 2026 14:49:10 +0100 Subject: [PATCH 2/7] DOCS-2997: Record the user story for the prepared-bridge guide Add a comment at the top of the page naming the user story it exists to serve. The page ends by handing the reader to the setup guide, so the two pages complete one path between them. That relationship is recorded as subsidiary, to make clear that the host-side work is a distinct goal rather than a section that belongs in the other guide. Co-Authored-By: Claude Opus 5 (1M context) --- calico-enterprise/networking/l2-bridge/byo-bridge.mdx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 60c46df5ee..90a553c3d4 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -2,6 +2,16 @@ description: Configure a Linux bridge on your nodes so Calico Enterprise can use it, for nodes whose only uplink also carries the node's own IP address. --- +{/* + User story, primary + S5: As a network engineer whose nodes have a single uplink that already carries the host's own IP address, I want to prepare a bridge Calico can use, so that VMs can share that uplink without handing Calico control of my host networking. + + User stories, subsidiary + S4: hands the reader to the setup guide once the bridge is ready, so the two pages complete one path together. + + Story ladder and page plan: DOCS-2997. +*/} + # Prepare an existing bridge for $[prodname] :::note From 4f60be2018cb4463d521d5b8e71c575acc44b852 Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Thu, 27 Aug 2026 13:48:21 +0100 Subject: [PATCH 3/7] DOCS-2997: Apply review feedback to the existing bridge guide Drop the instruction to avoid the trunk's MAC address. The trunk's address is a good choice on a working driver, and avoiding it does not help on the one driver where this fails. Document the Intel ice fault instead: what breaks, that nothing reports it, the forwarding database entry to remove, and why capturing on the trunk hides the fault. Correct two statements about what Calico does. It expects the trunk to be a bridge port before it starts, and it does add the bridge's own VLAN memberships for the VLANs in the Network, while never removing any. Memberships therefore accumulate, which the reader has to clean up. Give STP its real cost: two forwarding delays, around 30 seconds at the default, and a way to lower it. Warn that a trunk not named in the Network fails the eBPF reverse path filter check and takes the node off the network (CORE-13089). --- .../networking/l2-bridge/byo-bridge.mdx | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 90a553c3d4..3c2c9f5f55 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -78,7 +78,7 @@ Turning it on makes the kernel start enforcing VLAN membership immediately, and ::: -### Why the MAC address must be pinned, and must not be the trunk's +### Why the MAC address must be pinned A Linux bridge has no MAC address of its own. By default the kernel borrows one from whichever attached port has the numerically lowest address, and it re-runs that choice every time a port is added or removed. @@ -87,42 +87,54 @@ On an L2 bridge the ports are workload interfaces, which appear and disappear co Left to itself, the bridge's address changes under your node, and if the node's IP address is on that bridge, its traffic stops being delivered. Setting an address explicitly stops the kernel from ever reconsidering it. - -Do not use the trunk interface's own MAC address for the bridge. -When two devices in the same path present the same address, some network card drivers reject the second programming attempt and the bridge is left non-functional. -Use a distinct address. +Any address will do, as long as it is set deliberately and does not change. A locally administered address is a good choice, because it cannot collide with a real card. Set the second least significant bit of the first byte: `02:00:00:00:00:01` and `0a:1b:2c:3d:4e:5f` are both locally administered. +#### Intel ice NICs + +On a network card driven by Intel's `ice` driver, enslaving the card to a VLAN-filtering bridge breaks MAC filtering in hardware. +The kernel builds a list of MAC addresses to admit, and the physical card's own address in that list makes filter programming stop early. +Workload and management addresses are dropped from the hardware filter and their inbound traffic is discarded, with no counter and no log to show it. + +To avoid it, remove the physical card's MAC address from the bridge's forwarding database when the bridge comes up. +That requires the bridge and any VLAN devices above it to carry MAC addresses of their own, distinct from the card's, so nothing else depends on the entry you are removing. +Set this up the same way as the self VLAN membership below, with a hook that fires when the bridge comes up. + +:::caution + +Do not run `tcpdump` on an `ice` trunk interface to investigate this. +Capturing puts the card into promiscuous mode, which masks the fault while you are looking at it and heals it until you stop. +Capture from another interface instead. + +::: + ### What $[prodname] does not require Just as useful as the list above, and it keeps that list credible. -- **Enslaving the trunk.** - $[prodname] adds the trunk interface to the bridge itself. - The interface only has to exist and be named in the `Network`. - **A particular default PVID.** $[prodname] does not read or change the bridge's default PVID. - **Removing an existing VLAN device.** If you already have a VLAN sub-device on the bridge, $[prodname] reuses it rather than failing. - **Bringing the bridge up.** $[prodname] brings up a bridge that is administratively down. -- **Self VLAN memberships.** - $[prodname] never adds or removes the bridge device's own VLAN memberships, or their PVID and untagged flags. - They are yours to manage. ### Two hazards that are not settings **Do not name the bridge with a `calb-` prefix.** -$[prodname] treats that prefix as meaning it owns the bridge, and it deletes bridges it owns when they are no longer needed. +$[prodname] names the bridges it creates after the `Network` they belong to, as `calb-`, and treats that prefix as meaning it owns the bridge. +It deletes bridges it owns when they are no longer needed. A bridge of your own named that way can be deleted from under you. Nothing rejects the name when you create the `Network`, so this is on you to avoid. **Spanning Tree Protocol is allowed, but it costs workload startup time.** -Every new access port waits out a forwarding delay before it passes traffic, which delays every workload attaching to the bridge. +With STP enabled, every new access port passes through the listening and learning states before it forwards, waiting a full forwarding delay in each. +At the kernel default of 15 seconds that is around 30 seconds before a workload has connectivity, every time one starts. $[prodname] warns once and leaves STP alone on a bridge it did not create. -Turn it off unless you need it. +Turn STP off unless you need it. +If you do need it, lower the bridge's `forward_delay` so the wait is tolerable. ## Configure the host @@ -132,8 +144,9 @@ A change made with `ip` commands alone disappears at the next reboot, and the no The steps below describe what the configuration has to achieve. The example that follows shows one way to express it. -1. Create a bridge, with VLAN filtering enabled, the 802.1Q tag protocol, STP disabled, and an explicitly set MAC address that is not the trunk's. +1. Create a bridge, with VLAN filtering enabled, the 802.1Q tag protocol, STP disabled, and an explicitly set MAC address. 2. Add the trunk interface to the bridge as a port, and give that port membership of every VLAN it must carry, including the VLAN your host traffic uses. + Attach the trunk as part of this configuration, so it is already a bridge port before $[prodname] starts. 3. Give the bridge device itself membership of the VLAN your host traffic uses. See [Self VLAN membership](#self-vlan-membership). 4. Remove the IP configuration from the physical interface, so it carries no address. @@ -165,7 +178,12 @@ This is separate from the membership you give the trunk port, and it is the step Without it, the bridge forwards that VLAN's traffic between its ports but never delivers any of it to the host, so a node whose address depends on that VLAN goes dark. -$[prodname] does not add this membership and does not remove one you have added. +$[prodname] adds this membership for the VLANs named in the `Network`, and never removes a membership once it exists — including one you added yourself. +Your host's own VLAN is not named in the `Network`, so that membership is yours to add, and it persists. + +Because memberships are never removed, they accumulate. +Deleting a `Network`, or taking a node out of one by changing its labels, leaves the bridge holding the VLAN memberships it was given, including on the trunk port. +Remove them yourself on a node that should no longer carry those VLANs. Some configuration tools have no field for it. Where that is the case, set it with a hook that runs when the bridge comes up: @@ -302,7 +320,13 @@ After you create a `Network` and attach the first workload: ``` 2. Name the trunk interface in the same host configuration entry. - Omitting it leaves the node unable to route workload traffic. + + :::caution + + Name the trunk in the `Network`. + A trunk that $[prodname] uses but that is not named in the `Network` fails the eBPF reverse path filter check, and the node loses network access. + + ::: 3. Continue with [Connect workloads to an existing VLAN](connect-vlan.mdx) to create the IP pools, the attachment definitions, and your first workload. ## If $[prodname] will not use your bridge From 37020ef08170c81fafb46e89fff35c7495d51dca Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Thu, 27 Aug 2026 15:25:32 +0100 Subject: [PATCH 4/7] DOCS-2997: Situate the reader in the existing bridge guide Open by saying what the feature needs and which situation this page addresses, so a reader landing here can tell whether it applies to them before reading further. Name the case it does not cover and where to go instead. Use a concrete interface example on both sides of the fork, matching the VLAN setup guide, while keeping the rule itself about which interface carries the node's address rather than how many interfaces a node has. Demote the note about boot-time configuration to body text. It followed the tech preview admonition immediately and read oddly as a second one. --- .../networking/l2-bridge/byo-bridge.mdx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 3c2c9f5f55..5ed3925a7d 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -21,21 +21,22 @@ APIs and behavior may change before GA. ::: -Use this guide when the interface that must carry your VLANs is also the one your node's IP address depends on. -$[prodname] cannot take that interface over on its own, so you build the bridge and $[prodname] uses it. +L2 bridge networking puts VMs and pods directly onto the VLANs your existing network already carries, so a workload keeps the address and MAC address other systems know it by. +To do that, $[prodname] needs a VLAN-aware bridge on each node, with the trunk that carries those VLANs attached to it. + +This guide is for the case where $[prodname] cannot build that bridge for you, because the interface carrying your VLANs is also the one your node's IP address depends on — a node whose primary NIC is `eth0` and whose L2 trunk is also `eth0`. +Moving a node's address onto a bridge means reconfiguring whatever owns that address, NetworkManager or systemd-networkd, and getting it wrong takes the node off the network with no way back in. +So you build the bridge as part of your host network configuration, and $[prodname] uses it. For why, see [Who owns the host network configuration](about-l2-bridge.mdx#who-owns-the-host-network-configuration). -If $[prodname] can have an interface of its own, you do not need this guide. +You do not need this guide if your L2 trunk is separate from your host's primary NIC — the primary NIC is `eth0` and the trunk is `eth1`. +$[prodname] can take that trunk over and build the bridge itself. Use [Connect workloads to an existing VLAN](connect-vlan.mdx) instead. -:::note - -The goal is not to move a running IP address onto a bridge. +The goal here is not to move a running IP address onto a bridge. It is to have the host bring the bridge up correctly at boot, so the node's address arrives on the right device the first time. Configure this the way you configure the rest of your host networking, and verify it survives a reboot. -::: - ## Before you begin This procedure changes how your nodes obtain their IP addresses. From 4cde9b6207c53a76a4131db8da250c725f25f59e Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Thu, 27 Aug 2026 17:04:38 +0100 Subject: [PATCH 5/7] DOCS-2997: Note the bridge MTU and the Intel ice MAC exception Calico does not manage the bridge MTU, but attaching workloads lowers it, and a VLAN device carrying the node's address drops with it. Tell the operator to pin it. Point the MAC requirement at the ice exception, so the table and the driver guidance below it do not appear to disagree. --- calico-enterprise/networking/l2-bridge/byo-bridge.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 5ed3925a7d..58d9733746 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -69,7 +69,7 @@ It does not set them for you, because your host network configuration owns them | Requirement | Setting | Why | | ----------- | ------- | --- | | VLAN filtering enabled | `vlan_filtering=1` | Without it the bridge ignores VLAN tags entirely, so segments are not isolated from each other. | -| An explicitly set MAC address | Any address, set deliberately | Stops the bridge's address changing as workloads come and go. See below. | +| An explicitly set MAC address | Any address, set deliberately | Stops the bridge's address changing as workloads come and go. On an Intel `ice` NIC it must also differ from the card's own address. See below. | | The 802.1Q tag protocol | `vlan_protocol=802.1Q` | $[prodname] programs 802.1Q VLAN membership and creates 802.1Q sub-devices. On an 802.1ad bridge none of it matches. | :::caution @@ -115,6 +115,10 @@ Capture from another interface instead. Just as useful as the list above, and it keeps that list credible. +- **A particular MTU.** + $[prodname] does not read or set the bridge's MTU, but it does change it in practice. + Workload interfaces are attached at the cluster-wide MTU, and a Linux bridge takes the lowest MTU of its ports, so the bridge drops to that value when the first workload attaches — and any VLAN device above it, including the one holding the node's address, drops with it. + Pin the MTU on the bridge and its VLAN devices, or set `mtu` in the `calicoNetwork` section of the `Installation` to match your network. - **A particular default PVID.** $[prodname] does not read or change the bridge's default PVID. - **Removing an existing VLAN device.** From a124eec2d642a7a8337cc9532725a9e2ed6f1035 Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Thu, 27 Aug 2026 17:14:58 +0100 Subject: [PATCH 6/7] DOCS-2997: Say how to extend bpfDataIfacePattern The setting is a single regular expression rather than a list, so add an alternative was ambiguous between extending and replacing. Replacing it stops Calico attaching programs to a node's other data interfaces. Show the command to read the current value, the default it falls back to, and a worked result. --- .../networking/l2-bridge/byo-bridge.mdx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 58d9733746..557c26109f 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -285,9 +285,24 @@ Host network configuration varies with distribution and with whatever manages it Once the node's address is on the bridge or on a VLAN device above it, $[prodname] has to be told where to find it. Otherwise it attaches its programs to the physical interface, which no longer has an address, and traffic between workloads on different nodes fails. -1. Find the current value of `bpfDataIfacePattern` in the default [FelixConfiguration](../../reference/resources/felixconfig.mdx). -2. Add a pattern that matches the device holding the node's address. - For a node whose address is on `br-l2.100`, `br-l2.*` matches it. +`bpfDataIfacePattern` is a single regular expression, not a list, so extend the existing value rather than replacing it. +Removing the alternatives already there stops $[prodname] attaching its programs to your other data interfaces. + +1. Read the current value from the default [FelixConfiguration](../../reference/resources/felixconfig.mdx): + + ```bash + kubectl get felixconfiguration default -o jsonpath='{.spec.bpfDataIfacePattern}' + ``` + + If nothing is set, the default is `^((en|wl|ww|sl|ib)[Popsx].*|(eth|wlan|wwan|bond).*)`. + +2. Add an alternative matching the device that holds the node's address, keeping everything already in the expression. + For a node whose address is on `br-l2.100`: + + ``` + ^((en|wl|ww|sl|ib)[Popsx].*|(eth|wlan|wwan|bond).*|br-l2.*) + ``` + 3. Apply the change and confirm `calico-node` restarts cleanly. You do not need to add the trunk interface to this pattern. From 1da274be232613c64024a4b049d052ad20917cdf Mon Sep 17 00:00:00 2001 From: Chris Tauchen Date: Thu, 27 Aug 2026 17:25:02 +0100 Subject: [PATCH 7/7] DOCS-2997: Warn about VLAN drift on a bridge you manage The node's configuration and the Network are two sources of truth with nothing keeping them in sync. A VLAN added to the Network but not to the host survives until the next reactivation or configuration management run, then stops silently and does not recover without restarting calico-node. Add the warning by the trunk example, show the nmstate form OpenShift readers actually use, and note the default PVID entries that otherwise confuse a reading of bridge vlan show. Add a verification round for later host network changes, which is where this failure appears, and say that a bridge Calico owns is not exposed. --- .../networking/l2-bridge/byo-bridge.mdx | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx index 557c26109f..c8bf18faf9 100644 --- a/calico-enterprise/networking/l2-bridge/byo-bridge.mdx +++ b/calico-enterprise/networking/l2-bridge/byo-bridge.mdx @@ -245,6 +245,36 @@ slave-type=bridge vlans=10,20,100 ``` +:::caution + +Keep this list in step with the `Network`. +Every VLAN the `Network` uses must appear in `vlans=` here, on every node. + +Your host configuration and the `Network` are two sources of truth, and nothing keeps them in sync. +Add a VLAN to the `Network` without adding it here and that VLAN keeps working until the next time this connection is reactivated, or your configuration management reapplies it. +It then stops, silently: the node stays `Ready`, workloads stay `Running`, and the `Network` reports nothing. +It does not recover on its own — only restarting `calico-node` brings it back. + +The same applies to a native VLAN, where the PVID and untagged flags revert too, so the host's own untagged path breaks with it. + +::: + +On OpenShift, where the same configuration is usually written as a `NodeNetworkConfigurationPolicy`, the trunk port takes the same list: + +```yaml +port: + - name: eno1 + vlan: + mode: trunk + trunk-tags: + - id: 10 + - id: 20 + - id: 100 +``` + +Set `vlan-default-pvid: 0` on the bridge as well. +Without it the default PVID of 1 leaves stray `1 PVID Egress Untagged` entries on the bridge and its ports, which are harmless but confusing when you are reading `bridge vlan show` to diagnose something else. + ```ini # /etc/NetworkManager/system-connections/br-l2.100.nmconnection [connection] @@ -329,6 +359,18 @@ After you create a `Network` and attach the first workload: 2. Run `bridge link show` again and confirm the workload's interface joined the bridge. 3. Confirm the workload can reach its gateway. +After any later change to the host's network configuration: + +1. Run `bridge vlan show` and confirm the trunk still carries every VLAN the `Network` uses. + A change that reapplies the bridge or trunk configuration can remove VLAN memberships $[prodname] added, and nothing reports it: the node stays `Ready`, workloads stay `Running`, and only traffic on the affected VLAN stops. +2. If a VLAN is missing, add it to your host configuration so it survives the next reapply, then restart `calico-node` on that node. + +This is the check worth building a habit around. +The other rounds catch mistakes made while setting the bridge up; this one catches the change made weeks later, which is the failure that bites in production. + +A bridge $[prodname] owns is not exposed to this. +NetworkManager leaves it alone, and the VLAN list on the trunk cannot drift from the `Network`, because nothing else is managing it. + ## Reference the bridge from a Network 1. Set the bridge in the `Network`'s host configuration: