From 3ce80c8d6572b5cfcb7a8367147ef59608038424 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 12:12:20 +0200 Subject: [PATCH 1/7] device: simplify handling of IP config changes while initializing link This simplifies commit 31ca7962f8f7d1993f0a363b9677c7cee89e7ee3. We don't need the boolean flags like "queued_ip4_config_pending" to track whether we received any platform signals while being not yet initialized in platform (udev, NM_UNMANAGED_PLATFORM_INIT). In general, as long as the device is NM_UNMANAGED_PLATFORM_INIT, all platform signals are ignored. And when the device becomes managed, we schedule anyway an initial config-change. (cherry picked from commit 18ecc4b4f1d147fb9bd5b90080ab53fe146b0348) --- src/devices/nm-device.c | 56 +++++++++++++---------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index c86d8dc7df..5b417d0b43 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -280,11 +280,6 @@ typedef struct _NMDevicePrivate { bool real:1; - /* there was a IP config change, but no idle action was scheduled because device - * is still not platform-init */ - bool queued_ip4_config_pending:1; - bool queued_ip6_config_pending:1; - bool update_ip_config_completed_v4:1; bool update_ip_config_completed_v6:1; @@ -4070,9 +4065,6 @@ realize_start_setup (NMDevice *self, _notify (self, PROP_UDI); } - priv->queued_ip4_config_pending = TRUE; - priv->queued_ip6_config_pending = TRUE; - nm_device_update_hw_address (self); nm_device_update_initial_hw_address (self); nm_device_update_permanent_hw_address (self, FALSE); @@ -10229,13 +10221,11 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type) } if (IS_IPv4) { - priv->queued_ip4_config_pending = FALSE; dhcp4_cleanup (self, cleanup_type, FALSE); arp_cleanup (self); dnsmasq_cleanup (self); ipv4ll_cleanup (self); } else { - priv->queued_ip6_config_pending = FALSE; g_clear_object (&priv->dad6_ip6_config); dhcp6_cleanup (self, cleanup_type, FALSE); nm_clear_g_source (&priv->linklocal6_timeout_id); @@ -12244,8 +12234,6 @@ queued_ip_config_change (NMDevice *self, int addr_family) priv = NM_DEVICE_GET_PRIVATE (self); - nm_assert (IS_IPv4 ? !priv->queued_ip4_config_pending : !priv->queued_ip6_config_pending); - /* Wait for any queued state changes */ if (priv->queued_state.id) return G_SOURCE_CONTINUE; @@ -12261,13 +12249,10 @@ queued_ip_config_change (NMDevice *self, int addr_family) ? activate_stage5_ip4_config_result : activate_stage5_ip6_config_commit, addr_family)) { - if (IS_IPv4) { - priv->queued_ip4_config_pending = FALSE; + if (IS_IPv4) priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self); - } else { - priv->queued_ip6_config_pending = FALSE; + else priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self); - } _LOGT (LOGD_DEVICE, "IP%c update was postponed", nm_utils_addr_family_to_char (addr_family)); } else { @@ -12378,16 +12363,20 @@ device_ipx_changed (NMPlatform *platform, if (nm_device_get_ip_ifindex (self) != ifindex) return; + if (!nm_device_is_real (self)) + return; + + if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) { + /* ignore all platform signals until the link is initialized in platform. */ + return; + } + priv = NM_DEVICE_GET_PRIVATE (self); switch (obj_type) { case NMP_OBJECT_TYPE_IP4_ADDRESS: case NMP_OBJECT_TYPE_IP4_ROUTE: - if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) { - priv->queued_ip4_config_pending = TRUE; - nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4)); - } else if (!priv->queued_ip_config_id_4) { - priv->queued_ip4_config_pending = FALSE; + if (!priv->queued_ip_config_id_4) { priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self); _LOGD (LOGD_DEVICE, "queued IP4 config change"); } @@ -12401,13 +12390,10 @@ device_ipx_changed (NMPlatform *platform, priv->dad6_failed_addrs = g_slist_prepend (priv->dad6_failed_addrs, (gpointer) nmp_object_ref (NMP_OBJECT_UP_CAST (addr))); } + /* fall through */ case NMP_OBJECT_TYPE_IP6_ROUTE: - if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) { - priv->queued_ip6_config_pending = TRUE; - nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6)); - } else if (!priv->queued_ip_config_id_6) { - priv->queued_ip6_config_pending = FALSE; + if (!priv->queued_ip_config_id_6) { priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self); _LOGD (LOGD_DEVICE, "queued IP6 config change"); } @@ -12674,17 +12660,11 @@ _set_unmanaged_flags (NMDevice *self, !!unmanaged); } - if (priv->queued_ip4_config_pending) { - priv->queued_ip4_config_pending = FALSE; - nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4)); - priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self); - } - - if (priv->queued_ip6_config_pending) { - priv->queued_ip6_config_pending = FALSE; - nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6)); - priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self); - } + /* trigger an initial update of IP configuration. */ + nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_4)); + nm_assert_se (!nm_clear_g_source (&priv->queued_ip_config_id_6)); + priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self); + priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self); if (!priv->pending_actions) { do_notify_has_pending_actions = TRUE; From 171001c34db7985f4d75319201f2f873460ddd7a Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 12:16:09 +0200 Subject: [PATCH 2/7] device: clear dad6_failed_addrs in _cleanup_ip_pre() We also cancel the idle handler nm_clear_g_source (&priv->queued_ip_config_id_x[IS_IPv4]) which means, nobody is going to process these addresses (at least for the moment). The purpose of "dad6_failed_addrs" is to keep track of addresses that might be interesting for checking about DAD failures. If we are no longer reacting on IP changes (because the idle handler was removed), we also no longer need these addresses. (cherry picked from commit dbb936e5c8f69887c7bef97965f7035616be1baf) --- src/devices/nm-device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 5b417d0b43..b575f26be6 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -10226,6 +10226,8 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type) dnsmasq_cleanup (self); ipv4ll_cleanup (self); } else { + g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); + priv->dad6_failed_addrs = NULL; g_clear_object (&priv->dad6_ip6_config); dhcp6_cleanup (self, cleanup_type, FALSE); nm_clear_g_source (&priv->linklocal6_timeout_id); From b86d8968d5f8b9b5a8e43da3bd77294527f268ef Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 12:22:51 +0200 Subject: [PATCH 3/7] device: simplify postponing IP config change in queued_ip_config_change() We don't need to cancel the current idle-action and schedule a new one. Just return and wait to be called again. Also, drop the logging. Similarly, we don't log the postponing for the previous case either. (cherry picked from commit 63cf5bd249ba3d8674003560515e2ef6a27a5efc) --- src/devices/nm-device.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index b575f26be6..5575019c60 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -12240,8 +12240,6 @@ queued_ip_config_change (NMDevice *self, int addr_family) if (priv->queued_state.id) return G_SOURCE_CONTINUE; - priv->queued_ip_config_id_x[IS_IPv4] = 0; - /* If a commit is scheduled, this function would potentially interfere with * it changing IP configurations before they are applied. Postpone the * update in such case. @@ -12250,22 +12248,18 @@ queued_ip_config_change (NMDevice *self, int addr_family) IS_IPv4 ? activate_stage5_ip4_config_result : activate_stage5_ip6_config_commit, - addr_family)) { - if (IS_IPv4) - priv->queued_ip_config_id_4 = g_idle_add (queued_ip4_config_change, self); - else - priv->queued_ip_config_id_6 = g_idle_add (queued_ip6_config_change, self); - _LOGT (LOGD_DEVICE, "IP%c update was postponed", - nm_utils_addr_family_to_char (addr_family)); - } else { - update_ip_config (self, addr_family); + addr_family)) + return G_SOURCE_CONTINUE; - if (!IS_IPv4) { - /* Check whether we need to complete waiting for link-local. - * We are also called from an idle handler, so no problem doing state transitions - * now. */ - linklocal6_check_complete (self); - } + priv->queued_ip_config_id_x[IS_IPv4] = 0; + + update_ip_config (self, addr_family); + + if (!IS_IPv4) { + /* Check whether we need to complete waiting for link-local. + * We are also called from an idle handler, so no problem doing state transitions + * now. */ + linklocal6_check_complete (self); } if (!IS_IPv4) { From f5b48bd97389df5b747b71dc83e09f51a3437c4d Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 12:46:02 +0200 Subject: [PATCH 4/7] device: split handling for dad6_failed_addrs and dad6_ip6_config in queued_ip_config_change() There is no change in behavior, however don't handle dad6_failed_addrs and dad6_ip6_config in the same block. While both parts are related to IPv6 DAD, they do something rather different: - the first block, checks all candidates from dad6_failed_addrs whether they actually indicate DAD failed, and handles them by notifying NMNDisc about failed addresses. - the second block, checks whether we have now all addresses from dad6_ip6_config that we are waiting for. Split the blocks. (cherry picked from commit 3fcdba1a19176bfb3595ef4c2b948f604af5dd54) --- src/devices/nm-device.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 5575019c60..e2003b57be 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -12228,9 +12228,7 @@ static gboolean queued_ip_config_change (NMDevice *self, int addr_family) { NMDevicePrivate *priv; - gboolean need_ipv6ll = FALSE; const gboolean IS_IPv4 = (addr_family == AF_INET); - NMPlatform *platform; g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE); @@ -12263,6 +12261,9 @@ queued_ip_config_change (NMDevice *self, int addr_family) } if (!IS_IPv4) { + NMPlatform *platform; + gboolean need_ipv6ll = FALSE; + if ( priv->state < NM_DEVICE_STATE_DEACTIVATING && (platform = nm_device_get_platform (self)) && nm_platform_link_get (platform, priv->ifindex)) { @@ -12303,7 +12304,9 @@ queued_ip_config_change (NMDevice *self, int addr_family) g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); priv->dad6_failed_addrs = NULL; } + } + if (!IS_IPv4) { /* Check if DAD is still pending */ if ( priv->ip6_state == IP_CONF && priv->dad6_ip6_config From b638edf4a04b3807f8e6206af9b2a11e58b43712 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 12:53:43 +0200 Subject: [PATCH 5/7] device: refactor handling dad6_failed_addrs in queued_ip_config_change() Whenever we process queued IP changes, we must handle all pending dad6_failed_addrs. This is, to ensure we don't accumulate more and more addresses in the list. Rework the code, by stealing the entire list once at the beginning dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs); and free it at the end: g_slist_free_full (dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); This makes it easier to see, that we always process all addresses in priv->dad6_failed_addrs. (cherry picked from commit e2c13af805f983504240f27c544d52ab29936be7) --- src/devices/nm-device.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index e2003b57be..e3f2d4c71b 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -12263,18 +12263,18 @@ queued_ip_config_change (NMDevice *self, int addr_family) if (!IS_IPv4) { NMPlatform *platform; gboolean need_ipv6ll = FALSE; + GSList *dad6_failed_addrs, *iter; + + dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs); if ( priv->state < NM_DEVICE_STATE_DEACTIVATING && (platform = nm_device_get_platform (self)) && nm_platform_link_get (platform, priv->ifindex)) { /* Handle DAD failures */ - while (priv->dad6_failed_addrs) { - nm_auto_nmpobj const NMPObject *obj = NULL; + for (iter = dad6_failed_addrs; iter; iter = iter->next) { + const NMPObject *obj = iter->data; const NMPlatformIP6Address *addr; - obj = priv->dad6_failed_addrs->data; - priv->dad6_failed_addrs = g_slist_delete_link (priv->dad6_failed_addrs, priv->dad6_failed_addrs); - if (!nm_ndisc_dad_addr_is_fail_candidate (platform, obj)) continue; @@ -12300,10 +12300,9 @@ queued_ip_config_change (NMDevice *self, int addr_family) if (need_ipv6ll) check_and_add_ipv6ll_addr (self); - } else { - g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); - priv->dad6_failed_addrs = NULL; } + + g_slist_free_full (dad6_failed_addrs, (GDestroyNotify) nmp_object_unref); } if (!IS_IPv4) { From e7ea66eb1ac9921d36f098d3dc069f370bc8d201 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 13:06:30 +0200 Subject: [PATCH 6/7] device: emit IP address changes in queued_ip_config_change() only once We first iterate over addresses that might have failed IPv6 DAD and update the state in NMNDisc. However, while we do that, don't yet invoke the changed signal. Otherwise, we will invoke it multiple times (in case multiple addresses failed). Instead, keep track of whether something changed, and handle it once a bit later. (cherry picked from commit f312620276a3756fbc00362b5330b30a05a18cc6) --- src/devices/nm-device.c | 10 +++++++--- src/ndisc/nm-ndisc.c | 18 ++++++++++-------- src/ndisc/nm-ndisc.h | 7 ++++++- src/nm-iface-helper.c | 3 ++- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index e3f2d4c71b..51c15bcc16 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -12262,7 +12262,6 @@ queued_ip_config_change (NMDevice *self, int addr_family) if (!IS_IPv4) { NMPlatform *platform; - gboolean need_ipv6ll = FALSE; GSList *dad6_failed_addrs, *iter; dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs); @@ -12270,6 +12269,9 @@ queued_ip_config_change (NMDevice *self, int addr_family) if ( priv->state < NM_DEVICE_STATE_DEACTIVATING && (platform = nm_device_get_platform (self)) && nm_platform_link_get (platform, priv->ifindex)) { + gboolean need_ipv6ll = FALSE; + NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE; + /* Handle DAD failures */ for (iter = dad6_failed_addrs; iter; iter = iter->next) { const NMPObject *obj = iter->data; @@ -12286,9 +12288,12 @@ queued_ip_config_change (NMDevice *self, int addr_family) if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) need_ipv6ll = TRUE; else if (priv->ndisc) - nm_ndisc_dad_failed (priv->ndisc, &addr->address); + ndisc_config_changed |= nm_ndisc_dad_failed (priv->ndisc, &addr->address, FALSE); } + if (ndisc_config_changed != NM_NDISC_CONFIG_NONE) + nm_ndisc_emit_config_change (priv->ndisc, ndisc_config_changed); + /* 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 @@ -12297,7 +12302,6 @@ queued_ip_config_change (NMDevice *self, int addr_family) if ( priv->ip_config_6 && nm_ip6_config_get_num_addresses (priv->ip_config_6)) need_ipv6ll = TRUE; - if (need_ipv6ll) check_and_add_ipv6ll_addr (self); } diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c index ba61cb1125..04f8631298 100644 --- a/src/ndisc/nm-ndisc.c +++ b/src/ndisc/nm-ndisc.c @@ -231,8 +231,8 @@ _data_complete (NMNDiscDataInternal *data) return &data->public; } -static void -_emit_config_change (NMNDisc *self, NMNDiscConfigMap changed) +void +nm_ndisc_emit_config_change (NMNDisc *self, NMNDiscConfigMap changed) { _config_changed_log (self, changed); g_signal_emit (self, signals[CONFIG_RECEIVED], 0, @@ -743,7 +743,7 @@ nm_ndisc_set_iid (NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid) if (rdata->addresses->len) { _LOGD ("IPv6 interface identifier changed, flushing addresses"); g_array_remove_range (rdata->addresses, 0, rdata->addresses->len); - _emit_config_change (ndisc, NM_NDISC_CONFIG_ADDRESSES); + nm_ndisc_emit_config_change (ndisc, NM_NDISC_CONFIG_ADDRESSES); solicit_routers (ndisc); } return TRUE; @@ -796,8 +796,8 @@ nm_ndisc_start (NMNDisc *ndisc) } } -void -nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address) +NMNDiscConfigMap +nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address, gboolean emit_changed_signal) { NMNDiscDataInternal *rdata; guint i; @@ -819,8 +819,10 @@ nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address) i++; } - if (changed) - _emit_config_change (ndisc, NM_NDISC_CONFIG_ADDRESSES); + if (emit_changed_signal && changed) + nm_ndisc_emit_config_change (ndisc, NM_NDISC_CONFIG_ADDRESSES); + + return changed ? NM_NDISC_CONFIG_ADDRESSES : NM_NDISC_CONFIG_NONE; } #define CONFIG_MAP_MAX_STR 7 @@ -1131,7 +1133,7 @@ check_timestamps (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed) clean_dns_domains (ndisc, now, &changed, &nextevent); if (changed) - _emit_config_change (ndisc, changed); + nm_ndisc_emit_config_change (ndisc, changed); if (nextevent != G_MAXINT32) { if (nextevent <= now) diff --git a/src/ndisc/nm-ndisc.h b/src/ndisc/nm-ndisc.h index 9a8a27d7af..fdc5615f54 100644 --- a/src/ndisc/nm-ndisc.h +++ b/src/ndisc/nm-ndisc.h @@ -100,6 +100,7 @@ typedef struct { } NMNDiscDNSDomain; typedef enum { + NM_NDISC_CONFIG_NONE = 0, NM_NDISC_CONFIG_DHCP_LEVEL = 1 << 0, NM_NDISC_CONFIG_GATEWAYS = 1 << 1, NM_NDISC_CONFIG_ADDRESSES = 1 << 2, @@ -171,13 +172,17 @@ typedef struct { GType nm_ndisc_get_type (void); +void nm_ndisc_emit_config_change (NMNDisc *self, NMNDiscConfigMap changed); + int nm_ndisc_get_ifindex (NMNDisc *self); const char *nm_ndisc_get_ifname (NMNDisc *self); NMNDiscNodeType nm_ndisc_get_node_type (NMNDisc *self); gboolean nm_ndisc_set_iid (NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid); void nm_ndisc_start (NMNDisc *ndisc); -void nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address); +NMNDiscConfigMap nm_ndisc_dad_failed (NMNDisc *ndisc, + const struct in6_addr *address, + gboolean emit_changed_signal); void nm_ndisc_set_config (NMNDisc *ndisc, const GArray *addresses, const GArray *dns_servers, diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c index 58d766c124..308c9e1f5a 100644 --- a/src/nm-iface-helper.c +++ b/src/nm-iface-helper.c @@ -339,7 +339,8 @@ dad_failed_handle_idle (gpointer user_data) if (nm_ndisc_dad_addr_is_fail_candidate (data->platform, obj)) { nm_ndisc_dad_failed (data->ndisc, - &NMP_OBJECT_CAST_IP6_ADDRESS (obj)->address); + &NMP_OBJECT_CAST_IP6_ADDRESS (obj)->address, + TRUE); } } From 1609344eda7f8c35d69097562b4a3dae2d34706c Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 20 Jun 2018 13:23:51 +0200 Subject: [PATCH 7/7] device: only check for IPv6 DAD and link-local address on actively managed devices In device_ipx_changed() we only keep track of dad6_failed_addrs addresses if the device's state is > DISCONNECTED. For the same reason, we should also do that in queued_ip_config_change(). But it's worse. If the device is in state disconnected, and the user externally adds IPv6 addresses, we will end up in queued_ip_config_change(). It is easily possible that "need_ipv6ll" ends up being TRUE, which results in a call to check_and_add_ipv6ll_addr() and later possibly ip_config_merge_and_apply (self, AF_INET6, TRUE); This in turn will modify the IP configuration on the device, although the device may be externally managed and NetworkManager shouldn't touch it. https://bugzilla.redhat.com/show_bug.cgi?id=1593210 (cherry picked from commit 890c7486430e9448036773b49e493af7f9977b31) --- src/devices/nm-device.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 51c15bcc16..5a5cb50e9f 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -12266,7 +12266,9 @@ queued_ip_config_change (NMDevice *self, int addr_family) dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs); - if ( priv->state < NM_DEVICE_STATE_DEACTIVATING + if ( priv->state > NM_DEVICE_STATE_DISCONNECTED + && priv->state < NM_DEVICE_STATE_DEACTIVATING + && !nm_device_sys_iface_state_is_external (self) && (platform = nm_device_get_platform (self)) && nm_platform_link_get (platform, priv->ifindex)) { gboolean need_ipv6ll = FALSE;