Skip to content

Commit

Permalink
Merge branch 'ipv6_ecmp_fixes'
Browse files Browse the repository at this point in the history
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: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed May 20, 2015
2 parents 892bd62 + 2759647 commit 7764b9d
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 7 deletions.
39 changes: 37 additions & 2 deletions net/ipv6/ip6_fib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 **ins;
struct rt6_info **fallback_ins = NULL;
int replace = (info->nlh &&
(info->nlh->nlmsg_flags & NLM_F_REPLACE));
int add = (!info->nlh ||
Expand All @@ -716,8 +717,13 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
(info->nlh->nlmsg_flags & NLM_F_EXCL))
return -EEXIST;
if (replace) {
found++;
break;
if (rt_can_ecmp == rt6_qualify_for_ecmp(iter)) {
found++;
break;
}
if (rt_can_ecmp)
fallback_ins = fallback_ins ?: ins;
goto next_iter;
}

if (iter->dst.dev == rt->dst.dev &&
Expand Down Expand Up @@ -753,9 +759,17 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
if (iter->rt6i_metric > rt->rt6i_metric)
break;

next_iter:
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 */
if (ins == &fn->leaf)
fn->rr_ptr = NULL;
Expand Down Expand Up @@ -815,6 +829,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
}

} else {
int nsiblings;

if (!found) {
if (add)
goto add;
Expand All @@ -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++;
fn->fn_flags |= RTN_RTINFO;
}
nsiblings = iter->rt6i_nsiblings;
fib6_purge_rt(iter, fn, info->nl_net);
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;
Expand Down
14 changes: 9 additions & 5 deletions net/ipv6/route.c
Original file line number Diff line number Diff line change
Expand Up @@ -2504,9 +2504,9 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add)
int attrlen;
int err = 0, last_err = 0;

remaining = cfg->fc_mp_len;
beginning:
rtnh = (struct rtnexthop *)cfg->fc_mp;
remaining = cfg->fc_mp_len;

/* Parse a Multipath Entry */
while (rtnh_ok(rtnh, remaining)) {
Expand Down Expand Up @@ -2536,15 +2536,19 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add)
* next hops that have been already added.
*/
add = 0;
remaining = cfg->fc_mp_len - remaining;
goto beginning;
}
}
/* Because each route is added like a single route we remove
* this flag after the first nexthop (if there is a collision,
* we have already fail to add the first nexthop:
* fib6_add_rt2node() has reject it).
* these flags after the first nexthop: if there is a collision,
* we have already failed to add the first nexthop:
* 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);
}

Expand Down

0 comments on commit 7764b9d

Please sign in to comment.