From 77de91e5a8b1c1993ae65c54b37e0411e78e6fe6 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Thu, 19 Apr 2012 18:27:12 +0000 Subject: core: don't fight with the kernel over the default IPv6 route The kernel wants there to be a default route over every RA-ed IPv6 interface, and it gets confused and annoyed if we remove that default route and replace it with our own (causing it to effectively drop all further RAs on the floor, which is particularly bad if some of the information in the earlier RA had an expiration time). So, rather than replacing the kernel's default route(s), just add an additional one of our own, with a lower (ie, higher priority) metric. https://bugzilla.redhat.com/show_bug.cgi?id=785772 --- diff --git a/src/nm-system.c b/src/nm-system.c index 91153ec..4cebb13 100644 --- a/src/nm-system.c +++ b/src/nm-system.c @@ -1023,7 +1023,7 @@ add_ip6_route_to_gateway (int ifindex, const struct in6_addr *gw) } static int -replace_default_ip6_route (int ifindex, const struct in6_addr *gw) +add_default_ip6_route (int ifindex, const struct in6_addr *gw) { struct rtnl_route *route = NULL; struct nl_sock *nlh; @@ -1037,22 +1037,36 @@ replace_default_ip6_route (int ifindex, const struct in6_addr *gw) route = nm_netlink_route_new (ifindex, AF_INET6, 0, NMNL_PROP_SCOPE, RT_SCOPE_UNIVERSE, NMNL_PROP_TABLE, RT_TABLE_MAIN, + NMNL_PROP_PRIO, 1, NULL); g_return_val_if_fail (route != NULL, -ENOMEM); /* Add the new default route */ - err = nm_netlink_route6_add (route, &in6addr_any, 0, gw, NLM_F_REPLACE); - if (err == -NLE_EXIST) { - /* FIXME: even though we use NLM_F_REPLACE the kernel won't replace - * the route if it's the same. Suppress the pointless error. - */ + err = nm_netlink_route6_add (route, &in6addr_any, 0, gw, NLM_F_CREATE); + if (err == -NLE_EXIST) err = 0; - } rtnl_route_put (route); return err; } +static struct rtnl_route * +find_static_default_routes (struct rtnl_route *route, + struct nl_addr *dst, + const char *iface, + gpointer user_data) +{ + GList **def_routes = user_data; + + if ( nl_addr_get_prefixlen (dst) == 0 + && rtnl_route_get_protocol (route) == RTPROT_STATIC) { + rtnl_route_get (route); + *def_routes = g_list_prepend (*def_routes, route); + } + + return NULL; +} + /* * nm_system_replace_default_ip6_route * @@ -1062,12 +1076,35 @@ replace_default_ip6_route (int ifindex, const struct in6_addr *gw) gboolean nm_system_replace_default_ip6_route (int ifindex, const struct in6_addr *gw) { - struct rtnl_route *gw_route = NULL; + GList *def_routes, *iter; + struct rtnl_route *route, *gw_route = NULL; gboolean success = FALSE; char *iface; int err; - err = replace_default_ip6_route (ifindex, gw); + /* We can't just use NLM_F_REPLACE here like in the IPv4 case, because + * the kernel doesn't like it if we replace the default routes it + * creates. (See rh#785772.) So we delete any non-kernel default routes, + * and then add a new default route of our own with a lower metric than + * the kernel ones. + */ + def_routes = NULL; + nm_netlink_foreach_route (ifindex, AF_INET6, RT_SCOPE_UNIVERSE, TRUE, + find_static_default_routes, &def_routes); + for (iter = def_routes; iter; iter = iter->next) { + route = iter->data; + if (!nm_netlink_route_delete (route)) { + iface = nm_netlink_index_to_iface (ifindex); + nm_log_err (LOGD_DEVICE | LOGD_IP6, + "(%s): failed to delete existing IPv6 default route", + iface); + g_free (iface); + } + rtnl_route_put (route); + } + g_list_free (def_routes); + + err = add_default_ip6_route (ifindex, gw); if (err == 0) return TRUE; @@ -1091,7 +1128,7 @@ nm_system_replace_default_ip6_route (int ifindex, const struct in6_addr *gw) goto out; /* Try adding the original route again */ - err = replace_default_ip6_route (ifindex, gw); + err = add_default_ip6_route (ifindex, gw); if (err != 0) { nm_netlink_route_delete (gw_route); nm_log_err (LOGD_DEVICE | LOGD_IP6, -- cgit v0.9.0.2-2-gbebe