Commit f09bef61 authored by David S. Miller's avatar David S. Miller

Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf

Pablo Neira Ayuso says:

====================
Netfilter fixes for net

The following patchset contains Netfilter fixes for net:

1) Use CONFIG_NF_TABLES_INET from seltests, not NF_TABLES_INET.
   From Naresh Kamboju.

2) Add a test to cover masquerading and redirect case, from Florian
   Westphal.

3) Two packets coming from the same socket may race to set up NAT,
   ending up with different tuples and the packet losing race being
   dropped. Update nf_conntrack_tuple_taken() to exercise clash
   resolution for this case. From Martynas Pumputis and Florian
   Westphal.

4) Unbind anonymous sets from the commit and abort path, this fixes
   a splat due to double set list removal/release in case that the
   transaction needs to be aborted.

5) Do not preserve original output interface for packets that are
   redirected in the output chain when ip6_route_me_harder() is
   called. Otherwise packets end up going not going to the loopback
   device. From Eli Cooper.

6) Fix bogus splat in nft_compat with CONFIG_REFCOUNT_FULL=y, this
   also simplifies the existing logic to deal with the list insertions
   of the xtables extensions. From Florian Westphal.

Diffstat look rather larger than usual because of the new selftest, but
Florian and I consider that having tests soon into the tree is good to
improve coverage. If there's a different policy in this regard, please,
let me know.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents c8101f77 947e492c
......@@ -469,9 +469,7 @@ struct nft_set_binding {
int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding);
void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding);
void nf_tables_rebind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding);
struct nft_set_binding *binding, bool commit);
void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set);
/**
......@@ -721,6 +719,13 @@ struct nft_expr_type {
#define NFT_EXPR_STATEFUL 0x1
#define NFT_EXPR_GC 0x2
enum nft_trans_phase {
NFT_TRANS_PREPARE,
NFT_TRANS_ABORT,
NFT_TRANS_COMMIT,
NFT_TRANS_RELEASE
};
/**
* struct nft_expr_ops - nf_tables expression operations
*
......@@ -750,7 +755,8 @@ struct nft_expr_ops {
void (*activate)(const struct nft_ctx *ctx,
const struct nft_expr *expr);
void (*deactivate)(const struct nft_ctx *ctx,
const struct nft_expr *expr);
const struct nft_expr *expr,
enum nft_trans_phase phase);
void (*destroy)(const struct nft_ctx *ctx,
const struct nft_expr *expr);
void (*destroy_clone)(const struct nft_ctx *ctx,
......@@ -1323,12 +1329,15 @@ struct nft_trans_rule {
struct nft_trans_set {
struct nft_set *set;
u32 set_id;
bool bound;
};
#define nft_trans_set(trans) \
(((struct nft_trans_set *)trans->data)->set)
#define nft_trans_set_id(trans) \
(((struct nft_trans_set *)trans->data)->set_id)
#define nft_trans_set_bound(trans) \
(((struct nft_trans_set *)trans->data)->bound)
struct nft_trans_chain {
bool update;
......
......@@ -23,9 +23,11 @@ int ip6_route_me_harder(struct net *net, struct sk_buff *skb)
struct sock *sk = sk_to_full_sk(skb->sk);
unsigned int hh_len;
struct dst_entry *dst;
int strict = (ipv6_addr_type(&iph->daddr) &
(IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL));
struct flowi6 fl6 = {
.flowi6_oif = sk && sk->sk_bound_dev_if ? sk->sk_bound_dev_if :
rt6_need_strict(&iph->daddr) ? skb_dst(skb)->dev->ifindex : 0,
strict ? skb_dst(skb)->dev->ifindex : 0,
.flowi6_mark = skb->mark,
.flowi6_uid = sock_net_uid(net, sk),
.daddr = iph->daddr,
......
......@@ -1007,6 +1007,22 @@ nf_conntrack_tuple_taken(const struct nf_conntrack_tuple *tuple,
}
if (nf_ct_key_equal(h, tuple, zone, net)) {
/* Tuple is taken already, so caller will need to find
* a new source port to use.
*
* Only exception:
* If the *original tuples* are identical, then both
* conntracks refer to the same flow.
* This is a rare situation, it can occur e.g. when
* more than one UDP packet is sent from same socket
* in different threads.
*
* Let nf_ct_resolve_clash() deal with this later.
*/
if (nf_ct_tuple_equal(&ignored_conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple,
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple))
continue;
NF_CT_STAT_INC_ATOMIC(net, found);
rcu_read_unlock();
return 1;
......
......@@ -116,6 +116,23 @@ static void nft_trans_destroy(struct nft_trans *trans)
kfree(trans);
}
static void nft_set_trans_bind(const struct nft_ctx *ctx, struct nft_set *set)
{
struct net *net = ctx->net;
struct nft_trans *trans;
if (!nft_set_is_anonymous(set))
return;
list_for_each_entry_reverse(trans, &net->nft.commit_list, list) {
if (trans->msg_type == NFT_MSG_NEWSET &&
nft_trans_set(trans) == set) {
nft_trans_set_bound(trans) = true;
break;
}
}
}
static int nf_tables_register_hook(struct net *net,
const struct nft_table *table,
struct nft_chain *chain)
......@@ -211,18 +228,6 @@ static int nft_delchain(struct nft_ctx *ctx)
return err;
}
/* either expr ops provide both activate/deactivate, or neither */
static bool nft_expr_check_ops(const struct nft_expr_ops *ops)
{
if (!ops)
return true;
if (WARN_ON_ONCE((!ops->activate ^ !ops->deactivate)))
return false;
return true;
}
static void nft_rule_expr_activate(const struct nft_ctx *ctx,
struct nft_rule *rule)
{
......@@ -238,14 +243,15 @@ static void nft_rule_expr_activate(const struct nft_ctx *ctx,
}
static void nft_rule_expr_deactivate(const struct nft_ctx *ctx,
struct nft_rule *rule)
struct nft_rule *rule,
enum nft_trans_phase phase)
{
struct nft_expr *expr;
expr = nft_expr_first(rule);
while (expr != nft_expr_last(rule) && expr->ops) {
if (expr->ops->deactivate)
expr->ops->deactivate(ctx, expr);
expr->ops->deactivate(ctx, expr, phase);
expr = nft_expr_next(expr);
}
......@@ -296,7 +302,7 @@ static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
nft_trans_destroy(trans);
return err;
}
nft_rule_expr_deactivate(ctx, rule);
nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_PREPARE);
return 0;
}
......@@ -1929,9 +1935,6 @@ static int nf_tables_delchain(struct net *net, struct sock *nlsk,
*/
int nft_register_expr(struct nft_expr_type *type)
{
if (!nft_expr_check_ops(type->ops))
return -EINVAL;
nfnl_lock(NFNL_SUBSYS_NFTABLES);
if (type->family == NFPROTO_UNSPEC)
list_add_tail_rcu(&type->list, &nf_tables_expressions);
......@@ -2079,10 +2082,6 @@ static int nf_tables_expr_parse(const struct nft_ctx *ctx,
err = PTR_ERR(ops);
goto err1;
}
if (!nft_expr_check_ops(ops)) {
err = -EINVAL;
goto err1;
}
} else
ops = type->ops;
......@@ -2511,7 +2510,7 @@ static void nf_tables_rule_destroy(const struct nft_ctx *ctx,
static void nf_tables_rule_release(const struct nft_ctx *ctx,
struct nft_rule *rule)
{
nft_rule_expr_deactivate(ctx, rule);
nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_RELEASE);
nf_tables_rule_destroy(ctx, rule);
}
......@@ -3708,39 +3707,30 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
bind:
binding->chain = ctx->chain;
list_add_tail_rcu(&binding->list, &set->bindings);
nft_set_trans_bind(ctx, set);
return 0;
}
EXPORT_SYMBOL_GPL(nf_tables_bind_set);
void nf_tables_rebind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding)
{
if (list_empty(&set->bindings) && nft_set_is_anonymous(set) &&
nft_is_active(ctx->net, set))
list_add_tail_rcu(&set->list, &ctx->table->sets);
list_add_tail_rcu(&binding->list, &set->bindings);
}
EXPORT_SYMBOL_GPL(nf_tables_rebind_set);
void nf_tables_unbind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding)
struct nft_set_binding *binding, bool event)
{
list_del_rcu(&binding->list);
if (list_empty(&set->bindings) && nft_set_is_anonymous(set) &&
nft_is_active(ctx->net, set))
if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) {
list_del_rcu(&set->list);
if (event)
nf_tables_set_notify(ctx, set, NFT_MSG_DELSET,
GFP_KERNEL);
}
}
EXPORT_SYMBOL_GPL(nf_tables_unbind_set);
void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set)
{
if (list_empty(&set->bindings) && nft_set_is_anonymous(set) &&
nft_is_active(ctx->net, set)) {
nf_tables_set_notify(ctx, set, NFT_MSG_DELSET, GFP_ATOMIC);
if (list_empty(&set->bindings) && nft_set_is_anonymous(set))
nft_set_destroy(set);
}
}
EXPORT_SYMBOL_GPL(nf_tables_destroy_set);
......@@ -6535,6 +6525,9 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
nf_tables_rule_notify(&trans->ctx,
nft_trans_rule(trans),
NFT_MSG_DELRULE);
nft_rule_expr_deactivate(&trans->ctx,
nft_trans_rule(trans),
NFT_TRANS_COMMIT);
break;
case NFT_MSG_NEWSET:
nft_clear(net, nft_trans_set(trans));
......@@ -6621,6 +6614,7 @@ static void nf_tables_abort_release(struct nft_trans *trans)
nf_tables_rule_destroy(&trans->ctx, nft_trans_rule(trans));
break;
case NFT_MSG_NEWSET:
if (!nft_trans_set_bound(trans))
nft_set_destroy(nft_trans_set(trans));
break;
case NFT_MSG_NEWSETELEM:
......@@ -6682,7 +6676,9 @@ static int __nf_tables_abort(struct net *net)
case NFT_MSG_NEWRULE:
trans->ctx.chain->use--;
list_del_rcu(&nft_trans_rule(trans)->list);
nft_rule_expr_deactivate(&trans->ctx, nft_trans_rule(trans));
nft_rule_expr_deactivate(&trans->ctx,
nft_trans_rule(trans),
NFT_TRANS_ABORT);
break;
case NFT_MSG_DELRULE:
trans->ctx.chain->use++;
......@@ -6692,6 +6688,7 @@ static int __nf_tables_abort(struct net *net)
break;
case NFT_MSG_NEWSET:
trans->ctx.table->use--;
if (!nft_trans_set_bound(trans))
list_del_rcu(&nft_trans_set(trans)->list);
break;
case NFT_MSG_DELSET:
......
......@@ -61,6 +61,21 @@ static struct nft_compat_net *nft_compat_pernet(struct net *net)
return net_generic(net, nft_compat_net_id);
}
static void nft_xt_get(struct nft_xt *xt)
{
/* refcount_inc() warns on 0 -> 1 transition, but we can't
* init the reference count to 1 in .select_ops -- we can't
* undo such an increase when another expression inside the same
* rule fails afterwards.
*/
if (xt->listcnt == 0)
refcount_set(&xt->refcnt, 1);
else
refcount_inc(&xt->refcnt);
xt->listcnt++;
}
static bool nft_xt_put(struct nft_xt *xt)
{
if (refcount_dec_and_test(&xt->refcnt)) {
......@@ -291,7 +306,7 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
return -EINVAL;
nft_xt = container_of(expr->ops, struct nft_xt, ops);
refcount_inc(&nft_xt->refcnt);
nft_xt_get(nft_xt);
return 0;
}
......@@ -504,7 +519,7 @@ __nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
return ret;
nft_xt = container_of(expr->ops, struct nft_xt, ops);
refcount_inc(&nft_xt->refcnt);
nft_xt_get(nft_xt);
return 0;
}
......@@ -558,41 +573,16 @@ nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
__nft_match_destroy(ctx, expr, nft_expr_priv(expr));
}
static void nft_compat_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr,
struct list_head *h)
{
struct nft_xt *xt = container_of(expr->ops, struct nft_xt, ops);
if (xt->listcnt == 0)
list_add(&xt->head, h);
xt->listcnt++;
}
static void nft_compat_activate_mt(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_compat_net *cn = nft_compat_pernet(ctx->net);
nft_compat_activate(ctx, expr, &cn->nft_match_list);
}
static void nft_compat_activate_tg(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_compat_net *cn = nft_compat_pernet(ctx->net);
nft_compat_activate(ctx, expr, &cn->nft_target_list);
}
static void nft_compat_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_xt *xt = container_of(expr->ops, struct nft_xt, ops);
if (phase == NFT_TRANS_ABORT || phase == NFT_TRANS_COMMIT) {
if (--xt->listcnt == 0)
list_del_init(&xt->head);
}
}
static void
......@@ -848,7 +838,6 @@ nft_match_select_ops(const struct nft_ctx *ctx,
nft_match->ops.eval = nft_match_eval;
nft_match->ops.init = nft_match_init;
nft_match->ops.destroy = nft_match_destroy;
nft_match->ops.activate = nft_compat_activate_mt;
nft_match->ops.deactivate = nft_compat_deactivate;
nft_match->ops.dump = nft_match_dump;
nft_match->ops.validate = nft_match_validate;
......@@ -866,7 +855,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
nft_match->ops.size = matchsize;
nft_match->listcnt = 1;
nft_match->listcnt = 0;
list_add(&nft_match->head, &cn->nft_match_list);
return &nft_match->ops;
......@@ -953,7 +942,6 @@ nft_target_select_ops(const struct nft_ctx *ctx,
nft_target->ops.size = NFT_EXPR_SIZE(XT_ALIGN(target->targetsize));
nft_target->ops.init = nft_target_init;
nft_target->ops.destroy = nft_target_destroy;
nft_target->ops.activate = nft_compat_activate_tg;
nft_target->ops.deactivate = nft_compat_deactivate;
nft_target->ops.dump = nft_target_dump;
nft_target->ops.validate = nft_target_validate;
......@@ -964,7 +952,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
else
nft_target->ops.eval = nft_target_eval_xt;
nft_target->listcnt = 1;
nft_target->listcnt = 0;
list_add(&nft_target->head, &cn->nft_target_list);
return &nft_target->ops;
......
......@@ -235,20 +235,17 @@ static int nft_dynset_init(const struct nft_ctx *ctx,
return err;
}
static void nft_dynset_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_dynset *priv = nft_expr_priv(expr);
nf_tables_rebind_set(ctx, priv->set, &priv->binding);
}
static void nft_dynset_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_dynset *priv = nft_expr_priv(expr);
nf_tables_unbind_set(ctx, priv->set, &priv->binding);
if (phase == NFT_TRANS_PREPARE)
return;
nf_tables_unbind_set(ctx, priv->set, &priv->binding,
phase == NFT_TRANS_COMMIT);
}
static void nft_dynset_destroy(const struct nft_ctx *ctx,
......@@ -296,7 +293,6 @@ static const struct nft_expr_ops nft_dynset_ops = {
.eval = nft_dynset_eval,
.init = nft_dynset_init,
.destroy = nft_dynset_destroy,
.activate = nft_dynset_activate,
.deactivate = nft_dynset_deactivate,
.dump = nft_dynset_dump,
};
......
......@@ -72,10 +72,14 @@ static void nft_immediate_activate(const struct nft_ctx *ctx,
}
static void nft_immediate_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
if (phase == NFT_TRANS_COMMIT)
return;
return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg));
}
......
......@@ -121,20 +121,17 @@ static int nft_lookup_init(const struct nft_ctx *ctx,
return 0;
}
static void nft_lookup_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_lookup *priv = nft_expr_priv(expr);
nf_tables_rebind_set(ctx, priv->set, &priv->binding);
}
static void nft_lookup_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_lookup *priv = nft_expr_priv(expr);
nf_tables_unbind_set(ctx, priv->set, &priv->binding);
if (phase == NFT_TRANS_PREPARE)
return;
nf_tables_unbind_set(ctx, priv->set, &priv->binding,
phase == NFT_TRANS_COMMIT);
}
static void nft_lookup_destroy(const struct nft_ctx *ctx,
......@@ -225,7 +222,6 @@ static const struct nft_expr_ops nft_lookup_ops = {
.size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
.eval = nft_lookup_eval,
.init = nft_lookup_init,
.activate = nft_lookup_activate,
.deactivate = nft_lookup_deactivate,
.destroy = nft_lookup_destroy,
.dump = nft_lookup_dump,
......
......@@ -155,20 +155,17 @@ static int nft_objref_map_dump(struct sk_buff *skb, const struct nft_expr *expr)
return -1;
}
static void nft_objref_map_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
struct nft_objref_map *priv = nft_expr_priv(expr);
nf_tables_rebind_set(ctx, priv->set, &priv->binding);
}
static void nft_objref_map_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
struct nft_objref_map *priv = nft_expr_priv(expr);
nf_tables_unbind_set(ctx, priv->set, &priv->binding);
if (phase == NFT_TRANS_PREPARE)
return;
nf_tables_unbind_set(ctx, priv->set, &priv->binding,
phase == NFT_TRANS_COMMIT);
}
static void nft_objref_map_destroy(const struct nft_ctx *ctx,
......@@ -185,7 +182,6 @@ static const struct nft_expr_ops nft_objref_map_ops = {
.size = NFT_EXPR_SIZE(sizeof(struct nft_objref_map)),
.eval = nft_objref_map_eval,
.init = nft_objref_map_init,
.activate = nft_objref_map_activate,
.deactivate = nft_objref_map_deactivate,
.destroy = nft_objref_map_destroy,
.dump = nft_objref_map_dump,
......
# SPDX-License-Identifier: GPL-2.0
# Makefile for netfilter selftests
TEST_PROGS := nft_trans_stress.sh
TEST_PROGS := nft_trans_stress.sh nft_nat.sh
include ../lib.mk
CONFIG_NET_NS=y
NF_TABLES_INET=y
CONFIG_NF_TABLES_INET=y
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment