diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index f1df6ef0a5..4ce5eb4627 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -201,6 +201,7 @@ typedef struct { guint queued_ip4_config_id; guint queued_ip6_config_id; GSList *pending_actions; + GSList *dad6_failed_addrs; char * udi; char * iface; /* may change, could be renamed by user */ @@ -4774,16 +4775,20 @@ linklocal6_cleanup (NMDevice *self) } } +static void +linklocal6_failed (NMDevice *self) +{ + linklocal6_cleanup (self); + nm_device_activate_schedule_ip6_config_timeout (self); +} + static gboolean linklocal6_timeout_cb (gpointer user_data) { NMDevice *self = user_data; - linklocal6_cleanup (self); - _LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout"); - - nm_device_activate_schedule_ip6_config_timeout (self); + linklocal6_failed (self); return G_SOURCE_REMOVE; } @@ -4840,7 +4845,8 @@ check_and_add_ipv6ll_addr (NMDevice *self) const NMPlatformIP6Address *addr; addr = nm_ip6_config_get_address (priv->ip6_config, i); - if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) { + if ( IN6_IS_ADDR_LINKLOCAL (&addr->address) + && !(addr->flags & IFA_F_DADFAILED)) { /* Already have an LL address, nothing to do */ return; } @@ -4855,7 +4861,16 @@ check_and_add_ipv6ll_addr (NMDevice *self) 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 (priv->linklocal6_timeout_id) { + /* We already started and attempt to add a LL address. For the EUI-64 + * mode we can't pick a new one, we'll just fail. */ + _LOGW (LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address"); + linklocal6_failed (self); + return; + } + + _LOGD (LOGD_IP6, "linklocal6: adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL)); if (!nm_platform_ip6_address_add (NM_PLATFORM_GET, ip_ifindex, lladdr, @@ -7794,6 +7809,8 @@ queued_ip6_config_change (gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + GSList *iter; + gboolean need_ipv6ll = FALSE; /* Wait for any queued state changes */ if (priv->queued_state.id) @@ -7803,12 +7820,32 @@ queued_ip6_config_change (gpointer user_data) g_object_ref (self); update_ip6_config (self, FALSE); + /* Handle DAD falures */ + for (iter = priv->dad6_failed_addrs; iter; iter = g_slist_next (iter)) { + NMPlatformIP6Address *addr = iter->data; + + if (addr->source >= NM_IP_CONFIG_SOURCE_USER) + continue; + + _LOGI (LOGD_IP6, "ipv6: duplicate address check failed for the %s address", + nm_platform_ip6_address_to_string (addr, NULL, 0)); + + if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) + need_ipv6ll = TRUE; + else + nm_rdisc_dad_failed (priv->rdisc, &addr->address); + } + g_slist_free_full (priv->dad6_failed_addrs, g_free); + /* 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)) + need_ipv6ll = TRUE; + + if (need_ipv6ll) check_and_add_ipv6ll_addr (self); g_object_unref (self); @@ -7826,11 +7863,13 @@ device_ipx_changed (NMPlatform *platform, NMDevice *self) { NMDevicePrivate *priv; + NMPlatformIP6Address *addr; if (nm_device_get_ip_ifindex (self) != ifindex) return; priv = NM_DEVICE_GET_PRIVATE (self); + switch (obj_type) { case NMP_OBJECT_TYPE_IP4_ADDRESS: case NMP_OBJECT_TYPE_IP4_ROUTE: @@ -7840,6 +7879,14 @@ device_ipx_changed (NMPlatform *platform, } break; case NMP_OBJECT_TYPE_IP6_ADDRESS: + addr = platform_object; + + if ( (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->flags & IFA_F_DADFAILED) + || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->flags & IFA_F_TENTATIVE)) { + priv->dad6_failed_addrs = g_slist_append (priv->dad6_failed_addrs, + g_memdup (addr, sizeof (NMPlatformIP6Address))); + } + /* fallthrough */ case NMP_OBJECT_TYPE_IP6_ROUTE: if (!priv->queued_ip6_config_id) { priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self); @@ -9731,6 +9778,7 @@ finalize (GObject *object) g_free (priv->perm_hw_addr); g_free (priv->initial_hw_addr); g_slist_free_full (priv->pending_actions, g_free); + g_slist_free_full (priv->dad6_failed_addrs, g_free); g_clear_pointer (&priv->physical_port_id, g_free); g_free (priv->udi); g_free (priv->iface); diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c index ecb67ea7bb..93ea17e531 100644 --- a/src/nm-iface-helper.c +++ b/src/nm-iface-helper.c @@ -321,6 +321,20 @@ do_early_setup (int *argc, char **argv[]) global_opt.priority_v6 = (guint32) priority64_v6; } +static void +ip6_address_changed (NMPlatform *platform, + NMPObjectType obj_type, + int iface, + NMPlatformIP6Address *addr, + NMPlatformSignalChangeType change_type, + NMPlatformReason reason, + NMRDisc *rdisc) +{ + if ( (change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->flags & IFA_F_DADFAILED) + || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->flags & IFA_F_TENTATIVE)) + nm_rdisc_dad_failed (rdisc, &addr->address); +} + int main (int argc, char *argv[]) { @@ -467,6 +481,10 @@ main (int argc, char *argv[]) nm_platform_sysctl_set (NM_PLATFORM_GET, nm_utils_ip6_property_path (global_opt.ifname, "accept_ra_pinfo"), "0"); nm_platform_sysctl_set (NM_PLATFORM_GET, nm_utils_ip6_property_path (global_opt.ifname, "accept_ra_rtr_pref"), "0"); + g_signal_connect (NM_PLATFORM_GET, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + G_CALLBACK (ip6_address_changed), + rdisc); g_signal_connect (rdisc, NM_RDISC_CONFIG_CHANGED, G_CALLBACK (rdisc_config_changed), diff --git a/src/rdisc/nm-fake-rdisc.c b/src/rdisc/nm-fake-rdisc.c index edf5342209..b66f958039 100644 --- a/src/rdisc/nm-fake-rdisc.c +++ b/src/rdisc/nm-fake-rdisc.c @@ -274,6 +274,7 @@ receive_ra (gpointer user_data) .timestamp = item->timestamp, .lifetime = item->lifetime, .preferred = item->preferred, + .dad_counter = 0, }; if (nm_rdisc_complete_and_add_address (rdisc, &address)) diff --git a/src/rdisc/nm-rdisc.c b/src/rdisc/nm-rdisc.c index a7c7f631c7..66982f1163 100644 --- a/src/rdisc/nm-rdisc.c +++ b/src/rdisc/nm-rdisc.c @@ -385,6 +385,28 @@ nm_rdisc_start (NMRDisc *rdisc) solicit (rdisc); } +void +nm_rdisc_dad_failed (NMRDisc *rdisc, struct in6_addr *address) +{ + int i; + gboolean changed = FALSE; + + for (i = 0; i < rdisc->addresses->len; i++) { + NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i); + + if (!IN6_ARE_ADDR_EQUAL (&item->address, address)) + continue; + + _LOGD ("DAD failed for discovered address %s", nm_utils_inet6_ntop (address, NULL)); + if (!complete_address (rdisc, item)) + g_array_remove_index (rdisc->addresses, i--); + changed = TRUE; + } + + if (changed) + g_signal_emit_by_name (rdisc, NM_RDISC_CONFIG_CHANGED, NM_RDISC_CONFIG_ADDRESSES); +} + #define CONFIG_MAP_MAX_STR 7 static void diff --git a/src/rdisc/nm-rdisc.h b/src/rdisc/nm-rdisc.h index def63ee6fc..4802e240fb 100644 --- a/src/rdisc/nm-rdisc.h +++ b/src/rdisc/nm-rdisc.h @@ -61,6 +61,7 @@ typedef struct { typedef struct { struct in6_addr address; + guint8 dad_counter; guint32 timestamp; guint32 lifetime; guint32 preferred; @@ -143,5 +144,6 @@ GType nm_rdisc_get_type (void); gboolean nm_rdisc_set_iid (NMRDisc *rdisc, const NMUtilsIPv6IfaceId iid); void nm_rdisc_start (NMRDisc *rdisc); +void nm_rdisc_dad_failed (NMRDisc *rdisc, struct in6_addr *address); #endif /* __NETWORKMANAGER_RDISC_H__ */