ip6: pad RDNSS and DNSSL lifetimes to a minimum lifetime value (rh #60055)
[NetworkManager.git] / src / ip6-manager / nm-ip6-manager.c
index 8c9ef6d..8f05ddc 100644 (file)
 
 #include "nm-ip6-manager.h"
 #include "nm-netlink-monitor.h"
+#include "nm-netlink-utils.h"
+#include "nm-netlink-compat.h"
 #include "NetworkManagerUtils.h"
 #include "nm-marshal.h"
 #include "nm-logging.h"
-#include "nm-system.h"
 
 /* Pre-DHCP addrconf timeout, in seconds */
 #define NM_IP6_TIMEOUT 20
@@ -45,7 +46,7 @@ typedef struct {
        NMNetlinkMonitor *monitor;
        GHashTable *devices;
 
-       struct nl_handle *nlh;
+       struct nl_sock *nlh;
        struct nl_cache *addr_cache, *route_cache;
 
        guint netlink_id;
@@ -88,9 +89,13 @@ typedef struct {
        char *iface;
        int ifindex;
 
+       gboolean has_linklocal;
+       gboolean has_nonlinklocal;
+       guint dhcp_opts;
+
        char *disable_ip6_path;
        gboolean disable_ip6_save_valid;
-       guint32 disable_ip6_save;
+       gint32 disable_ip6_save;
 
        guint finish_addrconf_id;
        guint config_changed_id;
@@ -111,6 +116,14 @@ typedef struct {
 } NMIP6Device;
 
 static void
+clear_config_changed (NMIP6Device *device)
+{
+       if (device->config_changed_id)
+               g_source_remove (device->config_changed_id);
+       device->config_changed_id = 0;
+}
+
+static void
 nm_ip6_device_destroy (NMIP6Device *device)
 {
        g_return_if_fail (device != NULL);
@@ -123,8 +136,9 @@ nm_ip6_device_destroy (NMIP6Device *device)
 
        if (device->finish_addrconf_id)
                g_source_remove (device->finish_addrconf_id);
-       if (device->config_changed_id)
-               g_source_remove (device->config_changed_id);
+
+       clear_config_changed (device);
+
        g_free (device->iface);
        if (device->rdnss_servers)
                g_array_free (device->rdnss_servers, TRUE);
@@ -156,7 +170,7 @@ nm_ip6_device_new (NMIP6Manager *manager, int ifindex)
        }
 
        device->ifindex = ifindex;
-       device->iface = g_strdup (nm_netlink_index_to_iface (ifindex));
+       device->iface = nm_netlink_index_to_iface (ifindex);
        if (!device->iface) {
                nm_log_err (LOGD_IP6, "(%d): could not find interface name from index.",
                            ifindex);
@@ -175,9 +189,10 @@ nm_ip6_device_new (NMIP6Manager *manager, int ifindex)
        device->disable_ip6_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/disable_ipv6",
                                                    device->iface);
        g_assert (device->disable_ip6_path);
-       device->disable_ip6_save_valid = nm_utils_get_proc_sys_net_value (device->disable_ip6_path,
-                                                                         device->iface,
-                                                                         &device->disable_ip6_save);
+       device->disable_ip6_save_valid = nm_utils_get_proc_sys_net_value_with_bounds (device->disable_ip6_path,
+                                                                                     device->iface,
+                                                                                     &device->disable_ip6_save,
+                                                                                     0, 1);
 
        return device;
 
@@ -198,6 +213,44 @@ nm_ip6_manager_get_device (NMIP6Manager *manager, int ifindex)
        return g_hash_table_lookup (priv->devices, GINT_TO_POINTER (ifindex));
 }
 
+static char *
+device_get_iface (NMIP6Device *device)
+{
+       return device ? device->iface : "unknown";
+}
+
+static const char *
+state_to_string (NMIP6DeviceState state)
+{
+       switch (state) {
+       case NM_IP6_DEVICE_UNCONFIGURED:
+               return "unconfigured";
+       case NM_IP6_DEVICE_GOT_LINK_LOCAL:
+               return "got-link-local";
+       case NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT:
+               return "got-ra";
+       case NM_IP6_DEVICE_GOT_ADDRESS:
+               return "got-address";
+       case NM_IP6_DEVICE_TIMED_OUT:
+               return "timed-out";
+       default:
+               return "unknown";
+       }
+}
+
+static void
+device_set_state (NMIP6Device *device, NMIP6DeviceState state)
+{
+       NMIP6DeviceState oldstate;
+
+       g_return_if_fail (device != NULL);
+
+       oldstate = device->state;
+       device->state = state;
+       nm_log_dbg (LOGD_IP6, "(%s) IP6 device state: %s -> %s",
+                   device_get_iface (device), state_to_string (oldstate), state_to_string (state));
+}
+
 /******************************************************************/
 
 typedef struct {
@@ -266,6 +319,7 @@ rdnss_expired (gpointer user_data)
        nm_log_dbg (LOGD_IP6, "(%s): IPv6 RDNSS information expired", device->iface);
 
        set_rdnss_timeout (device);
+       clear_config_changed (device);
        emit_config_changed (&info);
        return FALSE;
 }
@@ -308,9 +362,9 @@ set_rdnss_timeout (NMIP6Device *device)
        }
 
        if (expires) {
-               device->rdnss_timeout_id = g_timeout_add_seconds (expires - now,
-                                                                                                                 rdnss_expired,
-                                                                                                                 device);
+               device->rdnss_timeout_id = g_timeout_add_seconds (MIN (expires - now, G_MAXUINT32 - 1),
+                                                                 rdnss_expired,
+                                                                 device);
        }
 }
 
@@ -320,11 +374,12 @@ static gboolean
 dnssl_expired (gpointer user_data)
 {
        NMIP6Device *device = user_data;
-       CallbackInfo info = { device, IP6_DHCP_OPT_NONE };
+       CallbackInfo info = { device, IP6_DHCP_OPT_NONE, FALSE };
 
        nm_log_dbg (LOGD_IP6, "(%s): IPv6 DNSSL information expired", device->iface);
 
        set_dnssl_timeout (device);
+       clear_config_changed (device);
        emit_config_changed (&info);
        return FALSE;
 }
@@ -363,7 +418,7 @@ set_dnssl_timeout (NMIP6Device *device)
        }
 
        if (expires) {
-               device->dnssl_timeout_id = g_timeout_add_seconds (expires - now,
+               device->dnssl_timeout_id = g_timeout_add_seconds (MIN (expires - now, G_MAXUINT32 - 1),
                                                                  dnssl_expired,
                                                                  device);
        }
@@ -381,41 +436,18 @@ callback_info_new (NMIP6Device *device, guint dhcp_opts, gboolean success)
        return info;
 }
 
-static const char *
-state_to_string (NMIP6DeviceState state)
-{
-       switch (state) {
-       case NM_IP6_DEVICE_UNCONFIGURED:
-               return "unconfigured";
-       case NM_IP6_DEVICE_GOT_LINK_LOCAL:
-               return "got-link-local";
-       case NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT:
-               return "got-ra";
-       case NM_IP6_DEVICE_GOT_ADDRESS:
-               return "got-address";
-       case NM_IP6_DEVICE_TIMED_OUT:
-               return "timed-out";
-       default:
-               return "unknown";
-       }
-}
-
 static void
-nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
+check_addresses (NMIP6Device *device)
 {
        NMIP6Manager *manager = device->manager;
        NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
        struct rtnl_addr *rtnladdr;
        struct nl_addr *nladdr;
        struct in6_addr *addr;
-       CallbackInfo *info;
-       guint dhcp_opts = IP6_DHCP_OPT_NONE;
-       gboolean found_linklocal = FALSE, found_other = FALSE;
 
-       nm_log_dbg (LOGD_IP6, "(%s): syncing with netlink (ra_flags 0x%X) (state/target '%s'/'%s')",
-                   device->iface, device->ra_flags,
-                   state_to_string (device->state),
-                   state_to_string (device->target_state));
+       /* Reset address information */
+       device->has_linklocal = FALSE;
+       device->has_nonlinklocal = FALSE;
 
        /* Look for any IPv6 addresses the kernel may have set for the device */
        for (rtnladdr = (struct rtnl_addr *) nl_cache_get_first (priv->addr_cache);
@@ -433,18 +465,19 @@ nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
                addr = nl_addr_get_binary_addr (nladdr);
 
                if (inet_ntop (AF_INET6, addr, buf, INET6_ADDRSTRLEN) > 0) {
-                       nm_log_dbg (LOGD_IP6, "(%s): netlink address: %s",
-                                   device->iface, buf);
+                       nm_log_dbg (LOGD_IP6, "(%s): netlink address: %s/%d",
+                                   device->iface, buf,
+                                   rtnl_addr_get_prefixlen (rtnladdr));
                }
 
                if (IN6_IS_ADDR_LINKLOCAL (addr)) {
                        if (device->state == NM_IP6_DEVICE_UNCONFIGURED)
-                               device->state = NM_IP6_DEVICE_GOT_LINK_LOCAL;
-                       found_linklocal = TRUE;
+                               device_set_state (device, NM_IP6_DEVICE_GOT_LINK_LOCAL);
+                       device->has_linklocal = TRUE;
                } else {
                        if (device->state < NM_IP6_DEVICE_GOT_ADDRESS)
-                               device->state = NM_IP6_DEVICE_GOT_ADDRESS;
-                       found_other = TRUE;
+                               device_set_state (device, NM_IP6_DEVICE_GOT_ADDRESS);
+                       device->has_nonlinklocal = TRUE;
                }
        }
 
@@ -452,27 +485,41 @@ nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
         * before in the initial run, but if it goes away later, make sure we
         * regress from GOT_LINK_LOCAL back to UNCONFIGURED.
         */
-       if ((device->state == NM_IP6_DEVICE_GOT_LINK_LOCAL) && !found_linklocal)
-               device->state = NM_IP6_DEVICE_UNCONFIGURED;
+       if ((device->state == NM_IP6_DEVICE_GOT_LINK_LOCAL) && !device->has_linklocal)
+               device_set_state (device, NM_IP6_DEVICE_UNCONFIGURED);
 
-       nm_log_dbg (LOGD_IP6, "(%s): addresses synced (state %s)",
-                   device->iface, state_to_string (device->state));
+       nm_log_dbg (LOGD_IP6, "(%s): addresses checked (state %s)",
+                   device->iface, state_to_string (device->state));
+}
+
+static void
+check_ra_flags (NMIP6Device *device)
+{
+       device->dhcp_opts = IP6_DHCP_OPT_NONE;
 
        /* We only care about router advertisements if we want a real IPv6 address */
        if (   (device->target_state == NM_IP6_DEVICE_GOT_ADDRESS)
            && (device->ra_flags & IF_RA_RCVD)) {
 
                if (device->state < NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT)
-                       device->state = NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT;
+                       device_set_state (device, NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT);
 
                if (device->ra_flags & IF_RA_MANAGED) {
-                       dhcp_opts = IP6_DHCP_OPT_MANAGED;
+                       device->dhcp_opts = IP6_DHCP_OPT_MANAGED;
                        nm_log_dbg (LOGD_IP6, "router advertisement deferred to DHCPv6");
                } else if (device->ra_flags & IF_RA_OTHERCONF) {
-                       dhcp_opts = IP6_DHCP_OPT_OTHERCONF;
+                       device->dhcp_opts = IP6_DHCP_OPT_OTHERCONF;
                        nm_log_dbg (LOGD_IP6, "router advertisement requests parallel DHCPv6");
                }
        }
+       nm_log_dbg (LOGD_IP6, "(%s): router advertisement checked (state %s)",
+                   device->iface, state_to_string (device->state));
+}
+
+static void
+check_addrconf_complete (NMIP6Device *device)
+{
+       CallbackInfo *info;
 
        if (!device->addrconf_complete) {
                /* Managed mode (ie DHCP only) short-circuits automatic addrconf, so
@@ -480,7 +527,7 @@ nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
                 * when the RA requests managed mode.
                 */
                if (   (device->state >= device->target_state)
-                   || (dhcp_opts == IP6_DHCP_OPT_MANAGED)) {
+                   || (device->dhcp_opts == IP6_DHCP_OPT_MANAGED)) {
                        /* device->finish_addrconf_id may currently be a timeout
                         * rather than an idle, so we remove the existing source.
                         */
@@ -489,15 +536,15 @@ nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
 
                        nm_log_dbg (LOGD_IP6, "(%s): reached target state or Managed-mode requested (state '%s') (dhcp opts 0x%X)",
                                    device->iface, state_to_string (device->state),
-                                   dhcp_opts);
+                                   device->dhcp_opts);
 
-                       info = callback_info_new (device, dhcp_opts, TRUE);
+                       info = callback_info_new (device, device->dhcp_opts, TRUE);
                        device->finish_addrconf_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
                                                                      finish_addrconf,
                                                                      info,
                                                                      (GDestroyNotify) g_free);
                }
-       } else if (config_changed) {
+       } else {
                if (!device->config_changed_id) {
                        gboolean success = TRUE;
 
@@ -506,19 +553,35 @@ nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
                         */
                        if (   (device->state == NM_IP6_DEVICE_GOT_ADDRESS)
                            && (device->target_state == NM_IP6_DEVICE_GOT_ADDRESS)
-                           && !found_other) {
-                               nm_log_dbg (LOGD_IP6, "(%s): RA-provided address no longer valid",
+                           && !device->has_nonlinklocal) {
+                               nm_log_dbg (LOGD_IP6, "(%s): RA-provided address no longer found",
                                            device->iface);
                                success = FALSE;
                        }
 
-                       info = callback_info_new (device, dhcp_opts, success);
+                       info = callback_info_new (device, device->dhcp_opts, success);
                        device->config_changed_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
                                                                     emit_config_changed,
                                                                     info,
                                                                     (GDestroyNotify) g_free);
                }
        }
+
+       nm_log_dbg (LOGD_IP6, "(%s): dhcp_opts checked (state %s)",
+                   device->iface, state_to_string (device->state));
+}
+
+static void
+nm_ip6_device_sync_from_netlink (NMIP6Device *device)
+{
+       nm_log_dbg (LOGD_IP6, "(%s): syncing with netlink (ra_flags 0x%X) (state/target '%s'/'%s')",
+                   device->iface, device->ra_flags,
+                   state_to_string (device->state),
+                   state_to_string (device->target_state));
+
+       check_addresses (device);
+       check_ra_flags (device);
+       check_addrconf_complete (device);
 }
 
 static void
@@ -530,16 +593,51 @@ ref_object (struct nl_object *obj, void *data)
        *out = obj;
 }
 
+static void
+dump_address_change (NMIP6Device *device, struct nlmsghdr *hdr, struct rtnl_addr *rtnladdr)
+{
+       char *event;
+       struct nl_addr *addr;
+       char addr_str[40] = "none";
+
+       event = hdr->nlmsg_type == RTM_NEWADDR ? "new" : "lost";
+       addr = rtnl_addr_get_local (rtnladdr);
+       if (addr)
+               nl_addr2str (addr, addr_str, 40);
+
+       nm_log_dbg (LOGD_IP6, "(%s) %s address: %s", device_get_iface (device), event, addr_str);
+}
+
+static void
+dump_route_change (NMIP6Device *device, struct nlmsghdr *hdr, struct rtnl_route *rtnlroute)
+{
+       char *event;
+       struct nl_addr *dst;
+       char dst_str[40] = "none";
+       struct nl_addr *gateway;
+       char gateway_str[40] = "none";
+
+       event = hdr->nlmsg_type == RTM_NEWROUTE ? "new" : "lost";
+       dst = rtnl_route_get_dst (rtnlroute);
+       gateway = rtnl_route_get_gateway (rtnlroute);
+       if (dst)
+               nl_addr2str (dst, dst_str, 40);
+       if (gateway)
+               nl_addr2str (gateway, gateway_str, 40);
+
+       nm_log_dbg (LOGD_IP6, "(%s) %s route: %s via %s",device_get_iface (device), event, dst_str, gateway_str);
+}
+
 static NMIP6Device *
-process_addr (NMIP6Manager *manager, struct nl_msg *msg)
+process_address_change (NMIP6Manager *manager, struct nl_msg *msg)
 {
        NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
        NMIP6Device *device;
+       struct nlmsghdr *hdr;
        struct rtnl_addr *rtnladdr;
        int old_size;
 
-       nm_log_dbg (LOGD_IP6, "processing netlink new/del address message");
-
+       hdr = nlmsg_hdr (msg);
        rtnladdr = NULL;
        nl_msg_parse (msg, ref_object, &rtnladdr);
        if (!rtnladdr) {
@@ -548,38 +646,34 @@ process_addr (NMIP6Manager *manager, struct nl_msg *msg)
        }
 
        device = nm_ip6_manager_get_device (manager, rtnl_addr_get_ifindex (rtnladdr));
-       if (!device) {
-               nm_log_dbg (LOGD_IP6, "ignoring message for unknown device");
-               return NULL;
-       }
 
        old_size = nl_cache_nitems (priv->addr_cache);
-       nl_cache_include (priv->addr_cache, (struct nl_object *)rtnladdr, NULL);
-       rtnl_addr_put (rtnladdr);
+       nl_cache_include (priv->addr_cache, (struct nl_object *)rtnladdr, NULL, NULL);
 
        /* The kernel will re-notify us of automatically-added addresses
         * every time it gets another router advertisement. We only want
         * to notify higher levels if we actually changed something.
         */
-       if (nl_cache_nitems (priv->addr_cache) == old_size) {
-               nm_log_dbg (LOGD_IP6, "(%s): address cache unchanged, ignoring message",
-                           device->iface);
+       nm_log_dbg (LOGD_IP6, "(%s): address cache size: %d -> %d:",
+                   device_get_iface (device), old_size, nl_cache_nitems (priv->addr_cache));
+       dump_address_change (device, hdr, rtnladdr);
+       rtnl_addr_put (rtnladdr);
+       if (nl_cache_nitems (priv->addr_cache) == old_size)
                return NULL;
-       }
 
        return device;
 }
 
 static NMIP6Device *
-process_route (NMIP6Manager *manager, struct nl_msg *msg)
+process_route_change (NMIP6Manager *manager, struct nl_msg *msg)
 {
        NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
        NMIP6Device *device;
+       struct nlmsghdr *hdr;
        struct rtnl_route *rtnlroute;
        int old_size;
 
-       nm_log_dbg (LOGD_IP6, "processing netlink new/del route message");
-
+       hdr = nlmsg_hdr (msg);
        rtnlroute = NULL;
        nl_msg_parse (msg, ref_object, &rtnlroute);
        if (!rtnlroute) {
@@ -588,47 +682,17 @@ process_route (NMIP6Manager *manager, struct nl_msg *msg)
        }
 
        device = nm_ip6_manager_get_device (manager, rtnl_route_get_oif (rtnlroute));
-       if (!device) {
-               nm_log_dbg (LOGD_IP6, "ignoring message for unknown device");
-               return NULL;
-       }
 
        old_size = nl_cache_nitems (priv->route_cache);
-       nl_cache_include (priv->route_cache, (struct nl_object *)rtnlroute, NULL);
-       rtnl_route_put (rtnlroute);
-
-       /* As above in process_addr */
-       if (nl_cache_nitems (priv->route_cache) == old_size) {
-               nm_log_dbg (LOGD_IP6, "(%s): route cache unchanged, ignoring message",
-                           device->iface);
-               return NULL;
-       }
-
-       return device;
-}
-
-static NMIP6Device *
-process_prefix (NMIP6Manager *manager, struct nl_msg *msg)
-{
-       struct prefixmsg *pmsg;
-       NMIP6Device *device;
-
-       /* We don't care about the prefix itself, but if we receive a
-        * router advertisement telling us to use DHCP, we might not
-        * get any RTM_NEWADDRs or RTM_NEWROUTEs, so this is our only
-        * way to notice immediately that an RA was received.
-        */
-
-       nm_log_dbg (LOGD_IP6, "processing netlink new prefix message");
+       nl_cache_include (priv->route_cache, (struct nl_object *)rtnlroute, NULL, NULL);
 
-       pmsg = (struct prefixmsg *) NLMSG_DATA (nlmsg_hdr (msg));
-       device = nm_ip6_manager_get_device (manager, pmsg->prefix_ifindex);
-
-       if (!device || device->addrconf_complete) {
-               nm_log_dbg (LOGD_IP6, "(%s): ignoring unknown or completed device",
-                           device ? device->iface : "(none)");
+       /* As above in process_address_change */
+       nm_log_dbg (LOGD_IP6, "(%s): route cache size: %d -> %d:",
+                   device_get_iface (device), old_size, nl_cache_nitems (priv->route_cache));
+       dump_route_change (device, hdr, rtnlroute);
+       rtnl_route_put (rtnlroute);
+       if (nl_cache_nitems (priv->route_cache) == old_size)
                return NULL;
-       }
 
        return device;
 }
@@ -684,14 +748,19 @@ process_nduseropt_rdnss (NMIP6Device *device, struct nd_opt_hdr *opt)
         */
        server.expires = ntohl (rdnss_opt->nd_opt_rdnss_lifetime);
        if (server.expires > 0)
-               server.expires += now + 10;
+               if (server.expires < 7200)
+                       server.expires = 7200;
+               server.expires += now;
 
        for (addr = (struct in6_addr *) (rdnss_opt + 1); opt_len >= 2; addr++, opt_len -= 2) {
                char buf[INET6_ADDRSTRLEN + 1];
 
-               if (!inet_ntop (AF_INET6, addr, buf, sizeof (buf)))
-                       strcpy(buf, "[invalid]");
+               if (!inet_ntop (AF_INET6, addr, buf, sizeof (buf))) {
+                       nm_log_warn (LOGD_IP6, "(%s): received invalid RA-provided nameserver", device->iface);
+                       continue;
+               }
 
+               /* Update the cached timeout if we already saw this server */
                for (i = 0; i < device->rdnss_servers->len; i++) {
                        cur_server = &(g_array_index (device->rdnss_servers, NMIP6RDNSS, i));
 
@@ -812,7 +881,9 @@ process_nduseropt_dnssl (NMIP6Device *device, struct nd_opt_hdr *opt)
         */
        domain.expires = ntohl (dnssl_opt->nd_opt_dnssl_lifetime);
        if (domain.expires > 0)
-               domain.expires += now + 10;
+               if (domain.expires < 7200)
+                       domain.expires = 7200;
+               domain.expires += now;
 
        while (opt_len) {
                const char *domain_str;
@@ -834,6 +905,7 @@ process_nduseropt_dnssl (NMIP6Device *device, struct nd_opt_hdr *opt)
                if (domain_str[0] == '\0')
                        continue;
 
+               /* Update cached domain information if we've seen this domain before */
                for (i = 0; i < device->dnssl_domains->len; i++) {
                        cur_domain = &(g_array_index (device->dnssl_domains, NMIP6DNSSL, i));
 
@@ -904,6 +976,13 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg)
 
        ndmsg = (struct nduseroptmsg *) NLMSG_DATA (nlmsg_hdr (msg));
 
+       if (!nlmsg_valid_hdr (nlmsg_hdr (msg), sizeof (*ndmsg)) ||
+           nlmsg_datalen (nlmsg_hdr (msg)) <
+               (ndmsg->nduseropt_opts_len + sizeof (*ndmsg))) {
+               nm_log_dbg (LOGD_IP6, "ignoring invalid nduseropt message");
+               return NULL;
+       }
+
        if (ndmsg->nduseropt_family != AF_INET6 ||
                ndmsg->nduseropt_icmp_type != ND_ROUTER_ADVERT ||
                ndmsg->nduseropt_icmp_code != 0) {
@@ -953,6 +1032,28 @@ static struct nla_policy link_prot_policy[IFLA_INET6_MAX + 1] = {
        [IFLA_INET6_FLAGS]      = { .type = NLA_U32 },
 };
 
+static char *
+ra_flags_to_string (guint32 ra_flags)
+{
+       GString *s = g_string_sized_new (20);
+
+       g_string_append (s, " (");
+       if (ra_flags & IF_RS_SENT)
+               g_string_append_c (s, 'S');
+
+       if (ra_flags & IF_RA_RCVD)
+               g_string_append_c (s, 'R');
+
+       if (ra_flags & IF_RA_OTHERCONF)
+               g_string_append_c (s, 'O');
+
+       if (ra_flags & IF_RA_MANAGED)
+               g_string_append_c (s, 'M');
+
+       g_string_append_c (s, ')');
+       return g_string_free (s, FALSE);
+}
+
 static NMIP6Device *
 process_newlink (NMIP6Manager *manager, struct nl_msg *msg)
 {
@@ -962,6 +1063,20 @@ process_newlink (NMIP6Manager *manager, struct nl_msg *msg)
        struct nlattr *tb[IFLA_MAX + 1];
        struct nlattr *pi[IFLA_INET6_MAX + 1];
        int err;
+       char *flags_str = NULL;
+
+       /* FIXME: we have to do this manually for now since libnl doesn't yet
+        * support the IFLA_PROTINFO attribute of NEWLINK messages.  When it does,
+        * we can get rid of this function and just grab IFLA_PROTINFO from
+        * nm_ip6_device_sync_from_netlink(), then get the IFLA_INET6_FLAGS out of
+        * the PROTINFO.
+        */
+       err = nlmsg_parse (hdr, sizeof (*ifi), tb, IFLA_MAX, link_policy);
+       if (err < 0) {
+               nm_log_dbg (LOGD_IP6, "ignoring invalid newlink netlink message "
+                                     "while parsing PROTINFO attribute");
+               return NULL;
+       }
 
        ifi = nlmsg_data (hdr);
        if (ifi->ifi_family != AF_INET6) {
@@ -976,18 +1091,6 @@ process_newlink (NMIP6Manager *manager, struct nl_msg *msg)
                return NULL;
        }
 
-       /* FIXME: we have to do this manually for now since libnl doesn't yet
-        * support the IFLA_PROTINFO attribute of NEWLINK messages.  When it does,
-        * we can get rid of this function and just grab IFLA_PROTINFO from
-        * nm_ip6_device_sync_from_netlink(), then get the IFLA_INET6_FLAGS out of
-        * the PROTINFO.
-        */
-
-       err = nlmsg_parse (hdr, sizeof (*ifi), tb, IFLA_MAX, link_policy);
-       if (err < 0) {
-               nm_log_dbg (LOGD_IP6, "(%s): error parsing PROTINFO attribute", device->iface);
-               return NULL;
-       }
        if (!tb[IFLA_PROTINFO]) {
                nm_log_dbg (LOGD_IP6, "(%s): message had no PROTINFO attribute", device->iface);
                return NULL;
@@ -1004,7 +1107,12 @@ process_newlink (NMIP6Manager *manager, struct nl_msg *msg)
        }
 
        device->ra_flags = nla_get_u32 (pi[IFLA_INET6_FLAGS]);
-       nm_log_dbg (LOGD_IP6, "(%s): got IPv6 flags 0x%X", device->iface, device->ra_flags);
+
+       if (nm_logging_level_enabled (LOGL_DEBUG))
+               flags_str = ra_flags_to_string (device->ra_flags);
+       nm_log_dbg (LOGD_IP6, "(%s): got IPv6 flags 0x%X%s",
+                   device->iface, device->ra_flags, flags_str ? flags_str : "");
+       g_free (flags_str);
 
        return device;
 }
@@ -1015,39 +1123,30 @@ netlink_notification (NMNetlinkMonitor *monitor, struct nl_msg *msg, gpointer us
        NMIP6Manager *manager = (NMIP6Manager *) user_data;
        NMIP6Device *device;
        struct nlmsghdr *hdr;
-       gboolean config_changed = FALSE;
 
        hdr = nlmsg_hdr (msg);
-       nm_log_dbg (LOGD_HW, "netlink notificate type %d", hdr->nlmsg_type);
+       nm_log_dbg (LOGD_HW, "netlink event type %d", hdr->nlmsg_type);
        switch (hdr->nlmsg_type) {
        case RTM_NEWADDR:
        case RTM_DELADDR:
-               device = process_addr (manager, msg);
-               config_changed = TRUE;
+               device = process_address_change (manager, msg);
                break;
        case RTM_NEWROUTE:
        case RTM_DELROUTE:
-               device = process_route (manager, msg);
-               config_changed = TRUE;
-               break;
-       case RTM_NEWPREFIX:
-               device = process_prefix (manager, msg);
+               device = process_route_change (manager, msg);
                break;
        case RTM_NEWNDUSEROPT:
                device = process_nduseropt (manager, msg);
-               config_changed = TRUE;
                break;
        case RTM_NEWLINK:
                device = process_newlink (manager, msg);
-               config_changed = TRUE;
                break;
        default:
                return;
        }
 
        if (device) {
-               nm_log_dbg (LOGD_IP6, "(%s): syncing device with netlink changes", device->iface);
-               nm_ip6_device_sync_from_netlink (device, config_changed);
+               nm_ip6_device_sync_from_netlink (device);
        }
 }
 
@@ -1084,7 +1183,7 @@ nm_ip6_manager_prepare_interface (NMIP6Manager *manager,
                nm_utils_do_sysctl (accept_ra_path, "0\n");
        } else {
                device->target_state = NM_IP6_DEVICE_GOT_ADDRESS;
-               nm_utils_do_sysctl (accept_ra_path, "1\n");
+               nm_utils_do_sysctl (accept_ra_path, "2\n");
        }
 
        return TRUE;
@@ -1143,7 +1242,7 @@ nm_ip6_manager_begin_addrconf (NMIP6Manager *manager, int ifindex)
         * device is already fully configured and schedule the
         * ADDRCONF_COMPLETE signal in that case.
         */
-       nm_ip6_device_sync_from_netlink (device, FALSE);
+       nm_ip6_device_sync_from_netlink (device);
 }
 
 void
@@ -1175,8 +1274,6 @@ nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex)
        struct rtnl_route *rtnlroute;
        struct nl_addr *nldest, *nlgateway;
        struct in6_addr *dest, *gateway;
-       gboolean defgw_set = FALSE;
-       struct in6_addr defgw;
        uint32_t metric;
        NMIP6Route *ip6route;
        int i;
@@ -1227,9 +1324,8 @@ nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex)
 
                if (rtnl_route_get_dst_len (rtnlroute) == 0) {
                        /* Default gateway route; don't add to normal routes but to each address */
-                       if (!defgw_set) {
-                               memcpy (&defgw, gateway, sizeof (defgw));
-                               defgw_set = TRUE;
+                       if (!nm_ip6_config_get_gateway (config)) {
+                               nm_ip6_config_set_gateway (config, gateway);
                        }
                        continue;
                }
@@ -1245,7 +1341,7 @@ nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex)
                nm_ip6_route_set_dest (ip6route, dest);
                nm_ip6_route_set_prefix (ip6route, rtnl_route_get_dst_len (rtnlroute));
                nm_ip6_route_set_next_hop (ip6route, gateway);
-               metric = rtnl_route_get_metric (rtnlroute, 1);
+               rtnl_route_get_metric(rtnlroute, 1, &metric);
                if (metric != UINT_MAX)
                        nm_ip6_route_set_metric (ip6route, metric);
                nm_ip6_config_take_route (config, ip6route);
@@ -1265,8 +1361,9 @@ nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex)
                nm_ip6_address_set_prefix (ip6addr, rtnl_addr_get_prefixlen (rtnladdr));
                nm_ip6_address_set_address (ip6addr, addr);
                nm_ip6_config_take_address (config, ip6addr);
-               if (defgw_set)
-                       nm_ip6_address_set_gateway (ip6addr, &defgw);
+               gateway = nm_ip6_config_get_gateway (config);
+               if (gateway)
+                       nm_ip6_address_set_gateway (ip6addr, gateway);
        }
 
        /* Add DNS servers */
@@ -1334,6 +1431,7 @@ nm_ip6_manager_init (NMIP6Manager *manager)
        priv->monitor = nm_netlink_monitor_get ();
        nm_netlink_monitor_subscribe (priv->monitor, RTNLGRP_IPV6_IFADDR, NULL);
        nm_netlink_monitor_subscribe (priv->monitor, RTNLGRP_IPV6_PREFIX, NULL);
+       nm_netlink_monitor_subscribe (priv->monitor, RTNLGRP_IPV6_ROUTE, NULL);
        nm_netlink_monitor_subscribe (priv->monitor, RTNLGRP_ND_USEROPT, NULL);
        nm_netlink_monitor_subscribe (priv->monitor, RTNLGRP_LINK, NULL);
 
@@ -1341,8 +1439,10 @@ nm_ip6_manager_init (NMIP6Manager *manager)
                                             G_CALLBACK (netlink_notification), manager);
 
        priv->nlh = nm_netlink_get_default_handle ();
-       priv->addr_cache = rtnl_addr_alloc_cache (priv->nlh);
-       priv->route_cache = rtnl_route_alloc_cache (priv->nlh);
+       rtnl_addr_alloc_cache (priv->nlh, &priv->addr_cache);
+       g_warn_if_fail (priv->addr_cache != NULL);
+       rtnl_route_alloc_cache (priv->nlh, NETLINK_ROUTE, NL_AUTO_PROVIDE, &priv->route_cache);
+       g_warn_if_fail (priv->route_cache != NULL);
 }
 
 static void