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@vger.kernel.org Assisted-by: LLM Signed-off-by: Andrea Parri --- 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