about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-09-23 10:11:11 +0200
committerMichael Biebl <biebl@debian.org>2018-09-23 10:11:11 +0200
commit135c3be0fb0eedb7a1b493ffde6c1ab3d23771f6 (patch)
tree40408d2ad887437e3ca63ff7fc6334bf22ae3fe6 /src
parent81e5928f26b40b0227604ccaf4b051fe14c9b3ef (diff)
parente126f3e804c35480c4f075777430419d6ece23da (diff)
Update upstream source from tag 'upstream/1.12.4'
Update to upstream version '1.12.4'
with Debian dir 650b52e60bf462f2972d0c33deec2cb171953445
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device.c82
-rw-r--r--src/devices/wifi/nm-device-wifi.c1
-rw-r--r--src/devices/wifi/nm-wifi-common.c2
-rw-r--r--src/dhcp/nm-dhcp-client.c33
-rw-r--r--src/dhcp/nm-dhcp-client.h4
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c8
-rw-r--r--src/dns/nm-dns-dnsmasq.c10
-rw-r--r--src/nm-connectivity.c236
-rw-r--r--src/nm-connectivity.h7
-rw-r--r--src/nm-ip4-config.c31
-rw-r--r--src/nm-ip4-config.h1
-rw-r--r--src/nm-ip6-config.c31
-rw-r--r--src/nm-ip6-config.h2
-rw-r--r--src/nm-manager.c18
-rw-r--r--src/ppp/nm-ppp-manager.c10
-rw-r--r--src/supplicant/nm-supplicant-interface.c2
-rw-r--r--src/vpn/nm-vpn-connection.c13
17 files changed, 360 insertions, 131 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 19e43762..33dd5e50 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -2615,7 +2615,6 @@ static void
 concheck_cb (NMConnectivity *connectivity,
              NMConnectivityCheckHandle *c_handle,
              NMConnectivityState state,
-             GError *error,
              gpointer user_data)
 {
 	_nm_unused gs_unref_object NMDevice *self_keep_alive = NULL;
@@ -2636,7 +2635,7 @@ concheck_cb (NMConnectivity *connectivity,
 	handle->c_handle = NULL;
 	self = handle->self;
 
-	if (nm_utils_error_is_cancelled (error, FALSE)) {
+	if (state == NM_CONNECTIVITY_CANCELLED) {
 		/* the only place where we nm_connectivity_check_cancel(@c_handle), is
 		 * from inside concheck_handle_complete(). This is a recursive call,
 		 * nothing to do. */
@@ -2645,15 +2644,14 @@ concheck_cb (NMConnectivity *connectivity,
 		return;
 	}
 
+	/* we keep NMConnectivity instance alive. It cannot be disposing. */
+	nm_assert (state != NM_CONNECTIVITY_DISPOSING);
+
 	self_keep_alive = g_object_ref (self);
 
-	_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s%s%s%s)",
+	_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s)",
 	       (long long unsigned) handle->seq,
-	       nm_connectivity_state_to_string (state),
-	       NM_PRINT_FMT_QUOTED (error, ", error: ", error->message, "", ""));
-
-	/* we keep NMConnectivity instance alive. It cannot be disposing. */
-	nm_assert (!nm_utils_error_is_cancelled (error, TRUE));
+	       nm_connectivity_state_to_string (state));
 
 	/* keep @self alive, while we invoke callbacks. */
 	priv = NM_DEVICE_GET_PRIVATE (self);
@@ -4236,6 +4234,9 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		}
 	}
 
+	nm_clear_g_source (&priv->queued_ip_config_id_4);
+	nm_clear_g_source (&priv->queued_ip_config_id_6);
+
 	g_object_freeze_notify (G_OBJECT (self));
 	NM_DEVICE_GET_CLASS (self)->unrealize_notify (self);
 
@@ -10337,10 +10338,37 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			_set_ip_state (self, AF_INET, IP_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
-		} else {
-			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
-				_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
+			return;
+		}
+
+		if (s_ip4_old && s_ip4_new) {
+			gint64 metric_old, metric_new;
+
+			/* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is
+			 * set at activation/renewal time using the value from static
+			 * configuration. To support runtime change we need to update the
+			 * dynamic configuration in place and tell the DHCP client the new
+			 * value to use for future renewals.
+			 */
+			metric_old = nm_setting_ip_config_get_route_metric (s_ip4_old);
+			metric_new = nm_setting_ip_config_get_route_metric (s_ip4_new);
+
+			if (metric_old != metric_new) {
+				if (priv->dev_ip4_config.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip4_config.orig,
+					                                    nm_device_get_route_metric (self, AF_INET));
+				}
+				if (priv->wwan_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->wwan_ip_config_4.orig,
+					                                    nm_device_get_route_metric (self, AF_INET));
+				}
+				if (priv->dhcp4.client)
+					nm_dhcp_client_set_route_metric (priv->dhcp4.client, metric_new);
+			}
 		}
+
+		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
+			_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 	}
 }
 
@@ -10382,10 +10410,36 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 			_set_ip_state (self, AF_INET6, IP_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
-		} else {
-			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
-				_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
+			return;
+		}
+
+		if (s_ip6_old && s_ip6_new) {
+			gint64 metric_old, metric_new;
+
+			/* See comment in nm_device_reactivate_ip6_config() */
+			metric_old = nm_setting_ip_config_get_route_metric (s_ip6_old);
+			metric_new = nm_setting_ip_config_get_route_metric (s_ip6_new);
+
+			if (metric_old != metric_new) {
+				if (priv->ac_ip6_config.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->ac_ip6_config.orig,
+					                                    nm_device_get_route_metric (self, AF_INET6));
+				}
+				if (priv->dhcp6.ip6_config.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dhcp6.ip6_config.orig,
+					                                    nm_device_get_route_metric (self, AF_INET6));
+				}
+				if (priv->wwan_ip_config_6.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->wwan_ip_config_6.orig,
+					                                    nm_device_get_route_metric (self, AF_INET6));
+				}
+				if (priv->dhcp6.client)
+					nm_dhcp_client_set_route_metric (priv->dhcp6.client, metric_new);
+			}
 		}
+
+		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
+			_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
 	}
 }
 
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 6bd06524..0dd6fa74 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -2557,6 +2557,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	set_current_ap (self, ap, FALSE);
 	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
 	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+	g_object_unref (ap);
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 
 done:
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
index 8e079d11..c95620e7 100644
--- a/src/devices/wifi/nm-wifi-common.c
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -196,7 +196,7 @@ const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",            "s",  NM_DEVICE_HW_ADDRESS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PermHwAddress",        "s",  NM_DEVICE_PERM_HW_ADDRESS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",                 "u",  NM_DEVICE_WIFI_MODE),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("BitRate",              "u",  NM_DEVICE_WIFI_BITRATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Bitrate",              "u",  NM_DEVICE_WIFI_BITRATE),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AccessPoints",         "ao", NM_DEVICE_WIFI_ACCESS_POINTS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveAccessPoint",    "o",  NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WirelessCapabilities", "u",  NM_DEVICE_WIFI_CAPABILITIES),
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 360bd367..e53c8d87 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -51,7 +51,7 @@ enum {
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_ADDR_FAMILY,
 	PROP_FLAGS,
 	PROP_HWADDR,
@@ -163,6 +163,17 @@ nm_dhcp_client_get_route_table (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_table;
 }
 
+void
+nm_dhcp_client_set_route_table (NMDhcpClient *self, guint32 route_table)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (route_table != priv->route_table) {
+		priv->route_table = route_table;
+		_notify (self, PROP_ROUTE_TABLE);
+	}
+}
+
 guint32
 nm_dhcp_client_get_route_metric (NMDhcpClient *self)
 {
@@ -171,6 +182,17 @@ nm_dhcp_client_get_route_metric (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_metric;
 }
 
+void
+nm_dhcp_client_set_route_metric (NMDhcpClient *self, guint32 route_metric)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (route_metric != priv->route_metric) {
+		priv->route_metric = route_metric;
+		_notify (self, PROP_ROUTE_METRIC);
+	}
+}
+
 guint32
 nm_dhcp_client_get_timeout (NMDhcpClient *self)
 {
@@ -834,6 +856,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_ROUTE_METRIC:
 		g_value_set_uint (value, priv->route_metric);
 		break;
+	case PROP_ROUTE_TABLE:
+		g_value_set_uint (value, priv->route_table);
+		break;
 	case PROP_TIMEOUT:
 		g_value_set_uint (value, priv->timeout);
 		break;
@@ -889,11 +914,9 @@ set_property (GObject *object, guint prop_id,
 		priv->uuid = g_value_dup_string (value);
 		break;
 	case PROP_ROUTE_TABLE:
-		/* construct-only */
 		priv->route_table = g_value_get_uint (value);
 		break;
 	case PROP_ROUTE_METRIC:
-		/* construct-only */
 		priv->route_metric = g_value_get_uint (value);
 		break;
 	case PROP_TIMEOUT:
@@ -1002,13 +1025,13 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	obj_properties[PROP_ROUTE_TABLE] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_TABLE, "", "",
 	                       0, G_MAXUINT32, RT_TABLE_MAIN,
-	                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_ROUTE_METRIC] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_METRIC, "", "",
 	                       0, G_MAXUINT32, 0,
-	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_TIMEOUT] =
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index f3d0b7d1..4c196045 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -131,8 +131,12 @@ GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
+void nm_dhcp_client_set_route_table (NMDhcpClient *self, guint32 route_table);
+
 guint32 nm_dhcp_client_get_route_metric (NMDhcpClient *self);
 
+void nm_dhcp_client_set_route_metric (NMDhcpClient *self, guint32 route_metric);
+
 guint32 nm_dhcp_client_get_timeout (NMDhcpClient *self);
 
 GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 3bd14ebe..bf93c831 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -374,11 +374,15 @@ dhclient_start (NMDhcpClient *client,
 		if (g_file_copy (src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
 			/* Success; use the preferred leasefile path */
 			g_free (priv->lease_file);
-			priv->lease_file = g_strdup (g_file_get_path (dst));
+			priv->lease_file = g_file_get_path (dst);
 		} else {
+			gs_free char *s_path = NULL;
+			gs_free char *d_path = NULL;
+
 			/* Failure; just use the existing leasefile */
 			_LOGW ("failed to copy leasefile %s to %s: %s",
-			       g_file_get_path (src), g_file_get_path (dst),
+			       (s_path = g_file_get_path (src)),
+			       (d_path = g_file_get_path (dst)),
 			       error->message);
 			g_clear_error (&error);
 		}
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index b5b93280..91f4c55b 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -183,10 +183,12 @@ add_ip_config (NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsIPConfig
 			                        domain[0] ? domain : NULL);
 		}
 
-		for (j = 0; ip_data->domains.reverse[j]; j++) {
-			add_dnsmasq_nameserver (self, servers,
-			                        ip_addr_to_string_buf,
-			                        ip_data->domains.reverse[j]);
+		if (ip_data->domains.reverse) {
+			for (j = 0; ip_data->domains.reverse[j]; j++) {
+				add_dnsmasq_nameserver (self, servers,
+				                        ip_addr_to_string_buf,
+				                        ip_data->domains.reverse[j]);
+			}
 		}
 	}
 }
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 1ce6d3cf..db26218b 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -46,8 +46,10 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, int /*NMConnectivityState*/
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL,   "PORTAL"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
 
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_ERROR,    "ERROR"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FAKE,     "FAKE"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_ERROR,     "ERROR"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FAKE,      "FAKE"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_CANCELLED, "CANCELLED"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_DISPOSING, "DISPOSING"),
 );
 
 const char *
@@ -77,6 +79,10 @@ struct _NMConnectivityCheckHandle {
 	} concheck;
 #endif
 
+	const char *completed_log_message;
+	char *completed_log_message_free;
+	NMConnectivityState completed_state;
+
 	guint timeout_id;
 };
 
@@ -90,6 +96,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	CList handles_lst_head;
+	CList completed_handles_lst_head;
 	char *uri;
 	char *response;
 	gboolean enabled;
@@ -142,60 +149,37 @@ NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECT
 /*****************************************************************************/
 
 static void
-cb_data_invoke_callback (NMConnectivityCheckHandle *cb_data,
-                         NMConnectivityState state,
-                         GError *error,
-                         const char *log_message)
+cb_data_complete (NMConnectivityCheckHandle *cb_data,
+                  NMConnectivityState state,
+                  const char *log_message)
 {
-	NMConnectivityCheckCallback callback;
+	NMConnectivity *self;
 
 	nm_assert (cb_data);
 	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
-
-	callback = cb_data->callback;
-	if (!callback)
-		return;
-
-	cb_data->callback = NULL;
-
+	nm_assert (cb_data->callback);
+	nm_assert (state != NM_CONNECTIVITY_UNKNOWN);
 	nm_assert (log_message);
 
-	_LOG2D ("check completed: %s; %s",
-	        nm_connectivity_state_to_string (state),
-	        log_message);
-
-	callback (cb_data->self,
-	          cb_data,
-	          state,
-	          error,
-	          cb_data->user_data);
-}
-
-static void
-cb_data_free (NMConnectivityCheckHandle *cb_data,
-              NMConnectivityState state,
-              GError *error,
-              const char *log_message)
-{
-	NMConnectivity *self;
-
-	nm_assert (cb_data);
-
 	self = cb_data->self;
 
-	nm_assert (NM_IS_CONNECTIVITY (self));
+	/* mark the handle as completing. After this point, nm_connectivity_check_cancel()
+	 * is no longer possible. */
+	cb_data->self = NULL;
 
-	c_list_unlink (&cb_data->handles_lst);
+	c_list_unlink_stale (&cb_data->handles_lst);
 
 #if WITH_CONCHECK
 	if (cb_data->concheck.curl_ehandle) {
 		NMConnectivityPrivate *priv;
 
 		/* Contrary to what cURL manual claim it is *not* safe to remove
-		 * the easy handle "at any moment"; specifically not from the
-		 * write function. Thus here we just dissociate the cb_data from
-		 * the easy handle and the easy handle will be cleaned up when the
-		 * message goes to CURLMSG_DONE in _con_curl_check_connectivity(). */
+		 * the easy handle "at any moment"; specifically it's not safe to
+		 * remove *any* handle from within a libcurl callback. That is
+		 * why we queue completed handles in this case.
+		 *
+		 * cb_data_complete() is however only called *not* from within a
+		 * libcurl callback. So, this is fine. */
 		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEFUNCTION, NULL);
 		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_WRITEDATA, NULL);
 		curl_easy_setopt (cb_data->concheck.curl_ehandle, CURLOPT_HEADERFUNCTION, NULL);
@@ -214,7 +198,18 @@ cb_data_free (NMConnectivityCheckHandle *cb_data,
 
 	nm_clear_g_source (&cb_data->timeout_id);
 
-	cb_data_invoke_callback (cb_data, state, error, log_message);
+	_LOG2D ("check completed: %s; %s",
+	        nm_connectivity_state_to_string (state),
+	        log_message);
+
+	cb_data->callback (self,
+	                   cb_data,
+	                   state,
+	                   cb_data->user_data);
+
+	/* Note: self might be a danling pointer at this point. It must not be used
+	 * after this point, and all callers must either take a reference first, or
+	 * not use the self pointer too. */
 
 #if WITH_CONCHECK
 	g_free (cb_data->concheck.response);
@@ -222,12 +217,54 @@ cb_data_free (NMConnectivityCheckHandle *cb_data,
 		g_string_free (cb_data->concheck.recv_msg, TRUE);
 #endif
 	g_free (cb_data->ifspec);
+	if (cb_data->completed_log_message_free)
+		g_free (cb_data->completed_log_message_free);
 	g_slice_free (NMConnectivityCheckHandle, cb_data);
 }
 
 /*****************************************************************************/
 
 #if WITH_CONCHECK
+
+static void
+cb_data_queue_completed (NMConnectivityCheckHandle *cb_data,
+                         NMConnectivityState state,
+                         const char *log_message_static,
+                         char *log_message_take /* take */)
+{
+	nm_assert (cb_data);
+	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
+	nm_assert (state != NM_CONNECTIVITY_UNKNOWN);
+	nm_assert (log_message_static || log_message_take);
+	nm_assert (cb_data->completed_state == NM_CONNECTIVITY_UNKNOWN);
+	nm_assert (!cb_data->completed_log_message);
+	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
+
+	cb_data->completed_state = state;
+	cb_data->completed_log_message = log_message_static ?: log_message_take;
+	cb_data->completed_log_message_free = log_message_take;
+
+	c_list_unlink_stale (&cb_data->handles_lst);
+	c_list_link_tail (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->completed_handles_lst_head, &cb_data->handles_lst);
+}
+
+static void
+_complete_queued (NMConnectivity *self)
+{
+	NMConnectivity *self_keep_alive = NULL;
+	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
+	NMConnectivityCheckHandle *cb_data;
+
+	while ((cb_data = c_list_first_entry (&priv->completed_handles_lst_head, NMConnectivityCheckHandle, handles_lst))) {
+		if (!self_keep_alive)
+			self_keep_alive = g_object_ref (self);
+		cb_data_complete (cb_data,
+		                  cb_data->completed_state,
+		                  cb_data->completed_log_message);
+	}
+	nm_g_object_unref (self_keep_alive);
+}
+
 static const char *
 _check_handle_get_response (NMConnectivityCheckHandle *cb_data)
 {
@@ -265,29 +302,37 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 			continue;
 		}
 
-		if (!cb_data->callback) {
-			/* callback was already invoked earlier. */
-			cb_data_free (cb_data, NM_CONNECTIVITY_UNKNOWN, NULL, NULL);
-		} else if (msg->data.result != CURLE_OK) {
-			gs_free char *log_message = NULL;
+		nm_assert (cb_data);
+		nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
 
-			log_message = g_strdup_printf ("check failed with curl status %d", msg->data.result);
-			cb_data_free (cb_data, NM_CONNECTIVITY_LIMITED, NULL,
-			              log_message);
+		if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
+			/* callback was already invoked earlier. Nothing to do. */
+			continue;
+		}
+
+		if (msg->data.result != CURLE_OK) {
+			cb_data_queue_completed (cb_data,
+			                         NM_CONNECTIVITY_LIMITED,
+			                         NULL,
+			                         g_strdup_printf ("check failed with curl status %d", msg->data.result));
 		} else if (   !((_check_handle_get_response (cb_data))[0])
 		           && (curl_easy_getinfo (msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK)
 		           && response_code == 204) {
 			/* If we got a 204 response code (no content) and we actually
 			 * requested no content, report full connectivity. */
-			cb_data_free (cb_data, NM_CONNECTIVITY_FULL, NULL,
-			              "no content, as expected");
+			cb_data_queue_completed (cb_data,
+			                         NM_CONNECTIVITY_FULL,
+			                         "no content, as expected",
+			                         NULL);
 		} else {
 			/* If we get here, it means that easy_write_cb() didn't read enough
 			 * bytes to be able to do a match, or that we were asking for no content
 			 * (204 response code) and we actually got some. Either way, that is
 			 * an indication of a captive portal */
-			cb_data_free (cb_data, NM_CONNECTIVITY_PORTAL, NULL,
-			              "unexpected short response");
+			cb_data_queue_completed (cb_data,
+			                         NM_CONNECTIVITY_PORTAL,
+			                         "unexpected short response",
+			                         NULL);
 		}
 	}
 
@@ -306,6 +351,7 @@ _con_curl_timeout_cb (gpointer user_data)
 
 	priv->concheck.curl_timer = 0;
 	_con_curl_check_connectivity (priv->concheck.curl_mhandle, CURL_SOCKET_TIMEOUT, 0);
+	_complete_queued (self);
 	return G_SOURCE_REMOVE;
 }
 
@@ -368,6 +414,8 @@ _con_curl_socketevent_cb (GIOChannel *ch, GIOCondition condition, gpointer user_
 			fdp->ev = 0;
 	}
 
+	_complete_queued (self);
+
 	return success ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
 }
 
@@ -419,10 +467,17 @@ easy_header_cb (char *buffer, size_t size, size_t nitems, void *userdata)
 	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nitems;
 
+	if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
+		/* already completed. */
+		return 0;
+	}
+
 	if (   len >= sizeof (HEADER_STATUS_ONLINE) - 1
 	    && !g_ascii_strncasecmp (buffer, HEADER_STATUS_ONLINE, sizeof (HEADER_STATUS_ONLINE) - 1)) {
-		cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_FULL,
-		                         NULL, "status header found");
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_FULL,
+		                         "status header found",
+		                         NULL);
 		return 0;
 	}
 
@@ -434,24 +489,33 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 {
 	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nmemb;
-	const char *response = _check_handle_get_response (cb_data);;
+	const char *response;
+
+	if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
+		/* already completed. */
+		return 0;
+	}
 
 	if (!cb_data->concheck.recv_msg)
 		cb_data->concheck.recv_msg = g_string_sized_new (len + 10);
 
 	g_string_append_len (cb_data->concheck.recv_msg, buffer, len);
 
+	response = _check_handle_get_response (cb_data);;
 	if (   response
 	    && cb_data->concheck.recv_msg->len >= strlen (response)) {
 		/* We already have enough data -- check response */
 		if (g_str_has_prefix (cb_data->concheck.recv_msg->str, response)) {
-			cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_FULL, NULL,
-			                         "expected response");
+			cb_data_queue_completed (cb_data,
+			                         NM_CONNECTIVITY_FULL,
+			                         "expected response",
+			                         NULL);
 		} else {
-			cb_data_invoke_callback (cb_data, NM_CONNECTIVITY_PORTAL, NULL,
-			                         "unexpected response");
+			cb_data_queue_completed (cb_data,
+			                         NM_CONNECTIVITY_PORTAL,
+			                         "unexpected response",
+			                         NULL);
 		}
-
 		return 0;
 	}
 
@@ -462,15 +526,11 @@ static gboolean
 _timeout_cb (gpointer user_data)
 {
 	NMConnectivityCheckHandle *cb_data = user_data;
-	NMConnectivity *self;
 
 	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
+	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
 
-	self = cb_data->self;
-
-	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (self)->handles_lst_head, &cb_data->handles_lst));
-
-	cb_data_free (cb_data, NM_CONNECTIVITY_LIMITED, NULL, "timeout");
+	cb_data_complete (cb_data, NM_CONNECTIVITY_LIMITED, "timeout");
 	return G_SOURCE_REMOVE;
 }
 #endif
@@ -490,9 +550,9 @@ _idle_cb (gpointer user_data)
 		/* the invocation was with an invalid ifname. It is a fail. */
 		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
 		             "no interface specified for connectivity check");
-		cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, NULL, "missing interface");
+		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "missing interface");
 	} else
-		cb_data_free (cb_data, NM_CONNECTIVITY_FAKE, NULL, "fake result");
+		cb_data_complete (cb_data, NM_CONNECTIVITY_FAKE, "fake result");
 	return G_SOURCE_REMOVE;
 }
 
@@ -516,6 +576,7 @@ nm_connectivity_check_start (NMConnectivity *self,
 	c_list_link_tail (&priv->handles_lst_head, &cb_data->handles_lst);
 	cb_data->callback = callback;
 	cb_data->user_data = user_data;
+	cb_data->completed_state = NM_CONNECTIVITY_UNKNOWN;
 
 	if (iface)
 		cb_data->ifspec = g_strdup_printf ("if!%s", iface);
@@ -556,22 +617,13 @@ nm_connectivity_check_start (NMConnectivity *self,
 void
 nm_connectivity_check_cancel (NMConnectivityCheckHandle *cb_data)
 {
-	NMConnectivity *self;
-	gs_free_error GError *error = NULL;
-
 	g_return_if_fail (cb_data);
+	g_return_if_fail (NM_IS_CONNECTIVITY (cb_data->self));
 
-	self = cb_data->self;
-
-	g_return_if_fail (NM_IS_CONNECTIVITY (self));
-	g_return_if_fail (!c_list_is_empty (&cb_data->handles_lst));
-	g_return_if_fail (cb_data->callback);
-
-	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (self)->handles_lst_head, &cb_data->handles_lst));
+	nm_assert (   c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head,           &cb_data->handles_lst)
+	           || c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->completed_handles_lst_head, &cb_data->handles_lst));
 
-	nm_utils_error_set_cancelled (&error, FALSE, "NMConnectivity");
-
-	cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, error, "cancelled");
+	cb_data_complete (cb_data, NM_CONNECTIVITY_CANCELLED, "cancelled");
 }
 
 /*****************************************************************************/
@@ -684,6 +736,7 @@ nm_connectivity_init (NMConnectivity *self)
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
 	c_list_init (&priv->handles_lst_head);
+	c_list_init (&priv->completed_handles_lst_head);
 
 	priv->config = g_object_ref (nm_config_get ());
 	g_signal_connect (G_OBJECT (priv->config),
@@ -715,16 +768,13 @@ dispose (GObject *object)
 	NMConnectivity *self = NM_CONNECTIVITY (object);
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 	NMConnectivityCheckHandle *cb_data;
-	GError *error = NULL;
-
-again:
-	c_list_for_each_entry (cb_data, &priv->handles_lst_head, handles_lst) {
-		if (!error)
-			nm_utils_error_set_cancelled (&error, TRUE, "NMConnectivity");
-		cb_data_free (cb_data, NM_CONNECTIVITY_ERROR, error, "shutting down");
-		goto again;
-	}
-	g_clear_error (&error);
+
+	nm_assert (c_list_is_empty (&priv->completed_handles_lst_head));
+
+	while ((cb_data = c_list_first_entry (&priv->handles_lst_head,
+	                                      NMConnectivityCheckHandle,
+	                                      handles_lst)))
+		cb_data_complete (cb_data, NM_CONNECTIVITY_DISPOSING, "shutting down");
 
 	g_clear_pointer (&priv->uri, g_free);
 	g_clear_pointer (&priv->response, g_free);
diff --git a/src/nm-connectivity.h b/src/nm-connectivity.h
index df9295e0..178f27ad 100644
--- a/src/nm-connectivity.h
+++ b/src/nm-connectivity.h
@@ -24,8 +24,10 @@
 
 #include "nm-dbus-interface.h"
 
-#define NM_CONNECTIVITY_ERROR ((NMConnectivityState) -1)
-#define NM_CONNECTIVITY_FAKE  ((NMConnectivityState) -2)
+#define NM_CONNECTIVITY_ERROR     ((NMConnectivityState) -1)
+#define NM_CONNECTIVITY_FAKE      ((NMConnectivityState) -2)
+#define NM_CONNECTIVITY_CANCELLED ((NMConnectivityState) -3)
+#define NM_CONNECTIVITY_DISPOSING ((NMConnectivityState) -4)
 
 #define NM_TYPE_CONNECTIVITY            (nm_connectivity_get_type ())
 #define NM_CONNECTIVITY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_CONNECTIVITY, NMConnectivity))
@@ -53,7 +55,6 @@ typedef struct _NMConnectivityCheckHandle NMConnectivityCheckHandle;
 typedef void (*NMConnectivityCheckCallback) (NMConnectivity *self,
                                              NMConnectivityCheckHandle *handle,
                                              NMConnectivityState state,
-                                             GError *error,
                                              gpointer user_data);
 
 NMConnectivityCheckHandle *nm_connectivity_check_start (NMConnectivity *self,
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 6ae81b57..657ebfc0 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -646,6 +646,37 @@ nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 }
 
 void
+nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric)
+{
+	gs_free NMPlatformIP4Route *routes = NULL;
+	gboolean need_update = FALSE;
+	const NMPlatformIP4Route *r;
+	NMDedupMultiIter iter;
+	guint num = 0, i = 0;
+
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
+		if (r->metric != metric)
+			need_update = TRUE;
+		num++;
+	}
+	if (!need_update)
+		return;
+
+	routes = g_new (NMPlatformIP4Route, num);
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
+		routes[i] = *r;
+		routes[i].metric = metric;
+		i++;
+	}
+
+	g_object_freeze_notify (G_OBJECT (self));
+	nm_ip4_config_reset_routes (self);
+	for (i = 0; i < num; i++)
+		nm_ip4_config_add_route (self, &routes[i], NULL);
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+void
 nm_ip4_config_add_dependent_routes (NMIP4Config *self,
                                     guint32 route_table,
                                     guint32 route_metric,
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 78aca14e..45c1fa3f 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -223,6 +223,7 @@ const NMPlatformIP4Route *_nmtst_ip4_config_get_route (const NMIP4Config *self,
 const NMPlatformIP4Route *nm_ip4_config_get_direct_route_for_host (const NMIP4Config *self,
                                                                    in_addr_t host,
                                                                    guint32 route_table);
+void nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric);
 
 void nm_ip4_config_reset_nameservers (NMIP4Config *self);
 void nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 nameserver);
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 9807d388..3869fee8 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -428,6 +428,37 @@ nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 }
 
 void
+nm_ip6_config_update_routes_metric (NMIP6Config *self, gint64 metric)
+{
+	gs_free NMPlatformIP6Route *routes = NULL;
+	gboolean need_update = FALSE;
+	const NMPlatformIP6Route *r;
+	NMDedupMultiIter iter;
+	guint num = 0, i = 0;
+
+	nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) {
+		if (r->metric != metric)
+			need_update = TRUE;
+		num++;
+	}
+	if (!need_update)
+		return;
+
+	routes = g_new (NMPlatformIP6Route, num);
+	nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) {
+		routes[i] = *r;
+		routes[i].metric = metric;
+		i++;
+	}
+
+	g_object_freeze_notify (G_OBJECT (self));
+	nm_ip6_config_reset_routes (self);
+	for (i = 0; i < num; i++)
+		nm_ip6_config_add_route (self, &routes[i], NULL);
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+void
 nm_ip6_config_add_dependent_routes (NMIP6Config *self,
                                     guint32 route_table,
                                     guint32 route_metric)
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 9762ef48..b8e80410 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -224,4 +224,6 @@ void nm_ip6_config_reset_routes_ndisc (NMIP6Config *self,
                                        guint32 route_metric,
                                        gboolean kernel_support_rta_pref);
 
+void nm_ip6_config_update_routes_metric (NMIP6Config *self, gint64 metric);
+
 #endif /* __NETWORKMANAGER_IP6_CONFIG_H__ */
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 289dcf83..ad906169 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -3008,10 +3008,21 @@ platform_link_added (NMManager *self,
 			continue;
 
 		if (nm_device_is_real (candidate)) {
-			/* Ignore the link added event since there's already a realized
-			 * device with the link's name.
+			/* There's already a realized device with the link's name
+			 * and a different ifindex.
 			 */
-			nm_device_update_from_platform_link (candidate, plink);
+			if (nm_device_get_ifindex (candidate) <= 0)
+				nm_device_update_from_platform_link (candidate, plink);
+			else {
+				/* The ifindex of a device can't be changed after
+				 * initialization because it is used as a key by
+				 * the dns-manager.
+				 */
+				_LOGD (LOGD_DEVICE, "(%s): removing old device %p after ifindex change from %d to %d",
+				       plink->name, candidate, nm_device_get_ifindex (candidate), ifindex);
+				remove_device (self, candidate, FALSE, TRUE);
+				goto add;
+			}
 			return;
 		} else if (nm_device_realize_start (candidate,
 		                                    plink,
@@ -3031,6 +3042,7 @@ platform_link_added (NMManager *self,
 		/* Try next unrealized device */
 	}
 
+add:
 	/* Try registered device factories */
 	factory = nm_device_factory_manager_find_factory_for_link_type (plink->type);
 	if (factory) {
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 5e893c85..40bdea64 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -442,7 +442,7 @@ impl_ppp_manager_set_ifindex (NMDBusObject *obj,
 
 	if (priv->ifindex >= 0) {
 		_LOGW ("can't change the ifindex from %d to %d", priv->ifindex, (int) ifindex);
-		return;
+		goto out;
 	}
 
 	if (ifindex > 0) {
@@ -462,7 +462,13 @@ impl_ppp_manager_set_ifindex (NMDBusObject *obj,
 
 	obj_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
 
-	g_signal_emit (self, signals[IFINDEX_SET], 0, ifindex, plink->name);
+	g_signal_emit (self,
+	               signals[IFINDEX_SET],
+	               0,
+	               ifindex,
+	               plink ? plink->name : NULL);
+
+out:
 	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 71e6a35a..e16e3130 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -111,7 +111,7 @@ typedef struct {
 	NMSupplicantInterfaceState state;
 	int            disconnect_reason;
 
-	gboolean       scanning:1;
+	bool           scanning:1;
 
 	bool           scan_done_pending:1;
 	bool           scan_done_success:1;
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index ad752c4c..91acf07e 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -2686,12 +2686,16 @@ plugin_interactive_secrets_required (NMVpnConnection *self,
 	gs_free const char **hints = NULL;
 	gs_free char *message_hint = NULL;
 
+	if (!NM_IN_SET (priv->vpn_state, STATE_CONNECT,
+	                                 STATE_NEED_AUTH)) {
+		_LOGD ("VPN plugin: requested secrets; state %s (%d); ignore request in current state",
+		       vpn_state_to_string (priv->vpn_state), priv->vpn_state);
+		return;
+	}
+
 	_LOGI ("VPN plugin: requested secrets; state %s (%d)",
 	       vpn_state_to_string (priv->vpn_state), priv->vpn_state);
 
-	g_return_if_fail (priv->vpn_state == STATE_CONNECT ||
-	                  priv->vpn_state == STATE_NEED_AUTH);
-
 	priv->secrets_idx = SECRETS_REQ_INTERACTIVE;
 	_set_vpn_state (self, STATE_NEED_AUTH, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE);
 
@@ -2758,6 +2762,9 @@ dispose (GObject *object)
 	NMVpnConnection *self = NM_VPN_CONNECTION (object);
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
+	if (priv->proxy)
+		g_signal_handlers_disconnect_by_data (priv->proxy, self);
+
 	nm_clear_g_source (&priv->start_timeout);
 
 	g_clear_pointer (&priv->connect_hash, g_variant_unref);