In nft_do_chain_bridge() pktinfo is only fully populated for plain packets and packets encapsulated in single 802.1q or 802.1ad. When implementing the software bridge-fastpath and testing all possible encapulations, there can be more encapsulations: The packet could (also) be encapsulated in PPPoE, or the packet could be encapsulated in an inner 802.1q, combined with an outer 802.1ad or 802.1q encapsulation. nft_flow_offload_eval() also examines the L4 header, with the L4 protocol known from the conntrack-tuplehash. To access the header it uses nft_thoff(), but for these packets it returns zero. Introduce nft_set_bridge_pktinfo() to help populate pktinfo with the offsets. Signed-off-by: Eric Woudstra --- net/netfilter/nft_chain_filter.c | 55 +++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c index d4d5eadaba9c..66ef30c60e56 100644 --- a/net/netfilter/nft_chain_filter.c +++ b/net/netfilter/nft_chain_filter.c @@ -227,21 +227,68 @@ static inline void nft_chain_filter_inet_fini(void) {} #endif /* CONFIG_NF_TABLES_IPV6 */ #if IS_ENABLED(CONFIG_NF_TABLES_BRIDGE) +static int nft_set_bridge_pktinfo(struct nft_pktinfo *pkt, struct sk_buff *skb, + const struct nf_hook_state *state, + __be16 *proto) +{ + nft_set_pktinfo(pkt, skb, state); + + switch (*proto) { + case htons(ETH_P_PPP_SES): { + struct ppp_hdr { + struct pppoe_hdr hdr; + __be16 proto; + } *ph, _ph; + + ph = skb_header_pointer(skb, 0, sizeof(_ph), &_ph); + if (!ph) { + *proto = 0; + return -1; + } + switch (ph->proto) { + case htons(PPP_IP): + *proto = htons(ETH_P_IP); + return PPPOE_SES_HLEN; + case htons(PPP_IPV6): + *proto = htons(ETH_P_IPV6); + return PPPOE_SES_HLEN; + } + break; + } + case htons(ETH_P_8021Q): { + struct vlan_hdr *vhdr, _vhdr; + + vhdr = skb_header_pointer(skb, 0, sizeof(_vhdr), &_vhdr); + if (!vhdr) { + *proto = 0; + return -1; + } + *proto = vhdr->h_vlan_encapsulated_proto; + return VLAN_HLEN; + } + } + return 0; +} + static unsigned int nft_do_chain_bridge(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct nft_pktinfo pkt; + __be16 proto; + int offset; - nft_set_pktinfo(&pkt, skb, state); + proto = eth_hdr(skb)->h_proto; + + offset = nft_set_bridge_pktinfo(&pkt, skb, state, &proto); - switch (eth_hdr(skb)->h_proto) { + switch (proto) { case htons(ETH_P_IP): - nft_set_pktinfo_ipv4_validate(&pkt, 0); + nft_set_pktinfo_ipv4_validate(&pkt, offset); break; case htons(ETH_P_IPV6): - nft_set_pktinfo_ipv6_validate(&pkt, 0); + nft_set_pktinfo_ipv6_validate(&pkt, offset); break; default: nft_set_pktinfo_unspec(&pkt); -- 2.53.0