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

netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook

Supporting a 1:n relationship between nft_hook and nf_hook_ops is
convenient since a chain's or flowtable's nft_hooks may remain in place
despite matching interfaces disappearing. This stabilizes ruleset dumps
in that regard and opens the possibility to claim newly added interfaces
which match the spec. Also it prepares for wildcard interface specs
since these will potentially match multiple interfaces.

All spots dealing with hook registration are updated to handle a list of
multiple nf_hook_ops, but nft_netdev_hook_alloc() only adds a single
item for now to retain the old behaviour. The only expected functional
change here is how vanishing interfaces are handled: Instead of dropping
the respective nft_hook, only the matching nf_hook_ops are dropped.

To safely remove individual ops from the list in netdev handlers, an
rcu_head is added to struct nf_hook_ops so kfree_rcu() may be used.
There is at least nft_flowtable_find_dev() which may be iterating
through the list at the same time.

Signed-off-by: Phil Sutter <phil@nwl.cc>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Phil Sutter and committed by
Pablo Neira Ayuso
73319a8e 91a089d0

+136 -62
+3
include/linux/netfilter.h
··· 95 95 }; 96 96 97 97 struct nf_hook_ops { 98 + struct list_head list; 99 + struct rcu_head rcu; 100 + 98 101 /* User fills in from here down. */ 99 102 nf_hookfn *hook; 100 103 struct net_device *dev;
+1 -1
include/net/netfilter/nf_tables.h
··· 1199 1199 1200 1200 struct nft_hook { 1201 1201 struct list_head list; 1202 - struct nf_hook_ops ops; 1202 + struct list_head ops_list; 1203 1203 struct rcu_head rcu; 1204 1204 char ifname[IFNAMSIZ]; 1205 1205 u8 ifnamelen;
+103 -39
net/netfilter/nf_tables_api.c
··· 300 300 static int nft_netdev_register_hooks(struct net *net, 301 301 struct list_head *hook_list) 302 302 { 303 + struct nf_hook_ops *ops; 303 304 struct nft_hook *hook; 304 305 int err, j; 305 306 306 307 j = 0; 307 308 list_for_each_entry(hook, hook_list, list) { 308 - err = nf_register_net_hook(net, &hook->ops); 309 - if (err < 0) 310 - goto err_register; 309 + list_for_each_entry(ops, &hook->ops_list, list) { 310 + err = nf_register_net_hook(net, ops); 311 + if (err < 0) 312 + goto err_register; 311 313 312 - j++; 314 + j++; 315 + } 313 316 } 314 317 return 0; 315 318 316 319 err_register: 317 320 list_for_each_entry(hook, hook_list, list) { 318 - if (j-- <= 0) 319 - break; 321 + list_for_each_entry(ops, &hook->ops_list, list) { 322 + if (j-- <= 0) 323 + break; 320 324 321 - nf_unregister_net_hook(net, &hook->ops); 325 + nf_unregister_net_hook(net, ops); 326 + } 322 327 } 323 328 return err; 324 329 } 325 330 331 + static void nft_netdev_hook_free_ops(struct nft_hook *hook) 332 + { 333 + struct nf_hook_ops *ops, *next; 334 + 335 + list_for_each_entry_safe(ops, next, &hook->ops_list, list) { 336 + list_del(&ops->list); 337 + kfree(ops); 338 + } 339 + } 340 + 326 341 static void nft_netdev_hook_free(struct nft_hook *hook) 327 342 { 343 + nft_netdev_hook_free_ops(hook); 328 344 kfree(hook); 345 + } 346 + 347 + static void __nft_netdev_hook_free_rcu(struct rcu_head *rcu) 348 + { 349 + struct nft_hook *hook = container_of(rcu, struct nft_hook, rcu); 350 + 351 + nft_netdev_hook_free(hook); 329 352 } 330 353 331 354 static void nft_netdev_hook_free_rcu(struct nft_hook *hook) 332 355 { 333 - kfree_rcu(hook, rcu); 356 + call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu); 334 357 } 335 358 336 359 static void nft_netdev_unregister_hooks(struct net *net, ··· 361 338 bool release_netdev) 362 339 { 363 340 struct nft_hook *hook, *next; 341 + struct nf_hook_ops *ops; 364 342 365 343 list_for_each_entry_safe(hook, next, hook_list, list) { 366 - nf_unregister_net_hook(net, &hook->ops); 344 + list_for_each_entry(ops, &hook->ops_list, list) 345 + nf_unregister_net_hook(net, ops); 367 346 if (release_netdev) { 368 347 list_del(&hook->list); 369 348 nft_netdev_hook_free_rcu(hook); ··· 2309 2284 static struct nft_hook *nft_netdev_hook_alloc(struct net *net, 2310 2285 const struct nlattr *attr) 2311 2286 { 2287 + struct nf_hook_ops *ops; 2312 2288 struct net_device *dev; 2313 2289 struct nft_hook *hook; 2314 2290 int err; ··· 2319 2293 err = -ENOMEM; 2320 2294 goto err_hook_alloc; 2321 2295 } 2296 + INIT_LIST_HEAD(&hook->ops_list); 2322 2297 2323 2298 err = nla_strscpy(hook->ifname, attr, IFNAMSIZ); 2324 2299 if (err < 0) ··· 2336 2309 err = -ENOENT; 2337 2310 goto err_hook_dev; 2338 2311 } 2339 - hook->ops.dev = dev; 2312 + 2313 + ops = kzalloc(sizeof(struct nf_hook_ops), GFP_KERNEL_ACCOUNT); 2314 + if (!ops) { 2315 + err = -ENOMEM; 2316 + goto err_hook_dev; 2317 + } 2318 + ops->dev = dev; 2319 + list_add_tail(&ops->list, &hook->ops_list); 2340 2320 2341 2321 return hook; 2342 2322 ··· 2603 2569 struct nft_chain_hook *hook, u32 flags) 2604 2570 { 2605 2571 struct nft_chain *chain; 2572 + struct nf_hook_ops *ops; 2606 2573 struct nft_hook *h; 2607 2574 2608 2575 basechain->type = hook->type; ··· 2612 2577 2613 2578 if (nft_base_chain_netdev(family, hook->num)) { 2614 2579 list_splice_init(&hook->list, &basechain->hook_list); 2615 - list_for_each_entry(h, &basechain->hook_list, list) 2616 - nft_basechain_hook_init(&h->ops, family, hook, chain); 2580 + list_for_each_entry(h, &basechain->hook_list, list) { 2581 + list_for_each_entry(ops, &h->ops_list, list) 2582 + nft_basechain_hook_init(ops, family, hook, chain); 2583 + } 2617 2584 } 2618 2585 nft_basechain_hook_init(&basechain->ops, family, hook, chain); 2619 2586 ··· 2834 2797 2835 2798 if (nft_base_chain_netdev(ctx->family, basechain->ops.hooknum)) { 2836 2799 list_for_each_entry_safe(h, next, &hook.list, list) { 2837 - h->ops.pf = basechain->ops.pf; 2838 - h->ops.hooknum = basechain->ops.hooknum; 2839 - h->ops.priority = basechain->ops.priority; 2840 - h->ops.priv = basechain->ops.priv; 2841 - h->ops.hook = basechain->ops.hook; 2800 + list_for_each_entry(ops, &h->ops_list, list) { 2801 + ops->pf = basechain->ops.pf; 2802 + ops->hooknum = basechain->ops.hooknum; 2803 + ops->priority = basechain->ops.priority; 2804 + ops->priv = basechain->ops.priv; 2805 + ops->hook = basechain->ops.hook; 2806 + } 2842 2807 2843 2808 if (nft_hook_list_find(&basechain->hook_list, h)) { 2844 2809 list_del(&h->list); ··· 2962 2923 err_hooks: 2963 2924 if (nla[NFTA_CHAIN_HOOK]) { 2964 2925 list_for_each_entry_safe(h, next, &hook.list, list) { 2965 - if (unregister) 2966 - nf_unregister_net_hook(ctx->net, &h->ops); 2926 + if (unregister) { 2927 + list_for_each_entry(ops, &h->ops_list, list) 2928 + nf_unregister_net_hook(ctx->net, ops); 2929 + } 2967 2930 list_del(&h->list); 2968 2931 nft_netdev_hook_free_rcu(h); 2969 2932 } ··· 8836 8795 struct netlink_ext_ack *extack, bool add) 8837 8796 { 8838 8797 struct nlattr *tb[NFTA_FLOWTABLE_HOOK_MAX + 1]; 8798 + struct nf_hook_ops *ops; 8839 8799 struct nft_hook *hook; 8840 8800 int hooknum, priority; 8841 8801 int err; ··· 8891 8849 } 8892 8850 8893 8851 list_for_each_entry(hook, &flowtable_hook->list, list) { 8894 - hook->ops.pf = NFPROTO_NETDEV; 8895 - hook->ops.hooknum = flowtable_hook->num; 8896 - hook->ops.priority = flowtable_hook->priority; 8897 - hook->ops.priv = &flowtable->data; 8898 - hook->ops.hook = flowtable->data.type->hook; 8852 + list_for_each_entry(ops, &hook->ops_list, list) { 8853 + ops->pf = NFPROTO_NETDEV; 8854 + ops->hooknum = flowtable_hook->num; 8855 + ops->priority = flowtable_hook->priority; 8856 + ops->priv = &flowtable->data; 8857 + ops->hook = flowtable->data.type->hook; 8858 + } 8899 8859 } 8900 8860 8901 8861 return err; ··· 8954 8910 bool release_netdev) 8955 8911 { 8956 8912 struct nft_hook *hook, *next; 8913 + struct nf_hook_ops *ops; 8957 8914 8958 8915 list_for_each_entry_safe(hook, next, hook_list, list) { 8959 - nft_unregister_flowtable_ops(net, flowtable, &hook->ops); 8916 + list_for_each_entry(ops, &hook->ops_list, list) 8917 + nft_unregister_flowtable_ops(net, flowtable, ops); 8960 8918 if (release_netdev) { 8961 8919 list_del(&hook->list); 8962 8920 nft_netdev_hook_free_rcu(hook); ··· 9000 8954 { 9001 8955 struct nft_hook *hook, *next; 9002 8956 struct nft_flowtable *ft; 8957 + struct nf_hook_ops *ops; 9003 8958 int err, i = 0; 9004 8959 9005 8960 list_for_each_entry(hook, hook_list, list) { ··· 9014 8967 } 9015 8968 } 9016 8969 9017 - err = nft_register_flowtable_ops(net, flowtable, &hook->ops); 9018 - if (err < 0) 9019 - goto err_unregister_net_hooks; 8970 + list_for_each_entry(ops, &hook->ops_list, list) { 8971 + err = nft_register_flowtable_ops(net, flowtable, ops); 8972 + if (err < 0) 8973 + goto err_unregister_net_hooks; 9020 8974 9021 - i++; 8975 + i++; 8976 + } 9022 8977 } 9023 8978 9024 8979 return 0; 9025 8980 9026 8981 err_unregister_net_hooks: 9027 8982 list_for_each_entry_safe(hook, next, hook_list, list) { 9028 - if (i-- <= 0) 9029 - break; 8983 + list_for_each_entry(ops, &hook->ops_list, list) { 8984 + if (i-- <= 0) 8985 + break; 9030 8986 9031 - nft_unregister_flowtable_ops(net, flowtable, &hook->ops); 8987 + nft_unregister_flowtable_ops(net, flowtable, ops); 8988 + } 9032 8989 list_del_rcu(&hook->list); 9033 8990 nft_netdev_hook_free_rcu(hook); 9034 8991 } ··· 9057 9006 const struct nlattr * const *nla = ctx->nla; 9058 9007 struct nft_flowtable_hook flowtable_hook; 9059 9008 struct nft_hook *hook, *next; 9009 + struct nf_hook_ops *ops; 9060 9010 struct nft_trans *trans; 9061 9011 bool unregister = false; 9062 9012 u32 flags; ··· 9115 9063 9116 9064 err_flowtable_update_hook: 9117 9065 list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) { 9118 - if (unregister) 9119 - nft_unregister_flowtable_ops(ctx->net, flowtable, &hook->ops); 9066 + if (unregister) { 9067 + list_for_each_entry(ops, &hook->ops_list, list) 9068 + nft_unregister_flowtable_ops(ctx->net, 9069 + flowtable, ops); 9070 + } 9120 9071 list_del_rcu(&hook->list); 9121 9072 nft_netdev_hook_free_rcu(hook); 9122 9073 } ··· 9666 9611 struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook, 9667 9612 const struct net_device *dev) 9668 9613 { 9669 - if (hook->ops.dev == dev) 9670 - return (struct nf_hook_ops *)&hook->ops; 9614 + struct nf_hook_ops *ops; 9671 9615 9616 + list_for_each_entry(ops, &hook->ops_list, list) { 9617 + if (ops->dev == dev) 9618 + return ops; 9619 + } 9672 9620 return NULL; 9673 9621 } 9674 9622 EXPORT_SYMBOL_GPL(nft_hook_find_ops); ··· 9679 9621 struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook, 9680 9622 const struct net_device *dev) 9681 9623 { 9682 - return nft_hook_find_ops(hook, dev); 9624 + struct nf_hook_ops *ops; 9625 + 9626 + list_for_each_entry_rcu(ops, &hook->ops_list, list) { 9627 + if (ops->dev == dev) 9628 + return ops; 9629 + } 9630 + return NULL; 9683 9631 } 9684 9632 EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu); 9685 9633 ··· 9702 9638 9703 9639 /* flow_offload_netdev_event() cleans up entries for us. */ 9704 9640 nft_unregister_flowtable_ops(dev_net(dev), flowtable, ops); 9705 - list_del_rcu(&hook->list); 9706 - kfree_rcu(hook, rcu); 9641 + list_del_rcu(&ops->list); 9642 + kfree_rcu(ops, rcu); 9707 9643 break; 9708 9644 } 9709 9645 }
+27 -20
net/netfilter/nf_tables_offload.c
··· 220 220 221 221 bool nft_chain_offload_support(const struct nft_base_chain *basechain) 222 222 { 223 + struct nf_hook_ops *ops; 223 224 struct net_device *dev; 224 225 struct nft_hook *hook; 225 226 ··· 228 227 return false; 229 228 230 229 list_for_each_entry(hook, &basechain->hook_list, list) { 231 - if (hook->ops.pf != NFPROTO_NETDEV || 232 - hook->ops.hooknum != NF_NETDEV_INGRESS) 233 - return false; 230 + list_for_each_entry(ops, &hook->ops_list, list) { 231 + if (ops->pf != NFPROTO_NETDEV || 232 + ops->hooknum != NF_NETDEV_INGRESS) 233 + return false; 234 234 235 - dev = hook->ops.dev; 236 - if (!dev->netdev_ops->ndo_setup_tc && !flow_indr_dev_exists()) 237 - return false; 235 + dev = ops->dev; 236 + if (!dev->netdev_ops->ndo_setup_tc && 237 + !flow_indr_dev_exists()) 238 + return false; 239 + } 238 240 } 239 241 240 242 return true; ··· 459 455 const struct net_device *this_dev, 460 456 enum flow_block_command cmd) 461 457 { 462 - struct net_device *dev; 458 + struct nf_hook_ops *ops; 463 459 struct nft_hook *hook; 464 460 int err, i = 0; 465 461 466 462 list_for_each_entry(hook, &basechain->hook_list, list) { 467 - dev = hook->ops.dev; 468 - if (this_dev && this_dev != dev) 469 - continue; 463 + list_for_each_entry(ops, &hook->ops_list, list) { 464 + if (this_dev && this_dev != ops->dev) 465 + continue; 470 466 471 - err = nft_chain_offload_cmd(basechain, dev, cmd); 472 - if (err < 0 && cmd == FLOW_BLOCK_BIND) { 473 - if (!this_dev) 474 - goto err_flow_block; 467 + err = nft_chain_offload_cmd(basechain, ops->dev, cmd); 468 + if (err < 0 && cmd == FLOW_BLOCK_BIND) { 469 + if (!this_dev) 470 + goto err_flow_block; 475 471 476 - return err; 472 + return err; 473 + } 474 + i++; 477 475 } 478 - i++; 479 476 } 480 477 481 478 return 0; 482 479 483 480 err_flow_block: 484 481 list_for_each_entry(hook, &basechain->hook_list, list) { 485 - if (i-- <= 0) 486 - break; 482 + list_for_each_entry(ops, &hook->ops_list, list) { 483 + if (i-- <= 0) 484 + break; 487 485 488 - dev = hook->ops.dev; 489 - nft_chain_offload_cmd(basechain, dev, FLOW_BLOCK_UNBIND); 486 + nft_chain_offload_cmd(basechain, ops->dev, 487 + FLOW_BLOCK_UNBIND); 488 + } 490 489 } 491 490 return err; 492 491 }
+2 -2
net/netfilter/nft_chain_filter.c
··· 332 332 if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT)) 333 333 nf_unregister_net_hook(dev_net(dev), ops); 334 334 335 - list_del_rcu(&hook->list); 336 - kfree_rcu(hook, rcu); 335 + list_del_rcu(&ops->list); 336 + kfree_rcu(ops, rcu); 337 337 break; 338 338 } 339 339 }