Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nft_payload: work around vlan header stripping

make payload expression aware of the fact that VLAN offload may have
removed a vlan header.

When we encounter tagged skb, transparently insert the tag into the
register so that vlan header matching can work without userspace being
aware of offload features.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Florian Westphal and committed by
Pablo Neira Ayuso
8cfd23e6 5e8018fc

+56 -1
+56 -1
net/netfilter/nft_payload.c
··· 9 9 */ 10 10 11 11 #include <linux/kernel.h> 12 + #include <linux/if_vlan.h> 12 13 #include <linux/init.h> 13 14 #include <linux/module.h> 14 15 #include <linux/netlink.h> ··· 17 16 #include <linux/netfilter/nf_tables.h> 18 17 #include <net/netfilter/nf_tables_core.h> 19 18 #include <net/netfilter/nf_tables.h> 19 + 20 + /* add vlan header into the user buffer for if tag was removed by offloads */ 21 + static bool 22 + nft_payload_copy_vlan(u32 *d, const struct sk_buff *skb, u8 offset, u8 len) 23 + { 24 + int mac_off = skb_mac_header(skb) - skb->data; 25 + u8 vlan_len, *vlanh, *dst_u8 = (u8 *) d; 26 + struct vlan_ethhdr veth; 27 + 28 + vlanh = (u8 *) &veth; 29 + if (offset < ETH_HLEN) { 30 + u8 ethlen = min_t(u8, len, ETH_HLEN - offset); 31 + 32 + if (skb_copy_bits(skb, mac_off, &veth, ETH_HLEN)) 33 + return false; 34 + 35 + veth.h_vlan_proto = skb->vlan_proto; 36 + 37 + memcpy(dst_u8, vlanh + offset, ethlen); 38 + 39 + len -= ethlen; 40 + if (len == 0) 41 + return true; 42 + 43 + dst_u8 += ethlen; 44 + offset = ETH_HLEN; 45 + } else if (offset >= VLAN_ETH_HLEN) { 46 + offset -= VLAN_HLEN; 47 + goto skip; 48 + } 49 + 50 + veth.h_vlan_TCI = htons(skb_vlan_tag_get(skb)); 51 + veth.h_vlan_encapsulated_proto = skb->protocol; 52 + 53 + vlanh += offset; 54 + 55 + vlan_len = min_t(u8, len, VLAN_ETH_HLEN - offset); 56 + memcpy(dst_u8, vlanh, vlan_len); 57 + 58 + len -= vlan_len; 59 + if (!len) 60 + return true; 61 + 62 + dst_u8 += vlan_len; 63 + skip: 64 + return skb_copy_bits(skb, offset + mac_off, dst_u8, len) == 0; 65 + } 20 66 21 67 static void nft_payload_eval(const struct nft_expr *expr, 22 68 struct nft_regs *regs, ··· 74 26 u32 *dest = &regs->data[priv->dreg]; 75 27 int offset; 76 28 29 + dest[priv->len / NFT_REG32_SIZE] = 0; 77 30 switch (priv->base) { 78 31 case NFT_PAYLOAD_LL_HEADER: 79 32 if (!skb_mac_header_was_set(skb)) 80 33 goto err; 34 + 35 + if (skb_vlan_tag_present(skb)) { 36 + if (!nft_payload_copy_vlan(dest, skb, 37 + priv->offset, priv->len)) 38 + goto err; 39 + return; 40 + } 81 41 offset = skb_mac_header(skb) - skb->data; 82 42 break; 83 43 case NFT_PAYLOAD_NETWORK_HEADER: ··· 99 43 } 100 44 offset += priv->offset; 101 45 102 - dest[priv->len / NFT_REG32_SIZE] = 0; 103 46 if (skb_copy_bits(skb, offset, dest, priv->len) < 0) 104 47 goto err; 105 48 return;