diff options
author | Lubomir Rintel <lkundrak@v3.sk> | 2015-10-03 19:50:30 +0200 |
---|---|---|
committer | Lubomir Rintel <lkundrak@v3.sk> | 2015-11-02 20:27:35 +0100 |
commit | f85728ecff824b1fece43aba51d8171db2766ea2 (patch) | |
tree | 7bd8e4f8faad5eb277b1ac976614dcda168dae22 | |
parent | b3e0811b811b8021dc52b32b88e13468494d9d7a (diff) | |
download | NetworkManager-f85728ecff824b1fece43aba51d8171db2766ea2.tar.gz |
core: support IPv6 duplicate address detection
NMDevice detects the DAD failures by watching the removal of tentative
addresses (happens for DAD of addresses with valid lifetime, typically
discovered addresses) or changes to addresses with dadfailed flag (permanent
addresses, typically link-local and manually configured addresses).
It retries creation of link-local addresses itself and lets RDisc know about
the rest so that it can decide if it's rdisc-managed address and retry
with a new address.
Currently NMDevice doesn't do anything useful about link-local address DAD
failures -- it just fails the link-local address addition instead of just
timing out, which happened before. RDisc just logs a warning and removes
the address from the list.
However, with RFC7217 stable privacy addresses the use of a different address
and thus a recovery from DAD failures would be possible.
-rw-r--r-- | src/devices/nm-device.c | 60 | ||||
-rw-r--r-- | src/nm-iface-helper.c | 18 | ||||
-rw-r--r-- | src/rdisc/nm-fake-rdisc.c | 1 | ||||
-rw-r--r-- | src/rdisc/nm-rdisc.c | 22 | ||||
-rw-r--r-- | src/rdisc/nm-rdisc.h | 2 |
5 files changed, 97 insertions, 6 deletions
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__ */ |