netfilter: nf_tables: add flow table netlink frontend
authorPablo Neira Ayuso <pablo@netfilter.org>
Sun, 7 Jan 2018 00:04:07 +0000 (01:04 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 8 Jan 2018 17:11:06 +0000 (18:11 +0100)
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>
include/net/netfilter/nf_flow_table.h [new file with mode: 0644]
include/net/netfilter/nf_tables.h
include/uapi/linux/netfilter/nf_tables.h
net/netfilter/nf_tables_api.c

diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
new file mode 100644 (file)
index 0000000..3a07795
--- /dev/null
@@ -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 */
index e3ec02f..dd23895 100644 (file)
@@ -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
@@ -943,6 +944,7 @@ unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv);
  *     @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)
@@ -954,6 +956,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,
@@ -1084,6 +1087,44 @@ struct nft_object_ops {
 int nft_register_obj(struct nft_object_type *obj_type);
 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
  *
@@ -1317,4 +1358,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 */
index 2efbf97..591b53b 100644 (file)
@@ -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,
 };
 
@@ -1309,6 +1315,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
  *
index fa564da..db09332 100644 (file)
@@ -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 *ctx, struct nft_object *obj)
        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 *net, struct sock *nlsk,
        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_ctx *ctx)
                        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)
@@ -4839,6 +4883,605 @@ static void nf_tables_obj_notify(const struct nft_ctx *ctx,
                       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)
 {
@@ -4869,6 +5512,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)
 {
@@ -5021,6 +5707,21 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
                .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)
@@ -5066,6 +5767,9 @@ static void nf_tables_commit_release(struct nft_trans *trans)
        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);
 }
@@ -5183,6 +5887,21 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
                        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;
                }
        }
 
@@ -5220,6 +5939,9 @@ static void nf_tables_abort_release(struct nft_trans *trans)
        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);
 }
@@ -5309,6 +6031,17 @@ static int nf_tables_abort(struct net *net, struct sk_buff *skb)
                        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;
                }
        }
 
@@ -5865,6 +6598,7 @@ EXPORT_SYMBOL_GPL(__nft_release_basechain);
 /* 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;
@@ -5878,6 +6612,9 @@ static void __nft_release_afinfo(struct net *net, struct nft_af_info *afi)
        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) {
@@ -5888,6 +6625,11 @@ static void __nft_release_afinfo(struct net *net, struct nft_af_info *afi)
                                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--;
@@ -5932,6 +6674,8 @@ static int __init nf_tables_module_init(void)
        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:
@@ -5946,6 +6690,7 @@ static void __exit nf_tables_module_exit(void)
 {
        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);