ip6: pad RDNSS and DNSSL lifetimes to a minimum lifetime value (rh #60055)
[NetworkManager.git] / src / ip6-manager / nm-ip6-manager.c
index 333638a..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;
@@ -76,6 +77,11 @@ typedef struct {
        time_t expires;
 } NMIP6RDNSS;
 
+typedef struct {
+       char domain[256];
+       time_t expires;
+} NMIP6DNSSL;
+
 /******************************************************************/
 
 typedef struct {
@@ -83,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;
@@ -97,12 +107,23 @@ typedef struct {
        GArray *rdnss_servers;
        guint rdnss_timeout_id;
 
+       GArray *dnssl_domains;
+       guint dnssl_timeout_id;
+
        guint ip6flags_poll_id;
 
        guint32 ra_flags;
 } 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);
@@ -115,13 +136,18 @@ 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);
        if (device->rdnss_timeout_id)
                g_source_remove (device->rdnss_timeout_id);
+       if (device->dnssl_domains)
+               g_array_free (device->dnssl_domains, TRUE);
+       if (device->dnssl_timeout_id)
+               g_source_remove (device->dnssl_timeout_id);
        if (device->ip6flags_poll_id)
                g_source_remove (device->ip6flags_poll_id);
 
@@ -144,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);
@@ -155,15 +181,18 @@ nm_ip6_device_new (NMIP6Manager *manager, int ifindex)
 
        device->rdnss_servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS));
 
+       device->dnssl_domains = g_array_new (FALSE, FALSE, sizeof (NMIP6DNSSL));
+
        g_hash_table_replace (priv->devices, GINT_TO_POINTER (device->ifindex), device);
 
        /* and the original value of IPv6 enable/disable */
        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;
 
@@ -184,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 {
@@ -247,11 +314,12 @@ static gboolean
 rdnss_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 RDNSS information expired", device->iface);
 
        set_rdnss_timeout (device);
+       clear_config_changed (device);
        emit_config_changed (&info);
        return FALSE;
 }
@@ -285,7 +353,7 @@ set_rdnss_timeout (NMIP6Device *device)
                                nm_log_dbg (LOGD_IP6, "(%s): removing expired RA-provided nameserver %s",
                                            device->iface, buf);
                        }
-                       g_array_remove_index_fast (device->rdnss_servers, i--);
+                       g_array_remove_index (device->rdnss_servers, i--);
                        continue;
                }
 
@@ -294,9 +362,65 @@ 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);
+       }
+}
+
+static void set_dnssl_timeout (NMIP6Device *device);
+
+static gboolean
+dnssl_expired (gpointer user_data)
+{
+       NMIP6Device *device = user_data;
+       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;
+}
+
+static void
+set_dnssl_timeout (NMIP6Device *device)
+{
+       time_t expires = 0, now = time (NULL);
+       NMIP6DNSSL *dnssl;
+       int i;
+
+       if (device->dnssl_timeout_id) {
+               g_source_remove (device->dnssl_timeout_id);
+               device->dnssl_timeout_id = 0;
+       }
+
+       /* Find the soonest expiration time. */
+       for (i = 0; i < device->dnssl_domains->len; i++) {
+               dnssl = &g_array_index (device->dnssl_domains, NMIP6DNSSL, i);
+               if (dnssl->expires == 0)
+                       continue;
+
+               /* If the entry has already expired, remove it; the "+ 1" is
+                * because g_timeout_add_seconds() might fudge the timing a
+                * bit.
+                */
+               if (dnssl->expires <= now + 1) {
+                       nm_log_dbg (LOGD_IP6, "(%s): removing expired RA-provided domain %s",
+                                   device->iface, dnssl->domain);
+                       g_array_remove_index (device->dnssl_domains, i--);
+                       continue;
+               }
+
+               if (!expires || dnssl->expires < expires)
+                       expires = dnssl->expires;
+       }
+
+       if (expires) {
+               device->dnssl_timeout_id = g_timeout_add_seconds (MIN (expires - now, G_MAXUINT32 - 1),
+                                                                 dnssl_expired,
+                                                                 device);
        }
 }
 
@@ -312,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);
@@ -364,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;
                }
        }
 
@@ -383,26 +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 checked (state %s)",
+                   device->iface, state_to_string (device->state));
+}
 
-       nm_log_dbg (LOGD_IP6, "(%s): addresses synced (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) {
-               if (   (device->ra_flags & IF_RA_RCVD)
-                   && (device->state < NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT))
-                       device->state = NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT;
+       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_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
@@ -410,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.
                         */
@@ -419,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;
 
@@ -436,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
@@ -460,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) {
@@ -478,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) {
@@ -518,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;
-       }
+       nl_cache_include (priv->route_cache, (struct nl_object *)rtnlroute, NULL, 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");
-
-       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;
 }
@@ -568,13 +702,266 @@ process_prefix (NMIP6Manager *manager, struct nl_msg *msg)
  */
 
 #define ND_OPT_RDNSS 25
+#define ND_OPT_DNSSL 31
+
 struct nd_opt_rdnss {
        uint8_t nd_opt_rdnss_type;
        uint8_t nd_opt_rdnss_len;
        uint16_t nd_opt_rdnss_reserved1;
        uint32_t nd_opt_rdnss_lifetime;
        /* followed by one or more IPv6 addresses */
-};
+} __attribute__ ((packed));
+
+struct nd_opt_dnssl {
+       uint8_t nd_opt_dnssl_type;
+       uint8_t nd_opt_dnssl_len;
+       uint16_t nd_opt_dnssl_reserved1;
+       uint32_t nd_opt_dnssl_lifetime;
+       /* followed by one or more suffixes */
+} __attribute__ ((packed));
+
+static gboolean
+process_nduseropt_rdnss (NMIP6Device *device, struct nd_opt_hdr *opt)
+{
+       size_t opt_len;
+       struct nd_opt_rdnss *rdnss_opt;
+       time_t now = time (NULL);
+       struct in6_addr *addr;
+       GArray *new_servers;
+       NMIP6RDNSS server, *cur_server;
+       gboolean changed = FALSE;
+       guint i;
+
+       opt_len = opt->nd_opt_len;
+
+       if (opt_len < 3 || (opt_len & 1) == 0)
+               return FALSE;
+
+       rdnss_opt = (struct nd_opt_rdnss *) opt;
+
+       new_servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS));
+
+       /* Pad the DNS server expiry somewhat to give a bit of slack in cases
+        * where one RA gets lost or something (which can happen on unreliable
+        * links like WiFi where certain types of frames are not retransmitted).
+        * Note that 0 has special meaning and is therefore not adjusted.
+        */
+       server.expires = ntohl (rdnss_opt->nd_opt_rdnss_lifetime);
+       if (server.expires > 0)
+               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))) {
+                       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));
+
+                       if (!IN6_ARE_ADDR_EQUAL (addr, &cur_server->addr))
+                               continue;
+
+                       cur_server->expires = server.expires;
+
+                       if (server.expires > 0) {
+                               nm_log_dbg (LOGD_IP6, "(%s): refreshing RA-provided nameserver %s (expires in %ld seconds)",
+                                           device->iface, buf,
+                                           server.expires - now);
+                               break;
+                       }
+
+                       nm_log_dbg (LOGD_IP6, "(%s): removing RA-provided nameserver %s on router request",
+                                   device->iface, buf);
+
+                       g_array_remove_index (device->rdnss_servers, i);
+                       changed = TRUE;
+                       break;
+               }
+
+               if (server.expires == 0)
+                       continue;
+               if (i < device->rdnss_servers->len)
+                       continue;
+
+               nm_log_dbg (LOGD_IP6, "(%s): found RA-provided nameserver %s (expires in %ld seconds)",
+                           device->iface, buf, server.expires - now);
+
+               server.addr = *addr;
+               g_array_append_val (new_servers, server);
+       }
+
+       /* New servers must be added in the order they are listed in the
+        * RA option and before any existing servers.
+        *
+        * Note: This is the place to remove servers if we want to cap the
+        *       number of resolvers. The RFC states that the one to expire
+        *       first of the existing servers should be removed.
+        */
+       if (new_servers->len) {
+               g_array_prepend_vals (device->rdnss_servers,
+                                     new_servers->data, new_servers->len);
+               changed = TRUE;
+       }
+
+       g_array_free (new_servers, TRUE);
+
+       /* Timeouts may have changed even if IPs didn't */
+       set_rdnss_timeout (device);
+
+       return changed;
+}
+
+static const char *
+parse_dnssl_domain (const unsigned char *buffer, size_t maxlen)
+{
+       static char domain[256];
+       size_t label_len;
+
+       domain[0] = '\0';
+
+       while (maxlen > 0) {
+               label_len = *buffer;
+               buffer++;
+               maxlen--;
+
+               if (label_len == 0)
+                       return domain;
+
+               if (label_len > maxlen)
+                       return NULL;
+               if ((sizeof (domain) - strlen (domain)) < (label_len + 2))
+                       return NULL;
+
+               if (domain[0] != '\0')
+                       strcat (domain, ".");
+               strncat (domain, (const char *)buffer, label_len);
+               buffer += label_len;
+               maxlen -= label_len;
+       }
+
+       return NULL;
+}
+
+static gboolean
+process_nduseropt_dnssl (NMIP6Device *device, struct nd_opt_hdr *opt)
+{
+       size_t opt_len;
+       struct nd_opt_dnssl *dnssl_opt;
+       unsigned char *opt_ptr;
+       time_t now = time (NULL);
+       GArray *new_domains;
+       NMIP6DNSSL domain, *cur_domain;
+       gboolean changed;
+       guint i;
+
+       opt_len = opt->nd_opt_len;
+
+       if (opt_len < 2)
+               return FALSE;
+
+       dnssl_opt = (struct nd_opt_dnssl *) opt;
+
+       opt_ptr = (unsigned char *)(dnssl_opt + 1);
+       opt_len = (opt_len - 1) * 8; /* prefer bytes for later handling */
+
+       new_domains = g_array_new (FALSE, FALSE, sizeof (NMIP6DNSSL));
+
+       changed = FALSE;
+
+       /* Pad the DNS server expiry somewhat to give a bit of slack in cases
+        * where one RA gets lost or something (which can happen on unreliable
+        * links like wifi where certain types of frames are not retransmitted).
+        * Note that 0 has special meaning and is therefore not adjusted.
+        */
+       domain.expires = ntohl (dnssl_opt->nd_opt_dnssl_lifetime);
+       if (domain.expires > 0)
+               if (domain.expires < 7200)
+                       domain.expires = 7200;
+               domain.expires += now;
+
+       while (opt_len) {
+               const char *domain_str;
+
+               domain_str = parse_dnssl_domain (opt_ptr, opt_len);
+               if (domain_str == NULL) {
+                       nm_log_dbg (LOGD_IP6, "(%s): invalid DNSSL option, parsing aborted",
+                                   device->iface);
+                       break;
+               }
+
+               /* The DNSSL encoding of domains happen to occupy the same size
+                * as the length of the resulting string, including terminating
+                * null. */
+               opt_ptr += strlen (domain_str) + 1;
+               opt_len -= strlen (domain_str) + 1;
+
+               /* Ignore empty domains. They're probably just padding... */
+               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));
+
+                       if (strcmp (domain_str, cur_domain->domain) != 0)
+                               continue;
+
+                       cur_domain->expires = domain.expires;
+
+                       if (domain.expires > 0) {
+                               nm_log_dbg (LOGD_IP6, "(%s): refreshing RA-provided domain %s (expires in %ld seconds)",
+                                           device->iface, domain_str,
+                                           domain.expires - now);
+                               break;
+                       }
+
+                       nm_log_dbg (LOGD_IP6, "(%s): removing RA-provided domain %s on router request",
+                                   device->iface, domain_str);
+
+                       g_array_remove_index (device->dnssl_domains, i);
+                       changed = TRUE;
+                       break;
+               }
+
+               if (domain.expires == 0)
+                       continue;
+               if (i < device->dnssl_domains->len)
+                       continue;
+
+               nm_log_dbg (LOGD_IP6, "(%s): found RA-provided domain %s (expires in %ld seconds)",
+                           device->iface, domain_str, domain.expires - now);
+
+               g_assert (strlen (domain_str) < sizeof (domain.domain));
+               strcpy (domain.domain, domain_str);
+               g_array_append_val (new_domains, domain);
+       }
+
+       /* New domains must be added in the order they are listed in the
+        * RA option and before any existing domains.
+        *
+        * Note: This is the place to remove domains if we want to cap the
+        *       number of domains. The RFC states that the one to expire
+        *       first of the existing domains should be removed.
+        */
+       if (new_domains->len) {
+               g_array_prepend_vals (device->dnssl_domains,
+                                     new_domains->data, new_domains->len);
+               changed = TRUE;
+       }
+
+       g_array_free (new_domains, TRUE);
+
+       /* Timeouts may have changed even if domains didn't */
+       set_dnssl_timeout (device);
+
+       return changed;
+}
 
 static NMIP6Device *
 process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg)
@@ -582,18 +969,20 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg)
        NMIP6Device *device;
        struct nduseroptmsg *ndmsg;
        struct nd_opt_hdr *opt;
-       guint opts_len, i;
-       time_t now = time (NULL);
-       struct nd_opt_rdnss *rdnss_opt;
-       struct in6_addr *addr;
-       GArray *servers;
-       NMIP6RDNSS server, *sa, *sb;
-       gboolean changed;
+       guint opts_len;
+       gboolean changed = FALSE;
 
        nm_log_dbg (LOGD_IP6, "processing netlink nduseropt message");
 
        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) {
@@ -607,8 +996,6 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg)
                return NULL;
        }
 
-       servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS));
-
        opt = (struct nd_opt_hdr *) (ndmsg + 1);
        opts_len = ndmsg->nduseropt_opts_len;
 
@@ -618,61 +1005,19 @@ process_nduseropt (NMIP6Manager *manager, struct nl_msg *msg)
                if (nd_opt_len == 0 || opts_len < (nd_opt_len << 3))
                        break;
 
-               if (opt->nd_opt_type != ND_OPT_RDNSS)
-                       goto next;
-
-               if (nd_opt_len < 3 || (nd_opt_len & 1) == 0)
-                       goto next;
-
-               rdnss_opt = (struct nd_opt_rdnss *) opt;
-
-               server.expires = now + ntohl (rdnss_opt->nd_opt_rdnss_lifetime);
-               for (addr = (struct in6_addr *) (rdnss_opt + 1); nd_opt_len >= 2; addr++, nd_opt_len -= 2) {
-                       char buf[INET6_ADDRSTRLEN + 1];
-
-                       if (inet_ntop (AF_INET6, addr, buf, sizeof (buf))) {
-                               nm_log_dbg (LOGD_IP6, "(%s): found RA-provided nameserver %s (expires in %d seconds)",
-                                           device->iface, buf,
-                                           ntohl (rdnss_opt->nd_opt_rdnss_lifetime));
-                       }
-
-                       server.addr = *addr;
-                       g_array_append_val (servers, server);
+               switch (opt->nd_opt_type) {
+               case ND_OPT_RDNSS:
+                       changed = process_nduseropt_rdnss (device, opt);
+                       break;
+               case ND_OPT_DNSSL:
+                       changed = process_nduseropt_dnssl (device, opt);
+                       break;
                }
 
-       next:
                opts_len -= opt->nd_opt_len << 3;
                opt = (struct nd_opt_hdr *) ((uint8_t *) opt + (opt->nd_opt_len << 3));
        }
 
-       /* See if anything (other than expiration time) changed */
-       if (servers->len != device->rdnss_servers->len)
-               changed = TRUE;
-       else {
-               for (i = 0; i < servers->len; i++) {
-                       sa = &(g_array_index (servers, NMIP6RDNSS, i));
-                       sb = &(g_array_index (device->rdnss_servers, NMIP6RDNSS, i));
-                       if (memcmp (&sa->addr, &sb->addr, sizeof (struct in6_addr)) != 0) {
-                               changed = TRUE;
-                               break;
-                       }
-               }
-               changed = FALSE;
-       }
-
-       if (changed) {
-               nm_log_dbg (LOGD_IP6, "(%s): RA-provided nameservers changed", device->iface);
-       }
-
-       /* Always copy in new servers (even if unchanged) to get their updated
-        * expiration times.
-        */
-       g_array_free (device->rdnss_servers, TRUE);
-       device->rdnss_servers = servers;
-
-       /* Timeouts may have changed even if IPs didn't */
-       set_rdnss_timeout (device);
-
        if (changed)
                return device;
        else
@@ -687,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)
 {
@@ -696,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) {
@@ -710,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;
@@ -738,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;
 }
@@ -749,43 +1123,34 @@ 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);
        }
 }
 
-void
+gboolean
 nm_ip6_manager_prepare_interface (NMIP6Manager *manager,
                                   int ifindex,
                                   NMSettingIP6Config *s_ip6,
@@ -795,16 +1160,17 @@ nm_ip6_manager_prepare_interface (NMIP6Manager *manager,
        NMIP6Device *device;
        const char *method = NULL;
 
-       g_return_if_fail (NM_IS_IP6_MANAGER (manager));
-       g_return_if_fail (ifindex > 0);
+       g_return_val_if_fail (NM_IS_IP6_MANAGER (manager), FALSE);
+       g_return_val_if_fail (ifindex > 0, FALSE);
 
        priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
 
        device = nm_ip6_device_new (manager, ifindex);
-       g_return_if_fail (device != NULL);
-       g_return_if_fail (   strchr (device->iface, '/') == NULL
-                         && strcmp (device->iface, "all") != 0
-                         && strcmp (device->iface, "default") != 0);
+       g_return_val_if_fail (device != NULL, FALSE);
+       g_return_val_if_fail (   strchr (device->iface, '/') == NULL
+                             && strcmp (device->iface, "all") != 0
+                             && strcmp (device->iface, "default") != 0,
+                             FALSE);
 
        if (s_ip6)
                method = nm_setting_ip6_config_get_method (s_ip6);
@@ -817,8 +1183,10 @@ 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;
 }
 
 static gboolean
@@ -874,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
@@ -906,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;
@@ -958,25 +1324,24 @@ 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;
                }
 
-               /* Also ignore routes where the destination and gateway are the same,
-                * which apparently get added by the kernel but return -EINVAL when
-                * we try to add them via netlink.
+               /* Also ignore link-local routes where the destination and gateway are
+                * the same, which apparently get added by the kernel but return -EINVAL
+                * when we try to add them via netlink.
                 */
-               if (gateway && !memcmp (dest, gateway, sizeof (struct in6_addr)))
+               if (gateway && IN6_ARE_ADDR_EQUAL (dest, gateway))
                        continue;
 
                ip6route = nm_ip6_route_new ();
                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);
@@ -996,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 */
@@ -1008,6 +1374,14 @@ nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex)
                        nm_ip6_config_add_nameserver (config, &rdnss[i].addr);
        }
 
+       /* Add DNS domains */
+       if (device->dnssl_domains) {
+               NMIP6DNSSL *dnssl = (NMIP6DNSSL *)(device->dnssl_domains->data);
+
+               for (i = 0; i < device->dnssl_domains->len; i++)
+                       nm_ip6_config_add_domain (config, dnssl[i].domain);
+       }
+
        return config;
 }
 
@@ -1057,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);
 
@@ -1064,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