netfilter: nf_tables: add hardware offload support
authorPablo Neira Ayuso <pablo@netfilter.org>
Tue, 9 Jul 2019 21:00:43 +0000 (23:00 +0200)
committerDavid S. Miller <davem@davemloft.net>
Tue, 9 Jul 2019 21:38:51 +0000 (14:38 -0700)
This patch adds hardware offload support for nftables through the
existing netdev_ops->ndo_setup_tc() interface, the TC_SETUP_CLSFLOWER
classifier and the flow rule API. This hardware offload support is
available for the NFPROTO_NETDEV family and the ingress hook.

Each nftables expression has a new ->offload interface, that is used to
populate the flow rule object that is attached to the transaction
object.

There is a new per-table NFT_TABLE_F_HW flag, that is set on to offload
an entire table, including all of its chains.

This patch supports for basic metadata (layer 3 and 4 protocol numbers),
5-tuple payload matching and the accept/drop actions; this also includes
basechain hardware offload only.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/netfilter/nf_tables.h
include/net/netfilter/nf_tables_offload.h [new file with mode: 0644]
include/uapi/linux/netfilter/nf_tables.h
net/netfilter/Makefile
net/netfilter/nf_tables_api.c
net/netfilter/nf_tables_offload.c [new file with mode: 0644]
net/netfilter/nft_cmp.c
net/netfilter/nft_immediate.c
net/netfilter/nft_meta.c
net/netfilter/nft_payload.c

index 9e8493a..35dfdd9 100644 (file)
@@ -161,6 +161,7 @@ struct nft_ctx {
        const struct nlattr * const     *nla;
        u32                             portid;
        u32                             seq;
+       u16                             flags;
        u8                              family;
        u8                              level;
        bool                            report;
@@ -735,6 +736,9 @@ enum nft_trans_phase {
        NFT_TRANS_RELEASE
 };
 
+struct nft_flow_rule;
+struct nft_offload_ctx;
+
 /**
  *     struct nft_expr_ops - nf_tables expression operations
  *
@@ -777,6 +781,10 @@ struct nft_expr_ops {
                                                    const struct nft_data **data);
        bool                            (*gc)(struct net *net,
                                              const struct nft_expr *expr);
+       int                             (*offload)(struct nft_offload_ctx *ctx,
+                                                  struct nft_flow_rule *flow,
+                                                  const struct nft_expr *expr);
+       u32                             offload_flags;
        const struct nft_expr_type      *type;
        void                            *data;
 };
@@ -859,6 +867,7 @@ static inline struct nft_userdata *nft_userdata(const struct nft_rule *rule)
 
 enum nft_chain_flags {
        NFT_BASE_CHAIN                  = 0x1,
+       NFT_CHAIN_HW_OFFLOAD            = 0x2,
 };
 
 /**
@@ -942,6 +951,7 @@ struct nft_stats {
  *     @stats: per-cpu chain stats
  *     @chain: the chain
  *     @dev_name: device name that this base chain is attached to (if any)
+ *     @cb_list: list of flow block callbacks (for hardware offload)
  */
 struct nft_base_chain {
        struct nf_hook_ops              ops;
@@ -951,6 +961,7 @@ struct nft_base_chain {
        struct nft_stats __percpu       *stats;
        struct nft_chain                chain;
        char                            dev_name[IFNAMSIZ];
+       struct list_head                cb_list;
 };
 
 static inline struct nft_base_chain *nft_base_chain(const struct nft_chain *chain)
@@ -1322,11 +1333,14 @@ struct nft_trans {
 
 struct nft_trans_rule {
        struct nft_rule                 *rule;
+       struct nft_flow_rule            *flow;
        u32                             rule_id;
 };
 
 #define nft_trans_rule(trans)  \
        (((struct nft_trans_rule *)trans->data)->rule)
+#define nft_trans_flow_rule(trans)     \
+       (((struct nft_trans_rule *)trans->data)->flow)
 #define nft_trans_rule_id(trans)       \
        (((struct nft_trans_rule *)trans->data)->rule_id)
 
diff --git a/include/net/netfilter/nf_tables_offload.h b/include/net/netfilter/nf_tables_offload.h
new file mode 100644 (file)
index 0000000..3196663
--- /dev/null
@@ -0,0 +1,76 @@
+#ifndef _NET_NF_TABLES_OFFLOAD_H
+#define _NET_NF_TABLES_OFFLOAD_H
+
+#include <net/flow_offload.h>
+#include <net/netfilter/nf_tables.h>
+
+struct nft_offload_reg {
+       u32             key;
+       u32             len;
+       u32             base_offset;
+       u32             offset;
+       struct nft_data mask;
+};
+
+enum nft_offload_dep_type {
+       NFT_OFFLOAD_DEP_UNSPEC  = 0,
+       NFT_OFFLOAD_DEP_NETWORK,
+       NFT_OFFLOAD_DEP_TRANSPORT,
+};
+
+struct nft_offload_ctx {
+       struct {
+               enum nft_offload_dep_type       type;
+               __be16                          l3num;
+               u8                              protonum;
+       } dep;
+       unsigned int                            num_actions;
+       struct nft_offload_reg                  regs[NFT_REG32_15 + 1];
+};
+
+void nft_offload_set_dependency(struct nft_offload_ctx *ctx,
+                               enum nft_offload_dep_type type);
+void nft_offload_update_dependency(struct nft_offload_ctx *ctx,
+                                  const void *data, u32 len);
+
+struct nft_flow_key {
+       struct flow_dissector_key_basic                 basic;
+       union {
+               struct flow_dissector_key_ipv4_addrs    ipv4;
+               struct flow_dissector_key_ipv6_addrs    ipv6;
+       };
+       struct flow_dissector_key_ports                 tp;
+       struct flow_dissector_key_ip                    ip;
+       struct flow_dissector_key_vlan                  vlan;
+       struct flow_dissector_key_eth_addrs             eth_addrs;
+} __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */
+
+struct nft_flow_match {
+       struct flow_dissector   dissector;
+       struct nft_flow_key     key;
+       struct nft_flow_key     mask;
+};
+
+struct nft_flow_rule {
+       __be16                  proto;
+       struct nft_flow_match   match;
+       struct flow_rule        *rule;
+};
+
+#define NFT_OFFLOAD_F_ACTION   (1 << 0)
+
+struct nft_rule;
+struct nft_flow_rule *nft_flow_rule_create(const struct nft_rule *rule);
+void nft_flow_rule_destroy(struct nft_flow_rule *flow);
+int nft_flow_rule_offload_commit(struct net *net);
+
+#define NFT_OFFLOAD_MATCH(__key, __base, __field, __len, __reg)                \
+       (__reg)->base_offset    =                                       \
+               offsetof(struct nft_flow_key, __base);                  \
+       (__reg)->offset         =                                       \
+               offsetof(struct nft_flow_key, __base.__field);          \
+       (__reg)->len            = __len;                                \
+       (__reg)->key            = __key;                                \
+       memset(&(__reg)->mask, 0xff, (__reg)->len);
+
+#endif
index 0e3462d..82abaa1 100644 (file)
@@ -192,6 +192,7 @@ enum nft_table_attributes {
  * @NFTA_CHAIN_USE: number of references to this chain (NLA_U32)
  * @NFTA_CHAIN_TYPE: type name of the string (NLA_NUL_STRING)
  * @NFTA_CHAIN_COUNTERS: counter specification of the chain (NLA_NESTED: nft_counter_attributes)
+ * @NFTA_CHAIN_FLAGS: chain flags
  */
 enum nft_chain_attributes {
        NFTA_CHAIN_UNSPEC,
@@ -204,6 +205,7 @@ enum nft_chain_attributes {
        NFTA_CHAIN_TYPE,
        NFTA_CHAIN_COUNTERS,
        NFTA_CHAIN_PAD,
+       NFTA_CHAIN_FLAGS,
        __NFTA_CHAIN_MAX
 };
 #define NFTA_CHAIN_MAX         (__NFTA_CHAIN_MAX - 1)
index deada20..9270a7f 100644 (file)
@@ -78,7 +78,7 @@ nf_tables-objs := nf_tables_core.o nf_tables_api.o nft_chain_filter.o \
                  nf_tables_trace.o nft_immediate.o nft_cmp.o nft_range.o \
                  nft_bitwise.o nft_byteorder.o nft_payload.o nft_lookup.o \
                  nft_dynset.o nft_meta.o nft_rt.o nft_exthdr.o \
-                 nft_chain_route.o
+                 nft_chain_route.o nf_tables_offload.o
 
 nf_tables_set-objs := nf_tables_set_core.o \
                      nft_set_hash.o nft_set_bitmap.o nft_set_rbtree.o
index d22d00c..ed17a7c 100644 (file)
@@ -18,6 +18,7 @@
 #include <net/netfilter/nf_flow_table.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_offload.h>
 #include <net/net_namespace.h>
 #include <net/sock.h>
 
@@ -97,6 +98,7 @@ static void nft_ctx_init(struct nft_ctx *ctx,
        ctx->nla        = nla;
        ctx->portid     = NETLINK_CB(skb).portid;
        ctx->report     = nlmsg_report(nlh);
+       ctx->flags      = nlh->nlmsg_flags;
        ctx->seq        = nlh->nlmsg_seq;
 }
 
@@ -1169,6 +1171,7 @@ static const struct nla_policy nft_chain_policy[NFTA_CHAIN_MAX + 1] = {
        [NFTA_CHAIN_POLICY]     = { .type = NLA_U32 },
        [NFTA_CHAIN_TYPE]       = { .type = NLA_STRING },
        [NFTA_CHAIN_COUNTERS]   = { .type = NLA_NESTED },
+       [NFTA_CHAIN_FLAGS]      = { .type = NLA_U32 },
 };
 
 static const struct nla_policy nft_hook_policy[NFTA_HOOK_MAX + 1] = {
@@ -1603,7 +1606,7 @@ static struct nft_rule **nf_tables_chain_alloc_rules(const struct nft_chain *cha
 }
 
 static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
-                             u8 policy)
+                             u8 policy, u32 flags)
 {
        const struct nlattr * const *nla = ctx->nla;
        struct nft_table *table = ctx->table;
@@ -1657,8 +1660,9 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                ops->hook       = hook.type->hooks[ops->hooknum];
                ops->dev        = hook.dev;
 
-               chain->flags |= NFT_BASE_CHAIN;
+               chain->flags |= NFT_BASE_CHAIN | flags;
                basechain->policy = NF_ACCEPT;
+               INIT_LIST_HEAD(&basechain->cb_list);
        } else {
                chain = kzalloc(sizeof(*chain), GFP_KERNEL);
                if (chain == NULL)
@@ -1718,7 +1722,8 @@ err1:
        return err;
 }
 
-static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy)
+static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
+                             u32 flags)
 {
        const struct nlattr * const *nla = ctx->nla;
        struct nft_table *table = ctx->table;
@@ -1730,6 +1735,9 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy)
        struct nft_trans *trans;
        int err;
 
+       if (chain->flags ^ flags)
+               return -EOPNOTSUPP;
+
        if (nla[NFTA_CHAIN_HOOK]) {
                if (!nft_is_base_chain(chain))
                        return -EBUSY;
@@ -1835,6 +1843,7 @@ static int nf_tables_newchain(struct net *net, struct sock *nlsk,
        u8 policy = NF_ACCEPT;
        struct nft_ctx ctx;
        u64 handle = 0;
+       u32 flags = 0;
 
        lockdep_assert_held(&net->nft.commit_mutex);
 
@@ -1889,6 +1898,9 @@ static int nf_tables_newchain(struct net *net, struct sock *nlsk,
                }
        }
 
+       if (nla[NFTA_CHAIN_FLAGS])
+               flags = ntohl(nla_get_be32(nla[NFTA_CHAIN_FLAGS]));
+
        nft_ctx_init(&ctx, net, skb, nlh, family, table, chain, nla);
 
        if (chain != NULL) {
@@ -1899,10 +1911,10 @@ static int nf_tables_newchain(struct net *net, struct sock *nlsk,
                if (nlh->nlmsg_flags & NLM_F_REPLACE)
                        return -EOPNOTSUPP;
 
-               return nf_tables_updchain(&ctx, genmask, policy);
+               return nf_tables_updchain(&ctx, genmask, policy, flags);
        }
 
-       return nf_tables_addchain(&ctx, family, genmask, policy);
+       return nf_tables_addchain(&ctx, family, genmask, policy, flags);
 }
 
 static int nf_tables_delchain(struct net *net, struct sock *nlsk,
@@ -2658,6 +2670,7 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk,
        u8 genmask = nft_genmask_next(net);
        struct nft_expr_info *info = NULL;
        int family = nfmsg->nfgen_family;
+       struct nft_flow_rule *flow;
        struct nft_table *table;
        struct nft_chain *chain;
        struct nft_rule *rule, *old_rule = NULL;
@@ -2804,7 +2817,8 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk,
 
                list_add_tail_rcu(&rule->list, &old_rule->list);
        } else {
-               if (nft_trans_rule_add(&ctx, NFT_MSG_NEWRULE, rule) == NULL) {
+               trans = nft_trans_rule_add(&ctx, NFT_MSG_NEWRULE, rule);
+               if (!trans) {
                        err = -ENOMEM;
                        goto err2;
                }
@@ -2827,6 +2841,14 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk,
        if (net->nft.validate_state == NFT_VALIDATE_DO)
                return nft_table_validate(net, table);
 
+       if (chain->flags & NFT_CHAIN_HW_OFFLOAD) {
+               flow = nft_flow_rule_create(rule);
+               if (IS_ERR(flow))
+                       return PTR_ERR(flow);
+
+               nft_trans_flow_rule(trans) = flow;
+       }
+
        return 0;
 err2:
        nf_tables_rule_release(&ctx, rule);
@@ -6624,6 +6646,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
        struct nft_trans_elem *te;
        struct nft_chain *chain;
        struct nft_table *table;
+       int err;
 
        if (list_empty(&net->nft.commit_list)) {
                mutex_unlock(&net->nft.commit_mutex);
@@ -6634,6 +6657,10 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
        if (nf_tables_validate(net) < 0)
                return -EAGAIN;
 
+       err = nft_flow_rule_offload_commit(net);
+       if (err < 0)
+               return err;
+
        /* 1.  Allocate space for next generation rules_gen_X[] */
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                int ret;
diff --git a/net/netfilter/nf_tables_offload.c b/net/netfilter/nf_tables_offload.c
new file mode 100644 (file)
index 0000000..2c33028
--- /dev/null
@@ -0,0 +1,267 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <net/flow_offload.h>
+#include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_offload.h>
+#include <net/pkt_cls.h>
+
+static struct nft_flow_rule *nft_flow_rule_alloc(int num_actions)
+{
+       struct nft_flow_rule *flow;
+
+       flow = kzalloc(sizeof(struct nft_flow_rule), GFP_KERNEL);
+       if (!flow)
+               return NULL;
+
+       flow->rule = flow_rule_alloc(num_actions);
+       if (!flow->rule) {
+               kfree(flow);
+               return NULL;
+       }
+
+       flow->rule->match.dissector     = &flow->match.dissector;
+       flow->rule->match.mask          = &flow->match.mask;
+       flow->rule->match.key           = &flow->match.key;
+
+       return flow;
+}
+
+struct nft_flow_rule *nft_flow_rule_create(const struct nft_rule *rule)
+{
+       struct nft_offload_ctx ctx = {
+               .dep    = {
+                       .type   = NFT_OFFLOAD_DEP_UNSPEC,
+               },
+       };
+       struct nft_flow_rule *flow;
+       int num_actions = 0, err;
+       struct nft_expr *expr;
+
+       expr = nft_expr_first(rule);
+       while (expr->ops && expr != nft_expr_last(rule)) {
+               if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
+                       num_actions++;
+
+               expr = nft_expr_next(expr);
+       }
+
+       flow = nft_flow_rule_alloc(num_actions);
+       if (!flow)
+               return ERR_PTR(-ENOMEM);
+
+       expr = nft_expr_first(rule);
+       while (expr->ops && expr != nft_expr_last(rule)) {
+               if (!expr->ops->offload) {
+                       err = -EOPNOTSUPP;
+                       goto err_out;
+               }
+               err = expr->ops->offload(&ctx, flow, expr);
+               if (err < 0)
+                       goto err_out;
+
+               expr = nft_expr_next(expr);
+       }
+       flow->proto = ctx.dep.l3num;
+
+       return flow;
+err_out:
+       nft_flow_rule_destroy(flow);
+
+       return ERR_PTR(err);
+}
+
+void nft_flow_rule_destroy(struct nft_flow_rule *flow)
+{
+       kfree(flow->rule);
+       kfree(flow);
+}
+
+void nft_offload_set_dependency(struct nft_offload_ctx *ctx,
+                               enum nft_offload_dep_type type)
+{
+       ctx->dep.type = type;
+}
+
+void nft_offload_update_dependency(struct nft_offload_ctx *ctx,
+                                  const void *data, u32 len)
+{
+       switch (ctx->dep.type) {
+       case NFT_OFFLOAD_DEP_NETWORK:
+               WARN_ON(len != sizeof(__u16));
+               memcpy(&ctx->dep.l3num, data, sizeof(__u16));
+               break;
+       case NFT_OFFLOAD_DEP_TRANSPORT:
+               WARN_ON(len != sizeof(__u8));
+               memcpy(&ctx->dep.protonum, data, sizeof(__u8));
+               break;
+       default:
+               break;
+       }
+       ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;
+}
+
+static void nft_flow_offload_common_init(struct flow_cls_common_offload *common,
+                                        __be16 proto,
+                                       struct netlink_ext_ack *extack)
+{
+       common->protocol = proto;
+       common->extack = extack;
+}
+
+static int nft_setup_cb_call(struct nft_base_chain *basechain,
+                            enum tc_setup_type type, void *type_data)
+{
+       struct flow_block_cb *block_cb;
+       int err;
+
+       list_for_each_entry(block_cb, &basechain->cb_list, list) {
+               err = block_cb->cb(type, type_data, block_cb->cb_priv);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+static int nft_flow_offload_rule(struct nft_trans *trans,
+                                enum flow_cls_command command)
+{
+       struct nft_flow_rule *flow = nft_trans_flow_rule(trans);
+       struct nft_rule *rule = nft_trans_rule(trans);
+       struct flow_cls_offload cls_flow = {};
+       struct nft_base_chain *basechain;
+       struct netlink_ext_ack extack;
+       __be16 proto = ETH_P_ALL;
+
+       if (!nft_is_base_chain(trans->ctx.chain))
+               return -EOPNOTSUPP;
+
+       basechain = nft_base_chain(trans->ctx.chain);
+
+       if (flow)
+               proto = flow->proto;
+
+       nft_flow_offload_common_init(&cls_flow.common, proto, &extack);
+       cls_flow.command = command;
+       cls_flow.cookie = (unsigned long) rule;
+       if (flow)
+               cls_flow.rule = flow->rule;
+
+       return nft_setup_cb_call(basechain, TC_SETUP_CLSFLOWER, &cls_flow);
+}
+
+static int nft_flow_offload_bind(struct flow_block_offload *bo,
+                                struct nft_base_chain *basechain)
+{
+       list_splice(&bo->cb_list, &basechain->cb_list);
+       return 0;
+}
+
+static int nft_flow_offload_unbind(struct flow_block_offload *bo,
+                                  struct nft_base_chain *basechain)
+{
+       struct flow_block_cb *block_cb, *next;
+
+       list_for_each_entry_safe(block_cb, next, &bo->cb_list, list) {
+               list_del(&block_cb->list);
+               flow_block_cb_free(block_cb);
+       }
+
+       return 0;
+}
+
+#define FLOW_SETUP_BLOCK TC_SETUP_BLOCK
+
+static int nft_flow_offload_chain(struct nft_trans *trans,
+                                 enum flow_block_command cmd)
+{
+       struct nft_chain *chain = trans->ctx.chain;
+       struct netlink_ext_ack extack = {};
+       struct flow_block_offload bo = {};
+       struct nft_base_chain *basechain;
+       struct net_device *dev;
+       int err;
+
+       if (!nft_is_base_chain(chain))
+               return -EOPNOTSUPP;
+
+       basechain = nft_base_chain(chain);
+       dev = basechain->ops.dev;
+       if (!dev || !dev->netdev_ops->ndo_setup_tc)
+               return -EOPNOTSUPP;
+
+       /* Only default policy to accept is supported for now. */
+       if (cmd == FLOW_BLOCK_BIND &&
+           nft_trans_chain_policy(trans) != -1 &&
+           nft_trans_chain_policy(trans) != NF_ACCEPT)
+               return -EOPNOTSUPP;
+
+       bo.command = cmd;
+       bo.binder_type = FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS;
+       bo.extack = &extack;
+       INIT_LIST_HEAD(&bo.cb_list);
+
+       err = dev->netdev_ops->ndo_setup_tc(dev, FLOW_SETUP_BLOCK, &bo);
+       if (err < 0)
+               return err;
+
+       switch (cmd) {
+       case FLOW_BLOCK_BIND:
+               err = nft_flow_offload_bind(&bo, basechain);
+               break;
+       case FLOW_BLOCK_UNBIND:
+               err = nft_flow_offload_unbind(&bo, basechain);
+               break;
+       }
+
+       return err;
+}
+
+int nft_flow_rule_offload_commit(struct net *net)
+{
+       struct nft_trans *trans;
+       int err = 0;
+
+       list_for_each_entry(trans, &net->nft.commit_list, list) {
+               if (trans->ctx.family != NFPROTO_NETDEV)
+                       continue;
+
+               switch (trans->msg_type) {
+               case NFT_MSG_NEWCHAIN:
+                       if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD))
+                               continue;
+
+                       err = nft_flow_offload_chain(trans, FLOW_BLOCK_BIND);
+                       break;
+               case NFT_MSG_DELCHAIN:
+                       if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD))
+                               continue;
+
+                       err = nft_flow_offload_chain(trans, FLOW_BLOCK_UNBIND);
+                       break;
+               case NFT_MSG_NEWRULE:
+                       if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD))
+                               continue;
+
+                       if (trans->ctx.flags & NLM_F_REPLACE ||
+                           !(trans->ctx.flags & NLM_F_APPEND))
+                               return -EOPNOTSUPP;
+
+                       err = nft_flow_offload_rule(trans, FLOW_CLS_REPLACE);
+                       nft_flow_rule_destroy(nft_trans_flow_rule(trans));
+                       break;
+               case NFT_MSG_DELRULE:
+                       if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD))
+                               continue;
+
+                       err = nft_flow_offload_rule(trans, FLOW_CLS_DESTROY);
+                       break;
+               }
+
+               if (err)
+                       return err;
+       }
+
+       return err;
+}
index 411c0cf..bd173b1 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/netfilter.h>
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
+#include <net/netfilter/nf_tables_offload.h>
 #include <net/netfilter/nf_tables.h>
 
 struct nft_cmp_expr {
@@ -107,12 +108,44 @@ nla_put_failure:
        return -1;
 }
 
+static int __nft_cmp_offload(struct nft_offload_ctx *ctx,
+                            struct nft_flow_rule *flow,
+                            const struct nft_cmp_expr *priv)
+{
+       struct nft_offload_reg *reg = &ctx->regs[priv->sreg];
+       u8 *mask = (u8 *)&flow->match.mask;
+       u8 *key = (u8 *)&flow->match.key;
+
+       if (priv->op != NFT_CMP_EQ)
+               return -EOPNOTSUPP;
+
+       memcpy(key + reg->offset, &priv->data, priv->len);
+       memcpy(mask + reg->offset, &reg->mask, priv->len);
+
+       flow->match.dissector.used_keys |= BIT(reg->key);
+       flow->match.dissector.offset[reg->key] = reg->base_offset;
+
+       nft_offload_update_dependency(ctx, &priv->data, priv->len);
+
+       return 0;
+}
+
+static int nft_cmp_offload(struct nft_offload_ctx *ctx,
+                          struct nft_flow_rule *flow,
+                          const struct nft_expr *expr)
+{
+       const struct nft_cmp_expr *priv = nft_expr_priv(expr);
+
+       return __nft_cmp_offload(ctx, flow, priv);
+}
+
 static const struct nft_expr_ops nft_cmp_ops = {
        .type           = &nft_cmp_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_cmp_expr)),
        .eval           = nft_cmp_eval,
        .init           = nft_cmp_init,
        .dump           = nft_cmp_dump,
+       .offload        = nft_cmp_offload,
 };
 
 static int nft_cmp_fast_init(const struct nft_ctx *ctx,
@@ -143,6 +176,25 @@ static int nft_cmp_fast_init(const struct nft_ctx *ctx,
        return 0;
 }
 
+static int nft_cmp_fast_offload(struct nft_offload_ctx *ctx,
+                               struct nft_flow_rule *flow,
+                               const struct nft_expr *expr)
+{
+       const struct nft_cmp_fast_expr *priv = nft_expr_priv(expr);
+       struct nft_cmp_expr cmp = {
+               .data   = {
+                       .data   = {
+                               [0] = priv->data,
+                       },
+               },
+               .sreg   = priv->sreg,
+               .len    = priv->len / BITS_PER_BYTE,
+               .op     = NFT_CMP_EQ,
+       };
+
+       return __nft_cmp_offload(ctx, flow, &cmp);
+}
+
 static int nft_cmp_fast_dump(struct sk_buff *skb, const struct nft_expr *expr)
 {
        const struct nft_cmp_fast_expr *priv = nft_expr_priv(expr);
@@ -169,6 +221,7 @@ const struct nft_expr_ops nft_cmp_fast_ops = {
        .eval           = NULL, /* inlined */
        .init           = nft_cmp_fast_init,
        .dump           = nft_cmp_fast_dump,
+       .offload        = nft_cmp_fast_offload,
 };
 
 static const struct nft_expr_ops *
index cb8547f..ca2ae4b 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_offload.h>
 
 void nft_immediate_eval(const struct nft_expr *expr,
                        struct nft_regs *regs,
@@ -124,6 +125,34 @@ static int nft_immediate_validate(const struct nft_ctx *ctx,
        return 0;
 }
 
+static int nft_immediate_offload(struct nft_offload_ctx *ctx,
+                                struct nft_flow_rule *flow,
+                                const struct nft_expr *expr)
+{
+       const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+       struct flow_action_entry *entry;
+       const struct nft_data *data;
+
+       if (priv->dreg != NFT_REG_VERDICT)
+               return -EOPNOTSUPP;
+
+       entry = &flow->rule->action.entries[ctx->num_actions++];
+
+       data = &priv->data;
+       switch (data->verdict.code) {
+       case NF_ACCEPT:
+               entry->id = FLOW_ACTION_ACCEPT;
+               break;
+       case NF_DROP:
+               entry->id = FLOW_ACTION_DROP;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
 static const struct nft_expr_ops nft_imm_ops = {
        .type           = &nft_imm_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)),
@@ -133,6 +162,8 @@ static const struct nft_expr_ops nft_imm_ops = {
        .deactivate     = nft_immediate_deactivate,
        .dump           = nft_immediate_dump,
        .validate       = nft_immediate_validate,
+       .offload        = nft_immediate_offload,
+       .offload_flags  = NFT_OFFLOAD_F_ACTION,
 };
 
 struct nft_expr_type nft_imm_type __read_mostly = {
index 417f8d3..76866f7 100644 (file)
@@ -22,6 +22,7 @@
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nft_meta.h>
+#include <net/netfilter/nf_tables_offload.h>
 
 #include <uapi/linux/netfilter_bridge.h> /* NF_BR_PRE_ROUTING */
 
@@ -490,6 +491,31 @@ void nft_meta_set_destroy(const struct nft_ctx *ctx,
 }
 EXPORT_SYMBOL_GPL(nft_meta_set_destroy);
 
+static int nft_meta_get_offload(struct nft_offload_ctx *ctx,
+                               struct nft_flow_rule *flow,
+                               const struct nft_expr *expr)
+{
+       const struct nft_meta *priv = nft_expr_priv(expr);
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+       switch (priv->key) {
+       case NFT_META_PROTOCOL:
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_BASIC, basic, n_proto,
+                                 sizeof(__u16), reg);
+               nft_offload_set_dependency(ctx, NFT_OFFLOAD_DEP_NETWORK);
+               break;
+       case NFT_META_L4PROTO:
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_BASIC, basic, ip_proto,
+                                 sizeof(__u8), reg);
+               nft_offload_set_dependency(ctx, NFT_OFFLOAD_DEP_TRANSPORT);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
 static const struct nft_expr_ops nft_meta_get_ops = {
        .type           = &nft_meta_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
@@ -497,6 +523,7 @@ static const struct nft_expr_ops nft_meta_get_ops = {
        .init           = nft_meta_get_init,
        .dump           = nft_meta_get_dump,
        .validate       = nft_meta_get_validate,
+       .offload        = nft_meta_get_offload,
 };
 
 static const struct nft_expr_ops nft_meta_set_ops = {
index 1260f78..22a80eb 100644 (file)
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_offload.h>
 /* For layer 4 checksum field offset. */
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/icmpv6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
 
 /* add vlan header into the user buffer for if tag was removed by offloads */
 static bool
@@ -150,12 +153,195 @@ nla_put_failure:
        return -1;
 }
 
+static int nft_payload_offload_ll(struct nft_offload_ctx *ctx,
+                                 struct nft_flow_rule *flow,
+                                 const struct nft_payload *priv)
+{
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+       switch (priv->offset) {
+       case offsetof(struct ethhdr, h_source):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_ETH_ADDRS, eth_addrs,
+                                 src, ETH_ALEN, reg);
+               break;
+       case offsetof(struct ethhdr, h_dest):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_ETH_ADDRS, eth_addrs,
+                                 dst, ETH_ALEN, reg);
+               break;
+       }
+
+       return 0;
+}
+
+static int nft_payload_offload_ip(struct nft_offload_ctx *ctx,
+                                 struct nft_flow_rule *flow,
+                                 const struct nft_payload *priv)
+{
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+       switch (priv->offset) {
+       case offsetof(struct iphdr, saddr):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_IPV4_ADDRS, ipv4, src,
+                                 sizeof(struct in_addr), reg);
+               break;
+       case offsetof(struct iphdr, daddr):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_IPV4_ADDRS, ipv4, dst,
+                                 sizeof(struct in_addr), reg);
+               break;
+       case offsetof(struct iphdr, protocol):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_BASIC, basic, ip_proto,
+                                 sizeof(__u8), reg);
+               nft_offload_set_dependency(ctx, NFT_OFFLOAD_DEP_TRANSPORT);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int nft_payload_offload_ip6(struct nft_offload_ctx *ctx,
+                                 struct nft_flow_rule *flow,
+                                 const struct nft_payload *priv)
+{
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+       switch (priv->offset) {
+       case offsetof(struct ipv6hdr, saddr):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_IPV6_ADDRS, ipv6, src,
+                                 sizeof(struct in6_addr), reg);
+               break;
+       case offsetof(struct ipv6hdr, daddr):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_IPV6_ADDRS, ipv6, dst,
+                                 sizeof(struct in6_addr), reg);
+               break;
+       case offsetof(struct ipv6hdr, nexthdr):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_BASIC, basic, ip_proto,
+                                 sizeof(__u8), reg);
+               nft_offload_set_dependency(ctx, NFT_OFFLOAD_DEP_TRANSPORT);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int nft_payload_offload_nh(struct nft_offload_ctx *ctx,
+                                 struct nft_flow_rule *flow,
+                                 const struct nft_payload *priv)
+{
+       int err;
+
+       switch (ctx->dep.l3num) {
+       case htons(ETH_P_IP):
+               err = nft_payload_offload_ip(ctx, flow, priv);
+               break;
+       case htons(ETH_P_IPV6):
+               err = nft_payload_offload_ip6(ctx, flow, priv);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return err;
+}
+
+static int nft_payload_offload_tcp(struct nft_offload_ctx *ctx,
+                                  struct nft_flow_rule *flow,
+                                  const struct nft_payload *priv)
+{
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+       switch (priv->offset) {
+       case offsetof(struct tcphdr, source):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_PORTS, tp, src,
+                                 sizeof(__be16), reg);
+               break;
+       case offsetof(struct tcphdr, dest):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_PORTS, tp, dst,
+                                 sizeof(__be16), reg);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int nft_payload_offload_udp(struct nft_offload_ctx *ctx,
+                                  struct nft_flow_rule *flow,
+                                  const struct nft_payload *priv)
+{
+       struct nft_offload_reg *reg = &ctx->regs[priv->dreg];
+
+       switch (priv->offset) {
+       case offsetof(struct udphdr, source):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_PORTS, tp, src,
+                                 sizeof(__be16), reg);
+               break;
+       case offsetof(struct udphdr, dest):
+               NFT_OFFLOAD_MATCH(FLOW_DISSECTOR_KEY_PORTS, tp, dst,
+                                 sizeof(__be16), reg);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int nft_payload_offload_th(struct nft_offload_ctx *ctx,
+                                 struct nft_flow_rule *flow,
+                                 const struct nft_payload *priv)
+{
+       int err;
+
+       switch (ctx->dep.protonum) {
+       case IPPROTO_TCP:
+               err = nft_payload_offload_tcp(ctx, flow, priv);
+               break;
+       case IPPROTO_UDP:
+               err = nft_payload_offload_udp(ctx, flow, priv);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return err;
+}
+
+static int nft_payload_offload(struct nft_offload_ctx *ctx,
+                              struct nft_flow_rule *flow,
+                              const struct nft_expr *expr)
+{
+       const struct nft_payload *priv = nft_expr_priv(expr);
+       int err;
+
+       switch (priv->base) {
+       case NFT_PAYLOAD_LL_HEADER:
+               err = nft_payload_offload_ll(ctx, flow, priv);
+               break;
+       case NFT_PAYLOAD_NETWORK_HEADER:
+               err = nft_payload_offload_nh(ctx, flow, priv);
+               break;
+       case NFT_PAYLOAD_TRANSPORT_HEADER:
+               err = nft_payload_offload_th(ctx, flow, priv);
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               break;
+       }
+       return err;
+}
+
 static const struct nft_expr_ops nft_payload_ops = {
        .type           = &nft_payload_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_payload)),
        .eval           = nft_payload_eval,
        .init           = nft_payload_init,
        .dump           = nft_payload_dump,
+       .offload        = nft_payload_offload,
 };
 
 const struct nft_expr_ops nft_payload_fast_ops = {
@@ -164,6 +350,7 @@ const struct nft_expr_ops nft_payload_fast_ops = {
        .eval           = nft_payload_eval,
        .init           = nft_payload_init,
        .dump           = nft_payload_dump,
+       .offload        = nft_payload_offload,
 };
 
 static inline void nft_csum_replace(__sum16 *sum, __wsum fsum, __wsum tsum)