Commit 48617387 authored by Jiri Pirko's avatar Jiri Pirko Committed by David S. Miller

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: default avatarJiri Pirko <jiri@mellanox.com>
Acked-by: default avatarJamal Hadi Salim <jhs@mojatatu.com>
Acked-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent a9b19443
......@@ -29,6 +29,7 @@ struct tcf_block_ext_info {
enum tcf_block_binder_type binder_type;
tcf_chain_head_change_t *chain_head_change;
void *chain_head_change_priv;
u32 block_index;
};
struct tcf_block_cb;
......@@ -48,8 +49,14 @@ void tcf_block_put(struct tcf_block *block);
void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q,
struct tcf_block_ext_info *ei);
static inline bool tcf_block_shared(struct tcf_block *block)
{
return block->index;
}
static inline struct Qdisc *tcf_block_q(struct tcf_block *block)
{
WARN_ON(tcf_block_shared(block));
return block->q;
}
......
......@@ -284,6 +284,8 @@ struct tcf_chain {
struct tcf_block {
struct list_head chain_list;
u32 index; /* block index for shared blocks */
unsigned int refcnt;
struct net *net;
struct Qdisc *q;
struct list_head cb_list;
......
......@@ -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,
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);
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);
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));
}
}
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);
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