Add NMIP6Manager to handle IPv6 addrconf

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.
This commit is contained in:
Dan Winship 2009-07-30 13:50:42 -04:00
parent 67a5f31fc8
commit 7344cc186d
10 changed files with 1544 additions and 42 deletions

View 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

View file

@ -21,4 +21,5 @@ VOID:POINTER,STRING
VOID:STRING,BOXED
BOOLEAN:POINTER,STRING,BOOLEAN,UINT,STRING,STRING
BOOLEAN:VOID
VOID:STRING,BOOLEAN

View 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 \

View file

@ -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

View file

@ -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;
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */

View 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) {

View 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 */