--- y/net/sched/cls_route.c +++ c/net/sched/cls_route.c @@ -43,10 +43,15 @@ struct route4_bucket { struct rcu_head rcu; }; +enum { + RT4_FILTER_FL_RCU_PENDING = 0, +}; + struct route4_filter { struct route4_filter __rcu *next; u32 id; int iif; + unsigned long flag; struct tcf_result res; struct tcf_exts exts; @@ -269,6 +274,8 @@ static void route4_delete_filter_work(st static void route4_queue_work(struct route4_filter *f) { + if (test_and_set_bit(RT4_FILTER_FL_RCU_PENDING, &f->flag)) + return; tcf_queue_work(&f->rwork, route4_delete_filter_work); } @@ -281,6 +288,9 @@ static void route4_destroy(struct tcf_pr if (head == NULL) return; + BUG_ON(rtnl_held && rtnl_trylock()); + if (!rtnl_held) + rtnl_lock(); for (h1 = 0; h1 <= 256; h1++) { struct route4_bucket *b; @@ -306,6 +316,8 @@ static void route4_destroy(struct tcf_pr } } kfree_rcu(head, rcu); + if (!rtnl_held) + rtnl_unlock(); } static int route4_delete(struct tcf_proto *tp, void *arg, bool *last, @@ -341,7 +353,7 @@ static int route4_delete(struct tcf_prot /* Delete it */ tcf_unbind_filter(tp, &f->res); tcf_exts_get_net(&f->exts); - tcf_queue_work(&f->rwork, route4_delete_filter_work); + route4_queue_work(f); /* Strip RTNL protected tree */ for (i = 0; i <= 32; i++) { @@ -476,6 +488,7 @@ static int route4_change(struct net *net unsigned int h, th; int err; bool new = true; + struct route4_filter *victim = NULL; if (opt == NULL) return handle ? -EINVAL : 0; @@ -514,6 +527,8 @@ static int route4_change(struct net *net if (err < 0) goto errout; + if (flags & TCA_ACT_FLAGS_NO_RTNL) + rtnl_lock(); h = from_hash(f->handle >> 16); fp = &f->bkt->ht[h]; for (pfp = rtnl_dereference(*fp); @@ -536,6 +551,7 @@ static int route4_change(struct net *net fp = &pfp->next, pfp = rtnl_dereference(*fp)) { if (pfp == fold) { rcu_assign_pointer(*fp, fold->next); + victim = fold; break; } } @@ -544,11 +560,14 @@ static int route4_change(struct net *net route4_reset_fastmap(head); *arg = f; - if (fold) { + if (victim) { + fold = victim; tcf_unbind_filter(tp, &fold->res); tcf_exts_get_net(&fold->exts); - tcf_queue_work(&fold->rwork, route4_delete_filter_work); + route4_queue_work(fold); } + if (flags & TCA_ACT_FLAGS_NO_RTNL) + rtnl_unlock(); return 0; errout: