Commit 7764b9dd authored by David S. Miller's avatar David S. Miller

Merge branch 'ipv6_ecmp_fixes'

Michal Kubecek says:

====================
IPv6 ECMP route add/replace fixes

(1) When adding a nexthop of a multipath route fails (e.g. because of a
conflict with an existing route), we are supposed to delete nexthops
already added. However, currently we try to also delete all nexthops we
haven't even tried to add yet so that a "ip route add" command can
actually remove pre-existing routes if it fails.

(2) Attempt to replace a multipath route results in a broken siblings
linked list. Following commands (like "ip route del") can then either
follow a link into freed memory or end in an infinite loop (if the slab
object has been reused).

v2: fix an omission in first patch

v3: change the semantics of replace operation to better match IPv4
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 892bd629 27596472
...@@ -693,6 +693,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -693,6 +693,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
{ {
struct rt6_info *iter = NULL; struct rt6_info *iter = NULL;
struct rt6_info **ins; struct rt6_info **ins;
struct rt6_info **fallback_ins = NULL;
int replace = (info->nlh && int replace = (info->nlh &&
(info->nlh->nlmsg_flags & NLM_F_REPLACE)); (info->nlh->nlmsg_flags & NLM_F_REPLACE));
int add = (!info->nlh || int add = (!info->nlh ||
...@@ -716,8 +717,13 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -716,8 +717,13 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
(info->nlh->nlmsg_flags & NLM_F_EXCL)) (info->nlh->nlmsg_flags & NLM_F_EXCL))
return -EEXIST; return -EEXIST;
if (replace) { if (replace) {
found++; if (rt_can_ecmp == rt6_qualify_for_ecmp(iter)) {
break; found++;
break;
}
if (rt_can_ecmp)
fallback_ins = fallback_ins ?: ins;
goto next_iter;
} }
if (iter->dst.dev == rt->dst.dev && if (iter->dst.dev == rt->dst.dev &&
...@@ -753,9 +759,17 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -753,9 +759,17 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
if (iter->rt6i_metric > rt->rt6i_metric) if (iter->rt6i_metric > rt->rt6i_metric)
break; break;
next_iter:
ins = &iter->dst.rt6_next; ins = &iter->dst.rt6_next;
} }
if (fallback_ins && !found) {
/* No ECMP-able route found, replace first non-ECMP one */
ins = fallback_ins;
iter = *ins;
found++;
}
/* Reset round-robin state, if necessary */ /* Reset round-robin state, if necessary */
if (ins == &fn->leaf) if (ins == &fn->leaf)
fn->rr_ptr = NULL; fn->rr_ptr = NULL;
...@@ -815,6 +829,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -815,6 +829,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
} }
} else { } else {
int nsiblings;
if (!found) { if (!found) {
if (add) if (add)
goto add; goto add;
...@@ -835,8 +851,27 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, ...@@ -835,8 +851,27 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
info->nl_net->ipv6.rt6_stats->fib_route_nodes++; info->nl_net->ipv6.rt6_stats->fib_route_nodes++;
fn->fn_flags |= RTN_RTINFO; fn->fn_flags |= RTN_RTINFO;
} }
nsiblings = iter->rt6i_nsiblings;
fib6_purge_rt(iter, fn, info->nl_net); fib6_purge_rt(iter, fn, info->nl_net);
rt6_release(iter); rt6_release(iter);
if (nsiblings) {
/* Replacing an ECMP route, remove all siblings */
ins = &rt->dst.rt6_next;
iter = *ins;
while (iter) {
if (rt6_qualify_for_ecmp(iter)) {
*ins = iter->dst.rt6_next;
fib6_purge_rt(iter, fn, info->nl_net);
rt6_release(iter);
nsiblings--;
} else {
ins = &iter->dst.rt6_next;
}
iter = *ins;
}
WARN_ON(nsiblings != 0);
}
} }
return 0; return 0;
......
...@@ -2504,9 +2504,9 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add) ...@@ -2504,9 +2504,9 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add)
int attrlen; int attrlen;
int err = 0, last_err = 0; int err = 0, last_err = 0;
remaining = cfg->fc_mp_len;
beginning: beginning:
rtnh = (struct rtnexthop *)cfg->fc_mp; rtnh = (struct rtnexthop *)cfg->fc_mp;
remaining = cfg->fc_mp_len;
/* Parse a Multipath Entry */ /* Parse a Multipath Entry */
while (rtnh_ok(rtnh, remaining)) { while (rtnh_ok(rtnh, remaining)) {
...@@ -2536,15 +2536,19 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add) ...@@ -2536,15 +2536,19 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add)
* next hops that have been already added. * next hops that have been already added.
*/ */
add = 0; add = 0;
remaining = cfg->fc_mp_len - remaining;
goto beginning; goto beginning;
} }
} }
/* Because each route is added like a single route we remove /* Because each route is added like a single route we remove
* this flag after the first nexthop (if there is a collision, * these flags after the first nexthop: if there is a collision,
* we have already fail to add the first nexthop: * we have already failed to add the first nexthop:
* fib6_add_rt2node() has reject it). * fib6_add_rt2node() has rejected it; when replacing, old
* nexthops have been replaced by first new, the rest should
* be added to it.
*/ */
cfg->fc_nlinfo.nlh->nlmsg_flags &= ~NLM_F_EXCL; cfg->fc_nlinfo.nlh->nlmsg_flags &= ~(NLM_F_EXCL |
NLM_F_REPLACE);
rtnh = rtnh_next(rtnh, &remaining); rtnh = rtnh_next(rtnh, &remaining);
} }
......
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