This adds the capability to evaluate 802.1ad, QinQ, PPPoE and PPPoE-in-Q
packets in the bridge filter chain.

Signed-off-by: Eric Woudstra <[email protected]>
---
 include/net/netfilter/nf_tables.h | 48 +++++++++++++++++++++++++++++++
 net/netfilter/nft_chain_filter.c  | 17 +++++++++--
 2 files changed, 62 insertions(+), 3 deletions(-)

diff --git a/include/net/netfilter/nf_tables.h 
b/include/net/netfilter/nf_tables.h
index e2128663b160..4a55972881b1 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -10,6 +10,7 @@
 #include <linux/netfilter/nf_tables.h>
 #include <linux/u64_stats_sync.h>
 #include <linux/rhashtable.h>
+#include <linux/if_vlan.h>
 #include <net/netfilter/nf_flow_table.h>
 #include <net/netlink.h>
 #include <net/flow_offload.h>
@@ -88,6 +89,53 @@ static inline void nft_set_pktinfo_unspec(struct nft_pktinfo 
*pkt)
        pkt->fragoff = 0;
 }
 
+/**
+ * nft_set_bridge_pktinfo - calls nft_set_pktinfo and advances
+ * network_header to the header that follows the pppoe- or vlan-header.
+ */
+static inline 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;
+
+               if (!pskb_may_pull(skb, PPPOE_SES_HLEN))
+                       return -1;
+               ph = (struct ppp_hdr *)(skb->data);
+               switch (ph->proto) {
+               case htons(PPP_IP):
+                       *proto = htons(ETH_P_IP);
+                       skb_set_network_header(skb, PPPOE_SES_HLEN);
+                       return PPPOE_SES_HLEN;
+               case htons(PPP_IPV6):
+                       *proto = htons(ETH_P_IPV6);
+                       skb_set_network_header(skb, PPPOE_SES_HLEN);
+                       return PPPOE_SES_HLEN;
+               }
+               break;
+       }
+       case htons(ETH_P_8021Q): {
+               struct vlan_hdr *vhdr;
+
+               if (!pskb_may_pull(skb, VLAN_HLEN))
+                       return -1;
+               vhdr = (struct vlan_hdr *)(skb->data);
+               *proto = vhdr->h_vlan_encapsulated_proto;
+               skb_set_network_header(skb, VLAN_HLEN);
+               return VLAN_HLEN;
+       }
+       }
+       return 0;
+}
+
 /**
  *     struct nft_verdict - nf_tables verdict
  *
diff --git a/net/netfilter/nft_chain_filter.c b/net/netfilter/nft_chain_filter.c
index b16185e9a6dd..a5174adb1abc 100644
--- a/net/netfilter/nft_chain_filter.c
+++ b/net/netfilter/nft_chain_filter.c
@@ -233,10 +233,16 @@ nft_do_chain_bridge(void *priv,
                    const struct nf_hook_state *state)
 {
        struct nft_pktinfo pkt;
+       int ret, offset;
+       __be16 proto;
 
-       nft_set_pktinfo(&pkt, skb, state);
+       proto = eth_hdr(skb)->h_proto;
+
+       offset = nft_set_bridge_pktinfo(&pkt, skb, state, &proto);
+       if (offset < 0)
+               return NF_ACCEPT;
 
-       switch (eth_hdr(skb)->h_proto) {
+       switch (proto) {
        case htons(ETH_P_IP):
                nft_set_pktinfo_ipv4_validate(&pkt);
                break;
@@ -248,7 +254,12 @@ nft_do_chain_bridge(void *priv,
                break;
        }
 
-       return nft_do_chain(&pkt, priv);
+       ret = nft_do_chain(&pkt, priv);
+
+       if (offset && ret == NF_ACCEPT)
+               skb_reset_network_header(skb);
+
+       return ret;
 }
 
 static const struct nft_chain_type nft_chain_filter_bridge = {
-- 
2.50.0


Reply via email to