netfilter: nftables_offload: special ethertype handling for VLAN
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 12 Apr 2021 12:20:55 +0000 (14:20 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Sun, 18 Apr 2021 20:02:21 +0000 (22:02 +0200)
The nftables offload parser sets FLOW_DISSECTOR_KEY_BASIC .n_proto to the
ethertype field in the ethertype frame. However:

- FLOW_DISSECTOR_KEY_BASIC .n_proto field always stores either IPv4 or IPv6
  ethertypes.
- FLOW_DISSECTOR_KEY_VLAN .vlan_tpid stores either the 802.1q and 802.1ad
  ethertypes. Same as for FLOW_DISSECTOR_KEY_CVLAN.

This function adjusts the flow dissector to handle two scenarios:

1) FLOW_DISSECTOR_KEY_VLAN .vlan_tpid is set to 802.1q or 802.1ad.
   Then, transfer:
   - the .n_proto field to FLOW_DISSECTOR_KEY_VLAN .tpid.
   - the original FLOW_DISSECTOR_KEY_VLAN .tpid to the
     FLOW_DISSECTOR_KEY_CVLAN .tpid
   - the original FLOW_DISSECTOR_KEY_CVLAN .tpid to the .n_proto field.

2) .n_proto is set to 802.1q or 802.1ad. Then, transfer:
   - the .n_proto field to FLOW_DISSECTOR_KEY_VLAN .tpid.
   - the original FLOW_DISSECTOR_KEY_VLAN .tpid to the .n_proto field.

Fixes: a82055af5959 ("netfilter: nft_payload: add VLAN offload support")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/nf_tables_offload.c

index 43b56ef..1d42879 100644 (file)
@@ -47,6 +47,48 @@ void nft_flow_rule_set_addr_type(struct nft_flow_rule *flow,
                offsetof(struct nft_flow_key, control);
 }
 
+struct nft_offload_ethertype {
+       __be16 value;
+       __be16 mask;
+};
+
+static void nft_flow_rule_transfer_vlan(struct nft_offload_ctx *ctx,
+                                       struct nft_flow_rule *flow)
+{
+       struct nft_flow_match *match = &flow->match;
+       struct nft_offload_ethertype ethertype;
+
+       if (match->dissector.used_keys & BIT(FLOW_DISSECTOR_KEY_CONTROL) &&
+           match->key.basic.n_proto != htons(ETH_P_8021Q) &&
+           match->key.basic.n_proto != htons(ETH_P_8021AD))
+               return;
+
+       ethertype.value = match->key.basic.n_proto;
+       ethertype.mask = match->mask.basic.n_proto;
+
+       if (match->dissector.used_keys & BIT(FLOW_DISSECTOR_KEY_VLAN) &&
+           (match->key.vlan.vlan_tpid == htons(ETH_P_8021Q) ||
+            match->key.vlan.vlan_tpid == htons(ETH_P_8021AD))) {
+               match->key.basic.n_proto = match->key.cvlan.vlan_tpid;
+               match->mask.basic.n_proto = match->mask.cvlan.vlan_tpid;
+               match->key.cvlan.vlan_tpid = match->key.vlan.vlan_tpid;
+               match->mask.cvlan.vlan_tpid = match->mask.vlan.vlan_tpid;
+               match->key.vlan.vlan_tpid = ethertype.value;
+               match->mask.vlan.vlan_tpid = ethertype.mask;
+               match->dissector.offset[FLOW_DISSECTOR_KEY_CVLAN] =
+                       offsetof(struct nft_flow_key, cvlan);
+               match->dissector.used_keys |= BIT(FLOW_DISSECTOR_KEY_CVLAN);
+       } else {
+               match->key.basic.n_proto = match->key.vlan.vlan_tpid;
+               match->mask.basic.n_proto = match->mask.vlan.vlan_tpid;
+               match->key.vlan.vlan_tpid = ethertype.value;
+               match->mask.vlan.vlan_tpid = ethertype.mask;
+               match->dissector.offset[FLOW_DISSECTOR_KEY_VLAN] =
+                       offsetof(struct nft_flow_key, vlan);
+               match->dissector.used_keys |= BIT(FLOW_DISSECTOR_KEY_VLAN);
+       }
+}
+
 struct nft_flow_rule *nft_flow_rule_create(struct net *net,
                                           const struct nft_rule *rule)
 {
@@ -91,6 +133,8 @@ struct nft_flow_rule *nft_flow_rule_create(struct net *net,
 
                expr = nft_expr_next(expr);
        }
+       nft_flow_rule_transfer_vlan(ctx, flow);
+
        flow->proto = ctx->dep.l3num;
        kfree(ctx);