aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/backport-4.14/321-netfilter-nf_tables-add-flow-table-netlink-frontend.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/generic/backport-4.14/321-netfilter-nf_tables-add-flow-table-netlink-frontend.patch')
-rw-r--r--target/linux/generic/backport-4.14/321-netfilter-nf_tables-add-flow-table-netlink-frontend.patch1079
1 files changed, 1079 insertions, 0 deletions
diff --git a/target/linux/generic/backport-4.14/321-netfilter-nf_tables-add-flow-table-netlink-frontend.patch b/target/linux/generic/backport-4.14/321-netfilter-nf_tables-add-flow-table-netlink-frontend.patch
new file mode 100644
index 0000000000..8a0d2f0fb7
--- /dev/null
+++ b/target/linux/generic/backport-4.14/321-netfilter-nf_tables-add-flow-table-netlink-frontend.patch
@@ -0,0 +1,1079 @@
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+Date: Sun, 7 Jan 2018 01:04:07 +0100
+Subject: [PATCH] netfilter: nf_tables: add flow table netlink frontend
+
+This patch introduces a netlink control plane to create, delete and dump
+flow tables. Flow tables are identified by name, this name is used from
+rules to refer to an specific flow table. Flow tables use the rhashtable
+class and a generic garbage collector to remove expired entries.
+
+This also adds the infrastructure to add different flow table types, so
+we can add one for each layer 3 protocol family.
+
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+---
+ create mode 100644 include/net/netfilter/nf_flow_table.h
+
+--- /dev/null
++++ b/include/net/netfilter/nf_flow_table.h
+@@ -0,0 +1,23 @@
++#ifndef _NF_FLOW_TABLE_H
++#define _NF_FLOW_TABLE_H
++
++#include <linux/rhashtable.h>
++
++struct nf_flowtable;
++
++struct nf_flowtable_type {
++ struct list_head list;
++ int family;
++ void (*gc)(struct work_struct *work);
++ const struct rhashtable_params *params;
++ nf_hookfn *hook;
++ struct module *owner;
++};
++
++struct nf_flowtable {
++ struct rhashtable rhashtable;
++ const struct nf_flowtable_type *type;
++ struct delayed_work gc_work;
++};
++
++#endif /* _FLOW_OFFLOAD_H */
+--- a/include/net/netfilter/nf_tables.h
++++ b/include/net/netfilter/nf_tables.h
+@@ -9,6 +9,7 @@
+ #include <linux/netfilter/x_tables.h>
+ #include <linux/netfilter/nf_tables.h>
+ #include <linux/u64_stats_sync.h>
++#include <net/netfilter/nf_flow_table.h>
+ #include <net/netlink.h>
+
+ #define NFT_JUMP_STACK_SIZE 16
+@@ -933,6 +934,7 @@ unsigned int nft_do_chain(struct nft_pkt
+ * @chains: chains in the table
+ * @sets: sets in the table
+ * @objects: stateful objects in the table
++ * @flowtables: flow tables in the table
+ * @hgenerator: handle generator state
+ * @use: number of chain references to this table
+ * @flags: table flag (see enum nft_table_flags)
+@@ -944,6 +946,7 @@ struct nft_table {
+ struct list_head chains;
+ struct list_head sets;
+ struct list_head objects;
++ struct list_head flowtables;
+ u64 hgenerator;
+ u32 use;
+ u16 flags:14,
+@@ -1075,6 +1078,44 @@ int nft_register_obj(struct nft_object_t
+ void nft_unregister_obj(struct nft_object_type *obj_type);
+
+ /**
++ * struct nft_flowtable - nf_tables flow table
++ *
++ * @list: flow table list node in table list
++ * @table: the table the flow table is contained in
++ * @name: name of this flow table
++ * @hooknum: hook number
++ * @priority: hook priority
++ * @ops_len: number of hooks in array
++ * @genmask: generation mask
++ * @use: number of references to this flow table
++ * @data: rhashtable and garbage collector
++ * @ops: array of hooks
++ */
++struct nft_flowtable {
++ struct list_head list;
++ struct nft_table *table;
++ char *name;
++ int hooknum;
++ int priority;
++ int ops_len;
++ u32 genmask:2,
++ use:30;
++ /* runtime data below here */
++ struct nf_hook_ops *ops ____cacheline_aligned;
++ struct nf_flowtable data;
++};
++
++struct nft_flowtable *nf_tables_flowtable_lookup(const struct nft_table *table,
++ const struct nlattr *nla,
++ u8 genmask);
++void nft_flow_table_iterate(struct net *net,
++ void (*iter)(struct nf_flowtable *flowtable, void *data),
++ void *data);
++
++void nft_register_flowtable_type(struct nf_flowtable_type *type);
++void nft_unregister_flowtable_type(struct nf_flowtable_type *type);
++
++/**
+ * struct nft_traceinfo - nft tracing information and state
+ *
+ * @pkt: pktinfo currently processed
+@@ -1310,4 +1351,11 @@ struct nft_trans_obj {
+ #define nft_trans_obj(trans) \
+ (((struct nft_trans_obj *)trans->data)->obj)
+
++struct nft_trans_flowtable {
++ struct nft_flowtable *flowtable;
++};
++
++#define nft_trans_flowtable(trans) \
++ (((struct nft_trans_flowtable *)trans->data)->flowtable)
++
+ #endif /* _NET_NF_TABLES_H */
+--- a/include/uapi/linux/netfilter/nf_tables.h
++++ b/include/uapi/linux/netfilter/nf_tables.h
+@@ -92,6 +92,9 @@ enum nft_verdicts {
+ * @NFT_MSG_GETOBJ: get a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_DELOBJ: delete a stateful object (enum nft_obj_attributes)
+ * @NFT_MSG_GETOBJ_RESET: get and reset a stateful object (enum nft_obj_attributes)
++ * @NFT_MSG_NEWFLOWTABLE: add new flow table (enum nft_flowtable_attributes)
++ * @NFT_MSG_GETFLOWTABLE: get flow table (enum nft_flowtable_attributes)
++ * @NFT_MSG_DELFLOWTABLE: delete flow table (enum nft_flowtable_attributes)
+ */
+ enum nf_tables_msg_types {
+ NFT_MSG_NEWTABLE,
+@@ -116,6 +119,9 @@ enum nf_tables_msg_types {
+ NFT_MSG_GETOBJ,
+ NFT_MSG_DELOBJ,
+ NFT_MSG_GETOBJ_RESET,
++ NFT_MSG_NEWFLOWTABLE,
++ NFT_MSG_GETFLOWTABLE,
++ NFT_MSG_DELFLOWTABLE,
+ NFT_MSG_MAX,
+ };
+
+@@ -1310,6 +1316,53 @@ enum nft_object_attributes {
+ #define NFTA_OBJ_MAX (__NFTA_OBJ_MAX - 1)
+
+ /**
++ * enum nft_flowtable_attributes - nf_tables flow table netlink attributes
++ *
++ * @NFTA_FLOWTABLE_TABLE: name of the table containing the expression (NLA_STRING)
++ * @NFTA_FLOWTABLE_NAME: name of this flow table (NLA_STRING)
++ * @NFTA_FLOWTABLE_HOOK: netfilter hook configuration(NLA_U32)
++ * @NFTA_FLOWTABLE_USE: number of references to this flow table (NLA_U32)
++ */
++enum nft_flowtable_attributes {
++ NFTA_FLOWTABLE_UNSPEC,
++ NFTA_FLOWTABLE_TABLE,
++ NFTA_FLOWTABLE_NAME,
++ NFTA_FLOWTABLE_HOOK,
++ NFTA_FLOWTABLE_USE,
++ __NFTA_FLOWTABLE_MAX
++};
++#define NFTA_FLOWTABLE_MAX (__NFTA_FLOWTABLE_MAX - 1)
++
++/**
++ * enum nft_flowtable_hook_attributes - nf_tables flow table hook netlink attributes
++ *
++ * @NFTA_FLOWTABLE_HOOK_NUM: netfilter hook number (NLA_U32)
++ * @NFTA_FLOWTABLE_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
++ * @NFTA_FLOWTABLE_HOOK_DEVS: input devices this flow table is bound to (NLA_NESTED)
++ */
++enum nft_flowtable_hook_attributes {
++ NFTA_FLOWTABLE_HOOK_UNSPEC,
++ NFTA_FLOWTABLE_HOOK_NUM,
++ NFTA_FLOWTABLE_HOOK_PRIORITY,
++ NFTA_FLOWTABLE_HOOK_DEVS,
++ __NFTA_FLOWTABLE_HOOK_MAX
++};
++#define NFTA_FLOWTABLE_HOOK_MAX (__NFTA_FLOWTABLE_HOOK_MAX - 1)
++
++/**
++ * enum nft_device_attributes - nf_tables device netlink attributes
++ *
++ * @NFTA_DEVICE_NAME: name of this device (NLA_STRING)
++ */
++enum nft_devices_attributes {
++ NFTA_DEVICE_UNSPEC,
++ NFTA_DEVICE_NAME,
++ __NFTA_DEVICE_MAX
++};
++#define NFTA_DEVICE_MAX (__NFTA_DEVICE_MAX - 1)
++
++
++/**
+ * enum nft_trace_attributes - nf_tables trace netlink attributes
+ *
+ * @NFTA_TRACE_TABLE: name of the table (NLA_STRING)
+--- a/net/netfilter/nf_tables_api.c
++++ b/net/netfilter/nf_tables_api.c
+@@ -17,6 +17,7 @@
+ #include <linux/netfilter.h>
+ #include <linux/netfilter/nfnetlink.h>
+ #include <linux/netfilter/nf_tables.h>
++#include <net/netfilter/nf_flow_table.h>
+ #include <net/netfilter/nf_tables_core.h>
+ #include <net/netfilter/nf_tables.h>
+ #include <net/net_namespace.h>
+@@ -24,6 +25,7 @@
+
+ static LIST_HEAD(nf_tables_expressions);
+ static LIST_HEAD(nf_tables_objects);
++static LIST_HEAD(nf_tables_flowtables);
+
+ /**
+ * nft_register_afinfo - register nf_tables address family info
+@@ -345,6 +347,40 @@ static int nft_delobj(struct nft_ctx *ct
+ return err;
+ }
+
++static int nft_trans_flowtable_add(struct nft_ctx *ctx, int msg_type,
++ struct nft_flowtable *flowtable)
++{
++ struct nft_trans *trans;
++
++ trans = nft_trans_alloc(ctx, msg_type,
++ sizeof(struct nft_trans_flowtable));
++ if (trans == NULL)
++ return -ENOMEM;
++
++ if (msg_type == NFT_MSG_NEWFLOWTABLE)
++ nft_activate_next(ctx->net, flowtable);
++
++ nft_trans_flowtable(trans) = flowtable;
++ list_add_tail(&trans->list, &ctx->net->nft.commit_list);
++
++ return 0;
++}
++
++static int nft_delflowtable(struct nft_ctx *ctx,
++ struct nft_flowtable *flowtable)
++{
++ int err;
++
++ err = nft_trans_flowtable_add(ctx, NFT_MSG_DELFLOWTABLE, flowtable);
++ if (err < 0)
++ return err;
++
++ nft_deactivate_next(ctx->net, flowtable);
++ ctx->table->use--;
++
++ return err;
++}
++
+ /*
+ * Tables
+ */
+@@ -728,6 +764,7 @@ static int nf_tables_newtable(struct net
+ INIT_LIST_HEAD(&table->chains);
+ INIT_LIST_HEAD(&table->sets);
+ INIT_LIST_HEAD(&table->objects);
++ INIT_LIST_HEAD(&table->flowtables);
+ table->flags = flags;
+
+ nft_ctx_init(&ctx, net, skb, nlh, afi, table, NULL, nla);
+@@ -749,10 +786,11 @@ err1:
+
+ static int nft_flush_table(struct nft_ctx *ctx)
+ {
+- int err;
++ struct nft_flowtable *flowtable, *nft;
+ struct nft_chain *chain, *nc;
+ struct nft_object *obj, *ne;
+ struct nft_set *set, *ns;
++ int err;
+
+ list_for_each_entry(chain, &ctx->table->chains, list) {
+ if (!nft_is_active_next(ctx->net, chain))
+@@ -778,6 +816,12 @@ static int nft_flush_table(struct nft_ct
+ goto out;
+ }
+
++ list_for_each_entry_safe(flowtable, nft, &ctx->table->flowtables, list) {
++ err = nft_delflowtable(ctx, flowtable);
++ if (err < 0)
++ goto out;
++ }
++
+ list_for_each_entry_safe(obj, ne, &ctx->table->objects, list) {
+ err = nft_delobj(ctx, obj);
+ if (err < 0)
+@@ -4765,6 +4809,605 @@ static void nf_tables_obj_notify(const s
+ ctx->afi->family, ctx->report, GFP_KERNEL);
+ }
+
++/*
++ * Flow tables
++ */
++void nft_register_flowtable_type(struct nf_flowtable_type *type)
++{
++ nfnl_lock(NFNL_SUBSYS_NFTABLES);
++ list_add_tail_rcu(&type->list, &nf_tables_flowtables);
++ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
++}
++EXPORT_SYMBOL_GPL(nft_register_flowtable_type);
++
++void nft_unregister_flowtable_type(struct nf_flowtable_type *type)
++{
++ nfnl_lock(NFNL_SUBSYS_NFTABLES);
++ list_del_rcu(&type->list);
++ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
++}
++EXPORT_SYMBOL_GPL(nft_unregister_flowtable_type);
++
++static const struct nla_policy nft_flowtable_policy[NFTA_FLOWTABLE_MAX + 1] = {
++ [NFTA_FLOWTABLE_TABLE] = { .type = NLA_STRING,
++ .len = NFT_NAME_MAXLEN - 1 },
++ [NFTA_FLOWTABLE_NAME] = { .type = NLA_STRING,
++ .len = NFT_NAME_MAXLEN - 1 },
++ [NFTA_FLOWTABLE_HOOK] = { .type = NLA_NESTED },
++};
++
++struct nft_flowtable *nf_tables_flowtable_lookup(const struct nft_table *table,
++ const struct nlattr *nla,
++ u8 genmask)
++{
++ struct nft_flowtable *flowtable;
++
++ list_for_each_entry(flowtable, &table->flowtables, list) {
++ if (!nla_strcmp(nla, flowtable->name) &&
++ nft_active_genmask(flowtable, genmask))
++ return flowtable;
++ }
++ return ERR_PTR(-ENOENT);
++}
++EXPORT_SYMBOL_GPL(nf_tables_flowtable_lookup);
++
++#define NFT_FLOWTABLE_DEVICE_MAX 8
++
++static int nf_tables_parse_devices(const struct nft_ctx *ctx,
++ const struct nlattr *attr,
++ struct net_device *dev_array[], int *len)
++{
++ const struct nlattr *tmp;
++ struct net_device *dev;
++ char ifname[IFNAMSIZ];
++ int rem, n = 0, err;
++
++ nla_for_each_nested(tmp, attr, rem) {
++ if (nla_type(tmp) != NFTA_DEVICE_NAME) {
++ err = -EINVAL;
++ goto err1;
++ }
++
++ nla_strlcpy(ifname, tmp, IFNAMSIZ);
++ dev = dev_get_by_name(ctx->net, ifname);
++ if (!dev) {
++ err = -ENOENT;
++ goto err1;
++ }
++
++ dev_array[n++] = dev;
++ if (n == NFT_FLOWTABLE_DEVICE_MAX) {
++ err = -EFBIG;
++ goto err1;
++ }
++ }
++ if (!len)
++ return -EINVAL;
++
++ err = 0;
++err1:
++ *len = n;
++ return err;
++}
++
++static const struct nla_policy nft_flowtable_hook_policy[NFTA_FLOWTABLE_HOOK_MAX + 1] = {
++ [NFTA_FLOWTABLE_HOOK_NUM] = { .type = NLA_U32 },
++ [NFTA_FLOWTABLE_HOOK_PRIORITY] = { .type = NLA_U32 },
++ [NFTA_FLOWTABLE_HOOK_DEVS] = { .type = NLA_NESTED },
++};
++
++static int nf_tables_flowtable_parse_hook(const struct nft_ctx *ctx,
++ const struct nlattr *attr,
++ struct nft_flowtable *flowtable)
++{
++ struct net_device *dev_array[NFT_FLOWTABLE_DEVICE_MAX];
++ struct nlattr *tb[NFTA_FLOWTABLE_HOOK_MAX + 1];
++ struct nf_hook_ops *ops;
++ int hooknum, priority;
++ int err, n = 0, i;
++
++ err = nla_parse_nested(tb, NFTA_FLOWTABLE_HOOK_MAX, attr,
++ nft_flowtable_hook_policy, NULL);
++ if (err < 0)
++ return err;
++
++ if (!tb[NFTA_FLOWTABLE_HOOK_NUM] ||
++ !tb[NFTA_FLOWTABLE_HOOK_PRIORITY] ||
++ !tb[NFTA_FLOWTABLE_HOOK_DEVS])
++ return -EINVAL;
++
++ hooknum = ntohl(nla_get_be32(tb[NFTA_FLOWTABLE_HOOK_NUM]));
++ if (hooknum >= ctx->afi->nhooks)
++ return -EINVAL;
++
++ priority = ntohl(nla_get_be32(tb[NFTA_FLOWTABLE_HOOK_PRIORITY]));
++
++ err = nf_tables_parse_devices(ctx, tb[NFTA_FLOWTABLE_HOOK_DEVS],
++ dev_array, &n);
++ if (err < 0)
++ goto err1;
++
++ ops = kzalloc(sizeof(struct nf_hook_ops) * n, GFP_KERNEL);
++ if (!ops) {
++ err = -ENOMEM;
++ goto err1;
++ }
++
++ flowtable->ops = ops;
++ flowtable->ops_len = n;
++
++ for (i = 0; i < n; i++) {
++ flowtable->ops[i].pf = NFPROTO_NETDEV;
++ flowtable->ops[i].hooknum = hooknum;
++ flowtable->ops[i].priority = priority;
++ flowtable->ops[i].priv = &flowtable->data.rhashtable;
++ flowtable->ops[i].hook = flowtable->data.type->hook;
++ flowtable->ops[i].dev = dev_array[i];
++ }
++
++ err = 0;
++err1:
++ for (i = 0; i < n; i++)
++ dev_put(dev_array[i]);
++
++ return err;
++}
++
++static const struct nf_flowtable_type *
++__nft_flowtable_type_get(const struct nft_af_info *afi)
++{
++ const struct nf_flowtable_type *type;
++
++ list_for_each_entry(type, &nf_tables_flowtables, list) {
++ if (afi->family == type->family)
++ return type;
++ }
++ return NULL;
++}
++
++static const struct nf_flowtable_type *
++nft_flowtable_type_get(const struct nft_af_info *afi)
++{
++ const struct nf_flowtable_type *type;
++
++ type = __nft_flowtable_type_get(afi);
++ if (type != NULL && try_module_get(type->owner))
++ return type;
++
++#ifdef CONFIG_MODULES
++ if (type == NULL) {
++ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
++ request_module("nf-flowtable-%u", afi->family);
++ nfnl_lock(NFNL_SUBSYS_NFTABLES);
++ if (__nft_flowtable_type_get(afi))
++ return ERR_PTR(-EAGAIN);
++ }
++#endif
++ return ERR_PTR(-ENOENT);
++}
++
++void nft_flow_table_iterate(struct net *net,
++ void (*iter)(struct nf_flowtable *flowtable, void *data),
++ void *data)
++{
++ struct nft_flowtable *flowtable;
++ const struct nft_af_info *afi;
++ const struct nft_table *table;
++
++ rcu_read_lock();
++ list_for_each_entry_rcu(afi, &net->nft.af_info, list) {
++ list_for_each_entry_rcu(table, &afi->tables, list) {
++ list_for_each_entry_rcu(flowtable, &table->flowtables, list) {
++ iter(&flowtable->data, data);
++ }
++ }
++ }
++ rcu_read_unlock();
++}
++EXPORT_SYMBOL_GPL(nft_flow_table_iterate);
++
++static void nft_unregister_flowtable_net_hooks(struct net *net,
++ struct nft_flowtable *flowtable)
++{
++ int i;
++
++ for (i = 0; i < flowtable->ops_len; i++) {
++ if (!flowtable->ops[i].dev)
++ continue;
++
++ nf_unregister_net_hook(net, &flowtable->ops[i]);
++ }
++}
++
++static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
++ struct sk_buff *skb,
++ const struct nlmsghdr *nlh,
++ const struct nlattr * const nla[],
++ struct netlink_ext_ack *extack)
++{
++ const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
++ const struct nf_flowtable_type *type;
++ u8 genmask = nft_genmask_next(net);
++ int family = nfmsg->nfgen_family;
++ struct nft_flowtable *flowtable;
++ struct nft_af_info *afi;
++ struct nft_table *table;
++ struct nft_ctx ctx;
++ int err, i, k;
++
++ if (!nla[NFTA_FLOWTABLE_TABLE] ||
++ !nla[NFTA_FLOWTABLE_NAME] ||
++ !nla[NFTA_FLOWTABLE_HOOK])
++ return -EINVAL;
++
++ afi = nf_tables_afinfo_lookup(net, family, true);
++ if (IS_ERR(afi))
++ return PTR_ERR(afi);
++
++ table = nf_tables_table_lookup(afi, nla[NFTA_FLOWTABLE_TABLE], genmask);
++ if (IS_ERR(table))
++ return PTR_ERR(table);
++
++ flowtable = nf_tables_flowtable_lookup(table, nla[NFTA_FLOWTABLE_NAME],
++ genmask);
++ if (IS_ERR(flowtable)) {
++ err = PTR_ERR(flowtable);
++ if (err != -ENOENT)
++ return err;
++ } else {
++ if (nlh->nlmsg_flags & NLM_F_EXCL)
++ return -EEXIST;
++
++ return 0;
++ }
++
++ nft_ctx_init(&ctx, net, skb, nlh, afi, table, NULL, nla);
++
++ flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL);
++ if (!flowtable)
++ return -ENOMEM;
++
++ flowtable->table = table;
++ flowtable->name = nla_strdup(nla[NFTA_FLOWTABLE_NAME], GFP_KERNEL);
++ if (!flowtable->name) {
++ err = -ENOMEM;
++ goto err1;
++ }
++
++ type = nft_flowtable_type_get(afi);
++ if (IS_ERR(type)) {
++ err = PTR_ERR(type);
++ goto err2;
++ }
++
++ flowtable->data.type = type;
++ err = rhashtable_init(&flowtable->data.rhashtable, type->params);
++ if (err < 0)
++ goto err3;
++
++ err = nf_tables_flowtable_parse_hook(&ctx, nla[NFTA_FLOWTABLE_HOOK],
++ flowtable);
++ if (err < 0)
++ goto err3;
++
++ for (i = 0; i < flowtable->ops_len; i++) {
++ err = nf_register_net_hook(net, &flowtable->ops[i]);
++ if (err < 0)
++ goto err4;
++ }
++
++ err = nft_trans_flowtable_add(&ctx, NFT_MSG_NEWFLOWTABLE, flowtable);
++ if (err < 0)
++ goto err5;
++
++ INIT_DEFERRABLE_WORK(&flowtable->data.gc_work, type->gc);
++ queue_delayed_work(system_power_efficient_wq,
++ &flowtable->data.gc_work, HZ);
++
++ list_add_tail_rcu(&flowtable->list, &table->flowtables);
++ table->use++;
++
++ return 0;
++err5:
++ i = flowtable->ops_len;
++err4:
++ for (k = i - 1; k >= 0; k--)
++ nf_unregister_net_hook(net, &flowtable->ops[i]);
++
++ kfree(flowtable->ops);
++err3:
++ module_put(type->owner);
++err2:
++ kfree(flowtable->name);
++err1:
++ kfree(flowtable);
++ return err;
++}
++
++static int nf_tables_delflowtable(struct net *net, struct sock *nlsk,
++ struct sk_buff *skb,
++ const struct nlmsghdr *nlh,
++ const struct nlattr * const nla[],
++ struct netlink_ext_ack *extack)
++{
++ const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
++ u8 genmask = nft_genmask_next(net);
++ int family = nfmsg->nfgen_family;
++ struct nft_flowtable *flowtable;
++ struct nft_af_info *afi;
++ struct nft_table *table;
++ struct nft_ctx ctx;
++
++ afi = nf_tables_afinfo_lookup(net, family, true);
++ if (IS_ERR(afi))
++ return PTR_ERR(afi);
++
++ table = nf_tables_table_lookup(afi, nla[NFTA_FLOWTABLE_TABLE], genmask);
++ if (IS_ERR(table))
++ return PTR_ERR(table);
++
++ flowtable = nf_tables_flowtable_lookup(table, nla[NFTA_FLOWTABLE_NAME],
++ genmask);
++ if (IS_ERR(flowtable))
++ return PTR_ERR(flowtable);
++ if (flowtable->use > 0)
++ return -EBUSY;
++
++ nft_ctx_init(&ctx, net, skb, nlh, afi, table, NULL, nla);
++
++ return nft_delflowtable(&ctx, flowtable);
++}
++
++static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net,
++ u32 portid, u32 seq, int event,
++ u32 flags, int family,
++ struct nft_flowtable *flowtable)
++{
++ struct nlattr *nest, *nest_devs;
++ struct nfgenmsg *nfmsg;
++ struct nlmsghdr *nlh;
++ int i;
++
++ event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
++ nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg), flags);
++ if (nlh == NULL)
++ goto nla_put_failure;
++
++ nfmsg = nlmsg_data(nlh);
++ nfmsg->nfgen_family = family;
++ nfmsg->version = NFNETLINK_V0;
++ nfmsg->res_id = htons(net->nft.base_seq & 0xffff);
++
++ if (nla_put_string(skb, NFTA_FLOWTABLE_TABLE, flowtable->table->name) ||
++ nla_put_string(skb, NFTA_FLOWTABLE_NAME, flowtable->name) ||
++ nla_put_be32(skb, NFTA_FLOWTABLE_USE, htonl(flowtable->use)))
++ goto nla_put_failure;
++
++ nest = nla_nest_start(skb, NFTA_FLOWTABLE_HOOK);
++ if (nla_put_be32(skb, NFTA_FLOWTABLE_HOOK_NUM, htonl(flowtable->hooknum)) ||
++ nla_put_be32(skb, NFTA_FLOWTABLE_HOOK_PRIORITY, htonl(flowtable->priority)))
++ goto nla_put_failure;
++
++ nest_devs = nla_nest_start(skb, NFTA_FLOWTABLE_HOOK_DEVS);
++ if (!nest_devs)
++ goto nla_put_failure;
++
++ for (i = 0; i < flowtable->ops_len; i++) {
++ if (flowtable->ops[i].dev &&
++ nla_put_string(skb, NFTA_DEVICE_NAME,
++ flowtable->ops[i].dev->name))
++ goto nla_put_failure;
++ }
++ nla_nest_end(skb, nest_devs);
++ nla_nest_end(skb, nest);
++
++ nlmsg_end(skb, nlh);
++ return 0;
++
++nla_put_failure:
++ nlmsg_trim(skb, nlh);
++ return -1;
++}
++
++struct nft_flowtable_filter {
++ char *table;
++};
++
++static int nf_tables_dump_flowtable(struct sk_buff *skb,
++ struct netlink_callback *cb)
++{
++ const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
++ struct nft_flowtable_filter *filter = cb->data;
++ unsigned int idx = 0, s_idx = cb->args[0];
++ struct net *net = sock_net(skb->sk);
++ int family = nfmsg->nfgen_family;
++ struct nft_flowtable *flowtable;
++ const struct nft_af_info *afi;
++ const struct nft_table *table;
++
++ rcu_read_lock();
++ cb->seq = net->nft.base_seq;
++
++ list_for_each_entry_rcu(afi, &net->nft.af_info, list) {
++ if (family != NFPROTO_UNSPEC && family != afi->family)
++ continue;
++
++ list_for_each_entry_rcu(table, &afi->tables, list) {
++ list_for_each_entry_rcu(flowtable, &table->flowtables, list) {
++ if (!nft_is_active(net, flowtable))
++ goto cont;
++ if (idx < s_idx)
++ goto cont;
++ if (idx > s_idx)
++ memset(&cb->args[1], 0,
++ sizeof(cb->args) - sizeof(cb->args[0]));
++ if (filter && filter->table[0] &&
++ strcmp(filter->table, table->name))
++ goto cont;
++
++ if (nf_tables_fill_flowtable_info(skb, net, NETLINK_CB(cb->skb).portid,
++ cb->nlh->nlmsg_seq,
++ NFT_MSG_NEWFLOWTABLE,
++ NLM_F_MULTI | NLM_F_APPEND,
++ afi->family, flowtable) < 0)
++ goto done;
++
++ nl_dump_check_consistent(cb, nlmsg_hdr(skb));
++cont:
++ idx++;
++ }
++ }
++ }
++done:
++ rcu_read_unlock();
++
++ cb->args[0] = idx;
++ return skb->len;
++}
++
++static int nf_tables_dump_flowtable_done(struct netlink_callback *cb)
++{
++ struct nft_flowtable_filter *filter = cb->data;
++
++ if (!filter)
++ return 0;
++
++ kfree(filter->table);
++ kfree(filter);
++
++ return 0;
++}
++
++static struct nft_flowtable_filter *
++nft_flowtable_filter_alloc(const struct nlattr * const nla[])
++{
++ struct nft_flowtable_filter *filter;
++
++ filter = kzalloc(sizeof(*filter), GFP_KERNEL);
++ if (!filter)
++ return ERR_PTR(-ENOMEM);
++
++ if (nla[NFTA_FLOWTABLE_TABLE]) {
++ filter->table = nla_strdup(nla[NFTA_FLOWTABLE_TABLE],
++ GFP_KERNEL);
++ if (!filter->table) {
++ kfree(filter);
++ return ERR_PTR(-ENOMEM);
++ }
++ }
++ return filter;
++}
++
++static int nf_tables_getflowtable(struct net *net, struct sock *nlsk,
++ struct sk_buff *skb,
++ const struct nlmsghdr *nlh,
++ const struct nlattr * const nla[],
++ struct netlink_ext_ack *extack)
++{
++ const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
++ u8 genmask = nft_genmask_cur(net);
++ int family = nfmsg->nfgen_family;
++ struct nft_flowtable *flowtable;
++ const struct nft_af_info *afi;
++ const struct nft_table *table;
++ struct sk_buff *skb2;
++ int err;
++
++ if (nlh->nlmsg_flags & NLM_F_DUMP) {
++ struct netlink_dump_control c = {
++ .dump = nf_tables_dump_flowtable,
++ .done = nf_tables_dump_flowtable_done,
++ };
++
++ if (nla[NFTA_FLOWTABLE_TABLE]) {
++ struct nft_flowtable_filter *filter;
++
++ filter = nft_flowtable_filter_alloc(nla);
++ if (IS_ERR(filter))
++ return -ENOMEM;
++
++ c.data = filter;
++ }
++ return netlink_dump_start(nlsk, skb, nlh, &c);
++ }
++
++ if (!nla[NFTA_FLOWTABLE_NAME])
++ return -EINVAL;
++
++ afi = nf_tables_afinfo_lookup(net, family, false);
++ if (IS_ERR(afi))
++ return PTR_ERR(afi);
++
++ table = nf_tables_table_lookup(afi, nla[NFTA_FLOWTABLE_TABLE], genmask);
++ if (IS_ERR(table))
++ return PTR_ERR(table);
++
++ flowtable = nf_tables_flowtable_lookup(table, nla[NFTA_FLOWTABLE_NAME],
++ genmask);
++ if (IS_ERR(table))
++ return PTR_ERR(flowtable);
++
++ skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
++ if (!skb2)
++ return -ENOMEM;
++
++ err = nf_tables_fill_flowtable_info(skb2, net, NETLINK_CB(skb).portid,
++ nlh->nlmsg_seq,
++ NFT_MSG_NEWFLOWTABLE, 0, family,
++ flowtable);
++ if (err < 0)
++ goto err;
++
++ return nlmsg_unicast(nlsk, skb2, NETLINK_CB(skb).portid);
++err:
++ kfree_skb(skb2);
++ return err;
++}
++
++static void nf_tables_flowtable_notify(struct nft_ctx *ctx,
++ struct nft_flowtable *flowtable,
++ int event)
++{
++ struct sk_buff *skb;
++ int err;
++
++ if (ctx->report &&
++ !nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
++ return;
++
++ skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
++ if (skb == NULL)
++ goto err;
++
++ err = nf_tables_fill_flowtable_info(skb, ctx->net, ctx->portid,
++ ctx->seq, event, 0,
++ ctx->afi->family, flowtable);
++ if (err < 0) {
++ kfree_skb(skb);
++ goto err;
++ }
++
++ nfnetlink_send(skb, ctx->net, ctx->portid, NFNLGRP_NFTABLES,
++ ctx->report, GFP_KERNEL);
++ return;
++err:
++ nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES, -ENOBUFS);
++}
++
++static void nft_flowtable_destroy(void *ptr, void *arg)
++{
++ kfree(ptr);
++}
++
++static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
++{
++ cancel_delayed_work_sync(&flowtable->data.gc_work);
++ kfree(flowtable->name);
++ rhashtable_free_and_destroy(&flowtable->data.rhashtable,
++ nft_flowtable_destroy, NULL);
++ module_put(flowtable->data.type->owner);
++}
++
+ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
+ u32 portid, u32 seq)
+ {
+@@ -4795,6 +5438,49 @@ nla_put_failure:
+ return -EMSGSIZE;
+ }
+
++static void nft_flowtable_event(unsigned long event, struct net_device *dev,
++ struct nft_flowtable *flowtable)
++{
++ int i;
++
++ for (i = 0; i < flowtable->ops_len; i++) {
++ if (flowtable->ops[i].dev != dev)
++ continue;
++
++ nf_unregister_net_hook(dev_net(dev), &flowtable->ops[i]);
++ flowtable->ops[i].dev = NULL;
++ break;
++ }
++}
++
++static int nf_tables_flowtable_event(struct notifier_block *this,
++ unsigned long event, void *ptr)
++{
++ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
++ struct nft_flowtable *flowtable;
++ struct nft_table *table;
++ struct nft_af_info *afi;
++
++ if (event != NETDEV_UNREGISTER)
++ return 0;
++
++ nfnl_lock(NFNL_SUBSYS_NFTABLES);
++ list_for_each_entry(afi, &dev_net(dev)->nft.af_info, list) {
++ list_for_each_entry(table, &afi->tables, list) {
++ list_for_each_entry(flowtable, &table->flowtables, list) {
++ nft_flowtable_event(event, dev, flowtable);
++ }
++ }
++ }
++ nfnl_unlock(NFNL_SUBSYS_NFTABLES);
++
++ return NOTIFY_DONE;
++}
++
++static struct notifier_block nf_tables_flowtable_notifier = {
++ .notifier_call = nf_tables_flowtable_event,
++};
++
+ static void nf_tables_gen_notify(struct net *net, struct sk_buff *skb,
+ int event)
+ {
+@@ -4947,6 +5633,21 @@ static const struct nfnl_callback nf_tab
+ .attr_count = NFTA_OBJ_MAX,
+ .policy = nft_obj_policy,
+ },
++ [NFT_MSG_NEWFLOWTABLE] = {
++ .call_batch = nf_tables_newflowtable,
++ .attr_count = NFTA_FLOWTABLE_MAX,
++ .policy = nft_flowtable_policy,
++ },
++ [NFT_MSG_GETFLOWTABLE] = {
++ .call = nf_tables_getflowtable,
++ .attr_count = NFTA_FLOWTABLE_MAX,
++ .policy = nft_flowtable_policy,
++ },
++ [NFT_MSG_DELFLOWTABLE] = {
++ .call_batch = nf_tables_delflowtable,
++ .attr_count = NFTA_FLOWTABLE_MAX,
++ .policy = nft_flowtable_policy,
++ },
+ };
+
+ static void nft_chain_commit_update(struct nft_trans *trans)
+@@ -4992,6 +5693,9 @@ static void nf_tables_commit_release(str
+ case NFT_MSG_DELOBJ:
+ nft_obj_destroy(nft_trans_obj(trans));
+ break;
++ case NFT_MSG_DELFLOWTABLE:
++ nf_tables_flowtable_destroy(nft_trans_flowtable(trans));
++ break;
+ }
+ kfree(trans);
+ }
+@@ -5109,6 +5813,21 @@ static int nf_tables_commit(struct net *
+ nf_tables_obj_notify(&trans->ctx, nft_trans_obj(trans),
+ NFT_MSG_DELOBJ);
+ break;
++ case NFT_MSG_NEWFLOWTABLE:
++ nft_clear(net, nft_trans_flowtable(trans));
++ nf_tables_flowtable_notify(&trans->ctx,
++ nft_trans_flowtable(trans),
++ NFT_MSG_NEWFLOWTABLE);
++ nft_trans_destroy(trans);
++ break;
++ case NFT_MSG_DELFLOWTABLE:
++ list_del_rcu(&nft_trans_flowtable(trans)->list);
++ nf_tables_flowtable_notify(&trans->ctx,
++ nft_trans_flowtable(trans),
++ NFT_MSG_DELFLOWTABLE);
++ nft_unregister_flowtable_net_hooks(net,
++ nft_trans_flowtable(trans));
++ break;
+ }
+ }
+
+@@ -5146,6 +5865,9 @@ static void nf_tables_abort_release(stru
+ case NFT_MSG_NEWOBJ:
+ nft_obj_destroy(nft_trans_obj(trans));
+ break;
++ case NFT_MSG_NEWFLOWTABLE:
++ nf_tables_flowtable_destroy(nft_trans_flowtable(trans));
++ break;
+ }
+ kfree(trans);
+ }
+@@ -5235,6 +5957,17 @@ static int nf_tables_abort(struct net *n
+ nft_clear(trans->ctx.net, nft_trans_obj(trans));
+ nft_trans_destroy(trans);
+ break;
++ case NFT_MSG_NEWFLOWTABLE:
++ trans->ctx.table->use--;
++ list_del_rcu(&nft_trans_flowtable(trans)->list);
++ nft_unregister_flowtable_net_hooks(net,
++ nft_trans_flowtable(trans));
++ break;
++ case NFT_MSG_DELFLOWTABLE:
++ trans->ctx.table->use++;
++ nft_clear(trans->ctx.net, nft_trans_flowtable(trans));
++ nft_trans_destroy(trans);
++ break;
+ }
+ }
+
+@@ -5785,6 +6518,7 @@ EXPORT_SYMBOL_GPL(__nft_release_basechai
+ /* Called by nft_unregister_afinfo() from __net_exit path, nfnl_lock is held. */
+ static void __nft_release_afinfo(struct net *net, struct nft_af_info *afi)
+ {
++ struct nft_flowtable *flowtable, *nf;
+ struct nft_table *table, *nt;
+ struct nft_chain *chain, *nc;
+ struct nft_object *obj, *ne;
+@@ -5798,6 +6532,9 @@ static void __nft_release_afinfo(struct
+ list_for_each_entry_safe(table, nt, &afi->tables, list) {
+ list_for_each_entry(chain, &table->chains, list)
+ nf_tables_unregister_hook(net, table, chain);
++ list_for_each_entry(flowtable, &table->flowtables, list)
++ nf_unregister_net_hooks(net, flowtable->ops,
++ flowtable->ops_len);
+ /* No packets are walking on these chains anymore. */
+ ctx.table = table;
+ list_for_each_entry(chain, &table->chains, list) {
+@@ -5808,6 +6545,11 @@ static void __nft_release_afinfo(struct
+ nf_tables_rule_destroy(&ctx, rule);
+ }
+ }
++ list_for_each_entry_safe(flowtable, nf, &table->flowtables, list) {
++ list_del(&flowtable->list);
++ table->use--;
++ nf_tables_flowtable_destroy(flowtable);
++ }
+ list_for_each_entry_safe(set, ns, &table->sets, list) {
+ list_del(&set->list);
+ table->use--;
+@@ -5851,6 +6593,8 @@ static int __init nf_tables_module_init(
+ if (err < 0)
+ goto err3;
+
++ register_netdevice_notifier(&nf_tables_flowtable_notifier);
++
+ pr_info("nf_tables: (c) 2007-2009 Patrick McHardy <kaber@trash.net>\n");
+ return register_pernet_subsys(&nf_tables_net_ops);
+ err3:
+@@ -5865,6 +6609,7 @@ static void __exit nf_tables_module_exit
+ {
+ unregister_pernet_subsys(&nf_tables_net_ops);
+ nfnetlink_subsys_unregister(&nf_tables_subsys);
++ unregister_netdevice_notifier(&nf_tables_flowtable_notifier);
+ rcu_barrier();
+ nf_tables_core_module_exit();
+ kfree(info);