diff options
Diffstat (limited to 'net/core/fib_rules.c')
| -rw-r--r-- | net/core/fib_rules.c | 27 | 
1 files changed, 17 insertions, 10 deletions
| diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index f21c4d3aeae0..a0093e1b0235 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -46,7 +46,7 @@ int fib_default_rule_add(struct fib_rules_ops *ops,  	if (r == NULL)  		return -ENOMEM; -	atomic_set(&r->refcnt, 1); +	refcount_set(&r->refcnt, 1);  	r->action = FR_ACT_TO_TBL;  	r->pref = pref;  	r->table = table; @@ -283,7 +283,7 @@ jumped:  		if (err != -EAGAIN) {  			if ((arg->flags & FIB_LOOKUP_NOREF) || -			    likely(atomic_inc_not_zero(&rule->refcnt))) { +			    likely(refcount_inc_not_zero(&rule->refcnt))) {  				arg->rule = rule;  				goto out;  			} @@ -517,7 +517,7 @@ int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr *nlh,  		last = r;  	} -	fib_rule_get(rule); +	refcount_set(&rule->refcnt, 1);  	if (last)  		list_add_rcu(&rule->list, &last->list); @@ -568,7 +568,7 @@ int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr *nlh,  	struct net *net = sock_net(skb->sk);  	struct fib_rule_hdr *frh = nlmsg_data(nlh);  	struct fib_rules_ops *ops = NULL; -	struct fib_rule *rule, *tmp; +	struct fib_rule *rule, *r;  	struct nlattr *tb[FRA_MAX+1];  	struct fib_kuid_range range;  	int err = -EINVAL; @@ -668,16 +668,23 @@ int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr *nlh,  		/*  		 * Check if this rule is a target to any of them. If so, +		 * adjust to the next one with the same preference or  		 * disable them. As this operation is eventually very -		 * expensive, it is only performed if goto rules have -		 * actually been added. +		 * expensive, it is only performed if goto rules, except +		 * current if it is goto rule, have actually been added.  		 */  		if (ops->nr_goto_rules > 0) { -			list_for_each_entry(tmp, &ops->rules_list, list) { -				if (rtnl_dereference(tmp->ctarget) == rule) { -					RCU_INIT_POINTER(tmp->ctarget, NULL); +			struct fib_rule *n; + +			n = list_next_entry(rule, list); +			if (&n->list == &ops->rules_list || n->pref != rule->pref) +				n = NULL; +			list_for_each_entry(r, &ops->rules_list, list) { +				if (rtnl_dereference(r->ctarget) != rule) +					continue; +				rcu_assign_pointer(r->ctarget, n); +				if (!n)  					ops->unresolved_rules++; -				}  			}  		} |