summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2014-07-24 17:14:30 -0500
committerDan Williams <dcbw@redhat.com>2014-08-20 00:10:52 -0500
commit8500a40818a87d4b86d5693086d819526ea2161d (patch)
tree306c35102179f4e382163d3438b2180fdf0112f8
parente7911f297956fbf5e85e5ebf69a1bf2342a73e78 (diff)
downloadNetworkManager-dcbw/ipv6-addrgenmode-el7.tar.gz
core: take over IPv6LL address management if kernel supports itdcbw/ipv6-addrgenmode-el7
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.
-rw-r--r--src/devices/nm-device.c127
-rw-r--r--src/platform/nm-linux-platform.c40
2 files changed, 155 insertions, 12 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 689be73792..62b36e2095 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -283,6 +283,7 @@ typedef struct {
IpState ip6_state;
NMIP6Config * vpn6_config; /* routes added by a VPN which uses this device */
NMIP6Config * ext_ip6_config; /* Stuff added outside NM */
+ gboolean nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
NMRDisc * rdisc;
gulong rdisc_config_changed_sigid;
@@ -444,8 +445,41 @@ 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";
+
+ nm_log_dbg (LOGD_IP6, "(%s): will %s userland IPv6LL", detail, nm_device_get_ip_iface (self));
+ if (!nm_platform_link_set_user_ipv6ll_enabled (ifindex, enable)) {
+ nm_log_warn (LOGD_IP6, "(%s): failed to %s userspace IPv6LL address handling", detail,
+ nm_device_get_ip_iface (self));
+ }
+ }
}
/*
@@ -728,6 +762,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 {
@@ -777,6 +814,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 guint8 *
nm_device_get_hw_address (NMDevice *dev, guint *out_len)
{
@@ -3390,6 +3433,55 @@ 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)) {
+ nm_log_warn (LOGD_IP6, "(%s): failed to get interface identifier; IPv6 may be broken",
+ nm_device_get_ip_iface (self));
+ return;
+ }
+
+ memset (&lladdr, 0, sizeof (lladdr));
+ lladdr.s6_addr16[0] = htons (0xfe80);
+ nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid);
+ nm_log_dbg (LOGD_IP6, "(%s): adding IPv6LL address %s",
+ nm_device_get_ip_iface (self), 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)) {
+ nm_log_warn (LOGD_IP6, "(%s): failed to add IPv6 link-local address %s",
+ nm_device_get_ip_iface (self),
+ nm_utils_inet6_ntop (&lladdr, NULL));
+ }
+}
+
static NMActStageReturn
linklocal6_start (NMDevice *self)
{
@@ -3409,6 +3501,8 @@ linklocal6_start (NMDevice *self)
nm_log_dbg (LOGD_DEVICE, "[%s] linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.",
nm_device_get_iface (self), 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;
@@ -3629,8 +3723,9 @@ addrconf6_start_with_link_ready (NMDevice *self)
g_assert (priv->rdisc);
- if (!NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, &iid)) {
- nm_log_warn (LOGD_IP6, "(%s): failed to get interface identifier", nm_device_get_ip_iface (self));
+ if (!nm_device_get_ip_iface_identifier (self, &iid)) {
+ nm_log_warn (LOGD_IP6, "(%s): failed to get interface identifier; IPv6 cannot continue",
+ nm_device_get_ip_iface (self));
return FALSE;
}
nm_rdisc_set_iid (priv->rdisc, iid);
@@ -3828,7 +3923,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),
@@ -4940,7 +5035,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason)
aipd_cleanup (self);
/* 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");
@@ -5737,6 +5832,9 @@ dispose (GObject *object)
nm_device_take_down (self, FALSE);
+ /* Let the kernel manage IPv6LL again */
+ set_nm_ipv6ll (self, FALSE);
+
restore_ip6_properties (self);
/* do a final check whether we should delete_link */
@@ -6687,6 +6785,7 @@ nm_device_state_changed (NMDevice *device,
if (nm_device_get_act_request (device))
nm_device_cleanup (device, reason);
nm_device_take_down (device, TRUE);
+ set_nm_ipv6ll (device, FALSE);
restore_ip6_properties (device);
}
break;
@@ -6694,7 +6793,8 @@ nm_device_state_changed (NMDevice *device,
if (old_state == NM_DEVICE_STATE_UNMANAGED) {
save_ip6_properties (device);
if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) {
- nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1");
+ set_nm_ipv6ll (device, TRUE);
+ set_disable_ipv6 (device, "1");
nm_device_ipv6_sysctl_set (device, "accept_ra_defrtr", "0");
nm_device_ipv6_sysctl_set (device, "accept_ra_pinfo", "0");
nm_device_ipv6_sysctl_set (device, "accept_ra_rtr_pref", "0");
@@ -6719,6 +6819,12 @@ nm_device_state_changed (NMDevice *device,
nm_device_cleanup (device, 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 (device, TRUE);
+
if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
nm_device_cleanup (device, reason);
break;
@@ -7121,6 +7227,15 @@ 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;
}
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index b8521ceabd..f85a4878b5 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -73,7 +73,7 @@ typedef struct {
GHashTable *udev_devices;
int support_kernel_extended_ifa_flags;
- gboolean support_user_ipv6ll;
+ int support_user_ipv6ll;
} NMLinuxPlatformPrivate;
#define NM_LINUX_PLATFORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate))
@@ -513,9 +513,18 @@ check_support_kernel_extended_ifa_flags (NMPlatform *platform)
static gboolean
check_support_user_ipv6ll (NMPlatform *platform)
{
+ NMLinuxPlatformPrivate *priv;
+
g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
- return NM_LINUX_PLATFORM_GET_PRIVATE (platform)->support_user_ipv6ll;
+ 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;
}
@@ -1199,11 +1208,11 @@ announce_object (NMPlatform *platform, const struct nl_object *object, ObjectSta
/* If we ever see a link with valid IPv6 link-local address
* generation modes, the kernel supports it.
*/
- if (priv->support_user_ipv6ll == FALSE) {
+ 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 = TRUE;
+ priv->support_user_ipv6ll = 1;
}
#endif
@@ -2012,7 +2021,7 @@ 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) {
+ if (priv->support_user_ipv6ll > 0) {
auto_nl_object struct rtnl_link *rtnllink = link_get (platform, ifindex);
uint8_t mode = 0;
@@ -2050,7 +2059,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
- if (priv->support_user_ipv6ll) {
+ if (priv->support_user_ipv6ll > 0) {
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];
@@ -3408,6 +3417,7 @@ setup (NMPlatform *platform)
int channel_flags;
gboolean status;
int nle;
+ struct nl_object *object;
/* Initialize netlink socket for requests */
priv->nlh = setup_socket (FALSE, platform);
@@ -3449,6 +3459,24 @@ setup (NMPlatform *platform)
rtnl_route_alloc_cache (priv->nlh, AF_UNSPEC, 0, &priv->route_cache);
g_assert (priv->link_cache && priv->address_cache && priv->route_cache);
+#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);