diff options
author | Jiri Pirko <jiri@mellanox.com> | 2018-01-17 11:46:46 +0100 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2018-01-17 14:53:56 -0500 |
commit | 4861738775d70e0165d04fe014f32b41bcb5414a (patch) | |
tree | 496b129c4d9aee0385f924d1e20fc1b0ea854795 /net/sched | |
parent | a9b19443edbaac97c5c094f3cc903c1f1548b3f5 (diff) | |
download | linux-4861738775d70e0165d04fe014f32b41bcb5414a.tar.bz2 |
net: sched: introduce shared filter blocks infrastructure
Allow qdiscs to share filter blocks among them. Each qdisc type has to
use block get/put extended modifications that enable sharing.
Shared blocks are tracked within each net namespace and identified
by u32 index. This index is passed from user during the qdisc creation.
If user passes index that is not used by any other qdisc, new block
is created. If user passes index that is already used, the existing
block will be re-used.
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Acked-by: David Ahern <dsahern@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched')
-rw-r--r-- | net/sched/cls_api.c | 167 |
1 files changed, 143 insertions, 24 deletions
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index e6b16b300844..ee319b1598b5 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -24,6 +24,7 @@ #include <linux/init.h> #include <linux/kmod.h> #include <linux/slab.h> +#include <linux/idr.h> #include <net/net_namespace.h> #include <net/sock.h> #include <net/netlink.h> @@ -333,22 +334,44 @@ tcf_chain_head_change_cb_del(struct tcf_chain *chain, WARN_ON(1); } -static struct tcf_chain *tcf_block_chain_zero(struct tcf_block *block) +struct tcf_net { + struct idr idr; +}; + +static unsigned int tcf_net_id; + +static int tcf_block_insert(struct tcf_block *block, struct net *net, + u32 block_index, struct netlink_ext_ack *extack) { - return list_first_entry(&block->chain_list, struct tcf_chain, list); + struct tcf_net *tn = net_generic(net, tcf_net_id); + int err; + + err = idr_alloc_ext(&tn->idr, block, NULL, block_index, + block_index + 1, GFP_KERNEL); + if (err) + return err; + block->index = block_index; + return 0; } -int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q, - struct tcf_block_ext_info *ei, - struct netlink_ext_ack *extack) +static void tcf_block_remove(struct tcf_block *block, struct net *net) +{ + struct tcf_net *tn = net_generic(net, tcf_net_id); + + idr_remove_ext(&tn->idr, block->index); +} + +static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q, + struct netlink_ext_ack *extack) { - struct tcf_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + struct tcf_block *block; struct tcf_chain *chain; int err; + block = kzalloc(sizeof(*block), GFP_KERNEL); if (!block) { NL_SET_ERR_MSG(extack, "Memory allocation for block failed"); - return -ENOMEM; + return ERR_PTR(-ENOMEM); } INIT_LIST_HEAD(&block->chain_list); INIT_LIST_HEAD(&block->cb_list); @@ -360,20 +383,76 @@ int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q, err = -ENOMEM; goto err_chain_create; } + block->net = qdisc_net(q); + block->refcnt = 1; + block->net = net; + block->q = q; + return block; + +err_chain_create: + kfree(block); + return ERR_PTR(err); +} + +static struct tcf_block *tcf_block_lookup(struct net *net, u32 block_index) +{ + struct tcf_net *tn = net_generic(net, tcf_net_id); + + return idr_find_ext(&tn->idr, block_index); +} + +static struct tcf_chain *tcf_block_chain_zero(struct tcf_block *block) +{ + return list_first_entry(&block->chain_list, struct tcf_chain, list); +} + +int tcf_block_get_ext(struct tcf_block **p_block, struct Qdisc *q, + struct tcf_block_ext_info *ei, + struct netlink_ext_ack *extack) +{ + struct net *net = qdisc_net(q); + struct tcf_block *block = NULL; + bool created = false; + int err; + + if (ei->block_index) { + /* block_index not 0 means the shared block is requested */ + block = tcf_block_lookup(net, ei->block_index); + if (block) + block->refcnt++; + } + + if (!block) { + block = tcf_block_create(net, q, extack); + if (IS_ERR(block)) + return PTR_ERR(block); + created = true; + if (ei->block_index) { + err = tcf_block_insert(block, net, + ei->block_index, extack); + if (err) + goto err_block_insert; + } + } + err = tcf_chain_head_change_cb_add(tcf_block_chain_zero(block), ei, extack); if (err) goto err_chain_head_change_cb_add; - block->net = qdisc_net(q); - block->q = q; tcf_block_offload_bind(block, q, ei); *p_block = block; return 0; -err_chain_create: - kfree(block); err_chain_head_change_cb_add: - kfree(chain); + if (created) { + if (tcf_block_shared(block)) + tcf_block_remove(block, net); +err_block_insert: + kfree(tcf_block_chain_zero(block)); + kfree(block); + } else { + block->refcnt--; + } return err; } EXPORT_SYMBOL(tcf_block_get_ext); @@ -407,26 +486,34 @@ void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q, { struct tcf_chain *chain, *tmp; - /* Hold a refcnt for all chains, so that they don't disappear - * while we are iterating. - */ if (!block) return; tcf_chain_head_change_cb_del(tcf_block_chain_zero(block), ei); - list_for_each_entry(chain, &block->chain_list, list) - tcf_chain_hold(chain); - list_for_each_entry(chain, &block->chain_list, list) - tcf_chain_flush(chain); + if (--block->refcnt == 0) { + if (tcf_block_shared(block)) + tcf_block_remove(block, block->net); + + /* Hold a refcnt for all chains, so that they don't disappear + * while we are iterating. + */ + list_for_each_entry(chain, &block->chain_list, list) + tcf_chain_hold(chain); + + list_for_each_entry(chain, &block->chain_list, list) + tcf_chain_flush(chain); + } tcf_block_offload_unbind(block, q, ei); - /* At this point, all the chains should have refcnt >= 1. */ - list_for_each_entry_safe(chain, tmp, &block->chain_list, list) - tcf_chain_put(chain); + if (block->refcnt == 0) { + /* At this point, all the chains should have refcnt >= 1. */ + list_for_each_entry_safe(chain, tmp, &block->chain_list, list) + tcf_chain_put(chain); - /* Finally, put chain 0 and allow block to be freed. */ - tcf_chain_put(tcf_block_chain_zero(block)); + /* Finally, put chain 0 and allow block to be freed. */ + tcf_chain_put(tcf_block_chain_zero(block)); + } } EXPORT_SYMBOL(tcf_block_put_ext); @@ -1313,18 +1400,50 @@ int tc_setup_cb_call(struct tcf_block *block, struct tcf_exts *exts, } EXPORT_SYMBOL(tc_setup_cb_call); +static __net_init int tcf_net_init(struct net *net) +{ + struct tcf_net *tn = net_generic(net, tcf_net_id); + + idr_init(&tn->idr); + return 0; +} + +static void __net_exit tcf_net_exit(struct net *net) +{ + struct tcf_net *tn = net_generic(net, tcf_net_id); + + idr_destroy(&tn->idr); +} + +static struct pernet_operations tcf_net_ops = { + .init = tcf_net_init, + .exit = tcf_net_exit, + .id = &tcf_net_id, + .size = sizeof(struct tcf_net), +}; + static int __init tc_filter_init(void) { + int err; + tc_filter_wq = alloc_ordered_workqueue("tc_filter_workqueue", 0); if (!tc_filter_wq) return -ENOMEM; + err = register_pernet_subsys(&tcf_net_ops); + if (err) + goto err_register_pernet_subsys; + rtnl_register(PF_UNSPEC, RTM_NEWTFILTER, tc_ctl_tfilter, NULL, 0); rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_ctl_tfilter, NULL, 0); rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_ctl_tfilter, tc_dump_tfilter, 0); return 0; + +err_register_pernet_subsys: + destroy_workqueue(tc_filter_wq); + return err; } subsys_initcall(tc_filter_init); |