{"id":833931,"url":"http://patchwork.ozlabs.org/api/1.2/patches/833931/?format=json","web_url":"http://patchwork.ozlabs.org/project/netdev/patch/20171103152636.9967-5-pablo@netfilter.org/","project":{"id":7,"url":"http://patchwork.ozlabs.org/api/1.2/projects/7/?format=json","name":"Linux network development","link_name":"netdev","list_id":"netdev.vger.kernel.org","list_email":"netdev@vger.kernel.org","web_url":null,"scm_url":null,"webscm_url":null,"list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<20171103152636.9967-5-pablo@netfilter.org>","list_archive_url":null,"date":"2017-11-03T15:26:35","name":"[RFC,WIP,4/5] netfilter: nf_tables: flow offload expression","commit_ref":null,"pull_url":null,"state":"rfc","archived":true,"hash":"2a5291d1a1c22f2fee728711d2a7f15a6fe0f85f","submitter":{"id":1315,"url":"http://patchwork.ozlabs.org/api/1.2/people/1315/?format=json","name":"Pablo Neira Ayuso","email":"pablo@netfilter.org"},"delegate":{"id":34,"url":"http://patchwork.ozlabs.org/api/1.2/users/34/?format=json","username":"davem","first_name":"David","last_name":"Miller","email":"davem@davemloft.net"},"mbox":"http://patchwork.ozlabs.org/project/netdev/patch/20171103152636.9967-5-pablo@netfilter.org/mbox/","series":[{"id":11752,"url":"http://patchwork.ozlabs.org/api/1.2/series/11752/?format=json","web_url":"http://patchwork.ozlabs.org/project/netdev/list/?series=11752","date":"2017-11-03T15:26:31","name":"Flow offload infrastructure","version":1,"mbox":"http://patchwork.ozlabs.org/series/11752/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/833931/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/833931/checks/","tags":{},"related":[],"headers":{"Return-Path":"<netdev-owner@vger.kernel.org>","X-Original-To":"patchwork-incoming@ozlabs.org","Delivered-To":"patchwork-incoming@ozlabs.org","Authentication-Results":"ozlabs.org;\n\tspf=none (mailfrom) smtp.mailfrom=vger.kernel.org\n\t(client-ip=209.132.180.67; helo=vger.kernel.org;\n\tenvelope-from=netdev-owner@vger.kernel.org;\n\treceiver=<UNKNOWN>)","Received":["from vger.kernel.org (vger.kernel.org [209.132.180.67])\n\tby ozlabs.org (Postfix) with ESMTP id 3yT5R84xQXz9ryT\n\tfor <patchwork-incoming@ozlabs.org>;\n\tSat,  4 Nov 2017 02:27:00 +1100 (AEDT)","(majordomo@vger.kernel.org) by vger.kernel.org via listexpand\n\tid S1755947AbdKCP07 (ORCPT <rfc822;patchwork-incoming@ozlabs.org>);\n\tFri, 3 Nov 2017 11:26:59 -0400","from mail.us.es ([193.147.175.20]:43336 \"EHLO mail.us.es\"\n\trhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP\n\tid S1755867AbdKCP0t (ORCPT <rfc822;netdev@vger.kernel.org>);\n\tFri, 3 Nov 2017 11:26:49 -0400","from antivirus1-rhel7.int (unknown [192.168.2.11])\n\tby mail.us.es (Postfix) with ESMTP id D8690C0B2C\n\tfor <netdev@vger.kernel.org>; Fri,  3 Nov 2017 16:26:47 +0100 (CET)","from antivirus1-rhel7.int (localhost [127.0.0.1])\n\tby antivirus1-rhel7.int (Postfix) with ESMTP id C5435B7FE2\n\tfor <netdev@vger.kernel.org>; Fri,  3 Nov 2017 16:26:47 +0100 (CET)","by antivirus1-rhel7.int (Postfix, from userid 99)\n\tid BAF15B7FE1; Fri,  3 Nov 2017 16:26:47 +0100 (CET)","from antivirus1-rhel7.int (localhost [127.0.0.1])\n\tby antivirus1-rhel7.int (Postfix) with ESMTP id 4310CB7FE6;\n\tFri,  3 Nov 2017 16:26:45 +0100 (CET)","from 192.168.1.97 (192.168.1.97) by antivirus1-rhel7.int\n\t(F-Secure/fsigk_smtp/550/antivirus1-rhel7.int); \n\tFri, 03 Nov 2017 16:26:45 +0100 (CET)","from salvia.here (unknown [31.4.245.115])\n\t(Authenticated sender: pneira@us.es)\n\tby entrada.int (Postfix) with ESMTPA id 03DD3403DFA0;\n\tFri,  3 Nov 2017 16:26:44 +0100 (CET)"],"X-Spam-Checker-Version":"SpamAssassin 3.4.1 (2015-04-28) on\n\tantivirus1-rhel7.int","X-Spam-Level":"","X-Spam-Status":"No, score=-108.2 required=7.5 tests=ALL_TRUSTED,BAYES_50,\n\tSMTPAUTH_US2,USER_IN_WHITELIST autolearn=disabled version=3.4.1","X-Virus-Status":"clean(F-Secure/fsigk_smtp/550/antivirus1-rhel7.int)","X-SMTPAUTHUS":"auth mail.us.es","From":"Pablo Neira Ayuso <pablo@netfilter.org>","To":"netfilter-devel@vger.kernel.org","Cc":"netdev@vger.kernel.org","Subject":"[PATCH RFC,WIP 4/5] netfilter: nf_tables: flow offload expression","Date":"Fri,  3 Nov 2017 16:26:35 +0100","Message-Id":"<20171103152636.9967-5-pablo@netfilter.org>","X-Mailer":"git-send-email 2.11.0","In-Reply-To":"<20171103152636.9967-1-pablo@netfilter.org>","References":"<20171103152636.9967-1-pablo@netfilter.org>","X-Virus-Scanned":"ClamAV using ClamSMTP","Sender":"netdev-owner@vger.kernel.org","Precedence":"bulk","List-ID":"<netdev.vger.kernel.org>","X-Mailing-List":"netdev@vger.kernel.org"},"content":"Add new instruction for the nf_tables VM that allows us to specify what\nflows are offloaded. This has an explicit dependency with the conntrack\nsubsystem.\n\nSigned-off-by: Pablo Neira Ayuso <pablo@netfilter.org>\n---\n include/uapi/linux/netfilter/nf_tables.h |   9 +\n net/netfilter/Kconfig                    |   7 +\n net/netfilter/Makefile                   |   1 +\n net/netfilter/nft_flow_offload.c         | 331 +++++++++++++++++++++++++++++++\n 4 files changed, 348 insertions(+)\n create mode 100644 net/netfilter/nft_flow_offload.c","diff":"diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h\nindex 871afa4871bf..2edde548de68 100644\n--- a/include/uapi/linux/netfilter/nf_tables.h\n+++ b/include/uapi/linux/netfilter/nf_tables.h\n@@ -948,6 +948,15 @@ enum nft_ct_attributes {\n };\n #define NFTA_CT_MAX\t\t(__NFTA_CT_MAX - 1)\n \n+/**\n+ * enum nft_ct_offload_attributes - ct offload expression attributes\n+ */\n+enum nft_offload_attributes {\n+\tNFTA_CT_OFFLOAD_UNSPEC,\n+\t__NFTA_CT_OFFLOAD_MAX,\n+};\n+#define NFTA_CT_OFFLOAD_MAX\t(__NFTA_CT_OFFLOAD_MAX - 1)\n+\n enum nft_limit_type {\n \tNFT_LIMIT_PKTS,\n \tNFT_LIMIT_PKT_BYTES\ndiff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig\nindex f022ca91f49d..0a5c33cfaeb8 100644\n--- a/net/netfilter/Kconfig\n+++ b/net/netfilter/Kconfig\n@@ -504,6 +504,13 @@ config NFT_CT\n \t  This option adds the \"ct\" expression that you can use to match\n \t  connection tracking information such as the flow state.\n \n+config NFT_FLOW_OFFLOAD\n+\tdepends on NF_CONNTRACK\n+\ttristate \"Netfilter nf_tables hardware flow offload module\"\n+\thelp\n+\t  This option adds the \"flow_offload\" expression that you can use to\n+\t  choose what flows are placed into the hardware.\n+\n config NFT_SET_RBTREE\n \ttristate \"Netfilter nf_tables rbtree set module\"\n \thelp\ndiff --git a/net/netfilter/Makefile b/net/netfilter/Makefile\nindex 518f54113e06..801ce5c25e5d 100644\n--- a/net/netfilter/Makefile\n+++ b/net/netfilter/Makefile\n@@ -86,6 +86,7 @@ obj-$(CONFIG_NFT_META)\t\t+= nft_meta.o\n obj-$(CONFIG_NFT_RT)\t\t+= nft_rt.o\n obj-$(CONFIG_NFT_NUMGEN)\t+= nft_numgen.o\n obj-$(CONFIG_NFT_CT)\t\t+= nft_ct.o\n+obj-$(CONFIG_NFT_FLOW_OFFLOAD)\t+= nft_flow_offload.o\n obj-$(CONFIG_NFT_LIMIT)\t\t+= nft_limit.o\n obj-$(CONFIG_NFT_NAT)\t\t+= nft_nat.o\n obj-$(CONFIG_NFT_OBJREF)\t+= nft_objref.o\ndiff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c\nnew file mode 100644\nindex 000000000000..d38d185a19a5\n--- /dev/null\n+++ b/net/netfilter/nft_flow_offload.c\n@@ -0,0 +1,331 @@\n+#include <linux/kernel.h>\n+#include <linux/module.h>\n+#include <linux/init.h>\n+#include <linux/netlink.h>\n+#include <linux/netfilter.h>\n+#include <linux/workqueue.h>\n+#include <linux/spinlock.h>\n+#include <linux/netfilter/nf_tables.h>\n+#include <net/flow_offload.h>\n+#include <net/netfilter/nf_tables.h>\n+#include <net/netfilter/nf_tables_core.h>\n+#include <net/netfilter/nf_conntrack_core.h>\n+#include <linux/netfilter/nf_conntrack_common.h>\n+\n+union flow_gateway {\n+\t__be32\t\tip;\n+\tstruct in6_addr\tip6;\n+};\n+\n+static int flow_offload_iterate_cleanup(struct nf_conn *ct, void *data)\n+{\n+\tstruct flow_offload_tuple_rhash *tuplehash;\n+\tstruct flow_offload_tuple tuple = {};\n+\tstruct net_device *indev = data;\n+\tstruct flow_offload *flow;\n+\n+\tif (!test_and_clear_bit(IPS_OFFLOAD_BIT, &ct->status))\n+\t\treturn 0;\n+\n+\ttuple.src_v4 = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.in;\n+\ttuple.dst_v4 = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in;\n+\ttuple.src_port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.tcp.port;\n+\ttuple.dst_port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port;\n+\ttuple.l3proto = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;\n+\ttuple.l4proto = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;\n+\n+\ttuplehash = flow_offload_lookup(&tuple);\n+\tBUG_ON(!tuplehash);\n+\n+\tif (indev && tuplehash->tuple.iifidx != indev->ifindex)\n+\t\treturn 0;\n+\n+\tflow = container_of(tuplehash, struct flow_offload,\n+\t\t\t    tuplehash[tuplehash->tuple.dir]);\n+\n+\tflow_offload_del(flow);\n+\n+\t/* Do not remove this conntrack from table. */\n+\treturn 0;\n+}\n+\n+static void flow_offload_cleanup(struct net *net,\n+\t\t\t\t const struct net_device *dev)\n+{\n+\tnf_ct_iterate_cleanup_net(net, flow_offload_iterate_cleanup,\n+\t\t\t\t  (void *)dev, 0, 0);\n+}\n+\n+static int flow_offload_netdev_event(struct notifier_block *this,\n+\t\t\t\t     unsigned long event, void *ptr)\n+{\n+\tconst struct net_device *dev = netdev_notifier_info_to_dev(ptr);\n+\n+\tif (event != NETDEV_DOWN)\n+\t\treturn NOTIFY_DONE;\n+\n+\tflow_offload_cleanup(dev_net(dev), dev);\n+\n+\treturn NOTIFY_DONE;\n+}\n+\n+static struct notifier_block flow_offload_netdev_notifier = {\n+\t.notifier_call\t= flow_offload_netdev_event,\n+};\n+\n+static struct flow_offload *\n+flow_offload_alloc(const struct nf_conn *ct, int iifindex, int oifindex,\n+\t\t   union flow_gateway *orig_gateway,\n+\t\t   union flow_gateway *reply_gateway)\n+{\n+\tstruct flow_offload *flow;\n+\n+\tflow = kzalloc(sizeof(*flow), GFP_ATOMIC);\n+\tif (!flow)\n+\t\treturn NULL;\n+\n+\tswitch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num) {\n+\tcase NFPROTO_IPV4:\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v4 =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.in;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v4 =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v4 =\n+\t\t\tct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.in;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v4 =\n+\t\t\tct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.in;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l3proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l4proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.l3proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.l4proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.gateway =\n+\t\t\torig_gateway->ip;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.gateway =\n+\t\t\treply_gateway->ip;\n+\t\tbreak;\n+\tcase NFPROTO_IPV6:\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6 =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.in6;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6 =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in6;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6 =\n+\t\t\tct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.in6;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6 =\n+\t\t\tct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.in6;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l3proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l4proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.l3proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.l4proto =\n+\t\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.gateway6 =\n+\t\t\torig_gateway->ip6;\n+\t\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.gateway6 =\n+\t\t\treply_gateway->ip6;\n+\t\tbreak;\n+\t}\n+\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_port =\n+\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.tcp.port;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_port =\n+\t\tct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_port =\n+\t\tct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u.tcp.port;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_port =\n+\t\tct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.tcp.port;\n+\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dir = FLOW_OFFLOAD_DIR_ORIGINAL;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dir = FLOW_OFFLOAD_DIR_REPLY;\n+\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.iifidx = oifindex;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.oifidx = iifindex;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.iifidx = iifindex;\n+\tflow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.oifidx = oifindex;\n+\n+\tif (ct->status & IPS_SRC_NAT)\n+\t\tflow->flags |= FLOW_OFFLOAD_SNAT;\n+\telse if (ct->status & IPS_DST_NAT)\n+\t\tflow->flags |= FLOW_OFFLOAD_DNAT;\n+\n+\treturn flow;\n+}\n+\n+static int nft_flow_route(const struct nft_pktinfo *pkt,\n+\t\t\t  const struct nf_conn *ct,\n+\t\t\t  union flow_gateway *orig_gw,\n+\t\t\t  union flow_gateway *reply_gw)\n+{\n+\tconst struct dst_entry *reply_dst = skb_dst(pkt->skb);\n+\tstruct dst_entry *orig_dst;\n+\tconst struct nf_afinfo *ai;\n+\tstruct flowi fl;\n+\n+\tmemset(&fl, 0, sizeof(fl));\n+\tswitch (nft_pf(pkt)) {\n+\tcase NFPROTO_IPV4:\n+\t\tfl.u.ip4.daddr = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.ip;\n+\t\tbreak;\n+\tcase NFPROTO_IPV6:\n+\t\tfl.u.ip6.daddr = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in6;\n+\t\tbreak;\n+\t}\n+\n+\tai = nf_get_afinfo(nft_pf(pkt));\n+\tif (ai) {\n+\t\tai->route(nft_net(pkt), &orig_dst, &fl, false);\n+\t\tif (!orig_dst)\n+\t\t\treturn -ENOENT;\n+\t}\n+\n+\tswitch (nft_pf(pkt)) {\n+\tcase NFPROTO_IPV4: {\n+\t\tconst struct rtable *orig_rt = (const struct rtable *)orig_dst;\n+\t\tconst struct rtable *reply_rt =\n+\t\t\t(const struct rtable *)reply_dst;\n+\n+\t\torig_gw->ip = orig_rt->rt_gateway;\n+\t\treply_gw->ip = reply_rt->rt_gateway;\n+\t\tbreak;\n+\t\t}\n+\tcase NFPROTO_IPV6:\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\tdst_release(orig_dst);\n+\n+\treturn 0;\n+}\n+\n+static void nft_flow_offload_eval(const struct nft_expr *expr,\n+\t\t\t\t  struct nft_regs *regs,\n+\t\t\t\t  const struct nft_pktinfo *pkt)\n+{\n+\tunion flow_gateway orig_gateway, reply_gateway;\n+\tstruct net_device *outdev = pkt->xt.state->out;\n+\tstruct net_device *indev = pkt->xt.state->in;\n+\tenum ip_conntrack_info ctinfo;\n+\tstruct flow_offload *flow;\n+\tstruct nf_conn *ct;\n+\tint ret;\n+\n+\tct = nf_ct_get(pkt->skb, &ctinfo);\n+\tif (!ct)\n+\t\tgoto out;\n+\n+\tswitch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {\n+\tcase IPPROTO_TCP:\n+\tcase IPPROTO_UDP:\n+\t\tbreak;\n+\tdefault:\n+\t\tgoto out;\n+\t}\n+\n+\tif (test_bit(IPS_HELPER_BIT, &ct->status))\n+\t\tgoto out;\n+\n+\tif (ctinfo == IP_CT_NEW ||\n+\t    ctinfo == IP_CT_RELATED)\n+\t\tgoto out;\n+\n+\tif (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status))\n+\t\tgoto out;\n+\n+\tif (nft_flow_route(pkt, ct, &orig_gateway, &reply_gateway) < 0)\n+\t\tgoto err1;\n+\n+\tflow = flow_offload_alloc(ct, indev->ifindex, outdev->ifindex,\n+\t\t\t\t  &orig_gateway, &reply_gateway);\n+\tif (!flow)\n+\t\tgoto err1;\n+\n+\tret = flow_offload_add(flow);\n+\tif (ret < 0)\n+\t\tgoto err2;\n+\n+\treturn;\n+err2:\n+\tkfree(flow);\n+err1:\n+\tclear_bit(IPS_OFFLOAD_BIT, &ct->status);\n+out:\n+\tregs->verdict.code = NFT_BREAK;\n+}\n+\n+static int nft_flow_offload_validate(const struct nft_ctx *ctx,\n+\t\t\t\t     const struct nft_expr *expr,\n+\t\t\t\t     const struct nft_data **data)\n+{\n+\tunsigned int hook_mask = (1 << NF_INET_FORWARD);\n+\n+\treturn nft_chain_validate_hooks(ctx->chain, hook_mask);\n+}\n+\n+static int nft_flow_offload_init(const struct nft_ctx *ctx,\n+\t\t\t\t const struct nft_expr *expr,\n+\t\t\t\t const struct nlattr * const tb[])\n+{\n+\treturn nf_ct_netns_get(ctx->net, ctx->afi->family);\n+}\n+\n+static void nft_flow_offload_destroy(const struct nft_ctx *ctx,\n+\t\t\t\t     const struct nft_expr *expr)\n+{\n+\tnf_ct_netns_put(ctx->net, ctx->afi->family);\n+}\n+\n+static int nft_flow_offload_dump(struct sk_buff *skb, const struct nft_expr *expr)\n+{\n+\treturn 0;\n+}\n+\n+struct nft_expr_type nft_flow_offload_type;\n+static const struct nft_expr_ops nft_flow_offload_ops = {\n+\t.type\t\t= &nft_flow_offload_type,\n+\t.size\t\t= NFT_EXPR_SIZE(0),\n+\t.eval\t\t= nft_flow_offload_eval,\n+\t.init\t\t= nft_flow_offload_init,\n+\t.destroy\t= nft_flow_offload_destroy,\n+\t.validate\t= nft_flow_offload_validate,\n+\t.dump\t\t= nft_flow_offload_dump,\n+};\n+\n+struct nft_expr_type nft_flow_offload_type __read_mostly = {\n+\t.name\t\t= \"flow_offload\",\n+\t.ops\t\t= &nft_flow_offload_ops,\n+\t.maxattr\t= NFTA_CT_OFFLOAD_MAX,\n+\t.owner\t\t= THIS_MODULE,\n+};\n+\n+static int __init nft_flow_offload_module_init(void)\n+{\n+\tregister_netdevice_notifier(&flow_offload_netdev_notifier);\n+\n+\treturn nft_register_expr(&nft_flow_offload_type);\n+}\n+\n+static void __exit nft_flow_offload_module_exit(void)\n+{\n+\tstruct net *net;\n+\n+\tnft_unregister_expr(&nft_flow_offload_type);\n+\tunregister_netdevice_notifier(&flow_offload_netdev_notifier);\n+\trtnl_lock();\n+\tfor_each_net(net)\n+\t\tflow_offload_cleanup(net, NULL);\n+\trtnl_unlock();\n+}\n+\n+module_init(nft_flow_offload_module_init);\n+module_exit(nft_flow_offload_module_exit);\n+\n+MODULE_LICENSE(\"GPL\");\n+MODULE_AUTHOR(\"Pablo Neira Ayuso <pablo@netfilter.org>\");\n+MODULE_ALIAS_NFT_EXPR(\"flow_offload\");\n","prefixes":["RFC","WIP","4/5"]}