Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/833931/?format=api
{ "id": 833931, "url": "http://patchwork.ozlabs.org/api/1.2/patches/833931/?format=api", "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=api", "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=api", "name": "Pablo Neira Ayuso", "email": "pablo@netfilter.org" }, "delegate": { "id": 34, "url": "http://patchwork.ozlabs.org/api/1.2/users/34/?format=api", "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=api", "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" ] }