mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-30 23:40:10 +01:00
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.
This commit is contained in:
parent
b3e0811b81
commit
f85728ecff
5 changed files with 97 additions and 6 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue