about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2018-12-10 18:50:07 +0100
committerSebastien Bacher <seb128@ubuntu.com>2018-12-10 21:58:19 +0100
commite335b8ae803a5a0f28a692edc0349ababa490445 (patch)
tree753f3d62d0aaa2ab6a831f8dcfa525d714868d0c /src
parent69385b013384918a005052098d5beae003b4c8d3 (diff)
parent227c401a10a930af6fd5f123aef345fcd130d6aa (diff)
Import Debian changes 1.12.6-0ubuntu1
network-manager (1.12.6-0ubuntu1) disco; urgency=medium

  * New upstream version
    - Fix a vulnerability in the internal DHCPv6 client (CVE-2018-15688).
  * debian/patches/git_mac_change.patch:
    - backported a regression fix from upstream git

  [ Alfonso Sanchez-Beato ]
  * Import some WoWLAN patches from the newer stable serie (LP: #1781597)
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device.c93
-rw-r--r--src/devices/nm-lldp-listener.c4
-rw-r--r--src/devices/tests/test-lldp.c5
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c4
-rw-r--r--src/devices/wwan/nm-modem-broadband.c2
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c2
-rw-r--r--src/ndisc/nm-fake-ndisc.c2
-rw-r--r--src/ndisc/nm-lndp-ndisc.c16
-rw-r--r--src/ndisc/nm-ndisc-private.h2
-rw-r--r--src/ndisc/nm-ndisc.c352
-rw-r--r--src/ndisc/nm-ndisc.h3
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c7
-rw-r--r--src/nm-dbus-manager.c26
-rw-r--r--src/platform/nm-netlink.h2
-rw-r--r--src/platform/nm-platform.c38
-rw-r--r--src/platform/nm-platform.h1
-rw-r--r--src/platform/tests/test-cleanup.c2
-rw-r--r--src/platform/tests/test-common.c2
-rw-r--r--src/ppp/nm-ppp-manager.c14
-rw-r--r--src/ppp/nm-pppd-plugin.c2
-rw-r--r--src/supplicant/nm-supplicant-config.c6
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-internal.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c87
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c15
28 files changed, 427 insertions, 269 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 33dd5e50..f0d747e5 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -3306,7 +3306,8 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 		guint32 lifetime, preferred;
 		gint32 base;
 
-		if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
+		if (   IN6_IS_ADDR_UNSPECIFIED (&addr->address)
+		    || IN6_IS_ADDR_LINKLOCAL (&addr->address))
 			continue;
 
 		if (   addr->n_ifa_flags & IFA_F_TENTATIVE
@@ -10362,8 +10363,10 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 					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 (priv->dhcp4.client) {
+					nm_dhcp_client_set_route_metric (priv->dhcp4.client,
+					                                 nm_device_get_route_metric (self, AF_INET));
+				}
 			}
 		}
 
@@ -10433,8 +10436,10 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 					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 (priv->dhcp6.client) {
+					nm_dhcp_client_set_route_metric (priv->dhcp6.client,
+					                                 nm_device_get_route_metric (self, AF_INET6));
+				}
 			}
 		}
 
@@ -14770,7 +14775,8 @@ _hw_addr_set (NMDevice *self,
 	NMPlatformError plerr;
 	guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
 	gsize addr_len;
-	gboolean was_up;
+	gboolean was_taken_down;
+	gboolean retry_down;
 
 	nm_assert (NM_IS_DEVICE (self));
 	nm_assert (addr);
@@ -14793,21 +14799,30 @@ _hw_addr_set (NMDevice *self,
 
 	_LOGT (LOGD_DEVICE, "set-hw-addr: setting MAC address to '%s' (%s, %s)...", addr, operation, detail);
 
-	was_up = nm_device_is_up (self);
-	if (was_up) {
-		/* Can't change MAC address while device is up */
-		nm_device_take_down (self, FALSE);
-	}
+	was_taken_down = FALSE;
 
+again:
 	plerr = nm_platform_link_set_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), addr_bytes, addr_len);
 	success = (plerr == NM_PLATFORM_ERROR_SUCCESS);
-	if (success) {
-		/* MAC address succesfully changed; update the current MAC to match */
+	if (!success) {
+		retry_down =    !was_taken_down
+		             && plerr != NM_PLATFORM_ERROR_NOT_FOUND
+		             && nm_platform_link_is_up (nm_device_get_platform (self),
+		                                        nm_device_get_ip_ifindex (self));
+		_NMLOG (     retry_down
+		          || plerr == NM_PLATFORM_ERROR_NOT_FOUND
+		        ? LOGL_DEBUG
+		        : LOGL_WARN,
+		        LOGD_DEVICE,
+		        "set-hw-addr: failed to %s MAC address to %s (%s) (%s)%s",
+		        operation, addr, detail,
+		        nm_platform_error_to_string_a (plerr),
+		        retry_down ? " (retry with taking down)" : "");
+	} else {
+		/* MAC address successfully changed; update the current MAC to match */
 		nm_device_update_hw_address (self);
-		if (_hw_addr_matches (self, addr_bytes, addr_len)) {
-			_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
-			       operation, addr, detail);
-		} else {
+
+		if (!_hw_addr_matches (self, addr_bytes, addr_len)) {
 			gint64 poll_end, now;
 
 			_LOGD (LOGD_DEVICE,
@@ -14848,24 +14863,40 @@ handle_fail:
 				success = FALSE;
 				break;
 			}
+		}
 
-			if (success) {
-				_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
-				       operation, addr, detail);
-			} else {
-				_LOGW (LOGD_DEVICE,
-				       "set-hw-addr: new MAC address %s not successfully %s (%s)",
-				       addr, operation, detail);
-			}
+		if (success) {
+			retry_down = FALSE;
+			_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
+			       operation, addr, detail);
+		} else {
+			retry_down =    !was_taken_down
+			             && nm_platform_link_is_up (nm_device_get_platform (self),
+			                                        nm_device_get_ip_ifindex (self));
+
+			_NMLOG (  retry_down
+			        ? LOGL_DEBUG
+			        : LOGL_WARN,
+			        LOGD_DEVICE,
+			        "set-hw-addr: new MAC address %s not successfully %s (%s)%s",
+			        addr,
+			        operation,
+			        detail,
+			        retry_down ? " (retry with taking down)" : "");
 		}
-	} else {
-		_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
-		        LOGD_DEVICE, "set-hw-addr: failed to %s MAC address to %s (%s) (%s)",
-		        operation, addr, detail,
-		        nm_platform_error_to_string_a (plerr));
 	}
 
-	if (was_up) {
+	if (retry_down) {
+		/* changing the MAC address failed, but also the device was up (and we did not yet try to take
+		 * it down). Optimally, we change the MAC address without taking the device down, but some
+		 * devices don't like that. So, retry with taking the device down. */
+		retry_down = FALSE;
+		was_taken_down = TRUE;
+		nm_device_take_down (self, FALSE);
+		goto again;
+	}
+
+	if (was_taken_down) {
 		if (!nm_device_bring_up (self, TRUE, NULL))
 			return FALSE;
 	}
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index f637825b..c0484ed3 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -534,11 +534,13 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 				l = data8[2];
 				if (len != 3 + l)
 					continue;
+				if (l > 32)
+					continue;
 
 				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID,
 				                       _access_uint16 (&data8[0]));
 				_lldp_attr_set_str_ptr (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
-				                        &data8[3], len);
+				                        &data8[3], l);
 				break;
 			}
 			default:
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index c2ac8e19..7227d082 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -219,11 +219,10 @@ TEST_RECV_FRAME_DEFINE (_test_recv_data1_frame0,
 	0x01, 0xe8,
 	0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* IEEE 802.1 - Port and Protocol VLAN ID */
 	0x01, 0x00, 0x00,
-	0xfe, 0x17, 0x00, 0x80, 0xc2, 0x03, /* IEEE 802.1 - VLAN Name */
-	0x01, 0xe8, 0x10, 0x76, 0x32, 0x2d,
+	0xfe, 0x16, 0x00, 0x80, 0xc2, 0x03, /* IEEE 802.1 - VLAN Name */
+	0x01, 0xe8, 0x0f, 0x76, 0x32, 0x2d,
 	0x30, 0x34, 0x38, 0x38, 0x2d, 0x30,
 	0x33, 0x2d, 0x30, 0x35, 0x30, 0x35,
-	0x00,
 	0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, /* IEEE 802.1 - Protocol Identity */
 	0x00,
 	0x00, 0x00                          /* End of LLDPDU */
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index fd7bf3f7..97a81bf4 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -302,7 +302,7 @@ companion_state_changed_cb (NMDeviceWifi *companion,
 }
 
 static gboolean
-companion_scan_prohibited_cb (NMDeviceWifi *companion, gpointer user_data)
+companion_scan_prohibited_cb (NMDeviceWifi *companion, gboolean periodic, gpointer user_data)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
 	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
@@ -369,7 +369,7 @@ device_added_cb (NMManager *manager, NMDevice *other, gpointer user_data)
 		nm_device_queue_recheck_available (NM_DEVICE (self),
 		                                   NM_DEVICE_STATE_REASON_NONE,
 		                                   NM_DEVICE_STATE_REASON_NONE);
-		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_COMPANION, TRUE);
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_COMPANION, FALSE);
 	}
 }
 
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index e5678b96..20529256 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -1067,7 +1067,7 @@ stage3_ip6_done (NMModemBroadband *self)
 
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->_priv.ipv6_config);
-	for (i = 0; dns[i]; i++) {
+	for (i = 0; dns && dns[i]; i++) {
 		struct in6_addr addr;
 
 		if (inet_pton (AF_INET6, dns[i], &addr)) {
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 041e75ea..0907d034 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -235,6 +235,7 @@ create_dm_cmd_line (const char *iface,
 			g_string_append_c (s, ',');
 			g_string_append (s, nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (ip4_config, i), tmpaddr));
 		}
+		nm_cmd_line_add_string (cmd, s->str);
 		g_string_truncate (s, 0);
 	}
 
@@ -244,6 +245,7 @@ create_dm_cmd_line (const char *iface,
 			g_string_append_c (s, ',');
 			g_string_append (s, nm_ip4_config_get_search (ip4_config, i));
 		}
+		nm_cmd_line_add_string (cmd, s->str);
 		g_string_truncate (s, 0);
 	}
 
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c
index 15abee88..6e9a72c6 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/ndisc/nm-fake-ndisc.c
@@ -296,7 +296,7 @@ receive_ra (gpointer user_data)
 				.dad_counter = 0,
 			};
 
-			if (nm_ndisc_complete_and_add_address (ndisc, &address))
+			if (nm_ndisc_complete_and_add_address (ndisc, &address, now))
 				changed |= NM_NDISC_CONFIG_ADDRESSES;
 		}
 	}
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index c0a0cd40..e1003ad1 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -221,10 +221,10 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 				.preferred = ndp_msg_opt_prefix_preferred_time (msg, offset),
 			};
 
-			if (address.preferred > address.lifetime)
-				address.preferred = address.lifetime;
-			if (nm_ndisc_complete_and_add_address (ndisc, &address))
-				changed |= NM_NDISC_CONFIG_ADDRESSES;
+			if (address.preferred <= address.lifetime) {
+				if (nm_ndisc_complete_and_add_address (ndisc, &address, now))
+					changed |= NM_NDISC_CONFIG_ADDRESSES;
+			}
 		}
 	}
 	ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_ROUTE) {
@@ -491,13 +491,17 @@ receive_rs (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 static gboolean
 event_ready (GIOChannel *source, GIOCondition condition, NMNDisc *ndisc)
 {
+	_nm_unused gs_unref_object NMNDisc *ndisc_keep_alive = g_object_ref (ndisc);
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
 
 	_LOGD ("processing libndp events");
 
-	if (!nm_ndisc_netns_push (ndisc, &netns))
-		return G_SOURCE_CONTINUE;
+	if (!nm_ndisc_netns_push (ndisc, &netns)) {
+		/* something is very wrong. Stop handling events. */
+		priv->event_id = 0;
+		return G_SOURCE_REMOVE;
+	}
 
 	ndp_callall_eventfd_handler (priv->ndp);
 	return G_SOURCE_CONTINUE;
diff --git a/src/ndisc/nm-ndisc-private.h b/src/ndisc/nm-ndisc-private.h
index bbecb01a..ae03c0ee 100644
--- a/src/ndisc/nm-ndisc-private.h
+++ b/src/ndisc/nm-ndisc-private.h
@@ -40,7 +40,7 @@ void nm_ndisc_ra_received (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
 void nm_ndisc_rs_received (NMNDisc *ndisc);
 
 gboolean nm_ndisc_add_gateway              (NMNDisc *ndisc, const NMNDiscGateway *new);
-gboolean nm_ndisc_complete_and_add_address (NMNDisc *ndisc, NMNDiscAddress *new);
+gboolean nm_ndisc_complete_and_add_address (NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s);
 gboolean nm_ndisc_add_route                (NMNDisc *ndisc, const NMNDiscRoute *new);
 gboolean nm_ndisc_add_dns_server           (NMNDisc *ndisc, const NMNDiscDNSServer *new);
 gboolean nm_ndisc_add_dns_domain           (NMNDisc *ndisc, const NMNDiscDNSDomain *new);
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 04f86312..f701731b 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -124,6 +124,79 @@ _preference_to_priority (NMIcmpv6RouterPref pref)
 
 /*****************************************************************************/
 
+/* we rely on the fact, that _EXPIRY_INFINITY > any other valid gint64 timestamps. */
+#define _EXPIRY_INFINITY G_MAXINT64
+
+static gint64
+get_expiry_time (guint32 timestamp, guint32 lifetime)
+{
+	nm_assert (timestamp > 0);
+	nm_assert (timestamp <= G_MAXINT32);
+
+	if (lifetime == NM_NDISC_INFINITY)
+		return _EXPIRY_INFINITY;
+	return ((gint64) timestamp) + ((gint64) lifetime);
+}
+
+#define get_expiry(item) \
+	({ \
+		typeof (item) _item = (item); \
+		nm_assert (_item); \
+		get_expiry_time (_item->timestamp, _item->lifetime); \
+	})
+
+#define get_expiry_half(item) \
+	({ \
+		typeof (item) _item = (item); \
+		nm_assert (_item); \
+		(_item->lifetime == NM_NDISC_INFINITY) \
+		  ? _EXPIRY_INFINITY \
+		  : get_expiry_time (_item->timestamp, _item->lifetime / 2); \
+	})
+
+#define get_expiry_preferred(item) \
+	({ \
+		typeof (item) _item = (item); \
+		nm_assert (_item); \
+		get_expiry_time (_item->timestamp, _item->preferred); \
+	})
+
+static gboolean
+expiry_next (gint32 now_s, gint64 expiry_timestamp, gint32 *nextevent)
+{
+	gint32 e;
+
+	if (expiry_timestamp == _EXPIRY_INFINITY)
+		return TRUE;
+	e = MIN (expiry_timestamp, ((gint64) (G_MAXINT32 - 1)));
+	if (now_s >= e)
+		return FALSE;
+	if (nextevent) {
+		if (*nextevent > e)
+			*nextevent = e;
+	}
+	return TRUE;
+}
+
+static const char *
+_get_exp (char *buf, gsize buf_size, gint64 now_ns, gint64 expiry_time)
+{
+	int l;
+
+	if (expiry_time == _EXPIRY_INFINITY)
+		return "permanent";
+	l = g_snprintf (buf, buf_size,
+	                "%.4f",
+	                ((double) ((expiry_time * NM_UTILS_NS_PER_SECOND) - now_ns)) / ((double) NM_UTILS_NS_PER_SECOND));
+	nm_assert (l < buf_size);
+	return buf;
+}
+
+#define get_exp(buf, now_ns, item) \
+	_get_exp ((buf), G_N_ELEMENTS (buf), (now_ns), (get_expiry (item)))
+
+/*****************************************************************************/
+
 NMPNetns *
 nm_ndisc_netns_get (NMNDisc *self)
 {
@@ -264,9 +337,12 @@ nm_ndisc_add_gateway (NMNDisc *ndisc, const NMNDiscGateway *new)
 				continue;
 			}
 
+			if (get_expiry (item) == get_expiry (new))
+				return FALSE;
+
 			*item = *new;
 			_ASSERT_data_gateways (rdata);
-			return FALSE;
+			return TRUE;
 		}
 
 		/* Put before less preferable gateways. */
@@ -343,33 +419,101 @@ complete_address (NMNDisc *ndisc, NMNDiscAddress *addr)
 }
 
 static gboolean
-nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new)
+nm_ndisc_add_address (NMNDisc *ndisc,
+                      const NMNDiscAddress *new,
+                      gint32 now_s,
+                      gboolean from_ra)
 {
 	NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc);
 	NMNDiscDataInternal *rdata = &priv->rdata;
+	NMNDiscAddress new2;
+	NMNDiscAddress *existing = NULL;
 	guint i;
 
 	nm_assert (new);
 	nm_assert (new->timestamp > 0 && new->timestamp < G_MAXINT32);
 	nm_assert (!IN6_IS_ADDR_UNSPECIFIED (&new->address));
 	nm_assert (!IN6_IS_ADDR_LINKLOCAL (&new->address));
+	nm_assert (new->preferred <= new->lifetime);
+	nm_assert (!from_ra || now_s > 0);
 
 	for (i = 0; i < rdata->addresses->len; i++) {
 		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
-		if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) {
-			gboolean changed;
+		if (from_ra) {
+			/* RFC4862 5.5.3.d, we find an existing address with the same prefix.
+			 * (note that all prefixes at this point have implicity length /64). */
+			if (memcmp (&item->address, &new->address, 8) == 0) {
+				existing = item;
+				break;
+			}
+		} else {
+			if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) {
+				existing = item;
+				break;
+			}
+		}
+	}
 
-			if (new->lifetime == 0) {
-				g_array_remove_index (rdata->addresses, i);
-				return TRUE;
+	if (existing) {
+		if (from_ra) {
+			const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */
+			gint64 old_expiry_lifetime, old_expiry_preferred;
+
+			old_expiry_lifetime = get_expiry (existing);
+			old_expiry_preferred = get_expiry_preferred (existing);
+
+			if (new->lifetime == NM_NDISC_INFINITY)
+				existing->lifetime = NM_NDISC_INFINITY;
+			else {
+				gint64 new_lifetime, remaining_lifetime;
+
+				/* see RFC4862 5.5.3.e */
+				if (existing->lifetime == NM_NDISC_INFINITY)
+					remaining_lifetime = G_MAXINT64;
+				else
+					remaining_lifetime = ((gint64) existing->timestamp) + ((gint64) existing->lifetime) - ((gint64) now_s);
+				new_lifetime = ((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s);
+
+				if (   new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN
+				    || new_lifetime > remaining_lifetime) {
+					existing->timestamp = now_s;
+					existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1));
+				} else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) {
+					/* keep the current lifetime. */
+				} else {
+					existing->timestamp = now_s;
+					existing->lifetime = NM_NDISC_PREFIX_LFT_MIN;
+				}
 			}
 
-			changed = item->timestamp + item->lifetime  != new->timestamp + new->lifetime ||
-			          item->timestamp + item->preferred != new->timestamp + new->preferred;
-			*item = *new;
-			return changed;
+			if (new->preferred == NM_NDISC_INFINITY) {
+				nm_assert (existing->lifetime == NM_NDISC_INFINITY);
+				existing->preferred = new->preferred;
+			} else {
+				existing->preferred = NM_CLAMP (((gint64) new->timestamp) + ((gint64) new->preferred) - ((gint64) existing->timestamp),
+				                                0, G_MAXUINT32 - 1);
+				if (existing->lifetime != NM_NDISC_INFINITY)
+					existing->preferred = MIN (existing->preferred, existing->lifetime);
+			}
+
+			return    old_expiry_lifetime != get_expiry (existing)
+			       || old_expiry_preferred != get_expiry_preferred (existing);
 		}
+
+		if (new->lifetime == 0) {
+			g_array_remove_index (rdata->addresses, i);
+			return TRUE;
+		}
+
+		if (   get_expiry (existing) == get_expiry (new)
+		    && get_expiry_preferred (existing) == get_expiry_preferred (new))
+			return FALSE;
+
+		existing->timestamp = new->timestamp;
+		existing->lifetime = new->lifetime;
+		existing->preferred = new->preferred;
+		return TRUE;
 	}
 
 	/* we create at most max_addresses autoconf addresses. This is different from
@@ -380,18 +524,27 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new)
 	    && rdata->addresses->len >= priv->max_addresses)
 		return FALSE;
 
-	if (new->lifetime)
-		g_array_append_val (rdata->addresses, *new);
-	return !!new->lifetime;
+	if (new->lifetime == 0)
+		return FALSE;
+
+	if (from_ra) {
+		new2 = *new;
+		new2.dad_counter = 0;
+		if (!complete_address (ndisc, &new2))
+			return FALSE;
+		new = &new2;
+	}
+
+	g_array_append_val (rdata->addresses, *new);
+	return TRUE;
 }
 
 gboolean
-nm_ndisc_complete_and_add_address (NMNDisc *ndisc, NMNDiscAddress *new)
+nm_ndisc_complete_and_add_address (NMNDisc *ndisc,
+                                   const NMNDiscAddress *new,
+                                   gint32 now_s)
 {
-	if (!complete_address (ndisc, new))
-		return FALSE;
-
-	return nm_ndisc_add_address (ndisc, new);
+	return nm_ndisc_add_address (ndisc, new, now_s, TRUE);
 }
 
 gboolean
@@ -419,7 +572,8 @@ nm_ndisc_add_route (NMNDisc *ndisc, const NMNDiscRoute *new)
 	for (i = 0; i < rdata->routes->len; ) {
 		NMNDiscRoute *item = &g_array_index (rdata->routes, NMNDiscRoute, i);
 
-		if (IN6_ARE_ADDR_EQUAL (&item->network, &new->network) && item->plen == new->plen) {
+		if (   IN6_ARE_ADDR_EQUAL (&item->network, &new->network)
+		    && item->plen == new->plen) {
 			if (new->lifetime == 0) {
 				g_array_remove_index (rdata->routes, i);
 				return TRUE;
@@ -430,8 +584,12 @@ nm_ndisc_add_route (NMNDisc *ndisc, const NMNDiscRoute *new)
 				continue;
 			}
 
-			memcpy (item, new, sizeof (*new));
-			return FALSE;
+			if (   get_expiry (item) == get_expiry (new)
+			    && IN6_ARE_ADDR_EQUAL (&item->gateway, &new->gateway))
+				return FALSE;
+
+			*item = *new;
+			return TRUE;
 		}
 
 		/* Put before less preferable routes. */
@@ -470,11 +628,12 @@ nm_ndisc_add_dns_server (NMNDisc *ndisc, const NMNDiscDNSServer *new)
 				g_array_remove_index (rdata->dns_servers, i);
 				return TRUE;
 			}
-			if (item->timestamp != new->timestamp || item->lifetime != new->lifetime) {
-				*item = *new;
-				return TRUE;
-			}
-			return FALSE;
+
+			if (get_expiry (item) == get_expiry (new))
+				return FALSE;
+
+			*item = *new;
+			return TRUE;
 		}
 	}
 
@@ -499,20 +658,17 @@ nm_ndisc_add_dns_domain (NMNDisc *ndisc, const NMNDiscDNSDomain *new)
 		item = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i);
 
 		if (!g_strcmp0 (item->domain, new->domain)) {
-			gboolean changed;
-
 			if (new->lifetime == 0) {
 				g_array_remove_index (rdata->dns_domains, i);
 				return TRUE;
 			}
 
-			changed = (item->timestamp != new->timestamp ||
-			           item->lifetime != new->lifetime);
-			if (changed) {
-				item->timestamp = new->timestamp;
-				item->lifetime = new->lifetime;
-			}
-			return changed;
+			if (get_expiry (item) == get_expiry (new))
+				return FALSE;
+
+			item->timestamp = new->timestamp;
+			item->lifetime = new->lifetime;
+			return TRUE;
 		}
 	}
 
@@ -686,7 +842,7 @@ nm_ndisc_set_config (NMNDisc *ndisc,
 	guint i;
 
 	for (i = 0; i < addresses->len; i++) {
-		if (nm_ndisc_add_address (ndisc, &g_array_index (addresses, NMNDiscAddress, i)))
+		if (nm_ndisc_add_address (ndisc, &g_array_index (addresses, NMNDiscAddress, i), 0, FALSE))
 			changed = TRUE;
 	}
 
@@ -860,57 +1016,6 @@ dhcp_level_to_string (NMNDiscDHCPLevel dhcp_level)
 	}
 }
 
-static gint32
-get_expiry_time (guint32 timestamp, guint32 lifetime)
-{
-	gint64 t;
-
-	/* timestamp is supposed to come from nm_utils_get_monotonic_timestamp_s().
-	 * It is expected to be within a certain range. */
-	nm_assert (timestamp > 0);
-	nm_assert (timestamp <= G_MAXINT32);
-
-	if (lifetime == NM_NDISC_INFINITY)
-		return G_MAXINT32;
-
-	t = (gint64) timestamp + (gint64) lifetime;
-	return CLAMP (t, 0, G_MAXINT32 - 1);
-}
-
-#define get_expiry(item) \
-	({ \
-		typeof (item) _item = (item); \
-		nm_assert (_item); \
-		get_expiry_time ((_item->timestamp), (_item->lifetime)); \
-	})
-
-#define get_expiry_half(item) \
-	({ \
-		typeof (item) _item = (item); \
-		nm_assert (_item); \
-		get_expiry_time ((_item->timestamp),\
-		                 (_item->lifetime) == NM_NDISC_INFINITY \
-		                   ? NM_NDISC_INFINITY \
-		                   : (_item->lifetime) / 2); \
-	})
-
-static const char *
-_get_exp (char *buf, gsize buf_size, gint64 now_ns, gint32 expiry_time)
-{
-	int l;
-
-	if (expiry_time == G_MAXINT32)
-		return "permanent";
-	l = g_snprintf (buf, buf_size,
-	                "%.4f",
-	                ((double) ((expiry_time * NM_UTILS_NS_PER_SECOND) - now_ns)) / ((double) NM_UTILS_NS_PER_SECOND));
-	nm_assert (l < buf_size);
-	return buf;
-}
-
-#define get_exp(buf, now_ns, item) \
-	_get_exp ((buf), G_N_ELEMENTS (buf), (now_ns), (get_expiry (item)))
-
 static void
 _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 {
@@ -984,17 +1089,12 @@ clean_gateways (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *n
 	for (i = 0; i < rdata->gateways->len; ) {
 		NMNDiscGateway *item = &g_array_index (rdata->gateways, NMNDiscGateway, i);
 
-		if (item->lifetime != NM_NDISC_INFINITY) {
-			gint32 expiry = get_expiry (item);
-
-			if (now >= expiry) {
-				g_array_remove_index (rdata->gateways, i);
-				*changed |= NM_NDISC_CONFIG_GATEWAYS;
-				continue;
-			}
-			if (*nextevent > expiry)
-				*nextevent = expiry;
+		if (!expiry_next (now, get_expiry (item), nextevent)) {
+			g_array_remove_index (rdata->gateways, i);
+			*changed |= NM_NDISC_CONFIG_GATEWAYS;
+			continue;
 		}
+
 		i++;
 	}
 
@@ -1012,17 +1112,12 @@ clean_addresses (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *
 	for (i = 0; i < rdata->addresses->len; ) {
 		const NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
-		if (item->lifetime != NM_NDISC_INFINITY) {
-			gint32 expiry = get_expiry (item);
-
-			if (now >= expiry) {
-				g_array_remove_index (rdata->addresses, i);
-				*changed |= NM_NDISC_CONFIG_ADDRESSES;
-				continue;
-			}
-			if (*nextevent > expiry)
-				*nextevent = expiry;
+		if (!expiry_next (now, get_expiry (item), nextevent)) {
+			g_array_remove_index (rdata->addresses, i);
+			*changed |= NM_NDISC_CONFIG_ADDRESSES;
+			continue;
 		}
+
 		i++;
 	}
 }
@@ -1038,17 +1133,12 @@ clean_routes (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nex
 	for (i = 0; i < rdata->routes->len; ) {
 		NMNDiscRoute *item = &g_array_index (rdata->routes, NMNDiscRoute, i);
 
-		if (item->lifetime != NM_NDISC_INFINITY) {
-			gint32 expiry = get_expiry (item);
-
-			if (now >= expiry) {
-				g_array_remove_index (rdata->routes, i);
-				*changed |= NM_NDISC_CONFIG_ROUTES;
-				continue;
-			}
-			if (*nextevent > expiry)
-				*nextevent = expiry;
+		if (!expiry_next (now, get_expiry (item), nextevent)) {
+			g_array_remove_index (rdata->routes, i);
+			*changed |= NM_NDISC_CONFIG_ROUTES;
+			continue;
 		}
+
 		i++;
 	}
 }
@@ -1063,18 +1153,16 @@ clean_dns_servers (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32
 
 	for (i = 0; i < rdata->dns_servers->len; ) {
 		NMNDiscDNSServer *item = &g_array_index (rdata->dns_servers, NMNDiscDNSServer, i);
+		gint64 refresh;
 
-		if (item->lifetime != NM_NDISC_INFINITY) {
-			gint32 expiry = get_expiry (item);
-			gint32 refresh;
-
-			if (now >= expiry) {
+		refresh = get_expiry_half (item);
+		if (refresh != _EXPIRY_INFINITY) {
+			if (!expiry_next (now, get_expiry (item), NULL)) {
 				g_array_remove_index (rdata->dns_servers, i);
 				*changed |= NM_NDISC_CONFIG_DNS_SERVERS;
 				continue;
 			}
 
-			refresh = get_expiry_half (item);
 			if (now >= refresh)
 				solicit_routers (ndisc);
 			else if (*nextevent > refresh)
@@ -1094,18 +1182,16 @@ clean_dns_domains (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32
 
 	for (i = 0; i < rdata->dns_domains->len; ) {
 		NMNDiscDNSDomain *item = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i);
+		gint64 refresh;
 
-		if (item->lifetime != NM_NDISC_INFINITY) {
-			gint32 expiry = get_expiry (item);
-			gint32 refresh;
-
-			if (now >= expiry) {
+		refresh = get_expiry_half (item);
+		if (refresh != _EXPIRY_INFINITY) {
+			if (!expiry_next (now, get_expiry (item), NULL)) {
 				g_array_remove_index (rdata->dns_domains, i);
 				*changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
 				continue;
 			}
 
-			refresh = get_expiry_half (item);
 			if (now >= refresh)
 				solicit_routers (ndisc);
 			else if (*nextevent > refresh)
@@ -1132,9 +1218,6 @@ check_timestamps (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
 	clean_dns_servers (ndisc, now, &changed, &nextevent);
 	clean_dns_domains (ndisc, now, &changed, &nextevent);
 
-	if (changed)
-		nm_ndisc_emit_config_change (ndisc, changed);
-
 	if (nextevent != G_MAXINT32) {
 		if (nextevent <= now)
 			g_return_if_reached ();
@@ -1142,6 +1225,9 @@ check_timestamps (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
 		       (int) (nextevent - now));
 		priv->timeout_id = g_timeout_add_seconds (nextevent - now, timeout_cb, ndisc);
 	}
+
+	if (changed)
+		nm_ndisc_emit_config_change (ndisc, changed);
 }
 
 static gboolean
diff --git a/src/ndisc/nm-ndisc.h b/src/ndisc/nm-ndisc.h
index fdc5615f..73eef368 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/ndisc/nm-ndisc.h
@@ -58,6 +58,9 @@ typedef enum {
 	NM_NDISC_DHCP_LEVEL_MANAGED
 } NMNDiscDHCPLevel;
 
+/* we rely on the fact that NM_NDISC_INFINITY is the largest possible
+ * time duration (G_MAXUINT32) and that the range of finite values
+ * goes from 0 to G_MAXUINT32-1. */
 #define NM_NDISC_INFINITY  G_MAXUINT32
 
 struct _NMNDiscGateway {
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index e99d2fc5..268f3b49 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -233,8 +233,9 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 
 		g_assert_cmpint (rdata->gateways_n, ==, 1);
 		match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-		g_assert_cmpint (rdata->addresses_n, ==, 1);
-		match_address (rdata, 0, "2001:db8:a:b::1", data->timestamp1, 10, 10);
+		g_assert_cmpint (rdata->addresses_n, ==, 2);
+		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0);
+		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->routes_n, ==, 1);
 		match_route (rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->dns_servers_n, ==, 1);
@@ -384,7 +385,7 @@ test_preference_changed_cb (NMNDisc *ndisc, const NMNDiscData *rdata, guint chan
 		match_gateway (rdata, 0, "fe80::1", data->timestamp1 + 2, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
 		match_gateway (rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 		g_assert_cmpint (rdata->addresses_n, ==, 2);
-		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 2, 10, 10);
+		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 3, 9, 9);
 		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
 		g_assert_cmpint (rdata->routes_n, ==, 2);
 		match_route (rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1 + 2, 10, 15);
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 7f1121f6..c89f8276 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -1546,6 +1546,18 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 		return FALSE;
 	}
 
+	registration_id = g_dbus_connection_register_object (connection,
+	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
+	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
+	                                                     &dbus_vtable_objmgr,
+	                                                     self,
+	                                                     NULL,
+	                                                     &error);
+	if (!registration_id) {
+		_LOGE ("failure to register object manager: %s", error->message);
+		return FALSE;
+	}
+
 	ret = _nm_dbus_proxy_call_sync (proxy,
 	                                "RequestName",
 	                                g_variant_new ("(su)",
@@ -1558,6 +1570,7 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	if (!ret) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s"": %s",
 		       NM_DBUS_SERVICE, error->message);
+		g_dbus_connection_unregister_object(connection, registration_id);
 		return FALSE;
 	}
 
@@ -1565,18 +1578,7 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken",
 		       NM_DBUS_SERVICE, (guint) result);
-		return FALSE;
-	}
-
-	registration_id = g_dbus_connection_register_object (connection,
-	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
-	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
-	                                                     &dbus_vtable_objmgr,
-	                                                     self,
-	                                                     NULL,
-	                                                     &error);
-	if (!registration_id) {
-		_LOGE ("failure to register object manager: %s", error->message);
+		g_dbus_connection_unregister_object(connection, registration_id);
 		return FALSE;
 	}
 
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index 185269ba..7d7ea0d2 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -252,7 +252,7 @@ struct nlattr *nla_find (const struct nlattr *head, int len, int attrtype);
 static inline int
 nla_ok (const struct nlattr *nla, int remaining)
 {
-	return remaining >= sizeof(*nla) &&
+	return remaining >= (int) sizeof(*nla) &&
 	       nla->nla_len >= sizeof(*nla) &&
 	       nla->nla_len <= remaining;
 }
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index f75019e8..227168ba 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -1191,13 +1191,29 @@ nm_platform_link_refresh (NMPlatform *self, int ifindex)
 	return TRUE;
 }
 
-static guint
-_link_get_flags (NMPlatform *self, int ifindex)
+int
+nm_platform_link_get_ifi_flags (NMPlatform *self,
+                                int ifindex,
+                                guint requested_flags)
 {
 	const NMPlatformLink *pllink;
 
-	pllink = nm_platform_link_get (self, ifindex);
-	return pllink ? pllink->n_ifi_flags : IFF_NOARP;
+	_CHECK_SELF (self, klass, -EINVAL);
+
+	if (ifindex <= 0)
+		return -EINVAL;
+
+	/* include invisible links (only in netlink, not udev). */
+	pllink = NMP_OBJECT_CAST_LINK (nm_platform_link_get_obj (self, ifindex, FALSE));
+	if (!pllink)
+		return -ENODEV;
+
+	/* Errors are signaled as negative values. That means, you cannot request
+	 * the most significant bit (2^31) with this API. Assert against that. */
+	nm_assert ((int) requested_flags >= 0);
+	nm_assert (requested_flags < (guint) G_MAXINT);
+
+	return (int) (pllink->n_ifi_flags & requested_flags);
 }
 
 /**
@@ -1210,9 +1226,7 @@ _link_get_flags (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_is_up (NMPlatform *self, int ifindex)
 {
-	_CHECK_SELF (self, klass, FALSE);
-
-	return NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_UP);
+	return nm_platform_link_get_ifi_flags (self, ifindex, IFF_UP) == IFF_UP;
 }
 
 /**
@@ -1243,9 +1257,15 @@ nm_platform_link_is_connected (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	int f;
 
-	return !NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_NOARP);
+	f = nm_platform_link_get_ifi_flags (self, ifindex, IFF_NOARP);
+
+	if (f < 0)
+		return FALSE;
+	if (f == IFF_NOARP)
+		return FALSE;
+	return TRUE;
 }
 
 /**
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 866df736..94028553 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -1111,6 +1111,7 @@ int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
 const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
 NMLinkType nm_platform_link_get_type (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_is_software (NMPlatform *self, int ifindex);
+int nm_platform_link_get_ifi_flags (NMPlatform *self, int ifindex, guint requested_flags);
 gboolean nm_platform_link_is_up (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex);
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 8b8c87d8..12d91812 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -60,7 +60,7 @@ test_cleanup_internal (void)
 	g_assert (ifindex > 0);
 
 	/* wait for kernel to add the IPv6 link local address... it takes a bit. */
-	NMTST_WAIT_ASSERT (100, {
+	NMTST_WAIT_ASSERT (300, {
 		gs_unref_array GArray *addrs = NULL;
 		const NMPlatformIP6Address *a;
 
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 42569d5b..1095a2b1 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -787,8 +787,6 @@ nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
 		if (lft == NM_PLATFORM_LIFETIME_PERMANENT)
 			g_assert_cmpint (adr, ==, NM_PLATFORM_LIFETIME_PERMANENT);
 		else {
-			g_assert_cmpint (adr, <=, lft);
-			g_assert_cmpint (offset, <=, adr);
 			g_assert_cmpint (adr - offset, <=, lft + CHECK_LIFETIME_MAX_DIFF);
 			g_assert_cmpint (adr - offset, >=, lft - CHECK_LIFETIME_MAX_DIFF);
 		}
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 40bdea64..a2a4df70 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -438,10 +438,11 @@ impl_ppp_manager_set_ifindex (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(i)", &ifindex);
 
-	_LOGD ("set-ifindex %d", (int) ifindex);
-
 	if (priv->ifindex >= 0) {
-		_LOGW ("can't change the ifindex from %d to %d", priv->ifindex, (int) ifindex);
+		if (priv->ifindex == ifindex)
+			_LOGD ("set-ifindex: ignore repeated calls setting ifindex to %d", (int) ifindex);
+		else
+			_LOGW ("set-ifindex: can't change the ifindex from %d to %d", priv->ifindex, (int) ifindex);
 		goto out;
 	}
 
@@ -454,14 +455,15 @@ impl_ppp_manager_set_ifindex (NMDBusObject *obj,
 	}
 
 	if (!plink) {
-		_LOGW ("unknown interface with ifindex %d", ifindex);
+		_LOGW ("set-ifindex: unknown interface with ifindex %d", ifindex);
 		ifindex = 0;
+	} else {
+		obj_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
+		_LOGD ("set-ifindex: %d, name \"%s\"", (int) ifindex, plink->name);
 	}
 
 	priv->ifindex = ifindex;
 
-	obj_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
-
 	g_signal_emit (self,
 	               signals[IFINDEX_SET],
 	               0,
diff --git a/src/ppp/nm-pppd-plugin.c b/src/ppp/nm-pppd-plugin.c
index 989f7433..09196340 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/ppp/nm-pppd-plugin.c
@@ -147,7 +147,7 @@ nm_phasechange (void *data, int arg)
 		if (   if_indextoname (index, new_name)
 		    && !nm_streq0 (ifname, new_name)) {
 			g_message ("nm-ppp-plugin: interface name changed from '%s' to '%s'", ifname, new_name);
-			strncpy (ifname, new_name, IF_NAMESIZE);
+			g_strlcpy (ifname, new_name, IF_NAMESIZE);
 		}
 	}
 }
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 80db5baa..22f9a3c0 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -491,6 +491,12 @@ nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self,
 	if (is_ap) {
 		if (!nm_supplicant_config_add_option (self, "mode", "2", -1, NULL, error))
 			return FALSE;
+
+		if (   nm_setting_wireless_get_hidden (setting)
+		    && !nm_supplicant_config_add_option (self,
+		                                         "ignore_broadcast_ssid", "1",
+		                                         -1, NULL, error))
+			return FALSE;
 	}
 
 	if ((is_adhoc || is_ap) && fixed_freq) {
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 317afff9..01babbf7 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -151,6 +151,7 @@ static const struct Opt opt_table[] = {
 	{ "mka_ckn",            TYPE_BYTES,   0, 65536, FALSE, NULL },
 	{ "macsec_port",        TYPE_INT,     1, 65534, FALSE, NULL },
 	{ "ieee80211w",         TYPE_INT,     0, 2, FALSE, NULL },
+	{ "ignore_broadcast_ssid", TYPE_INT,  0, 2, FALSE, NULL },
 };
 
 static gboolean
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index 90fe29d0..80e9577c 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -128,8 +128,6 @@ int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link,
         const uint8_t *bcast_addr = NULL;
         uint8_t dhcp_hlen = 0;
 
-        assert_return(mac_addr_len > 0, -EINVAL);
-
         if (arp_type == ARPHRD_ETHER) {
                 assert_return(mac_addr_len == ETH_ALEN, -EINVAL);
                 memcpy(&eth_mac, mac_addr, ETH_ALEN);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/systemd/src/libsystemd-network/dhcp6-internal.h
index f1cbd6a4..06e2e532 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -91,7 +91,7 @@ int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd);
 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
                        size_t *optlen, uint8_t **optvalue);
-int dhcp6_option_parse_status(DHCP6Option *option);
+int dhcp6_option_parse_status(DHCP6Option *option, size_t len);
 int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia);
 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
                                 struct in6_addr **addrs, size_t count,
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index a8a56463..22970443 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -105,7 +105,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
                 return -EINVAL;
         }
 
-        if (*buflen < len)
+        if (*buflen < offsetof(DHCP6Option, data) + len)
                 return -ENOBUFS;
 
         ia_hdr = *buf;
@@ -249,10 +249,11 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
         return 0;
 }
 
-int dhcp6_option_parse_status(DHCP6Option *option) {
+int dhcp6_option_parse_status(DHCP6Option *option, size_t len) {
         DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option;
 
-        if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*statusopt))
+        if (len < sizeof(DHCP6StatusOption) ||
+            be16toh(option->len) + sizeof(DHCP6Option) < sizeof(DHCP6StatusOption))
                 return -ENOBUFS;
 
         return be16toh(statusopt->status);
@@ -279,7 +280,7 @@ static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia,
         }
 
         if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*addr_option)) {
-                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options);
+                r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options, be16toh(option->len) + sizeof(DHCP6Option) - sizeof(*addr_option));
                 if (r != 0)
                         return r < 0 ? r: 0;
         }
@@ -319,7 +320,7 @@ static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
         }
 
         if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*pdprefix_option)) {
-                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options);
+                r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options, be16toh(option->len) + sizeof(DHCP6Option) - sizeof(*pdprefix_option));
                 if (r != 0)
                         return r < 0 ? r: 0;
         }
@@ -464,13 +465,15 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
 
-                        status = dhcp6_option_parse_status(option);
-                        if (status) {
+                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
+                        if (status < 0) {
+                                r = status;
+                                goto error;
+                        }
+                        if (status > 0) {
                                 log_dhcp6_client(client, "IA status %d",
                                                  status);
 
-                                dhcp6_lease_free_ia(ia);
-
                                 r = -EINVAL;
                                 goto error;
                         }
@@ -552,6 +555,7 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
                 bool first = true;
 
                 for (;;) {
+                        const char *label;
                         uint8_t c;
 
                         c = optval[pos++];
@@ -559,47 +563,41 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
                         if (c == 0)
                                 /* End of name */
                                 break;
-                        else if (c <= 63) {
-                                const char *label;
-
-                                /* Literal label */
-                                label = (const char *)&optval[pos];
-                                pos += c;
-                                if (pos > optlen)
-                                        return -EMSGSIZE;
-
-                                if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
-
-                                if (first)
-                                        first = false;
-                                else
-                                        ret[n++] = '.';
-
-                                r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
-                                if (r < 0)
-                                        goto fail;
-
-                                n += r;
-                                continue;
-                        } else {
-                                r = -EBADMSG;
-                                goto fail;
-                        }
-                }
+                        if (c > 63)
+                                return -EBADMSG;
+
+                        /* Literal label */
+                        label = (const char *)&optval[pos];
+                        pos += c;
+                        if (pos >= optlen)
+                                return -EMSGSIZE;
+
+                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                return -ENOMEM;
+
+                        if (first)
+                                first = false;
+                        else
+                                ret[n++] = '.';
 
-                if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
-                        r = -ENOMEM;
-                        goto fail;
+                        r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
+                        if (r < 0)
+                                return r;
+
+                        n += r;
                 }
 
+                if (n == 0)
+                        continue;
+
+                if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                        return -ENOMEM;
+
                 ret[n] = 0;
 
                 r = strv_extend(&names, ret);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 idx++;
         }
@@ -607,7 +605,4 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
         *str_arr = TAKE_PTR(names);
 
         return idx;
-
-fail:
-        return r;
 }
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index c2f81e1c..c2802541 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -1649,6 +1649,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                         client->timeout_resend =
                                 sd_event_source_unref(client->timeout_resend);
 
+                        client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
                         r = client_initialize(client);
                         if (r < 0)
                                 goto error;
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index 33a0796a..841d0792 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -279,6 +279,8 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
                 free(option);
         }
 
+        free(lease->root_path);
+        free(lease->timezone);
         free(lease->hostname);
         free(lease->domainname);
         free(lease->dns);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index ca03f580..5641c545 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -776,8 +776,8 @@ static int client_parse_message(
                 uint8_t *optval;
                 be32_t iaid_lease;
 
-                if (len < offsetof(DHCP6Option, data) ||
-                    len < offsetof(DHCP6Option, data) + be16toh(option->len))
+                if (len < pos + offsetof(DHCP6Option, data) ||
+                    len < pos + offsetof(DHCP6Option, data) + be16toh(option->len))
                         return -ENOBUFS;
 
                 optcode = be16toh(option->code);
@@ -828,13 +828,14 @@ static int client_parse_message(
                         break;
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
-                        status = dhcp6_option_parse_status(option);
-                        if (status) {
+                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
+                        if (status < 0)
+                                return status;
+
+                        if (status > 0) {
                                 log_dhcp6_client(client, "%s Status %s",
                                                  dhcp6_message_type_to_string(message->type),
                                                  dhcp6_message_status_to_string(status));
-                                dhcp6_lease_free_ia(&lease->ia);
-                                dhcp6_lease_free_ia(&lease->pd);
 
                                 return -EINVAL;
                         }
@@ -1233,6 +1234,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                 log_dhcp6_client(client, "T1 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
 
+                client->lease->ia.timeout_t1 = sd_event_source_unref(client->lease->ia.timeout_t1);
                 r = sd_event_add_time(client->event,
                                       &client->lease->ia.timeout_t1,
                                       clock_boottime_or_monotonic(), time_now + timeout,
@@ -1255,6 +1257,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                 log_dhcp6_client(client, "T2 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
 
+                client->lease->ia.timeout_t2 = sd_event_source_unref(client->lease->ia.timeout_t2);
                 r = sd_event_add_time(client->event,
                                       &client->lease->ia.timeout_t2,
                                       clock_boottime_or_monotonic(), time_now + timeout,