Skip to content

Commit 1de178e

Browse files
idoschdavem330
authored andcommitted
ipv6: Flush multipath routes when all siblings are dead
By default, IPv6 deletes nexthops from a multipath route when the nexthop device is put administratively down. This differs from IPv4 where the nexthops are kept, but marked with the RTNH_F_DEAD flag. A multipath route is flushed when all of its nexthops become dead. Align IPv6 with IPv4 and have it conform to the same guidelines. In case the multipath route needs to be flushed, its siblings are flushed one by one. Otherwise, the nexthops are marked with the appropriate flags and the tree walker is instructed to skip all the siblings. As explained in previous patches, care is taken to update the sernum of the affected tree nodes, so as to prevent the use of wrong dst entries. Signed-off-by: Ido Schimmel <[email protected]> Acked-by: David Ahern <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 922c2ac commit 1de178e

File tree

1 file changed

+75
-8
lines changed

1 file changed

+75
-8
lines changed

net/ipv6/route.c

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3486,8 +3486,10 @@ static int fib6_ifup(struct rt6_info *rt, void *p_arg)
34863486
const struct arg_netdev_event *arg = p_arg;
34873487
const struct net *net = dev_net(arg->dev);
34883488

3489-
if (rt != net->ipv6.ip6_null_entry && rt->dst.dev == arg->dev)
3489+
if (rt != net->ipv6.ip6_null_entry && rt->dst.dev == arg->dev) {
34903490
rt->rt6i_nh_flags &= ~arg->nh_flags;
3491+
fib6_update_sernum_upto_root(dev_net(rt->dst.dev), rt);
3492+
}
34913493

34923494
return 0;
34933495
}
@@ -3505,27 +3507,92 @@ void rt6_sync_up(struct net_device *dev, unsigned int nh_flags)
35053507
fib6_clean_all(dev_net(dev), fib6_ifup, &arg);
35063508
}
35073509

3510+
static bool rt6_multipath_uses_dev(const struct rt6_info *rt,
3511+
const struct net_device *dev)
3512+
{
3513+
struct rt6_info *iter;
3514+
3515+
if (rt->dst.dev == dev)
3516+
return true;
3517+
list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
3518+
if (iter->dst.dev == dev)
3519+
return true;
3520+
3521+
return false;
3522+
}
3523+
3524+
static void rt6_multipath_flush(struct rt6_info *rt)
3525+
{
3526+
struct rt6_info *iter;
3527+
3528+
rt->should_flush = 1;
3529+
list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
3530+
iter->should_flush = 1;
3531+
}
3532+
3533+
static unsigned int rt6_multipath_dead_count(const struct rt6_info *rt,
3534+
const struct net_device *down_dev)
3535+
{
3536+
struct rt6_info *iter;
3537+
unsigned int dead = 0;
3538+
3539+
if (rt->dst.dev == down_dev || rt->rt6i_nh_flags & RTNH_F_DEAD)
3540+
dead++;
3541+
list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
3542+
if (iter->dst.dev == down_dev ||
3543+
iter->rt6i_nh_flags & RTNH_F_DEAD)
3544+
dead++;
3545+
3546+
return dead;
3547+
}
3548+
3549+
static void rt6_multipath_nh_flags_set(struct rt6_info *rt,
3550+
const struct net_device *dev,
3551+
unsigned int nh_flags)
3552+
{
3553+
struct rt6_info *iter;
3554+
3555+
if (rt->dst.dev == dev)
3556+
rt->rt6i_nh_flags |= nh_flags;
3557+
list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
3558+
if (iter->dst.dev == dev)
3559+
iter->rt6i_nh_flags |= nh_flags;
3560+
}
3561+
35083562
/* called with write lock held for table with rt */
35093563
static int fib6_ifdown(struct rt6_info *rt, void *p_arg)
35103564
{
35113565
const struct arg_netdev_event *arg = p_arg;
35123566
const struct net_device *dev = arg->dev;
35133567
const struct net *net = dev_net(dev);
35143568

3515-
if (rt->dst.dev != dev || rt == net->ipv6.ip6_null_entry)
3569+
if (rt == net->ipv6.ip6_null_entry)
35163570
return 0;
35173571

35183572
switch (arg->event) {
35193573
case NETDEV_UNREGISTER:
3520-
return -1;
3574+
return rt->dst.dev == dev ? -1 : 0;
35213575
case NETDEV_DOWN:
3522-
if (rt->rt6i_nsiblings == 0 ||
3523-
!rt->rt6i_idev->cnf.ignore_routes_with_linkdown)
3576+
if (rt->should_flush)
35243577
return -1;
3525-
rt->rt6i_nh_flags |= RTNH_F_DEAD;
3526-
/* fall through */
3578+
if (!rt->rt6i_nsiblings)
3579+
return rt->dst.dev == dev ? -1 : 0;
3580+
if (rt6_multipath_uses_dev(rt, dev)) {
3581+
unsigned int count;
3582+
3583+
count = rt6_multipath_dead_count(rt, dev);
3584+
if (rt->rt6i_nsiblings + 1 == count) {
3585+
rt6_multipath_flush(rt);
3586+
return -1;
3587+
}
3588+
rt6_multipath_nh_flags_set(rt, dev, RTNH_F_DEAD |
3589+
RTNH_F_LINKDOWN);
3590+
fib6_update_sernum(rt);
3591+
}
3592+
return -2;
35273593
case NETDEV_CHANGE:
3528-
if (rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST))
3594+
if (rt->dst.dev != dev ||
3595+
rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST))
35293596
break;
35303597
rt->rt6i_nh_flags |= RTNH_F_LINKDOWN;
35313598
break;

0 commit comments

Comments
 (0)