Skip to content

Commit 2759647

Browse files
mkubecekdavem330
authored andcommitted
ipv6: fix ECMP route replacement
When replacing an IPv6 multipath route with "ip route replace", i.e. NLM_F_CREATE | NLM_F_REPLACE, fib6_add_rt2node() replaces only first matching route without fixing its siblings, resulting in corrupted siblings linked list; removing one of the siblings can then end in an infinite loop. IPv6 ECMP implementation is a bit different from IPv4 so that route replacement cannot work in exactly the same way. This should be a reasonable approximation: 1. If the new route is ECMP-able and there is a matching ECMP-able one already, replace it and all its siblings (if any). 2. If the new route is ECMP-able and no matching ECMP-able route exists, replace first matching non-ECMP-able (if any) or just add the new one. 3. If the new route is not ECMP-able, replace first matching non-ECMP-able route (if any) or add the new route. We also need to remove the NLM_F_REPLACE flag after replacing old route(s) by first nexthop of an ECMP route so that each subsequent nexthop does not replace previous one. Fixes: 51ebd31 ("ipv6: add support of equal cost multipath (ECMP)") Signed-off-by: Michal Kubecek <mkubecek@suse.cz> Acked-by: Nicolas Dichtel <nicolas.dichtel@6wind.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 35f1b4e commit 2759647

File tree

2 files changed

+44
-6
lines changed

2 files changed

+44
-6
lines changed

net/ipv6/ip6_fib.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
693693
{
694694
struct rt6_info *iter = NULL;
695695
struct rt6_info **ins;
696+
struct rt6_info **fallback_ins = NULL;
696697
int replace = (info->nlh &&
697698
(info->nlh->nlmsg_flags & NLM_F_REPLACE));
698699
int add = (!info->nlh ||
@@ -716,8 +717,13 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
716717
(info->nlh->nlmsg_flags & NLM_F_EXCL))
717718
return -EEXIST;
718719
if (replace) {
719-
found++;
720-
break;
720+
if (rt_can_ecmp == rt6_qualify_for_ecmp(iter)) {
721+
found++;
722+
break;
723+
}
724+
if (rt_can_ecmp)
725+
fallback_ins = fallback_ins ?: ins;
726+
goto next_iter;
721727
}
722728

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

762+
next_iter:
756763
ins = &iter->dst.rt6_next;
757764
}
758765

766+
if (fallback_ins && !found) {
767+
/* No ECMP-able route found, replace first non-ECMP one */
768+
ins = fallback_ins;
769+
iter = *ins;
770+
found++;
771+
}
772+
759773
/* Reset round-robin state, if necessary */
760774
if (ins == &fn->leaf)
761775
fn->rr_ptr = NULL;
@@ -815,6 +829,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
815829
}
816830

817831
} else {
832+
int nsiblings;
833+
818834
if (!found) {
819835
if (add)
820836
goto add;
@@ -835,8 +851,27 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
835851
info->nl_net->ipv6.rt6_stats->fib_route_nodes++;
836852
fn->fn_flags |= RTN_RTINFO;
837853
}
854+
nsiblings = iter->rt6i_nsiblings;
838855
fib6_purge_rt(iter, fn, info->nl_net);
839856
rt6_release(iter);
857+
858+
if (nsiblings) {
859+
/* Replacing an ECMP route, remove all siblings */
860+
ins = &rt->dst.rt6_next;
861+
iter = *ins;
862+
while (iter) {
863+
if (rt6_qualify_for_ecmp(iter)) {
864+
*ins = iter->dst.rt6_next;
865+
fib6_purge_rt(iter, fn, info->nl_net);
866+
rt6_release(iter);
867+
nsiblings--;
868+
} else {
869+
ins = &iter->dst.rt6_next;
870+
}
871+
iter = *ins;
872+
}
873+
WARN_ON(nsiblings != 0);
874+
}
840875
}
841876

842877
return 0;

net/ipv6/route.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,11 +2541,14 @@ static int ip6_route_multipath(struct fib6_config *cfg, int add)
25412541
}
25422542
}
25432543
/* Because each route is added like a single route we remove
2544-
* this flag after the first nexthop (if there is a collision,
2545-
* we have already fail to add the first nexthop:
2546-
* fib6_add_rt2node() has reject it).
2544+
* these flags after the first nexthop: if there is a collision,
2545+
* we have already failed to add the first nexthop:
2546+
* fib6_add_rt2node() has rejected it; when replacing, old
2547+
* nexthops have been replaced by first new, the rest should
2548+
* be added to it.
25472549
*/
2548-
cfg->fc_nlinfo.nlh->nlmsg_flags &= ~NLM_F_EXCL;
2550+
cfg->fc_nlinfo.nlh->nlmsg_flags &= ~(NLM_F_EXCL |
2551+
NLM_F_REPLACE);
25492552
rtnh = rtnh_next(rtnh, &remaining);
25502553
}
25512554

0 commit comments

Comments
 (0)