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

Pablo Neira Ayuso says:

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

1) Fix NAT support for NFPROTO_INET without layer 3 address,
   from Florian Westphal.

2) Use kfree_rcu(ptr, rcu) variant in nf_tables clean_net path.

3) Use list to collect flowtable hooks to be deleted.

4) Initialize list of hook field in flowtable transaction.

5) Release hooks on error for flowtable updates.

6) Memleak in hardware offload rule commit and abort paths.

7) Early bail out in case device does not support for hardware offload.
   This adds a new interface to net/core/flow_offload.c to check if the
   flow indirect block list is empty.

* git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf:
  netfilter: nf_tables: bail out early if hardware offload is not supported
  netfilter: nf_tables: memleak flow rule from commit path
  netfilter: nf_tables: release new hooks on unsupported flowtable flags
  netfilter: nf_tables: always initialize flowtable hook list in transaction
  netfilter: nf_tables: delete flowtable hooks via transaction list
  netfilter: nf_tables: use kfree_rcu(ptr, rcu) to release hooks in clean_net path
  netfilter: nat: really support inet nat without l3 address
====================

Link: https://lore.kernel.org/r/20220606212055.98300-1-pablo@netfilter.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2022-06-07 17:49:47 -07:00
commit 91ffb08932
8 changed files with 98 additions and 35 deletions

View file

@ -612,5 +612,6 @@ int flow_indr_dev_setup_offload(struct net_device *dev, struct Qdisc *sch,
enum tc_setup_type type, void *data,
struct flow_block_offload *bo,
void (*cleanup)(struct flow_block_cb *block_cb));
bool flow_indr_dev_exists(void);
#endif /* _NET_FLOW_OFFLOAD_H */

View file

@ -1090,7 +1090,6 @@ struct nft_stats {
struct nft_hook {
struct list_head list;
bool inactive;
struct nf_hook_ops ops;
struct rcu_head rcu;
};

View file

@ -92,7 +92,7 @@ int nft_flow_rule_offload_commit(struct net *net);
NFT_OFFLOAD_MATCH(__key, __base, __field, __len, __reg) \
memset(&(__reg)->mask, 0xff, (__reg)->len);
int nft_chain_offload_priority(struct nft_base_chain *basechain);
bool nft_chain_offload_support(const struct nft_base_chain *basechain);
int nft_offload_init(void);
void nft_offload_exit(void);

View file

@ -595,3 +595,9 @@ int flow_indr_dev_setup_offload(struct net_device *dev, struct Qdisc *sch,
return (bo && list_empty(&bo->cb_list)) ? -EOPNOTSUPP : count;
}
EXPORT_SYMBOL(flow_indr_dev_setup_offload);
bool flow_indr_dev_exists(void)
{
return !list_empty(&flow_block_indr_dev_list);
}
EXPORT_SYMBOL(flow_indr_dev_exists);

View file

@ -544,6 +544,7 @@ static int nft_trans_flowtable_add(struct nft_ctx *ctx, int msg_type,
if (msg_type == NFT_MSG_NEWFLOWTABLE)
nft_activate_next(ctx->net, flowtable);
INIT_LIST_HEAD(&nft_trans_flowtable_hooks(trans));
nft_trans_flowtable(trans) = flowtable;
nft_trans_commit_list_add_tail(ctx->net, trans);
@ -1914,7 +1915,6 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
goto err_hook_dev;
}
hook->ops.dev = dev;
hook->inactive = false;
return hook;
@ -2166,7 +2166,7 @@ static int nft_basechain_init(struct nft_base_chain *basechain, u8 family,
chain->flags |= NFT_CHAIN_BASE | flags;
basechain->policy = NF_ACCEPT;
if (chain->flags & NFT_CHAIN_HW_OFFLOAD &&
nft_chain_offload_priority(basechain) < 0)
!nft_chain_offload_support(basechain))
return -EOPNOTSUPP;
flow_block_init(&basechain->flow_block);
@ -7332,7 +7332,7 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
nf_unregister_net_hook(net, &hook->ops);
if (release_netdev) {
list_del(&hook->list);
kfree_rcu(hook);
kfree_rcu(hook, rcu);
}
}
}
@ -7433,11 +7433,15 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
if (nla[NFTA_FLOWTABLE_FLAGS]) {
flags = ntohl(nla_get_be32(nla[NFTA_FLOWTABLE_FLAGS]));
if (flags & ~NFT_FLOWTABLE_MASK)
return -EOPNOTSUPP;
if (flags & ~NFT_FLOWTABLE_MASK) {
err = -EOPNOTSUPP;
goto err_flowtable_update_hook;
}
if ((flowtable->data.flags & NFT_FLOWTABLE_HW_OFFLOAD) ^
(flags & NFT_FLOWTABLE_HW_OFFLOAD))
return -EOPNOTSUPP;
(flags & NFT_FLOWTABLE_HW_OFFLOAD)) {
err = -EOPNOTSUPP;
goto err_flowtable_update_hook;
}
} else {
flags = flowtable->data.flags;
}
@ -7618,6 +7622,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx,
{
const struct nlattr * const *nla = ctx->nla;
struct nft_flowtable_hook flowtable_hook;
LIST_HEAD(flowtable_del_list);
struct nft_hook *this, *hook;
struct nft_trans *trans;
int err;
@ -7633,7 +7638,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx,
err = -ENOENT;
goto err_flowtable_del_hook;
}
hook->inactive = true;
list_move(&hook->list, &flowtable_del_list);
}
trans = nft_trans_alloc(ctx, NFT_MSG_DELFLOWTABLE,
@ -7646,6 +7651,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx,
nft_trans_flowtable(trans) = flowtable;
nft_trans_flowtable_update(trans) = true;
INIT_LIST_HEAD(&nft_trans_flowtable_hooks(trans));
list_splice(&flowtable_del_list, &nft_trans_flowtable_hooks(trans));
nft_flowtable_hook_release(&flowtable_hook);
nft_trans_commit_list_add_tail(ctx->net, trans);
@ -7653,13 +7659,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx,
return 0;
err_flowtable_del_hook:
list_for_each_entry(this, &flowtable_hook.list, list) {
hook = nft_hook_list_find(&flowtable->hook_list, this);
if (!hook)
break;
hook->inactive = false;
}
list_splice(&flowtable_del_list, &flowtable->hook_list);
nft_flowtable_hook_release(&flowtable_hook);
return err;
@ -8329,6 +8329,9 @@ static void nft_commit_release(struct nft_trans *trans)
nf_tables_chain_destroy(&trans->ctx);
break;
case NFT_MSG_DELRULE:
if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD)
nft_flow_rule_destroy(nft_trans_flow_rule(trans));
nf_tables_rule_destroy(&trans->ctx, nft_trans_rule(trans));
break;
case NFT_MSG_DELSET:
@ -8563,17 +8566,6 @@ void nft_chain_del(struct nft_chain *chain)
list_del_rcu(&chain->list);
}
static void nft_flowtable_hooks_del(struct nft_flowtable *flowtable,
struct list_head *hook_list)
{
struct nft_hook *hook, *next;
list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
if (hook->inactive)
list_move(&hook->list, hook_list);
}
}
static void nf_tables_module_autoload_cleanup(struct net *net)
{
struct nftables_pernet *nft_net = nft_pernet(net);
@ -8828,6 +8820,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_NEWRULE);
if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD)
nft_flow_rule_destroy(nft_trans_flow_rule(trans));
nft_trans_destroy(trans);
break;
case NFT_MSG_DELRULE:
@ -8918,8 +8913,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
break;
case NFT_MSG_DELFLOWTABLE:
if (nft_trans_flowtable_update(trans)) {
nft_flowtable_hooks_del(nft_trans_flowtable(trans),
&nft_trans_flowtable_hooks(trans));
nf_tables_flowtable_notify(&trans->ctx,
nft_trans_flowtable(trans),
&nft_trans_flowtable_hooks(trans),
@ -9000,7 +8993,6 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
struct nftables_pernet *nft_net = nft_pernet(net);
struct nft_trans *trans, *next;
struct nft_trans_elem *te;
struct nft_hook *hook;
if (action == NFNL_ABORT_VALIDATE &&
nf_tables_validate(net) < 0)
@ -9131,8 +9123,8 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
break;
case NFT_MSG_DELFLOWTABLE:
if (nft_trans_flowtable_update(trans)) {
list_for_each_entry(hook, &nft_trans_flowtable(trans)->hook_list, list)
hook->inactive = false;
list_splice(&nft_trans_flowtable_hooks(trans),
&nft_trans_flowtable(trans)->hook_list);
} else {
trans->ctx.table->use++;
nft_clear(trans->ctx.net, nft_trans_flowtable(trans));

View file

@ -208,7 +208,7 @@ static int nft_setup_cb_call(enum tc_setup_type type, void *type_data,
return 0;
}
int nft_chain_offload_priority(struct nft_base_chain *basechain)
static int nft_chain_offload_priority(const struct nft_base_chain *basechain)
{
if (basechain->ops.priority <= 0 ||
basechain->ops.priority > USHRT_MAX)
@ -217,6 +217,27 @@ int nft_chain_offload_priority(struct nft_base_chain *basechain)
return 0;
}
bool nft_chain_offload_support(const struct nft_base_chain *basechain)
{
struct net_device *dev;
struct nft_hook *hook;
if (nft_chain_offload_priority(basechain) < 0)
return false;
list_for_each_entry(hook, &basechain->hook_list, list) {
if (hook->ops.pf != NFPROTO_NETDEV ||
hook->ops.hooknum != NF_NETDEV_INGRESS)
return false;
dev = hook->ops.dev;
if (!dev->netdev_ops->ndo_setup_tc && !flow_indr_dev_exists())
return false;
}
return true;
}
static void nft_flow_cls_offload_setup(struct flow_cls_offload *cls_flow,
const struct nft_base_chain *basechain,
const struct nft_rule *rule,

View file

@ -335,7 +335,8 @@ static void nft_nat_inet_eval(const struct nft_expr *expr,
{
const struct nft_nat *priv = nft_expr_priv(expr);
if (priv->family == nft_pf(pkt))
if (priv->family == nft_pf(pkt) ||
priv->family == NFPROTO_INET)
nft_nat_eval(expr, regs, pkt);
}

View file

@ -374,6 +374,45 @@ EOF
return $lret
}
test_local_dnat_portonly()
{
local family=$1
local daddr=$2
local lret=0
local sr_s
local sr_r
ip netns exec "$ns0" nft -f /dev/stdin <<EOF
table $family nat {
chain output {
type nat hook output priority 0; policy accept;
meta l4proto tcp dnat to :2000
}
}
EOF
if [ $? -ne 0 ]; then
if [ $family = "inet" ];then
echo "SKIP: inet port test"
test_inet_nat=false
return
fi
echo "SKIP: Could not add $family dnat hook"
return
fi
echo SERVER-$family | ip netns exec "$ns1" timeout 5 socat -u STDIN TCP-LISTEN:2000 &
sc_s=$!
result=$(ip netns exec "$ns0" timeout 1 socat TCP:$daddr:2000 STDOUT)
if [ "$result" = "SERVER-inet" ];then
echo "PASS: inet port rewrite without l3 address"
else
echo "ERROR: inet port rewrite"
ret=1
fi
}
test_masquerade6()
{
@ -1148,6 +1187,10 @@ fi
reset_counters
test_local_dnat ip
test_local_dnat6 ip6
reset_counters
test_local_dnat_portonly inet 10.0.1.99
reset_counters
$test_inet_nat && test_local_dnat inet
$test_inet_nat && test_local_dnat6 inet