Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+98 -35
+1
include/net/flow_offload.h
··· 612 612 enum tc_setup_type type, void *data, 613 613 struct flow_block_offload *bo, 614 614 void (*cleanup)(struct flow_block_cb *block_cb)); 615 + bool flow_indr_dev_exists(void); 615 616 616 617 #endif /* _NET_FLOW_OFFLOAD_H */
-1
include/net/netfilter/nf_tables.h
··· 1090 1090 1091 1091 struct nft_hook { 1092 1092 struct list_head list; 1093 - bool inactive; 1094 1093 struct nf_hook_ops ops; 1095 1094 struct rcu_head rcu; 1096 1095 };
+1 -1
include/net/netfilter/nf_tables_offload.h
··· 92 92 NFT_OFFLOAD_MATCH(__key, __base, __field, __len, __reg) \ 93 93 memset(&(__reg)->mask, 0xff, (__reg)->len); 94 94 95 - int nft_chain_offload_priority(struct nft_base_chain *basechain); 95 + bool nft_chain_offload_support(const struct nft_base_chain *basechain); 96 96 97 97 int nft_offload_init(void); 98 98 void nft_offload_exit(void);
+6
net/core/flow_offload.c
··· 595 595 return (bo && list_empty(&bo->cb_list)) ? -EOPNOTSUPP : count; 596 596 } 597 597 EXPORT_SYMBOL(flow_indr_dev_setup_offload); 598 + 599 + bool flow_indr_dev_exists(void) 600 + { 601 + return !list_empty(&flow_block_indr_dev_list); 602 + } 603 + EXPORT_SYMBOL(flow_indr_dev_exists);
+23 -31
net/netfilter/nf_tables_api.c
··· 544 544 if (msg_type == NFT_MSG_NEWFLOWTABLE) 545 545 nft_activate_next(ctx->net, flowtable); 546 546 547 + INIT_LIST_HEAD(&nft_trans_flowtable_hooks(trans)); 547 548 nft_trans_flowtable(trans) = flowtable; 548 549 nft_trans_commit_list_add_tail(ctx->net, trans); 549 550 ··· 1915 1914 goto err_hook_dev; 1916 1915 } 1917 1916 hook->ops.dev = dev; 1918 - hook->inactive = false; 1919 1917 1920 1918 return hook; 1921 1919 ··· 2166 2166 chain->flags |= NFT_CHAIN_BASE | flags; 2167 2167 basechain->policy = NF_ACCEPT; 2168 2168 if (chain->flags & NFT_CHAIN_HW_OFFLOAD && 2169 - nft_chain_offload_priority(basechain) < 0) 2169 + !nft_chain_offload_support(basechain)) 2170 2170 return -EOPNOTSUPP; 2171 2171 2172 2172 flow_block_init(&basechain->flow_block); ··· 7332 7332 nf_unregister_net_hook(net, &hook->ops); 7333 7333 if (release_netdev) { 7334 7334 list_del(&hook->list); 7335 - kfree_rcu(hook); 7335 + kfree_rcu(hook, rcu); 7336 7336 } 7337 7337 } 7338 7338 } ··· 7433 7433 7434 7434 if (nla[NFTA_FLOWTABLE_FLAGS]) { 7435 7435 flags = ntohl(nla_get_be32(nla[NFTA_FLOWTABLE_FLAGS])); 7436 - if (flags & ~NFT_FLOWTABLE_MASK) 7437 - return -EOPNOTSUPP; 7436 + if (flags & ~NFT_FLOWTABLE_MASK) { 7437 + err = -EOPNOTSUPP; 7438 + goto err_flowtable_update_hook; 7439 + } 7438 7440 if ((flowtable->data.flags & NFT_FLOWTABLE_HW_OFFLOAD) ^ 7439 - (flags & NFT_FLOWTABLE_HW_OFFLOAD)) 7440 - return -EOPNOTSUPP; 7441 + (flags & NFT_FLOWTABLE_HW_OFFLOAD)) { 7442 + err = -EOPNOTSUPP; 7443 + goto err_flowtable_update_hook; 7444 + } 7441 7445 } else { 7442 7446 flags = flowtable->data.flags; 7443 7447 } ··· 7622 7618 { 7623 7619 const struct nlattr * const *nla = ctx->nla; 7624 7620 struct nft_flowtable_hook flowtable_hook; 7621 + LIST_HEAD(flowtable_del_list); 7625 7622 struct nft_hook *this, *hook; 7626 7623 struct nft_trans *trans; 7627 7624 int err; ··· 7638 7633 err = -ENOENT; 7639 7634 goto err_flowtable_del_hook; 7640 7635 } 7641 - hook->inactive = true; 7636 + list_move(&hook->list, &flowtable_del_list); 7642 7637 } 7643 7638 7644 7639 trans = nft_trans_alloc(ctx, NFT_MSG_DELFLOWTABLE, ··· 7651 7646 nft_trans_flowtable(trans) = flowtable; 7652 7647 nft_trans_flowtable_update(trans) = true; 7653 7648 INIT_LIST_HEAD(&nft_trans_flowtable_hooks(trans)); 7649 + list_splice(&flowtable_del_list, &nft_trans_flowtable_hooks(trans)); 7654 7650 nft_flowtable_hook_release(&flowtable_hook); 7655 7651 7656 7652 nft_trans_commit_list_add_tail(ctx->net, trans); ··· 7659 7653 return 0; 7660 7654 7661 7655 err_flowtable_del_hook: 7662 - list_for_each_entry(this, &flowtable_hook.list, list) { 7663 - hook = nft_hook_list_find(&flowtable->hook_list, this); 7664 - if (!hook) 7665 - break; 7666 - 7667 - hook->inactive = false; 7668 - } 7656 + list_splice(&flowtable_del_list, &flowtable->hook_list); 7669 7657 nft_flowtable_hook_release(&flowtable_hook); 7670 7658 7671 7659 return err; ··· 8329 8329 nf_tables_chain_destroy(&trans->ctx); 8330 8330 break; 8331 8331 case NFT_MSG_DELRULE: 8332 + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) 8333 + nft_flow_rule_destroy(nft_trans_flow_rule(trans)); 8334 + 8332 8335 nf_tables_rule_destroy(&trans->ctx, nft_trans_rule(trans)); 8333 8336 break; 8334 8337 case NFT_MSG_DELSET: ··· 8564 8561 WARN_ON_ONCE(rhltable_remove(&table->chains_ht, &chain->rhlhead, 8565 8562 nft_chain_ht_params)); 8566 8563 list_del_rcu(&chain->list); 8567 - } 8568 - 8569 - static void nft_flowtable_hooks_del(struct nft_flowtable *flowtable, 8570 - struct list_head *hook_list) 8571 - { 8572 - struct nft_hook *hook, *next; 8573 - 8574 - list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) { 8575 - if (hook->inactive) 8576 - list_move(&hook->list, hook_list); 8577 - } 8578 8564 } 8579 8565 8580 8566 static void nf_tables_module_autoload_cleanup(struct net *net) ··· 8820 8828 nf_tables_rule_notify(&trans->ctx, 8821 8829 nft_trans_rule(trans), 8822 8830 NFT_MSG_NEWRULE); 8831 + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) 8832 + nft_flow_rule_destroy(nft_trans_flow_rule(trans)); 8833 + 8823 8834 nft_trans_destroy(trans); 8824 8835 break; 8825 8836 case NFT_MSG_DELRULE: ··· 8913 8918 break; 8914 8919 case NFT_MSG_DELFLOWTABLE: 8915 8920 if (nft_trans_flowtable_update(trans)) { 8916 - nft_flowtable_hooks_del(nft_trans_flowtable(trans), 8917 - &nft_trans_flowtable_hooks(trans)); 8918 8921 nf_tables_flowtable_notify(&trans->ctx, 8919 8922 nft_trans_flowtable(trans), 8920 8923 &nft_trans_flowtable_hooks(trans), ··· 8993 9000 struct nftables_pernet *nft_net = nft_pernet(net); 8994 9001 struct nft_trans *trans, *next; 8995 9002 struct nft_trans_elem *te; 8996 - struct nft_hook *hook; 8997 9003 8998 9004 if (action == NFNL_ABORT_VALIDATE && 8999 9005 nf_tables_validate(net) < 0) ··· 9123 9131 break; 9124 9132 case NFT_MSG_DELFLOWTABLE: 9125 9133 if (nft_trans_flowtable_update(trans)) { 9126 - list_for_each_entry(hook, &nft_trans_flowtable(trans)->hook_list, list) 9127 - hook->inactive = false; 9134 + list_splice(&nft_trans_flowtable_hooks(trans), 9135 + &nft_trans_flowtable(trans)->hook_list); 9128 9136 } else { 9129 9137 trans->ctx.table->use++; 9130 9138 nft_clear(trans->ctx.net, nft_trans_flowtable(trans));
+22 -1
net/netfilter/nf_tables_offload.c
··· 208 208 return 0; 209 209 } 210 210 211 - int nft_chain_offload_priority(struct nft_base_chain *basechain) 211 + static int nft_chain_offload_priority(const struct nft_base_chain *basechain) 212 212 { 213 213 if (basechain->ops.priority <= 0 || 214 214 basechain->ops.priority > USHRT_MAX) 215 215 return -1; 216 216 217 217 return 0; 218 + } 219 + 220 + bool nft_chain_offload_support(const struct nft_base_chain *basechain) 221 + { 222 + struct net_device *dev; 223 + struct nft_hook *hook; 224 + 225 + if (nft_chain_offload_priority(basechain) < 0) 226 + return false; 227 + 228 + list_for_each_entry(hook, &basechain->hook_list, list) { 229 + if (hook->ops.pf != NFPROTO_NETDEV || 230 + hook->ops.hooknum != NF_NETDEV_INGRESS) 231 + return false; 232 + 233 + dev = hook->ops.dev; 234 + if (!dev->netdev_ops->ndo_setup_tc && !flow_indr_dev_exists()) 235 + return false; 236 + } 237 + 238 + return true; 218 239 } 219 240 220 241 static void nft_flow_cls_offload_setup(struct flow_cls_offload *cls_flow,
+2 -1
net/netfilter/nft_nat.c
··· 335 335 { 336 336 const struct nft_nat *priv = nft_expr_priv(expr); 337 337 338 - if (priv->family == nft_pf(pkt)) 338 + if (priv->family == nft_pf(pkt) || 339 + priv->family == NFPROTO_INET) 339 340 nft_nat_eval(expr, regs, pkt); 340 341 } 341 342
+43
tools/testing/selftests/netfilter/nft_nat.sh
··· 374 374 return $lret 375 375 } 376 376 377 + test_local_dnat_portonly() 378 + { 379 + local family=$1 380 + local daddr=$2 381 + local lret=0 382 + local sr_s 383 + local sr_r 384 + 385 + ip netns exec "$ns0" nft -f /dev/stdin <<EOF 386 + table $family nat { 387 + chain output { 388 + type nat hook output priority 0; policy accept; 389 + meta l4proto tcp dnat to :2000 390 + 391 + } 392 + } 393 + EOF 394 + if [ $? -ne 0 ]; then 395 + if [ $family = "inet" ];then 396 + echo "SKIP: inet port test" 397 + test_inet_nat=false 398 + return 399 + fi 400 + echo "SKIP: Could not add $family dnat hook" 401 + return 402 + fi 403 + 404 + echo SERVER-$family | ip netns exec "$ns1" timeout 5 socat -u STDIN TCP-LISTEN:2000 & 405 + sc_s=$! 406 + 407 + result=$(ip netns exec "$ns0" timeout 1 socat TCP:$daddr:2000 STDOUT) 408 + 409 + if [ "$result" = "SERVER-inet" ];then 410 + echo "PASS: inet port rewrite without l3 address" 411 + else 412 + echo "ERROR: inet port rewrite" 413 + ret=1 414 + fi 415 + } 377 416 378 417 test_masquerade6() 379 418 { ··· 1187 1148 reset_counters 1188 1149 test_local_dnat ip 1189 1150 test_local_dnat6 ip6 1151 + 1152 + reset_counters 1153 + test_local_dnat_portonly inet 10.0.1.99 1154 + 1190 1155 reset_counters 1191 1156 $test_inet_nat && test_local_dnat inet 1192 1157 $test_inet_nat && test_local_dnat6 inet