summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLubomir Rintel <lkundrak@v3.sk>2015-10-03 19:50:30 +0200
committerLubomir Rintel <lkundrak@v3.sk>2015-11-02 20:27:35 +0100
commitf85728ecff824b1fece43aba51d8171db2766ea2 (patch)
tree7bd8e4f8faad5eb277b1ac976614dcda168dae22
parentb3e0811b811b8021dc52b32b88e13468494d9d7a (diff)
downloadNetworkManager-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.c60
-rw-r--r--src/nm-iface-helper.c18
-rw-r--r--src/rdisc/nm-fake-rdisc.c1
-rw-r--r--src/rdisc/nm-rdisc.c22
-rw-r--r--src/rdisc/nm-rdisc.h2
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__ */