summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/net/ip6_fib.h4
-rw-r--r--include/net/ip6_route.h4
-rw-r--r--net/ipv6/addrconf.c9
-rw-r--r--net/ipv6/ip6_fib.c28
-rw-r--r--net/ipv6/route.c185
-rw-r--r--tools/testing/selftests/net/Makefile1
-rwxr-xr-xtools/testing/selftests/net/fib_tests.sh429
7 files changed, 618 insertions, 42 deletions
diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 44d96a91e745..ddf53dd1e948 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -173,7 +173,8 @@ struct rt6_info {
unsigned short rt6i_nfheader_len;
u8 rt6i_protocol;
u8 exception_bucket_flushed:1,
- unused:7;
+ should_flush:1,
+ unused:6;
};
#define for_each_fib6_node_rt_rcu(fn) \
@@ -404,6 +405,7 @@ unsigned int fib6_tables_seq_read(struct net *net);
int fib6_tables_dump(struct net *net, struct notifier_block *nb);
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
int fib6_rules_init(void);
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 18e442ea93d8..34cd3b0c6ded 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -165,10 +165,12 @@ struct rt6_rtnl_dump_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_remove_prefsrc(struct inet6_ifaddr *ifp);
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)
{
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index ed06b1190f05..2435f7ab070b 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -3438,6 +3438,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
} else if (event == NETDEV_CHANGE) {
if (!addrconf_link_ready(dev)) {
/* device is still not ready. */
+ rt6_sync_down_dev(dev, event);
break;
}
@@ -3449,6 +3450,7 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
* multicast snooping switches
*/
ipv6_mc_up(idev);
+ rt6_sync_up(dev, RTNH_F_LINKDOWN);
break;
}
idev->if_flags |= IF_READY;
@@ -3484,6 +3486,9 @@ static int addrconf_notify(struct notifier_block *this, unsigned long event,
if (run_pending)
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,
* when the interface up, the changed MTU must be
@@ -3577,6 +3582,7 @@ static bool addr_is_local(const struct in6_addr *addr)
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 inet6_dev *idev;
struct inet6_ifaddr *ifa, *tmp;
@@ -3586,8 +3592,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
ASSERT_RTNL();
- rt6_ifdown(net, dev);
- neigh_ifdown(&nd_tbl, dev);
+ rt6_disable_ip(dev, event);
idev = __in6_dev_get(dev);
if (!idev)
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index a64d559fa513..edda5ad3b405 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -107,16 +107,13 @@ enum {
void fib6_update_sernum(struct rt6_info *rt)
{
- struct fib6_table *table = rt->rt6i_table;
struct net *net = dev_net(rt->dst.dev);
struct fib6_node *fn;
- spin_lock_bh(&table->tb6_lock);
fn = rcu_dereference_protected(rt->rt6i_node,
- lockdep_is_held(&table->tb6_lock));
+ lockdep_is_held(&rt->rt6i_table->tb6_lock));
if (fn)
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)
jiffies + net->ipv6.sysctl.ip6_rt_gc_interval);
}
-static void fib6_update_sernum_upto_root(struct rt6_info *rt,
- int sernum)
+static void __fib6_update_sernum_upto_root(struct rt6_info *rt,
+ int sernum)
{
struct fib6_node *fn = rcu_dereference_protected(rt->rt6i_node,
lockdep_is_held(&rt->rt6i_table->tb6_lock));
@@ -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.
* <destination addr>/<source addr>
@@ -1230,7 +1232,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
err = fib6_add_rt2node(fn, rt, info, mxc, extack);
if (!err) {
- fib6_update_sernum_upto_root(rt, sernum);
+ __fib6_update_sernum_upto_root(rt, sernum);
fib6_start_gc(info->nl_net, rt);
}
@@ -1887,7 +1889,7 @@ static int fib6_clean_node(struct fib6_walker *w)
for_each_fib6_walker_rt(w) {
res = c->func(rt, c->arg);
- if (res < 0) {
+ if (res == -1) {
w->leaf = rt;
res = fib6_del(rt, &info);
if (res) {
@@ -1900,6 +1902,12 @@ static int fib6_clean_node(struct fib6_walker *w)
continue;
}
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);
}
@@ -1911,7 +1919,8 @@ static int fib6_clean_node(struct fib6_walker *w)
* Convenient frontend to tree walker.
*
* 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
*/
@@ -2103,7 +2112,6 @@ static void fib6_net_exit(struct net *net)
{
unsigned int i;
- rt6_ifdown(net, NULL);
del_timer_sync(&net->ipv6.ip6_fib_timer);
for (i = 0; i < FIB6_TABLE_HASHSZ; i++) {
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 2490280b3394..1054b059747f 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -474,7 +474,9 @@ static struct rt6_info *rt6_multipath_select(struct rt6_info *match,
if (route_choosen == 0) {
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)
break;
if (rt6_score_route(sibling, oif, strict) < 0)
@@ -499,12 +501,15 @@ static inline struct rt6_info *rt6_device_match(struct net *net,
struct rt6_info *local = NULL;
struct rt6_info *sprt;
- if (!oif && ipv6_addr_any(saddr))
- goto out;
+ if (!oif && ipv6_addr_any(saddr) && !(rt->rt6i_nh_flags & RTNH_F_DEAD))
+ return rt;
for (sprt = rt; sprt; sprt = rcu_dereference(sprt->rt6_next)) {
struct net_device *dev = sprt->dst.dev;
+ if (sprt->rt6i_nh_flags & RTNH_F_DEAD)
+ continue;
+
if (oif) {
if (dev->ifindex == oif)
return sprt;
@@ -533,8 +538,8 @@ static inline struct rt6_info *rt6_device_match(struct net *net,
if (flags & RT6_LOOKUP_F_IFACE)
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
@@ -679,10 +684,12 @@ static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict,
int m;
bool match_do_rr = false;
struct inet6_dev *idev = rt->rt6i_idev;
- struct net_device *dev = rt->dst.dev;
- if (dev && !netif_carrier_ok(dev) &&
- idev->cnf.ignore_routes_with_linkdown &&
+ if (rt->rt6i_nh_flags & RTNH_F_DEAD)
+ goto out;
+
+ if (idev->cnf.ignore_routes_with_linkdown &&
+ rt->rt6i_nh_flags & RTNH_F_LINKDOWN &&
!(strict & RT6_LOOKUP_F_IGNORE_LINKSTATE))
goto out;
@@ -1346,7 +1353,9 @@ out:
/* Update fn->fn_sernum to invalidate all cached dst */
if (!err) {
+ spin_lock_bh(&ort->rt6i_table->tb6_lock);
fib6_update_sernum(ort);
+ spin_unlock_bh(&ort->rt6i_table->tb6_lock);
fib6_force_start_gc(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);
restart:
for_each_fib6_node_rt_rcu(fn) {
+ if (rt->rt6i_nh_flags & RTNH_F_DEAD)
+ continue;
if (rt6_check_expired(rt))
continue;
if (rt->dst.error)
@@ -2344,7 +2355,7 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
rt->rt6i_idev = idev;
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
*/
rt6_uncached_list_add(rt);
@@ -2746,6 +2757,9 @@ static struct rt6_info *ip6_route_info_create(struct fib6_config *cfg,
rt->rt6i_flags = cfg->fc_flags;
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->rt6i_idev = idev;
rt->rt6i_table = table;
@@ -3459,37 +3473,149 @@ void rt6_clean_tohost(struct net *net, struct in6_addr *gateway)
fib6_clean_all(net, fib6_clean_tohost, gateway);
}
-struct arg_dev_net {
- struct net_device *dev;
- struct net *net;
+struct arg_netdev_event {
+ const struct net_device *dev;
+ 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 */
-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 net_device *dev = adn->dev;
+ const struct arg_netdev_event *arg = p_arg;
+ const struct net_device *dev = arg->dev;
+ const struct net *net = dev_net(dev);
- if ((rt->dst.dev == dev || !dev) &&
- rt != adn->net->ipv6.ip6_null_entry &&
- (rt->rt6i_nsiblings == 0 ||
- (dev && netdev_unregistering(dev)) ||
- !rt->rt6i_idev->cnf.ignore_routes_with_linkdown))
- return -1;
+ if (rt == net->ipv6.ip6_null_entry)
+ return 0;
+
+ switch (arg->event) {
+ case NETDEV_UNREGISTER:
+ 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;
}
-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,
- .net = net,
+ .event = event,
};
- fib6_clean_all(net, fib6_ifdown, &adn);
- if (dev)
- rt6_uncached_list_flush_dev(net, dev);
+ fib6_clean_all(dev_net(dev), fib6_ifdown, &arg);
+}
+
+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 {
@@ -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,
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;
if (rt->rt6i_idev->cnf.ignore_routes_with_linkdown)
*flags |= RTNH_F_DEAD;
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 500c74db746c..d7c30d366935 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -5,6 +5,7 @@ CFLAGS = -Wall -Wl,--no-as-needed -O2 -g
CFLAGS += -I../../../../usr/include/
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 += psock_fanout psock_tpacket msg_zerocopy
TEST_GEN_PROGS = reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
diff --git a/tools/testing/selftests/net/fib_tests.sh b/tools/testing/selftests/net/fib_tests.sh
new file mode 100755
index 000000000000..a9154eefb2e2
--- /dev/null
+++ b/tools/testing/selftests/net/fib_tests.sh
@@ -0,0 +1,429 @@
+#!/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