netfilter: nf_tables: use new transaction infrastructure to handle chain
This patch speeds up rule-set updates and it also introduces a way to revert chain updates if the batch is aborted. The idea is to store the changes in the transaction to apply that in the commit step. Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
parent
ff3cd7b3c9
commit
91c7b38dc9
|
@ -420,6 +420,22 @@ struct nft_trans_set {
|
||||||
#define nft_trans_set_id(trans) \
|
#define nft_trans_set_id(trans) \
|
||||||
(((struct nft_trans_set *)trans->data)->set_id)
|
(((struct nft_trans_set *)trans->data)->set_id)
|
||||||
|
|
||||||
|
struct nft_trans_chain {
|
||||||
|
bool update;
|
||||||
|
char name[NFT_CHAIN_MAXNAMELEN];
|
||||||
|
struct nft_stats __percpu *stats;
|
||||||
|
u8 policy;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define nft_trans_chain_update(trans) \
|
||||||
|
(((struct nft_trans_chain *)trans->data)->update)
|
||||||
|
#define nft_trans_chain_name(trans) \
|
||||||
|
(((struct nft_trans_chain *)trans->data)->name)
|
||||||
|
#define nft_trans_chain_stats(trans) \
|
||||||
|
(((struct nft_trans_chain *)trans->data)->stats)
|
||||||
|
#define nft_trans_chain_policy(trans) \
|
||||||
|
(((struct nft_trans_chain *)trans->data)->policy)
|
||||||
|
|
||||||
static inline struct nft_expr *nft_expr_first(const struct nft_rule *rule)
|
static inline struct nft_expr *nft_expr_first(const struct nft_rule *rule)
|
||||||
{
|
{
|
||||||
return (struct nft_expr *)&rule->data[0];
|
return (struct nft_expr *)&rule->data[0];
|
||||||
|
@ -452,6 +468,7 @@ static inline void *nft_userdata(const struct nft_rule *rule)
|
||||||
|
|
||||||
enum nft_chain_flags {
|
enum nft_chain_flags {
|
||||||
NFT_BASE_CHAIN = 0x1,
|
NFT_BASE_CHAIN = 0x1,
|
||||||
|
NFT_CHAIN_INACTIVE = 0x2,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -782,6 +782,8 @@ static int nf_tables_getchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
chain = nf_tables_chain_lookup(table, nla[NFTA_CHAIN_NAME]);
|
chain = nf_tables_chain_lookup(table, nla[NFTA_CHAIN_NAME]);
|
||||||
if (IS_ERR(chain))
|
if (IS_ERR(chain))
|
||||||
return PTR_ERR(chain);
|
return PTR_ERR(chain);
|
||||||
|
if (chain->flags & NFT_CHAIN_INACTIVE)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
|
skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
|
||||||
if (!skb2)
|
if (!skb2)
|
||||||
|
@ -847,6 +849,34 @@ static void nft_chain_stats_replace(struct nft_base_chain *chain,
|
||||||
rcu_assign_pointer(chain->stats, newstats);
|
rcu_assign_pointer(chain->stats, newstats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int nft_trans_chain_add(struct nft_ctx *ctx, int msg_type)
|
||||||
|
{
|
||||||
|
struct nft_trans *trans;
|
||||||
|
|
||||||
|
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_chain));
|
||||||
|
if (trans == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (msg_type == NFT_MSG_NEWCHAIN)
|
||||||
|
ctx->chain->flags |= NFT_CHAIN_INACTIVE;
|
||||||
|
|
||||||
|
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nf_tables_chain_destroy(struct nft_chain *chain)
|
||||||
|
{
|
||||||
|
BUG_ON(chain->use > 0);
|
||||||
|
|
||||||
|
if (chain->flags & NFT_BASE_CHAIN) {
|
||||||
|
module_put(nft_base_chain(chain)->type->owner);
|
||||||
|
free_percpu(nft_base_chain(chain)->stats);
|
||||||
|
kfree(nft_base_chain(chain));
|
||||||
|
} else {
|
||||||
|
kfree(chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
|
static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
const struct nlmsghdr *nlh,
|
const struct nlmsghdr *nlh,
|
||||||
const struct nlattr * const nla[])
|
const struct nlattr * const nla[])
|
||||||
|
@ -866,6 +896,7 @@ static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
struct nft_stats __percpu *stats;
|
struct nft_stats __percpu *stats;
|
||||||
int err;
|
int err;
|
||||||
bool create;
|
bool create;
|
||||||
|
struct nft_ctx ctx;
|
||||||
|
|
||||||
create = nlh->nlmsg_flags & NLM_F_CREATE ? true : false;
|
create = nlh->nlmsg_flags & NLM_F_CREATE ? true : false;
|
||||||
|
|
||||||
|
@ -911,6 +942,11 @@ static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chain != NULL) {
|
if (chain != NULL) {
|
||||||
|
struct nft_stats *stats = NULL;
|
||||||
|
struct nft_trans *trans;
|
||||||
|
|
||||||
|
if (chain->flags & NFT_CHAIN_INACTIVE)
|
||||||
|
return -ENOENT;
|
||||||
if (nlh->nlmsg_flags & NLM_F_EXCL)
|
if (nlh->nlmsg_flags & NLM_F_EXCL)
|
||||||
return -EEXIST;
|
return -EEXIST;
|
||||||
if (nlh->nlmsg_flags & NLM_F_REPLACE)
|
if (nlh->nlmsg_flags & NLM_F_REPLACE)
|
||||||
|
@ -927,17 +963,28 @@ static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
stats = nft_stats_alloc(nla[NFTA_CHAIN_COUNTERS]);
|
stats = nft_stats_alloc(nla[NFTA_CHAIN_COUNTERS]);
|
||||||
if (IS_ERR(stats))
|
if (IS_ERR(stats))
|
||||||
return PTR_ERR(stats);
|
return PTR_ERR(stats);
|
||||||
|
|
||||||
nft_chain_stats_replace(nft_base_chain(chain), stats);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nft_ctx_init(&ctx, skb, nlh, afi, table, chain, nla);
|
||||||
|
trans = nft_trans_alloc(&ctx, NFT_MSG_NEWCHAIN,
|
||||||
|
sizeof(struct nft_trans_chain));
|
||||||
|
if (trans == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nft_trans_chain_stats(trans) = stats;
|
||||||
|
nft_trans_chain_update(trans) = true;
|
||||||
|
|
||||||
if (nla[NFTA_CHAIN_POLICY])
|
if (nla[NFTA_CHAIN_POLICY])
|
||||||
nft_base_chain(chain)->policy = policy;
|
nft_trans_chain_policy(trans) = policy;
|
||||||
|
else
|
||||||
|
nft_trans_chain_policy(trans) = -1;
|
||||||
|
|
||||||
if (nla[NFTA_CHAIN_HANDLE] && name)
|
if (nla[NFTA_CHAIN_HANDLE] && name) {
|
||||||
nla_strlcpy(chain->name, name, NFT_CHAIN_MAXNAMELEN);
|
nla_strlcpy(nft_trans_chain_name(trans), name,
|
||||||
|
NFT_CHAIN_MAXNAMELEN);
|
||||||
goto notify;
|
}
|
||||||
|
list_add_tail(&trans->list, &net->nft.commit_list);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table->use == UINT_MAX)
|
if (table->use == UINT_MAX)
|
||||||
|
@ -1033,31 +1080,26 @@ static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
if (!(table->flags & NFT_TABLE_F_DORMANT) &&
|
if (!(table->flags & NFT_TABLE_F_DORMANT) &&
|
||||||
chain->flags & NFT_BASE_CHAIN) {
|
chain->flags & NFT_BASE_CHAIN) {
|
||||||
err = nf_register_hooks(nft_base_chain(chain)->ops, afi->nops);
|
err = nf_register_hooks(nft_base_chain(chain)->ops, afi->nops);
|
||||||
if (err < 0) {
|
if (err < 0)
|
||||||
module_put(basechain->type->owner);
|
goto err1;
|
||||||
free_percpu(basechain->stats);
|
|
||||||
kfree(basechain);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nft_ctx_init(&ctx, skb, nlh, afi, table, chain, nla);
|
||||||
|
err = nft_trans_chain_add(&ctx, NFT_MSG_NEWCHAIN);
|
||||||
|
if (err < 0)
|
||||||
|
goto err2;
|
||||||
|
|
||||||
list_add_tail(&chain->list, &table->chains);
|
list_add_tail(&chain->list, &table->chains);
|
||||||
table->use++;
|
|
||||||
notify:
|
|
||||||
nf_tables_chain_notify(skb, nlh, table, chain, NFT_MSG_NEWCHAIN,
|
|
||||||
family);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
err2:
|
||||||
|
if (!(table->flags & NFT_TABLE_F_DORMANT) &&
|
||||||
static void nf_tables_chain_destroy(struct nft_chain *chain)
|
chain->flags & NFT_BASE_CHAIN) {
|
||||||
{
|
nf_unregister_hooks(nft_base_chain(chain)->ops,
|
||||||
BUG_ON(chain->use > 0);
|
afi->nops);
|
||||||
|
}
|
||||||
if (chain->flags & NFT_BASE_CHAIN) {
|
err1:
|
||||||
module_put(nft_base_chain(chain)->type->owner);
|
nf_tables_chain_destroy(chain);
|
||||||
free_percpu(nft_base_chain(chain)->stats);
|
return err;
|
||||||
kfree(nft_base_chain(chain));
|
|
||||||
} else
|
|
||||||
kfree(chain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
|
static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
|
@ -1070,6 +1112,8 @@ static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
struct nft_chain *chain;
|
struct nft_chain *chain;
|
||||||
struct net *net = sock_net(skb->sk);
|
struct net *net = sock_net(skb->sk);
|
||||||
int family = nfmsg->nfgen_family;
|
int family = nfmsg->nfgen_family;
|
||||||
|
struct nft_ctx ctx;
|
||||||
|
int err;
|
||||||
|
|
||||||
afi = nf_tables_afinfo_lookup(net, family, false);
|
afi = nf_tables_afinfo_lookup(net, family, false);
|
||||||
if (IS_ERR(afi))
|
if (IS_ERR(afi))
|
||||||
|
@ -1082,24 +1126,17 @@ static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
|
||||||
chain = nf_tables_chain_lookup(table, nla[NFTA_CHAIN_NAME]);
|
chain = nf_tables_chain_lookup(table, nla[NFTA_CHAIN_NAME]);
|
||||||
if (IS_ERR(chain))
|
if (IS_ERR(chain))
|
||||||
return PTR_ERR(chain);
|
return PTR_ERR(chain);
|
||||||
|
if (chain->flags & NFT_CHAIN_INACTIVE)
|
||||||
|
return -ENOENT;
|
||||||
if (!list_empty(&chain->rules) || chain->use > 0)
|
if (!list_empty(&chain->rules) || chain->use > 0)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
|
||||||
|
nft_ctx_init(&ctx, skb, nlh, afi, table, chain, nla);
|
||||||
|
err = nft_trans_chain_add(&ctx, NFT_MSG_DELCHAIN);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
list_del(&chain->list);
|
list_del(&chain->list);
|
||||||
table->use--;
|
|
||||||
|
|
||||||
if (!(table->flags & NFT_TABLE_F_DORMANT) &&
|
|
||||||
chain->flags & NFT_BASE_CHAIN)
|
|
||||||
nf_unregister_hooks(nft_base_chain(chain)->ops, afi->nops);
|
|
||||||
|
|
||||||
nf_tables_chain_notify(skb, nlh, table, chain, NFT_MSG_DELCHAIN,
|
|
||||||
family);
|
|
||||||
|
|
||||||
/* Make sure all rule references are gone before this is released */
|
|
||||||
synchronize_rcu();
|
|
||||||
|
|
||||||
nf_tables_chain_destroy(chain);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1542,6 +1579,8 @@ static int nf_tables_getrule(struct sock *nlsk, struct sk_buff *skb,
|
||||||
chain = nf_tables_chain_lookup(table, nla[NFTA_RULE_CHAIN]);
|
chain = nf_tables_chain_lookup(table, nla[NFTA_RULE_CHAIN]);
|
||||||
if (IS_ERR(chain))
|
if (IS_ERR(chain))
|
||||||
return PTR_ERR(chain);
|
return PTR_ERR(chain);
|
||||||
|
if (chain->flags & NFT_CHAIN_INACTIVE)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
rule = nf_tables_rule_lookup(chain, nla[NFTA_RULE_HANDLE]);
|
rule = nf_tables_rule_lookup(chain, nla[NFTA_RULE_HANDLE]);
|
||||||
if (IS_ERR(rule))
|
if (IS_ERR(rule))
|
||||||
|
@ -3109,7 +3148,7 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
|
||||||
.policy = nft_table_policy,
|
.policy = nft_table_policy,
|
||||||
},
|
},
|
||||||
[NFT_MSG_NEWCHAIN] = {
|
[NFT_MSG_NEWCHAIN] = {
|
||||||
.call = nf_tables_newchain,
|
.call_batch = nf_tables_newchain,
|
||||||
.attr_count = NFTA_CHAIN_MAX,
|
.attr_count = NFTA_CHAIN_MAX,
|
||||||
.policy = nft_chain_policy,
|
.policy = nft_chain_policy,
|
||||||
},
|
},
|
||||||
|
@ -3119,7 +3158,7 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
|
||||||
.policy = nft_chain_policy,
|
.policy = nft_chain_policy,
|
||||||
},
|
},
|
||||||
[NFT_MSG_DELCHAIN] = {
|
[NFT_MSG_DELCHAIN] = {
|
||||||
.call = nf_tables_delchain,
|
.call_batch = nf_tables_delchain,
|
||||||
.attr_count = NFTA_CHAIN_MAX,
|
.attr_count = NFTA_CHAIN_MAX,
|
||||||
.policy = nft_chain_policy,
|
.policy = nft_chain_policy,
|
||||||
},
|
},
|
||||||
|
@ -3170,6 +3209,27 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void nft_chain_commit_update(struct nft_trans *trans)
|
||||||
|
{
|
||||||
|
struct nft_base_chain *basechain;
|
||||||
|
|
||||||
|
if (nft_trans_chain_name(trans)[0])
|
||||||
|
strcpy(trans->ctx.chain->name, nft_trans_chain_name(trans));
|
||||||
|
|
||||||
|
if (!(trans->ctx.chain->flags & NFT_BASE_CHAIN))
|
||||||
|
return;
|
||||||
|
|
||||||
|
basechain = nft_base_chain(trans->ctx.chain);
|
||||||
|
nft_chain_stats_replace(basechain, nft_trans_chain_stats(trans));
|
||||||
|
|
||||||
|
switch (nft_trans_chain_policy(trans)) {
|
||||||
|
case NF_DROP:
|
||||||
|
case NF_ACCEPT:
|
||||||
|
basechain->policy = nft_trans_chain_policy(trans);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int nf_tables_commit(struct sk_buff *skb)
|
static int nf_tables_commit(struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct net *net = sock_net(skb->sk);
|
struct net *net = sock_net(skb->sk);
|
||||||
|
@ -3188,6 +3248,33 @@ static int nf_tables_commit(struct sk_buff *skb)
|
||||||
|
|
||||||
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
||||||
switch (trans->msg_type) {
|
switch (trans->msg_type) {
|
||||||
|
case NFT_MSG_NEWCHAIN:
|
||||||
|
if (nft_trans_chain_update(trans))
|
||||||
|
nft_chain_commit_update(trans);
|
||||||
|
else {
|
||||||
|
trans->ctx.chain->flags &= ~NFT_CHAIN_INACTIVE;
|
||||||
|
trans->ctx.table->use++;
|
||||||
|
}
|
||||||
|
nf_tables_chain_notify(trans->ctx.skb, trans->ctx.nlh,
|
||||||
|
trans->ctx.table,
|
||||||
|
trans->ctx.chain,
|
||||||
|
NFT_MSG_NEWCHAIN,
|
||||||
|
trans->ctx.afi->family);
|
||||||
|
nft_trans_destroy(trans);
|
||||||
|
break;
|
||||||
|
case NFT_MSG_DELCHAIN:
|
||||||
|
trans->ctx.table->use--;
|
||||||
|
nf_tables_chain_notify(trans->ctx.skb, trans->ctx.nlh,
|
||||||
|
trans->ctx.table,
|
||||||
|
trans->ctx.chain,
|
||||||
|
NFT_MSG_DELCHAIN,
|
||||||
|
trans->ctx.afi->family);
|
||||||
|
if (!(trans->ctx.table->flags & NFT_TABLE_F_DORMANT) &&
|
||||||
|
trans->ctx.chain->flags & NFT_BASE_CHAIN) {
|
||||||
|
nf_unregister_hooks(nft_base_chain(trans->ctx.chain)->ops,
|
||||||
|
trans->ctx.afi->nops);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case NFT_MSG_NEWRULE:
|
case NFT_MSG_NEWRULE:
|
||||||
nft_rule_clear(trans->ctx.net, nft_trans_rule(trans));
|
nft_rule_clear(trans->ctx.net, nft_trans_rule(trans));
|
||||||
nf_tables_rule_notify(trans->ctx.skb, trans->ctx.nlh,
|
nf_tables_rule_notify(trans->ctx.skb, trans->ctx.nlh,
|
||||||
|
@ -3225,6 +3312,9 @@ static int nf_tables_commit(struct sk_buff *skb)
|
||||||
/* Now we can safely release unused old rules */
|
/* Now we can safely release unused old rules */
|
||||||
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
||||||
switch (trans->msg_type) {
|
switch (trans->msg_type) {
|
||||||
|
case NFT_MSG_DELCHAIN:
|
||||||
|
nf_tables_chain_destroy(trans->ctx.chain);
|
||||||
|
break;
|
||||||
case NFT_MSG_DELRULE:
|
case NFT_MSG_DELRULE:
|
||||||
nf_tables_rule_destroy(&trans->ctx,
|
nf_tables_rule_destroy(&trans->ctx,
|
||||||
nft_trans_rule(trans));
|
nft_trans_rule(trans));
|
||||||
|
@ -3246,6 +3336,26 @@ static int nf_tables_abort(struct sk_buff *skb)
|
||||||
|
|
||||||
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
||||||
switch (trans->msg_type) {
|
switch (trans->msg_type) {
|
||||||
|
case NFT_MSG_NEWCHAIN:
|
||||||
|
if (nft_trans_chain_update(trans)) {
|
||||||
|
if (nft_trans_chain_stats(trans))
|
||||||
|
free_percpu(nft_trans_chain_stats(trans));
|
||||||
|
|
||||||
|
nft_trans_destroy(trans);
|
||||||
|
} else {
|
||||||
|
list_del(&trans->ctx.chain->list);
|
||||||
|
if (!(trans->ctx.table->flags & NFT_TABLE_F_DORMANT) &&
|
||||||
|
trans->ctx.chain->flags & NFT_BASE_CHAIN) {
|
||||||
|
nf_unregister_hooks(nft_base_chain(trans->ctx.chain)->ops,
|
||||||
|
trans->ctx.afi->nops);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NFT_MSG_DELCHAIN:
|
||||||
|
list_add_tail(&trans->ctx.chain->list,
|
||||||
|
&trans->ctx.table->chains);
|
||||||
|
nft_trans_destroy(trans);
|
||||||
|
break;
|
||||||
case NFT_MSG_NEWRULE:
|
case NFT_MSG_NEWRULE:
|
||||||
list_del_rcu(&nft_trans_rule(trans)->list);
|
list_del_rcu(&nft_trans_rule(trans)->list);
|
||||||
break;
|
break;
|
||||||
|
@ -3269,6 +3379,9 @@ static int nf_tables_abort(struct sk_buff *skb)
|
||||||
|
|
||||||
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
|
||||||
switch (trans->msg_type) {
|
switch (trans->msg_type) {
|
||||||
|
case NFT_MSG_NEWCHAIN:
|
||||||
|
nf_tables_chain_destroy(trans->ctx.chain);
|
||||||
|
break;
|
||||||
case NFT_MSG_NEWRULE:
|
case NFT_MSG_NEWRULE:
|
||||||
nf_tables_rule_destroy(&trans->ctx,
|
nf_tables_rule_destroy(&trans->ctx,
|
||||||
nft_trans_rule(trans));
|
nft_trans_rule(trans));
|
||||||
|
|
Loading…
Reference in New Issue