Add NMIP6Manager to handle IPv6 addrconf
authorDan Winship <danw@gnome.org>
Thu, 30 Jul 2009 17:50:42 +0000 (13:50 -0400)
committerDan Winship <danw@gnome.org>
Wed, 26 Aug 2009 18:37:45 +0000 (14:37 -0400)
Automatic IPv6 configuration is handled by the kernel, but to
integrate it properly with NetworkManager, we need to watch what the
kernel does to see whether or not it was successful (so that we can
let the user know if there is no IPv6 router present, for example).
NMIP6Manager takes care of this.

configure.ac
marshallers/nm-marshal.list
src/Makefile.am
src/ip6-manager/Makefile.am [new file with mode: 0644]
src/ip6-manager/nm-ip6-manager.c [new file with mode: 0644]
src/ip6-manager/nm-ip6-manager.h [new file with mode: 0644]
src/ip6-manager/nm-netlink-listener.c [new file with mode: 0644]
src/ip6-manager/nm-netlink-listener.h [new file with mode: 0644]
src/nm-device.c
src/nm-netlink-monitor.c

index 4be9c49..c24dcaf 100644 (file)
@@ -407,6 +407,7 @@ marshallers/Makefile
 src/named-manager/Makefile
 src/vpn-manager/Makefile
 src/dhcp-manager/Makefile
+src/ip6-manager/Makefile
 src/supplicant-manager/Makefile
 src/supplicant-manager/tests/Makefile
 src/ppp-manager/Makefile
index cb166f5..38669dd 100644 (file)
@@ -21,4 +21,5 @@ VOID:POINTER,STRING
 VOID:STRING,BOXED
 BOOLEAN:POINTER,STRING,BOOLEAN,UINT,STRING,STRING
 BOOLEAN:VOID
+VOID:STRING,BOOLEAN
 
index 8d936e6..fb337a6 100644 (file)
@@ -2,6 +2,7 @@ SUBDIRS= \
        named-manager \
        vpn-manager \
        dhcp-manager \
+       ip6-manager \
        supplicant-manager \
        ppp-manager \
        backends \
@@ -18,6 +19,7 @@ INCLUDES = -I${top_srcdir}                   \
            -I${top_srcdir}/src/named-manager \
            -I${top_srcdir}/src/vpn-manager   \
            -I${top_srcdir}/src/dhcp-manager  \
+           -I${top_srcdir}/src/ip6-manager  \
            -I${top_srcdir}/src/supplicant-manager  \
            -I${top_srcdir}/src/dnsmasq-manager  \
            -I${top_srcdir}/src/modem-manager  \
@@ -182,6 +184,7 @@ NetworkManager_LDADD = \
        ./named-manager/libnamed-manager.la \
        ./vpn-manager/libvpn-manager.la \
        ./dhcp-manager/libdhcp-manager.la \
+       ./ip6-manager/libip6-manager.la \
        ./supplicant-manager/libsupplicant-manager.la \
        ./dnsmasq-manager/libdnsmasq-manager.la \
        ./ppp-manager/libppp-manager.la \
diff --git a/src/ip6-manager/Makefile.am b/src/ip6-manager/Makefile.am
new file mode 100644 (file)
index 0000000..50db421
--- /dev/null
@@ -0,0 +1,26 @@
+INCLUDES = \
+       -I${top_srcdir} \
+       -I${top_srcdir}/include \
+       -I${top_builddir}/marshallers \
+       -I${top_srcdir}/libnm-util \
+       -I${top_srcdir}/src \
+       -I${top_srcdir}/src/named-manager
+
+noinst_LTLIBRARIES = libip6-manager.la
+
+libip6_manager_la_SOURCES = \
+       nm-ip6-manager.c \
+       nm-ip6-manager.h \
+       nm-netlink-listener.c \
+       nm-netlink-listener.h
+
+libip6_manager_la_CPPFLAGS = \
+       $(DBUS_CFLAGS) \
+       $(GLIB_CFLAGS) \
+       $(HAL_CFLAGS) \
+       -DG_DISABLE_DEPRECATED
+
+libip6_manager_la_LIBADD = \
+       $(DBUS_LIBS) \
+       $(GLIB_LIBS) \
+       $(top_builddir)/marshallers/libmarshallers.la
diff --git a/src/ip6-manager/nm-ip6-manager.c b/src/ip6-manager/nm-ip6-manager.c
new file mode 100644 (file)
index 0000000..7f280ce
--- /dev/null
@@ -0,0 +1,809 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-ip6-manager.c - Handle IPv6 address configuration for NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <netinet/icmp6.h>
+
+#include <netlink/route/rtnl.h>
+#include <netlink/route/route.h>
+
+#include "nm-ip6-manager.h"
+#include "nm-netlink-listener.h"
+#include "NetworkManagerUtils.h"
+#include "nm-marshal.h"
+#include "nm-utils.h"
+
+/* Pre-DHCP addrconf timeout, in seconds */
+#define NM_IP6_TIMEOUT 10
+
+/* FIXME? Stolen from the kernel sources */
+#define IF_RA_OTHERCONF 0x80
+#define IF_RA_MANAGED   0x40
+#define IF_RA_RCVD      0x20
+#define IF_RS_SENT      0x10
+
+typedef struct {
+       NMNetlinkListener *netlink;
+       GHashTable *devices_by_iface, *devices_by_index;
+
+       struct nl_handle *nlh;
+       struct nl_cache *addr_cache, *route_cache;
+} NMIP6ManagerPrivate;
+
+#define NM_IP6_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IP6_MANAGER, NMIP6ManagerPrivate))
+
+typedef enum {
+       NM_IP6_DEVICE_UNCONFIGURED,
+       NM_IP6_DEVICE_GOT_LINK_LOCAL,
+       NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT,
+       NM_IP6_DEVICE_GOT_ADDRESS,
+       NM_IP6_DEVICE_WAITING_FOR_DHCP,
+       NM_IP6_DEVICE_GOT_DHCP,
+       NM_IP6_DEVICE_TIMED_OUT
+} NMIP6DeviceState;
+
+typedef struct {
+       struct in6_addr addr;
+       time_t expires;
+} NMIP6RDNSS;
+
+typedef struct {
+       NMIP6Manager *manager;
+       char *iface;
+       int index;
+
+       guint finish_addrconf_id;
+       guint config_changed_id;
+
+       NMIP6DeviceState state;
+       NMIP6DeviceState target_state;
+       gboolean want_signal;
+
+       GArray *rdnss_servers;
+       guint rdnss_timeout_id;
+} NMIP6Device;
+
+G_DEFINE_TYPE (NMIP6Manager, nm_ip6_manager, G_TYPE_OBJECT)
+
+enum {
+       ADDRCONF_COMPLETE,
+       CONFIG_CHANGED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static NMIP6Manager *nm_ip6_manager_new (void);
+
+static void netlink_notification (NMNetlinkListener *listener, struct nl_msg *msg, gpointer user_data);
+
+static void nm_ip6_device_destroy (NMIP6Device *device);
+
+NMIP6Manager *
+nm_ip6_manager_get (void)
+{
+       static NMIP6Manager *singleton = NULL;
+
+       if (!singleton)
+               singleton = nm_ip6_manager_new ();
+       g_assert (singleton);
+
+       return g_object_ref (singleton);
+}
+
+static void
+nm_ip6_manager_init (NMIP6Manager *manager)
+{
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       priv->devices_by_iface = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                                                                       NULL,
+                                                                                                       (GDestroyNotify) nm_ip6_device_destroy);
+       priv->devices_by_index = g_hash_table_new (NULL, NULL);
+
+       priv->netlink = nm_netlink_listener_get ();
+       g_signal_connect (priv->netlink, "notification",
+                                         G_CALLBACK (netlink_notification), manager);
+       nm_netlink_listener_subscribe (priv->netlink, RTNLGRP_IPV6_IFADDR, NULL);
+       nm_netlink_listener_subscribe (priv->netlink, RTNLGRP_IPV6_PREFIX, NULL);
+       nm_netlink_listener_subscribe (priv->netlink, RTNLGRP_ND_USEROPT, NULL);
+
+       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);
+}
+
+static void
+finalize (GObject *object)
+{
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (object);
+
+       g_hash_table_destroy (priv->devices_by_iface);
+       g_hash_table_destroy (priv->devices_by_index);
+       g_object_unref (priv->netlink);
+       nl_cache_free (priv->addr_cache);
+       nl_cache_free (priv->route_cache);
+
+       G_OBJECT_CLASS (nm_ip6_manager_parent_class)->finalize (object);
+}
+
+static void
+nm_ip6_manager_class_init (NMIP6ManagerClass *manager_class)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
+
+       g_type_class_add_private (manager_class, sizeof (NMIP6ManagerPrivate));
+
+       /* virtual methods */
+       object_class->finalize = finalize;
+
+       /* signals */
+       signals[ADDRCONF_COMPLETE] =
+               g_signal_new ("addrconf-complete",
+                                         G_OBJECT_CLASS_TYPE (object_class),
+                                         G_SIGNAL_RUN_FIRST,
+                                         G_STRUCT_OFFSET (NMIP6ManagerClass, addrconf_complete),
+                                         NULL, NULL,
+                                         _nm_marshal_VOID__STRING_BOOLEAN,
+                                         G_TYPE_NONE, 2,
+                                         G_TYPE_STRING,
+                                         G_TYPE_BOOLEAN);
+
+       signals[CONFIG_CHANGED] =
+               g_signal_new ("config-changed",
+                                         G_OBJECT_CLASS_TYPE (object_class),
+                                         G_SIGNAL_RUN_FIRST,
+                                         G_STRUCT_OFFSET (NMIP6ManagerClass, config_changed),
+                                         NULL, NULL,
+                                         g_cclosure_marshal_VOID__STRING,
+                                         G_TYPE_NONE, 1,
+                                         G_TYPE_STRING);
+}
+
+static void
+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);
+       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);
+
+       g_slice_free (NMIP6Device, device);
+}
+
+static NMIP6Manager *
+nm_ip6_manager_new (void)
+{
+       NMIP6Manager *manager;
+       NMIP6ManagerPrivate *priv;
+
+       manager = g_object_new (NM_TYPE_IP6_MANAGER, NULL);
+       priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       if (!priv->devices_by_iface || !priv->devices_by_index) {
+               nm_warning ("Error: not enough memory to initialize IP6 manager tables");
+               g_object_unref (manager);
+               manager = NULL;
+       }
+
+       return manager;
+}
+
+static NMIP6Device *
+nm_ip6_manager_get_device (NMIP6Manager *manager, int ifindex)
+{
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       return g_hash_table_lookup (priv->devices_by_index,
+                                                               GINT_TO_POINTER (ifindex));
+}
+
+static gboolean
+finish_addrconf (gpointer user_data)
+{
+       NMIP6Device *device = user_data;
+       NMIP6Manager *manager = device->manager;
+       char *iface_copy;
+
+       device->finish_addrconf_id = 0;
+       device->want_signal = FALSE;
+
+       if (device->state >= device->target_state) {
+               g_signal_emit (manager, signals[ADDRCONF_COMPLETE], 0,
+                                          device->iface, TRUE);
+       } else {
+               nm_info ("Device '%s' IP6 addrconf timed out or failed.",
+                                device->iface);
+
+               iface_copy = g_strdup (device->iface);
+
+               nm_ip6_manager_cancel_addrconf (manager, device->iface);
+               g_signal_emit (manager, signals[ADDRCONF_COMPLETE], 0,
+                                          iface_copy, FALSE);
+
+               g_free (iface_copy);
+       }
+
+       return FALSE;
+}
+
+static gboolean
+emit_config_changed (gpointer user_data)
+{
+       NMIP6Device *device = user_data;
+       NMIP6Manager *manager = device->manager;
+
+       device->config_changed_id = 0;
+       g_signal_emit (manager, signals[CONFIG_CHANGED], 0, device->iface);
+       return FALSE;
+}
+
+static void set_rdnss_timeout (NMIP6Device *device);
+
+static gboolean
+rdnss_expired (gpointer user_data)
+{
+       NMIP6Device *device = user_data;
+
+       set_rdnss_timeout (device);
+       emit_config_changed (device);
+       return FALSE;
+}
+
+static void
+set_rdnss_timeout (NMIP6Device *device)
+{
+       time_t expires = 0, now = time (NULL);
+       NMIP6RDNSS *rdnss;
+       int i;
+
+       if (device->rdnss_timeout_id) {
+               g_source_remove (device->rdnss_timeout_id);
+               device->rdnss_timeout_id = 0;
+       }
+
+       /* Find the soonest expiration time. */
+       for (i = 0; i < device->rdnss_servers->len; i++) {
+               rdnss = &g_array_index (device->rdnss_servers, NMIP6RDNSS, i);
+               if (rdnss->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 (rdnss->expires <= now + 1) {
+                       g_array_remove_index_fast (device->rdnss_servers, i--);
+                       continue;
+               }
+
+               if (!expires || rdnss->expires < expires)
+                       expires = rdnss->expires;
+       }
+
+       if (expires) {
+               device->rdnss_timeout_id = g_timeout_add_seconds (expires - now,
+                                                                                                                 rdnss_expired,
+                                                                                                                 device);
+       }
+}
+
+static void
+nm_ip6_device_sync_from_netlink (NMIP6Device *device, gboolean config_changed)
+{
+       NMIP6Manager *manager = device->manager;
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+       struct rtnl_addr *rtnladdr;
+       struct nl_addr *nladdr;
+       struct in6_addr *addr;
+       struct rtnl_link *link;
+       guint flags;
+
+       for (rtnladdr = (struct rtnl_addr *)nl_cache_get_first (priv->addr_cache);
+                rtnladdr;
+                rtnladdr = (struct rtnl_addr *)nl_cache_get_next ((struct nl_object *)rtnladdr)) {
+               if (rtnl_addr_get_ifindex (rtnladdr) != device->index)
+                       continue;
+
+               nladdr = rtnl_addr_get_local (rtnladdr);
+               if (!nladdr || nl_addr_get_family (nladdr) != AF_INET6)
+                       continue;
+
+               addr = nl_addr_get_binary_addr (nladdr);
+               if (IN6_IS_ADDR_LINKLOCAL (addr)) {
+                       if (device->state == NM_IP6_DEVICE_UNCONFIGURED)
+                               device->state = NM_IP6_DEVICE_GOT_LINK_LOCAL;
+               } else {
+                       if (device->state < NM_IP6_DEVICE_GOT_ADDRESS)
+                               device->state = NM_IP6_DEVICE_GOT_ADDRESS;
+               }
+       }
+
+       /* Note: we don't want to keep a cache of links, because the
+        * kernel doesn't send notifications when the flags change, so the
+        * cached rtnl_links would have out-of-date flags.
+        */
+       link = nm_netlink_index_to_rtnl_link (device->index);
+       flags = rtnl_link_get_flags (link);
+       rtnl_link_put (link);
+
+       if ((flags & IF_RA_RCVD) && device->state < NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT)
+               device->state = NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT;
+
+//     if (flags & (IF_RA_MANAGED | IF_RA_OTHERCONF))
+//             device->need_dhcp = TRUE;
+
+       if (device->want_signal) {
+               if (device->state >= device->target_state ||
+                       device->state == NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT) {
+                       /* device->finish_addrconf_id may currently be a timeout
+                        * rather than an idle, so we remove the existing source.
+                        */
+                       if (device->finish_addrconf_id)
+                               g_source_remove (device->finish_addrconf_id);
+                       device->finish_addrconf_id = g_idle_add (finish_addrconf,
+                                                                                                        device);
+               }
+       } else if (config_changed) {
+               if (!device->config_changed_id) {
+                       device->config_changed_id = g_idle_add (emit_config_changed,
+                                                                                                       device);
+               }
+       }
+}
+
+static void
+ref_object (struct nl_object *obj, void *data)
+{
+       struct nl_object **out = data;
+
+       nl_object_get (obj);
+       *out = obj;
+}
+
+static NMIP6Device *
+process_addr (NMIP6Manager *manager, struct nl_msg *msg)
+{
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+       NMIP6Device *device;
+       struct rtnl_addr *rtnladdr;
+       int old_size;
+
+       rtnladdr = NULL;
+       nl_msg_parse (msg, ref_object, &rtnladdr);
+       if (!rtnladdr)
+               return NULL;
+
+       device = nm_ip6_manager_get_device (manager, rtnl_addr_get_ifindex (rtnladdr));
+
+       old_size = nl_cache_nitems (priv->addr_cache);
+       nl_cache_include (priv->addr_cache, (struct nl_object *)rtnladdr, NULL);
+       rtnl_addr_put (rtnladdr);
+
+       /* 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)
+               return NULL;
+
+       return device;
+}
+
+static NMIP6Device *
+process_route (NMIP6Manager *manager, struct nl_msg *msg)
+{
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+       NMIP6Device *device;
+       struct rtnl_route *rtnlroute;
+       int old_size;
+
+       rtnlroute = NULL;
+       nl_msg_parse (msg, ref_object, &rtnlroute);
+       if (!rtnlroute)
+               return NULL;
+
+       device = nm_ip6_manager_get_device (manager, rtnl_route_get_oif (rtnlroute));
+
+       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)
+               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.
+        */
+
+       pmsg = (struct prefixmsg *) NLMSG_DATA (nlmsg_hdr (msg));
+       device = nm_ip6_manager_get_device (manager, pmsg->prefix_ifindex);
+
+       if (!device || !device->want_signal)
+               return NULL;
+
+       return device;
+}
+
+/* RDNSS parsing code based on rdnssd, Copyright 2007 Pierre Ynard,
+ * RĂ©mi Denis-Courmont. GPLv2/3
+ */
+
+#define ND_OPT_RDNSS 25
+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 */
+};
+
+static NMIP6Device *
+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;
+
+       ndmsg = (struct nduseroptmsg *) NLMSG_DATA (nlmsg_hdr (msg));
+
+       if (ndmsg->nduseropt_family != AF_INET6 ||
+               ndmsg->nduseropt_icmp_type != ND_ROUTER_ADVERT ||
+               ndmsg->nduseropt_icmp_code != 0)
+               return NULL;
+
+       device = nm_ip6_manager_get_device (manager, ndmsg->nduseropt_ifindex);
+       if (!device)
+               return NULL;
+
+       servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS));
+
+       opt = (struct nd_opt_hdr *) (ndmsg + 1);
+       opts_len = ndmsg->nduseropt_opts_len;
+
+       while (opts_len >= sizeof (struct nd_opt_hdr)) {
+               size_t nd_opt_len = opt->nd_opt_len;
+
+               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) {
+                       server.addr = *addr;
+                       g_array_append_val (servers, server);
+               }
+
+       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) {
+               g_array_free (device->rdnss_servers, TRUE);
+               device->rdnss_servers = servers;
+       } else
+               g_array_free (servers, TRUE);
+
+       /* Timeouts may have changed even if IPs didn't */
+       set_rdnss_timeout (device);
+
+       if (changed)
+               return device;
+       else
+               return NULL;
+}
+
+static void
+netlink_notification (NMNetlinkListener *listener, struct nl_msg *msg, gpointer user_data)
+{
+       NMIP6Manager *manager = (NMIP6Manager *) user_data;
+       NMIP6Device *device;
+       struct nlmsghdr *hdr;
+       gboolean config_changed = FALSE;
+
+       hdr = nlmsg_hdr (msg);
+       switch (hdr->nlmsg_type) {
+       case RTM_NEWADDR:
+       case RTM_DELADDR:
+               device = process_addr (manager, msg);
+               config_changed = TRUE;
+               break;
+
+       case RTM_NEWROUTE:
+       case RTM_DELROUTE:
+               device = process_route (manager, msg);
+               config_changed = TRUE;
+               break;
+
+       case RTM_NEWPREFIX:
+               device = process_prefix (manager, msg);
+               break;
+
+       case RTM_NEWNDUSEROPT:
+               device = process_nduseropt (manager, msg);
+               config_changed = TRUE;
+               break;
+
+       default:
+               return;
+       }
+
+       if (device)
+               nm_ip6_device_sync_from_netlink (device, config_changed);
+}
+
+static NMIP6Device *
+nm_ip6_device_new (NMIP6Manager *manager, const char *iface)
+{
+       NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+       NMIP6Device *device;
+
+       device = g_slice_new0 (NMIP6Device);
+       if (!device) {
+               nm_warning ("%s: Out of memory creating IP6 addrconf object.", iface);
+               return NULL;
+       }
+
+       device->iface = g_strdup (iface);
+       if (!device->iface) {
+               nm_warning ("%s: Out of memory creating IP6 addrconf object "
+                           "property 'iface'.",
+                           iface);
+               goto error;
+       }
+       device->index = nm_netlink_iface_to_index (iface);
+
+       device->manager = manager;
+
+       device->rdnss_servers = g_array_new (FALSE, FALSE, sizeof (NMIP6RDNSS));
+
+       g_hash_table_replace (priv->devices_by_iface, device->iface, device);
+       g_hash_table_replace (priv->devices_by_index, GINT_TO_POINTER (device->index), device);
+
+       return device;
+
+error:
+       nm_ip6_device_destroy (device);
+       return NULL;
+}
+
+void
+nm_ip6_manager_prepare_interface (NMIP6Manager *manager,
+                                                                 const char *iface,
+                                                                 NMSettingIP6Config *s_ip6)
+{
+       NMIP6ManagerPrivate *priv;
+       NMIP6Device *device;
+       const char *method = NULL;
+       char *sysctl_path;
+
+       g_return_if_fail (NM_IS_IP6_MANAGER (manager));
+       g_return_if_fail (iface != NULL);
+
+       priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       device = nm_ip6_device_new (manager, iface);
+
+       if (s_ip6)
+               method = nm_setting_ip6_config_get_method (s_ip6);
+       if (!method)
+               method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+
+       if (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)
+               || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
+               device->target_state = NM_IP6_DEVICE_GOT_LINK_LOCAL;
+       else
+               device->target_state = NM_IP6_DEVICE_GOT_ADDRESS;
+
+       g_return_if_fail (strchr (iface, '/') == NULL &&
+                                         strcmp (iface, "all") != 0 &&
+                                         strcmp (iface, "default") != 0);
+
+       sysctl_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/accept_ra", iface);
+       nm_utils_do_sysctl (sysctl_path,
+                                               device->target_state >= NM_IP6_DEVICE_GOT_ADDRESS ? "1\n" : "0\n");
+       g_free (sysctl_path);
+}
+
+void
+nm_ip6_manager_begin_addrconf (NMIP6Manager *manager,
+                                                          const char *iface)
+{
+       NMIP6ManagerPrivate *priv;
+       NMIP6Device *device;
+
+       g_return_if_fail (NM_IS_IP6_MANAGER (manager));
+       g_return_if_fail (iface != NULL);
+
+       priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       device = (NMIP6Device *) g_hash_table_lookup (priv->devices_by_iface, iface);
+       g_return_if_fail (device != NULL);
+
+       nm_info ("Activation (%s) Beginning IP6 addrconf.", iface);
+
+       /* Set up a timeout on the transaction to kill it after the timeout */
+       device->finish_addrconf_id = g_timeout_add_seconds (NM_IP6_TIMEOUT,
+                                                                                                               finish_addrconf,
+                                                                                                               device);
+
+       /* Sync flags, etc, from netlink; this will also notice if the
+        * device is already fully configured and schedule the
+        * ADDRCONF_COMPLETE signal in that case.
+        */
+       nm_ip6_device_sync_from_netlink (device, FALSE);
+}
+
+void
+nm_ip6_manager_cancel_addrconf (NMIP6Manager *manager,
+                                                               const char *iface)
+{
+       NMIP6ManagerPrivate *priv;
+       NMIP6Device *device;
+
+       g_return_if_fail (NM_IS_IP6_MANAGER (manager));
+       g_return_if_fail (iface != NULL);
+
+       priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       device = g_hash_table_lookup (priv->devices_by_iface, iface);
+       if (device) {
+               g_hash_table_remove (priv->devices_by_index, GINT_TO_POINTER (device->index));
+               g_hash_table_remove (priv->devices_by_iface, iface);
+       }
+}
+
+NMIP6Config *
+nm_ip6_manager_get_ip6_config (NMIP6Manager *manager,
+                                                          const char *iface)
+{
+       NMIP6ManagerPrivate *priv;
+       NMIP6Device *device;
+       NMIP6Config *config;
+       struct rtnl_addr *rtnladdr;
+       struct nl_addr *nladdr;
+       struct in6_addr *addr;
+       NMIP6Address *ip6addr;
+       struct rtnl_route *rtnlroute;
+       struct nl_addr *nldest, *nlgateway;
+       struct in6_addr *dest, *gateway;
+       uint32_t metric;
+       NMIP6Route *ip6route;
+       int i;
+
+       g_return_val_if_fail (NM_IS_IP6_MANAGER (manager), NULL);
+       g_return_val_if_fail (iface != NULL, NULL);
+
+       priv = NM_IP6_MANAGER_GET_PRIVATE (manager);
+
+       device = (NMIP6Device *) g_hash_table_lookup (priv->devices_by_iface, iface);
+       if (!device) {
+               nm_warning ("Device '%s' addrconf not started.", iface);
+               return NULL;
+       }
+
+       config = nm_ip6_config_new ();
+       if (!config) {
+               nm_warning ("%s: Out of memory creating IP6 config object.",
+                           iface);
+               return NULL;
+       }
+
+       /* Add addresses */
+       for (rtnladdr = (struct rtnl_addr *)nl_cache_get_first (priv->addr_cache);
+                rtnladdr;
+                rtnladdr = (struct rtnl_addr *)nl_cache_get_next ((struct nl_object *)rtnladdr)) {
+               if (rtnl_addr_get_ifindex (rtnladdr) != device->index)
+                       continue;
+
+               nladdr = rtnl_addr_get_local (rtnladdr);
+               if (!nladdr || nl_addr_get_family (nladdr) != AF_INET6)
+                       continue;
+
+               addr = nl_addr_get_binary_addr (nladdr);
+               ip6addr = nm_ip6_address_new ();
+               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);
+       }
+
+       /* Add routes */
+       for (rtnlroute = (struct rtnl_route *)nl_cache_get_first (priv->route_cache);
+                rtnlroute;
+                rtnlroute = (struct rtnl_route *)nl_cache_get_next ((struct nl_object *)rtnlroute)) {
+               if (rtnl_route_get_oif (rtnlroute) != device->index)
+                       continue;
+
+               nldest = rtnl_route_get_dst (rtnlroute);
+               if (!nldest || nl_addr_get_family (nldest) != AF_INET6)
+                       continue;
+               dest = nl_addr_get_binary_addr (nldest);
+
+               nlgateway = rtnl_route_get_gateway (rtnlroute);
+               if (!nlgateway || nl_addr_get_family (nlgateway) != AF_INET6)
+                       continue;
+               gateway = nl_addr_get_binary_addr (nlgateway);
+
+               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);
+               if (metric != UINT_MAX)
+                       nm_ip6_route_set_metric (ip6route, metric);
+               nm_ip6_config_take_route (config, ip6route);
+       }
+
+       /* Add DNS servers */
+       if (device->rdnss_servers) {
+               NMIP6RDNSS *rdnss = (NMIP6RDNSS *)(device->rdnss_servers->data);
+
+               for (i = 0; i < device->rdnss_servers->len; i++)
+                       nm_ip6_config_add_nameserver (config, &rdnss[i].addr);
+       }
+
+       return config;
+}
diff --git a/src/ip6-manager/nm-ip6-manager.h b/src/ip6-manager/nm-ip6-manager.h
new file mode 100644 (file)
index 0000000..2c79a45
--- /dev/null
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-ip6-manager.c - Handle IPv6 address configuration for NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef NM_IP6_MANAGER_H
+#define NM_IP6_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <nm-setting-ip6-config.h>
+
+#include "nm-ip6-config.h"
+
+#define NM_TYPE_IP6_MANAGER            (nm_ip6_manager_get_type ())
+#define NM_IP6_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IP6_MANAGER, NMIP6Manager))
+#define NM_IP6_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IP6_MANAGER, NMIP6ManagerClass))
+#define NM_IS_IP6_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IP6_MANAGER))
+#define NM_IS_IP6_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_IP6_MANAGER))
+#define NM_IP6_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IP6_MANAGER, NMIP6ManagerClass))
+
+typedef struct {
+       GObject parent;
+} NMIP6Manager;
+
+typedef struct {
+       GObjectClass parent;
+
+       /* Signals */
+       void (*addrconf_complete) (NMIP6Manager *manager, char *iface, gboolean success);
+
+       void (*config_changed)    (NMIP6Manager *manager, char *iface);
+} NMIP6ManagerClass;
+
+GType nm_ip6_manager_get_type (void);
+
+NMIP6Manager *nm_ip6_manager_get                  (void);
+void          nm_ip6_manager_prepare_interface    (NMIP6Manager *manager,
+                                                                                                  const char *iface,
+                                                                                                  NMSettingIP6Config *s_ip6);
+void          nm_ip6_manager_begin_addrconf       (NMIP6Manager *manager,
+                                                                                                  const char *iface);
+void          nm_ip6_manager_cancel_addrconf      (NMIP6Manager *manager,
+                                                                                                  const char *iface);
+
+NMIP6Config * nm_ip6_manager_get_ip6_config       (NMIP6Manager *manager,
+                                                                                                  const char *iface);
+
+#endif /* NM_IP6_MANAGER_H */
diff --git a/src/ip6-manager/nm-netlink-listener.c b/src/ip6-manager/nm-netlink-listener.c
new file mode 100644 (file)
index 0000000..bfcc22f
--- /dev/null
@@ -0,0 +1,407 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2009 Red Hat, Inc.
+ * Copyright (C) 2005 - 2008 Novell, Inc.
+ * Copyright (C) 2005 Ray Strode
+ *
+ * Some code borrowed from HAL:
+ *
+ * Copyright (C) 2003 David Zeuthen, <david@fubar.dk>
+ * Copyright (C) 2004 Novell, Inc.
+ */
+
+/* FIXME: this should be merged with src/nm-netlink-monitor.c */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if.h>
+#include <linux/unistd.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "NetworkManager.h"
+#include "NetworkManagerSystem.h"
+#include "nm-netlink-listener.h"
+#include "nm-utils.h"
+#include "nm-marshal.h"
+#include "nm-netlink.h"
+
+#define NM_NETLINK_LISTENER_EVENT_CONDITIONS \
+       ((GIOCondition) (G_IO_IN | G_IO_PRI))
+
+#define NM_NETLINK_LISTENER_ERROR_CONDITIONS \
+       ((GIOCondition) (G_IO_ERR | G_IO_NVAL))
+
+#define NM_NETLINK_LISTENER_DISCONNECT_CONDITIONS \
+       ((GIOCondition) (G_IO_HUP))
+
+#define NM_NETLINK_LISTENER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
+                                            NM_TYPE_NETLINK_LISTENER, \
+                                            NMNetlinkListenerPrivate))
+
+typedef struct {
+       struct nl_handle *nlh;
+       struct nl_cb *    nlh_cb;
+       struct nl_cache * nlh_link_cache;
+
+       GIOChannel *      io_channel;
+       guint             event_id;
+
+       guint             request_status_id;
+} NMNetlinkListenerPrivate;
+
+static gboolean nm_netlink_listener_event_handler (GIOChannel       *channel,
+                                                                                                  GIOCondition      io_condition,
+                                                                                                  gpointer          user_data);
+
+static gboolean nm_netlink_listener_error_handler (GIOChannel       *channel,
+                                                                                                  GIOCondition      io_condition,
+                                                                                                  NMNetlinkListener *listener);
+
+static gboolean nm_netlink_listener_disconnect_handler (GIOChannel       *channel,
+                                                                                                               GIOCondition      io_condition,
+                                                                                                               NMNetlinkListener *listener);
+
+static void close_connection (NMNetlinkListener *listener);
+
+enum {
+  NOTIFICATION = 0,
+  ERROR,
+
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (NMNetlinkListener, nm_netlink_listener, G_TYPE_OBJECT);
+
+NMNetlinkListener *
+nm_netlink_listener_get (void)
+{
+       static NMNetlinkListener *singleton = NULL;
+
+       if (!singleton)
+               singleton = NM_NETLINK_LISTENER (g_object_new (NM_TYPE_NETLINK_LISTENER, NULL));
+       else
+               g_object_ref (singleton);
+
+       return singleton;
+}
+
+static void
+nm_netlink_listener_init (NMNetlinkListener *listener)
+{
+}
+
+static void
+finalize (GObject *object)
+{
+       NMNetlinkListenerPrivate *priv = NM_NETLINK_LISTENER_GET_PRIVATE (object);
+
+       if (priv->request_status_id)
+               g_source_remove (priv->request_status_id);
+
+       if (priv->io_channel)
+               close_connection (NM_NETLINK_LISTENER (object));
+
+       if (priv->nlh) {
+               nl_handle_destroy (priv->nlh);
+               priv->nlh = NULL;
+       }
+
+       if (priv->nlh_cb) {
+               nl_cb_put (priv->nlh_cb);
+               priv->nlh_cb = NULL;
+       }
+
+       G_OBJECT_CLASS (nm_netlink_listener_parent_class)->finalize (object);
+}
+
+static void
+nm_netlink_listener_class_init (NMNetlinkListenerClass *listener_class)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (listener_class);
+
+       g_type_class_add_private (listener_class, sizeof (NMNetlinkListenerPrivate));
+
+       /* Virtual methods */
+       object_class->finalize = finalize;
+
+       /* Signals */
+       signals[NOTIFICATION] =
+               g_signal_new ("notification",
+                                         G_OBJECT_CLASS_TYPE (object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (NMNetlinkListenerClass, notification),
+                                         NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+                                         G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+       signals[ERROR] =
+               g_signal_new ("error",
+                                         G_OBJECT_CLASS_TYPE (object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (NMNetlinkListenerClass, error),
+                                         NULL, NULL, _nm_marshal_VOID__POINTER,
+                                         G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+static int
+netlink_event_input (struct nl_msg *msg, void *listener)
+{
+       struct nlmsghdr *hdr = nlmsg_hdr (msg);
+
+       if (hdr->nlmsg_pid != 0)
+               return NL_STOP;
+
+       g_signal_emit (listener, signals[NOTIFICATION], 0, msg);
+
+       /* Stop processing messages */
+       return NL_STOP;
+}
+
+static gboolean
+open_connection (NMNetlinkListener *listener, GError **error)
+{
+       NMNetlinkListenerPrivate *priv = NM_NETLINK_LISTENER_GET_PRIVATE (listener);
+       int fd;
+       GError *channel_error = NULL;
+       GIOFlags channel_flags;
+
+       g_return_val_if_fail (priv->io_channel == NULL, FALSE);
+
+       priv->nlh_cb = nl_cb_alloc (NL_CB_DEFAULT);
+       priv->nlh = nl_handle_alloc_cb (priv->nlh_cb);
+       if (!priv->nlh) {
+               g_set_error (error, NM_NETLINK_LISTENER_ERROR,
+                            NM_NETLINK_LISTENER_ERROR_NETLINK_ALLOC_HANDLE,
+                            _("unable to allocate netlink handle: %s"),
+                            nl_geterror ());
+               goto error;
+       }
+
+       nl_disable_sequence_check (priv->nlh);
+       nl_socket_modify_cb (priv->nlh, NL_CB_VALID, NL_CB_CUSTOM, netlink_event_input, listener);
+       if (nl_connect (priv->nlh, NETLINK_ROUTE) < 0) {
+               g_set_error (error, NM_NETLINK_LISTENER_ERROR,
+                            NM_NETLINK_LISTENER_ERROR_NETLINK_CONNECT,
+                            _("unable to connect to netlink: %s"),
+                            nl_geterror ());
+               goto error;
+       }
+
+       fd = nl_socket_get_fd (priv->nlh);
+       priv->io_channel = g_io_channel_unix_new (fd);
+
+       g_io_channel_set_encoding (priv->io_channel, NULL, &channel_error);
+       /* Encoding is NULL, so no conversion error can possibly occur */
+       g_assert (channel_error == NULL);
+
+       g_io_channel_set_close_on_unref (priv->io_channel, TRUE);
+       channel_flags = g_io_channel_get_flags (priv->io_channel);
+       channel_error = NULL;
+       g_io_channel_set_flags (priv->io_channel,
+                               channel_flags | G_IO_FLAG_NONBLOCK,
+                               &channel_error);
+       if (channel_error != NULL) {
+               g_propagate_error (error, channel_error);
+               goto error;
+       }
+
+       priv->event_id = g_io_add_watch (priv->io_channel,
+                                        (NM_NETLINK_LISTENER_EVENT_CONDITIONS |
+                                         NM_NETLINK_LISTENER_ERROR_CONDITIONS |
+                                         NM_NETLINK_LISTENER_DISCONNECT_CONDITIONS),
+                                        nm_netlink_listener_event_handler,
+                                        listener);
+       return TRUE;
+
+error:
+       if (priv->io_channel)
+               close_connection (listener);
+
+       if (priv->nlh) {
+               nl_handle_destroy (priv->nlh);
+               priv->nlh = NULL;
+       }
+
+       if (priv->nlh_cb) {
+               nl_cb_put (priv->nlh_cb);
+               priv->nlh_cb = NULL;
+       }
+       return FALSE;
+}
+
+static void
+close_connection (NMNetlinkListener *listener)
+{
+       NMNetlinkListenerPrivate *priv = NM_NETLINK_LISTENER_GET_PRIVATE (listener);
+
+       g_return_if_fail (priv->io_channel != NULL);
+
+       if (priv->event_id) {
+               g_source_remove (priv->event_id);
+               priv->event_id = 0;
+       }
+
+       g_io_channel_shutdown (priv->io_channel,
+                                                  TRUE /* flush pending data */,
+                                                  NULL);
+
+       g_io_channel_unref (priv->io_channel);
+       priv->io_channel = NULL;
+}
+
+GQuark
+nm_netlink_listener_error_quark (void)
+{
+       static GQuark error_quark = 0;
+
+       if (error_quark == 0)
+               error_quark = g_quark_from_static_string ("nm-netlink-listener-error-quark");
+
+       return error_quark;
+}
+
+gboolean
+nm_netlink_listener_subscribe (NMNetlinkListener *listener,
+                                                          int group,
+                                                          GError **error)
+{
+       NMNetlinkListenerPrivate *priv;
+
+       g_return_val_if_fail (NM_IS_NETLINK_LISTENER (listener), FALSE);
+
+       priv = NM_NETLINK_LISTENER_GET_PRIVATE (listener);
+
+       if (!priv->nlh) {
+               if (!open_connection (listener, error))
+                       return FALSE;
+       }
+
+       if (nl_socket_add_membership (priv->nlh, group) < 0) {
+               g_set_error (error, NM_NETLINK_LISTENER_ERROR,
+                            NM_NETLINK_LISTENER_ERROR_NETLINK_JOIN_GROUP,
+                            _("unable to join netlink group: %s"),
+                            nl_geterror ());
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+void
+nm_netlink_listener_unsubscribe (NMNetlinkListener *listener, int group)
+{
+       NMNetlinkListenerPrivate *priv;
+
+       g_return_if_fail (NM_IS_NETLINK_LISTENER (listener));
+
+       priv = NM_NETLINK_LISTENER_GET_PRIVATE (listener);
+       g_return_if_fail (priv->nlh != NULL);
+
+       nl_socket_drop_membership (priv->nlh, group);
+}
+
+static gboolean
+nm_netlink_listener_event_handler (GIOChannel       *channel,
+                                                                  GIOCondition      io_condition,
+                                                                  gpointer          user_data)
+{
+       NMNetlinkListener *listener = (NMNetlinkListener *) user_data;
+       NMNetlinkListenerPrivate *priv;
+       GError *error = NULL;
+
+       g_return_val_if_fail (NM_IS_NETLINK_LISTENER (listener), TRUE);
+
+       priv = NM_NETLINK_LISTENER_GET_PRIVATE (listener);
+       g_return_val_if_fail (priv->event_id > 0, TRUE);
+
+       if (io_condition & NM_NETLINK_LISTENER_ERROR_CONDITIONS)
+               return nm_netlink_listener_error_handler (channel, io_condition, listener);
+       else if (io_condition & NM_NETLINK_LISTENER_DISCONNECT_CONDITIONS)
+               return nm_netlink_listener_disconnect_handler (channel, io_condition, listener);
+
+       g_return_val_if_fail (!(io_condition & ~(NM_NETLINK_LISTENER_EVENT_CONDITIONS)), FALSE);
+
+       if (nl_recvmsgs_default (priv->nlh) < 0) {
+               error = g_error_new (NM_NETLINK_LISTENER_ERROR,
+                                    NM_NETLINK_LISTENER_ERROR_PROCESSING_MESSAGE,
+                                    _("error processing netlink message: %s"),
+                                    nl_geterror ());
+
+               g_signal_emit (G_OBJECT (listener),
+                              signals[ERROR],
+                              0, error);
+               g_error_free (error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+nm_netlink_listener_error_handler (GIOChannel       *channel,
+                                                                  GIOCondition      io_condition,
+                                                                  NMNetlinkListener *listener)
+{
+       GError *socket_error;
+       const char *err_msg;
+       int err_code;
+       socklen_t err_len;
+
+       g_return_val_if_fail (io_condition & NM_NETLINK_LISTENER_ERROR_CONDITIONS, FALSE);
+
+       err_code = 0;
+       err_len = sizeof (err_code);
+       if (getsockopt (g_io_channel_unix_get_fd (channel),
+                                       SOL_SOCKET, SO_ERROR, (void *) &err_code, &err_len))
+               err_msg = strerror (err_code);
+       else
+               err_msg = _("error occurred while waiting for data on socket");
+
+       socket_error = g_error_new (NM_NETLINK_LISTENER_ERROR,
+                                   NM_NETLINK_LISTENER_ERROR_WAITING_FOR_SOCKET_DATA,
+                                   "%s",
+                                   err_msg);
+
+       g_signal_emit (G_OBJECT (listener),
+                      signals[ERROR],
+                      0, socket_error);
+
+       g_error_free (socket_error);
+
+       return TRUE;
+}
+
+static gboolean
+nm_netlink_listener_disconnect_handler (GIOChannel       *channel,
+                                       GIOCondition      io_condition,
+                                       NMNetlinkListener *listener)
+{
+
+       g_return_val_if_fail (!(io_condition & ~(NM_NETLINK_LISTENER_DISCONNECT_CONDITIONS)), FALSE);
+       return FALSE;
+}
+
diff --git a/src/ip6-manager/nm-netlink-listener.h b/src/ip6-manager/nm-netlink-listener.h
new file mode 100644 (file)
index 0000000..0baabac
--- /dev/null
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Netlink socket listener
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2009 Red Hat, Inc.
+ * Copyright (C) 2005 - 2008 Novell, Inc.
+ * Copyright (C) 2005 Ray Strode
+ */
+
+#ifndef NM_NETLINK_LISTENER_H
+#define NM_NETLINK_LISTENER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "nm-netlink.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_NETLINK_LISTENER           (nm_netlink_listener_get_type ())
+#define NM_NETLINK_LISTENER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_NETLINK_LISTENER, NMNetlinkListener))
+#define NM_NETLINK_LISTENER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_NETLINK_LISTENER, NMNetlinkListenerClass))
+#define NM_IS_NETLINK_LISTENER(obj)     (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_NETLINK_LISTENER))
+#define NM_IS_NETLINK_LISTENER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_NETLINK_LISTENER))
+#define NM_NETLINK_LISTENER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_NETLINK_LISTENER, NMNetlinkListenerClass))
+#define NM_NETLINK_LISTENER_ERROR         (nm_netlink_listener_error_quark ())
+
+typedef enum {
+       NM_NETLINK_LISTENER_ERROR_GENERIC = 0,
+       NM_NETLINK_LISTENER_ERROR_NETLINK_ALLOC_HANDLE,
+       NM_NETLINK_LISTENER_ERROR_NETLINK_CONNECT,
+       NM_NETLINK_LISTENER_ERROR_NETLINK_JOIN_GROUP,
+       NM_NETLINK_LISTENER_ERROR_NETLINK_ALLOC_LINK_CACHE,
+       NM_NETLINK_LISTENER_ERROR_PROCESSING_MESSAGE,
+       NM_NETLINK_LISTENER_ERROR_BAD_ALLOC,
+       NM_NETLINK_LISTENER_ERROR_WAITING_FOR_SOCKET_DATA,
+       NM_NETLINK_LISTENER_ERROR_LINK_CACHE_UPDATE
+} NMNetlinkListenerError;
+
+typedef struct {
+       GObject parent;
+} NMNetlinkListener;
+
+typedef struct {
+       GObjectClass parent_class;
+
+       /* Signals */
+       void (*notification) (NMNetlinkListener *listener, struct nl_msg *msg);
+       void (*error)        (NMNetlinkListener *listener, GError *error);
+} NMNetlinkListenerClass;
+
+GType  nm_netlink_listener_get_type    (void)  G_GNUC_CONST;
+GQuark nm_netlink_listener_error_quark (void)  G_GNUC_CONST;
+
+NMNetlinkListener *nm_netlink_listener_get (void);
+
+gboolean           nm_netlink_listener_subscribe        (NMNetlinkListener *listener,
+                                                                                                                int group,
+                                                                                                                GError **error);
+void               nm_netlink_listener_unsubscribe      (NMNetlinkListener *listener,
+                                                                                                                int group);
+
+G_END_DECLS
+
+#endif  /* NM_NETLINK_LISTENER_H */
index 7d0d905..f236716 100644 (file)
@@ -51,6 +51,7 @@
 #include "nm-setting-connection.h"
 #include "nm-dnsmasq-manager.h"
 #include "nm-dhcp4-config.h"
+#include "nm-ip6-manager.h"
 #include "nm-marshal.h"
 
 #define NM_ACT_REQUEST_IP4_CONFIG "nm-act-request-ip4-config"
@@ -120,7 +121,11 @@ typedef struct {
        guint32 aipd_addr;
 
        /* IP6 configuration info */
-       NMIP6Config *ip6_config;
+       NMIP6Config *  ip6_config;
+       NMIP6Manager * ip6_manager;
+       gulong         ip6_addrconf_sigid;
+       gulong         ip6_config_changed_sigid;
+       gboolean       ip6_waiting_for_config;
 } NMDevicePrivate;
 
 static gboolean check_connection_compatible (NMDeviceInterface *device,
@@ -509,16 +514,53 @@ activation_source_schedule (NMDevice *self, GSourceFunc func, int family)
 }
 
 static void
-configure_ip6_router_advertisements (NMDevice *dev)
+ip6_addrconf_complete (NMIP6Manager *ip6_manager,
+                                          const char *iface,
+                                          gboolean success,
+                                          gpointer user_data)
 {
+       NMDevice *self = NM_DEVICE (user_data);
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+       if (strcmp (nm_device_get_iface (self), iface) != 0)
+               return;
+       if (!nm_device_get_act_request (self))
+               return;
+
+       if (priv->ip6_waiting_for_config) {
+               priv->ip6_waiting_for_config = FALSE;
+               if (success)
+                       nm_device_activate_schedule_stage4_ip6_config_get (self);
+               else
+                       nm_device_activate_schedule_stage4_ip6_config_timeout (self);
+       }
+}
+
+static void
+ip6_config_changed (NMIP6Manager *ip6_manager,
+                                       const char *iface,
+                                       gpointer user_data)
+{
+       NMDevice *self = NM_DEVICE (user_data);
+
+       if (strcmp (nm_device_get_iface (self), iface) != 0)
+               return;
+       if (!nm_device_get_act_request (self))
+               return;
+
+       nm_device_activate_schedule_stage4_ip6_config_get (self);
+}
+
+static void
+nm_device_setup_ip6 (NMDevice *self)
+{
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
        NMActRequest *req;
        NMConnection *connection;
        const char *iface, *method = NULL;
        NMSettingIP6Config *s_ip6;
-       gboolean accept_ra = TRUE;
-       char *sysctl_path;
 
-       req = nm_device_get_act_request (dev);
+       req = nm_device_get_act_request (self);
        if (!req)
                return;
        connection = nm_act_request_get_connection (req);
@@ -532,18 +574,44 @@ configure_ip6_router_advertisements (NMDevice *dev)
        if (!method || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
                return;
 
-       if (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)
-               || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
-                       accept_ra = FALSE;
+       if (!priv->ip6_manager) {
+               priv->ip6_manager = nm_ip6_manager_get ();
+               priv->ip6_addrconf_sigid = g_signal_connect (priv->ip6_manager,
+                                                                                                        "addrconf-complete",
+                                                                                                        G_CALLBACK (ip6_addrconf_complete),
+                                                                                                        self);
+               priv->ip6_config_changed_sigid = g_signal_connect (priv->ip6_manager,
+                                                                                                                  "config-changed",
+                                                                                                                  G_CALLBACK (ip6_config_changed),
+                                                                                                                  self);
+       }
 
-       iface = nm_device_get_iface (dev);
-       g_return_if_fail (strchr (iface, '/') == NULL &&
-                                         strcmp (iface, "all") != 0 &&
-                                         strcmp (iface, "default") != 0);
+       priv->ip6_waiting_for_config = FALSE;
 
-       sysctl_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/accept_ra", iface);
-       nm_utils_do_sysctl (sysctl_path, accept_ra ? "1\n" : "0\n");
-       g_free (sysctl_path);
+       iface = nm_device_get_iface (self);
+       nm_ip6_manager_prepare_interface (priv->ip6_manager, iface, s_ip6);
+}
+
+static void
+nm_device_cleanup_ip6 (NMDevice *self)
+{
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+       if (priv->ip6_manager) {
+               if (priv->ip6_addrconf_sigid) {
+                       g_signal_handler_disconnect (priv->ip6_manager,
+                                                                                priv->ip6_addrconf_sigid);
+                       priv->ip6_addrconf_sigid = 0;
+               }
+               if (priv->ip6_config_changed_sigid) {
+                       g_signal_handler_disconnect (priv->ip6_manager,
+                                                                                priv->ip6_config_changed_sigid);
+                       priv->ip6_config_changed_sigid = 0;
+               }
+
+               g_object_unref (priv->ip6_manager);
+               priv->ip6_manager = NULL;
+       }
 }
 
 /*
@@ -567,10 +635,7 @@ nm_device_activate_stage1_device_prepare (gpointer user_data)
        nm_info ("Activation (%s) Stage 1 of 5 (Device Prepare) started...", iface);
        nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
-   /* Ensure that IPv6 Router Advertisement handling is properly
-       * enabled/disabled before bringing up the interface.
-       */
-       configure_ip6_router_advertisements (self);
+       nm_device_setup_ip6 (self);
 
        ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &reason);
        if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
@@ -1063,7 +1128,21 @@ real_act_stage3_ip4_config_start (NMDevice *self, NMDeviceStateReason *reason)
 static NMActStageReturn
 real_act_stage3_ip6_config_start (NMDevice *self, NMDeviceStateReason *reason)
 {
-       return NM_ACT_STAGE_RETURN_SUCCESS;
+       NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+       const char *iface = nm_device_get_iface (self);
+
+       g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+       /* If we are ignoring IPv6 on this interface then we can go right
+        * to stage 4.
+        */
+       if (!priv->ip6_manager)
+               return NM_ACT_STAGE_RETURN_SUCCESS;
+
+       priv->ip6_waiting_for_config = TRUE;
+       nm_ip6_manager_begin_addrconf (priv->ip6_manager, iface);
+
+       return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 
@@ -1424,6 +1503,7 @@ real_act_stage4_get_ip6_config (NMDevice *self,
                                 NMIP6Config **config,
                                 NMDeviceStateReason *reason)
 {
+       NMDevicePrivate *priv;
        NMConnection *connection;
        NMSettingIP6Config *s_ip6;
        const char *ip_iface;
@@ -1448,15 +1528,15 @@ real_act_stage4_get_ip6_config (NMDevice *self,
                return NM_ACT_STAGE_RETURN_SUCCESS;
        }
 
-       *config = nm_ip6_config_new ();
+       priv = NM_DEVICE_GET_PRIVATE (self);
+       *config = nm_ip6_manager_get_ip6_config (priv->ip6_manager, ip_iface);
        if (!*config) {
                *reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
                return NM_ACT_STAGE_RETURN_FAILURE;
        }
 
        /* Merge user-defined overrides into the IP6Config to be applied */
-       if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
-               nm_utils_merge_ip6_config (*config, s_ip6);
+       nm_utils_merge_ip6_config (*config, s_ip6);
 
        return NM_ACT_STAGE_RETURN_SUCCESS;
 }
@@ -1738,6 +1818,7 @@ nm_device_activate_stage5_ip_config_commit (gpointer user_data)
 {
        NMDevice *self = NM_DEVICE (user_data);
        NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+       NMActRequest *act_request;
        NMIP4Config *ip4_config = NULL;
        NMIP6Config *ip6_config = NULL;
        const char *iface, *method = NULL;
@@ -1746,11 +1827,22 @@ nm_device_activate_stage5_ip_config_commit (gpointer user_data)
        NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
        gboolean assumed;
 
-       ip4_config = g_object_get_data (G_OBJECT (nm_device_get_act_request (self)),
+       /* Get the new IP4 and IP6 configs; since this stage gets rerun
+        * when automatic configuration changes (DHCP lease renewal, new
+        * IPv6 router advertisement, etc), it's possible that only one of
+        * them will be set.
+        */
+       act_request = nm_device_get_act_request (self);
+
+       ip4_config = g_object_get_data (G_OBJECT (act_request),
                                                                        NM_ACT_REQUEST_IP4_CONFIG);
-       g_assert (ip4_config);
-       ip6_config = g_object_get_data (G_OBJECT (nm_device_get_act_request (self)),
+       g_object_set_data (G_OBJECT (act_request),
+                                          NM_ACT_REQUEST_IP4_CONFIG, NULL);
+
+       ip6_config = g_object_get_data (G_OBJECT (act_request),
                                                                        NM_ACT_REQUEST_IP6_CONFIG);
+       g_object_set_data (G_OBJECT (act_request),
+                                          NM_ACT_REQUEST_IP6_CONFIG, NULL);
 
        /* Clear the activation source ID now that this stage has run */
        activation_source_clear (self, FALSE, 0);
@@ -1761,7 +1853,7 @@ nm_device_activate_stage5_ip_config_commit (gpointer user_data)
 
        assumed = nm_act_request_get_assumed (priv->act_request);
 
-       if (!nm_device_set_ip4_config (self, ip4_config, assumed, &reason)) {
+       if (ip4_config && !nm_device_set_ip4_config (self, ip4_config, assumed, &reason)) {
                nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
                goto out;
        }
@@ -1772,15 +1864,18 @@ nm_device_activate_stage5_ip_config_commit (gpointer user_data)
        }
 
        connection = nm_act_request_get_connection (nm_device_get_act_request (self));
-       s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
-       if (s_ip4)
-               method = nm_setting_ip4_config_get_method (s_ip4);
 
-       if (s_ip4 && !strcmp (method, "shared")) {
-               if (!start_sharing (self)) {
-                       nm_warning ("Activation (%s) Stage 5 of 5 (IP Configure Commit) start sharing failed.", iface);
-                       nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
-                       goto out;
+       if (ip4_config) {
+               s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+               if (s_ip4)
+                       method = nm_setting_ip4_config_get_method (s_ip4);
+
+               if (s_ip4 && !strcmp (method, "shared")) {
+                       if (!start_sharing (self)) {
+                               nm_warning ("Activation (%s) Stage 5 of 5 (IP Configure Commit) start sharing failed.", iface);
+                               nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
+                               goto out;
+                       }
                }
        }
 
@@ -1791,7 +1886,8 @@ out:
                 iface);
 
        /* Balance IP config creation; device takes ownership in set_ip*_config() */
-       g_object_unref (ip4_config);
+       if (ip4_config)
+               g_object_unref (ip4_config);
        if (ip6_config)
                g_object_unref (ip6_config);
 
@@ -1819,6 +1915,11 @@ nm_device_activate_schedule_stage5_ip_config_commit (NMDevice *self, int family)
        else if (family == AF_INET6)
                priv->ip6_ready = TRUE;
 
+       /* Note that these are only set FALSE at stage3, so once you've
+        * made it all the way through activation once, you can jump back
+        * into stage4 (eg, for a DHCP lease change) and not worry about
+        * needing both IPv4 and IPv6 to complete.
+        */
        if (!priv->ip4_ready || !priv->ip6_ready)
                return;
 
@@ -1905,6 +2006,7 @@ nm_device_deactivate_quickly (NMDevice *self)
        }
 
        aipd_cleanup (self);
+       nm_device_cleanup_ip6 (self);
 
        /* Call device type-specific deactivation */
        if (NM_DEVICE_GET_CLASS (self)->deactivate_quickly)
@@ -1936,15 +2038,15 @@ nm_device_deactivate (NMDeviceInterface *device, NMDeviceStateReason reason)
 
        nm_device_deactivate_quickly (self);
 
-       /* Clean up nameservers and addresses */
-       nm_device_set_ip4_config (self, NULL, FALSE, &ignored);
-       nm_device_set_ip6_config (self, NULL, FALSE, &ignored);
-
        /* Take out any entries in the routing table and any IP address the device had. */
        nm_system_device_flush_routes (self);
        nm_system_device_flush_addresses (self);
        nm_device_update_ip4_address (self);    
 
+       /* Clean up nameservers and addresses */
+       nm_device_set_ip4_config (self, NULL, FALSE, &ignored);
+       nm_device_set_ip6_config (self, NULL, FALSE, &ignored);
+
        /* Call device type-specific deactivation */
        if (NM_DEVICE_GET_CLASS (self)->deactivate)
                NM_DEVICE_GET_CLASS (self)->deactivate (self);
@@ -2606,7 +2708,10 @@ dispose (GObject *object)
                if (   connection
                    && (nm_connection_get_scope (connection) == NM_CONNECTION_SCOPE_SYSTEM)) {
 
-                       /* Only static or DHCP connections can be left up */
+                       /* Only static or DHCP IPv4 connections can be left up.
+                        * All IPv6 connections can be left up, so we don't have
+                        * to check that.
+                        */
                        s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
                        g_assert (s_ip4);
 
@@ -2635,8 +2740,10 @@ dispose (GObject *object)
        activation_source_clear (self, TRUE, AF_INET);
        activation_source_clear (self, TRUE, AF_INET6);
 
-       if (!take_down)
+       if (!take_down) {
                nm_device_set_use_dhcp (self, FALSE);
+               nm_device_cleanup_ip6 (self);
+       }
 
        if (priv->dnsmasq_manager) {
                if (priv->dnsmasq_state_id) {
index 63d23d2..aed3230 100644 (file)
@@ -227,6 +227,11 @@ out:
 static int
 netlink_event_input (struct nl_msg *msg, void *arg)
 {
+       struct nlmsghdr *hdr = nlmsg_hdr (msg);
+
+       if (hdr->nlmsg_pid != 0)
+               return NL_STOP;
+
        nl_msg_parse (msg, &netlink_object_message_handler, arg);
 
        /* Stop processing messages */