|
|
|
@@ -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);
|
|
|
|
|