merge: handle IPv6 link-local addresses from userspace when possible (bgo #734149)

This commit is contained in:
Dan Williams 2014-09-04 15:11:55 -05:00
commit 4e530adf60
5 changed files with 326 additions and 12 deletions

View file

@ -386,6 +386,25 @@ PKG_CHECK_MODULES(LIBNL, libnl-3.0 >= 3.2.8 libnl-route-3.0 libnl-genl-3.0)
AC_SUBST(LIBNL_CFLAGS)
AC_SUBST(LIBNL_LIBS)
AC_CHECK_LIB([nl-route-3], [rtnl_link_inet6_get_addr_gen_mode],
ac_have_addr_gen_mode="1",
ac_have_addr_gen_mode="0")
AC_DEFINE_UNQUOTED(HAVE_LIBNL_INET6_ADDR_GEN_MODE,
$ac_have_addr_gen_mode, [Define if libnl has rtnl_link_inet6_get_addr_gen_mode()])
AC_MSG_CHECKING([Linux kernel IN6_ADDR_GEN_MODE enum])
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[[#ifndef __user
#define __user
#endif
#include <linux/if_link.h>]],
[[int a = IN6_ADDR_GEN_MODE_EUI64; a++;]])],
[ac_have_kernel_gen_mode="1"],
[ac_have_kernel_gen_mode="0"])
AC_DEFINE_UNQUOTED(HAVE_KERNEL_INET6_ADDR_GEN_MODE,
$ac_have_kernel_gen_mode, [Define if the kernel has IN6_ADDR_GEN_MODE_*])
# uuid library
PKG_CHECK_MODULES(UUID, uuid)
AC_SUBST(UUID_CFLAGS)

View file

@ -268,6 +268,7 @@ typedef struct {
NMIP6Config * vpn6_config; /* routes added by a VPN which uses this device */
NMIP6Config * wwan_ip6_config;
NMIP6Config * ext_ip6_config; /* Stuff added outside NM */
gboolean nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
NMRDisc * rdisc;
gulong rdisc_changed_id;
@ -563,6 +564,9 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
if (priv->ip_iface) {
priv->ip_ifindex = nm_platform_link_get_ifindex (priv->ip_iface);
if (priv->ip_ifindex > 0) {
if (nm_platform_check_support_user_ipv6ll ())
nm_platform_link_set_user_ipv6ll_enabled (priv->ip_ifindex, TRUE);
if (!nm_platform_link_is_up (priv->ip_ifindex))
nm_platform_link_set_up (priv->ip_ifindex);
} else {
@ -611,6 +615,12 @@ get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
return success;
}
static gboolean
nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid)
{
return NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, iid);
}
const char *
nm_device_get_driver (NMDevice *self)
{
@ -1132,7 +1142,12 @@ device_ip_link_changed (NMDevice *self, NMPlatformLink *info)
}
static void
link_changed_cb (NMPlatform *platform, int ifindex, NMPlatformLink *info, NMPlatformSignalChangeType change_type, NMPlatformReason reason, NMDevice *self)
link_changed_cb (NMPlatform *platform,
int ifindex,
NMPlatformLink *info,
NMPlatformSignalChangeType change_type,
NMPlatformReason reason,
NMDevice *self)
{
if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
return;
@ -3368,6 +3383,52 @@ linklocal6_complete (NMDevice *self)
g_return_if_fail (FALSE);
}
static void
check_and_add_ipv6ll_addr (NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
int ip_ifindex = nm_device_get_ip_ifindex (self);
NMUtilsIPv6IfaceId iid;
struct in6_addr lladdr;
guint i, n;
if (priv->nm_ipv6ll == FALSE)
return;
if (priv->ip6_config) {
n = nm_ip6_config_get_num_addresses (priv->ip6_config);
for (i = 0; i < n; i++) {
const NMPlatformIP6Address *addr;
addr = nm_ip6_config_get_address (priv->ip6_config, i);
if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) {
/* Already have an LL address, nothing to do */
return;
}
}
}
if (!nm_device_get_ip_iface_identifier (self, &iid)) {
_LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 may be broken");
return;
}
memset (&lladdr, 0, sizeof (lladdr));
lladdr.s6_addr16[0] = htons (0xfe80);
nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid);
_LOGD (LOGD_IP6, "adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL));
if (!nm_platform_ip6_address_add (ip_ifindex,
lladdr,
in6addr_any,
64,
NM_PLATFORM_LIFETIME_PERMANENT,
NM_PLATFORM_LIFETIME_PERMANENT,
0)) {
_LOGW (LOGD_IP6, "failed to add IPv6 link-local address %s",
nm_utils_inet6_ntop (&lladdr, NULL));
}
}
static NMActStageReturn
linklocal6_start (NMDevice *self)
{
@ -3386,6 +3447,8 @@ linklocal6_start (NMDevice *self)
method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
_LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.", method);
check_and_add_ipv6ll_addr (self);
priv->linklocal6_timeout_id = g_timeout_add_seconds (5, linklocal6_timeout_cb, self);
return NM_ACT_STAGE_RETURN_POSTPONE;
@ -3630,8 +3693,8 @@ addrconf6_start_with_link_ready (NMDevice *self)
g_assert (priv->rdisc);
if (!NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, &iid)) {
_LOGW (LOGD_IP6, "failed to get interface identifier");
if (!nm_device_get_ip_iface_identifier (self, &iid)) {
_LOGW (LOGD_IP6, "failed to get interface identifier; IPv6 cannot continue");
return FALSE;
}
nm_rdisc_set_iid (priv->rdisc, iid);
@ -3759,8 +3822,39 @@ restore_ip6_properties (NMDevice *self)
gpointer key, value;
g_hash_table_iter_init (&iter, priv->ip6_saved_properties);
while (g_hash_table_iter_next (&iter, &key, &value))
while (g_hash_table_iter_next (&iter, &key, &value)) {
/* Don't touch "disable_ipv6" if we're doing userland IPv6LL */
if (priv->nm_ipv6ll && strcmp (key, "disable_ipv6") == 0)
continue;
nm_device_ipv6_sysctl_set (self, key, value);
}
}
static inline void
set_disable_ipv6 (NMDevice *self, const char *value)
{
/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
if (NM_DEVICE_GET_PRIVATE (self)->nm_ipv6ll == FALSE)
nm_device_ipv6_sysctl_set (self, "disable_ipv6", value);
}
static inline void
set_nm_ipv6ll (NMDevice *self, gboolean enable)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
int ifindex = nm_device_get_ip_ifindex (self);
if (!nm_platform_check_support_user_ipv6ll ())
return;
priv->nm_ipv6ll = enable;
if (ifindex > 0) {
const char *detail = enable ? "enable" : "disable";
_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
if (!nm_platform_link_set_user_ipv6ll_enabled (ifindex, enable))
_LOGW (LOGD_IP6, "failed to %s userspace IPv6LL address handling", detail);
}
}
static NMSettingIP6ConfigPrivacy
@ -3889,7 +3983,7 @@ act_stage3_ip6_config_start (NMDevice *self,
}
/* Re-enable IPv6 on the interface */
nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0");
set_disable_ipv6 (self, "0");
/* Enable/disable IPv6 Privacy Extensions.
* If a global value is configured by sysadmin (e.g. /etc/sysctl.conf),
@ -5908,17 +6002,29 @@ queued_ip_config_change (gpointer user_data)
priv->queued_ip_config_id = 0;
update_ip_config (self, FALSE);
/* If no IPv6 link-local address exists but other addresses do then we
* must add the LL address to remain conformant with RFC 3513 chapter 2.1
* ("Addressing Model"): "All interfaces are required to have at least
* one link-local unicast address".
*/
if (priv->ip6_config && nm_ip6_config_get_num_addresses (priv->ip6_config))
check_and_add_ipv6ll_addr (self);
return FALSE;
}
static void
device_ip_changed (NMPlatform *platform, int ifindex, gpointer platform_object, NMPlatformSignalChangeType change_type, NMPlatformReason reason, gpointer user_data)
device_ip_changed (NMPlatform *platform,
int ifindex,
gpointer platform_object,
NMPlatformSignalChangeType change_type,
NMPlatformReason reason,
NMDevice *self)
{
NMDevice *self = user_data;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
if (nm_device_get_ip_ifindex (self) == ifindex) {
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
if (!priv->queued_ip_config_id)
priv->queued_ip_config_id = g_idle_add (queued_ip_config_change, self);
@ -6538,7 +6644,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason)
_cleanup_generic_pre (self, TRUE);
/* Turn off kernel IPv6 */
nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
set_disable_ipv6 (self, "1");
nm_device_ipv6_sysctl_set (self, "accept_ra", "0");
nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
@ -6653,6 +6759,7 @@ _set_state_full (NMDevice *self,
if (nm_device_get_act_request (self))
nm_device_cleanup (self, reason);
nm_device_take_down (self, TRUE);
set_nm_ipv6ll (self, FALSE);
restore_ip6_properties (self);
}
break;
@ -6660,7 +6767,8 @@ _set_state_full (NMDevice *self,
if (old_state == NM_DEVICE_STATE_UNMANAGED) {
save_ip6_properties (self);
if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) {
nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
set_nm_ipv6ll (self, TRUE);
set_disable_ipv6 (self, "1");
nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
@ -6685,6 +6793,12 @@ _set_state_full (NMDevice *self,
nm_device_cleanup (self, reason);
break;
case NM_DEVICE_STATE_DISCONNECTED:
/* Ensure devices that previously assumed a connection now have
* userspace IPv6LL enabled.
*/
if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED)
set_nm_ipv6ll (self, TRUE);
if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
nm_device_cleanup (self, reason);
break;
@ -7179,6 +7293,13 @@ constructor (GType type,
g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ip_changed), self);
g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
if (nm_platform_check_support_user_ipv6ll ()) {
int ip_ifindex = nm_device_get_ip_ifindex (self);
if (ip_ifindex > 0)
priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (ip_ifindex);
}
return object;
error:
@ -7257,6 +7378,9 @@ dispose (GObject *object)
g_warn_if_fail (priv->slaves == NULL);
g_assert (priv->master_ready_id == 0);
/* Let the kernel manage IPv6LL again */
set_nm_ipv6ll (self, FALSE);
_cleanup_generic_post (self, FALSE);
g_clear_pointer (&priv->ip6_saved_properties, g_hash_table_unref);

View file

@ -44,6 +44,16 @@
#include <netlink/route/route.h>
#include <gudev/gudev.h>
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
#include <netlink/route/link/inet6.h>
#if HAVE_KERNEL_INET6_ADDR_GEN_MODE
#include <linux/if_link.h>
#else
#define IN6_ADDR_GEN_MODE_EUI64 0
#define IN6_ADDR_GEN_MODE_NONE 1
#endif
#endif
#include "NetworkManagerUtils.h"
#include "nm-linux-platform.h"
#include "NetworkManagerUtils.h"
@ -83,6 +93,7 @@ typedef struct {
GHashTable *wifi_data;
int support_kernel_extended_ifa_flags;
int support_user_ipv6ll;
} NMLinuxPlatformPrivate;
#define NM_LINUX_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate))
@ -669,6 +680,23 @@ check_support_kernel_extended_ifa_flags (NMPlatform *platform)
return priv->support_kernel_extended_ifa_flags > 0;
}
static gboolean
check_support_user_ipv6ll (NMPlatform *platform)
{
NMLinuxPlatformPrivate *priv;
g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
if (priv->support_user_ipv6ll == 0) {
nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for IFLA_INET6_ADDR_GEN_MODE. Assume no kernel support.");
priv->support_user_ipv6ll = -1;
}
return priv->support_user_ipv6ll > 0;
}
/* Object type specific utilities */
@ -1529,8 +1557,20 @@ announce_object (NMPlatform *platform, const struct nl_object *object, NMPlatfor
switch (object_type) {
case OBJECT_TYPE_LINK:
{
NMPlatformLink device;
struct rtnl_link *rtnl_link = (struct rtnl_link *) object;
NMPlatformLink device;
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
/* If we ever see a link with valid IPv6 link-local address
* generation modes, the kernel supports it.
*/
if (priv->support_user_ipv6ll == 0) {
uint8_t mode;
if (rtnl_link_inet6_get_addr_gen_mode (rtnl_link, &mode) == 0)
priv->support_user_ipv6ll = 1;
}
#endif
if (!init_link (platform, &device, rtnl_link))
return;
@ -2405,6 +2445,46 @@ link_set_noarp (NMPlatform *platform, int ifindex)
return link_change_flags (platform, ifindex, IFF_NOARP, TRUE);
}
static gboolean
link_get_user_ipv6ll_enabled (NMPlatform *platform, int ifindex)
{
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
if (priv->support_user_ipv6ll > 0) {
auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
uint8_t mode = 0;
if (rtnllink) {
if (rtnl_link_inet6_get_addr_gen_mode (rtnllink, &mode) != 0) {
/* Default to "disabled" on error */
return FALSE;
}
return mode == IN6_ADDR_GEN_MODE_NONE;
}
}
#endif
return FALSE;
}
static gboolean
link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
{
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
if (check_support_user_ipv6ll (platform)) {
auto_nl_object struct rtnl_link *change = _nm_rtnl_link_alloc (ifindex, NULL);
guint8 mode = enabled ? IN6_ADDR_GEN_MODE_NONE : IN6_ADDR_GEN_MODE_EUI64;
char buf[32];
rtnl_link_inet6_set_addr_gen_mode (change, mode);
debug ("link: change %d: set IPv6 address generation mode to %s",
ifindex, rtnl_link_inet6_addrgenmode2str (mode, buf, sizeof (buf)));
return link_change (platform, ifindex, change);
}
#endif
return FALSE;
}
static gboolean
supports_ethtool_carrier_detect (const char *ifname)
{
@ -4060,6 +4140,24 @@ setup (NMPlatform *platform)
for (object = nl_cache_get_first (priv->address_cache); object; object = nl_cache_get_next (object))
_rtnl_addr_hack_lifetimes_rel_to_abs ((struct rtnl_addr *) object);
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
/* Initial check for user IPv6LL support once the link cache is allocated
* and filled. If there are no links in the cache yet then we'll check
* when a new link shows up in announce_object().
*/
object = nl_cache_get_first (priv->link_cache);
if (object) {
uint8_t mode;
if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) object, &mode) == 0)
priv->support_user_ipv6ll = 1;
else
priv->support_user_ipv6ll = -1;
}
#else
priv->support_user_ipv6ll = -1;
#endif
/* Set up udev monitoring */
priv->udev_client = g_udev_client_new (udev_subsys);
g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (handle_udev_event), platform);
@ -4147,6 +4245,9 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
platform_class->link_is_connected = link_is_connected;
platform_class->link_uses_arp = link_uses_arp;
platform_class->link_get_user_ipv6ll_enabled = link_get_user_ipv6ll_enabled;
platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled;
platform_class->link_get_address = link_get_address;
platform_class->link_set_address = link_set_address;
platform_class->link_get_mtu = link_get_mtu;
@ -4213,4 +4314,5 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
platform_class->ip6_route_exists = ip6_route_exists;
platform_class->check_support_kernel_extended_ifa_flags = check_support_kernel_extended_ifa_flags;
platform_class->check_support_user_ipv6ll = check_support_user_ipv6ll;
}

View file

@ -215,6 +215,21 @@ nm_platform_check_support_kernel_extended_ifa_flags ()
return klass->check_support_kernel_extended_ifa_flags (platform);
}
gboolean
nm_platform_check_support_user_ipv6ll (void)
{
static int supported = -1;
g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
if (!klass->check_support_user_ipv6ll)
return FALSE;
if (supported < 0)
supported = klass->check_support_user_ipv6ll (platform) ? 1 : 0;
return !!supported;
}
/******************************************************************/
/**
@ -729,6 +744,52 @@ nm_platform_link_uses_arp (int ifindex)
return klass->link_uses_arp (platform, ifindex);
}
/**
* nm_platform_link_get_user_ip6vll_enabled:
* @ifindex: Interface index
*
* Check whether NM handles IPv6LL address creation for the link. If the
* platform or OS doesn't support changing the IPv6LL address mode, this call
* will fail and return %FALSE.
*
* Returns: %TRUE if NM handles the IPv6LL address for @ifindex
*/
gboolean
nm_platform_link_get_user_ipv6ll_enabled (int ifindex)
{
reset_error ();
g_return_val_if_fail (ifindex >= 0, FALSE);
g_return_val_if_fail (klass->check_support_user_ipv6ll, FALSE);
if (klass->link_get_user_ipv6ll_enabled)
return klass->link_get_user_ipv6ll_enabled (platform, ifindex);
return FALSE;
}
/**
* nm_platform_link_set_user_ip6vll_enabled:
* @ifindex: Interface index
*
* Set whether NM handles IPv6LL address creation for the link. If the
* platform or OS doesn't support changing the IPv6LL address mode, this call
* will fail and return %FALSE.
*
* Returns: %TRUE if the operation was successful, %FALSE if it failed.
*/
gboolean
nm_platform_link_set_user_ipv6ll_enabled (int ifindex, gboolean enabled)
{
reset_error ();
g_return_val_if_fail (ifindex >= 0, FALSE);
g_return_val_if_fail (klass->check_support_user_ipv6ll, FALSE);
if (klass->link_set_user_ipv6ll_enabled)
return klass->link_set_user_ipv6ll_enabled (platform, ifindex, enabled);
return FALSE;
}
/**
* nm_platform_link_set_address:
* @ifindex: Interface index

View file

@ -388,6 +388,9 @@ typedef struct {
gboolean (*link_is_connected) (NMPlatform *, int ifindex);
gboolean (*link_uses_arp) (NMPlatform *, int ifindex);
gboolean (*link_get_user_ipv6ll_enabled) (NMPlatform *, int ifindex);
gboolean (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
gconstpointer (*link_get_address) (NMPlatform *, int ifindex, size_t *length);
gboolean (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
guint32 (*link_get_mtu) (NMPlatform *, int ifindex);
@ -463,6 +466,7 @@ typedef struct {
gboolean (*ip6_route_exists) (NMPlatform *, int ifindex, struct in6_addr network, int plen, int metric);
gboolean (*check_support_kernel_extended_ifa_flags) (NMPlatform *);
gboolean (*check_support_user_ipv6ll) (NMPlatform *);
} NMPlatformClass;
/* NMPlatform signals
@ -528,6 +532,9 @@ gboolean nm_platform_link_is_up (int ifindex);
gboolean nm_platform_link_is_connected (int ifindex);
gboolean nm_platform_link_uses_arp (int ifindex);
gboolean nm_platform_link_get_user_ipv6ll_enabled (int ifindex);
gboolean nm_platform_link_set_user_ipv6ll_enabled (int ifindex, gboolean enabled);
gconstpointer nm_platform_link_get_address (int ifindex, size_t *length);
gboolean nm_platform_link_set_address (int ifindex, const void *address, size_t length);
guint32 nm_platform_link_get_mtu (int ifindex);
@ -623,6 +630,7 @@ int nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6R
gboolean nm_platform_check_support_libnl_extended_ifa_flags (void);
gboolean nm_platform_check_support_kernel_extended_ifa_flags (void);
gboolean nm_platform_check_support_user_ipv6ll (void);
void nm_platform_addr_flags2str (int flags, char *buf, size_t size);