Skip to content

Commit a5b72a0

Browse files
dcarattidavem330
authored andcommitted
net/sched: add delete_empty() to filters and use it in cls_flower
Revert "net/sched: cls_u32: fix refcount leak in the error path of u32_change()", and fix the u32 refcount leak in a more generic way that preserves the semantic of rule dumping. On tc filters that don't support lockless insertion/removal, there is no need to guard against concurrent insertion when a removal is in progress. Therefore, for most of them we can avoid a full walk() when deleting, and just decrease the refcount, like it was done on older Linux kernels. This fixes situations where walk() was wrongly detecting a non-empty filter, like it happened with cls_u32 in the error path of change(), thus leading to failures in the following tdc selftests: 6aa7: (filter, u32) Add/Replace u32 with source match and invalid indev 6658: (filter, u32) Add/Replace u32 with custom hash table and invalid handle 74c2: (filter, u32) Add/Replace u32 filter with invalid hash table id On cls_flower, and on (future) lockless filters, this check is necessary: move all the check_empty() logic in a callback so that each filter can have its own implementation. For cls_flower, it's sufficient to check if no IDRs have been allocated. This reverts commit 275c44a. Changes since v1: - document the need for delete_empty() when TCF_PROTO_OPS_DOIT_UNLOCKED is used, thanks to Vlad Buslov - implement delete_empty() without doing fl_walk(), thanks to Vlad Buslov - squash revert and new fix in a single patch, to be nice with bisect tests that run tdc on u32 filter, thanks to Dave Miller Fixes: 275c44a ("net/sched: cls_u32: fix refcount leak in the error path of u32_change()") Fixes: 6676d5e ("net: sched: set dedicated tcf_walker flag when tp is empty") Suggested-by: Jamal Hadi Salim <[email protected]> Suggested-by: Vlad Buslov <[email protected]> Signed-off-by: Davide Caratti <[email protected]> Reviewed-by: Vlad Buslov <[email protected]> Tested-by: Jamal Hadi Salim <[email protected]> Acked-by: Jamal Hadi Salim <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 8536975 commit a5b72a0

File tree

4 files changed

+22
-51
lines changed

4 files changed

+22
-51
lines changed

include/net/sch_generic.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ struct tcf_proto_ops {
308308
int (*delete)(struct tcf_proto *tp, void *arg,
309309
bool *last, bool rtnl_held,
310310
struct netlink_ext_ack *);
311+
bool (*delete_empty)(struct tcf_proto *tp);
311312
void (*walk)(struct tcf_proto *tp,
312313
struct tcf_walker *arg, bool rtnl_held);
313314
int (*reoffload)(struct tcf_proto *tp, bool add,
@@ -336,6 +337,10 @@ struct tcf_proto_ops {
336337
int flags;
337338
};
338339

340+
/* Classifiers setting TCF_PROTO_OPS_DOIT_UNLOCKED in tcf_proto_ops->flags
341+
* are expected to implement tcf_proto_ops->delete_empty(), otherwise race
342+
* conditions can occur when filters are inserted/deleted simultaneously.
343+
*/
339344
enum tcf_proto_ops_flags {
340345
TCF_PROTO_OPS_DOIT_UNLOCKED = 1,
341346
};

net/sched/cls_api.c

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -308,33 +308,12 @@ static void tcf_proto_put(struct tcf_proto *tp, bool rtnl_held,
308308
tcf_proto_destroy(tp, rtnl_held, true, extack);
309309
}
310310

311-
static int walker_check_empty(struct tcf_proto *tp, void *fh,
312-
struct tcf_walker *arg)
311+
static bool tcf_proto_check_delete(struct tcf_proto *tp)
313312
{
314-
if (fh) {
315-
arg->nonempty = true;
316-
return -1;
317-
}
318-
return 0;
319-
}
320-
321-
static bool tcf_proto_is_empty(struct tcf_proto *tp, bool rtnl_held)
322-
{
323-
struct tcf_walker walker = { .fn = walker_check_empty, };
324-
325-
if (tp->ops->walk) {
326-
tp->ops->walk(tp, &walker, rtnl_held);
327-
return !walker.nonempty;
328-
}
329-
return true;
330-
}
313+
if (tp->ops->delete_empty)
314+
return tp->ops->delete_empty(tp);
331315

332-
static bool tcf_proto_check_delete(struct tcf_proto *tp, bool rtnl_held)
333-
{
334-
spin_lock(&tp->lock);
335-
if (tcf_proto_is_empty(tp, rtnl_held))
336-
tp->deleting = true;
337-
spin_unlock(&tp->lock);
316+
tp->deleting = true;
338317
return tp->deleting;
339318
}
340319

@@ -1751,7 +1730,7 @@ static void tcf_chain_tp_delete_empty(struct tcf_chain *chain,
17511730
* concurrently.
17521731
* Mark tp for deletion if it is empty.
17531732
*/
1754-
if (!tp_iter || !tcf_proto_check_delete(tp, rtnl_held)) {
1733+
if (!tp_iter || !tcf_proto_check_delete(tp)) {
17551734
mutex_unlock(&chain->filter_chain_lock);
17561735
return;
17571736
}

net/sched/cls_flower.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,17 @@ static void fl_bind_class(void *fh, u32 classid, unsigned long cl)
27732773
f->res.class = cl;
27742774
}
27752775

2776+
static bool fl_delete_empty(struct tcf_proto *tp)
2777+
{
2778+
struct cls_fl_head *head = fl_head_dereference(tp);
2779+
2780+
spin_lock(&tp->lock);
2781+
tp->deleting = idr_is_empty(&head->handle_idr);
2782+
spin_unlock(&tp->lock);
2783+
2784+
return tp->deleting;
2785+
}
2786+
27762787
static struct tcf_proto_ops cls_fl_ops __read_mostly = {
27772788
.kind = "flower",
27782789
.classify = fl_classify,
@@ -2782,6 +2793,7 @@ static struct tcf_proto_ops cls_fl_ops __read_mostly = {
27822793
.put = fl_put,
27832794
.change = fl_change,
27842795
.delete = fl_delete,
2796+
.delete_empty = fl_delete_empty,
27852797
.walk = fl_walk,
27862798
.reoffload = fl_reoffload,
27872799
.hw_add = fl_hw_add,

net/sched/cls_u32.c

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,33 +1108,10 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
11081108
return err;
11091109
}
11101110

1111-
static bool u32_hnode_empty(struct tc_u_hnode *ht, bool *non_root_ht)
1112-
{
1113-
int i;
1114-
1115-
if (!ht)
1116-
return true;
1117-
if (!ht->is_root) {
1118-
*non_root_ht = true;
1119-
return false;
1120-
}
1121-
if (*non_root_ht)
1122-
return false;
1123-
if (ht->refcnt < 2)
1124-
return true;
1125-
1126-
for (i = 0; i <= ht->divisor; i++) {
1127-
if (rtnl_dereference(ht->ht[i]))
1128-
return false;
1129-
}
1130-
return true;
1131-
}
1132-
11331111
static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg,
11341112
bool rtnl_held)
11351113
{
11361114
struct tc_u_common *tp_c = tp->data;
1137-
bool non_root_ht = false;
11381115
struct tc_u_hnode *ht;
11391116
struct tc_u_knode *n;
11401117
unsigned int h;
@@ -1147,8 +1124,6 @@ static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg,
11471124
ht = rtnl_dereference(ht->next)) {
11481125
if (ht->prio != tp->prio)
11491126
continue;
1150-
if (u32_hnode_empty(ht, &non_root_ht))
1151-
return;
11521127
if (arg->count >= arg->skip) {
11531128
if (arg->fn(tp, ht, arg) < 0) {
11541129
arg->stop = 1;

0 commit comments

Comments
 (0)