Commit f66faae2 authored by David S. Miller's avatar David S. Miller

Merge branch 'ipv6-ipv4-nexthop-align'

Ido Schimmel says:

====================
ipv6: Align nexthop behaviour with IPv4

This set tries to eliminate some differences between IPv4's and IPv6's
treatment of nexthops. These differences are most likely a side effect
of IPv6's data structures (specifically 'rt6_info') that incorporate
both the route and the nexthop and the late addition of ECMP support in
commit 51ebd318 ("ipv6: add support of equal cost multipath
(ECMP)").

IPv4 and IPv6 do not react the same to certain netdev events. For
example, upon carrier change affected IPv4 nexthops are marked using the
RTNH_F_LINKDOWN flag and the nexthop group is rebalanced accordingly.
IPv6 on the other hand, does nothing which forces us to perform a
carrier check during route lookup and dump. This makes it difficult to
introduce features such as non-equal-cost multipath that are built on
top of this set [1].

In addition, when a netdev is put administratively down IPv4 nexthops
are marked using the RTNH_F_DEAD flag, whereas IPv6 simply flushes all
the routes using these nexthops. To be consistent with IPv4, multipath
routes should only be flushed when all nexthops in the group are
considered dead.

The first 12 patches introduce non-functional changes that store the
RTNH_F_DEAD and RTNH_F_LINKDOWN flags in IPv6 routes based on netdev
events, in a similar fashion to IPv4. This allows us to remove the
carrier check performed during route lookup and dump.

The next three patches make sure we only flush a multipath route when
all of its nexthops are dead.

Last three patches add test cases for IPv4/IPv6 FIB. These verify that
both address families react similarly to netdev events.

Finally, this series also serves as a good first step towards David
Ahern's goal of treating nexthops as standalone objects [2], as it makes
the code more in line with IPv4 where the nexthop and the nexthop group
are separate objects from the route itself.

1. https://github.com/idosch/linux/tree/ipv6-nexthops
2. http://vger.kernel.org/netconf2017_files/nexthop-objects.pdf

Changes since RFC (feedback from David Ahern):
* Remove redundant declaration of rt6_ifdown() in patch 4 and adjust
comment referencing it accordingly
* Drop patch to flush multipath routes upon NETDEV_UNREGISTER. Reword
cover letter accordingly
* Use a temporary variable to make code more readable in patch 15
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 7f0b8000 82e45b6f
...@@ -173,7 +173,8 @@ struct rt6_info { ...@@ -173,7 +173,8 @@ struct rt6_info {
unsigned short rt6i_nfheader_len; unsigned short rt6i_nfheader_len;
u8 rt6i_protocol; u8 rt6i_protocol;
u8 exception_bucket_flushed:1, u8 exception_bucket_flushed:1,
unused:7; should_flush:1,
unused:6;
}; };
#define for_each_fib6_node_rt_rcu(fn) \ #define for_each_fib6_node_rt_rcu(fn) \
...@@ -404,6 +405,7 @@ unsigned int fib6_tables_seq_read(struct net *net); ...@@ -404,6 +405,7 @@ unsigned int fib6_tables_seq_read(struct net *net);
int fib6_tables_dump(struct net *net, struct notifier_block *nb); int fib6_tables_dump(struct net *net, struct notifier_block *nb);
void fib6_update_sernum(struct rt6_info *rt); void fib6_update_sernum(struct rt6_info *rt);
void fib6_update_sernum_upto_root(struct net *net, struct rt6_info *rt);
#ifdef CONFIG_IPV6_MULTIPLE_TABLES #ifdef CONFIG_IPV6_MULTIPLE_TABLES
int fib6_rules_init(void); int fib6_rules_init(void);
......
...@@ -165,10 +165,12 @@ struct rt6_rtnl_dump_arg { ...@@ -165,10 +165,12 @@ struct rt6_rtnl_dump_arg {
}; };
int rt6_dump_route(struct rt6_info *rt, void *p_arg); int rt6_dump_route(struct rt6_info *rt, void *p_arg);
void rt6_ifdown(struct net *net, struct net_device *dev);
void rt6_mtu_change(struct net_device *dev, unsigned int mtu); void rt6_mtu_change(struct net_device *dev, unsigned int mtu);
void rt6_remove_prefsrc(struct inet6_ifaddr *ifp); void rt6_remove_prefsrc(struct inet6_ifaddr *ifp);
void rt6_clean_tohost(struct net *net, struct in6_addr *gateway); void rt6_clean_tohost(struct net *net, struct in6_addr *gateway);
void rt6_sync_up(struct net_device *dev, unsigned int nh_flags);
void rt6_disable_ip(struct net_device *dev, unsigned long event);
void rt6_sync_down_dev(struct net_device *dev, unsigned long event);
static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb) static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb)
{ {
......
...@@ -3438,6 +3438,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event, ...@@ -3438,6 +3438,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
} else if (event == NETDEV_CHANGE) { } else if (event == NETDEV_CHANGE) {
if (!addrconf_link_ready(dev)) { if (!addrconf_link_ready(dev)) {
/* device is still not ready. */ /* device is still not ready. */
rt6_sync_down_dev(dev, event);
break; break;
} }
...@@ -3449,6 +3450,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event, ...@@ -3449,6 +3450,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
* multicast snooping switches * multicast snooping switches
*/ */
ipv6_mc_up(idev); ipv6_mc_up(idev);
rt6_sync_up(dev, RTNH_F_LINKDOWN);
break; break;
} }
idev->if_flags |= IF_READY; idev->if_flags |= IF_READY;
...@@ -3484,6 +3486,9 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event, ...@@ -3484,6 +3486,9 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
if (run_pending) if (run_pending)
addrconf_dad_run(idev); addrconf_dad_run(idev);
/* Device has an address by now */
rt6_sync_up(dev, RTNH_F_DEAD);
/* /*
* If the MTU changed during the interface down, * If the MTU changed during the interface down,
* when the interface up, the changed MTU must be * when the interface up, the changed MTU must be
...@@ -3577,6 +3582,7 @@ static bool addr_is_local(const struct in6_addr *addr) ...@@ -3577,6 +3582,7 @@ static bool addr_is_local(const struct in6_addr *addr)
static int addrconf_ifdown(struct net_device *dev, int how) static int addrconf_ifdown(struct net_device *dev, int how)
{ {
unsigned long event = how ? NETDEV_UNREGISTER : NETDEV_DOWN;
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
struct inet6_dev *idev; struct inet6_dev *idev;
struct inet6_ifaddr *ifa, *tmp; struct inet6_ifaddr *ifa, *tmp;
...@@ -3586,8 +3592,7 @@ static int addrconf_ifdown(struct net_device *dev, int how) ...@@ -3586,8 +3592,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
ASSERT_RTNL(); ASSERT_RTNL();
rt6_ifdown(net, dev); rt6_disable_ip(dev, event);
neigh_ifdown(&nd_tbl, dev);
idev = __in6_dev_get(dev); idev = __in6_dev_get(dev);
if (!idev) if (!idev)
......
...@@ -107,16 +107,13 @@ enum { ...@@ -107,16 +107,13 @@ enum {
void fib6_update_sernum(struct rt6_info *rt) void fib6_update_sernum(struct rt6_info *rt)
{ {
struct fib6_table *table = rt->rt6i_table;
struct net *net = dev_net(rt->dst.dev); struct net *net = dev_net(rt->dst.dev);
struct fib6_node *fn; struct fib6_node *fn;
spin_lock_bh(&table->tb6_lock);
fn = rcu_dereference_protected(rt->rt6i_node, fn = rcu_dereference_protected(rt->rt6i_node,
lockdep_is_held(&table->tb6_lock)); lockdep_is_held(&rt->rt6i_table->tb6_lock));
if (fn) if (fn)
fn->fn_sernum = fib6_new_sernum(net); fn->fn_sernum = fib6_new_sernum(net);
spin_unlock_bh(&table->tb6_lock);
} }
/* /*
...@@ -1102,8 +1099,8 @@ void fib6_force_start_gc(struct net *net) ...@@ -1102,8 +1099,8 @@ void fib6_force_start_gc(struct net *net)
jiffies + net->ipv6.sysctl.ip6_rt_gc_interval); jiffies + net->ipv6.sysctl.ip6_rt_gc_interval);
} }
static void fib6_update_sernum_upto_root(struct rt6_info *rt, static void __fib6_update_sernum_upto_root(struct rt6_info *rt,
int sernum) int sernum)
{ {
struct fib6_node *fn = rcu_dereference_protected(rt->rt6i_node, struct fib6_node *fn = rcu_dereference_protected(rt->rt6i_node,
lockdep_is_held(&rt->rt6i_table->tb6_lock)); lockdep_is_held(&rt->rt6i_table->tb6_lock));
...@@ -1117,6 +1114,11 @@ static void fib6_update_sernum_upto_root(struct rt6_info *rt, ...@@ -1117,6 +1114,11 @@ static void fib6_update_sernum_upto_root(struct rt6_info *rt,
} }
} }
void fib6_update_sernum_upto_root(struct net *net, struct rt6_info *rt)
{
__fib6_update_sernum_upto_root(rt, fib6_new_sernum(net));
}
/* /*
* Add routing information to the routing tree. * Add routing information to the routing tree.
* <destination addr>/<source addr> * <destination addr>/<source addr>
...@@ -1230,7 +1232,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, ...@@ -1230,7 +1232,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
err = fib6_add_rt2node(fn, rt, info, mxc, extack); err = fib6_add_rt2node(fn, rt, info, mxc, extack);
if (!err) { if (!err) {
fib6_update_sernum_upto_root(rt, sernum); __fib6_update_sernum_upto_root(rt, sernum);
fib6_start_gc(info->nl_net, rt); fib6_start_gc(info->nl_net, rt);
} }
...@@ -1887,7 +1889,7 @@ static int fib6_clean_node(struct fib6_walker *w) ...@@ -1887,7 +1889,7 @@ static int fib6_clean_node(struct fib6_walker *w)
for_each_fib6_walker_rt(w) { for_each_fib6_walker_rt(w) {
res = c->func(rt, c->arg); res = c->func(rt, c->arg);
if (res < 0) { if (res == -1) {
w->leaf = rt; w->leaf = rt;
res = fib6_del(rt, &info); res = fib6_del(rt, &info);
if (res) { if (res) {
...@@ -1900,6 +1902,12 @@ static int fib6_clean_node(struct fib6_walker *w) ...@@ -1900,6 +1902,12 @@ static int fib6_clean_node(struct fib6_walker *w)
continue; continue;
} }
return 0; return 0;
} else if (res == -2) {
if (WARN_ON(!rt->rt6i_nsiblings))
continue;
rt = list_last_entry(&rt->rt6i_siblings,
struct rt6_info, rt6i_siblings);
continue;
} }
WARN_ON(res != 0); WARN_ON(res != 0);
} }
...@@ -1911,7 +1919,8 @@ static int fib6_clean_node(struct fib6_walker *w) ...@@ -1911,7 +1919,8 @@ static int fib6_clean_node(struct fib6_walker *w)
* Convenient frontend to tree walker. * Convenient frontend to tree walker.
* *
* func is called on each route. * func is called on each route.
* It may return -1 -> delete this route. * It may return -2 -> skip multipath route.
* -1 -> delete this route.
* 0 -> continue walking * 0 -> continue walking
*/ */
...@@ -2103,7 +2112,6 @@ static void fib6_net_exit(struct net *net) ...@@ -2103,7 +2112,6 @@ static void fib6_net_exit(struct net *net)
{ {
unsigned int i; unsigned int i;
rt6_ifdown(net, NULL);
del_timer_sync(&net->ipv6.ip6_fib_timer); del_timer_sync(&net->ipv6.ip6_fib_timer);
for (i = 0; i < FIB6_TABLE_HASHSZ; i++) { for (i = 0; i < FIB6_TABLE_HASHSZ; i++) {
......
...@@ -474,7 +474,9 @@ static struct rt6_info *rt6_multipath_select(struct rt6_info *match, ...@@ -474,7 +474,9 @@ static struct rt6_info *rt6_multipath_select(struct rt6_info *match,
if (route_choosen == 0) { if (route_choosen == 0) {
struct inet6_dev *idev = sibling->rt6i_idev; struct inet6_dev *idev = sibling->rt6i_idev;
if (!netif_carrier_ok(sibling->dst.dev) && if (sibling->rt6i_nh_flags & RTNH_F_DEAD)
break;
if (sibling->rt6i_nh_flags & RTNH_F_LINKDOWN &&
idev->cnf.ignore_routes_with_linkdown) idev->cnf.ignore_routes_with_linkdown)
break; break;
if (rt6_score_route(sibling, oif, strict) < 0) if (rt6_score_route(sibling, oif, strict) < 0)
...@@ -499,12 +501,15 @@ static inline struct rt6_info *rt6_device_match(struct net *net, ...@@ -499,12 +501,15 @@ static inline struct rt6_info *rt6_device_match(struct net *net,
struct rt6_info *local = NULL; struct rt6_info *local = NULL;
struct rt6_info *sprt; struct rt6_info *sprt;
if (!oif && ipv6_addr_any(saddr)) if (!oif && ipv6_addr_any(saddr) && !(rt->rt6i_nh_flags & RTNH_F_DEAD))
goto out; return rt;
for (sprt = rt; sprt; sprt = rcu_dereference(sprt->rt6_next)) { for (sprt = rt; sprt; sprt = rcu_dereference(sprt->rt6_next)) {
struct net_device *dev = sprt->dst.dev; struct net_device *dev = sprt->dst.dev;
if (sprt->rt6i_nh_flags & RTNH_F_DEAD)
continue;
if (oif) { if (oif) {
if (dev->ifindex == oif) if (dev->ifindex == oif)
return sprt; return sprt;
...@@ -533,8 +538,8 @@ static inline struct rt6_info *rt6_device_match(struct net *net, ...@@ -533,8 +538,8 @@ static inline struct rt6_info *rt6_device_match(struct net *net,
if (flags & RT6_LOOKUP_F_IFACE) if (flags & RT6_LOOKUP_F_IFACE)
return net->ipv6.ip6_null_entry; return net->ipv6.ip6_null_entry;
} }
out:
return rt; return rt->rt6i_nh_flags & RTNH_F_DEAD ? net->ipv6.ip6_null_entry : rt;
} }
#ifdef CONFIG_IPV6_ROUTER_PREF #ifdef CONFIG_IPV6_ROUTER_PREF
...@@ -679,10 +684,12 @@ static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict, ...@@ -679,10 +684,12 @@ static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict,
int m; int m;
bool match_do_rr = false; bool match_do_rr = false;
struct inet6_dev *idev = rt->rt6i_idev; struct inet6_dev *idev = rt->rt6i_idev;
struct net_device *dev = rt->dst.dev;
if (dev && !netif_carrier_ok(dev) && if (rt->rt6i_nh_flags & RTNH_F_DEAD)
idev->cnf.ignore_routes_with_linkdown && goto out;
if (idev->cnf.ignore_routes_with_linkdown &&
rt->rt6i_nh_flags & RTNH_F_LINKDOWN &&
!(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE)) !(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE))
goto out; goto out;
...@@ -1346,7 +1353,9 @@ static int rt6_insert_exception(struct rt6_info *nrt, ...@@ -1346,7 +1353,9 @@ static int rt6_insert_exception(struct rt6_info *nrt,
/* Update fn->fn_sernum to invalidate all cached dst */ /* Update fn->fn_sernum to invalidate all cached dst */
if (!err) { if (!err) {
spin_lock_bh(&ort->rt6i_table->tb6_lock);
fib6_update_sernum(ort); fib6_update_sernum(ort);
spin_unlock_bh(&ort->rt6i_table->tb6_lock);
fib6_force_start_gc(net); fib6_force_start_gc(net);
} }
...@@ -2154,6 +2163,8 @@ static struct rt6_info *__ip6_route_redirect(struct net *net, ...@@ -2154,6 +2163,8 @@ static struct rt6_info *__ip6_route_redirect(struct net *net,
fn = fib6_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr); fn = fib6_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr);
restart: restart:
for_each_fib6_node_rt_rcu(fn) { for_each_fib6_node_rt_rcu(fn) {
if (rt->rt6i_nh_flags & RTNH_F_DEAD)
continue;
if (rt6_check_expired(rt)) if (rt6_check_expired(rt))
continue; continue;
if (rt->dst.error) if (rt->dst.error)
...@@ -2344,7 +2355,7 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev, ...@@ -2344,7 +2355,7 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
rt->rt6i_idev = idev; rt->rt6i_idev = idev;
dst_metric_set(&rt->dst, RTAX_HOPLIMIT, 0); dst_metric_set(&rt->dst, RTAX_HOPLIMIT, 0);
/* Add this dst into uncached_list so that rt6_ifdown() can /* Add this dst into uncached_list so that rt6_disable_ip() can
* do proper release of the net_device * do proper release of the net_device
*/ */
rt6_uncached_list_add(rt); rt6_uncached_list_add(rt);
...@@ -2746,6 +2757,9 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg, ...@@ -2746,6 +2757,9 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
rt->rt6i_flags = cfg->fc_flags; rt->rt6i_flags = cfg->fc_flags;
install_route: install_route:
if (!(rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST)) &&
!netif_carrier_ok(dev))
rt->rt6i_nh_flags |= RTNH_F_LINKDOWN;
rt->dst.dev = dev; rt->dst.dev = dev;
rt->rt6i_idev = idev; rt->rt6i_idev = idev;
rt->rt6i_table = table; rt->rt6i_table = table;
...@@ -3459,37 +3473,149 @@ void rt6_clean_tohost(struct net *net, struct in6_addr *gateway) ...@@ -3459,37 +3473,149 @@ void rt6_clean_tohost(struct net *net, struct in6_addr *gateway)
fib6_clean_all(net, fib6_clean_tohost, gateway); fib6_clean_all(net, fib6_clean_tohost, gateway);
} }
struct arg_dev_net { struct arg_netdev_event {
struct net_device *dev; const struct net_device *dev;
struct net *net; union {
unsigned int nh_flags;
unsigned long event;
};
}; };
static int fib6_ifup(struct rt6_info *rt, void *p_arg)
{
const struct arg_netdev_event *arg = p_arg;
const struct net *net = dev_net(arg->dev);
if (rt != net->ipv6.ip6_null_entry && rt->dst.dev == arg->dev) {
rt->rt6i_nh_flags &= ~arg->nh_flags;
fib6_update_sernum_upto_root(dev_net(rt->dst.dev), rt);
}
return 0;
}
void rt6_sync_up(struct net_device *dev, unsigned int nh_flags)
{
struct arg_netdev_event arg = {
.dev = dev,
.nh_flags = nh_flags,
};
if (nh_flags & RTNH_F_DEAD && netif_carrier_ok(dev))
arg.nh_flags |= RTNH_F_LINKDOWN;
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;
}
static unsigned int rt6_multipath_dead_count(const struct rt6_info *rt,
const struct net_device *down_dev)
{
struct rt6_info *iter;
unsigned int dead = 0;
if (rt->dst.dev == down_dev || rt->rt6i_nh_flags & RTNH_F_DEAD)
dead++;
list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
if (iter->dst.dev == down_dev ||
iter->rt6i_nh_flags & RTNH_F_DEAD)
dead++;
return dead;
}
static void rt6_multipath_nh_flags_set(struct rt6_info *rt,
const struct net_device *dev,
unsigned int nh_flags)
{
struct rt6_info *iter;
if (rt->dst.dev == dev)
rt->rt6i_nh_flags |= nh_flags;
list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
if (iter->dst.dev == dev)
iter->rt6i_nh_flags |= nh_flags;
}
/* called with write lock held for table with rt */ /* called with write lock held for table with rt */
static int fib6_ifdown(struct rt6_info *rt, void *arg) static int fib6_ifdown(struct rt6_info *rt, void *p_arg)
{ {
const struct arg_dev_net *adn = arg; const struct arg_netdev_event *arg = p_arg;
const struct net_device *dev = adn->dev; const struct net_device *dev = arg->dev;
const struct net *net = dev_net(dev);
if ((rt->dst.dev == dev || !dev) && if (rt == net->ipv6.ip6_null_entry)
rt != adn->net->ipv6.ip6_null_entry && return 0;
(rt->rt6i_nsiblings == 0 ||
(dev && netdev_unregistering(dev)) || switch (arg->event) {
!rt->rt6i_idev->cnf.ignore_routes_with_linkdown)) case NETDEV_UNREGISTER:
return -1; return rt->dst.dev == dev ? -1 : 0;
case NETDEV_DOWN:
if (rt->should_flush)
return -1;
if (!rt->rt6i_nsiblings)
return rt->dst.dev == dev ? -1 : 0;
if (rt6_multipath_uses_dev(rt, dev)) {
unsigned int count;
count = rt6_multipath_dead_count(rt, dev);
if (rt->rt6i_nsiblings + 1 == count) {
rt6_multipath_flush(rt);
return -1;
}
rt6_multipath_nh_flags_set(rt, dev, RTNH_F_DEAD |
RTNH_F_LINKDOWN);
fib6_update_sernum(rt);
}
return -2;
case NETDEV_CHANGE:
if (rt->dst.dev != dev ||
rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST))
break;
rt->rt6i_nh_flags |= RTNH_F_LINKDOWN;
break;
}
return 0; return 0;
} }
void rt6_ifdown(struct net *net, struct net_device *dev) void rt6_sync_down_dev(struct net_device *dev, unsigned long event)
{ {
struct arg_dev_net adn = { struct arg_netdev_event arg = {
.dev = dev, .dev = dev,
.net = net, .event = event,
}; };
fib6_clean_all(net, fib6_ifdown, &adn); fib6_clean_all(dev_net(dev), fib6_ifdown, &arg);
if (dev) }
rt6_uncached_list_flush_dev(net, dev);
void rt6_disable_ip(struct net_device *dev, unsigned long event)
{
rt6_sync_down_dev(dev, event);
rt6_uncached_list_flush_dev(dev_net(dev), dev);
neigh_ifdown(&nd_tbl, dev);
} }
struct rt6_mtu_change_arg { struct rt6_mtu_change_arg {
...@@ -3992,7 +4118,10 @@ static size_t rt6_nlmsg_size(struct rt6_info *rt) ...@@ -3992,7 +4118,10 @@ static size_t rt6_nlmsg_size(struct rt6_info *rt)
static int rt6_nexthop_info(struct sk_buff *skb, struct rt6_info *rt, static int rt6_nexthop_info(struct sk_buff *skb, struct rt6_info *rt,
unsigned int *flags, bool skip_oif) unsigned int *flags, bool skip_oif)
{ {
if (!netif_running(rt->dst.dev) || !netif_carrier_ok(rt->dst.dev)) { if (rt->rt6i_nh_flags & RTNH_F_DEAD)
*flags |= RTNH_F_DEAD;
if (rt->rt6i_nh_flags & RTNH_F_LINKDOWN) {
*flags |= RTNH_F_LINKDOWN; *flags |= RTNH_F_LINKDOWN;
if (rt->rt6i_idev->cnf.ignore_routes_with_linkdown) if (rt->rt6i_idev->cnf.ignore_routes_with_linkdown)
*flags |= RTNH_F_DEAD; *flags |= RTNH_F_DEAD;
......
...@@ -5,6 +5,7 @@ CFLAGS = -Wall -Wl,--no-as-needed -O2 -g ...@@ -5,6 +5,7 @@ CFLAGS = -Wall -Wl,--no-as-needed -O2 -g
CFLAGS += -I../../../../usr/include/ CFLAGS += -I../../../../usr/include/
TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
TEST_PROGS += fib_tests.sh
TEST_GEN_FILES = socket TEST_GEN_FILES = socket
TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy
TEST_GEN_PROGS = reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa TEST_GEN_PROGS = reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
......
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This test is for checking IPv4 and IPv6 FIB behavior in response to
# different events.
ret=0
check_err()
{
if [ $ret -eq 0 ]; then
ret=$1
fi
}
check_fail()
{
if [ $1 -eq 0 ]; then
ret=1
fi
}
netns_create()
{
local testns=$1
ip netns add $testns
ip netns exec $testns ip link set dev lo up
}
fib_unreg_unicast_test()
{
ret=0
netns_create "testns"
ip netns exec testns ip link add dummy0 type dummy
ip netns exec testns ip link set dev dummy0 up
ip netns exec testns ip address add 198.51.100.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:1::1/64 dev dummy0
ip netns exec testns ip route get fibmatch 198.51.100.2 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 &> /dev/null
check_err $?
ip netns exec testns ip link del dev dummy0
check_err $?
ip netns exec testns ip route get fibmatch 198.51.100.2 &> /dev/null
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 &> /dev/null
check_fail $?
ip netns del testns
if [ $ret -ne 0 ]; then
echo "FAIL: unicast route test"
return 1
fi
echo "PASS: unicast route test"
}
fib_unreg_multipath_test()
{
ret=0
netns_create "testns"
ip netns exec testns ip link add dummy0 type dummy
ip netns exec testns ip link set dev dummy0 up
ip netns exec testns ip link add dummy1 type dummy
ip netns exec testns ip link set dev dummy1 up
ip netns exec testns ip address add 198.51.100.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:1::1/64 dev dummy0
ip netns exec testns ip address add 192.0.2.1/24 dev dummy1
ip netns exec testns ip -6 address add 2001:db8:2::1/64 dev dummy1
ip netns exec testns ip route add 203.0.113.0/24 \
nexthop via 198.51.100.2 dev dummy0 \
nexthop via 192.0.2.2 dev dummy1
ip netns exec testns ip -6 route add 2001:db8:3::/64 \
nexthop via 2001:db8:1::2 dev dummy0 \
nexthop via 2001:db8:2::2 dev dummy1
ip netns exec testns ip route get fibmatch 203.0.113.1 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 &> /dev/null
check_err $?
ip netns exec testns ip link del dev dummy0
check_err $?
ip netns exec testns ip route get fibmatch 203.0.113.1 &> /dev/null
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 &> /dev/null
# In IPv6 we do not flush the entire multipath route.
check_err $?
ip netns exec testns ip link del dev dummy1
ip netns del testns
if [ $ret -ne 0 ]; then
echo "FAIL: multipath route test"
return 1
fi
echo "PASS: multipath route test"
}
fib_unreg_test()
{
echo "Running netdev unregister tests"
fib_unreg_unicast_test
fib_unreg_multipath_test
}
fib_down_unicast_test()
{
ret=0
netns_create "testns"
ip netns exec testns ip link add dummy0 type dummy
ip netns exec testns ip link set dev dummy0 up
ip netns exec testns ip address add 198.51.100.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:1::1/64 dev dummy0
ip netns exec testns ip route get fibmatch 198.51.100.2 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 &> /dev/null
check_err $?
ip netns exec testns ip link set dev dummy0 down
check_err $?
ip netns exec testns ip route get fibmatch 198.51.100.2 &> /dev/null
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 &> /dev/null
check_fail $?
ip netns exec testns ip link del dev dummy0
ip netns del testns
if [ $ret -ne 0 ]; then
echo "FAIL: unicast route test"
return 1
fi
echo "PASS: unicast route test"
}
fib_down_multipath_test_do()
{
local down_dev=$1
local up_dev=$2
ip netns exec testns ip route get fibmatch 203.0.113.1 \
oif $down_dev &> /dev/null
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 \
oif $down_dev &> /dev/null
check_fail $?
ip netns exec testns ip route get fibmatch 203.0.113.1 \
oif $up_dev &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 \
oif $up_dev &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 203.0.113.1 | \
grep $down_dev | grep -q "dead linkdown"
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 | \
grep $down_dev | grep -q "dead linkdown"
check_err $?
ip netns exec testns ip route get fibmatch 203.0.113.1 | \
grep $up_dev | grep -q "dead linkdown"
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 | \
grep $up_dev | grep -q "dead linkdown"
check_fail $?
}
fib_down_multipath_test()
{
ret=0
netns_create "testns"
ip netns exec testns ip link add dummy0 type dummy
ip netns exec testns ip link set dev dummy0 up
ip netns exec testns ip link add dummy1 type dummy
ip netns exec testns ip link set dev dummy1 up
ip netns exec testns ip address add 198.51.100.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:1::1/64 dev dummy0
ip netns exec testns ip address add 192.0.2.1/24 dev dummy1
ip netns exec testns ip -6 address add 2001:db8:2::1/64 dev dummy1
ip netns exec testns ip route add 203.0.113.0/24 \
nexthop via 198.51.100.2 dev dummy0 \
nexthop via 192.0.2.2 dev dummy1
ip netns exec testns ip -6 route add 2001:db8:3::/64 \
nexthop via 2001:db8:1::2 dev dummy0 \
nexthop via 2001:db8:2::2 dev dummy1
ip netns exec testns ip route get fibmatch 203.0.113.1 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 &> /dev/null
check_err $?
ip netns exec testns ip link set dev dummy0 down
check_err $?
fib_down_multipath_test_do "dummy0" "dummy1"
ip netns exec testns ip link set dev dummy0 up
check_err $?
ip netns exec testns ip link set dev dummy1 down
check_err $?
fib_down_multipath_test_do "dummy1" "dummy0"
ip netns exec testns ip link set dev dummy0 down
check_err $?
ip netns exec testns ip route get fibmatch 203.0.113.1 &> /dev/null
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:3::1 &> /dev/null
check_fail $?
ip netns exec testns ip link del dev dummy1
ip netns exec testns ip link del dev dummy0
ip netns del testns
if [ $ret -ne 0 ]; then
echo "FAIL: multipath route test"
return 1
fi
echo "PASS: multipath route test"
}
fib_down_test()
{
echo "Running netdev down tests"
fib_down_unicast_test
fib_down_multipath_test
}
fib_carrier_local_test()
{
ret=0
# Local routes should not be affected when carrier changes.
netns_create "testns"
ip netns exec testns ip link add dummy0 type dummy
ip netns exec testns ip link set dev dummy0 up
ip netns exec testns ip link set dev dummy0 carrier on
ip netns exec testns ip address add 198.51.100.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:1::1/64 dev dummy0
ip netns exec testns ip route get fibmatch 198.51.100.1 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::1 &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 198.51.100.1 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::1 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip link set dev dummy0 carrier off
ip netns exec testns ip route get fibmatch 198.51.100.1 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::1 &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 198.51.100.1 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::1 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip address add 192.0.2.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:2::1/64 dev dummy0
ip netns exec testns ip route get fibmatch 192.0.2.1 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:2::1 &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 192.0.2.1 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:2::1 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip link del dev dummy0
ip netns del testns
if [ $ret -ne 0 ]; then
echo "FAIL: local route carrier test"
return 1
fi
echo "PASS: local route carrier test"
}
fib_carrier_unicast_test()
{
ret=0
netns_create "testns"
ip netns exec testns ip link add dummy0 type dummy
ip netns exec testns ip link set dev dummy0 up
ip netns exec testns ip link set dev dummy0 carrier on
ip netns exec testns ip address add 198.51.100.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:1::1/64 dev dummy0
ip netns exec testns ip route get fibmatch 198.51.100.2 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 198.51.100.2 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 | \
grep -q "linkdown"
check_fail $?
ip netns exec testns ip link set dev dummy0 carrier off
ip netns exec testns ip route get fibmatch 198.51.100.2 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 198.51.100.2 | \
grep -q "linkdown"
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:1::2 | \
grep -q "linkdown"
check_err $?
ip netns exec testns ip address add 192.0.2.1/24 dev dummy0
ip netns exec testns ip -6 address add 2001:db8:2::1/64 dev dummy0
ip netns exec testns ip route get fibmatch 192.0.2.2 &> /dev/null
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:2::2 &> /dev/null
check_err $?
ip netns exec testns ip route get fibmatch 192.0.2.2 | \
grep -q "linkdown"
check_err $?
ip netns exec testns ip -6 route get fibmatch 2001:db8:2::2 | \
grep -q "linkdown"
check_err $?
ip netns exec testns ip link del dev dummy0
ip netns del testns
if [ $ret -ne 0 ]; then
echo "FAIL: unicast route carrier test"
return 1
fi
echo "PASS: unicast route carrier test"
}
fib_carrier_test()
{
echo "Running netdev carrier change tests"
fib_carrier_local_test
fib_carrier_unicast_test
}
fib_test()
{
fib_unreg_test
fib_down_test
fib_carrier_test
}
if [ "$(id -u)" -ne 0 ];then
echo "SKIP: Need root privileges"
exit 0
fi
if [ ! -x "$(command -v ip)" ]; then
echo "SKIP: Could not run test without ip tool"
exit 0
fi
ip route help 2>&1 | grep -q fibmatch
if [ $? -ne 0 ]; then
echo "SKIP: iproute2 too old, missing fibmatch"
exit 0
fi
fib_test
exit $ret
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