diff options
author | Hannes Frederic Sowa <hannes@stressinduktion.org> | 2015-01-26 09:11:17 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2015-01-27 03:22:14 -0500 |
commit | 6e9e16e6143b725662e47026a1d0f270721cdd24 (patch) | |
tree | b81335dc5bc05fe51f9e76afd68178258aaf215c /net/ipv6 | |
parent | 225776098b36a5aee5211e881d484f48ad61ea8c (diff) |
ipv6: replacing a rt6_info needs to purge possible propagated rt6_infos too
Lubomir Rintel reported that during replacing a route the interface
reference counter isn't correctly decremented.
To quote bug <https://bugzilla.kernel.org/show_bug.cgi?id=91941>:
| [root@rhel7-5 lkundrak]# sh -x lal
| + ip link add dev0 type dummy
| + ip link set dev0 up
| + ip link add dev1 type dummy
| + ip link set dev1 up
| + ip addr add 2001:db8:8086::2/64 dev dev0
| + ip route add 2001:db8:8086::/48 dev dev0 proto static metric 20
| + ip route add 2001:db8:8088::/48 dev dev1 proto static metric 10
| + ip route replace 2001:db8:8086::/48 dev dev1 proto static metric 20
| + ip link del dev0 type dummy
| Message from syslogd@rhel7-5 at Jan 23 10:54:41 ...
| kernel:unregister_netdevice: waiting for dev0 to become free. Usage count = 2
|
| Message from syslogd@rhel7-5 at Jan 23 10:54:51 ...
| kernel:unregister_netdevice: waiting for dev0 to become free. Usage count = 2
During replacement of a rt6_info we must walk all parent nodes and check
if the to be replaced rt6_info got propagated. If so, replace it with
an alive one.
Fixes: 4a287eba2de3957 ("IPv6 routing, NLM_F_* flag support: REPLACE and EXCL flags support, warn about missing CREATE flag")
Reported-by: Lubomir Rintel <lkundrak@v3.sk>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Tested-by: Lubomir Rintel <lkundrak@v3.sk>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6')
-rw-r--r-- | net/ipv6/ip6_fib.c | 45 |
1 files changed, 26 insertions, 19 deletions
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index b2d1838897c9..f1c6d5e98322 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c | |||
@@ -659,6 +659,29 @@ static int fib6_commit_metrics(struct dst_entry *dst, | |||
659 | return 0; | 659 | return 0; |
660 | } | 660 | } |
661 | 661 | ||
662 | static void fib6_purge_rt(struct rt6_info *rt, struct fib6_node *fn, | ||
663 | struct net *net) | ||
664 | { | ||
665 | if (atomic_read(&rt->rt6i_ref) != 1) { | ||
666 | /* This route is used as dummy address holder in some split | ||
667 | * nodes. It is not leaked, but it still holds other resources, | ||
668 | * which must be released in time. So, scan ascendant nodes | ||
669 | * and replace dummy references to this route with references | ||
670 | * to still alive ones. | ||
671 | */ | ||
672 | while (fn) { | ||
673 | if (!(fn->fn_flags & RTN_RTINFO) && fn->leaf == rt) { | ||
674 | fn->leaf = fib6_find_prefix(net, fn); | ||
675 | atomic_inc(&fn->leaf->rt6i_ref); | ||
676 | rt6_release(rt); | ||
677 | } | ||
678 | fn = fn->parent; | ||
679 | } | ||
680 | /* No more references are possible at this point. */ | ||
681 | BUG_ON(atomic_read(&rt->rt6i_ref) != 1); | ||
682 | } | ||
683 | } | ||
684 | |||
662 | /* | 685 | /* |
663 | * Insert routing information in a node. | 686 | * Insert routing information in a node. |
664 | */ | 687 | */ |
@@ -807,11 +830,12 @@ add: | |||
807 | rt->dst.rt6_next = iter->dst.rt6_next; | 830 | rt->dst.rt6_next = iter->dst.rt6_next; |
808 | atomic_inc(&rt->rt6i_ref); | 831 | atomic_inc(&rt->rt6i_ref); |
809 | inet6_rt_notify(RTM_NEWROUTE, rt, info); | 832 | inet6_rt_notify(RTM_NEWROUTE, rt, info); |
810 | rt6_release(iter); | ||
811 | if (!(fn->fn_flags & RTN_RTINFO)) { | 833 | if (!(fn->fn_flags & RTN_RTINFO)) { |
812 | info->nl_net->ipv6.rt6_stats->fib_route_nodes++; | 834 | info->nl_net->ipv6.rt6_stats->fib_route_nodes++; |
813 | fn->fn_flags |= RTN_RTINFO; | 835 | fn->fn_flags |= RTN_RTINFO; |
814 | } | 836 | } |
837 | fib6_purge_rt(iter, fn, info->nl_net); | ||
838 | rt6_release(iter); | ||
815 | } | 839 | } |
816 | 840 | ||
817 | return 0; | 841 | return 0; |
@@ -1322,24 +1346,7 @@ static void fib6_del_route(struct fib6_node *fn, struct rt6_info **rtp, | |||
1322 | fn = fib6_repair_tree(net, fn); | 1346 | fn = fib6_repair_tree(net, fn); |
1323 | } | 1347 | } |
1324 | 1348 | ||
1325 | if (atomic_read(&rt->rt6i_ref) != 1) { | 1349 | fib6_purge_rt(rt, fn, net); |
1326 | /* This route is used as dummy address holder in some split | ||
1327 | * nodes. It is not leaked, but it still holds other resources, | ||
1328 | * which must be released in time. So, scan ascendant nodes | ||
1329 | * and replace dummy references to this route with references | ||
1330 | * to still alive ones. | ||
1331 | */ | ||
1332 | while (fn) { | ||
1333 | if (!(fn->fn_flags & RTN_RTINFO) && fn->leaf == rt) { | ||
1334 | fn->leaf = fib6_find_prefix(net, fn); | ||
1335 | atomic_inc(&fn->leaf->rt6i_ref); | ||
1336 | rt6_release(rt); | ||
1337 | } | ||
1338 | fn = fn->parent; | ||
1339 | } | ||
1340 | /* No more references are possible at this point. */ | ||
1341 | BUG_ON(atomic_read(&rt->rt6i_ref) != 1); | ||
1342 | } | ||
1343 | 1350 | ||
1344 | inet6_rt_notify(RTM_DELROUTE, rt, info); | 1351 | inet6_rt_notify(RTM_DELROUTE, rt, info); |
1345 | rt6_release(rt); | 1352 | rt6_release(rt); |