/* -*- 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 - 2010 Red Hat, Inc. */ #define _GNU_SOURCE /* for struct in6_pktinfo */ #include #include #include #include #include #include #include #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-utils.h" /* Pre-DHCP addrconf timeout, in seconds */ #define NM_IP6_TIMEOUT 20 /* 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 { NMNetlinkMonitor *monitor; GHashTable *devices; struct nl_sock *nlh; struct nl_cache *addr_cache, *route_cache; guint netlink_id; } NMIP6ManagerPrivate; #define NM_IP6_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_IP6_MANAGER, NMIP6ManagerPrivate)) G_DEFINE_TYPE (NMIP6Manager, nm_ip6_manager, G_TYPE_OBJECT) enum { ADDRCONF_COMPLETE, CONFIG_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; 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_TIMED_OUT } NMIP6DeviceState; typedef struct { struct in6_addr addr; time_t expires; } NMIP6RDNSS; typedef struct { char domain[256]; time_t expires; } NMIP6DNSSL; /******************************************************************/ typedef struct { NMIP6Manager *manager; char *iface; int ifindex; gboolean has_linklocal; gboolean has_nonlinklocal; guint dhcp_opts; char *disable_ip6_path; gboolean disable_ip6_save_valid; gint32 disable_ip6_save; guint finish_addrconf_id; guint config_changed_id; NMIP6DeviceState state; NMIP6DeviceState target_state; gboolean addrconf_complete; GArray *rdnss_servers; guint rdnss_timeout_id; guint32 rdnss_timeout; GArray *dnssl_domains; guint dnssl_timeout_id; guint32 dnssl_timeout; time_t last_solicitation; 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); /* reset the saved IPv6 value */ if (device->disable_ip6_save_valid) { nm_utils_do_sysctl (device->disable_ip6_path, device->disable_ip6_save ? "1\n" : "0\n"); } if (device->finish_addrconf_id) g_source_remove (device->finish_addrconf_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); g_slice_free (NMIP6Device, device); } static NMIP6Device * nm_ip6_device_new (NMIP6Manager *manager, int ifindex) { NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager); NMIP6Device *device; g_return_val_if_fail (ifindex > 0, NULL); device = g_slice_new0 (NMIP6Device); if (!device) { nm_log_err (LOGD_IP6, "(%d): out of memory creating IP6 addrconf object.", ifindex); return NULL; } device->ifindex = 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); goto error; } device->manager = manager; 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_with_bounds (device->disable_ip6_path, device->iface, &device->disable_ip6_save, 0, 1); return device; error: nm_ip6_device_destroy (device); return NULL; } static NMIP6Device * nm_ip6_manager_get_device (NMIP6Manager *manager, int ifindex) { NMIP6ManagerPrivate *priv; g_return_val_if_fail (manager != NULL, NULL); g_return_val_if_fail (NM_IS_IP6_MANAGER (manager), NULL); priv = NM_IP6_MANAGER_GET_PRIVATE (manager); return g_hash_table_lookup (priv->devices, GINT_TO_POINTER (ifindex)); } static int get_hwaddr (int ifindex, guint8 *buf) { struct rtnl_link *lk; struct nl_addr *addr; int len; lk = nm_netlink_index_to_rtnl_link (ifindex); if (!lk) return -1; addr = rtnl_link_get_addr (lk); len = nl_addr_get_len (addr); if (len > NM_UTILS_HWADDR_LEN_MAX) len = -1; else memcpy (buf, nl_addr_get_binary_addr (addr), len); rtnl_link_put (lk); return len; } static void device_send_router_solicitation (NMIP6Device *device, const char *why) { int sock, hops; struct sockaddr_in6 sin6; struct nd_router_solicit rs; struct nd_opt_hdr lladdr_hdr; guint8 hwaddr[NM_UTILS_HWADDR_LEN_MAX + 7]; int hwaddr_len; static const guint8 local_routers_addr[] = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; struct msghdr mhdr; struct iovec iov[3]; struct cmsghdr *cmsg; struct in6_pktinfo *ipi; guint8 cmsgbuf[128]; int cmsglen = 0; time_t now; now = time (NULL); if (device->last_solicitation > now - 5) return; device->last_solicitation = now; nm_log_dbg (LOGD_IP6, "(%s): %s: sending router solicitation", device->iface, why); sock = socket (AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (sock < 0) { nm_log_dbg (LOGD_IP6, "(%s): could not create ICMPv6 socket: %s", device->iface, g_strerror (errno)); return; } hops = 255; if ( setsockopt (sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof (hops)) == -1 || setsockopt (sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, sizeof (hops)) == -1) { nm_log_dbg (LOGD_IP6, "(%s): could not set hop limit on ICMPv6 socket: %s", device->iface, g_strerror (errno)); close (sock); return; } /* Use the "all link-local routers" multicast address */ memset (&sin6, 0, sizeof (sin6)); memcpy (&sin6.sin6_addr, local_routers_addr, sizeof (sin6.sin6_addr)); mhdr.msg_name = &sin6; mhdr.msg_namelen = sizeof (sin6); /* Build the router solicitation */ mhdr.msg_iov = iov; memset (&rs, 0, sizeof (rs)); rs.nd_rs_type = ND_ROUTER_SOLICIT; iov[0].iov_len = sizeof (rs); iov[0].iov_base = &rs; memset (hwaddr, 0, sizeof (hwaddr)); hwaddr_len = get_hwaddr (device->ifindex, hwaddr); if (hwaddr_len > 0) { memset (&lladdr_hdr, 0, sizeof (lladdr_hdr)); lladdr_hdr.nd_opt_type = ND_OPT_SOURCE_LINKADDR; lladdr_hdr.nd_opt_len = (sizeof (lladdr_hdr) + hwaddr_len + 7) % 8; iov[1].iov_len = sizeof (lladdr_hdr); iov[1].iov_base = &lladdr_hdr; iov[2].iov_len = (lladdr_hdr.nd_opt_len * 8) - 2; iov[2].iov_base = hwaddr; mhdr.msg_iovlen = 3; } else mhdr.msg_iovlen = 1; /* Force this to go on the right device */ memset (cmsgbuf, 0, sizeof (cmsgbuf)); cmsg = (struct cmsghdr *) cmsgbuf; cmsglen = CMSG_SPACE (sizeof (*ipi)); cmsg->cmsg_len = CMSG_LEN (sizeof (*ipi)); cmsg->cmsg_level = SOL_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; ipi = (struct in6_pktinfo *) CMSG_DATA (cmsg); ipi->ipi6_ifindex = device->ifindex; mhdr.msg_control = cmsg; mhdr.msg_controllen = cmsglen; if (sendmsg (sock, &mhdr, 0) == -1) { nm_log_dbg (LOGD_IP6, "(%s): could not send router solicitation: %s", device->iface, g_strerror (errno)); } close (sock); } 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 gboolean device_set_state (NMIP6Device *device, NMIP6DeviceState state) { NMIP6DeviceState old_state; g_return_val_if_fail (device != NULL, FALSE); if (state == device->state) return FALSE; old_state = device->state; device->state = state; nm_log_dbg (LOGD_IP6, "(%s) IP6 device state: %s -> %s", device_get_iface (device), state_to_string (old_state), state_to_string (state)); return TRUE; } 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_MANAGED) g_string_append_c (s, 'M'); if (ra_flags & IF_RA_OTHERCONF) g_string_append_c (s, 'O'); g_string_append_c (s, ')'); return g_string_free (s, FALSE); } static gboolean device_set_ra_flags (NMIP6Device *device, guint ra_flags) { guint old_ra_flags; gchar *ra_flags_str, *old_ra_flags_str; g_return_val_if_fail (device != NULL, FALSE); if (ra_flags == device->ra_flags) return FALSE; old_ra_flags = device->ra_flags; device->ra_flags = ra_flags; if (nm_logging_level_enabled (LOGL_DEBUG)) { ra_flags_str = ra_flags_to_string (ra_flags); old_ra_flags_str = ra_flags_to_string (old_ra_flags); nm_log_dbg (LOGD_IP6, "(%s) IP6 device ra_flags: 0x%08x %s -> 0x%08x %s", device_get_iface (device), old_ra_flags, old_ra_flags_str, ra_flags, ra_flags_str); g_free (ra_flags_str); g_free (old_ra_flags_str); } return TRUE; } /******************************************************************/ typedef struct { NMIP6Device *device; gboolean success; } CallbackInfo; static gboolean finish_addrconf (gpointer user_data) { CallbackInfo *info = user_data; NMIP6Device *device = info->device; NMIP6Manager *manager = device->manager; int ifindex; device->finish_addrconf_id = 0; device->addrconf_complete = TRUE; ifindex = device->ifindex; /* We're done, stop polling IPv6 flags */ if (device->ip6flags_poll_id) { g_source_remove (device->ip6flags_poll_id); device->ip6flags_poll_id = 0; } /* And tell listeners that addrconf is complete */ if (info->success) { g_signal_emit (manager, signals[ADDRCONF_COMPLETE], 0, ifindex, device->dhcp_opts, TRUE); } else { nm_log_info (LOGD_IP6, "(%s): IP6 addrconf timed out or failed.", device->iface); nm_ip6_manager_cancel_addrconf (manager, ifindex); g_signal_emit (manager, signals[ADDRCONF_COMPLETE], 0, ifindex, device->dhcp_opts, FALSE); } return FALSE; } static gboolean emit_config_changed (gpointer user_data) { CallbackInfo *info = user_data; NMIP6Device *device = info->device; NMIP6Manager *manager = device->manager; device->config_changed_id = 0; g_signal_emit (manager, signals[CONFIG_CHANGED], 0, device->ifindex, device->dhcp_opts, info->success); return FALSE; } static void set_rdnss_timeout (NMIP6Device *device); static gboolean rdnss_expired (gpointer user_data) { NMIP6Device *device = user_data; CallbackInfo info = { device, 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; } static gboolean rdnss_needs_refresh (gpointer user_data) { NMIP6Device *device = user_data; gchar *msg; msg = g_strdup_printf ("IPv6 RDNSS due to expire in %d seconds", device->rdnss_timeout); device_send_router_solicitation (device, msg); g_free (msg); set_rdnss_timeout (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) { char buf[INET6_ADDRSTRLEN + 1]; if (inet_ntop (AF_INET6, &(rdnss->addr), buf, sizeof (buf)) > 0) { nm_log_dbg (LOGD_IP6, "(%s): removing expired RA-provided nameserver %s", device->iface, buf); } g_array_remove_index (device->rdnss_servers, i--); continue; } if (!expires || rdnss->expires < expires) expires = rdnss->expires; } if (expires) { gchar *msg; device->rdnss_timeout = MIN (expires - now, G_MAXUINT32 - 1); if (device->rdnss_timeout <= 5) { msg = g_strdup_printf ("IPv6 RDNSS about to expire in %d seconds", device->rdnss_timeout); device_send_router_solicitation (device, msg); g_free (msg); device->rdnss_timeout_id = g_timeout_add_seconds (device->rdnss_timeout, rdnss_expired, device); } else { device->rdnss_timeout_id = g_timeout_add_seconds (device->rdnss_timeout / 2, rdnss_needs_refresh, device); } } } static void set_dnssl_timeout (NMIP6Device *device); static gboolean dnssl_expired (gpointer user_data) { NMIP6Device *device = user_data; CallbackInfo info = { device, 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 gboolean dnssl_needs_refresh (gpointer user_data) { NMIP6Device *device = user_data; gchar *msg; msg = g_strdup_printf ("IPv6 DNSSL due to expire in %d seconds", device->dnssl_timeout); device_send_router_solicitation (device, msg); g_free (msg); set_dnssl_timeout (device); 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) { gchar *msg; device->dnssl_timeout = MIN (expires - now, G_MAXUINT32 - 1); if (device->dnssl_timeout <= 5) { msg = g_strdup_printf ("IPv6 DNSSL about to expire in %d seconds", device->dnssl_timeout); device_send_router_solicitation (device, msg); g_free (msg); device->dnssl_timeout_id = g_timeout_add_seconds (device->dnssl_timeout, dnssl_expired, device); } else { device->dnssl_timeout_id = g_timeout_add_seconds (device->dnssl_timeout / 2, dnssl_needs_refresh, device); } } } static CallbackInfo * callback_info_new (NMIP6Device *device, gboolean success) { CallbackInfo *info; info = g_malloc0 (sizeof (CallbackInfo)); info->device = device; info->success = success; return info; } static void 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; /* 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); rtnladdr; rtnladdr = (struct rtnl_addr *) nl_cache_get_next ((struct nl_object *) rtnladdr)) { char buf[INET6_ADDRSTRLEN]; if (rtnl_addr_get_ifindex (rtnladdr) != device->ifindex) 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 (inet_ntop (AF_INET6, addr, buf, INET6_ADDRSTRLEN) > 0) { 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_set_state (device, NM_IP6_DEVICE_GOT_LINK_LOCAL); device->has_linklocal = TRUE; } else { if (device->state < NM_IP6_DEVICE_GOT_ADDRESS) device_set_state (device, NM_IP6_DEVICE_GOT_ADDRESS); device->has_nonlinklocal = TRUE; } } /* There might be a LL address hanging around on the interface from * 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) && !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)); } static void check_ra_flags (NMIP6Device *device) { device->dhcp_opts = IP6_DHCP_OPT_NONE; /* We only care about router advertisements if we want a real IPv6 address */ if ( (device->target_state == NM_IP6_DEVICE_GOT_ADDRESS) && (device->ra_flags & IF_RA_RCVD)) { if (device->state < NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT) device_set_state (device, NM_IP6_DEVICE_GOT_ROUTER_ADVERTISEMENT); if (device->ra_flags & IF_RA_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) { 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 * we don't bother waiting for the device's target state to be reached * when the RA requests managed mode. */ if ( (device->state >= device->target_state) || (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. */ if (device->finish_addrconf_id) g_source_remove (device->finish_addrconf_id); 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), device->dhcp_opts); info = callback_info_new (device, TRUE); device->finish_addrconf_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, finish_addrconf, info, (GDestroyNotify) g_free); } } else { if (!device->config_changed_id) { gboolean success = TRUE; /* If for some reason an RA-provided address disappeared, we need * to make sure we fail the connection as it's no longer valid. */ if ( (device->state == NM_IP6_DEVICE_GOT_ADDRESS) && (device->target_state == NM_IP6_DEVICE_GOT_ADDRESS) && !device->has_nonlinklocal) { nm_log_dbg (LOGD_IP6, "(%s): RA-provided address no longer found", device->iface); success = FALSE; } info = callback_info_new (device, 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 ref_object (struct nl_object *obj, void *data) { struct nl_object **out = data; nl_object_get (obj); *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_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; hdr = nlmsg_hdr (msg); rtnladdr = NULL; nl_msg_parse (msg, ref_object, &rtnladdr); if (!rtnladdr) { nm_log_dbg (LOGD_IP6, "error processing netlink new/del address message"); 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, 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. */ 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_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; hdr = nlmsg_hdr (msg); rtnlroute = NULL; nl_msg_parse (msg, ref_object, &rtnlroute); if (!rtnlroute) { nm_log_dbg (LOGD_IP6, "error processing netlink new/del route message"); 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, NULL); /* 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; } /* RDNSS parsing code based on rdnssd, Copyright 2007 Pierre Ynard, * Rémi Denis-Courmont. GPLv2/3 */ #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) { NMIP6Device *device; struct nduseroptmsg *ndmsg; struct nd_opt_hdr *opt; 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) { nm_log_dbg (LOGD_IP6, "ignoring non-Router Advertisement message"); return NULL; } device = nm_ip6_manager_get_device (manager, ndmsg->nduseropt_ifindex); if (!device) { nm_log_dbg (LOGD_IP6, "ignoring message for unknown device"); return NULL; } 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; 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; } opts_len -= opt->nd_opt_len << 3; opt = (struct nd_opt_hdr *) ((uint8_t *) opt + (opt->nd_opt_len << 3)); } if (changed) return device; else return NULL; } static struct nla_policy link_policy[IFLA_MAX + 1] = { [IFLA_PROTINFO] = { .type = NLA_NESTED }, }; static struct nla_policy link_prot_policy[IFLA_INET6_MAX + 1] = { [IFLA_INET6_FLAGS] = { .type = NLA_U32 }, }; static NMIP6Device * process_newlink (NMIP6Manager *manager, struct nl_msg *msg) { struct nlmsghdr *hdr = nlmsg_hdr (msg); struct ifinfomsg *ifi; NMIP6Device *device; struct nlattr *tb[IFLA_MAX + 1]; struct nlattr *pi[IFLA_INET6_MAX + 1]; int err; /* 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) { nm_log_dbg (LOGD_IP6, "ignoring netlink message family %d", ifi->ifi_family); return NULL; } device = nm_ip6_manager_get_device (manager, ifi->ifi_index); if (!device || device->addrconf_complete) { nm_log_dbg (LOGD_IP6, "(%s): ignoring unknown or completed device", device ? device->iface : "(none)"); return NULL; } if (!tb[IFLA_PROTINFO]) { nm_log_dbg (LOGD_IP6, "(%s): message had no PROTINFO attribute", device->iface); return NULL; } err = nla_parse_nested (pi, IFLA_INET6_MAX, tb[IFLA_PROTINFO], link_prot_policy); if (err < 0) { nm_log_dbg (LOGD_IP6, "(%s): error parsing PROTINFO flags", device->iface); return NULL; } if (!pi[IFLA_INET6_FLAGS]) { nm_log_dbg (LOGD_IP6, "(%s): message had no PROTINFO flags", device->iface); return NULL; } device_set_ra_flags (device, nla_get_u32 (pi[IFLA_INET6_FLAGS])); return device; } static void netlink_notification (NMNetlinkMonitor *monitor, struct nl_msg *msg, gpointer user_data) { NMIP6Manager *manager = (NMIP6Manager *) user_data; NMIP6Device *device; struct nlmsghdr *hdr; hdr = nlmsg_hdr (msg); nm_log_dbg (LOGD_HW, "netlink event type %d", hdr->nlmsg_type); switch (hdr->nlmsg_type) { case RTM_NEWADDR: case RTM_DELADDR: device = process_address_change (manager, msg); break; case RTM_NEWROUTE: case RTM_DELROUTE: device = process_route_change (manager, msg); break; case RTM_NEWNDUSEROPT: device = process_nduseropt (manager, msg); break; case RTM_NEWLINK: device = process_newlink (manager, msg); break; default: return; } if (device) { nm_ip6_device_sync_from_netlink (device); } } gboolean nm_ip6_manager_prepare_interface (NMIP6Manager *manager, int ifindex, NMSettingIP6Config *s_ip6, const char *accept_ra_path) { NMIP6ManagerPrivate *priv; NMIP6Device *device; const char *method = NULL; 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_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); if (!method) method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; /* Establish target state and turn router advertisement acceptance on or off */ if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) { device->target_state = NM_IP6_DEVICE_GOT_LINK_LOCAL; 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, "2\n"); } return TRUE; } static gboolean poll_ip6_flags (gpointer user_data) { nm_netlink_monitor_request_ip6_info (NM_NETLINK_MONITOR (user_data), NULL); return TRUE; } void nm_ip6_manager_begin_addrconf (NMIP6Manager *manager, int ifindex) { NMIP6ManagerPrivate *priv; NMIP6Device *device; CallbackInfo *info; g_return_if_fail (NM_IS_IP6_MANAGER (manager)); g_return_if_fail (ifindex > 0); priv = NM_IP6_MANAGER_GET_PRIVATE (manager); device = (NMIP6Device *) g_hash_table_lookup (priv->devices, GINT_TO_POINTER (ifindex)); g_return_if_fail (device != NULL); nm_log_info (LOGD_IP6, "Activation (%s) Beginning IP6 addrconf.", device->iface); device->addrconf_complete = FALSE; device->ra_flags = 0; /* Set up a timeout on the transaction to kill it after the timeout */ info = callback_info_new (device, FALSE); device->finish_addrconf_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, NM_IP6_TIMEOUT, finish_addrconf, info, (GDestroyNotify) g_free); /* Bounce IPv6 on the interface to ensure the kernel will start looking for * new RAs; there doesn't seem to be a better way to do this right now. */ if (device->target_state >= NM_IP6_DEVICE_GOT_ADDRESS) { nm_utils_do_sysctl (device->disable_ip6_path, "1\n"); g_usleep (200); nm_utils_do_sysctl (device->disable_ip6_path, "0\n"); } device->ip6flags_poll_id = g_timeout_add_seconds (1, poll_ip6_flags, priv->monitor); /* Kick off the initial IPv6 flags request */ nm_netlink_monitor_request_ip6_info (priv->monitor, NULL); /* 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); } void nm_ip6_manager_cancel_addrconf (NMIP6Manager *manager, int ifindex) { g_return_if_fail (NM_IS_IP6_MANAGER (manager)); g_return_if_fail (ifindex > 0); g_hash_table_remove (NM_IP6_MANAGER_GET_PRIVATE (manager)->devices, GINT_TO_POINTER (ifindex)); } #define FIRST_ROUTE(m) ((struct rtnl_route *) nl_cache_get_first (m)) #define NEXT_ROUTE(m) ((struct rtnl_route *) nl_cache_get_next ((struct nl_object *) m)) #define FIRST_ADDR(m) ((struct rtnl_addr *) nl_cache_get_first (m)) #define NEXT_ADDR(m) ((struct rtnl_addr *) nl_cache_get_next ((struct nl_object *) m)) NMIP6Config * nm_ip6_manager_get_ip6_config (NMIP6Manager *manager, int ifindex) { 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; const 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 (ifindex > 0, NULL); priv = NM_IP6_MANAGER_GET_PRIVATE (manager); device = (NMIP6Device *) g_hash_table_lookup (priv->devices, GINT_TO_POINTER (ifindex)); if (!device) { nm_log_warn (LOGD_IP6, "(%d): addrconf not started.", ifindex); return NULL; } config = nm_ip6_config_new (); if (!config) { nm_log_err (LOGD_IP6, "(%s): out of memory creating IP6 config object.", device->iface); return NULL; } /* Make sure we refill the route and address caches, otherwise we won't get * up-to-date information here since the netlink route/addr change messages * may be lagging a bit. */ nl_cache_refill (priv->nlh, priv->route_cache); nl_cache_refill (priv->nlh, priv->addr_cache); /* Add routes */ for (rtnlroute = FIRST_ROUTE (priv->route_cache); rtnlroute; rtnlroute = NEXT_ROUTE (rtnlroute)) { /* Make sure it's an IPv6 route for this device */ if (rtnl_route_get_oif (rtnlroute) != device->ifindex) continue; if (rtnl_route_get_family (rtnlroute) != AF_INET6) 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); if (rtnl_route_get_dst_len (rtnlroute) == 0) { /* Default gateway route; cache the router's address for later */ if (!nm_ip6_config_get_gateway (config)) nm_ip6_config_set_gateway (config, gateway); continue; } /* 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 && 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); 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); } /* Add addresses */ for (rtnladdr = FIRST_ADDR (priv->addr_cache); rtnladdr; rtnladdr = NEXT_ADDR (rtnladdr)) { if (rtnl_addr_get_ifindex (rtnladdr) != device->ifindex) 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); gateway = nm_ip6_config_get_gateway (config); if (gateway) nm_ip6_address_set_gateway (ip6addr, gateway); } /* 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); } /* 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; } /******************************************************************/ 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) { nm_log_err (LOGD_IP6, "not enough memory to initialize IP6 manager tables"); g_object_unref (manager); manager = NULL; } return manager; } static NMIP6Manager *singleton = NULL; NMIP6Manager * nm_ip6_manager_get (void) { if (!singleton) { singleton = nm_ip6_manager_new (); g_assert (singleton); } else g_object_ref (singleton); return singleton; } static void nm_ip6_manager_init (NMIP6Manager *manager) { NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (manager); priv->devices = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) nm_ip6_device_destroy); 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); priv->netlink_id = g_signal_connect (priv->monitor, "notification", G_CALLBACK (netlink_notification), manager); priv->nlh = nm_netlink_get_default_handle (); 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 finalize (GObject *object) { NMIP6ManagerPrivate *priv = NM_IP6_MANAGER_GET_PRIVATE (object); g_signal_handler_disconnect (priv->monitor, priv->netlink_id); g_hash_table_destroy (priv->devices); g_object_unref (priv->monitor); nl_cache_free (priv->addr_cache); nl_cache_free (priv->route_cache); singleton = NULL; 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__INT_UINT_BOOLEAN, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_UINT, 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, _nm_marshal_VOID__INT_UINT_BOOLEAN, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_UINT, G_TYPE_BOOLEAN); }