IPv4 and IPv6 react differently to a netdev being unregistered. In IPv4, in case the netdev is used as a nexthop device in a multipath route, the entire route is flushed.
However, IPv6 only removes the nexthops associated with the unregistered netdev. Align IPv4 and IPv6 and flush all the sibling routes when a nexthop device is unregistered. Signed-off-by: Ido Schimmel <ido...@mellanox.com> --- net/ipv6/route.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 4d0d32309bcc..cf895989f2bb 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3503,6 +3503,29 @@ void rt6_sync_up(struct net_device *dev, unsigned int nh_flags) fib6_clean_all(dev_net(dev), fib6_ifup, &arg); } +static bool rt6_multipath_uses_dev(const struct rt6_info *rt, + const struct net_device *dev) +{ + struct rt6_info *iter; + + if (rt->dst.dev == dev) + return true; + list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings) + if (iter->dst.dev == dev) + return true; + + return false; +} + +static void rt6_multipath_flush(struct rt6_info *rt) +{ + struct rt6_info *iter; + + rt->should_flush = 1; + list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings) + iter->should_flush = 1; +} + /* called with write lock held for table with rt */ static int fib6_ifdown(struct rt6_info *rt, void *p_arg) { @@ -3510,20 +3533,31 @@ static int fib6_ifdown(struct rt6_info *rt, void *p_arg) const struct net_device *dev = arg->dev; const struct net *net = dev_net(dev); - if (rt->dst.dev != dev || rt == net->ipv6.ip6_null_entry) + if (rt == net->ipv6.ip6_null_entry) return 0; switch (arg->event) { case NETDEV_UNREGISTER: - return -1; + if (rt->should_flush) + return -1; + if (!rt->rt6i_nsiblings) + return rt->dst.dev == dev ? -1 : 0; + if (rt6_multipath_uses_dev(rt, dev)) { + rt6_multipath_flush(rt); + return -1; + } + return -2; case NETDEV_DOWN: + if (rt->dst.dev != dev) + break; if (rt->rt6i_nsiblings == 0 || !rt->rt6i_idev->cnf.ignore_routes_with_linkdown) return -1; - rt->rt6i_nh_flags |= RTNH_F_DEAD; - /* fall through */ + rt->rt6i_nh_flags |= (RTNH_F_DEAD | RTNH_F_LINKDOWN); + break; case NETDEV_CHANGE: - if (rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST)) + if (rt->dst.dev != dev || + rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST)) break; rt->rt6i_nh_flags |= RTNH_F_LINKDOWN; break; -- 2.14.3