You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1079 lines
29 KiB
1079 lines
29 KiB
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)
|
|
@@ -4774,6 +4818,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)
|
|
{
|
|
@@ -4804,6 +5447,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)
|
|
{
|
|
@@ -4956,6 +5642,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)
|
|
@@ -5001,6 +5702,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);
|
|
}
|
|
@@ -5118,6 +5822,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;
|
|
}
|
|
}
|
|
|
|
@@ -5155,6 +5874,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);
|
|
}
|
|
@@ -5244,6 +5966,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;
|
|
}
|
|
}
|
|
|
|
@@ -5794,6 +6527,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;
|
|
@@ -5807,6 +6541,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) {
|
|
@@ -5817,6 +6554,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--;
|
|
@@ -5860,6 +6602,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:
|
|
@@ -5874,6 +6618,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);
|
|
|