From 5e4761a3a9cb1cba692d41d79aa389f539752e50 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 24 Jul 2014 17:14:30 -0500 Subject: [PATCH] core: take over IPv6LL address management if kernel supports it (bgo #734149) NM keeps interfaces IFF_UP when possible to receive link layer events like carrier changes. Unfortunately, the kernel also uses IFF_UP as a flag to assign an IPv6LL address to the interface, which results in IPv6 connectivity on the link even if the interface is not supposed to be activated/connected. NM sets disable_ipv6=1 to ensure that the kernel does not set up IPv6LL connectivity on interfaces when they are not supposed to be active and connected. Unfortunately, that prevents users from manually adding IPv6 addresses to the interface, since they expect previous kernel behavior where IPv6 is enabled whenever the interface is IFF_UP. Furthermore, interfaces like PPP and some WWAN devices provide misleading information to the kernel which causes the kernel to create the wrong IPv6LL address for the interface. The IPv6LL address for these devices is obtained through control channels instead (IPV6CP for PPP, proprietary protocols for WWAN devices) and should be used instead of the kernel address. So we'd like to suppress kernel IPv6LL address generation on these interfaces anyway. This patch makes use of the netlink IFLA_INET6_ADDR_GEN_MODE attribute to take over assignment of IPv6LL addresses while keeping the interface IFF_UP, to ensure there is only IPv6 connectivity when the user requests it. To remain compliant with standards, if a user adds IPv6 addresses externally, NetworkManager must also immediately add an IPv6LL address for that interface too. https://bugzilla.gnome.org/show_bug.cgi?id=734149 --- src/devices/nm-device.c | 146 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 11 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index ef10d0c239..18b0ad21c7 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -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);