[PATCH nf v2 1/2] netfilter: br_netfilter: restore VLAN tag on refragmented IPv6 packets
From: Andrea Parri
Date: Sun Oct 04 2026 - 17:26:05 EST
Bridged IPv6 packets can lose their VLAN tag or acquire an unrelated tag
when conntrack-reassembled packets are refragmented. On a VLAN-aware
bridge, or on any bridge with bridge-nf-filter-vlan-tagged enabled, this
can send fragments with a different VLAN tag from the one selected for
forwarding.
br_nf_push_frag_xmit() restores the VLAN tag from per-CPU storage, but
only the IPv4 branch of br_nf_dev_queue_xmit() saves it. The IPv6 branch
leaves the saved tag from the previous IPv4 refragmentation on that CPU.
Newly allocated fragments do not inherit the tag through
ip6_copy_metadata().
Commit d7b597421519 ("netfilter: bridge: restore vlan tag when
refragmenting") added VLAN tag restoration for IPv4 only, shortly after
IPv6 refragmentation was introduced.
This was reproduced on a VLAN-aware bridge by first refragmenting an IPv4
VLAN 100 flow on the same CPU. All fragments of subsequent IPv6 flows left
with stale VLAN 100 instead of the VLAN selected for their egress ports.
Move saving the L2 header and VLAN tag into br_nf_save_frag_data() and
call it from both branches, so the fragments that ip6_fragment() builds
use the current packet's VLAN information.
Fixes: efb6de9b4ba0 ("netfilter: bridge: forward IPv6 fragmented packets")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Andrea Parri <parri.andrea@xxxxxxxxx>
---
net/bridge/br_netfilter_hooks.c | 45 +++++++++++++++------------------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/net/bridge/br_netfilter_hooks.c b/net/bridge/br_netfilter_hooks.c
index 0a394e5f43916..fe8d2910dae21 100644
--- a/net/bridge/br_netfilter_hooks.c
+++ b/net/bridge/br_netfilter_hooks.c
@@ -832,6 +832,25 @@ static unsigned int nf_bridge_mtu_reduction(const struct sk_buff *skb)
return 0;
}
+/* Saved for br_nf_push_frag_xmit() to restore on every fragment. */
+static void br_nf_save_frag_data(const struct sk_buff *skb)
+{
+ struct brnf_frag_data *data = this_cpu_ptr(&brnf_frag_data_storage);
+
+ if (skb_vlan_tag_present(skb)) {
+ data->vlan_tci = skb->vlan_tci;
+ data->vlan_proto = skb->vlan_proto;
+ } else {
+ data->vlan_proto = 0;
+ }
+
+ data->encap_size = nf_bridge_encap_header_len(skb);
+ data->size = ETH_HLEN + data->encap_size;
+
+ skb_copy_from_linear_data_offset(skb, -data->size, data->mac,
+ data->size);
+}
+
static int br_nf_dev_queue_xmit(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
@@ -866,28 +885,13 @@ static int br_nf_dev_queue_xmit(struct net *net, struct sock *sk, struct sk_buff
*/
if (IS_ENABLED(CONFIG_NF_DEFRAG_IPV4) &&
skb->protocol == htons(ETH_P_IP)) {
- struct brnf_frag_data *data;
-
if (br_validate_ipv4(net, skb))
goto drop;
IPCB(skb)->frag_max_size = nf_bridge->frag_max_size;
local_lock_nested_bh(&brnf_frag_data_storage.bh_lock);
- data = this_cpu_ptr(&brnf_frag_data_storage);
-
- if (skb_vlan_tag_present(skb)) {
- data->vlan_tci = skb->vlan_tci;
- data->vlan_proto = skb->vlan_proto;
- } else {
- data->vlan_proto = 0;
- }
-
- data->encap_size = nf_bridge_encap_header_len(skb);
- data->size = ETH_HLEN + data->encap_size;
-
- skb_copy_from_linear_data_offset(skb, -data->size, data->mac,
- data->size);
+ br_nf_save_frag_data(skb);
ret = br_nf_ip_fragment(net, sk, skb, br_nf_push_frag_xmit);
local_unlock_nested_bh(&brnf_frag_data_storage.bh_lock);
@@ -895,20 +899,13 @@ static int br_nf_dev_queue_xmit(struct net *net, struct sock *sk, struct sk_buff
}
if (IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) &&
skb->protocol == htons(ETH_P_IPV6)) {
- struct brnf_frag_data *data;
-
if (br_validate_ipv6(net, skb))
goto drop;
IP6CB(skb)->frag_max_size = nf_bridge->frag_max_size;
local_lock_nested_bh(&brnf_frag_data_storage.bh_lock);
- data = this_cpu_ptr(&brnf_frag_data_storage);
- data->encap_size = nf_bridge_encap_header_len(skb);
- data->size = ETH_HLEN + data->encap_size;
-
- skb_copy_from_linear_data_offset(skb, -data->size, data->mac,
- data->size);
+ br_nf_save_frag_data(skb);
ret = ip6_fragment(net, sk, skb, br_nf_push_frag_xmit);
local_unlock_nested_bh(&brnf_frag_data_storage.bh_lock);
--
2.53.0