about summary refs log tree commit diff
path: root/src/devices/nm-device.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-08-28 01:13:48 +0200
committerMichael Biebl <biebl@debian.org>2015-08-28 01:13:48 +0200
commit81836c2d44802b4cca833d7775dd627e0797a7e2 (patch)
tree91154e6cefc0465306603f51ec0010d9963bbea4 /src/devices/nm-device.c
parent50a58f0fabd8a34c1b6108a107e08abe3c1ccd24 (diff)
Imported Upstream version 1.0.6 upstream/1.0.6
Diffstat (limited to 'src/devices/nm-device.c')
-rw-r--r--src/devices/nm-device.c438
1 files changed, 319 insertions, 119 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index c5d4c203..58895821 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -75,6 +75,10 @@ _LOG_DECLARE_SELF (NMDevice);
 static void impl_device_disconnect (NMDevice *self, DBusGMethodInvocation *context);
 static void impl_device_delete     (NMDevice *self, DBusGMethodInvocation *context);
 static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
+static gboolean ip_config_valid (NMDeviceState state);
+static void nm_device_update_metered (NMDevice *self);
+static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason);
+static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason);
 
 #include "nm-device-glue.h"
 
@@ -128,6 +132,7 @@ enum {
 	PROP_MASTER,
 	PROP_HW_ADDRESS,
 	PROP_HAS_PENDING_ACTION,
+	PROP_METERED,
 	LAST_PROP
 };
 
@@ -269,18 +274,20 @@ typedef struct {
 	struct {
 		gboolean v4_has;
 		gboolean v4_is_assumed;
-		gboolean v4_configure_first_time;
 		NMPlatformIP4Route v4;
 		gboolean v6_has;
 		gboolean v6_is_assumed;
-		gboolean v6_configure_first_time;
 		NMPlatformIP6Route v6;
 	} default_route;
 
+	gboolean v4_commit_first_time;
+	gboolean v6_commit_first_time;
+
 	/* DHCPv4 tracking */
 	NMDhcpClient *  dhcp4_client;
 	gulong          dhcp4_state_sigid;
 	NMDhcp4Config * dhcp4_config;
+	guint           dhcp4_restart_id;
 	NMIP4Config *   vpn4_config;  /* routes added by a VPN which uses this device */
 
 	guint           arp_round2_id;
@@ -325,6 +332,9 @@ typedef struct {
 	NMDhcp6Config * dhcp6_config;
 	/* IP6 config from DHCP */
 	NMIP6Config *   dhcp6_ip6_config;
+	/* Event ID of the current IP6 config from DHCP */
+	char *          dhcp6_event_id;
+	guint           dhcp6_restart_id;
 
 	/* allow autoconnect feature */
 	gboolean        autoconnect;
@@ -338,6 +348,8 @@ typedef struct {
 	gboolean        is_master;
 	GSList *        slaves;    /* list of SlaveInfo */
 
+	NMMetered       metered;
+
 	NMConnectionProvider *con_provider;
 } NMDevicePrivate;
 
@@ -687,6 +699,21 @@ nm_device_get_device_type (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->type;
 }
 
+/**
+ * nm_device_get_metered:
+ * @setting: the #NMDevice
+ *
+ * Returns: the #NMDevice:metered property of the device.
+ *
+ * Since: 1.0.6
+ **/
+NMMetered
+nm_device_get_metered (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_METERED_UNKNOWN);
+
+	return NM_DEVICE_GET_PRIVATE (self)->metered;
+}
 
 /**
  * nm_device_get_priority():
@@ -735,14 +762,14 @@ nm_device_get_priority (NMDevice *self)
 		return 400;
 	case NM_DEVICE_TYPE_BRIDGE:
 		return 425;
-	case NM_DEVICE_TYPE_MODEM:
-		return 450;
-	case NM_DEVICE_TYPE_BT:
-		return 550;
 	case NM_DEVICE_TYPE_WIFI:
 		return 600;
 	case NM_DEVICE_TYPE_OLPC_MESH:
 		return 650;
+	case NM_DEVICE_TYPE_MODEM:
+		return 700;
+	case NM_DEVICE_TYPE_BT:
+		return 750;
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -3181,6 +3208,8 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	nm_clear_g_source (&priv->dhcp4_restart_id);
+
 	if (priv->dhcp4_client) {
 		/* Stop any ongoing DHCP transaction on this device */
 		if (priv->dhcp4_state_sigid) {
@@ -3218,6 +3247,8 @@ ip4_config_merge_and_apply (NMDevice *self,
 	guint32 gateway;
 	gboolean connection_has_default_route, connection_is_never_default;
 	gboolean routes_full_sync;
+	gboolean ignore_auto_routes = FALSE;
+	gboolean ignore_auto_dns = FALSE;
 
 	/* Merge all the configs into the composite config */
 	if (config) {
@@ -3225,44 +3256,45 @@ ip4_config_merge_and_apply (NMDevice *self,
 		priv->dev_ip4_config = g_object_ref (config);
 	}
 
+	/* Apply ignore-auto-routes and ignore-auto-dns settings */
+	connection = nm_device_get_connection (self);
+	if (connection) {
+		NMSettingIPConfig *s_ip4 = nm_connection_get_setting_ip4_config (connection);
+
+		if (s_ip4) {
+			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip4);
+			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip4);
+		}
+	}
+
 	composite = nm_ip4_config_new ();
 
 	if (commit)
 		ensure_con_ip4_config (self);
 
-	if (priv->dev_ip4_config)
-		nm_ip4_config_merge (composite, priv->dev_ip4_config);
+	if (priv->dev_ip4_config) {
+		nm_ip4_config_merge (composite, priv->dev_ip4_config,
+		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+	}
 	if (priv->vpn4_config)
-		nm_ip4_config_merge (composite, priv->vpn4_config);
+		nm_ip4_config_merge (composite, priv->vpn4_config, NM_IP_CONFIG_MERGE_DEFAULT);
 	if (priv->ext_ip4_config)
-		nm_ip4_config_merge (composite, priv->ext_ip4_config);
+		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	if (priv->wwan_ip4_config)
-		nm_ip4_config_merge (composite, priv->wwan_ip4_config);
-
-	/* Apply ignore-auto-routes and ignore-auto-dns settings */
-	connection = nm_device_get_connection (self);
-	if (connection) {
-		NMSettingIPConfig *s_ip4 = nm_connection_get_setting_ip4_config (connection);
-
-		if (s_ip4) {
-			if (nm_setting_ip_config_get_ignore_auto_routes (s_ip4))
-				nm_ip4_config_reset_routes (composite);
-			if (nm_setting_ip_config_get_ignore_auto_dns (s_ip4)) {
-				nm_ip4_config_reset_nameservers (composite);
-				nm_ip4_config_reset_domains (composite);
-				nm_ip4_config_reset_searches (composite);
-			}
-		}
+	if (priv->wwan_ip4_config) {
+		nm_ip4_config_merge (composite, priv->wwan_ip4_config,
+		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
 	}
 
 	/* Merge user overrides into the composite config. For assumed connections,
 	 * con_ip4_config is empty. */
 	if (priv->con_ip4_config)
-		nm_ip4_config_merge (composite, priv->con_ip4_config);
+		nm_ip4_config_merge (composite, priv->con_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
 
 	/* Add the default route.
@@ -3282,21 +3314,26 @@ ip4_config_merge_and_apply (NMDevice *self,
 	priv->default_route.v4_has = FALSE;
 	priv->default_route.v4_is_assumed = TRUE;
 
-	routes_full_sync =    commit
-	                   && priv->default_route.v4_configure_first_time
-	                   && !nm_device_uses_assumed_connection (self);
-
 	if (!commit) {
 		/* during a non-commit event, we always pickup whatever is configured. */
 		goto END_ADD_DEFAULT_ROUTE;
 	}
 
+	if (nm_device_uses_generated_assumed_connection (self)) {
+		/* a generate-assumed-connection always detects the default route from platform */
+		goto END_ADD_DEFAULT_ROUTE;
+	}
+
+	/* At this point, we treat assumed and non-assumed connections alike.
+	 * For assumed connections we do that because we still manage RA and DHCP
+	 * leases for them, so we must extend/update the default route on commits.
+	 */
+
 	connection_has_default_route
 	    = nm_default_route_manager_ip4_connection_has_default_route (nm_default_route_manager_get (),
 	                                                                 connection, &connection_is_never_default);
 
-	if (   !priv->default_route.v4_configure_first_time
-	    && !nm_device_uses_assumed_connection (self)
+	if (   !priv->v4_commit_first_time
 	    && connection_is_never_default) {
 		/* If the connection is explicitly configured as never-default, we enforce the (absense of the)
 		 * default-route only once. That allows the user to configure a connection as never-default,
@@ -3304,14 +3341,8 @@ ip4_config_merge_and_apply (NMDevice *self,
 		goto END_ADD_DEFAULT_ROUTE;
 	}
 
-	/* At this point, we treat assumed and non-assumed connections alike.
-	 * For assumed connections we do that because we still manage RA and DHCP
-	 * leases for them, so we must extend/update the default route on commits.
-	 */
-
 	/* we are about to commit (for a non-assumed connection). Enforce whatever we have
 	 * configured. */
-	priv->default_route.v4_configure_first_time = FALSE;
 	priv->default_route.v4_is_assumed = FALSE;
 
 	if (!connection_has_default_route)
@@ -3323,7 +3354,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 	}
 
 	gateway = nm_ip4_config_get_gateway (composite);
-	if (   !gateway
+	if (   !nm_ip4_config_has_gateway (composite)
 	    && nm_device_get_device_type (self) != NM_DEVICE_TYPE_MODEM)
 		goto END_ADD_DEFAULT_ROUTE;
 
@@ -3365,8 +3396,15 @@ END_ADD_DEFAULT_ROUTE:
 			NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
 	}
 
+	routes_full_sync =    commit
+	                   && priv->v4_commit_first_time
+	                   && !nm_device_uses_assumed_connection (self);
+
 	success = nm_device_set_ip4_config (self, composite, default_route_metric, commit, routes_full_sync, out_reason);
 	g_object_unref (composite);
+
+	if (commit)
+		priv->v4_commit_first_time = FALSE;
 	return success;
 }
 
@@ -3391,12 +3429,44 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 	}
 }
 
+static gboolean
+dhcp4_restart_cb (gpointer user_data)
+{
+	NMDevice *self = user_data;
+	NMDevicePrivate *priv;
+	NMDeviceStateReason reason;
+	NMConnection *connection;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	priv->dhcp4_restart_id = 0;
+	connection = nm_device_get_connection (self);
+
+	if (dhcp4_start (self, connection, &reason) == NM_ACT_STAGE_RETURN_FAILURE)
+		priv->dhcp4_restart_id = g_timeout_add_seconds (120, dhcp4_restart_cb, self);
+
+	return FALSE;
+}
+
 static void
 dhcp4_fail (NMDevice *self, gboolean timeout)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+
+	/* Don't fail if there are static addresses configured on
+	 * the device, instead retry after some time.
+	 */
+	if (   priv->ip4_state == IP_DONE
+	    && priv->con_ip4_config
+	    && nm_ip4_config_get_num_addresses (priv->con_ip4_config) > 0) {
+		_LOGI (LOGD_DHCP4, "Scheduling DHCPv4 restart because device has IP addresses");
+		priv->dhcp4_restart_id = g_timeout_add_seconds (120, dhcp4_restart_cb, self);
+		return;
+	}
+
 	if (timeout || (priv->ip4_state == IP_CONF))
 		nm_device_activate_schedule_ip4_config_timeout (self);
 	else if (priv->ip4_state == IP_DONE)
@@ -3426,6 +3496,7 @@ dhcp4_state_changed (NMDhcpClient *client,
                      NMDhcpState state,
                      NMIP4Config *ip4_config,
                      GHashTable *options,
+                     const char *event_id,
                      gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
@@ -3450,8 +3521,10 @@ dhcp4_state_changed (NMDhcpClient *client,
 
 		if (priv->ip4_state == IP_CONF)
 			nm_device_activate_schedule_ip4_config_result (self, ip4_config);
-		else if (priv->ip4_state == IP_DONE)
+		else if (priv->ip4_state == IP_DONE) {
 			dhcp4_lease_change (self, ip4_config);
+			nm_device_update_metered (self);
+		}
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
 		dhcp4_fail (self, TRUE);
@@ -3802,6 +3875,9 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 
 	priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
 	g_clear_object (&priv->dhcp6_ip6_config);
+	g_clear_pointer (&priv->dhcp6_event_id, g_free);
+
+	nm_clear_g_source (&priv->dhcp6_restart_id);
 
 	if (priv->dhcp6_client) {
 		if (priv->dhcp6_state_sigid) {
@@ -3837,6 +3913,19 @@ ip6_config_merge_and_apply (NMDevice *self,
 	const struct in6_addr *gateway;
 	gboolean connection_has_default_route, connection_is_never_default;
 	gboolean routes_full_sync;
+	gboolean ignore_auto_routes = FALSE;
+	gboolean ignore_auto_dns = FALSE;
+
+	/* Apply ignore-auto-routes and ignore-auto-dns settings */
+	connection = nm_device_get_connection (self);
+	if (connection) {
+		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
+
+		if (s_ip6) {
+			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip6);
+			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip6);
+		}
+	}
 
 	/* If no config was passed in, create a new one */
 	composite = nm_ip6_config_new ();
@@ -3845,41 +3934,34 @@ ip6_config_merge_and_apply (NMDevice *self,
 		ensure_con_ip6_config (self);
 
 	/* Merge all the IP configs into the composite config */
-	if (priv->ac_ip6_config)
-		nm_ip6_config_merge (composite, priv->ac_ip6_config);
-	if (priv->dhcp6_ip6_config)
-		nm_ip6_config_merge (composite, priv->dhcp6_ip6_config);
+	if (priv->ac_ip6_config) {
+		nm_ip6_config_merge (composite, priv->ac_ip6_config,
+		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+	}
+	if (priv->dhcp6_ip6_config) {
+		nm_ip6_config_merge (composite, priv->dhcp6_ip6_config,
+		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+	}
 	if (priv->vpn6_config)
-		nm_ip6_config_merge (composite, priv->vpn6_config);
+		nm_ip6_config_merge (composite, priv->vpn6_config, NM_IP_CONFIG_MERGE_DEFAULT);
 	if (priv->ext_ip6_config)
-		nm_ip6_config_merge (composite, priv->ext_ip6_config);
+		nm_ip6_config_merge (composite, priv->ext_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	if (priv->wwan_ip6_config)
-		nm_ip6_config_merge (composite, priv->wwan_ip6_config);
-
-	/* Apply ignore-auto-routes and ignore-auto-dns settings */
-	connection = nm_device_get_connection (self);
-	if (connection) {
-		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
-
-		if (s_ip6) {
-			if (nm_setting_ip_config_get_ignore_auto_routes (s_ip6))
-				nm_ip6_config_reset_routes (composite);
-			if (nm_setting_ip_config_get_ignore_auto_dns (s_ip6)) {
-				nm_ip6_config_reset_nameservers (composite);
-				nm_ip6_config_reset_domains (composite);
-				nm_ip6_config_reset_searches (composite);
-			}
-		}
+	if (priv->wwan_ip6_config) {
+		nm_ip6_config_merge (composite, priv->wwan_ip6_config,
+		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
 	}
 
 	/* Merge user overrides into the composite config. For assumed connections,
 	 * con_ip6_config is empty. */
 	if (priv->con_ip6_config)
-		nm_ip6_config_merge (composite, priv->con_ip6_config);
+		nm_ip6_config_merge (composite, priv->con_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT);
 
 	/* Add the default route.
 	 *
@@ -3898,21 +3980,26 @@ ip6_config_merge_and_apply (NMDevice *self,
 	priv->default_route.v6_has = FALSE;
 	priv->default_route.v6_is_assumed = TRUE;
 
-	routes_full_sync =    commit
-	                   && priv->default_route.v6_configure_first_time
-	                   && !nm_device_uses_assumed_connection (self);
-
 	if (!commit) {
 		/* during a non-commit event, we always pickup whatever is configured. */
 		goto END_ADD_DEFAULT_ROUTE;
 	}
 
+	if (nm_device_uses_generated_assumed_connection (self)) {
+		/* a generate-assumed-connection always detects the default route from platform */
+		goto END_ADD_DEFAULT_ROUTE;
+	}
+
+	/* At this point, we treat assumed and non-assumed connections alike.
+	 * For assumed connections we do that because we still manage RA and DHCP
+	 * leases for them, so we must extend/update the default route on commits.
+	 */
+
 	connection_has_default_route
 	    = nm_default_route_manager_ip6_connection_has_default_route (nm_default_route_manager_get (),
 	                                                                 connection, &connection_is_never_default);
 
-	if (   !priv->default_route.v6_configure_first_time
-	    && !nm_device_uses_assumed_connection (self)
+	if (   !priv->v6_commit_first_time
 	    && connection_is_never_default) {
 		/* If the connection is explicitly configured as never-default, we enforce the (absence of the)
 		 * default-route only once. That allows the user to configure a connection as never-default,
@@ -3920,14 +4007,8 @@ ip6_config_merge_and_apply (NMDevice *self,
 		goto END_ADD_DEFAULT_ROUTE;
 	}
 
-	/* At this point, we treat assumed and non-assumed connections alike.
-	 * For assumed connections we do that because we still manage RA and DHCP
-	 * leases for them, so we must extend/update the default route on commits.
-	 */
-
 	/* we are about to commit (for a non-assumed connection). Enforce whatever we have
 	 * configured. */
-	priv->default_route.v6_configure_first_time = FALSE;
 	priv->default_route.v6_is_assumed = FALSE;
 
 	if (!connection_has_default_route)
@@ -3984,8 +4065,14 @@ END_ADD_DEFAULT_ROUTE:
 			NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self, composite);
 	}
 
+	routes_full_sync =    commit
+	                   && priv->v6_commit_first_time
+	                   && !nm_device_uses_assumed_connection (self);
+
 	success = nm_device_set_ip6_config (self, composite, commit, routes_full_sync, out_reason);
 	g_object_unref (composite);
+	if (commit)
+		priv->v6_commit_first_time = FALSE;
 	return success;
 }
 
@@ -4017,6 +4104,24 @@ dhcp6_lease_change (NMDevice *self)
 	}
 }
 
+static gboolean
+dhcp6_restart_cb (gpointer user_data)
+{
+	NMDevice *self = user_data;
+	NMDevicePrivate *priv;
+	NMDeviceStateReason reason;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	priv->dhcp6_restart_id = 0;
+
+	if (!dhcp6_start (self, FALSE, &reason))
+		priv->dhcp6_restart_id = g_timeout_add_seconds (120, dhcp6_restart_cb, self);
+
+	return FALSE;
+}
+
 static void
 dhcp6_fail (NMDevice *self, gboolean timeout)
 {
@@ -4025,6 +4130,17 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 	dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 
 	if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
+		/* Don't fail if there are static addresses configured on
+		 * the device, instead retry after some time.
+		 */
+		if (   priv->ip6_state == IP_DONE
+		    && priv->con_ip6_config
+		    && nm_ip6_config_get_num_addresses (priv->con_ip6_config)) {
+			_LOGI (LOGD_DHCP6, "Scheduling DHCPv6 restart because device has IP addresses");
+			priv->dhcp6_restart_id = g_timeout_add_seconds (120, dhcp6_restart_cb, self);
+			return;
+		}
+
 		if (timeout || (priv->ip6_state == IP_CONF))
 			nm_device_activate_schedule_ip6_config_timeout (self);
 		else if (priv->ip6_state == IP_DONE)
@@ -4074,10 +4190,12 @@ dhcp6_state_changed (NMDhcpClient *client,
                      NMDhcpState state,
                      NMIP6Config *ip6_config,
                      GHashTable *options,
+                     const char *event_id,
                      gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint i;
 
 	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
 	g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config));
@@ -4086,10 +4204,27 @@ dhcp6_state_changed (NMDhcpClient *client,
 
 	switch (state) {
 	case NM_DHCP_STATE_BOUND:
-		g_clear_object (&priv->dhcp6_ip6_config);
-		if (ip6_config) {
-			priv->dhcp6_ip6_config = g_object_ref (ip6_config);
-			dhcp6_update_config (self, priv->dhcp6_config, options);
+		/* If the server sends multiple IPv6 addresses, we receive a state
+		 * changed event for each of them. Use the event ID to merge IPv6
+		 * addresses from the same transaction into a single configuration.
+		 */
+		if (   ip6_config
+		    && event_id
+		    && priv->dhcp6_event_id
+		    && !strcmp (event_id, priv->dhcp6_event_id)) {
+			for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
+				nm_ip6_config_add_address (priv->dhcp6_ip6_config,
+				                           nm_ip6_config_get_address (ip6_config, i));
+			}
+		} else {
+			g_clear_object (&priv->dhcp6_ip6_config);
+			g_clear_pointer (&priv->dhcp6_event_id, g_free);
+			if (ip6_config) {
+				priv->dhcp6_ip6_config = g_object_ref (ip6_config);
+				priv->dhcp6_event_id = g_strdup (event_id);
+				dhcp6_update_config (self, priv->dhcp6_config, options);
+				g_object_notify (G_OBJECT (self), NM_DEVICE_DHCP6_CONFIG);
+			}
 		}
 
 		if (priv->ip6_state == IP_CONF) {
@@ -4182,6 +4317,7 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason)
 
 	g_warn_if_fail (priv->dhcp6_ip6_config == NULL);
 	g_clear_object (&priv->dhcp6_ip6_config);
+	g_clear_pointer (&priv->dhcp6_event_id, g_free);
 
 	connection = nm_device_get_connection (self);
 	g_assert (connection);
@@ -4437,7 +4573,7 @@ static void
 nm_device_set_mtu (NMDevice *self, guint32 mtu)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex = nm_device_get_ifindex (self);
+	int ifindex = nm_device_get_ip_ifindex (self);
 
 	if (mtu)
 		priv->mtu = mtu;
@@ -4446,7 +4582,7 @@ nm_device_set_mtu (NMDevice *self, guint32 mtu)
 	if (priv->ip6_mtu)
 		nm_device_ipv6_set_mtu (self, priv->ip6_mtu);
 
-	if (priv->mtu != nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex))
+	if (priv->mtu && priv->mtu != nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex))
 		nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, priv->mtu);
 }
 
@@ -4459,20 +4595,20 @@ nm_device_ipv6_set_mtu (NMDevice *self, guint32 mtu)
 
 	priv->ip6_mtu = mtu ?: plat_mtu;
 
-	if (priv->ip6_mtu && priv->mtu < priv->ip6_mtu) {
-		_LOGW (LOGD_DEVICE | LOGD_IP6, "Lowering IPv6 MTU (%d) to match device MTU (%d)",
+	if (priv->ip6_mtu && priv->mtu && priv->mtu < priv->ip6_mtu) {
+		_LOGI (LOGD_DEVICE | LOGD_IP6, "Lowering IPv6 MTU (%d) to match device MTU (%d)",
 		       priv->ip6_mtu, priv->mtu);
 		priv->ip6_mtu = priv->mtu;
 	}
 
-	if (priv->ip6_mtu < 1280) {
-		_LOGW (LOGD_DEVICE | LOGD_IP6, "IPv6 MTU (%d) smaller than 1280, adjusting",
+	if (priv->ip6_mtu && priv->ip6_mtu < 1280) {
+		_LOGI (LOGD_DEVICE | LOGD_IP6, "IPv6 MTU (%d) smaller than 1280, adjusting",
 		       priv->ip6_mtu);
 		priv->ip6_mtu = 1280;
 	}
 
-	if (priv->mtu < priv->ip6_mtu) {
-		_LOGW (LOGD_DEVICE | LOGD_IP6, "Raising device MTU (%d) to match IPv6 MTU (%d)",
+	if (priv->ip6_mtu && priv->mtu && priv->mtu < priv->ip6_mtu) {
+		_LOGI (LOGD_DEVICE | LOGD_IP6, "Raising device MTU (%d) to match IPv6 MTU (%d)",
 		       priv->mtu, priv->ip6_mtu);
 		nm_device_set_mtu (self, priv->ip6_mtu);
 	}
@@ -5010,11 +5146,6 @@ act_stage3_ip6_config_start (NMDevice *self,
 			ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
 		ret = linklocal6_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_FINISH) {
-			/* New blank config; LL address is already in priv->ext_ip6_config */
-			*out_config = nm_ip6_config_new ();
-			g_assert (*out_config);
-		}
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
 		priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_MANAGED;
 		if (!dhcp6_start (self, TRUE, reason)) {
@@ -5903,26 +6034,19 @@ static void
 _update_ip4_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	struct ifreq req;
-	guint32 new_address;
-	int fd;
-
-	g_return_if_fail (self  != NULL);
+	guint32 addr;
 
-	fd = socket (PF_INET, SOCK_DGRAM, 0);
-	if (fd < 0) {
-		_LOGE (LOGD_IP4, "couldn't open control socket.");
-		return;
-	}
+	g_return_if_fail (NM_IS_DEVICE (self));
 
-	memset (&req, 0, sizeof (struct ifreq));
-	strncpy (req.ifr_name, nm_device_get_ip_iface (self), IFNAMSIZ);
-	if (ioctl (fd, SIOCGIFADDR, &req) == 0) {
-		new_address = ((struct sockaddr_in *)(&req.ifr_addr))->sin_addr.s_addr;
-		if (new_address != priv->ip4_address)
-			priv->ip4_address = new_address;
+	if (   priv->ip4_config
+	    && ip_config_valid (priv->state)
+	    && nm_ip4_config_get_num_addresses (priv->ip4_config)) {
+		addr = nm_ip4_config_get_address (priv->ip4_config, 0)->address;
+		if (addr != priv->ip4_address) {
+			priv->ip4_address = addr;
+			g_object_notify (G_OBJECT (self), NM_DEVICE_IP4_ADDRESS);
+		}
 	}
-	close (fd);
 }
 
 gboolean
@@ -7497,6 +7621,59 @@ nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr)
 	priv->dhcp_anycast_address = g_strdup (addr);
 }
 
+static void
+nm_device_update_metered (NMDevice *self)
+{
+#define NM_METERED_INVALID ((NMMetered) -1)
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMSettingConnection *setting;
+	NMMetered conn_value, value = NM_METERED_INVALID;
+	NMConnection *connection = NULL;
+	NMDeviceState state;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	state = nm_device_get_state (self);
+	if (   state <= NM_DEVICE_STATE_DISCONNECTED
+	    || state > NM_DEVICE_STATE_ACTIVATED)
+		value = NM_METERED_UNKNOWN;
+
+	if (value == NM_METERED_INVALID) {
+		connection = nm_device_get_connection (self);
+		if (connection) {
+			setting = nm_connection_get_setting_connection (connection);
+			if (setting) {
+				conn_value = nm_setting_connection_get_metered (setting);
+				if (conn_value != NM_METERED_UNKNOWN)
+					value = conn_value;
+			}
+		}
+	}
+
+	/* Try to guess a value using the metered flag in IP configuration */
+	if (value == NM_METERED_INVALID) {
+		if (   priv->ip4_config
+		    && priv->ip4_state == IP_DONE
+		    && nm_ip4_config_get_metered (priv->ip4_config))
+			value = NM_METERED_GUESS_YES;
+	}
+
+	/* Otherwise look at connection type */
+	if (value == NM_METERED_INVALID) {
+		if (   nm_connection_is_type (connection, NM_SETTING_GSM_SETTING_NAME)
+		    || nm_connection_is_type (connection, NM_SETTING_CDMA_SETTING_NAME))
+			value = NM_METERED_GUESS_YES;
+		else
+			value = NM_METERED_GUESS_NO;
+	}
+
+	if (value != priv->metered) {
+		_LOGD (LOGD_DEVICE, "set metered value %d", value);
+		priv->metered = value;
+		g_object_notify (G_OBJECT (self), NM_DEVICE_METERED);
+	}
+}
+
 /**
  * nm_device_check_connection_available():
  * @self: the #NMDevice
@@ -7856,10 +8033,11 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 
 	priv->default_route.v4_has = FALSE;
 	priv->default_route.v4_is_assumed = TRUE;
-	priv->default_route.v4_configure_first_time = TRUE;
 	priv->default_route.v6_has = FALSE;
 	priv->default_route.v6_is_assumed = TRUE;
-	priv->default_route.v6_configure_first_time = TRUE;
+
+	priv->v4_commit_first_time = TRUE;
+	priv->v6_commit_first_time = TRUE;
 
 	nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), self);
 	nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self);
@@ -7943,9 +8121,11 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	nm_device_master_release_slaves (self);
 
 	/* slave: mark no longer enslaved */
-	g_clear_object (&priv->master);
-	priv->enslaved = FALSE;
-	g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
+	if (nm_platform_link_get_master (NM_PLATFORM_GET, priv->ifindex) <= 0) {
+		g_clear_object (&priv->master);
+		priv->enslaved = FALSE;
+		g_object_notify (G_OBJECT (self), NM_DEVICE_MASTER);
+	}
 
 	/* Take out any entries in the routing table and any IP address the device had. */
 	ifindex = nm_device_get_ip_ifindex (self);
@@ -7954,6 +8134,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		nm_platform_address_flush (NM_PLATFORM_GET, ifindex);
 	}
 
+	nm_device_update_metered (self);
 	_cleanup_generic_post (self, cleanup_type);
 }
 
@@ -8426,6 +8607,7 @@ _set_state_full (NMDevice *self,
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
 		_LOGI (LOGD_DEVICE, "Activation: successful, device activated.");
+		nm_device_update_metered (self);
 		nm_dispatcher_call (DISPATCHER_ACTION_UP, nm_act_request_get_connection (req), self, NULL, NULL, NULL);
 		break;
 	case NM_DEVICE_STATE_FAILED:
@@ -8799,9 +8981,10 @@ nm_device_init (NMDevice *self)
 	priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
 
 	priv->default_route.v4_is_assumed = TRUE;
-	priv->default_route.v4_configure_first_time = TRUE;
 	priv->default_route.v6_is_assumed = TRUE;
-	priv->default_route.v6_configure_first_time = TRUE;
+
+	priv->v4_commit_first_time = TRUE;
+	priv->v6_commit_first_time = TRUE;
 }
 
 static GObject*
@@ -9298,6 +9481,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_HAS_PENDING_ACTION:
 		g_value_set_boolean (value, nm_device_has_pending_action (self));
 		break;
+	case PROP_METERED:
+		g_value_set_uint (value, priv->metered);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -9561,6 +9747,20 @@ nm_device_class_init (NMDeviceClass *klass)
 		                       G_PARAM_READABLE |
 		                       G_PARAM_STATIC_STRINGS));
 
+	/**
+	 * NMDevice:metered:
+	 *
+	 * Whether the connection is metered.
+	 *
+	 * Since: 1.0.6
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_METERED,
+		 g_param_spec_uint (NM_DEVICE_METERED, "", "",
+		                    0, G_MAXUINT32, NM_METERED_UNKNOWN,
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS));
+
 	/* Signals */
 	signals[STATE_CHANGED] =
 		g_signal_new ("state-changed",