about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:13:33 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:13:33 +0100
commitdd428301eb6f02542015121d7b08d9997f137e50 (patch)
tree5530189f63510287d65268fc36025bdbc9414c00 /src
parentbbae86d3d2997a853ca0365e8eb7a3ca7489ee09 (diff)
New upstream version 1.15.91
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c57
-rw-r--r--src/devices/adsl/nm-atm-manager.c1
-rw-r--r--src/devices/adsl/nm-device-adsl.c26
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c2
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c2
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c33
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c1
-rw-r--r--src/devices/bluetooth/nm-device-bt.c49
-rw-r--r--src/devices/nm-acd-manager.c2
-rw-r--r--src/devices/nm-device-bond.c1
-rw-r--r--src/devices/nm-device-dummy.c1
-rw-r--r--src/devices/nm-device-ethernet.c30
-rw-r--r--src/devices/nm-device-factory.c2
-rw-r--r--src/devices/nm-device-ip-tunnel.c11
-rw-r--r--src/devices/nm-device-macvlan.c1
-rw-r--r--src/devices/nm-device-ppp.c39
-rw-r--r--src/devices/nm-device-private.h77
-rw-r--r--src/devices/nm-device-tun.c1
-rw-r--r--src/devices/nm-device-veth.c2
-rw-r--r--src/devices/nm-device-vxlan.c2
-rw-r--r--src/devices/nm-device-wireguard.c1501
-rw-r--r--src/devices/nm-device-wpan.c1
-rw-r--r--src/devices/nm-device.c1228
-rw-r--r--src/devices/nm-device.h26
-rw-r--r--src/devices/nm-lldp-listener.c11
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c18
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c32
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c18
-rw-r--r--src/devices/ovs/nm-ovsdb.c79
-rw-r--r--src/devices/team/nm-team-factory.c1
-rw-r--r--src/devices/tests/meson.build1
-rw-r--r--src/devices/wifi/meson.build8
-rw-r--r--src/devices/wifi/nm-device-iwd.c16
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c2
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c1316
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.h50
-rw-r--r--src/devices/wifi/nm-device-wifi.c265
-rw-r--r--src/devices/wifi/nm-device-wifi.h1
-rw-r--r--src/devices/wifi/nm-iwd-manager.c9
-rw-r--r--src/devices/wifi/nm-wifi-ap.c6
-rw-r--r--src/devices/wifi/nm-wifi-factory.c23
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c796
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.h114
-rw-r--r--src/devices/wifi/nm-wifi-utils.c7
-rw-r--r--src/devices/wifi/tests/meson.build1
-rw-r--r--src/devices/wifi/tests/test-general.c3
-rw-r--r--src/devices/wwan/nm-device-modem.c65
-rw-r--r--src/devices/wwan/nm-modem-broadband.c18
-rw-r--r--src/devices/wwan/nm-modem-manager.c1
-rw-r--r--src/devices/wwan/nm-modem-ofono.c5
-rw-r--r--src/devices/wwan/nm-modem.c13
-rw-r--r--src/devices/wwan/nm-wwan-factory.c1
-rw-r--r--src/dhcp/nm-dhcp-client.c4
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c3
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c14
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c9
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c9
-rw-r--r--src/dhcp/nm-dhcp-helper.c1
-rw-r--r--src/dhcp/nm-dhcp-listener.c2
-rw-r--r--src/dhcp/nm-dhcp-manager.c2
-rw-r--r--src/dhcp/nm-dhcp-systemd.c143
-rw-r--r--src/dhcp/nm-dhcp-utils.c6
-rw-r--r--src/dhcp/tests/meson.build1
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c5
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c1
-rw-r--r--src/dns/nm-dns-manager.c47
-rw-r--r--src/dns/nm-dns-plugin.c1
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c1
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.c1
-rw-r--r--src/initrd/nm-initrd-generator.c4
-rw-r--r--src/initrd/nm-initrd-generator.h21
-rw-r--r--src/initrd/nmi-cmdline-reader.c8
-rw-r--r--src/initrd/nmi-ibft-reader.c23
-rw-r--r--src/initrd/tests/test-cmdline-reader.c1
-rw-r--r--src/initrd/tests/test-ibft-reader.c22
-rw-r--r--src/main-utils.c26
-rw-r--r--src/main.c14
-rw-r--r--src/meson.build2
-rw-r--r--src/ndisc/nm-fake-ndisc.c1
-rw-r--r--src/ndisc/nm-lndp-ndisc.c17
-rw-r--r--src/ndisc/nm-ndisc.c3
-rw-r--r--src/ndisc/tests/meson.build1
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c1
-rw-r--r--src/ndisc/tests/test-ndisc-linux.c1
-rw-r--r--src/nm-act-request.c4
-rw-r--r--src/nm-act-request.h2
-rw-r--r--src/nm-audit-manager.c10
-rw-r--r--src/nm-auth-subject.c1
-rw-r--r--src/nm-auth-utils.c3
-rw-r--r--src/nm-checkpoint.c2
-rw-r--r--src/nm-config-data.c3
-rw-r--r--src/nm-config.c1
-rw-r--r--src/nm-connectivity.c138
-rw-r--r--src/nm-core-utils.c81
-rw-r--r--src/nm-core-utils.h4
-rw-r--r--src/nm-dbus-manager.c2
-rw-r--r--src/nm-dcb.c1
-rw-r--r--src/nm-dhcp4-config.c2
-rw-r--r--src/nm-dhcp6-config.c2
-rw-r--r--src/nm-dispatcher.c5
-rw-r--r--src/nm-firewall-manager.c2
-rw-r--r--src/nm-hostname-manager.c2
-rw-r--r--src/nm-iface-helper.c28
-rw-r--r--src/nm-ip4-config.c73
-rw-r--r--src/nm-ip4-config.h7
-rw-r--r--src/nm-ip6-config.c70
-rw-r--r--src/nm-ip6-config.h2
-rw-r--r--src/nm-keep-alive.c6
-rw-r--r--src/nm-logging.c582
-rw-r--r--src/nm-logging.h44
-rw-r--r--src/nm-manager.c26
-rw-r--r--src/nm-policy.c53
-rw-r--r--src/nm-rfkill-manager.c1
-rw-r--r--src/nm-session-monitor.c6
-rw-r--r--src/nm-sleep-monitor.c2
-rw-r--r--src/nm-types.h1
-rw-r--r--src/org.freedesktop.NetworkManager.conf4
-rw-r--r--src/platform/nm-fake-platform.c1
-rw-r--r--src/platform/nm-linux-platform.c918
-rw-r--r--src/platform/nm-netlink.c183
-rw-r--r--src/platform/nm-netlink.h243
-rw-r--r--src/platform/nm-platform-utils.c31
-rw-r--r--src/platform/nm-platform.c57
-rw-r--r--src/platform/nm-platform.h37
-rw-r--r--src/platform/nmp-netns.c239
-rw-r--r--src/platform/nmp-object.c19
-rw-r--r--src/platform/nmp-object.h18
-rw-r--r--src/platform/tests/meson.build20
-rw-r--r--src/platform/tests/test-common.c12
-rw-r--r--src/platform/tests/test-common.h1
-rw-r--r--src/platform/tests/test-link.c79
-rw-r--r--src/platform/tests/test-route.c2
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c201
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c38
-rw-r--r--src/platform/wifi/nm-wifi-utils.c1
-rw-r--r--src/platform/wpan/nm-wpan-utils.c19
-rw-r--r--src/ppp/nm-ppp-manager-call.c1
-rw-r--r--src/ppp/nm-ppp-manager.c40
-rw-r--r--src/ppp/nm-pppd-plugin.c1
-rw-r--r--src/settings/nm-agent-manager.c1
-rw-r--r--src/settings/nm-settings-connection.c170
-rw-r--r--src/settings/nm-settings.c6
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.c1
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c2
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c2
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c8
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c50
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c6
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c26
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae5
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c51
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.c1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c6
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c7
-rw-r--r--src/settings/plugins/ifupdown/tests/test-ifupdown.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c1
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c1
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c5
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c73
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build1
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c10
-rw-r--r--src/supplicant/nm-supplicant-config.c75
-rw-r--r--src/supplicant/nm-supplicant-interface.c43
-rw-r--r--src/supplicant/nm-supplicant-manager.c70
-rw-r--r--src/supplicant/nm-supplicant-manager.h3
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c20
-rw-r--r--src/supplicant/tests/meson.build1
-rw-r--r--src/supplicant/tests/test-supplicant-config.c72
-rw-r--r--src/systemd/sd-adapt-core/device-util.h3
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c7
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h4
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c12
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-option.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-network.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c1
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c58
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h11
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c25
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c76
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c10
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c1
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c1
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c1
-rw-r--r--src/systemd/src/libsystemd/sd-event/event-util.c1
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c23
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c1
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c1
-rw-r--r--src/systemd/src/shared/dns-domain.h1
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h2
-rw-r--r--src/tests/config/meson.build1
-rw-r--r--src/tests/config/nm-test-device.c3
-rw-r--r--src/tests/meson.build1
-rw-r--r--src/tests/test-dcb.c2
-rw-r--r--src/tests/test-general-with-expect.c5
-rw-r--r--src/tests/test-general.c18
-rw-r--r--src/tests/test-ip4-config.c1
-rw-r--r--src/tests/test-ip6-config.c1
-rw-r--r--src/tests/test-systemd.c12
-rw-r--r--src/tests/test-utils.c2
-rw-r--r--src/vpn/nm-vpn-connection.c48
-rw-r--r--src/vpn/nm-vpn-connection.h2
-rw-r--r--src/vpn/nm-vpn-manager.c2
212 files changed, 7704 insertions, 3068 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index fc0c5b8d..71bfbf7c 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -212,10 +212,7 @@ nm_utils_connection_has_default_route (NMConnection *connection,
 	if (!connection)
 		goto out;
 
-	if (addr_family == AF_INET)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 	if (!s_ip)
 		goto out;
 	if (nm_setting_ip_config_get_never_default (s_ip)) {
@@ -377,40 +374,36 @@ check_ip6_method (NMConnection *orig,
 static int
 route_compare (NMIPRoute *route1, NMIPRoute *route2, gint64 default_metric)
 {
-	gint64 r, metric1, metric2;
+	NMIPAddr a1;
+	NMIPAddr a2;
+	guint64 m1;
+	guint64 m2;
 	int family;
 	guint plen;
-	NMIPAddr a1 = { 0 }, a2 = { 0 };
 
 	family = nm_ip_route_get_family (route1);
-	r = family - nm_ip_route_get_family (route2);
-	if (r)
-		return r > 0 ? 1 : -1;
+	NM_CMP_DIRECT (family, nm_ip_route_get_family (route2));
+
+	nm_assert_addr_family (family);
 
 	plen = nm_ip_route_get_prefix (route1);
-	r = plen - nm_ip_route_get_prefix (route2);
-	if (r)
-		return r > 0 ? 1 : -1;
-
-	metric1 = nm_ip_route_get_metric (route1) == -1 ? default_metric : nm_ip_route_get_metric (route1);
-	metric2 = nm_ip_route_get_metric (route2) == -1 ? default_metric : nm_ip_route_get_metric (route2);
-
-	r = metric1 - metric2;
-	if (r)
-		return r > 0 ? 1 : -1;
-
-	r = g_strcmp0 (nm_ip_route_get_next_hop (route1), nm_ip_route_get_next_hop (route2));
-	if (r)
-		return r;
-
-	/* NMIPRoute validates family and dest. inet_pton() is not expected to fail. */
-	inet_pton (family, nm_ip_route_get_dest (route1), &a1);
-	inet_pton (family, nm_ip_route_get_dest (route2), &a2);
-	nm_utils_ipx_address_clear_host_address (family, &a1, &a1, plen);
-	nm_utils_ipx_address_clear_host_address (family, &a2, &a2, plen);
-	r = memcmp (&a1, &a2, sizeof (a1));
-	if (r)
-		return r;
+	NM_CMP_DIRECT (plen, nm_ip_route_get_prefix (route2));
+
+	m1 = nm_ip_route_get_metric (route1);
+	m2 = nm_ip_route_get_metric (route2);
+	NM_CMP_DIRECT (m1 == -1 ? default_metric : m1,
+	               m2 == -1 ? default_metric : m2);
+
+	NM_CMP_DIRECT_STRCMP0 (nm_ip_route_get_next_hop (route1),
+	                       nm_ip_route_get_next_hop (route2));
+
+	if (!inet_pton (family, nm_ip_route_get_dest (route1), &a1))
+		nm_assert_not_reached ();
+	if (!inet_pton (family, nm_ip_route_get_dest (route2), &a2))
+		nm_assert_not_reached ();
+	nm_utils_ipx_address_clear_host_address (family, &a1, NULL, plen);
+	nm_utils_ipx_address_clear_host_address (family, &a2, NULL, plen);
+	NM_CMP_DIRECT_MEMCMP (&a1, &a2, nm_utils_addr_family_to_size (family));
 
 	return 0;
 }
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index 0ff4603d..dddb8342 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 #include <libudev.h>
 
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index ddc03ce3..b3b87dc7 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -25,13 +25,12 @@
 #include <sys/socket.h>
 #include <linux/atmdev.h>
 #include <linux/atmbr2684.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <stdlib.h>
-#include <string.h>
 
+#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
 #include "ppp/nm-ppp-manager-call.h"
@@ -371,8 +370,8 @@ br2684_create_iface (NMDeviceAdsl *self,
 			priv->nas_update_id = g_timeout_add (100, nas_update_cb, self);
 			return NM_ACT_STAGE_RETURN_POSTPONE;
 		}
-		if (errno != EEXIST) {
-			errsv = errno;
+		errsv = errno;
+		if (errsv != EEXIST) {
 			_LOGW (LOGD_ADSL, "failed to create br2684 interface (%d)", errsv);
 			break;
 		}
@@ -449,9 +448,8 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	NMDevice *device = NM_DEVICE (user_data);
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
-	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_activate_schedule_ip4_config_result (device, config);
-	}
+	if (nm_device_activate_ip4_state_in_conf (device))
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static NMActStageReturn
@@ -520,6 +518,18 @@ act_stage3_ip4_config_start (NMDevice *device,
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	if (addr_family == AF_INET)
+		return act_stage3_ip4_config_start (device, (NMIP4Config **) out_config, out_failure_reason);
+
+	return NM_DEVICE_CLASS (nm_device_adsl_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
 static void
 adsl_cleanup (NMDeviceAdsl *self)
 {
@@ -689,7 +699,7 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 	device_class->complete_connection = complete_connection;
 
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->deactivate = deactivate;
 
 	obj_properties[PROP_ATM_INDEX] =
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index 8e2a96b7..377ee478 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -23,8 +23,6 @@
 
 #include "nm-bluez-device.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 #include "nm-bt-error.h"
 #include "nm-bluez-common.h"
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 711f2e57..dc85a8b2 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -21,7 +21,6 @@
 #include "nm-default.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index c8ef7a27..3a456deb 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -22,8 +22,6 @@
 
 #include "nm-bluez4-adapter.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index 1fe02f18..82d995be 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -24,7 +24,6 @@
 #include "nm-bluez4-manager.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-bluez4-adapter.h"
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index 32f8da65..ff3a0da9 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -27,7 +27,6 @@
 #include <net/ethernet.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
-#include <errno.h>
 #include <fcntl.h>
 
 #include "nm-bluez5-dun.h"
@@ -57,6 +56,7 @@ dun_connect (NMBluez5DunContext *context)
 	char tty[100];
 	const int ttylen = sizeof (tty) - 1;
 	GError *error = NULL;
+	int errsv;
 
 	struct rfcomm_dev_req req = {
 		.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP),
@@ -66,10 +66,10 @@ dun_connect (NMBluez5DunContext *context)
 
 	context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM);
 	if (context->rfcomm_fd < 0) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to create RFCOMM socket: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
@@ -78,20 +78,20 @@ dun_connect (NMBluez5DunContext *context)
 	sa.rc_channel = 0;
 	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
 	if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to bind socket: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
 	sa.rc_channel = context->rfcomm_channel;
 	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
 	if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to connect to remote device: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
@@ -103,10 +103,10 @@ dun_connect (NMBluez5DunContext *context)
 	memcpy (&req.dst, &context->dst, ETH_ALEN);
 	devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req);
 	if (devid < 0) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to create rfcomm device: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 	context->rfcomm_id = devid;
@@ -250,7 +250,8 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 	sdp_list_t *search, *attrs;
 	uuid_t svclass;
 	uint16_t attr;
-	int fd, err, fd_err = 0;
+	int fd, fd_err = 0;
+	int err;
 	socklen_t len = sizeof (fd_err);
 	GError *error = NULL;
 
@@ -258,19 +259,19 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 
 	fd = g_io_channel_unix_get_fd (channel);
 	if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) {
-		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
-		            context->src_str, context->dst_str, errno);
 		err = errno;
+		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
+		            context->src_str, context->dst_str, err);
 	} else {
+		err = fd_err;
 		nm_log_dbg (LOGD_BT, "(%s -> %s): SO_ERROR error=%d",
 		            context->src_str, context->dst_str, fd_err);
-		err = fd_err;
 	}
 
 	if (err != 0) {
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Error on Service Discovery socket: (%d) %s",
-		                     err, strerror (err));
+		                     err, nm_strerror_native (err));
 		goto done;
 	}
 
@@ -297,7 +298,7 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 		error = g_error_new (NM_BT_ERROR,
 		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Error starting Service Discovery: (%d) %s",
-		                     err, strerror (err));
+		                     err, nm_strerror_native (err));
 	}
 
 	sdp_list_free (attrs, NULL);
@@ -358,7 +359,7 @@ nm_bluez5_dun_connect (NMBluez5DunContext *context,
 
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to connect to the SDP server: (%d) %s",
-		                      err, strerror (err));
+		                      err, nm_strerror_native (err));
 		/* FIXME: don't invoke the callback synchronously. */
 		context->callback (context, NULL, error, context->user_data);
 		return;
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index e984212b..be15d824 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -25,7 +25,6 @@
 #include "nm-bluez5-manager.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-core-internal.h"
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 68209d8a..e79251ce 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -23,7 +23,6 @@
 #include "nm-device-bt.h"
 
 #include <stdio.h>
-#include <string.h>
 
 #include "nm-bluez-common.h"
 #include "nm-bluez-device.h"
@@ -39,6 +38,7 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-utils.h"
 #include "nm-bt-error.h"
+#include "nm-ip4-config.h"
 #include "platform/nm-platform.h"
 
 #include "devices/wwan/nm-modem-manager.h"
@@ -398,9 +398,9 @@ ppp_failed (NMModem *modem,
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
-			nm_device_activate_schedule_ip4_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET);
 		else if (nm_device_activate_ip6_state_in_conf (device))
-			nm_device_activate_schedule_ip6_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET6);
 		else if (nm_device_activate_ip4_state_done (device)) {
 			nm_device_ip_method_failed (device,
 			                            AF_INET,
@@ -542,7 +542,7 @@ modem_ip4_config_result (NMModem *modem,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else
-		nm_device_activate_schedule_ip4_config_result (device, config);
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static void
@@ -899,33 +899,29 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
 
+	nm_assert_addr_family (addr_family);
+
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
-		return nm_modem_stage3_ip4_config_start (priv->modem,
-		                                         device,
-		                                         NM_DEVICE_CLASS (nm_device_bt_parent_class),
-		                                         out_failure_reason);
+		if (addr_family == AF_INET) {
+			return nm_modem_stage3_ip4_config_start (priv->modem,
+			                                         device,
+			                                         NM_DEVICE_CLASS (nm_device_bt_parent_class),
+			                                         out_failure_reason);
+		} else {
+			return nm_modem_stage3_ip6_config_start (priv->modem,
+			                                         device,
+			                                         out_failure_reason);
+		}
 	}
 
-	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN)
-		return nm_modem_stage3_ip6_config_start (priv->modem, device, out_failure_reason);
-
-	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static void
@@ -1204,8 +1200,7 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index a0a175be..a8f7a63a 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -99,7 +99,7 @@ static const char *
 acd_error_to_string (int error)
 {
 	if (error < 0)
-		return g_strerror (-error);
+		return nm_strerror_native (-error);
 
 	switch (error) {
 	case _N_ACD_E_SUCCESS:
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 78cba9bb..6dabdfe8 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-bond.h"
 
-#include <errno.h>
 #include <stdlib.h>
 
 #include "NetworkManagerUtils.h"
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index eb90456a..47a45342 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -17,7 +17,6 @@
 #include "nm-device-dummy.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 
 #include "nm-act-request.h"
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 3b6fb35b..24c99f76 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -24,10 +24,8 @@
 #include "nm-device-ethernet.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <errno.h>
 #include <libudev.h>
 
 #include "nm-device-private.h"
@@ -984,9 +982,8 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	NMDevice *device = NM_DEVICE (user_data);
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
-	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_activate_schedule_ip4_config_result (device, config);
-	}
+	if (nm_device_activate_ip4_state_in_conf (device))
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static NMActStageReturn
@@ -1317,22 +1314,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMSettingConnection *s_con;
 	const char *connection_type;
 
-	s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
+	if (addr_family == AF_INET) {
+		s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
 
-	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
+		g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
 
-	connection_type = nm_setting_connection_get_connection_type (s_con);
-	if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME))
-		return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), out_failure_reason);
+		connection_type = nm_setting_connection_get_connection_type (s_con);
+		if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME))
+			return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), out_failure_reason);
+	}
 
-	return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -1794,7 +1794,7 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->deactivate = deactivate;
 	device_class->get_s390_subchannels = get_s390_subchannels;
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index a11ead6e..30aca038 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -24,8 +24,6 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 #include <gmodule.h>
 
 #include "platform/nm-platform.h"
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index ca5f9c3f..96275947 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-ip-tunnel.h"
 
-#include <string.h>
 #include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/ip.h>
@@ -149,9 +148,13 @@ update_properties_from_ifindex (NMDevice *device, int ifindex)
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 	int parent_ifindex = 0;
-	in_addr_t local4 = 0, remote4 = 0;
-	struct in6_addr local6 = { 0 }, remote6 = { 0 };
-	guint8 ttl = 0, tos = 0, encap_limit = 0;
+	in_addr_t local4 = 0;
+	in_addr_t remote4 = 0;
+	struct in6_addr local6 = IN6ADDR_ANY_INIT;
+	struct in6_addr remote6 = IN6ADDR_ANY_INIT;
+	guint8 ttl = 0;
+	guint8 tos = 0;
+	guint8 encap_limit = 0;
 	gboolean pmtud = FALSE;
 	guint32 flow_label = 0;
 	NMIPTunnelFlags flags = NM_IP_TUNNEL_FLAG_NONE;
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index bb629713..aa2a0ac0 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-macvlan.h"
 
-#include <string.h>
 #include <linux/if_link.h>
 
 #include "nm-device-private.h"
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 2f565d4b..3c310146 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -16,6 +16,7 @@
 
 #include "nm-device-ppp.h"
 
+#include "nm-ip4-config.h"
 #include "nm-act-request.h"
 #include "nm-device-factory.h"
 #include "nm-device-private.h"
@@ -106,7 +107,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
 		if (nm_device_activate_ip4_state_in_conf (device)) {
-			nm_device_activate_schedule_ip4_config_result (device, config);
+			nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 			return;
 		}
 	} else {
@@ -172,23 +173,31 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
-	NMDevicePpp *self = NM_DEVICE_PPP (device);
-	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	if (addr_family == AF_INET) {
+		NMDevicePpp *self = NM_DEVICE_PPP (device);
+		NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+		if (priv->ip4_config) {
+			if (out_config)
+				*out_config = g_steal_pointer (&priv->ip4_config);
+			else
+				g_clear_object (&priv->ip4_config);
+			return NM_ACT_STAGE_RETURN_SUCCESS;
+		}
 
-	if (priv->ip4_config) {
-		if (out_config)
-			*out_config = g_steal_pointer (&priv->ip4_config);
-		else
-			g_clear_object (&priv->ip4_config);
-		return NM_ACT_STAGE_RETURN_SUCCESS;
+		/* Wait IPCP termination */
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	/* Wait IPCP termination */
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return NM_DEVICE_CLASS (nm_device_ppp_parent_class)->act_stage3_ip_config_start (device,
+	                                                                                 addr_family,
+	                                                                                 out_config,
+	                                                                                 out_failure_reason);
 }
 
 static gboolean
@@ -270,7 +279,7 @@ nm_device_ppp_class_init (NMDevicePppClass *klass)
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_PPP);
 
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->create_and_realize = create_and_realize;
 	device_class->deactivate = deactivate;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 6c0f473d..6e2372ab 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -26,6 +26,14 @@
 
 /* This file should only be used by subclasses of NMDevice */
 
+typedef enum {
+	NM_DEVICE_IP_STATE_NONE,
+	NM_DEVICE_IP_STATE_WAIT,
+	NM_DEVICE_IP_STATE_CONF,
+	NM_DEVICE_IP_STATE_DONE,
+	NM_DEVICE_IP_STATE_FAIL,
+} NMDeviceIPState;
+
 enum NMActStageReturn {
 	NM_ACT_STAGE_RETURN_FAILURE = 0, /* Hard failure of activation */
 	NM_ACT_STAGE_RETURN_SUCCESS,     /* Activation stage done */
@@ -75,19 +83,51 @@ void nm_device_set_firmware_missing (NMDevice *self, gboolean missing);
 void nm_device_activate_schedule_stage1_device_prepare (NMDevice *device);
 void nm_device_activate_schedule_stage2_device_config (NMDevice *device);
 
-void nm_device_activate_schedule_ip4_config_result(NMDevice *device, NMIP4Config *config);
-void nm_device_activate_schedule_ip4_config_timeout (NMDevice *device);
-
-void nm_device_activate_schedule_ip6_config_result (NMDevice *device);
-void nm_device_activate_schedule_ip6_config_timeout (NMDevice *device);
-
-gboolean nm_device_activate_ip4_state_in_conf (NMDevice *device);
-gboolean nm_device_activate_ip4_state_in_wait (NMDevice *device);
-gboolean nm_device_activate_ip4_state_done (NMDevice *device);
-
-gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device);
-gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device);
-gboolean nm_device_activate_ip6_state_done (NMDevice *device);
+void nm_device_activate_schedule_ip_config_result (NMDevice *device,
+                                                   int addr_family,
+                                                   NMIPConfig *config);
+
+void nm_device_activate_schedule_ip_config_timeout (NMDevice *device,
+                                                    int addr_family);
+
+NMDeviceIPState nm_device_activate_get_ip_state (NMDevice *self,
+                                                 int addr_family);
+
+static inline gboolean
+nm_device_activate_ip4_state_in_conf (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_CONF;
+}
+
+static inline gboolean
+nm_device_activate_ip4_state_in_wait (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_WAIT;
+}
+
+static inline gboolean
+nm_device_activate_ip4_state_done (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_DONE;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_in_conf (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_CONF;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_in_wait (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_WAIT;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_done (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_DONE;
+}
 
 void nm_device_set_dhcp_anycast_address (NMDevice *device, const char *addr);
 
@@ -106,8 +146,9 @@ void nm_device_queue_recheck_available (NMDevice *device,
                                         NMDeviceStateReason available_reason,
                                         NMDeviceStateReason unavailable_reason);
 
-void nm_device_set_wwan_ip4_config (NMDevice *device, NMIP4Config *config);
-void nm_device_set_wwan_ip6_config (NMDevice *device, NMIP6Config *config);
+void nm_device_set_dev2_ip_config (NMDevice *device,
+                                   int addr_family,
+                                   NMIPConfig *config);
 
 gboolean nm_device_hw_addr_is_explict (NMDevice *device);
 
@@ -118,6 +159,12 @@ gboolean nm_device_sysctl_ip_conf_set (NMDevice *self,
                                        const char *property,
                                        const char *value);
 
+NMIP4Config *nm_device_ip4_config_new (NMDevice *self);
+
+NMIP6Config *nm_device_ip6_config_new (NMDevice *self);
+
+NMIPConfig *nm_device_ip_config_new (NMDevice *self, int addr_family);
+
 /*****************************************************************************/
 
 gint64 nm_device_get_configured_mtu_from_connection_default (NMDevice *self,
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 3fe3dfd4..90360c9e 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -23,7 +23,6 @@
 #include "nm-device-tun.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 6f90758c..d7a59bae 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -20,9 +20,7 @@
 
 #include "nm-default.h"
 
-#include <errno.h>
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-device-veth.h"
 #include "nm-device-private.h"
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index 50730320..fc73c099 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-vxlan.h"
 
-#include <string.h>
-
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index 62ec0274..a9eb1ab4 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -21,24 +21,128 @@
 
 #include "nm-device-wireguard.h"
 
+#include "nm-setting-wireguard.h"
+#include "nm-core-internal.h"
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-device-private.h"
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "nm-device-factory.h"
+#include "nm-active-connection.h"
+#include "nm-act-request.h"
+#include "dns/nm-dns-manager.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWireGuard);
 
 /*****************************************************************************/
 
+/* TODO: activate profile with peer preshared-key-flags=2. On first activation, the secret is
+ *   requested (good). Enter it and connect. Reactivate the profile, now there is no password
+ *   prompt, as the secret is cached (good??). */
+
+/* TODO: unlike for other VPNs, we don't inject a direct route to the peers. That means,
+ *   you might get a routing sceneraio where the peer (VPN server) is reachable via the VPN.
+ *   How we handle adding routes to external gateway for other peers, has severe issues
+ *   as well. We may use policy-routing like wg-quick does. See also disussions at
+ *   https://www.wireguard.com/netns/#improving-the-classic-solutions */
+
+/* TODO: honor the TTL of DNS to determine when to retry resolving endpoints. */
+
+/* TODO: when we get multiple IP addresses when resolving a peer endpoint. We currently
+ *   just take the first from GAI. We should only accept AAAA/IPv6 if we also have a suitable
+ *   IPv6 address. The problem is, that we have to recheck that when IP addressing on other
+ *   interfaces changes. This makes it almost too cumbersome to implement. */
+
+/*****************************************************************************/
+
+G_STATIC_ASSERT (NM_WIREGUARD_PUBLIC_KEY_LEN   == NMP_WIREGUARD_PUBLIC_KEY_LEN);
+G_STATIC_ASSERT (NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_LEN);
+
+/*****************************************************************************/
+
+#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NS_PER_MSEC)
+
+/* a special @next_try_at_nsec timestamp indicating that we should try again as soon as possible. */
+#define NEXT_TRY_AT_NSEC_ASAP ((gint64) G_MAXINT64)
+
+/* a special @next_try_at_nsec timestamp that is
+ *  - positive (indicating resolve-checks are enabled)
+ *  - already in the past (we use the absolute timestamp of 1nsec for that). */
+#define NEXT_TRY_AT_NSEC_PAST ((gint64) 1)
+
+/* like %NEXT_TRY_AT_NSEC_ASAP, but used for indicating to retry ASAP for a @retry_in_msec value.
+ * That is a relative time duraction, contrary to @next_try_at_nsec which is an absolute
+ * timestamp. */
+#define RETRY_IN_MSEC_ASAP ((gint64) G_MAXINT64)
+
+#define RETRY_IN_MSEC_MAX ((gint64) (30 * 60 * 1000))
+
+typedef enum {
+	LINK_CONFIG_MODE_FULL,
+	LINK_CONFIG_MODE_REAPPLY,
+	LINK_CONFIG_MODE_ASSUME,
+	LINK_CONFIG_MODE_ENDPOINTS,
+} LinkConfigMode;
+
+typedef struct {
+	GCancellable *cancellable;
+
+	NMSockAddrUnion sockaddr;
+
+	/* the timestamp (in nm_utils_get_monotonic_timestamp_ns() scale) when we want
+	 * to retry resolving the endpoint (again).
+	 *
+	 * It may be set to %NEXT_TRY_AT_NSEC_ASAP to indicate to re-resolve as soon as possible.
+	 *
+	 * A @sockaddr is either fixed or it has
+	 *   - @cancellable set to indicate an ongoing request
+	 *   - @next_try_at_nsec set to a positive value, indicating when
+	 *     we ought to retry. */
+	gint64 next_try_at_nsec;
+
+	guint resolv_fail_count;
+} PeerEndpointResolveData;
+
+typedef struct {
+	NMWireGuardPeer *peer;
+
+	NMDeviceWireGuard *self;
+
+	CList lst_peers;
+
+	PeerEndpointResolveData ep_resolv;
+
+	/* dirty flag used during _peers_update_all(). */
+	bool dirty_update_all:1;
+} PeerData;
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard,
 	PROP_PUBLIC_KEY,
 	PROP_LISTEN_PORT,
 	PROP_FWMARK,
 );
 
+typedef struct {
+
+	NMDnsManager *dns_manager;
+
+	NMPlatformLnkWireGuard lnk_curr;
+	NMActRequestGetSecretsCallId *secrets_call_id;
+
+	CList lst_peers_head;
+	GHashTable *peers;
+
+	gint64 resolve_next_try_at;
+	guint  resolve_next_try_id;
+
+	gint64 link_config_last_at;
+	guint  link_config_delayed_id;
+} NMDeviceWireGuardPrivate;
+
 struct _NMDeviceWireGuard {
 	NMDevice parent;
-	NMPlatformLnkWireGuard props;
+	NMDeviceWireGuardPrivate _priv;
 };
 
 struct _NMDeviceWireGuardClass {
@@ -47,25 +151,710 @@ struct _NMDeviceWireGuardClass {
 
 G_DEFINE_TYPE (NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE)
 
-/******************************************************************/
+#define NM_DEVICE_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWireGuard, NM_IS_DEVICE_WIREGUARD, NMDevice)
+
+/*****************************************************************************/
+
+static void _peers_resolve_start (NMDeviceWireGuard *self,
+                                  PeerData *peer_data);
+
+static void _peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+                                             gint64 new_next_try_at_nsec);
+
+static gboolean link_config_delayed_resolver_cb (gpointer user_data);
+
+static gboolean link_config_delayed_ratelimit_cb (gpointer user_data);
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
+	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (NULL),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_FULL,      "full"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_REAPPLY,   "reapply"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ASSUME,    "assume"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ENDPOINTS, "endpoints"),
+);
+
+/*****************************************************************************/
+
+static gboolean
+_peer_data_equal (gconstpointer ptr_a, gconstpointer ptr_b)
+{
+	const PeerData *peer_data_a = ptr_a;
+	const PeerData *peer_data_b = ptr_b;
+
+	return nm_streq (nm_wireguard_peer_get_public_key (peer_data_a->peer),
+	                 nm_wireguard_peer_get_public_key (peer_data_b->peer));
+}
+
+static guint
+_peer_data_hash (gconstpointer ptr)
+{
+	const PeerData *peer_data = ptr;
+
+	return nm_hash_str (nm_wireguard_peer_get_public_key (peer_data->peer));
+}
+
+static PeerData *
+_peers_find (NMDeviceWireGuardPrivate *priv,
+             NMWireGuardPeer *peer)
+{
+	nm_assert (peer);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (PeerData, peer) == 0);
+
+	return g_hash_table_lookup (priv->peers, &peer);
+}
+
+static void
+_peers_remove (NMDeviceWireGuardPrivate *priv,
+               PeerData *peer_data)
+{
+	nm_assert (peer_data);
+	nm_assert (g_hash_table_lookup (priv->peers, peer_data) == peer_data);
+
+	if (!g_hash_table_remove (priv->peers, peer_data))
+		nm_assert_not_reached ();
+
+	c_list_unlink_stale (&peer_data->lst_peers);
+	nm_wireguard_peer_unref (peer_data->peer);
+	nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+	g_slice_free (PeerData, peer_data);
+
+	if (c_list_is_empty (&peer_data->lst_peers)) {
+		nm_clear_g_source (&priv->resolve_next_try_id);
+		nm_clear_g_source (&priv->link_config_delayed_id);
+	}
+}
+
+static PeerData *
+_peers_add (NMDeviceWireGuard *self,
+            NMWireGuardPeer *peer)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	nm_assert (peer);
+	nm_assert (nm_wireguard_peer_is_sealed (peer));
+	nm_assert (!_peers_find (priv, peer));
+
+	peer_data = g_slice_new (PeerData);
+	*peer_data = (PeerData) {
+		.self = self,
+		.peer = nm_wireguard_peer_ref (peer),
+		.ep_resolv = {
+			.sockaddr = NM_SOCK_ADDR_UNION_INIT_UNSPEC,
+		},
+	};
+
+	c_list_link_tail (&priv->lst_peers_head, &peer_data->lst_peers);
+	if (!nm_g_hash_table_add (priv->peers, peer_data))
+		nm_assert_not_reached ();
+	return peer_data;
+}
+
+static gboolean
+_peers_resolve_retry_timeout (gpointer user_data)
+{
+	NMDeviceWireGuard *self = user_data;
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+	gint64 now;
+	gint64 next;
+
+	priv->resolve_next_try_id = 0;
+
+	_LOGT (LOGD_DEVICE, "wireguard-peers: rechecking peer endpoints...");
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+	next = G_MAXINT64;
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->ep_resolv.next_try_at_nsec <= 0)
+			continue;
+
+		if (peer_data->ep_resolv.cancellable) {
+			/* we are currently resolving a name. We don't need the global
+			 * watchdog to guard this peer. No need to adjust @next for
+			 * this one, when the currently ongoing resolving completes, we
+			 * may reschedule. Skip. */
+			continue;
+		}
+
+		if (   peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP
+		    || now >= peer_data->ep_resolv.next_try_at_nsec) {
+			_peers_resolve_start (self, peer_data);
+			/* same here. Now we are resolving. We don't need the global
+			 * watchdog. Skip w.r.t. finding @next. */
+			continue;
+		}
+
+		if (next > peer_data->ep_resolv.next_try_at_nsec)
+			next = peer_data->ep_resolv.next_try_at_nsec;
+	}
+	if (next < G_MAXINT64)
+		_peers_resolve_retry_reschedule (self, next);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+                                 gint64 new_next_try_at_nsec)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	guint32 interval_ms;
+	gint64 now;
+
+	nm_assert (new_next_try_at_nsec > 0);
+	nm_assert (new_next_try_at_nsec != NEXT_TRY_AT_NSEC_ASAP);
+
+	if (   priv->resolve_next_try_id
+	    && priv->resolve_next_try_at <= new_next_try_at_nsec) {
+		/* we already have an earlier timeout scheduled (possibly for
+		 * another peer that expires sooner). Don't reschedule now.
+		 * Even if the scheduled timeout expires too early, we will
+		 * compute the right next-timeout and reschedule then. */
+		return;
+	}
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+
+	/* schedule at most one day ahead. No problem if we expire earlier
+	 * than expected. Also, rate-limit to 500 msec. */
+	interval_ms = NM_CLAMP ((new_next_try_at_nsec - now) / NM_UTILS_NS_PER_MSEC,
+	                        (gint64) 500,
+	                        (gint64) (24*60*60*1000));
+
+	_LOGT (LOGD_DEVICE, "wireguard-peers: schedule rechecking peer endpoints in %u msec",
+	       interval_ms);
+
+	nm_clear_g_source (&priv->resolve_next_try_id);
+	priv->resolve_next_try_at = new_next_try_at_nsec;
+	priv->resolve_next_try_id = g_timeout_add (interval_ms,
+	                                           _peers_resolve_retry_timeout,
+	                                           self);
+}
+
+static void
+_peers_resolve_retry_reschedule_for_peer (NMDeviceWireGuard *self,
+                                          PeerData *peer_data,
+                                          gint64 retry_in_msec)
+{
+	nm_assert (retry_in_msec >= 0);
+
+	if (retry_in_msec == RETRY_IN_MSEC_ASAP) {
+		_peers_resolve_start (self, peer_data);
+		return;
+	}
+
+	peer_data->ep_resolv.next_try_at_nsec =   nm_utils_get_monotonic_timestamp_ns ()
+	                                        + (retry_in_msec * NM_UTILS_NS_PER_MSEC);
+	_peers_resolve_retry_reschedule (self, peer_data->ep_resolv.next_try_at_nsec);
+}
+
+static gint64
+_peers_retry_in_msec (PeerData *peer_data,
+                      gboolean after_failure)
+{
+	if (peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP) {
+		peer_data->ep_resolv.resolv_fail_count = 0;
+		return RETRY_IN_MSEC_ASAP;
+	}
+
+	if (after_failure) {
+		if (peer_data->ep_resolv.resolv_fail_count < G_MAXUINT)
+			peer_data->ep_resolv.resolv_fail_count++;
+	} else
+		peer_data->ep_resolv.resolv_fail_count = 0;
+
+	if (!after_failure)
+		return RETRY_IN_MSEC_MAX;
 
-static GVariant *
-get_public_key_as_variant (const NMDeviceWireGuard *self)
+	if (peer_data->ep_resolv.resolv_fail_count > 20)
+		return RETRY_IN_MSEC_MAX;
+
+	/* double the retry-time, starting with one second. */
+	return NM_MIN (RETRY_IN_MSEC_MAX,
+	               (1u << peer_data->ep_resolv.resolv_fail_count) * 500);
+}
+
+static void
+_peers_resolve_cb (GObject *source_object,
+                   GAsyncResult *res,
+                   gpointer user_data)
+{
+	NMDeviceWireGuard *self;
+	PeerData *peer_data;
+	gs_free_error GError *resolv_error = NULL;
+	GList *list;
+	gboolean changed = FALSE;
+	NMSockAddrUnion sockaddr;
+	gint64 retry_in_msec;
+	char s_sockaddr[100];
+	char s_retry[100];
+
+	list = g_resolver_lookup_by_name_finish (G_RESOLVER (source_object), res, &resolv_error);
+
+	if (nm_utils_error_is_cancelled (resolv_error, FALSE))
+		return;
+
+	peer_data = user_data;
+	self = peer_data->self;
+
+	g_clear_object (&peer_data->ep_resolv.cancellable);
+
+	nm_assert ((!resolv_error) != (!list));
+
+#define _retry_in_msec_to_string(retry_in_msec, s_retry) \
+	({ \
+		gint64 _retry_in_msec = (retry_in_msec); \
+		\
+		  _retry_in_msec == RETRY_IN_MSEC_ASAP \
+		? "right away" \
+		: nm_sprintf_buf (s_retry, "in %"G_GINT64_FORMAT" msec", _retry_in_msec); \
+	})
+
+	if (   resolv_error
+	    && !g_error_matches (resolv_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) {
+		retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: failure to resolve endpoint \"%s\": %s (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       resolv_error->message,
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+
+		_peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+		return;
+	}
+
+	sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (!resolv_error) {
+		GList *iter;
+
+		for (iter = list; iter; iter = iter->next) {
+			GInetAddress *a = iter->data;
+			GSocketFamily f = g_inet_address_get_family (a);
+
+			if (f == G_SOCKET_FAMILY_IPV4) {
+				nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in_addr));
+				sockaddr.in = (struct sockaddr_in) {
+					.sin_family = AF_INET,
+					.sin_port   = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+				};
+				memcpy (&sockaddr.in.sin_addr, g_inet_address_to_bytes (a), sizeof (struct in_addr));
+				break;
+			}
+			if (f == G_SOCKET_FAMILY_IPV6) {
+				nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in6_addr));
+				sockaddr.in6 = (struct sockaddr_in6) {
+					.sin6_family   = AF_INET6,
+					.sin6_port     = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+					.sin6_scope_id = 0,
+					.sin6_flowinfo = 0,
+				};
+				memcpy (&sockaddr.in6.sin6_addr, g_inet_address_to_bytes (a), sizeof (struct in6_addr));
+				break;
+			}
+		}
+
+		g_list_free_full (list, g_object_unref);
+	}
+
+	if (sockaddr.sa.sa_family == AF_UNSPEC) {
+		/* we failed to resolve the name. There is no need to reset the previous
+		 * sockaddr. Either it was already AF_UNSPEC, or we had a good name
+		 * from resolving before. In that case, we don't want to throw away
+		 * a possibly good IP address, since WireGuard supports automatic roaming
+		 * anyway. Either the IP address is still good (and we would wrongly
+		 * reject it), or it isn't -- in which case it does not hurt much. */
+	} else {
+		if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+			changed = TRUE;
+		peer_data->ep_resolv.sockaddr = sockaddr;
+	}
+
+	if (   resolv_error
+	    || peer_data->ep_resolv.sockaddr.sa.sa_family == AF_UNSPEC) {
+		/* while it technically did not fail, something is probably odd. Retry frequently to
+		 * resolve the name, like we would do for normal failures. */
+		retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no %sresults for endpoint \"%s\" (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       resolv_error ? "" : "suitable ",
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+	} else {
+		retry_in_msec = _peers_retry_in_msec (peer_data, FALSE);
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: endpoint \"%s\" resolved to %s (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, s_sockaddr, sizeof (s_sockaddr)),
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+	}
+
+	_peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+
+	if (changed) {
+		NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+		/* schedule the job in the background, to give multiple resolve events time
+		 * to complete. */
+		nm_clear_g_source (&priv->link_config_delayed_id);
+		priv->link_config_delayed_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1,
+		                                                link_config_delayed_resolver_cb,
+		                                                self,
+		                                                NULL);
+	}
+}
+
+static void
+_peers_resolve_start (NMDeviceWireGuard *self,
+                      PeerData *peer_data)
+{
+	gs_unref_object GResolver *resolver = NULL;
+	const char *host;
+
+	resolver = g_resolver_get_default ();
+
+	nm_assert (!peer_data->ep_resolv.cancellable);
+
+	peer_data->ep_resolv.cancellable = g_cancellable_new ();
+
+	/* set a special next-try timestamp. It is positive, and indicates
+	 * that we are in the process of trying.
+	 * This timestamp however already lies in the past, but that is correct,
+	 * because we are currently in the process of trying. We will determine
+	 * a next-try timestamp once the try completes. */
+	peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_PAST;
+
+	host = nm_sock_addr_endpoint_get_host (_nm_wireguard_peer_get_endpoint (peer_data->peer));
+
+	g_resolver_lookup_by_name_async (resolver,
+	                                 host,
+	                                 peer_data->ep_resolv.cancellable,
+	                                 _peers_resolve_cb,
+	                                 peer_data);
+
+	_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: resolving name \"%s\" for endpoint \"%s\"...",
+	       nm_wireguard_peer_get_public_key (peer_data->peer),
+	       host,
+	       nm_wireguard_peer_get_endpoint (peer_data->peer));
+}
+
+static void
+_peers_resolve_reresolve_all (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->ep_resolv.cancellable) {
+			/* remember to retry when the currently ongoing request completes. */
+			peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_ASAP;
+		} else if (peer_data->ep_resolv.next_try_at_nsec <= 0) {
+			/* this peer does not require resolving the name. Skip it. */
+		} else {
+			/* we have a next-try scheduled. Restart right away. */
+			peer_data->ep_resolv.resolv_fail_count = 0;
+			_peers_resolve_start (self, peer_data);
+		}
+	}
+}
+
+static gboolean
+_peers_update (NMDeviceWireGuard *self,
+               PeerData *peer_data,
+               NMWireGuardPeer *peer,
+               gboolean force_update)
+{
+	nm_auto_unref_wgpeer NMWireGuardPeer *old_peer = NULL;
+	NMSockAddrEndpoint *old_endpoint;
+	NMSockAddrEndpoint *endpoint;
+	gboolean endpoint_changed = FALSE;
+	gboolean changed;
+	NMSockAddrUnion sockaddr;
+	gboolean sockaddr_fixed;
+	char sockaddr_sbuf[100];
+
+	nm_assert (peer);
+	nm_assert (nm_wireguard_peer_is_sealed (peer));
+
+	if (   peer == peer_data->peer
+	    && !force_update)
+		return FALSE;
+
+	changed = (nm_wireguard_peer_cmp (peer,
+	                                  peer_data->peer,
+	                                  NM_SETTING_COMPARE_FLAG_EXACT) != 0);
+
+	old_peer = peer_data->peer;
+	peer_data->peer = nm_wireguard_peer_ref (peer);
+
+	old_endpoint = old_peer ? _nm_wireguard_peer_get_endpoint (old_peer) : NULL;
+	endpoint     = peer     ? _nm_wireguard_peer_get_endpoint (peer)     : NULL;
+
+	endpoint_changed = (   endpoint != old_endpoint
+	                    && (   !old_endpoint
+	                        || !endpoint
+	                        || !nm_streq (nm_sock_addr_endpoint_get_endpoint (old_endpoint),
+	                                      nm_sock_addr_endpoint_get_endpoint (endpoint))));
+
+	if (   !force_update
+	    && !endpoint_changed) {
+		/* nothing to do. */
+		return changed;
+	}
+
+	sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+	sockaddr_fixed = TRUE;
+	if (   endpoint
+	    && nm_sock_addr_endpoint_get_host (endpoint)) {
+		if (!nm_sock_addr_endpoint_get_fixed_sockaddr (endpoint, &sockaddr)) {
+			/* we have an endpoint, but it's not a static IP address. We need to resolve
+			 * the names. */
+			sockaddr_fixed = FALSE;
+		}
+	}
+
+	if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+		changed = TRUE;
+
+	nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+
+	peer_data->ep_resolv = (PeerEndpointResolveData) {
+		.sockaddr          = sockaddr,
+		.resolv_fail_count = 0,
+		.cancellable       = NULL,
+		.next_try_at_nsec  = 0,
+	};
+
+	if (!endpoint) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no endpoint configured",
+		       nm_wireguard_peer_get_public_key (peer_data->peer));
+	} else if (!nm_sock_addr_endpoint_get_host (endpoint)) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: invalid endpoint \"%s\"",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_sock_addr_endpoint_get_endpoint (endpoint));
+	} else if (sockaddr_fixed) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: fixed endpoint \"%s\" (%s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_sock_addr_endpoint_get_endpoint (endpoint),
+		       nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, sockaddr_sbuf, sizeof (sockaddr_sbuf)));
+	} else
+		_peers_resolve_start (self, peer_data);
+
+	return changed;
+}
+
+static void
+_peers_remove_all (NMDeviceWireGuardPrivate *priv)
 {
-	return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-	                                  self->props.public_key, sizeof (self->props.public_key), 1);
+	PeerData *peer_data;
+
+	while ((peer_data = c_list_first_entry (&priv->lst_peers_head, PeerData, lst_peers)))
+		_peers_remove (priv, peer_data);
 }
 
 static void
+_peers_update_all (NMDeviceWireGuard *self,
+                   NMSettingWireGuard *s_wg,
+                   gboolean *out_peers_removed)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data_safe;
+	PeerData *peer_data;
+	guint i, n;
+	gboolean peers_removed = FALSE;
+
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers)
+		peer_data->dirty_update_all = TRUE;
+
+	n = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i = 0; i < n; i++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+		gboolean added = FALSE;
+
+		peer_data = _peers_find (priv, peer);
+		if (!peer_data) {
+			peer_data = _peers_add (self, peer);
+			added = TRUE;
+		}
+		_peers_update (self, peer_data, peer, added);
+		peer_data->dirty_update_all = FALSE;
+	}
+
+	c_list_for_each_entry_safe (peer_data, peer_data_safe, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->dirty_update_all) {
+			_peers_remove (priv, peer_data);
+			peers_removed = TRUE;
+		}
+	}
+
+	NM_SET_OUT (out_peers_removed, peers_removed);
+}
+
+static void
+_peers_get_platform_list (NMDeviceWireGuardPrivate *priv,
+                          LinkConfigMode config_mode,
+                          NMPWireGuardPeer **out_peers,
+                          NMPlatformWireGuardChangePeerFlags **out_peer_flags,
+                          guint *out_len,
+                          GArray **out_allowed_ips_data)
+{
+	gs_free NMPWireGuardPeer *plpeers = NULL;
+	gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+	gs_unref_array GArray *allowed_ips = NULL;
+	PeerData *peer_data;
+	guint i_good;
+	guint n_aip;
+	guint i_aip;
+	guint len;
+	guint i;
+
+	nm_assert (out_peers && !*out_peers);
+	nm_assert (out_peer_flags && !*out_peer_flags);
+	nm_assert (out_len && *out_len == 0);
+	nm_assert (out_allowed_ips_data && !*out_allowed_ips_data);
+
+	len = g_hash_table_size (priv->peers);
+
+	nm_assert (len == c_list_length (&priv->lst_peers_head));
+
+	if (len == 0)
+		return;
+
+	plpeers = g_new0 (NMPWireGuardPeer, len);
+	plpeer_flags = g_new0 (NMPlatformWireGuardChangePeerFlags, len);
+
+	i_good = 0;
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		NMPlatformWireGuardChangePeerFlags *plf = &plpeer_flags[i_good];
+		NMPWireGuardPeer *plp = &plpeers[i_good];
+		NMSettingSecretFlags psk_secret_flags;
+
+		if (!nm_utils_base64secret_decode (nm_wireguard_peer_get_public_key (peer_data->peer),
+		                                   sizeof (plp->public_key),
+		                                   plp->public_key))
+			continue;
+
+		*plf = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE;
+
+		plp->persistent_keepalive_interval = nm_wireguard_peer_get_persistent_keepalive (peer_data->peer);
+		if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                            LINK_CONFIG_MODE_REAPPLY))
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL;
+
+		/* if the peer has an endpoint but it is not yet resolved (not ready),
+		 * we still configure it and leave the endpoint unspecified. Later,
+		 * when we can resolve the endpoint, we will update. */
+		plp->endpoint = peer_data->ep_resolv.sockaddr;
+		if (plp->endpoint.sa.sa_family == AF_UNSPEC) {
+			/* we don't actually ever clear endpoints, if we don't have better information. */
+		} else
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT;
+
+		if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                            LINK_CONFIG_MODE_REAPPLY)) {
+			psk_secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer_data->peer);
+			if (!NM_FLAGS_HAS (psk_secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+				if (   !nm_utils_base64secret_decode (nm_wireguard_peer_get_preshared_key (peer_data->peer),
+				                                      sizeof (plp->preshared_key),
+				                                      plp->preshared_key)
+				    && config_mode == LINK_CONFIG_MODE_FULL)
+					goto skip;
+			}
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY;
+		}
+
+		if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                               LINK_CONFIG_MODE_REAPPLY)
+		    && ((n_aip = nm_wireguard_peer_get_allowed_ips_len (peer_data->peer)) > 0)) {
+			if (!allowed_ips)
+				allowed_ips = g_array_new (FALSE, FALSE, sizeof (NMPWireGuardAllowedIP));
+
+			*plf |=   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
+			        | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS;
+
+			plp->_construct_idx_start = allowed_ips->len;
+			for (i_aip = 0; i_aip < n_aip; i_aip++) {
+				const char *aip;
+				NMIPAddr addrbin = { };
+				int addr_family;
+				gboolean valid;
+				int prefix;
+
+				aip = nm_wireguard_peer_get_allowed_ip (peer_data->peer, i_aip, &valid);
+				if (   !valid
+				    || !nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+				                                          aip,
+				                                          &addr_family,
+				                                          &addrbin,
+				                                          &prefix)) {
+					/* the address is really not expected to be invalid, because then
+					 * the connection would not verify. Anyway, silently skip it. */
+					continue;
+				}
+
+				if (prefix == -1)
+					prefix = addr_family == AF_INET ? 32 : 128;
+
+				g_array_append_val (allowed_ips,
+				                    ((NMPWireGuardAllowedIP) {
+				                        .family = addr_family,
+				                        .mask = prefix,
+				                        .addr = addrbin,
+				                    }));
+			}
+			plp->_construct_idx_end = allowed_ips->len;
+		}
+
+		i_good++;
+		continue;
+
+skip:
+		memset (plp, 0, sizeof (*plp));
+	}
+
+	if (i_good == 0)
+		return;
+
+	for (i = 0; i < i_good; i++) {
+		NMPWireGuardPeer *plp = &plpeers[i];
+		guint l;
+
+		if (plp->_construct_idx_end == 0) {
+			nm_assert (plp->_construct_idx_start == 0);
+			plp->allowed_ips = NULL;
+			plp->allowed_ips_len = 0;
+		} else {
+			nm_assert (plp->_construct_idx_start < plp->_construct_idx_end);
+			l = plp->_construct_idx_end - plp->_construct_idx_start;
+			plp->allowed_ips = &g_array_index (allowed_ips, NMPWireGuardAllowedIP, plp->_construct_idx_start);
+			plp->allowed_ips_len = l;
+		}
+	}
+	*out_peers            = g_steal_pointer (&plpeers);
+	*out_peer_flags       = g_steal_pointer (&plpeer_flags);;
+	*out_len              = i_good;
+	*out_allowed_ips_data = g_steal_pointer (&allowed_ips);
+}
+
+/*****************************************************************************/
+
+static void
 update_properties (NMDevice *device)
 {
 	NMDeviceWireGuard *self;
+	NMDeviceWireGuardPrivate *priv;
 	const NMPlatformLink *plink;
 	const NMPlatformLnkWireGuard *props = NULL;
 	int ifindex;
 
 	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (device));
 	self = NM_DEVICE_WIREGUARD (device);
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
 	ifindex = nm_device_get_ifindex (device);
 	props = nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device), ifindex, &plink);
@@ -78,16 +867,16 @@ update_properties (NMDevice *device)
 
 #define CHECK_PROPERTY_CHANGED(field, prop) \
 	G_STMT_START { \
-		if (self->props.field != props->field) { \
-			self->props.field = props->field; \
+		if (priv->lnk_curr.field != props->field) { \
+			priv->lnk_curr.field = props->field; \
 			_notify (self, prop); \
 		} \
 	} G_STMT_END
 
 #define CHECK_PROPERTY_CHANGED_ARRAY(field, prop) \
 	G_STMT_START { \
-		if (memcmp (&self->props.field, &props->field, sizeof (props->field)) != 0) { \
-			memcpy (&self->props.field, &props->field, sizeof (props->field)); \
+		if (memcmp (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)) != 0) { \
+			memcpy (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)); \
 			_notify (self, prop); \
 		} \
 	} G_STMT_END
@@ -107,24 +896,655 @@ link_changed (NMDevice *device,
 	update_properties (device);
 }
 
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	const char *iface = nm_device_get_iface (device);
+	int r;
+
+	g_return_val_if_fail (iface, FALSE);
+
+	r = nm_platform_link_wireguard_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+		             "Failed to create WireGuard interface '%s' for '%s': %s",
+		             iface,
+		             nm_connection_get_id (connection),
+		             nm_strerror (r));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+_secrets_cancel (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	if (priv->secrets_call_id)
+		nm_act_request_cancel_secrets (NULL, priv->secrets_call_id);
+	nm_assert (!priv->secrets_call_id);
+}
+
+static void
+_secrets_cb (NMActRequest *req,
+             NMActRequestGetSecretsCallId *call_id,
+             NMSettingsConnection *connection,
+             GError *error,
+             gpointer user_data)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWireGuardPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->secrets_call_id == call_id);
+
+	priv->secrets_call_id = NULL;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
+
+	if (error) {
+		_LOGW (LOGD_ETHER, "%s", error->message);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	} else
+		nm_device_activate_schedule_stage1_device_prepare (device);
+}
+
+static void
+_secrets_get_secrets (NMDeviceWireGuard *self,
+                      const char *setting_name,
+                      NMSecretAgentGetSecretsFlags flags,
+                      const char *const*hints)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	NMActRequest *req;
+
+	_secrets_cancel (self);
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv->secrets_call_id = nm_act_request_get_secrets (req,
+	                                                    TRUE,
+	                                                    setting_name,
+	                                                    flags,
+	                                                    hints,
+	                                                    _secrets_cb,
+	                                                    self);
+	g_return_if_fail (priv->secrets_call_id);
+}
+
+static NMActStageReturn
+_secrets_handle_auth_or_fail (NMDeviceWireGuard *self,
+                              NMActRequest *req,
+                              gboolean new_secrets)
+{
+	NMConnection *applied_connection;
+	const char *setting_name;
+	gs_unref_ptrarray GPtrArray *hints = NULL;
+
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
+		return NM_ACT_STAGE_RETURN_FAILURE;
+
+	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+
+	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
+
+	applied_connection = nm_act_request_get_applied_connection (req);
+	setting_name = nm_connection_need_secrets (applied_connection, &hints);
+	if (!setting_name) {
+		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
+	_secrets_get_secrets (self,
+	                      setting_name,
+	                        NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+	                      | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0),
+	                      (  hints
+	                       ? (const char *const*) hints->pdata
+	                       : NULL));
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+_dns_config_changed (NMDnsManager *dns_manager, NMDeviceWireGuard *self)
+{
+	/* when the DNS configuration changes, we re-resolve the peer addresses.
+	 *
+	 * Possibly, we should also do that when the default-route changes, but it's
+	 * hard to figure out when that happens. */
+	_peers_resolve_reresolve_all (self);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+link_config (NMDeviceWireGuard *self,
+             const char *reason,
+             LinkConfigMode config_mode,
+             NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	nm_auto_bzero_secret_ptr NMSecretPtr wg_lnk_clear_private_key = NM_SECRET_PTR_INIT ();
+	NMSettingWireGuard *s_wg;
+	NMConnection *connection;
+	NMActStageReturn ret;
+	gs_unref_array GArray *allowed_ips_data = NULL;
+	NMPlatformLnkWireGuard wg_lnk;
+	gs_free NMPWireGuardPeer *plpeers = NULL;
+	gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+	guint plpeers_len = 0;
+	const char *setting_name;
+	gboolean peers_removed;
+	NMPlatformWireGuardChangeFlags wg_change_flags;
+	int ifindex;
+	int r;
+
+	NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+	g_return_val_if_fail (s_wg, NM_ACT_STAGE_RETURN_FAILURE);
+
+	priv->link_config_last_at = nm_utils_get_monotonic_timestamp_ns ();
+
+	_LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
+	       reason, _link_config_mode_to_string (config_mode));
+
+	if (!priv->dns_manager) {
+		priv->dns_manager = g_object_ref (nm_dns_manager_get ());
+		g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (_dns_config_changed), self);
+	}
+
+	if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+	    && (setting_name = nm_connection_need_secrets (connection, NULL))) {
+		NMActRequest *req = nm_device_get_act_request (NM_DEVICE (self));
+
+		_LOGD (LOGD_DEVICE,
+		       "Activation: connection '%s' has security, but secrets are required.",
+		       nm_connection_get_id (connection));
+
+		ret = _secrets_handle_auth_or_fail (self, req, FALSE);
+		if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+			if (ret != NM_ACT_STAGE_RETURN_POSTPONE) {
+				nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+			}
+			return ret;
+		}
+	}
+
+	ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+	if (ifindex <= 0) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	_peers_update_all (self, s_wg, &peers_removed);
+
+	wg_lnk = (NMPlatformLnkWireGuard) { };
 
-/******************************************************************/
+	wg_change_flags = NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE;
+
+	if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+	    || (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_REAPPLY)
+	        && peers_removed))
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS;
+
+	if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+	                            LINK_CONFIG_MODE_REAPPLY)) {
+
+		wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg),
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT;
+
+		wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg),
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
+
+		if (nm_utils_base64secret_decode (nm_setting_wireguard_get_private_key (s_wg),
+		                                  sizeof (wg_lnk.private_key),
+		                                  wg_lnk.private_key)) {
+			wg_lnk_clear_private_key = NM_SECRET_PTR_ARRAY (wg_lnk.private_key);
+			wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY;
+		} else {
+			if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)) {
+				_LOGD (LOGD_DEVICE, "the provided private-key is invalid");
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+				return NM_ACT_STAGE_RETURN_FAILURE;
+			}
+		}
+	}
+
+	_peers_get_platform_list (priv,
+	                          config_mode,
+	                          &plpeers,
+	                          &plpeer_flags,
+	                          &plpeers_len,
+	                          &allowed_ips_data);
+
+	r = nm_platform_link_wireguard_change (nm_device_get_platform (NM_DEVICE (self)),
+	                                       ifindex,
+	                                       &wg_lnk,
+	                                       plpeers,
+	                                       plpeer_flags,
+	                                       plpeers_len,
+	                                       wg_change_flags);
+
+	nm_explicit_bzero (plpeers, sizeof (plpeers) * plpeers_len);
+
+	if (r < 0) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+link_config_delayed (NMDeviceWireGuard *self,
+                     const char *reason)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	gint64 now;
+
+	priv->link_config_delayed_id = 0;
+
+	if (priv->link_config_last_at != 0) {
+		now = nm_utils_get_monotonic_timestamp_ns ();
+		if (now < priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC) {
+			/* we ratelimit calls to link_config(), because we call this whenever a resolver
+			 * completes. */
+			_LOGT (LOGD_DEVICE, "wireguard link config (%s) (postponed)", reason);
+			priv->link_config_delayed_id = g_timeout_add (NM_MAX ((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) / NM_UTILS_NS_PER_MSEC,
+			                                                      (gint64) 1),
+			                                              link_config_delayed_ratelimit_cb,
+			                                              self);
+			return;
+		}
+	}
+
+	link_config (self, reason, LINK_CONFIG_MODE_ENDPOINTS, NULL);
+}
+
+static gboolean
+link_config_delayed_ratelimit_cb (gpointer user_data)
+{
+	link_config_delayed (user_data, "after-ratelimiting");
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+link_config_delayed_resolver_cb (gpointer user_data)
+{
+	link_config_delayed (user_data, "resolver-update");
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device,
+                   NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceSysIfaceState sys_iface_state;
+	NMDeviceStateReason failure_reason;
+	NMActStageReturn ret;
+
+	sys_iface_state = nm_device_sys_iface_state_get (device);
+
+	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	ret = link_config (NM_DEVICE_WIREGUARD (device),
+	                                        "configure",
+	                                          (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
+	                                        ? LINK_CONFIG_MODE_ASSUME
+	                                        : LINK_CONFIG_MODE_FULL,
+	                                        &failure_reason);
+
+	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
+		/* this never fails. */
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (ret != NM_ACT_STAGE_RETURN_FAILURE) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return ret;
+	}
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         failure_reason);
+	NM_SET_OUT (out_failure_reason, failure_reason);
+	return NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+static NMIPConfig *
+_get_dev2_ip_config (NMDeviceWireGuard *self,
+                     int addr_family)
+{
+	gs_unref_object NMIPConfig *ip_config = NULL;
+	NMConnection *connection;
+	NMSettingWireGuard *s_wg;
+	guint n_peers;
+	guint i;
+	int ip_ifindex;
+	guint32 route_metric;
+	guint32 route_table_coerced;
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+
+	/* Differences to `wg-quick`.
+	 *
+	 * `wg-quick` supports the "Table" setting with 3 modes:
+	 *
+	 * a1) "off": this is what we do with "peer-routes" disabled.
+	 *
+	 * a2) an explicit routing table. This is our behavior with "peer-routes" on. In this case
+	 *   we honor the "ipv4.route-table" and "ipv6.route-table" settings. One difference is that
+	 *   `wg-quick` would resolve table names from /etc/iproute2/rt_tables. Our connection profiles
+	 *   only contain table numbers, so that conversion from name to table must have happened
+	 *   before already.
+	 *
+	 * a3) "auto" (the default). In this case, `wg-quick` would only add the route to the
+	 *   main table, if the AllowedIP range is not yet reachable on the link. With "peer-routes"
+	 *   enabled, we don't check for that and always add the routes to the main-table
+	 *   (with 'ipv4.route-table' and 'ipv6.route-table' set to zero or RT_TABLE_MAIN (254)).
+	 *
+	 *   Also, in "auto" mode, `wg-quick` would add special handling for /0 routes and pick
+	 *   an empty table to configure policy routing to avoid routing loops. This handling
+	 *   of routing-loops via policy routing is not yet done, and requires a separate solution
+	 *   from constructing the peer-routes here.
+	 */
+	if (!nm_setting_wireguard_get_peer_routes (s_wg))
+		return NULL;
+
+	ip_ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+
+	if (ip_ifindex <= 0)
+		return NULL;
+
+	route_metric = nm_device_get_route_metric (NM_DEVICE (self), addr_family);
+
+	route_table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (NM_DEVICE (self), addr_family, TRUE));
+
+	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i = 0; i < n_peers; i++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+		guint n_aips;
+		guint j;
+
+		n_aips = nm_wireguard_peer_get_allowed_ips_len (peer);
+		for (j = 0; j < n_aips; j++) {
+			NMPlatformIPXRoute rt;
+			NMIPAddr addrbin;
+			const char *aip;
+			gboolean valid;
+			int prefix;
+
+			aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
+
+			if (   !valid
+			    || !nm_utils_parse_inaddr_prefix_bin (addr_family,
+			                                          aip,
+			                                          NULL,
+			                                          &addrbin,
+			                                          &prefix))
+				continue;
+
+			if (prefix < 0)
+				prefix = (addr_family == AF_INET) ? 32 : 128;
+
+			if (!ip_config)
+				ip_config = nm_device_ip_config_new (NM_DEVICE (self), addr_family);
+
+			nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix);
+
+			if (addr_family == AF_INET) {
+				rt.r4 = (NMPlatformIP4Route) {
+					.network       = addrbin.addr4,
+					.plen          = prefix,
+					.ifindex       = ip_ifindex,
+					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
+					.table_coerced = route_table_coerced,
+					.metric        = route_metric,
+				};
+			} else {
+				rt.r6 = (NMPlatformIP6Route) {
+					.network       = addrbin.addr6,
+					.plen          = prefix,
+					.ifindex       = ip_ifindex,
+					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
+					.table_coerced = route_table_coerced,
+					.metric        = route_metric,
+				};
+			}
+
+			nm_ip_config_add_route (ip_config, &rt.rx, NULL);
+		}
+	}
+
+	return g_steal_pointer (&ip_config);
+}
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gs_unref_object NMIPConfig *ip_config = NULL;
+
+	ip_config = _get_dev2_ip_config (NM_DEVICE_WIREGUARD (device), addr_family);
+
+	nm_device_set_dev2_ip_config (device, addr_family, ip_config);
+
+	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	/* When "MTU" for `wg-quick up` is unset, it calls `ip route get` for
+	 * each configured endpoint, to determine the suitable MTU how to reach
+	 * each endpoint.
+	 * For `wg-quick` this works very well, because whenever the script runs it
+	 * determines the best setting at that point in time. It's simply not concerned
+	 * with what happens later (and it's not around anyway).
+	 *
+	 * NetworkManager sticks around, so the right MTU would need to be re-determined
+	 * whenever anything relevant changes. Which basically means, to re-evaluate whenever
+	 * something related to addresses or routing changes (which happens all the time).
+	 *
+	 * The correct MTU indeed depends on the MTU setting of other interfaces (or routes).
+	 * But it's still odd, that activating/deactivating a seemingly unrelated interface
+	 * would trigger an MTU change. It's odd to explain/document and odd to implemented
+	 * -- despite this being the reality.
+	 *
+	 * For now, only support configuring an explicit MTU, or leave the setting untouched.
+	 * The same limitation also applies to other "ip-tunnel" types, where we could use
+	 * similar smarts for autodetecting the MTU.
+	 */
+	return nm_device_get_configured_mtu_from_connection (device,
+	                                                     NM_TYPE_SETTING_WIREGUARD,
+	                                                     out_source);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWireGuardPrivate *priv;
+
+	if (new_state <= NM_DEVICE_STATE_ACTIVATED)
+		return;
+
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+
+	_peers_remove_all (priv);
+	_secrets_cancel (NM_DEVICE_WIREGUARD (device));
+}
+
+/*****************************************************************************/
+
+static gboolean
+can_reapply_change (NMDevice *device,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
+{
+	if (nm_streq (setting_name, NM_SETTING_WIREGUARD_SETTING_NAME)) {
+		/* Most, but not all WireGuard settings can be reapplied. Whitelist.
+		 *
+		 * MTU cannot be reapplied. */
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_WIREGUARD_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_WIREGUARD_FWMARK,
+		                                          NM_SETTING_WIREGUARD_LISTEN_PORT,
+		                                          NM_SETTING_WIREGUARD_PEERS,
+		                                          NM_SETTING_WIREGUARD_PEER_ROUTES,
+		                                          NM_SETTING_WIREGUARD_PRIVATE_KEY,
+		                                          NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS);
+	}
+
+	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->can_reapply_change (device,
+	                                                                               setting_name,
+	                                                                               s_old,
+	                                                                               s_new,
+	                                                                               diffs,
+	                                                                               error);
+}
+
+static void
+reapply_connection (NMDevice *device,
+                    NMConnection *con_old,
+                    NMConnection *con_new)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+	gs_unref_object NMIPConfig *ip4_config = NULL;
+	gs_unref_object NMIPConfig *ip6_config = NULL;
+
+	ip4_config = _get_dev2_ip_config (self, AF_INET);
+	ip6_config = _get_dev2_ip_config (self, AF_INET6);
+
+	nm_device_set_dev2_ip_config (device, AF_INET, ip4_config);
+	nm_device_set_dev2_ip_config (device, AF_INET6, ip6_config);
+
+	NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->reapply_connection (device,
+	                                                                        con_old,
+	                                                                        con_new);
+
+	link_config (NM_DEVICE_WIREGUARD (device),
+	             "reapply",
+	             LINK_CONFIG_MODE_REAPPLY,
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+	NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+	const NMPObject *obj_wg;
+	const NMPObjectLnkWireGuard *olnk_wg;
+	guint i;
+
+	if (!s_wg) {
+		s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wg));
+	}
+
+	g_object_set (s_wg,
+	              NM_SETTING_WIREGUARD_FWMARK,
+	              (guint) priv->lnk_curr.fwmark,
+	              NM_SETTING_WIREGUARD_LISTEN_PORT,
+	              (guint) priv->lnk_curr.listen_port,
+	              NULL);
+
+	obj_wg = NMP_OBJECT_UP_CAST (nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device),
+	                                                                 nm_device_get_ip_ifindex (device),
+	                                                                 NULL));
+	if (!obj_wg)
+		return;
+
+	olnk_wg = &obj_wg->_lnk_wireguard;
+
+	for (i = 0; i < olnk_wg->peers_len; i++) {
+		nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+		const NMPWireGuardPeer *ppeer = &olnk_wg->peers[i];
+
+		peer = nm_wireguard_peer_new ();
+
+		_nm_wireguard_peer_set_public_key_bin (peer, ppeer->public_key);
+
+		nm_setting_wireguard_append_peer (s_wg, peer);
+	}
+}
+
+/*****************************************************************************/
 
 static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
 	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_PUBLIC_KEY:
-		g_value_take_variant (value, get_public_key_as_variant (self));
+		g_value_take_variant (value,
+		                      g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                                 priv->lnk_curr.public_key,
+		                                                 sizeof (priv->lnk_curr.public_key),
+		                                                 1));
 		break;
 	case PROP_LISTEN_PORT:
-		g_value_set_uint (value, self->props.listen_port);
+		g_value_set_uint (value, priv->lnk_curr.listen_port);
 		break;
 	case PROP_FWMARK:
-		g_value_set_uint (value, self->props.fwmark);
+		g_value_set_uint (value, priv->lnk_curr.fwmark);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -132,9 +1552,44 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+/*****************************************************************************/
+
 static void
 nm_device_wireguard_init (NMDeviceWireGuard *self)
 {
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	c_list_init (&priv->lst_peers_head);
+	priv->peers = g_hash_table_new (_peer_data_hash, _peer_data_equal);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	_secrets_cancel (self);
+
+	_peers_remove_all (priv);
+
+	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	nm_explicit_bzero (priv->lnk_curr.private_key, sizeof (priv->lnk_curr.private_key));
+
+	if (priv->dns_manager) {
+		g_signal_handlers_disconnect_by_func (priv->dns_manager, _dns_config_changed, self);
+		g_object_unref (priv->dns_manager);
+	}
+
+	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->finalize (object);
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = {
@@ -156,13 +1611,26 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wireguard);
 
-	device_class->connection_type_supported = NULL;
+	device_class->connection_type_supported = NM_SETTING_WIREGUARD_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIREGUARD_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD);
 
+	device_class->state_changed = device_state_changed;
+	device_class->create_and_realize = create_and_realize;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->act_stage2_config_also_for_external_or_assume = TRUE;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->link_changed = link_changed;
+	device_class->update_connection = update_connection;
+	device_class->can_reapply_change = can_reapply_change;
+	device_class->reapply_connection = reapply_connection;
+	device_class->get_configured_mtu = get_configured_mtu;
 
 	obj_properties[PROP_PUBLIC_KEY] =
 	    g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,
@@ -207,6 +1675,7 @@ create_device (NMDeviceFactory *factory,
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (WIREGUARD, WireGuard, wireguard,
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD),
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WIREGUARD_SETTING_NAME),
 	factory_class->create_device = create_device;
 )
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
index c2fa7f4e..cdfd1f70 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/devices/nm-device-wpan.c
@@ -23,7 +23,6 @@
 #include "nm-device-wpan.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <linux/if.h>
 
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 9ded911c..7514fa78 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -24,9 +24,7 @@
 #include "nm-device.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <signal.h>
 #include <sys/types.h>
@@ -115,14 +113,6 @@ typedef enum {
 	CLEANUP_TYPE_DECONFIGURE,
 } CleanupType;
 
-typedef enum {
-	IP_NONE = 0,
-	IP_WAIT,
-	IP_CONF,
-	IP_DONE,
-	IP_FAIL
-} IpState;
-
 typedef struct {
 	CList lst_slave;
 	NMDevice *slave;
@@ -397,6 +387,8 @@ typedef struct _NMDevicePrivate {
 	bool            ndisc_started:1;
 	bool            device_link_changed_down:1;
 
+	bool            concheck_rp_filter_checked:1;
+
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
 
@@ -416,11 +408,8 @@ typedef struct _NMDevicePrivate {
 		NMIPConfig *ip_config_x[2];
 	};
 
-	union {
-		const IpState   ip4_state;
-		IpState         ip4_state_;
-	};
-	AppliedConfig   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
+	/* Config from DHCP, PPP, LLv4, etc */
+	AppliedConfig  dev_ip_config_4;
 
 	/* config from the setting */
 	union {
@@ -449,13 +438,14 @@ typedef struct _NMDevicePrivate {
 		GSList *vpn_configs_x[2];
 	};
 
-	/* WWAN configuration */
+	/* Extra device configuration, injected by the subclass of NMDevice.
+	 * This is used for example by NMDeviceModem for WWAN configuration. */
 	union {
 		struct {
-			AppliedConfig  wwan_ip_config_6;
-			AppliedConfig  wwan_ip_config_4;
+			AppliedConfig dev2_ip_config_6;
+			AppliedConfig dev2_ip_config_4;
 		};
-		AppliedConfig wwan_ip_config_x[2];
+		AppliedConfig dev2_ip_config_x[2];
 	};
 
 	/* DHCPv4 tracking */
@@ -500,9 +490,16 @@ typedef struct _NMDevicePrivate {
 	} acd;
 
 	union {
-		const IpState   ip6_state;
-		IpState         ip6_state_;
+		struct {
+			const NMDeviceIPState   ip_state_6;
+			const NMDeviceIPState   ip_state_4;
+		};
+		union {
+			const NMDeviceIPState   ip_state_x[2];
+			NMDeviceIPState         ip_state_x_[2];
+		};
 	};
+
 	AppliedConfig  ac_ip6_config;  /* config from IPv6 autoconfiguration */
 	NMIP6Config *  ext_ip6_config_captured; /* Configuration captured from platform. */
 	NMIP6Config *  dad6_ip6_config;
@@ -648,6 +645,22 @@ static void concheck_update_state (NMDevice *self,
                                    NMConnectivityState state,
                                    gboolean is_periodic);
 
+static void activate_stage4_ip_config_timeout_4 (NMDevice *self);
+static void activate_stage4_ip_config_timeout_6 (NMDevice *self);
+
+static void (*const activate_stage4_ip_config_timeout_x[2]) (NMDevice *self) = {
+	activate_stage4_ip_config_timeout_6,
+	activate_stage4_ip_config_timeout_4,
+};
+
+static void activate_stage5_ip_config_result_4 (NMDevice *self);
+static void activate_stage5_ip_config_result_6 (NMDevice *self);
+
+static void (*const activate_stage5_ip_config_result_x[2]) (NMDevice *self) = {
+	activate_stage5_ip_config_result_6,
+	activate_stage5_ip_config_result_4,
+};
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (queued_state_to_string, NMDeviceState,
@@ -742,6 +755,7 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_device_state_reason_to_str, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE,           "ip-address-duplicate"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED,          "ip-method-unsupported"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,     "sriov-configuration-failed"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PEER_NOT_FOUND,                 "peer-not-found"),
 );
 
 #define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A (nm_device_state_reason_to_str, reason)
@@ -870,28 +884,28 @@ concheck_get_mgr (NMDevice *self)
 	return priv->concheck_mgr;
 }
 
-static NMIP4Config *
-_ip4_config_new (NMDevice *self)
+NMIP4Config *
+nm_device_ip4_config_new (NMDevice *self)
 {
 	return nm_ip4_config_new (nm_device_get_multi_index (self),
 	                          nm_device_get_ip_ifindex (self));
 }
 
-static NMIP6Config *
-_ip6_config_new (NMDevice *self)
+NMIP6Config *
+nm_device_ip6_config_new (NMDevice *self)
 {
 	return nm_ip6_config_new (nm_device_get_multi_index (self),
 	                          nm_device_get_ip_ifindex (self));
 }
 
-static NMIPConfig *
-_ip_config_new (NMDevice *self, int addr_family)
+NMIPConfig *
+nm_device_ip_config_new (NMDevice *self, int addr_family)
 {
 	nm_assert_addr_family (addr_family);
 
 	return addr_family == AF_INET
-	       ? (gpointer) _ip4_config_new (self)
-	       : (gpointer) _ip6_config_new (self);
+	       ? (gpointer) nm_device_ip4_config_new (self)
+	       : (gpointer) nm_device_ip6_config_new (self);
 }
 
 static void
@@ -904,6 +918,12 @@ applied_config_clear (AppliedConfig *config)
 static void
 applied_config_init (AppliedConfig *config, gpointer ip_config)
 {
+	nm_assert (   !ip_config
+	           || (!config->orig && !config->current)
+	           || nm_ip_config_get_addr_family (ip_config) == nm_ip_config_get_addr_family (config->orig ?: config->current));
+	nm_assert (   !ip_config
+	           || NM_IS_IP_CONFIG (ip_config, AF_UNSPEC));
+
 	nm_g_object_ref (ip_config);
 	applied_config_clear (config);
 	config->orig = ip_config;
@@ -912,7 +932,7 @@ applied_config_init (AppliedConfig *config, gpointer ip_config)
 static void
 applied_config_init_new (AppliedConfig *config, NMDevice *self, int addr_family)
 {
-	gs_unref_object NMIPConfig *c = _ip_config_new (self, addr_family);
+	gs_unref_object NMIPConfig *c = nm_device_ip_config_new (self, addr_family);
 
 	applied_config_init (config, c);
 }
@@ -1274,7 +1294,7 @@ _get_stable_id (NMDevice *self,
 		uuid = nm_connection_get_uuid (connection);
 
 		/* the cloned-mac-address may be generated based on the stable-id.
-		 * Thus, at this point, we can only use the permanant MAC address
+		 * Thus, at this point, we can only use the permanent MAC address
 		 * as seed. */
 		hwaddr = nm_device_get_permanent_hw_address_full (self, TRUE, &hwaddr_is_fake);
 
@@ -1315,44 +1335,42 @@ _get_stable_id (NMDevice *self,
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, IpState,
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, NMDeviceIPState,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_NONE, "none"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_WAIT, "wait"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_CONF, "conf"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_DONE, "done"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_FAIL, "fail"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_NONE, "none"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_WAIT, "wait"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_CONF, "conf"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_DONE, "done"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_FAIL, "fail"),
 );
 
 static void
-_set_ip_state (NMDevice *self, int addr_family, IpState new_state)
+_set_ip_state (NMDevice *self, int addr_family, NMDeviceIPState new_state)
 {
-	IpState *p;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	nm_assert_addr_family (addr_family);
 
-	p =   (addr_family == AF_INET)
-	    ? &priv->ip4_state_
-	    : &priv->ip6_state_;
+	if (priv->ip_state_x[IS_IPv4] == new_state)
+		return;
 
-	if (*p != new_state) {
-		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
-		       nm_utils_addr_family_to_char (addr_family),
-		       (int) new_state,
-		       _ip_state_to_string (new_state));
-		*p = new_state;
+	_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
+	       nm_utils_addr_family_to_char (addr_family),
+	       (int) new_state,
+	       _ip_state_to_string (new_state));
 
-		if (new_state == IP_DONE) {
-			/* we only set the IPx_READY flag once we reach IP_DONE state. We don't
-			 * ever clear it, even if we later enter IP_FAIL state.
-			 *
-			 * This is not documented/guaranteed behavior, but seems to make sense for now. */
-			_active_connection_set_state_flags (self,
-			                                    addr_family == AF_INET
-			                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
-			                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
-		}
+	priv->ip_state_x_[IS_IPv4] = new_state;
+
+	if (new_state == NM_DEVICE_IP_STATE_DONE) {
+		/* we only set the IPx_READY flag once we reach NM_DEVICE_IP_STATE_DONE state. We don't
+		 * ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAIL state.
+		 *
+		 * This is not documented/guaranteed behavior, but seems to make sense for now. */
+		_active_connection_set_state_flags (self,
+		                                    addr_family == AF_INET
+		                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
+		                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
 	}
 }
 
@@ -2041,9 +2059,14 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 	 */
 
 	switch (device_type) {
-	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT) */
+
+	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT).
+	 *
+	 * Note that returning 50 from this function means that this device-type is
+	 * in some aspects a VPN. */
 	case NM_DEVICE_TYPE_WIREGUARD:
-		return 50;
+		return NM_VPN_ROUTE_METRIC_DEFAULT;
+
 	case NM_DEVICE_TYPE_ETHERNET:
 	case NM_DEVICE_TYPE_VETH:
 		return 100;
@@ -2091,6 +2114,7 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 		return 800;
 	case NM_DEVICE_TYPE_WPAN:
 		return 850;
+	case NM_DEVICE_TYPE_WIFI_P2P:
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -2146,9 +2170,7 @@ nm_device_get_route_metric (NMDevice *self,
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		s_ip = addr_family == AF_INET
-		       ? nm_connection_get_setting_ip4_config (connection)
-		       : nm_connection_get_setting_ip6_config (connection);
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 		/* Slave interfaces don't have IP settings, but we may get here when
 		 * external changes are made or when noticing IP changes when starting
@@ -2253,11 +2275,7 @@ nm_device_get_route_table (NMDevice *self,
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		if (addr_family == AF_INET)
-			s_ip = nm_connection_get_setting_ip4_config (connection);
-		else
-			s_ip = nm_connection_get_setting_ip6_config (connection);
-
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip)
 			route_table = nm_setting_ip_config_get_route_table (s_ip);
 
@@ -3041,6 +3059,7 @@ concheck_start (NMDevice *self,
 	static guint64 seq_counter = 0;
 	NMDevicePrivate *priv;
 	NMDeviceConnectivityHandle *handle;
+	const char *ifname;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
@@ -3063,6 +3082,39 @@ concheck_start (NMDevice *self,
 	       (long long unsigned) handle->seq,
 	       is_periodic ? ", periodic-check" : "");
 
+	if (   addr_family == AF_INET
+	    && !priv->concheck_rp_filter_checked) {
+
+		if ((ifname = nm_device_get_ip_iface_from_platform (self))) {
+			int val, val_all;
+
+			val = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+			                                                  AF_INET,
+			                                                  ifname,
+			                                                  "rp_filter",
+			                                                  10, 0, 2, 3);
+			if (val < 2) {
+				val_all = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+				                                                      AF_INET,
+				                                                      "all",
+				                                                      "rp_filter",
+				                                                      10, 0, 2, val);
+				if (val_all > val) {
+					val = val_all;
+					ifname = "all";
+				}
+			}
+
+			if (val == 1) {
+				_LOGW (LOGD_CONCHECK, "connectivity: \"/proc/sys/net/ipv4/conf/%s/rp_filter\" is set to \"1\". "
+				       "This might break connectivity checking for IPv4 on this device", ifname);
+			}
+		}
+
+		/* we only check once per device. It's a warning after all.  */
+		priv->concheck_rp_filter_checked = TRUE;
+	}
+
 	handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self),
 	                                                handle->addr_family,
 	                                                nm_device_get_ip_ifindex (self),
@@ -3160,6 +3212,7 @@ find_slave_info (NMDevice *self, NMDevice *slave)
 static gboolean
 nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connection)
 {
+	NMDevicePrivate *priv;
 	SlaveInfo *info;
 	gboolean success = FALSE;
 	gboolean configure;
@@ -3168,6 +3221,7 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	g_return_val_if_fail (slave != NULL, FALSE);
 	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL, FALSE);
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	info = find_slave_info (self, slave);
 	if (!info)
 		return FALSE;
@@ -3190,15 +3244,20 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	 */
 	nm_device_update_hw_address (self);
 
+	/* Send ARP announcements if did not yet and have addresses. */
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
+	    && !priv->acd.announcing)
+		nm_device_arp_announce (self);
+
 	/* Restart IP configuration if we're waiting for slaves.  Do this
 	 * after updating the hardware address as IP config may need the
 	 * new address.
 	 */
 	if (success) {
-		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT)
 			nm_device_activate_stage3_ip4_start (self);
 
-		if (NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT)
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT)
 			nm_device_activate_stage3_ip6_start (self);
 	}
 
@@ -3821,11 +3880,13 @@ device_link_changed (NMDevice *self)
 	if (priv->up && (!was_up || seen_down)) {
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
-		if (priv->ip4_state == IP_DONE) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
-		if (priv->ip6_state == IP_DONE) {
+
+		priv->linklocal6_dad_counter = 0;
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
 		}
@@ -4323,8 +4384,7 @@ realize_start_setup (NMDevice *self,
 	 * NetworkManager might down the interface or remove the 127.0.0.1 address. */
 	nm_device_set_unmanaged_flags (self,
 	                               NM_UNMANAGED_BY_TYPE,
-	                                  is_loopback (self)
-	                               || NM_IS_DEVICE_WIREGUARD (self));
+	                               is_loopback (self));
 
 	nm_device_set_unmanaged_by_user_udev (self);
 	nm_device_set_unmanaged_by_user_conf (self);
@@ -4848,21 +4908,11 @@ static gboolean
 get_ip_config_may_fail (NMDevice *self, int addr_family)
 {
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
+	NMSettingIPConfig *s_ip;
 
 	connection = nm_device_get_applied_connection (self);
 
-	/* Fail the connection if the failed IP method is required to complete */
-	switch (addr_family) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		break;
-	default:
-		nm_assert_not_reached ();
-	}
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
 }
@@ -4906,22 +4956,22 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
 		ip6_ignore = TRUE;
 
-	if (   priv->ip4_state == IP_DONE
-	    && priv->ip6_state == IP_DONE) {
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
+	    && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 		/* Both method completed (or disabled), proceed with activation */
 		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
 	}
 
-	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
-	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
+	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
+	    && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL || (ip6_ignore && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) {
 		/* Either both methods failed, or only one failed and the other is
 		 * disabled */
 		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 			/* We have assumed configuration, but couldn't redo it. No problem,
 			 * move to check state. */
-			_set_ip_state (self, AF_INET, IP_DONE);
-			_set_ip_state (self, AF_INET6, IP_DONE);
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 			state = NM_DEVICE_STATE_IP_CHECK;
 		} else if (   may_fail
 		           && get_ip_config_may_fail (self, AF_INET)
@@ -4944,14 +4994,14 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	}
 
 	/* If a method is still pending but required, wait */
-	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
+	if (priv->ip_state_4 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail (self, AF_INET))
 		return;
-	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
+	if (priv->ip_state_6 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail (self, AF_INET6))
 		return;
 
 	/* If at least a method has completed, proceed with activation */
-	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
-	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
+	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled)
+	    || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_ignore)) {
 		if (full_state_update)
 			nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
@@ -5642,7 +5692,6 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	NMDeviceClass *klass;
 	const char *const *patterns;
 	NMSettingMatch *s_match;
-	guint num_patterns;
 
 	klass = NM_DEVICE_GET_CLASS (self);
 	if (klass->connection_type_check_compatible) {
@@ -5683,6 +5732,8 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	s_match = (NMSettingMatch *) nm_connection_get_setting (connection,
 	                                                        NM_TYPE_SETTING_MATCH);
 	if (s_match) {
+		guint num_patterns = 0;
+
 		patterns = nm_setting_match_get_interface_names (s_match, &num_patterns);
 		if (!nm_wildcard_match_check (device_iface, patterns, num_patterns)) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -6267,8 +6318,8 @@ activate_stage1_device_prepare (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 
-	_set_ip_state (self, AF_INET, IP_NONE);
-	_set_ip_state (self, AF_INET6, IP_NONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_NONE);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_NONE);
 
 	/* Notify the new ActiveConnection along with the state change */
 	nm_dbus_track_obj_path_set (&priv->act_request,
@@ -6445,23 +6496,25 @@ static void
 activate_stage2_device_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass;
 	NMActStageReturn ret;
 	gboolean no_firmware = FALSE;
 	CList *iter;
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
-	/* Assumed connections were already set up outside NetworkManager */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
+	if (!nm_device_sys_iface_state_is_external_or_assume (self))
 		_ethtool_state_set (self);
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!tc_commit (self)) {
 			_LOGW (LOGD_IP6, "failed applying traffic control rules");
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			return;
 		}
+	}
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
 			if (no_firmware)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
@@ -6469,15 +6522,21 @@ activate_stage2_device_config (NMDevice *self)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 			return;
 		}
+	}
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (   klass->act_stage2_config_also_for_external_or_assume
+	    || !nm_device_sys_iface_state_is_external_or_assume (self)) {
+		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-		ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &failure_reason);
+		ret = klass->act_stage2_config (self, &failure_reason);
 		if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 			return;
-		else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+			nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 			return;
 		}
-		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 	}
 
 	/* If we have slaves that aren't yet enslaved, do that now */
@@ -6494,6 +6553,7 @@ activate_stage2_device_config (NMDevice *self)
 	}
 
 	lldp_init (self, TRUE);
+
 	nm_device_activate_schedule_stage3_ip_config_start (self);
 }
 
@@ -6555,7 +6615,7 @@ nm_device_ip_method_failed (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	_set_ip_state (self, addr_family, IP_FAIL);
+	_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_FAIL);
 
 	if (get_ip_config_may_fail (self, addr_family))
 		check_ip_state (self, FALSE, (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG));
@@ -6606,7 +6666,6 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 {
 	NMConnection *connection;
 	const char *method;
-	NMIP4Config *empty;
 
 	connection = nm_device_get_applied_connection (self);
 	nm_assert (connection);
@@ -6621,12 +6680,10 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 		return;
 	}
 
-	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-		empty = _ip4_config_new (self);
-		nm_device_activate_schedule_ip4_config_result (self, empty);
-		g_object_unref (empty);
-	} else {
-		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state != IP_DONE)
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NULL);
+	else {
+		if (NM_DEVICE_GET_PRIVATE (self)->ip_state_4 != NM_DEVICE_IP_STATE_DONE)
 			ip_config_merge_and_apply (self, AF_INET, TRUE);
 	}
 }
@@ -6781,7 +6838,7 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	NMPlatformIP4Address address;
 	NMPlatformIP4Route route;
 
-	config = _ip4_config_new (self);
+	config = nm_device_ip4_config_new (self);
 	g_assert (config);
 
 	memset (&address, 0, sizeof (address));
@@ -6841,11 +6898,11 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 			return;
 		}
 
-		if (priv->ip4_state == IP_CONF) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
 			nm_clear_g_source (&priv->ipv4ll_timeout);
-			nm_device_activate_schedule_ip4_config_result (self, config);
-		} else if (priv->ip4_state == IP_DONE) {
-			applied_config_init (&priv->dev_ip4_config, config);
+			nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (config));
+		} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
+			applied_config_init (&priv->dev_ip_config_4, config);
 			if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
 				nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
@@ -6872,8 +6929,8 @@ ipv4ll_timeout_cb (gpointer user_data)
 		priv->ipv4ll_timeout = 0;
 		ipv4ll_cleanup (self);
 
-		if (priv->ip4_state == IP_CONF)
-			nm_device_activate_schedule_ip4_config_timeout (self);
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 	}
 
 	return FALSE;
@@ -6956,7 +7013,7 @@ ensure_con_ip_config (NMDevice *self, int addr_family)
 	if (!connection)
 		return;
 
-	con_ip_config = _ip_config_new (self, addr_family);
+	con_ip_config = nm_device_ip_config_new (self, addr_family);
 
 	if (IS_IPv4) {
 		nm_ip4_config_merge_setting (NM_IP4_CONFIG (con_ip_config),
@@ -7038,10 +7095,9 @@ ip_config_merge_and_apply (NMDevice *self,
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	if (connection) {
-		NMSettingIPConfig *s_ip =   IS_IPv4
-		                          ? nm_connection_get_setting_ip4_config (connection)
-		                          : nm_connection_get_setting_ip6_config (connection);
+		NMSettingIPConfig *s_ip;
 
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip) {
 			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip);
 			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip);
@@ -7060,7 +7116,7 @@ ip_config_merge_and_apply (NMDevice *self,
 		}
 	}
 
-	composite = _ip_config_new (self, addr_family);
+	composite = nm_device_ip_config_new (self, addr_family);
 
 	if (!IS_IPv4) {
 		nm_ip6_config_set_privacy (NM_IP6_CONFIG (composite),
@@ -7112,7 +7168,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	/* Merge all the IP configs into the composite config */
 
 	if (IS_IPv4) {
-		config = applied_config_get_current (&priv->dev_ip4_config);
+		config = applied_config_get_current (&priv->dev_ip_config_4);
 		if (config) {
 			nm_ip4_config_merge (NM_IP4_CONFIG (composite), NM_IP4_CONFIG (config),
 			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
@@ -7153,7 +7209,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	config = applied_config_get_current (&priv->wwan_ip_config_x[IS_IPv4]);
+	config = applied_config_get_current (&priv->dev2_ip_config_x[IS_IPv4]);
 	if (config) {
 		nm_ip_config_merge (composite, config,
 		                      (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
@@ -7234,7 +7290,7 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 
 	g_return_val_if_fail (config, FALSE);
 
-	applied_config_init (&priv->dev_ip4_config, config);
+	applied_config_init (&priv->dev_ip_config_4, config);
 
 	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
@@ -7273,13 +7329,13 @@ dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s, was_active %d)",
-	       _ip_state_to_string (priv->ip4_state),
+	       _ip_state_to_string (priv->ip_state_4),
 	       priv->dhcp4.was_active);
 
 	/* Keep client running if there are static addresses configured
 	 * on the interface.
 	 */
-	if (   priv->ip4_state == IP_DONE
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 	    && priv->con_ip_config_4
 	    && nm_ip4_config_get_num_addresses (priv->con_ip_config_4) > 0)
 		goto clear_config;
@@ -7291,9 +7347,9 @@ dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 	 *    not active before.
 	 */
 	if (   dhcp_state == NM_DHCP_STATE_TERMINATED
-	    || (!priv->dhcp4.was_active && priv->ip4_state == IP_CONF)) {
+	    || (!priv->dhcp4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
 		dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-		nm_device_activate_schedule_ip4_config_timeout (self);
+		nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 		return;
 	}
 
@@ -7325,7 +7381,7 @@ static void
 dhcp4_dad_cb (NMDevice *self, NMIP4Config **configs, gboolean success)
 {
 	if (success)
-		nm_device_activate_schedule_ip4_config_result (self, configs[1]);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (configs[1]));
 	else {
 		nm_device_ip_method_failed (self, AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
@@ -7363,8 +7419,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 		/* After some failures, we have been able to renew the lease:
 		 * update the ip state
 		 */
-		if (priv->ip4_state == IP_FAIL)
-			_set_ip_state (self, AF_INET, IP_CONF);
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_CONF);
 
 		g_free (priv->dhcp4.pac_url);
 		priv->dhcp4.pac_url = g_strdup (g_hash_table_lookup (options, "wpad"));
@@ -7376,11 +7432,11 @@ dhcp4_state_changed (NMDhcpClient *client,
 		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
 
-		if (priv->ip4_state == IP_CONF) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
 			connection = nm_device_get_applied_connection (self);
 			g_assert (connection);
 
-			manual = _ip4_config_new (self);
+			manual = nm_device_ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
 			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
@@ -7393,7 +7449,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 			configs[1] = g_object_ref (ip4_config);
 
 			ipv4_dad_start (self, configs, dhcp4_dad_cb);
-		} else if (priv->ip4_state == IP_DONE) {
+		} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			if (dhcp4_lease_change (self, ip4_config))
 				nm_device_update_metered (self);
 			else
@@ -7405,7 +7461,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-		if (priv->ip4_state == IP_CONF)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
 			break;
 		/* fall through */
 	case NM_DHCP_STATE_DONE:
@@ -7431,10 +7487,7 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 
 	connection = nm_device_get_applied_connection (self);
 
-	if (addr_family == AF_INET)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 	timeout = nm_setting_ip_config_get_dhcp_timeout (s_ip);
 	if (timeout)
@@ -7721,7 +7774,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 		is_generated = TRUE;
 	}
 
-	config = _ip4_config_new (self);
+	config = nm_device_ip4_config_new (self);
 	nm_ip4_config_add_address (config, &address);
 	if (is_generated) {
 		/* Remove the address lock when the object gets disposed */
@@ -7735,25 +7788,21 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 /*****************************************************************************/
 
 static gboolean
-connection_ip4_method_requires_carrier (NMConnection *connection,
-                                        gboolean *out_ip4_enabled)
+connection_ip_method_requires_carrier (NMConnection *connection,
+                                       int addr_family,
+                                       gboolean *out_ip_enabled)
 {
 	const char *method;
 
-	method = nm_utils_get_ip_config_method (connection, AF_INET);
-	NM_SET_OUT (out_ip4_enabled, !nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED));
-	return NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-	                             NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
-}
+	method = nm_utils_get_ip_config_method (connection, addr_family);
 
-static gboolean
-connection_ip6_method_requires_carrier (NMConnection *connection,
-                                        gboolean *out_ip6_enabled)
-{
-	const char *method;
+	if (addr_family == AF_INET) {
+		NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED));
+		return NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		                             NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
+	}
 
-	method = nm_utils_get_ip_config_method (connection, AF_INET6);
-	NM_SET_OUT (out_ip6_enabled, !nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE));
+	NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE));
 	return NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 	                             NM_SETTING_IP6_CONFIG_METHOD_DHCP,
 	                             NM_SETTING_IP6_CONFIG_METHOD_SHARED,
@@ -7774,7 +7823,7 @@ connection_requires_carrier (NMConnection *connection)
 	if (nm_setting_connection_get_master (s_con))
 		return FALSE;
 
-	ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used);
+	ip4_carrier_wanted = connection_ip_method_requires_carrier (connection, AF_INET, &ip4_used);
 	if (ip4_carrier_wanted) {
 		/* If IPv4 wants a carrier and cannot fail, the whole connection
 		 * requires a carrier regardless of the IPv6 method.
@@ -7784,7 +7833,7 @@ connection_requires_carrier (NMConnection *connection)
 			return TRUE;
 	}
 
-	ip6_carrier_wanted = connection_ip6_method_requires_carrier (connection, &ip6_used);
+	ip6_carrier_wanted = connection_ip_method_requires_carrier (connection, AF_INET6, &ip6_used);
 	if (ip6_carrier_wanted) {
 		/* If IPv6 wants a carrier and cannot fail, the whole connection
 		 * requires a carrier regardless of the IPv4 method.
@@ -7825,108 +7874,6 @@ have_any_ready_slaves (NMDevice *self)
 	return FALSE;
 }
 
-static gboolean
-ip4_requires_slaves (NMDevice *self)
-{
-	const char *method;
-
-	method = nm_device_get_effective_ip_config_method (self, AF_INET);
-	return nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-}
-
-static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *self,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
-	const char *method;
-
-	connection = nm_device_get_applied_connection (self);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	if (   connection_ip4_method_requires_carrier (connection, NULL)
-	    && nm_device_is_master (self)
-	    && !priv->carrier) {
-		_LOGI (LOGD_IP4 | LOGD_DEVICE,
-		       "IPv4 config waiting until carrier is on");
-		return NM_ACT_STAGE_RETURN_IP_WAIT;
-	}
-
-	if (nm_device_is_master (self) && ip4_requires_slaves (self)) {
-		/* If the master has no ready slaves, and depends on slaves for
-		 * a successful IPv4 attempt, then postpone IPv4 addressing.
-		 */
-		if (!have_any_ready_slaves (self)) {
-			_LOGI (LOGD_DEVICE | LOGD_IP4,
-			       "IPv4 config waiting until slaves are ready");
-			return NM_ACT_STAGE_RETURN_IP_WAIT;
-		}
-	}
-
-	method = nm_device_get_effective_ip_config_method (self, AF_INET);
-	_LOGD (LOGD_IP4 | LOGD_DEVICE, "IPv4 config method is %s", method);
-
-	if (NM_IN_STRSET (method,
-	                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-	                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-		NMSettingIPConfig *s_ip4;
-		NMIP4Config **configs, *config;
-		guint num_addresses;
-
-		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
-		num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
-
-		if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-			ret = dhcp4_start (self);
-			if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
-				return ret;
-			}
-		} else {
-			g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		}
-
-		if (num_addresses) {
-			config = _ip4_config_new (self);
-			nm_ip4_config_merge_setting (config,
-			                             nm_connection_get_setting_ip4_config (connection),
-			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
-			                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-			                             nm_device_get_route_table (self, AF_INET, TRUE),
-			                             nm_device_get_route_metric (self, AF_INET));
-			configs = g_new0 (NMIP4Config *, 2);
-			configs[0] = config;
-			ipv4_dad_start (self, configs, ipv4_manual_method_apply);
-		}
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
-		ret = ipv4ll_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
-		if (out_config) {
-			*out_config = shared4_new_config (self, connection);
-			if (*out_config) {
-				priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
-				ret = NM_ACT_STAGE_RETURN_SUCCESS;
-			} else {
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-				ret = NM_ACT_STAGE_RETURN_FAILURE;
-			}
-		} else
-			g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	else
-		_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
-
-	return ret;
-}
-
 /*****************************************************************************/
 /* DHCPv6 stuff */
 
@@ -8015,7 +7962,7 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 	gboolean is_dhcp_managed;
 
 	_LOGD (LOGD_DHCP6, "DHCPv6 failed (ip_state %s, was_active %d)",
-	       _ip_state_to_string (priv->ip6_state),
+	       _ip_state_to_string (priv->ip_state_6),
 	       priv->dhcp6.was_active);
 
 	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
@@ -8024,7 +7971,7 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 		/* Keep client running if there are static addresses configured
 		 * on the interface.
 		 */
-		if (   priv->ip6_state == IP_DONE
+		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE
 		    && priv->con_ip_config_6
 		    && nm_ip6_config_get_num_addresses (priv->con_ip_config_6))
 			goto clear_config;
@@ -8036,9 +7983,9 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 		 *    not active before.
 		 */
 		if (   dhcp_state == NM_DHCP_STATE_TERMINATED
-		    || (!priv->dhcp6.was_active && priv->ip6_state == IP_CONF)) {
+		    || (!priv->dhcp6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
 			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 			return;
 		}
 
@@ -8058,8 +8005,8 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 	} else {
 		/* not a hard failure; just live with the RA info */
 		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-		if (priv->ip6_state == IP_CONF)
-			nm_device_activate_schedule_ip6_config_result (self);
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	}
 	return;
 
@@ -8118,16 +8065,16 @@ dhcp6_state_changed (NMDhcpClient *client,
 		/* After long time we have been able to renew the lease:
 		 * update the ip state
 		 */
-		if (priv->ip6_state == IP_FAIL)
-			_set_ip_state (self, AF_INET6, IP_CONF);
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
 
-		if (priv->ip6_state == IP_CONF) {
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 			if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
-			nm_device_activate_schedule_ip6_config_result (self);
-		} else if (priv->ip6_state == IP_DONE)
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
+		} else if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
 			if (!dhcp6_lease_change (self))
 				dhcp6_fail (self, state);
 		break;
@@ -8137,13 +8084,13 @@ dhcp6_state_changed (NMDhcpClient *client,
 		else {
 			/* not a hard failure; just live with the RA info */
 			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-			if (priv->ip6_state == IP_CONF)
-				nm_device_activate_schedule_ip6_config_result (self);
+			if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
+				nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		}
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-		if (priv->ip6_state != IP_CONF)
+		if (priv->ip_state_6 != NM_DEVICE_IP_STATE_CONF)
 			dhcp6_fail (self, state);
 		break;
 	case NM_DHCP_STATE_TERMINATED:
@@ -8350,7 +8297,7 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 		/* preferably, we would salt the checksum differently for each @duid type. We missed
 		 * to do that initially, so most types use the DEFAULT_SALT.
 		 *
-		 * Implemenations that are added later, should use a distinct salt instead,
+		 * Implementations that are added later, should use a distinct salt instead,
 		 * like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */
 		const guint32 DEFAULT_SALT = 670531087u;
 		nm_auto_free_checksum GChecksum *sum = NULL;
@@ -8719,7 +8666,7 @@ linklocal6_failed (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	nm_clear_g_source (&priv->linklocal6_timeout_id);
-	nm_device_activate_schedule_ip6_config_timeout (self);
+	nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 }
 
 static gboolean
@@ -8767,10 +8714,10 @@ linklocal6_check_complete (NMDevice *self)
 	else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
 		if (!dhcp6_start_with_link_ready (self, connection)) {
 			/* Time out IPv6 instead of failing the entire activation */
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 		}
 	} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
-		nm_device_activate_schedule_ip6_config_result (self);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	else
 		g_return_if_fail (FALSE);
 }
@@ -8927,6 +8874,10 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 		if (setting)
 			mtu = nm_setting_ip_tunnel_get_mtu (NM_SETTING_IP_TUNNEL (setting));
 		global_property_name = NM_CON_DEFAULT ("ip-tunnel.mtu");
+	} else if (setting_type == NM_TYPE_SETTING_WIREGUARD) {
+		if (setting)
+			mtu = nm_setting_wireguard_get_mtu (NM_SETTING_WIREGUARD (setting));
+		global_property_name = NM_CON_DEFAULT ("wireguard.mtu");
 	} else
 		g_return_val_if_reached (0);
 
@@ -9282,7 +9233,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		}
 	}
 
-	nm_device_activate_schedule_ip6_config_result (self);
+	nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 }
 
 static void
@@ -9296,7 +9247,7 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 	 */
 
 	_LOGD (LOGD_IP6, "timed out waiting for IPv6 router advertisement");
-	if (priv->ip6_state == IP_CONF) {
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 		/* If RA is our only source of addressing information and we don't
 		 * ever receive one, then time out IPv6.  But if there is other
 		 * IPv6 configuration, like manual IPv6 addresses or external IPv6
@@ -9309,9 +9260,9 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 		    && nm_ip6_config_find_first_address (priv->ip_config_6,
 		                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
 		                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
-			nm_device_activate_schedule_ip6_config_result (self);
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		else
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 	}
 }
 
@@ -9348,7 +9299,7 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	case NM_NDISC_NODE_TYPE_ROUTER:
 		/* We're the router. */
 		nm_device_sysctl_ip_conf_set (self, AF_INET6, "forwarding", "1");
-		nm_device_activate_schedule_ip6_config_result (self);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		priv->needs_ip6_subnet = TRUE;
 		g_signal_emit (self, signals[IP6_SUBNET_NEEDED], 0);
 		break;
@@ -9515,7 +9466,7 @@ restore_ip6_properties (NMDevice *self)
 	}
 }
 
-static inline void
+static void
 set_disable_ipv6 (NMDevice *self, const char *value)
 {
 	/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
@@ -9523,7 +9474,7 @@ set_disable_ipv6 (NMDevice *self, const char *value)
 		nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", value);
 }
 
-static inline void
+static void
 set_nm_ipv6ll (NMDevice *self, gboolean enable)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -9541,7 +9492,7 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 		_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
 		r = nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), ifindex, enable);
 		if (r < 0) {
-			_NMLOG (  NM_IN_SET (r, -NME_PL_NOT_FOUND
+			_NMLOG (  NM_IN_SET (r, -NME_PL_NOT_FOUND,
 			                        -NME_PL_OPNOTSUPP)
 			        ? LOGL_DEBUG
 			        : LOGL_WARN,
@@ -9632,11 +9583,14 @@ _ip6_privacy_get (NMDevice *self)
 /*****************************************************************************/
 
 static gboolean
-ip6_requires_slaves (NMDevice *self)
+ip_requires_slaves (NMDevice *self, int addr_family)
 {
 	const char *method;
 
-	method = nm_device_get_effective_ip_config_method (self, AF_INET6);
+	method = nm_device_get_effective_ip_config_method (self, addr_family);
+
+	if (addr_family == AF_INET)
+		return nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
 	/* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves)
 	 * to complete addressing.  SLAAC and DHCP need a peer to provide a prefix.
@@ -9646,128 +9600,202 @@ ip6_requires_slaves (NMDevice *self)
 }
 
 static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *self,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *self,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMConnection *connection;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	const char *method;
-	NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
-	const char *ip6_privacy_str = "0";
+
+	nm_assert_addr_family (addr_family);
 
 	connection = nm_device_get_applied_connection (self);
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	if (   connection_ip6_method_requires_carrier (connection, NULL)
+	if (   connection_ip_method_requires_carrier (connection, addr_family, NULL)
 	    && nm_device_is_master (self)
 	    && !priv->carrier) {
-		_LOGI (LOGD_IP6 | LOGD_DEVICE,
-		       "IPv6 config waiting until carrier is on");
+		_LOGI (LOGD_IP | LOGD_DEVICE,
+		       "IPv%c config waiting until carrier is on",
+		       nm_utils_addr_family_to_char (addr_family));
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (nm_device_is_master (self) && ip6_requires_slaves (self)) {
+	if (   nm_device_is_master (self)
+	    && ip_requires_slaves (self, addr_family)) {
 		/* If the master has no ready slaves, and depends on slaves for
-		 * a successful IPv6 attempt, then postpone IPv6 addressing.
+		 * a successful IP configuration attempt, then postpone IP addressing.
 		 */
 		if (!have_any_ready_slaves (self)) {
-			_LOGI (LOGD_DEVICE | LOGD_IP6,
-			       "IPv6 config waiting until slaves are ready");
+			_LOGI (LOGD_DEVICE | LOGD_IP,
+			       "IPv%c config waiting until slaves are ready",
+			       nm_utils_addr_family_to_char (addr_family));
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
 		}
 	}
 
-	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	method = nm_device_get_effective_ip_config_method (self, AF_INET6);
-	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
-		if (   !priv->master
-		    && !nm_device_sys_iface_state_is_external (self)) {
-			gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
+	if (!IS_IPv4)
+		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
 
-			/* When activating an IPv6 'ignore' connection we need to revert back
-			 * to kernel IPv6LL, but the kernel won't actually assign an address
-			 * to the interface until disable_ipv6 is bounced.
-			 */
-			set_nm_ipv6ll (self, FALSE);
-			if (ipv6ll_handle_old)
-				nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
-			restore_ip6_properties (self);
+	method = nm_device_get_effective_ip_config_method (self, addr_family);
+
+	_LOGD (LOGD_IP | LOGD_DEVICE, "IPv%c config method is %s",
+	       nm_utils_addr_family_to_char (addr_family), method);
+
+	if (IS_IPv4) {
+		if (NM_IN_STRSET (method,
+		                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+			NMSettingIPConfig *s_ip4;
+			NMIP4Config **configs, *config;
+			guint num_addresses;
+
+			s_ip4 = nm_connection_get_setting_ip4_config (connection);
+			g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
+			num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
+
+			if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+				ret = dhcp4_start (self);
+				if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
+					return ret;
+				}
+			} else {
+				g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+			}
+
+			if (num_addresses) {
+				config = nm_device_ip4_config_new (self);
+				nm_ip4_config_merge_setting (config,
+				                             nm_connection_get_setting_ip4_config (connection),
+				                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
+				                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
+				                             nm_device_get_route_table (self, AF_INET, TRUE),
+				                             nm_device_get_route_metric (self, AF_INET));
+				configs = g_new0 (NMIP4Config *, 2);
+				configs[0] = config;
+				ipv4_dad_start (self, configs, ipv4_manual_method_apply);
+			}
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+			ret = ipv4ll_start (self);
+			if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+			if (out_config) {
+				*out_config = shared4_new_config (self, connection);
+				if (*out_config) {
+					priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
+					ret = NM_ACT_STAGE_RETURN_SUCCESS;
+				} else {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+					ret = NM_ACT_STAGE_RETURN_FAILURE;
+				}
+			} else
+				g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+		else
+			_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
+
+		return ret;
+	} else {
+		NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+		const char *ip6_privacy_str = "0";
+
+		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+			if (   !priv->master
+			    && !nm_device_sys_iface_state_is_external (self)) {
+				gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
+
+				/* When activating an IPv6 'ignore' connection we need to revert back
+				 * to kernel IPv6LL, but the kernel won't actually assign an address
+				 * to the interface until disable_ipv6 is bounced.
+				 */
+				set_nm_ipv6ll (self, FALSE);
+				if (ipv6ll_handle_old)
+					nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
+				restore_ip6_properties (self);
+			}
+			return NM_ACT_STAGE_RETURN_IP_DONE;
 		}
-		return NM_ACT_STAGE_RETURN_IP_DONE;
-	}
 
-	/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
-	 * expose any ipv6 sysctls or allow presence of any addresses on the interface,
-	 * including LL, which * would make it impossible to autoconfigure MTU to a
-	 * correct value. */
-	_commit_mtu (self, priv->ip_config_4);
+		/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
+		 * expose any ipv6 sysctls or allow presence of any addresses on the interface,
+		 * including LL, which * would make it impossible to autoconfigure MTU to a
+		 * correct value. */
+		_commit_mtu (self, priv->ip_config_4);
 
-	/* Any method past this point requires an IPv6LL address. Use NM-controlled
-	 * IPv6LL if this is not an assumed connection, since assumed connections
-	 * will already have IPv6 set up.
-	 */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self))
-		set_nm_ipv6ll (self, TRUE);
+		/* Any method past this point requires an IPv6LL address. Use NM-controlled
+		 * IPv6LL if this is not an assumed connection, since assumed connections
+		 * will already have IPv6 set up.
+		 */
+		if (!nm_device_sys_iface_state_is_external_or_assume (self))
+			set_nm_ipv6ll (self, TRUE);
 
-	/* Re-enable IPv6 on the interface */
-	set_disable_ipv6 (self, "0");
+		/* Re-enable IPv6 on the interface */
+		set_disable_ipv6 (self, "0");
 
-	/* Synchronize external IPv6 configuration with kernel, since
-	 * linklocal6_start() uses the information there to determine if we can
-	 * proceed with the selected method (SLAAC, DHCP, link-local).
-	 */
-	nm_platform_process_events (nm_device_get_platform (self));
-	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
-	                                                       nm_device_get_platform (self),
-	                                                       nm_device_get_ip_ifindex (self),
-	                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+		/* Synchronize external IPv6 configuration with kernel, since
+		 * linklocal6_start() uses the information there to determine if we can
+		 * proceed with the selected method (SLAAC, DHCP, link-local).
+		 */
+		nm_platform_process_events (nm_device_get_platform (self));
+		g_clear_object (&priv->ext_ip6_config_captured);
+		priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
+		                                                       nm_device_get_platform (self),
+		                                                       nm_device_get_ip_ifindex (self),
+		                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
-	ip6_privacy = _ip6_privacy_get (self);
+		ip6_privacy = _ip6_privacy_get (self);
 
-	if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-	                          NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
-		if (!addrconf6_start (self, ip6_privacy)) {
-			/* IPv6 might be disabled; allow IPv4 to proceed */
-			ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-		} else
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
-		ret = linklocal6_start (self)
-		      ? NM_ACT_STAGE_RETURN_SUCCESS
-		      : NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
-		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
-		if (!dhcp6_start (self, TRUE)) {
-			/* IPv6 might be disabled; allow IPv4 to proceed */
-			ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-		} else
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	else
-		_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
-
-	if (   ret != NM_ACT_STAGE_RETURN_FAILURE
-	    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
-		switch (ip6_privacy) {
-		case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
-		case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
-			ip6_privacy_str = "0";
-			break;
-		case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
-			ip6_privacy_str = "1";
-			break;
-		case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
-			ip6_privacy_str = "2";
-			break;
+		if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                          NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
+			if (!addrconf6_start (self, ip6_privacy)) {
+				/* IPv6 might be disabled; allow IPv4 to proceed */
+				ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+			} else
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+			ret = linklocal6_start (self)
+			      ? NM_ACT_STAGE_RETURN_SUCCESS
+			      : NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+			priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
+			if (!dhcp6_start (self, TRUE)) {
+				/* IPv6 might be disabled; allow IPv4 to proceed */
+				ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+			} else
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+		else
+			_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
+
+		if (   ret != NM_ACT_STAGE_RETURN_FAILURE
+		    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
+			switch (ip6_privacy) {
+			case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
+			case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
+				ip6_privacy_str = "0";
+				break;
+			case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
+				ip6_privacy_str = "1";
+				break;
+			case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
+				ip6_privacy_str = "2";
+				break;
+			}
+			nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", ip6_privacy_str);
 		}
-		nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", ip6_privacy_str);
-	}
 
-	return ret;
+		return ret;
+	}
 }
 
 /**
@@ -9782,35 +9810,32 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP4Config *ip4_config = NULL;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
 
-	g_assert (priv->ip4_state == IP_WAIT);
+	g_assert (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT);
 
 	if (nm_device_sys_iface_state_is_external (self)) {
-		_set_ip_state (self, AF_INET, IP_DONE);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
-	_set_ip_state (self, AF_INET, IP_CONF);
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &failure_reason);
-	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
-		if (!ip4_config)
-			ip4_config = _ip4_config_new (self);
-		nm_device_activate_schedule_ip4_config_result (self, ip4_config);
-		g_object_unref (ip4_config);
-	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-		_set_ip_state (self, AF_INET, IP_DONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_CONF);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip_config_start (self, AF_INET, (gpointer *) &ip4_config, &failure_reason);
+	if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (ip4_config));
+	else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_FAIL) {
 		/* Activation not wanted */
-		_set_ip_state (self, AF_INET, IP_FAIL);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
 		/* Wait for something to try IP config again */
-		_set_ip_state (self, AF_INET, IP_WAIT);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
@@ -9831,37 +9856,37 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 	NMIP6Config *ip6_config = NULL;
 
-	g_assert (priv->ip6_state == IP_WAIT);
+	g_assert (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT);
 
 	if (nm_device_sys_iface_state_is_external (self)) {
-		_set_ip_state (self, AF_INET6, IP_DONE);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
-	_set_ip_state (self, AF_INET6, IP_CONF);
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &failure_reason);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip_config_start (self, AF_INET6, (gpointer *) &ip6_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip6_config)
-			ip6_config = _ip6_config_new (self);
+			ip6_config = nm_device_ip6_config_new (self);
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
 		nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
 		applied_config_init (&priv->ac_ip6_config, ip6_config);
-		nm_device_activate_schedule_ip6_config_result (self);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-		_set_ip_state (self, AF_INET6, IP_DONE);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_FAIL) {
 		/* Activation not wanted */
-		_set_ip_state (self, AF_INET6, IP_FAIL);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
 		/* Wait for something to try IP config again */
-		_set_ip_state (self, AF_INET6, IP_WAIT);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
@@ -9879,8 +9904,8 @@ activate_stage3_ip_config_start (NMDevice *self)
 {
 	int ifindex;
 
-	_set_ip_state (self, AF_INET, IP_WAIT);
-	_set_ip_state (self, AF_INET6, IP_WAIT);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 
 	_active_connection_set_state_flags (self,
 	                                    NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
@@ -9935,7 +9960,7 @@ fw_change_zone_cb (NMFirewallManager *firewall_manager,
 		break;
 	case FIREWALL_STATE_WAIT_IP_CONFIG:
 		priv->fw_state = FIREWALL_STATE_INITIALIZED;
-		if (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
 			nm_device_start_ip_check (self);
 		break;
 	case FIREWALL_STATE_INITIALIZED:
@@ -10012,15 +10037,21 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 }
 
 static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *self,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	if (!get_ip_config_may_fail (self, AF_INET)) {
+	nm_assert_addr_family (addr_family);
+
+	if (!get_ip_config_may_fail (self, addr_family)) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
+
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+
 /*
  * nm_device_activate_stage4_ip4_config_timeout
  *
@@ -10028,12 +10059,12 @@ act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_
  *
  */
 static void
-activate_stage4_ip4_config_timeout (NMDevice *self)
+activate_stage4_ip_config_timeout_4 (NMDevice *self)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &failure_reason);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip_config_timeout (self, AF_INET6, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 		return;
 	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
@@ -10042,54 +10073,35 @@ activate_stage4_ip4_config_timeout (NMDevice *self)
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-	_set_ip_state (self, AF_INET, IP_FAIL);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
 
 	check_ip_state (self, FALSE, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_ip4_config_timeout
- *
- * Deal with a timeout of the IPv4 configuration
- *
- */
 void
-nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
+nm_device_activate_schedule_ip_config_timeout (NMDevice *self,
+                                               int addr_family)
 {
 	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request.obj);
 
-	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_reason)
-{
-	if (!get_ip_config_may_fail (self, AF_INET6)) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-		return NM_ACT_STAGE_RETURN_FAILURE;
-	}
+	g_return_if_fail (priv->act_request.obj);
 
-	return NM_ACT_STAGE_RETURN_SUCCESS;
+	activation_source_schedule (self, activate_stage4_ip_config_timeout_x[IS_IPv4], addr_family);
 }
 
-/*
- * activate_stage4_ip6_config_timeout
- *
- * Time out on retrieving the IPv6 config.
- *
- */
 static void
-activate_stage4_ip6_config_timeout (NMDevice *self)
+activate_stage4_ip_config_timeout_6 (NMDevice *self)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &failure_reason);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip_config_timeout (self, AF_INET6, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 		return;
 	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
@@ -10098,30 +10110,11 @@ activate_stage4_ip6_config_timeout (NMDevice *self)
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-	_set_ip_state (self, AF_INET6, IP_FAIL);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
 
 	check_ip_state (self, FALSE, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_ip6_config_timeout
- *
- * Deal with a timeout of the IPv6 configuration
- *
- */
-void
-nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
-{
-	NMDevicePrivate *priv;
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request.obj);
-
-	activation_source_schedule (self, activate_stage4_ip6_config_timeout, AF_INET6);
-}
-
 static gboolean
 share_init (NMDevice *self, GError **error)
 {
@@ -10136,9 +10129,9 @@ share_init (NMDevice *self, GError **error)
 	} else if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv4/ip_forward"), "1")) {
 		errsv = errno;
 		_LOGD (LOGD_SHARING, "share: error enabling IPv4 forwarding: (%d) %s",
-		       errsv, g_strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "cannot set ipv4/ip_forward: %s", g_strerror (errsv));
+		             "cannot set ipv4/ip_forward: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -10147,7 +10140,7 @@ share_init (NMDevice *self, GError **error)
 	} else if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv4/ip_dynaddr"), "1")) {
 		errsv = errno;
 		_LOGD (LOGD_SHARING, "share: error enabling dynamic addresses: (%d) %s",
-		       errsv, strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 	}
 
 	for (iter = modules; *iter; iter++)
@@ -10321,12 +10314,13 @@ nm_device_arp_announce (NMDevice *self)
 }
 
 static void
-activate_stage5_ip4_config_result (NMDevice *self)
+activate_stage5_ip_config_result_4 (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	const char *method;
 	int ip_ifindex;
+	gboolean do_announce = FALSE;
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
@@ -10369,45 +10363,77 @@ activate_stage5_ip4_config_result (NMDevice *self)
 		                           NULL, NULL, NULL);
 	}
 
-	nm_device_arp_announce (self);
+	/* Send ARP announcements */
+
+	if (nm_device_is_master (self)) {
+		CList *iter;
+		SlaveInfo *info;
+
+		/* Skip announcement if there are no device enslaved, for two reasons:
+		 * 1) the master has a temporary MAC address until the first slave comes
+		 * 2) announcements are going to be dropped anyway without slaves
+		 */
+		do_announce = FALSE;
+
+		c_list_for_each (iter, &priv->slaves) {
+			info = c_list_entry (iter, SlaveInfo, lst_slave);
+			if (info->slave_is_enslaved) {
+				do_announce = TRUE;
+				break;
+			}
+		}
+	} else
+		do_announce = TRUE;
+
+	if (do_announce)
+		nm_device_arp_announce (self);
+
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP4, FALSE);
 
 	/* Enter the IP_CHECK state if this is the first method to complete */
-	_set_ip_state (self, AF_INET, IP_DONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 	check_ip_state (self, FALSE, TRUE);
 }
 
 void
-nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *config)
+nm_device_activate_schedule_ip_config_result (NMDevice *self,
+                                              int addr_family,
+                                              NMIPConfig *config)
 {
 	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_return_if_fail (   !config
+	                  || (   addr_family == AF_INET
+	                      && nm_ip_config_get_addr_family (config) == AF_INET));
+
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	applied_config_init (&priv->dev_ip4_config, config);
-	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
-}
+	if (IS_IPv4) {
+		applied_config_init (&priv->dev_ip_config_4, config);
+	} else {
+		/* If IP had previously failed, move it back to NM_DEVICE_IP_STATE_CONF since we
+		 * clearly now have configuration.
+		 */
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
+	}
 
-gboolean
-nm_device_activate_ip4_state_in_conf (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_CONF;
+	activation_source_schedule (self, activate_stage5_ip_config_result_x[IS_IPv4], addr_family);
 }
 
-gboolean
-nm_device_activate_ip4_state_in_wait (NMDevice *self)
+NMDeviceIPState
+nm_device_activate_get_ip_state (NMDevice *self,
+                                 int addr_family)
 {
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT;
-}
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-gboolean
-nm_device_activate_ip4_state_done (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_DONE;
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_IP_STATE_NONE);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NM_DEVICE_IP_STATE_NONE);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ip_state_x[IS_IPv4];
 }
 
 static void
@@ -10430,7 +10456,7 @@ dad6_add_pending_address (NMDevice *self,
 		       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
 
 		if (!*dad6_config)
-			*dad6_config = _ip6_config_new (self);
+			*dad6_config = nm_device_ip6_config_new (self);
 
 		nm_ip6_config_add_address (*dad6_config, pl_addr);
 	}
@@ -10447,7 +10473,7 @@ dad6_get_pending_addresses (NMDevice *self)
 	NMIP6Config *confs[] = { (NMIP6Config *) applied_config_get_current (&priv->ac_ip6_config),
 	                         (NMIP6Config *) applied_config_get_current (&priv->dhcp6.ip6_config),
 	                         priv->con_ip_config_6,
-	                         (NMIP6Config *) applied_config_get_current (&priv->wwan_ip_config_6) };
+	                         (NMIP6Config *) applied_config_get_current (&priv->dev2_ip_config_6) };
 	const NMPlatformIP6Address *addr;
 	NMIP6Config *dad6_config = NULL;
 	NMDedupMultiIter ipconf_iter;
@@ -10488,7 +10514,7 @@ dad6_get_pending_addresses (NMDevice *self)
 }
 
 static void
-activate_stage5_ip6_config_commit (NMDevice *self)
+activate_stage5_ip_config_result_6 (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
@@ -10511,7 +10537,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 
 	if (ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		if (   priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
-		    && priv->ip6_state == IP_CONF) {
+		    && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 			if (applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				/* If IPv6 wasn't the first IP to complete, and DHCP was used,
 				 * then ensure dispatcher scripts get the DHCP lease information.
@@ -10533,13 +10559,13 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
 			if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/all/forwarding"), "1")) {
 				errsv = errno;
-				_LOGE (LOGD_SHARING, "share: error enabling IPv6 forwarding: (%d) %s", errsv, strerror (errsv));
+				_LOGE (LOGD_SHARING, "share: error enabling IPv6 forwarding: (%d) %s", errsv, nm_strerror_native (errsv));
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 			}
 		}
 
 		/* Check if we have to wait for DAD */
-		if (priv->ip6_state == IP_CONF && !priv->dad6_ip6_config) {
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && !priv->dad6_ip6_config) {
 			if (!priv->carrier && priv->ignore_carrier && get_ip_config_may_fail (self, AF_INET6))
 				_LOGI (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: carrier missing and ignored, not delaying activation");
 			else
@@ -10548,7 +10574,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 			if (priv->dad6_ip6_config) {
 				_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
 			} else {
-				_set_ip_state (self, AF_INET6, IP_DONE);
+				_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 				check_ip_state (self, FALSE, TRUE);
 			}
 		}
@@ -10558,43 +10584,6 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 	}
 }
 
-void
-nm_device_activate_schedule_ip6_config_result (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	/* If IP had previously failed, move it back to IP_CONF since we
-	 * clearly now have configuration.
-	 */
-	if (priv->ip6_state == IP_FAIL)
-		_set_ip_state (self, AF_INET6, IP_CONF);
-
-	activation_source_schedule (self, activate_stage5_ip6_config_commit, AF_INET6);
-}
-
-gboolean
-nm_device_activate_ip6_state_in_conf (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_CONF;
-}
-
-gboolean
-nm_device_activate_ip6_state_in_wait (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT;
-}
-
-gboolean
-nm_device_activate_ip6_state_done (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_DONE;
-}
-
 /*****************************************************************************/
 
 static void
@@ -10760,7 +10749,7 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	_set_ip_state (self, addr_family, IP_NONE);
+	_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_NONE);
 
 	if (nm_clear_g_source (&priv->queued_ip_config_id_x[IS_IPv4])) {
 		_LOGD (LOGD_DEVICE, "clearing queued IP%c config change",
@@ -10862,12 +10851,12 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->ip4_state != IP_NONE) {
+	if (priv->ip_state_4 != NM_DEVICE_IP_STATE_NONE) {
 		g_clear_object (&priv->con_ip_config_4);
 		g_clear_object (&priv->ext_ip_config_4);
-		g_clear_object (&priv->dev_ip4_config.current);
-		g_clear_object (&priv->wwan_ip_config_4.current);
-		priv->con_ip_config_4 = _ip4_config_new (self);
+		g_clear_object (&priv->dev_ip_config_4.current);
+		g_clear_object (&priv->dev2_ip_config_4.current);
+		priv->con_ip_config_4 = nm_device_ip4_config_new (self);
 		nm_ip4_config_merge_setting (priv->con_ip_config_4,
 		                             s_ip4_new,
 		                             _get_mdns (self),
@@ -10884,7 +10873,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 
 		if (!nm_streq0 (method_old, method_new)) {
 			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
-			_set_ip_state (self, AF_INET, IP_WAIT);
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 			return;
@@ -10903,12 +10892,12 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			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,
+				if (priv->dev_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip_config_4.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,
+				if (priv->dev2_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev2_ip_config_4.orig,
 					                                    nm_device_get_route_metric (self, AF_INET));
 				}
 				if (priv->dhcp4.client) {
@@ -10934,16 +10923,16 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->ip6_state != IP_NONE) {
+	if (priv->ip_state_6 != NM_DEVICE_IP_STATE_NONE) {
 		g_clear_object (&priv->con_ip_config_6);
 		g_clear_object (&priv->ext_ip_config_6);
 		g_clear_object (&priv->ac_ip6_config.current);
 		g_clear_object (&priv->dhcp6.ip6_config.current);
-		g_clear_object (&priv->wwan_ip_config_6.current);
+		g_clear_object (&priv->dev2_ip_config_6.current);
 		if (   priv->ipv6ll_handle
 		    && !IN6_IS_ADDR_UNSPECIFIED (&priv->ipv6ll_addr))
 			priv->ipv6ll_has = TRUE;
-		priv->con_ip_config_6 = _ip6_config_new (self);
+		priv->con_ip_config_6 = nm_device_ip6_config_new (self);
 		nm_ip6_config_merge_setting (priv->con_ip_config_6,
 		                             s_ip6_new,
 		                             nm_device_get_route_table (self, AF_INET6, TRUE),
@@ -10958,7 +10947,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 
 		if (!nm_streq0 (method_old, method_new)) {
 			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
-			_set_ip_state (self, AF_INET6, IP_WAIT);
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
 			return;
@@ -10980,8 +10969,8 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 					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,
+				if (priv->dev2_ip_config_6.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dev2_ip_config_6.orig,
 					                                    nm_device_get_route_metric (self, AF_INET6));
 				}
 				if (priv->dhcp6.client) {
@@ -11091,7 +11080,6 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 static void
 reapply_connection (NMDevice *self, NMConnection *con_old, NMConnection *con_new)
 {
-
 }
 
 /* check_and_reapply_connection:
@@ -11490,7 +11478,7 @@ _rt6_temporary_not_available_timeout (gpointer user_data)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->rt6_temporary_not_available_id = 0;
-	nm_device_activate_schedule_ip6_config_result (self);
+	nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 
 	return G_SOURCE_REMOVE;
 }
@@ -12074,7 +12062,7 @@ nm_device_set_ip_config (NMDevice *self,
 		       nm_dbus_object_get_path (NM_DBUS_OBJECT (old_config)));
 		if (IS_IPv4) {
 			/* Device config is invalid if combined config is invalid */
-			applied_config_clear (&priv->dev_ip4_config);
+			applied_config_clear (&priv->dev_ip_config_4);
 		} else
 			priv->needs_ip6_subnet = FALSE;
 	}
@@ -12175,13 +12163,25 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 }
 
 void
-nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
+nm_device_set_dev2_ip_config (NMDevice *self,
+                              int addr_family,
+                              NMIPConfig *config)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	applied_config_init (&priv->wwan_ip_config_4, config);
-	if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
-		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_return_if_fail (   !config
+	                  || nm_ip_config_get_addr_family (config) == addr_family);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	applied_config_init (&priv->dev2_ip_config_x[IS_IPv4], config);
+	if (!ip_config_merge_and_apply (self, addr_family, TRUE)) {
+		_LOGW (LOGD_IP, "failed to set extra device IPv%c configuration",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 }
 
 void
@@ -12202,16 +12202,6 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
 }
 
-void
-nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	applied_config_init (&priv->wwan_ip_config_6, config);
-	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
-		_LOGW (LOGD_IP6, "failed to set WWAN IPv6 configuration");
-}
-
 NMDhcp6Config *
 nm_device_get_dhcp6_config (NMDevice *self)
 {
@@ -12446,7 +12436,7 @@ nm_device_start_ip_check (NMDevice *self)
 	g_return_if_fail (!priv->gw_ping.watch);
 	g_return_if_fail (!priv->gw_ping.timeout);
 	g_return_if_fail (!priv->gw_ping.pid);
-	g_return_if_fail (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE);
+	g_return_if_fail (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE);
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -12459,14 +12449,14 @@ nm_device_start_ip_check (NMDevice *self)
 	if (timeout) {
 		const NMPObject *gw;
 
-		if (priv->ip_config_4 && priv->ip4_state == IP_DONE) {
+		if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip4_config_best_default_route_get (priv->ip_config_4);
 			if (gw) {
 				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
 				log_domain = LOGD_IP4;
 			}
-		} else if (priv->ip_config_6 && priv->ip6_state == IP_DONE) {
+		} else if (priv->ip_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip6_config_best_default_route_get (priv->ip_config_6);
 			if (gw) {
 				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
@@ -12606,11 +12596,11 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 	_update_ip4_address (self);
 
 	/* when the link comes up, we must restore IP configuration if necessary. */
-	if (priv->ip4_state == IP_DONE) {
+	if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
-	if (priv->ip6_state == IP_DONE) {
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 			_LOGW (LOGD_IP6, "failed applying IP6 config after bringing link up");
 	}
@@ -12679,6 +12669,7 @@ nm_device_get_firmware_missing (NMDevice *self)
 static void
 intersect_ext_config (NMDevice *self,
                       AppliedConfig *config,
+                      gboolean intersect_addresses,
                       gboolean intersect_routes)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -12695,11 +12686,16 @@ intersect_ext_config (NMDevice *self,
 	      ? (NMIPConfig *) priv->ext_ip_config_4
 	      : (NMIPConfig *) priv->ext_ip_config_6;
 
-	if (config->current)
-		nm_ip_config_intersect (config->current, ext, intersect_routes, penalty);
-	else {
+	if (config->current) {
+		nm_ip_config_intersect (config->current,
+		                        ext,
+		                        intersect_addresses,
+		                        intersect_routes,
+		                        penalty);
+	} else {
 		config->current = nm_ip_config_intersect_alloc (config->orig,
 		                                                ext,
+		                                                intersect_addresses,
 		                                                intersect_routes,
 		                                                penalty);
 	}
@@ -12735,15 +12731,16 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				 * by the user. */
 				if (priv->con_ip_config_4) {
 					nm_ip4_config_intersect (priv->con_ip_config_4, priv->ext_ip_config_4,
+					                         TRUE,
 					                         is_up,
 					                         default_route_metric_penalty_get (self, AF_INET));
 				}
 
-				intersect_ext_config (self, &priv->dev_ip4_config, is_up);
-				intersect_ext_config (self, &priv->wwan_ip_config_4, is_up);
+				intersect_ext_config (self, &priv->dev_ip_config_4, TRUE, is_up);
+				intersect_ext_config (self, &priv->dev2_ip_config_4, TRUE, is_up);
 
 				for (iter = priv->vpn_configs_4; iter; iter = iter->next)
-					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, is_up, 0);
+					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, TRUE, is_up, 0);
 			}
 
 			/* Remove parts from ext_ip_config_4 to only contain the information that
@@ -12753,14 +12750,14 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				nm_ip4_config_subtract (priv->ext_ip_config_4, priv->con_ip_config_4,
 				                        default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (applied_config_get_current (&priv->dev_ip4_config)) {
+			if (applied_config_get_current (&priv->dev_ip_config_4)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
-				                       applied_config_get_current (&priv->dev_ip4_config),
+				                       applied_config_get_current (&priv->dev_ip_config_4),
 				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (applied_config_get_current (&priv->wwan_ip_config_4)) {
+			if (applied_config_get_current (&priv->dev2_ip_config_4)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
-				                       applied_config_get_current (&priv->wwan_ip_config_4),
+				                       applied_config_get_current (&priv->dev2_ip_config_4),
 				                       default_route_metric_penalty_get (self, AF_INET));
 			}
 			for (iter = priv->vpn_configs_4; iter; iter = iter->next)
@@ -12788,15 +12785,16 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				if (priv->con_ip_config_6) {
 					nm_ip6_config_intersect (priv->con_ip_config_6, priv->ext_ip_config_6,
 					                         is_up,
+					                         is_up,
 					                         default_route_metric_penalty_get (self, AF_INET6));
 				}
 
-				intersect_ext_config (self, &priv->ac_ip6_config, is_up);
-				intersect_ext_config (self, &priv->dhcp6.ip6_config, is_up);
-				intersect_ext_config (self, &priv->wwan_ip_config_6, is_up);
+				intersect_ext_config (self, &priv->ac_ip6_config, is_up, is_up);
+				intersect_ext_config (self, &priv->dhcp6.ip6_config, is_up, is_up);
+				intersect_ext_config (self, &priv->dev2_ip_config_6, is_up, is_up);
 
 				for (iter = priv->vpn_configs_6; iter; iter = iter->next)
-					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, 0);
+					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, is_up, 0);
 
 				if (   priv->ipv6ll_has
 				    && !nm_ip6_config_lookup_address (priv->ext_ip_config_6, &priv->ipv6ll_addr))
@@ -12820,9 +12818,9 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				                       applied_config_get_current (&priv->dhcp6.ip6_config),
 				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (applied_config_get_current (&priv->wwan_ip_config_6)) {
+			if (applied_config_get_current (&priv->dev2_ip_config_6)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
-				                       applied_config_get_current (&priv->wwan_ip_config_6),
+				                       applied_config_get_current (&priv->dev2_ip_config_6),
 				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
 			for (iter = priv->vpn_configs_6; iter; iter = iter->next)
@@ -12886,9 +12884,7 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	 * update in such case.
 	 */
 	if (activation_source_is_scheduled (self,
-	                                    IS_IPv4
-	                                      ? activate_stage5_ip4_config_result
-	                                      : activate_stage5_ip6_config_commit,
+	                                    activate_stage5_ip_config_result_x[IS_IPv4],
 	                                    addr_family))
 		return G_SOURCE_CONTINUE;
 
@@ -12906,6 +12902,7 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	if (!IS_IPv4) {
 		NMPlatform *platform;
 		GSList *dad6_failed_addrs, *iter;
+		const NMPlatformLink *pllink;
 
 		dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs);
 
@@ -12914,7 +12911,8 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 		    && priv->ifindex > 0
 		    && !nm_device_sys_iface_state_is_external (self)
 		    && (platform = nm_device_get_platform (self))
-		    && nm_platform_link_get (platform, priv->ifindex)) {
+		    && (pllink = nm_platform_link_get (platform, priv->ifindex))
+		    && (pllink->n_ifi_flags & IFF_UP)) {
 			gboolean need_ipv6ll = FALSE;
 			NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE;
 
@@ -12957,17 +12955,17 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 
 	if (!IS_IPv4) {
 		/* Check if DAD is still pending */
-		if (   priv->ip6_state == IP_CONF
+		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF
 		    && priv->dad6_ip6_config
 		    && priv->ext_ip6_config_captured
 		    && !nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
 		                                           priv->dad6_ip6_config)) {
 			_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
 			g_clear_object (&priv->dad6_ip6_config);
-			_set_ip_state (self, addr_family, IP_DONE);
+			_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_DONE);
 			check_ip_state (self, FALSE, TRUE);
 			if (priv->rt6_temporary_not_available)
-				nm_device_activate_schedule_ip6_config_result (self);
+				nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		}
 	}
 
@@ -13639,7 +13637,7 @@ nm_device_update_metered (NMDevice *self)
 	/* Try to guess a value using the metered flag in IP configuration */
 	if (value == NM_METERED_INVALID) {
 		if (   priv->ip_config_4
-		    && priv->ip4_state == IP_DONE
+		    && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 		    && nm_ip4_config_get_metered (priv->ip_config_4))
 			value = NM_METERED_GUESS_YES;
 	}
@@ -14240,15 +14238,15 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	nm_device_set_ip_config (self, AF_INET6, NULL, TRUE, NULL);
 	g_clear_object (&priv->proxy_config);
 	g_clear_object (&priv->con_ip_config_4);
-	applied_config_clear (&priv->dev_ip4_config);
-	applied_config_clear (&priv->wwan_ip_config_4);
+	applied_config_clear (&priv->dev_ip_config_4);
+	applied_config_clear (&priv->dev2_ip_config_4);
 	g_clear_object (&priv->ext_ip_config_4);
 	g_clear_object (&priv->ip_config_4);
 	g_clear_object (&priv->con_ip_config_6);
 	applied_config_clear (&priv->ac_ip6_config);
 	g_clear_object (&priv->ext_ip_config_6);
 	g_clear_object (&priv->ext_ip6_config_captured);
-	applied_config_clear (&priv->wwan_ip_config_6);
+	applied_config_clear (&priv->dev2_ip_config_6);
 	g_clear_object (&priv->ip_config_6);
 	g_clear_object (&priv->dad6_ip6_config);
 	priv->ipv6ll_has = FALSE;
@@ -14485,7 +14483,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		NMSettingIPConfig *s_ip4;
 
 		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		g_assert (s_ip4);
+		nm_assert (s_ip4);
 
 		g_ptr_array_add (argv, g_strdup ("--priority4"));
 		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET)));
@@ -14503,11 +14501,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 			if (client_id) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
 				g_ptr_array_add (argv,
-				                 _nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
-				                                            g_bytes_get_size (client_id),
-				                                            ':',
-				                                            FALSE,
-				                                            NULL));
+				                 nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
+				                                           g_bytes_get_size (client_id),
+				                                           ':',
+				                                           FALSE,
+				                                           NULL));
 			}
 
 			hostname = nm_dhcp_client_get_hostname (priv->dhcp4.client);
@@ -14545,11 +14543,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		if (nm_device_get_ip_iface_identifier (self, &iid, FALSE)) {
 			g_ptr_array_add (argv, g_strdup ("--iid"));
 			g_ptr_array_add (argv,
-			                 _nm_utils_bin2hexstr_full (iid.id_u8,
-			                                            sizeof (NMUtilsIPv6IfaceId),
-			                                            ':',
-			                                            FALSE,
-			                                            NULL));
+			                 nm_utils_bin2hexstr_full (iid.id_u8,
+			                                           sizeof (NMUtilsIPv6IfaceId),
+			                                           ':',
+			                                           FALSE,
+			                                           NULL));
 		}
 
 		g_ptr_array_add (argv, g_strdup ("--addr-gen-mode"));
@@ -15995,10 +15993,10 @@ _activation_func_to_string (ActivationHandleFunc func)
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage1_device_prepare);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage2_device_config);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage3_ip_config_start);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip4_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip6_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_result);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip6_config_commit);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip_config_timeout_4);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip_config_timeout_6);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip_config_result_4);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip_config_result_6);
 	g_return_val_if_reached ("unknown");
 }
 
@@ -16346,6 +16344,8 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		nm_assert (priv->type == NM_DEVICE_TYPE_UNKNOWN);
 		priv->type = g_value_get_uint (value);
+		nm_assert (priv->type > NM_DEVICE_TYPE_UNKNOWN);
+		nm_assert (priv->type <= NM_DEVICE_TYPE_WIFI_P2P);
 		break;
 	case PROP_LINK_TYPE:
 		/* construct-only */
@@ -16694,10 +16694,8 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->is_available = is_available;
 	klass->act_stage1_prepare = act_stage1_prepare;
 	klass->act_stage2_config = act_stage2_config;
-	klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	klass->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 
 	klass->get_type_description = get_type_description;
 	klass->can_auto_connect = can_auto_connect;
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index c2e3c474..45c9dda0 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -183,13 +183,13 @@ typedef enum { /*< skip >*/
 	_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP           = (1L << 2),
 
 	/* a device can be marked as unmanaged for various reasons. Some of these reasons
-	 * are authorative, others not. Non-authoritative reasons can be overruled by
+	 * are authoritative, others not. Non-authoritative reasons can be overruled by
 	 * `nmcli device set $DEVICE managed yes`. Also, for an explicit user activation
 	 * request we may want to consider the device as managed. This flag makes devices
 	 * that are unmanaged appear available. */
 	_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED  = (1L << 3),
 
-	/* a collection of flags, that are commonly set for an explict user-request. */
+	/* a collection of flags, that are commonly set for an explicit user-request. */
 	NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST                      = _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST
 	                                                                    | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER
 	                                                                    | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP
@@ -369,16 +369,13 @@ typedef struct _NMDeviceClass {
 	                                             NMDeviceStateReason *out_failure_reason);
 	NMActStageReturn    (* act_stage2_config)   (NMDevice *self,
 	                                             NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage3_ip4_config_start) (NMDevice *self,
-	                                                     NMIP4Config **out_config,
-	                                                     NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage3_ip6_config_start) (NMDevice *self,
-	                                                     NMIP6Config **out_config,
-	                                                     NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage4_ip4_config_timeout)   (NMDevice *self,
-	                                                         NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage4_ip6_config_timeout)   (NMDevice *self,
-	                                                         NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn    (* act_stage3_ip_config_start) (NMDevice *self,
+	                                                    int addr_family,
+	                                                    gpointer *out_config,
+	                                                    NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn    (* act_stage4_ip_config_timeout)   (NMDevice *self,
+	                                                        int addr_family,
+	                                                        NMDeviceStateReason *out_failure_reason);
 
 	void                (* ip4_config_pre_commit) (NMDevice *self, NMIP4Config *config);
 
@@ -456,6 +453,11 @@ typedef struct _NMDeviceClass {
 
 	guint32         (* get_dhcp_timeout) (NMDevice *self,
 	                                      int addr_family);
+
+	/* Controls, whether to call act_stage2_config() callback also for assuming
+	 * a device or for external activations. In this case, act_stage2_config() must
+	 * take care not to touch the device's configuration. */
+	bool act_stage2_config_also_for_external_or_assume:1;
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index ae180637..cb3b5907 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -23,7 +23,6 @@
 #include "nm-lldp-listener.h"
 
 #include <net/ethernet.h>
-#include <errno.h>
 
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
@@ -384,7 +383,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	                                     &chassis_id, &chassis_id_len);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading chassis-id: %s", g_strerror (-r));
+		             "failed reading chassis-id: %s", nm_strerror_native (-r));
 		return NULL;
 	}
 	if (chassis_id_len < 1) {
@@ -397,7 +396,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	                                  &port_id, &port_id_len);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading port-id: %s", g_strerror (-r));
+		             "failed reading port-id: %s", nm_strerror_native (-r));
 		return NULL;
 	}
 	if (port_id_len < 1) {
@@ -413,7 +412,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	r = sd_lldp_neighbor_get_destination_address (neighbor_sd, &neigh->destination_address);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed getting destination address: %s", g_strerror (-r));
+		             "failed getting destination address: %s", nm_strerror_native (-r));
 		goto out;
 	}
 
@@ -464,7 +463,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	r = sd_lldp_neighbor_tlv_rewind (neighbor_sd);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading tlv (rewind): %s", g_strerror (-r));
+		             "failed reading tlv (rewind): %s", nm_strerror_native (-r));
 		goto out;
 	}
 	do {
@@ -476,7 +475,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 			if (r == -ENXIO)
 				continue;
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "failed reading tlv: %s", g_strerror (-r));
+			             "failed reading tlv: %s", nm_strerror_native (-r));
 			goto out;
 		}
 
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
index eff355a3..be707e7a 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -77,17 +77,10 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	return NM_ACT_STAGE_RETURN_IP_FAIL;
 }
@@ -146,8 +139,7 @@ nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
 	device_class->create_and_realize = create_and_realize;
 	device_class->unrealize = unrealize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
 }
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 960b8f35..e3d3f9ee 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -131,39 +131,22 @@ _is_internal_interface (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
 
 	if (!_is_internal_interface (device))
 		return NM_ACT_STAGE_RETURN_IP_FAIL;
 
-	if (!nm_device_get_ip_ifindex (device)) {
+	if (nm_device_get_ip_ifindex (device) <= 0) {
 		priv->waiting_for_interface = TRUE;
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
-
-	if (!_is_internal_interface (device))
-		return NM_ACT_STAGE_RETURN_IP_FAIL;
-
-	if (!nm_device_get_ip_ifindex (device)) {
-		priv->waiting_for_interface = TRUE;
-		return NM_ACT_STAGE_RETURN_POSTPONE;
-	}
-
-	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static gboolean
@@ -207,7 +190,6 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	device_class->is_available = is_available;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->link_changed = link_changed;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
 }
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index 1f9afbab..b96eba68 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -71,17 +71,10 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	return NM_ACT_STAGE_RETURN_IP_FAIL;
 }
@@ -186,8 +179,7 @@ nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
 }
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 48f6b4f3..9d73c3ac 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -21,7 +21,6 @@
 
 #include "nm-ovsdb.h"
 
-#include <string.h>
 #include <gmodule.h>
 #include <gio/gunixsocketaddress.h>
 
@@ -349,14 +348,14 @@ _insert_interface (json_t *params, NMConnection *interface)
 	if (s_ovs_iface)
 		type = nm_setting_ovs_interface_get_interface_type (s_ovs_iface);
 
-	json_array_append (options, json_string ("map"));
+	json_array_append_new (options, json_string ("map"));
 	s_ovs_patch = nm_connection_get_setting_ovs_patch (interface);
 	if (s_ovs_patch) {
-		json_array_append (options, json_pack ("[[s, s]]",
+		json_array_append_new (options, json_pack ("[[s, s]]",
 		                                       "peer",
 		                                        nm_setting_ovs_patch_get_peer (s_ovs_patch)));
 	} else {
-		json_array_append (options, json_array ());
+		json_array_append_new (options, json_array ());
 	}
 
 	json_array_append_new (params,
@@ -504,12 +503,15 @@ _add_interface (NMOvsdb *self, json_t *params,
 	OpenvswitchBridge *ovs_bridge = NULL;
 	OpenvswitchPort *ovs_port = NULL;
 	OpenvswitchInterface *ovs_interface = NULL;
+	nm_auto_decref_json json_t *bridges = NULL;
+	nm_auto_decref_json json_t *new_bridges = NULL;
+	nm_auto_decref_json json_t *ports = NULL;
+	nm_auto_decref_json json_t *new_ports = NULL;
+	nm_auto_decref_json json_t *interfaces = NULL;
+	nm_auto_decref_json json_t *new_interfaces = NULL;
+	gboolean has_interface = FALSE;
 	int pi;
 	int ii;
-	json_t *bridges, *new_bridges;
-	json_t *ports, *new_ports;
-	json_t *interfaces, *new_interfaces;
-	gboolean has_interface = FALSE;
 
 	bridges = json_array ();
 	ports = json_array ();
@@ -585,14 +587,6 @@ _add_interface (NMOvsdb *self, json_t *params,
 		_insert_interface (params, interface);
 		json_array_append_new (new_interfaces, json_pack ("[s, s]", "named-uuid", "rowInterface"));
 	}
-
-	json_decref (interfaces);
-	json_decref (ports);
-	json_decref (bridges);
-
-	json_decref (new_interfaces);
-	json_decref (new_ports);
-	json_decref (new_bridges);
 }
 
 /**
@@ -612,14 +606,13 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 	OpenvswitchBridge *ovs_bridge;
 	OpenvswitchPort *ovs_port;
 	OpenvswitchInterface *ovs_interface;
-	int pi;
-	int ii;
-	json_t *bridges, *new_bridges;
-	json_t *ports, *new_ports;
-	json_t *interfaces, *new_interfaces;
+	nm_auto_decref_json json_t *bridges = NULL;
+	nm_auto_decref_json json_t *new_bridges = NULL;
 	gboolean bridges_changed;
 	gboolean ports_changed;
 	gboolean interfaces_changed;
+	int pi;
+	int ii;
 
 	bridges = json_array ();
 	new_bridges = json_array ();
@@ -627,20 +620,26 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 
 	g_hash_table_iter_init (&iter, priv->bridges);
 	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
-		json_array_append_new (bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+		nm_auto_decref_json json_t *ports = NULL;
+		nm_auto_decref_json json_t *new_ports = NULL;
 
 		ports = json_array ();
 		new_ports = json_array ();
 		ports_changed = FALSE;
 
+		json_array_append_new (bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+
 		for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+			nm_auto_decref_json json_t *interfaces = NULL;
+			nm_auto_decref_json json_t *new_interfaces = NULL;
+
+			interfaces = json_array ();
+			new_interfaces = json_array ();
 			port_uuid = g_ptr_array_index (ovs_bridge->ports, pi);
 			ovs_port = g_hash_table_lookup (priv->ports, port_uuid);
 
 			json_array_append_new (ports, json_pack ("[s,s]", "uuid", port_uuid));
 
-			interfaces = json_array ();
-			new_interfaces = json_array ();
 			interfaces_changed = FALSE;
 
 			for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
@@ -667,9 +666,6 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 				}
 				json_array_append_new (new_ports, json_pack ("[s,s]", "uuid", port_uuid));
 			}
-
-			json_decref (interfaces);
-			json_decref (new_interfaces);
 		}
 
 		if (json_array_size (new_ports) == 0) {
@@ -681,9 +677,6 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 			}
 			json_array_append_new (new_bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
 		}
-
-		json_decref (ports);
-		json_decref (new_ports);
 	}
 
 	if (bridges_changed) {
@@ -708,7 +701,7 @@ ovsdb_next_command (NMOvsdb *self)
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 	OvsdbMethodCall *call = NULL;
 	char *cmd;
-	json_t *msg = NULL;
+	nm_auto_decref_json json_t *msg = NULL;
 	json_t *params;
 
 	if (!priv->conn)
@@ -764,7 +757,6 @@ ovsdb_next_command (NMOvsdb *self)
 	cmd = json_dumps (msg, 0);
 
 	g_string_append (priv->output, cmd);
-	json_decref (msg);
 	free (cmd);
 
 	ovsdb_write (self);
@@ -1041,7 +1033,7 @@ static void
 ovsdb_got_echo (NMOvsdb *self, json_int_t id, json_t *data)
 {
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
-	json_t *msg;
+	nm_auto_decref_json json_t *msg = NULL;
 	char *reply;
 	gboolean output_was_empty;
 
@@ -1050,7 +1042,6 @@ ovsdb_got_echo (NMOvsdb *self, json_int_t id, json_t *data)
 	msg = json_pack ("{s:I, s:O}", "id", id, "result", data);
 	reply = json_dumps (msg, 0);
 	g_string_append (priv->output, reply);
-	json_decref (msg);
 	free (reply);
 
 	if (output_was_empty)
@@ -1300,6 +1291,9 @@ ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 	gpointer user_data;
 	gs_free_error GError *error = NULL;
 
+	if (!priv->client)
+		return;
+
 	_LOGD ("disconnecting from ovsdb");
 	nm_utils_error_set_cancelled (&error, is_disposing, "NMOvsdb");
 
@@ -1317,6 +1311,7 @@ ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 	g_clear_object (&priv->client);
 	g_clear_object (&priv->conn);
 	g_clear_pointer (&priv->db_uuid, g_free);
+	nm_clear_g_cancellable (&priv->cancellable);
 }
 
 static void
@@ -1535,11 +1530,14 @@ dispose (GObject *object)
 
 	ovsdb_disconnect (self, TRUE);
 
-	g_string_free (priv->input, TRUE);
-	priv->input = NULL;
-	g_string_free (priv->output, TRUE);
-	priv->output = NULL;
-
+	if (priv->input) {
+		g_string_free (priv->input, TRUE);
+		priv->input = NULL;
+	}
+	if (priv->output) {
+		g_string_free (priv->output, TRUE);
+		priv->output = NULL;
+	}
 	if (priv->calls) {
 		g_array_free (priv->calls, TRUE);
 		priv->calls = NULL;
@@ -1549,9 +1547,6 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->ports, g_hash_table_destroy);
 	g_clear_pointer (&priv->interfaces, g_hash_table_destroy);
 
-	g_cancellable_cancel (priv->cancellable);
-	g_clear_object (&priv->cancellable);
-
 	G_OBJECT_CLASS (nm_ovsdb_parent_class)->dispose (object);
 }
 
diff --git a/src/devices/team/nm-team-factory.c b/src/devices/team/nm-team-factory.c
index d062429f..f18b943d 100644
--- a/src/devices/team/nm-team-factory.c
+++ b/src/devices/team/nm-team-factory.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 
 #include "nm-manager.h"
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
index 11f35a05..4702c656 100644
--- a/src/devices/tests/meson.build
+++ b/src/devices/tests/meson.build
@@ -14,5 +14,6 @@ foreach test_unit: test_units
     'devices/' + test_unit,
     test_script,
     args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index dd2be540..4dfbe4c8 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -1,13 +1,15 @@
 common_sources = files(
   'nm-wifi-ap.c',
+  'nm-wifi-p2p-peer.c',
   'nm-wifi-utils.c',
 )
 
 sources = common_sources + files(
-  'nm-wifi-factory.c',
-  'nm-wifi-common.c',
-  'nm-device-wifi.c',
   'nm-device-olpc-mesh.c',
+  'nm-device-wifi-p2p.c',
+  'nm-device-wifi.c',
+  'nm-wifi-common.c',
+  'nm-wifi-factory.c',
 )
 
 if enable_iwd
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 71b8d151..dcc161d2 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-iwd.h"
 
-#include <string.h>
-
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
@@ -1132,7 +1130,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1380,7 +1378,7 @@ wifi_secrets_get_one (NMDeviceIwd *self,
 	                                                    TRUE,
 	                                                    setting_name,
 	                                                    flags,
-	                                                    setting_key,
+	                                                    NM_MAKE_STRV (setting_key),
 	                                                    wifi_secrets_cb,
 	                                                    nm_utils_user_data_pack (self, invocation));
 }
@@ -1549,11 +1547,11 @@ error:
  * DBus interface has appeared already.  If so proceed to call Start or
  * StartOpen on that interface.
  */
-static void act_check_interface (NMDeviceIwd *self)
+static void
+act_check_interface (NMDeviceIwd *self)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
-	gs_free_error GError *error = NULL;
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
 	GDBusProxy *proxy = NULL;
@@ -1894,7 +1892,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 			                                                    TRUE,
 			                                                    NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
 			                                                    NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION,
-			                                                    "psk",
+			                                                    NM_MAKE_STRV (NM_SETTING_WIRELESS_SECURITY_PSK),
 			                                                    act_psk_cb,
 			                                                    self);
 			nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
@@ -2335,7 +2333,7 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 			value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "State");
 			if (value) {
 				g_variant_unref (value);
-				interface = g_object_ref (priv->dbus_device_proxy);
+				interface = g_object_ref (G_DBUS_INTERFACE (priv->dbus_device_proxy));
 			} else {
 				_LOGE (LOGD_WIFI, "Interface %s not found on obj %s",
 				       NM_IWD_STATION_INTERFACE,
@@ -2401,7 +2399,7 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	gboolean powered;
 	NMDeviceWifiCapabilities capabilities;
 
-	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
+	if (!nm_g_object_ref_set (&priv->dbus_obj, object))
 		return;
 
 	if (priv->dbus_device_proxy) {
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 193779a7..1172a613 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -29,13 +29,11 @@
 #include "nm-device-olpc-mesh.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
-#include <errno.h>
 
 #include "devices/nm-device.h"
 #include "nm-device-wifi.h"
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
new file mode 100644
index 00000000..8381ebc7
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -0,0 +1,1316 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-wifi-p2p.h"
+
+#include <sys/socket.h>
+
+#include "supplicant/nm-supplicant-manager.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#include "nm-manager.h"
+#include "nm-utils.h"
+#include "nm-wifi-p2p-peer.h"
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "nm-setting-wifi-p2p.h"
+#include "nm-act-request.h"
+#include "nm-ip4-config.h"
+#include "platform/nm-platform.h"
+#include "nm-manager.h"
+#include "nm-core-internal.h"
+#include "platform/nmp-object.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceWifiP2P);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifiP2P,
+	PROP_PEERS,
+);
+
+typedef struct {
+	NMSupplicantManager *sup_mgr;
+
+	/* NOTE: In theory management and group ifaces could be identical. However,
+	 * in practice, this cannot happen currently as NMDeviceWifiP2P is only
+	 * created for existing non-P2P interfaces.
+	 * (i.e. a single standalone P2P interface is not supported at this point)
+	 */
+	NMSupplicantInterface *mgmt_iface;
+	NMSupplicantInterface *group_iface;
+
+	CList peers_lst_head;
+
+	guint sup_timeout_id;
+	guint peer_dump_id;
+	guint peer_missing_id;
+
+	bool is_waiting_for_supplicant:1;
+} NMDeviceWifiP2PPrivate;
+
+struct _NMDeviceWifiP2P {
+	NMDevice parent;
+	NMDeviceWifiP2PPrivate _priv;
+};
+
+struct _NMDeviceWifiP2PClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceWifiP2P, nm_device_wifi_p2p, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_WIFI_P2P_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWifiP2P, NM_IS_DEVICE_WIFI_P2P, NMDevice)
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed;
+
+static void supplicant_group_interface_release (NMDeviceWifiP2P *self);
+static void supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting);
+
+/*****************************************************************************/
+
+static void
+_peer_dump (NMDeviceWifiP2P *self,
+            NMLogLevel log_level,
+            const NMWifiP2PPeer *peer,
+            const char *prefix,
+            gint32 now_s)
+{
+	char buf[1024];
+
+	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-peer: %-7s %s",
+	        prefix,
+	        nm_wifi_p2p_peer_to_string (peer, buf, sizeof (buf), now_s));
+}
+
+static gboolean
+peer_list_dump (gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->peer_dump_id = 0;
+
+	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
+		NMWifiP2PPeer *peer;
+		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
+
+		_LOGD (LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s);
+		c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst)
+			_peer_dump (self, LOGL_DEBUG, peer, "dump", now_s);
+	}
+	return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_peer_list_dump (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (   !priv->peer_dump_id
+	    && _LOGD_ENABLED (LOGD_WIFI_SCAN))
+		priv->peer_dump_id = g_timeout_add_seconds (1, peer_list_dump, self);
+}
+
+/*****************************************************************************/
+
+static void
+_set_is_waiting_for_supplicant (NMDeviceWifiP2P *self, gboolean is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->is_waiting_for_supplicant == (!!is_waiting))
+		return;
+
+	priv->is_waiting_for_supplicant = is_waiting;
+
+	if (is_waiting)
+		nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+	else
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+}
+
+/*****************************************************************************/
+
+static gboolean
+check_connection_peer_joined (NMDeviceWifiP2P *device)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (device);
+	NMConnection *conn = nm_device_get_applied_connection (NM_DEVICE (device));
+	NMWifiP2PPeer *peer;
+	const char* group;
+	const char * const * groups;
+
+	if (!conn || !priv->group_iface)
+		return FALSE;
+
+	/* Comparing the object path found on the group_iface with the peers
+	 * found on the mgmt_iface is legal. */
+	group = nm_supplicant_interface_get_p2p_group_path (priv->group_iface);
+	if (!group)
+		return FALSE;
+
+	/* NOTE: We currently only support connections to a specific peer */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, conn);
+	if (!peer)
+		return FALSE;
+
+	groups = nm_wifi_p2p_peer_get_groups (peer);
+	if (   !groups
+	    || !g_strv_contains (groups, group))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+disconnect_on_connection_peer_missing_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	_LOGW (LOGD_WIFI, "Peer requested in connection is missing for too long, failing connection.");
+
+	priv->peer_missing_id = 0;
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	return FALSE;
+}
+
+static void
+update_disconnect_on_connection_peer_missing (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	state = nm_device_get_state (NM_DEVICE (self));
+	if (   state < NM_DEVICE_STATE_IP_CONFIG
+	    || state > NM_DEVICE_STATE_ACTIVATED) {
+		nm_clear_g_source (&priv->peer_missing_id);
+		return;
+	}
+
+	if (check_connection_peer_joined (self)) {
+		if (nm_clear_g_source (&priv->peer_missing_id))
+			_LOGD (LOGD_WIFI, "Peer requested in connection is joined, removing timeout");
+		return;
+	}
+
+	if (priv->peer_missing_id == 0) {
+		_LOGD (LOGD_WIFI, "Peer requested in connection is missing, adding timeout");
+		priv->peer_missing_id = g_timeout_add_seconds (5, disconnect_on_connection_peer_missing_cb, self);
+	}
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMSupplicantInterfaceState supplicant_state;
+
+	if (!priv->mgmt_iface)
+		return FALSE;
+
+	supplicant_state = nm_supplicant_interface_get_state (priv->mgmt_iface);
+	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	if (!NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	/* TODO: Allow limitting the interface using the HW-address? */
+
+	/* We don't need to check anything else here. The P2P device will only
+	 * exists if we are able to establish a P2P connection, and there should
+	 * be no further restrictions necessary.
+	 */
+
+	return TRUE;
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	gs_free char *setting_name = NULL;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	const char *setting_peer;
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+
+	if (!specific_object) {
+		/* If not given a specific object, we need at minimum a peer address */
+		if (!s_wifi_p2p) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+		setting_peer = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+		if (!setting_peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting with a valid Peer is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+	} else {
+		peer = nm_wifi_p2p_peer_lookup_for_device (NM_DEVICE (self), specific_object);
+		if (!peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+			             "The P2P peer %s is unknown",
+			             specific_object);
+			return FALSE;
+		}
+
+		setting_peer = nm_wifi_p2p_peer_get_address (peer);
+		g_return_val_if_fail (setting_peer, FALSE);
+	}
+
+	/* Add a Wi-Fi P2P setting if one doesn't exist yet */
+	if (!s_wifi_p2p) {
+		s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_setting_wifi_p2p_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wifi_p2p));
+	}
+
+	g_object_set (G_OBJECT (s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL);
+
+	setting_name = g_strdup_printf ("Wi-Fi P2P Peer %s", setting_peer);
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WIFI_P2P_SETTING_NAME,
+	                           existing_connections,
+	                           setting_name,
+	                           setting_name,
+	                           NULL,
+	                           TRUE);
+
+	return TRUE;
+}
+
+/*
+ * supplicant_find_timeout_cb
+ *
+ * Called when the supplicant has been unable to find the peer we want to connect to.
+ */
+static gboolean
+supplicant_find_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) could not find peer, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	ret = NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	g_return_val_if_fail (s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE);
+
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		/* Set up a timeout on the find attempt and run a find for the same period of time */
+		priv->sup_timeout_id = g_timeout_add_seconds (10,
+		                                              supplicant_find_timeout_cb,
+		                                              self);
+
+		nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, 10);
+
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+cleanup_p2p_connect_attempt (NMDeviceWifiP2P *self, gboolean disconnect)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	nm_clear_g_source (&priv->peer_missing_id);
+
+	if (priv->mgmt_iface)
+		nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (disconnect && priv->group_iface)
+		nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+}
+
+/*
+ * supplicant_connection_timeout_cb
+ *
+ * Called when the supplicant has been unable to connect to a peer
+ * within a specified period of time.
+ */
+static gboolean
+supplicant_connection_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) connecting took too long, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	GBytes *wfd_ies;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	connection = nm_device_get_applied_connection (device);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	nm_assert (NM_IS_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P)));
+
+	/* The prepare stage ensures that the peer has been found */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	/* Set the WFD IEs before trying to establish the connection. */
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	wfd_ies = nm_setting_wifi_p2p_get_wfd_ies (s_wifi_p2p);
+	nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, wfd_ies);
+
+	/* TODO: Grab secrets if we don't have them yet! */
+
+	/* TODO: Fix "pbc" being hardcoded here! */
+	nm_supplicant_interface_p2p_connect (priv->mgmt_iface,
+	                                     nm_wifi_p2p_peer_get_supplicant_path (peer),
+	                                     "pbc", NULL);
+
+	/* Set up a timeout on the connect attempt */
+	priv->sup_timeout_id = g_timeout_add_seconds (45,
+	                                              supplicant_connection_timeout_cb,
+	                                              self);
+
+	/* We'll get stage3 started when the P2P group has been started */
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+emit_signal_p2p_peer_add_remove (NMDeviceWifiP2P *device,
+                                 NMWifiP2PPeer *peer,
+                                 gboolean is_added /* or else is_removed */)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (device),
+	                            &interface_info_device_wifi_p2p,
+	                            is_added
+	                              ? &nm_signal_info_wifi_p2p_peer_added
+	                              : &nm_signal_info_wifi_p2p_peer_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (peer)));
+}
+
+static void
+peer_add_remove (NMDeviceWifiP2P *self,
+                 gboolean is_adding, /* or else removing */
+                 NMWifiP2PPeer *peer,
+                 gboolean recheck_available_connections)
+{
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (is_adding) {
+		g_object_ref (peer);
+		peer->wifi_device = device;
+		c_list_link_tail (&priv->peers_lst_head, &peer->peers_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (peer));
+		_peer_dump (self, LOGL_DEBUG, peer, "added", 0);
+
+		emit_signal_p2p_peer_add_remove (self, peer, TRUE);
+	} else {
+		peer->wifi_device = NULL;
+		c_list_unlink (&peer->peers_lst);
+		_peer_dump (self, LOGL_DEBUG, peer, "removed", 0);
+	}
+
+	_notify (self, PROP_PEERS);
+
+	if (!is_adding) {
+		emit_signal_p2p_peer_add_remove (self, peer, FALSE);
+		nm_dbus_object_clear_and_unexport (&peer);
+	}
+
+	if (is_adding) {
+		/* If we are in prepare state, then we are currently runnign a find
+		 * to search for the requested peer. */
+		if (nm_device_get_state (device) == NM_DEVICE_STATE_PREPARE) {
+			NMConnection *connection;
+
+			connection = nm_device_get_applied_connection (device);
+			g_assert (connection);
+
+			peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+			if (peer) {
+				/* A peer for the connection was found, cancel the timeout and go to configure state. */
+				nm_clear_g_source (&priv->sup_timeout_id);
+				nm_device_activate_schedule_stage2_device_config (device);
+			}
+		}
+
+		/* TODO: We may want to re-check auto-activation here, otherwise it will never work. */
+	}
+
+	update_disconnect_on_connection_peer_missing (self);
+}
+
+static void
+remove_all_peers (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMWifiP2PPeer *peer;
+
+	if (c_list_is_empty (&priv->peers_lst_head))
+		return;
+
+	while ((peer = c_list_first_entry (&priv->peers_lst_head, NMWifiP2PPeer, peers_lst)))
+		peer_add_remove (self, FALSE, peer, FALSE);
+
+	nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+/*****************************************************************************/
+
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gboolean indicate_addressing_running;
+	NMConnection *connection;
+	const char *method;
+
+	connection = nm_device_get_applied_connection (device);
+
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
+
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
+
+	return NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	int ifindex = nm_device_get_ip_ifindex (device);
+
+	cleanup_p2p_connect_attempt (self, TRUE);
+
+	/* Clear any critical protocol notification in the Wi-Fi stack */
+	if (ifindex > 0)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	*out_source = NM_DEVICE_MTU_SOURCE_NONE;
+	return 0;
+}
+
+static const char *
+get_auto_ip_config_method (NMDevice *device, int addr_family)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	/* Override the AUTO method to mean shared if we are group owner. */
+	if (   priv->group_iface
+	    && nm_supplicant_interface_get_p2p_group_owner (priv->group_iface)) {
+		if (addr_family == AF_INET)
+			return NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+
+		if (addr_family == AF_INET6)
+			return NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+	}
+
+	return NULL;
+}
+
+static gboolean
+unmanaged_on_quit (NMDevice *self)
+{
+	return TRUE;
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+                           int new_state_i,
+                           int old_state_i,
+                           int disconnect_reason,
+                           gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "supplicant management interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "supplicant ready");
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_interfaces_release (self, TRUE);
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_iface_peer_updated_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  GVariant *properties,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *found_peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	found_peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (found_peer) {
+		if (!nm_wifi_p2p_peer_update_from_properties (found_peer, object_path, properties))
+			return;
+
+		update_disconnect_on_connection_peer_missing (self);
+		_peer_dump (self, LOGL_DEBUG, found_peer, "updated", 0);
+	} else {
+		gs_unref_object NMWifiP2PPeer *peer = NULL;
+
+		peer = nm_wifi_p2p_peer_new_from_properties (object_path, properties);
+		if (!peer) {
+			_LOGD (LOGD_WIFI, "invalid P2P peer properties received for %s", object_path);
+			return;
+		}
+
+		peer_add_remove (self, TRUE, peer, TRUE);
+	}
+
+	schedule_peer_list_dump (self);
+}
+
+static void
+supplicant_iface_peer_removed_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (!peer)
+		return;
+
+	peer_add_remove (self, FALSE, peer, TRUE);
+	schedule_peer_list_dump (self);
+}
+
+static void
+check_group_iface_ready (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);;
+
+	if (!priv->group_iface)
+		return;
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	if (!nm_supplicant_interface_get_p2p_group_joined (priv->group_iface))
+		return;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	update_disconnect_on_connection_peer_missing (self);
+
+	nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+}
+
+static void
+supplicant_group_iface_state_cb (NMSupplicantInterface *iface,
+                                 int new_state_i,
+                                 int old_state_i,
+                                 int disconnect_reason,
+                                 gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "P2P Group supplicant interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "P2P Group supplicant ready");
+
+		if (!nm_device_set_ip_iface (device, nm_supplicant_interface_get_ifname (priv->group_iface))) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			break;
+		}
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+
+		check_group_iface_ready (self);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_group_interface_release (self);
+
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_DISCONNECTED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_group_iface_group_finished_cb (NMSupplicantInterface *iface,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	supplicant_group_interface_release (self);
+
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_DISCONNECTED,
+	                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+}
+
+static void
+supplicant_iface_group_joined_updated_cb (NMSupplicantInterface *iface,
+                                          GParamSpec *pspec,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
+                                   NMSupplicantInterface *group_iface,
+                                   NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (self != NULL);
+
+	if (!nm_device_is_activating (NM_DEVICE (self))) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant notified a group start but we are not trying to connect! Ignoring the event.");
+		return;
+	}
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	supplicant_group_interface_release (self);
+	priv->group_iface = g_object_ref (group_iface);
+
+	/* We need to wait for the interface to be ready and the group
+	 * information to be resolved. */
+	g_signal_connect (priv->group_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED,
+	                  G_CALLBACK (supplicant_iface_group_joined_updated_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface,
+	                  NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_group_iface_state_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface, NM_SUPPLICANT_INTERFACE_GROUP_FINISHED,
+	                  G_CALLBACK (supplicant_group_iface_group_finished_cb),
+	                  self);
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		_set_is_waiting_for_supplicant (self, TRUE);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_group_interface_release (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->group_iface)
+		return;
+
+	g_signal_handlers_disconnect_by_data (priv->group_iface, self);
+
+	nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+
+	g_clear_object (&priv->group_iface);
+}
+
+static void
+supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->peer_dump_id);
+
+	remove_all_peers (self);
+
+	if (priv->mgmt_iface) {
+		_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: Releasing WPA supplicant interface.");
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		g_signal_handlers_disconnect_by_data (priv->mgmt_iface, self);
+		g_clear_object (&priv->mgmt_iface);
+		nm_clear_g_source (&priv->sup_timeout_id);
+	}
+
+	supplicant_group_interface_release (self);
+
+	if (set_is_waiting)
+		_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	update_disconnect_on_connection_peer_missing (self);
+
+	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		/* Clean up the supplicant interface because in these states the
+		 * device cannot be used.
+		 * Do not clean up for the UNMANAGED to UNAVAILABLE transition which
+		 * will happen during initialization.
+		 */
+		if (priv->mgmt_iface && old_state > new_state)
+			supplicant_interfaces_release (self, TRUE);
+
+		/* TODO: More cleanup needed? */
+	}
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		if (   !priv->mgmt_iface
+		    || nm_supplicant_interface_get_state (priv->mgmt_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, TRUE);
+
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		/* Disconnect? */
+		break;
+	case NM_DEVICE_STATE_IP_CHECK:
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		//activation_success_handler (device);
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		/* Clear any critical protocol notification in the wifi stack.
+		 * At this point the IP device may have been removed already. */
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		if (nm_device_get_ip_ifindex (device) > 0)
+			nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+impl_device_wifi_p2p_start_find (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	gs_unref_variant GVariant *options = NULL;
+	const char *opts_key;
+	GVariant *opts_val;
+	GVariantIter iter;
+	gint32 timeout = 30;
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	g_variant_iter_init (&iter, options);
+	while (g_variant_iter_next (&iter, "{&sv}", &opts_key, &opts_val)) {
+		_nm_unused gs_unref_variant GVariant *opts_val_free = opts_val;
+
+		if (nm_streq (opts_key, "timeout")) {
+			if (!g_variant_is_of_type (opts_val, G_VARIANT_TYPE_INT32)) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_INVALID_ARGUMENT,
+				                                               "\"timeout\" must be an integer \"i\"");
+				return;
+			}
+
+			timeout = g_variant_get_int32 (opts_val);
+			if (timeout <= 0 || timeout > 600) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "The timeout for a find operation needs to be in the range of 1-600s.");
+				return;
+			}
+
+			continue;
+		}
+
+		g_dbus_method_invocation_return_error (invocation,
+		                                       NM_DEVICE_ERROR,
+		                                       NM_DEVICE_ERROR_INVALID_ARGUMENT,
+		                                       "Unsupported options key \"%s\"",
+		                                       opts_key);
+		return;
+	}
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, timeout);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+impl_device_wifi_p2p_stop_find (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_stop_find (priv->mgmt_iface);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+/*****************************************************************************/
+
+NMSupplicantInterface *
+nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE_WIFI_P2P (self), NULL);
+
+	return NM_DEVICE_WIFI_P2P_GET_PRIVATE (self)->mgmt_iface;
+}
+
+void
+nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                   NMSupplicantInterface *iface)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI_P2P (self));
+	g_return_if_fail (!iface || NM_IS_SUPPLICANT_INTERFACE (iface));
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->mgmt_iface == iface)
+		goto done;
+
+	supplicant_interfaces_release (self, FALSE);
+
+	if (!iface)
+		goto done;
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant management interface changed to %s.",
+	       nm_supplicant_interface_get_object_path (iface));
+
+	priv->mgmt_iface = g_object_ref (iface);
+
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_iface_state_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
+	                  G_CALLBACK (supplicant_iface_peer_updated_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
+	                  G_CALLBACK (supplicant_iface_peer_removed_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	                  G_CALLBACK (supplicant_iface_group_started_cb),
+	                  self);
+done:
+	nm_device_queue_recheck_available (NM_DEVICE (self),
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	_set_is_waiting_for_supplicant (self,
+	                                   !priv->mgmt_iface
+	                                || (  nm_supplicant_interface_get_state (priv->mgmt_iface)
+	                                    < NM_SUPPLICANT_INTERFACE_STATE_READY));
+}
+
+void
+nm_device_wifi_p2p_remove (NMDeviceWifiP2P* self)
+{
+	g_signal_emit_by_name (self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "wifi-p2p";
+}
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIFI_P2P,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StartFind",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"),
+					),
+				),
+				.handle = impl_device_wifi_p2p_start_find,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StopFind",
+				),
+				.handle = impl_device_wifi_p2p_stop_find,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_wifi_p2p_peer_added,
+			&nm_signal_info_wifi_p2p_peer_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",  "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Peers",      "ao", NM_DEVICE_WIFI_P2P_PEERS),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	const char **list;
+
+	switch (prop_id) {
+	case PROP_PEERS:
+		list = nm_wifi_p2p_peers_get_paths (&priv->peers_lst_head);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_wifi_p2p_init (NMDeviceWifiP2P * self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	c_list_init (&priv->peers_lst_head);
+
+	priv->sup_mgr = g_object_ref (nm_supplicant_manager_get ());
+}
+
+static void
+constructed (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->constructed (object);
+
+	_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+NMDeviceWifiP2P *
+nm_device_wifi_p2p_new (const char *iface)
+{
+	return g_object_new (NM_TYPE_DEVICE_WIFI_P2P,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi P2P",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI_P2P,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+	                     NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (object);
+
+	g_clear_object (&priv->sup_mgr);
+
+	supplicant_interfaces_release (self, FALSE);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWifiP2P *peer = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (peer);
+
+	nm_assert (c_list_is_empty (&priv->peers_lst_head));
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->finalize (object);
+}
+
+static void
+nm_device_wifi_p2p_class_init (NMDeviceWifiP2PClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	object_class->constructed  = constructed;
+	object_class->get_property = get_property;
+	object_class->dispose      = dispose;
+	object_class->finalize     = finalize;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wifi_p2p);
+
+	device_class->connection_type_supported        = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI_P2P);
+	device_class->get_type_description             = get_type_description;
+
+	/* Do we need compatibility checking or is the default good enough? */
+	device_class->is_available                     = is_available;
+	device_class->check_connection_compatible      = check_connection_compatible;
+	device_class->complete_connection              = complete_connection;
+
+	device_class->act_stage1_prepare               = act_stage1_prepare;
+	device_class->act_stage2_config                = act_stage2_config;
+	device_class->get_configured_mtu               = get_configured_mtu;
+	device_class->get_auto_ip_config_method        = get_auto_ip_config_method;
+	device_class->act_stage3_ip_config_start      = act_stage3_ip_config_start;
+
+	device_class->deactivate                       = deactivate;
+	device_class->unmanaged_on_quit                = unmanaged_on_quit;
+
+	device_class->state_changed                    = device_state_changed;
+
+	obj_properties[PROP_PEERS] =
+	    g_param_spec_boxed (NM_DEVICE_WIFI_P2P_PEERS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-device-wifi-p2p.h b/src/devices/wifi/nm-device-wifi-p2p.h
new file mode 100644
index 00000000..a13eef15
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.h
@@ -0,0 +1,50 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEVICE_WIFI_P2P_H__
+#define __NM_DEVICE_WIFI_P2P_H__
+
+#include "devices/nm-device.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#define NM_TYPE_DEVICE_WIFI_P2P            (nm_device_wifi_p2p_get_type ())
+#define NM_DEVICE_WIFI_P2P(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P))
+#define NM_DEVICE_WIFI_P2P_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+#define NM_IS_DEVICE_WIFI_P2P(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_IS_DEVICE_WIFI_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_DEVICE_WIFI_P2P_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+
+#define NM_DEVICE_WIFI_P2P_PEERS       "peers"
+#define NM_DEVICE_WIFI_P2P_GROUPS      "groups"
+
+typedef struct _NMDeviceWifiP2P NMDeviceWifiP2P;
+typedef struct _NMDeviceWifiP2PClass NMDeviceWifiP2PClass;
+
+GType nm_device_wifi_p2p_get_type (void);
+
+NMDeviceWifiP2P *nm_device_wifi_p2p_new (const char *iface);
+
+NMSupplicantInterface * nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self);
+void                    nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                                           NMSupplicantInterface  *iface);
+
+void nm_device_wifi_p2p_remove (NMDeviceWifiP2P *self);
+
+#endif /* __NM_DEVICE_WIFI_P2P_H__ */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index e0be38c3..d33d4307 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -24,10 +24,9 @@
 #include "nm-device-wifi.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 
+#include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
@@ -79,6 +78,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 
 enum {
 	SCANNING_PROHIBITED,
+	P2P_DEVICE_CREATED,
 
 	LAST_SIGNAL
 };
@@ -124,6 +124,8 @@ typedef struct {
 	guint             wps_timeout_id;
 
 	NMSettingWirelessWakeOnWLan wowlan_restore;
+
+	NMDeviceWifiP2P  *p2p_device;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -186,6 +188,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
+static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                                   GParamSpec *pspec,
+                                                   NMDeviceWifi *self);
+
 static void request_wireless_scan (NMDeviceWifi *self,
                                    gboolean periodic,
                                    gboolean force_if_scanning,
@@ -198,6 +204,8 @@ static void ap_add_remove (NMDeviceWifi *self,
 
 static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
 
+static void recheck_p2p_availability (NMDeviceWifi *self);
+
 /*****************************************************************************/
 
 static void
@@ -291,6 +299,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  "notify::" NM_SUPPLICANT_INTERFACE_CURRENT_BSS,
 	                  G_CALLBACK (supplicant_iface_notify_current_bss),
 	                  self);
+	g_signal_connect (priv->sup_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE,
+	                  G_CALLBACK (supplicant_iface_notify_p2p_available),
+	                  self);
 
 	_notify_scanning (self);
 
@@ -347,6 +359,11 @@ supplicant_interface_release (NMDeviceWifi *self)
 		g_clear_object (&priv->sup_iface);
 	}
 
+	if (priv->p2p_device) {
+		/* Signal to P2P device to also release its reference */
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL);
+	}
+
 	_notify_scanning (self);
 }
 
@@ -1185,7 +1202,7 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -2028,6 +2045,10 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	    && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		priv->ssid_found = TRUE;
 
+	if (   old_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    && new_state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+		recheck_p2p_availability (self);
+
 	switch (new_state) {
 	case NM_SUPPLICANT_INTERFACE_STATE_READY:
 		_LOGD (LOGD_WIFI, "supplicant ready");
@@ -2204,6 +2225,67 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 	}
 }
 
+/* We bind the existence of the P2P device to a wifi device that is being
+ * managed by NetworkManager and is capable of P2P operation.
+ * Note that some care must be taken here, because we don't want to re-create
+ * the device every time the supplicant interface is destroyed (e.g. due to
+ * a suspend/resume cycle).
+ * Therefore, this function will be called when a change in the P2P capability
+ * is detected and the supplicant interface has been initialised.
+ */
+static void
+recheck_p2p_availability (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	gboolean p2p_available;
+
+	g_object_get (priv->sup_iface,
+	              NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, &p2p_available,
+	              NULL);
+
+	if (p2p_available && !priv->p2p_device) {
+		gs_free char *iface_name = NULL;
+
+		/* Create a P2P device. "p2p-dev-" is the same prefix as chosen by
+		 * wpa_supplicant internally.
+		 */
+		iface_name = g_strconcat ("p2p-dev-", nm_device_get_iface (NM_DEVICE (self)), NULL);
+
+		priv->p2p_device = nm_device_wifi_p2p_new (iface_name);
+
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+
+		g_signal_emit (self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device);
+		g_object_add_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		g_object_unref (priv->p2p_device);
+		return;
+	}
+
+	if (p2p_available && priv->p2p_device) {
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+		return;
+	}
+
+	if (!p2p_available && priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+		return;
+	}
+}
+
+static void
+supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                       GParamSpec *pspec,
+                                       NMDeviceWifi *self)
+{
+	/* Do not update when the interface is still initializing. */
+	if (nm_supplicant_interface_get_state (iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	recheck_p2p_availability (self);
+}
+
 static gboolean
 handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
@@ -2792,52 +2874,29 @@ out:
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	gboolean indicate_addressing_running;
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+	const char *method;
 
 	connection = nm_device_get_applied_connection (device);
 
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	if (s_ip4)
-		method = nm_setting_ip_config_get_method (s_ip4);
-
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
-
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
-
-	connection = nm_device_get_applied_connection (device);
-
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (s_ip6)
-		method = nm_setting_ip_config_get_method (s_ip6);
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
 
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 ||
-	    strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
 
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -2872,19 +2931,27 @@ is_static_wep (NMConnection *connection)
 }
 
 static NMActStageReturn
-handle_ip_config_timeout (NMDeviceWifi *self,
-                          NMConnection *connection,
-                          gboolean may_fail,
-                          gboolean *chain_up,
-                          NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *device,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	gboolean may_fail;
 
-	g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	connection = nm_device_get_applied_connection (device);
+	s_ip = nm_connection_get_setting_ip4_config (connection);
+	may_fail = nm_setting_ip_config_get_may_fail (s_ip);
 
-	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) {
-		*chain_up = TRUE;
-		return NM_ACT_STAGE_RETURN_FAILURE;
+	if (priv->mode == NM_802_11_MODE_AP)
+		goto call_parent;
+
+	if (   may_fail
+	    && !is_static_wep (connection)) {
+		/* Not static WEP or failure allowed; let superclass handle it */
+		goto call_parent;
 	}
 
 	/* If IP configuration times out and it's a static WEP connection, that
@@ -2893,71 +2960,23 @@ handle_ip_config_timeout (NMDeviceWifi *self,
 	 * to wait for DHCP to fail to figure it out.  For all other Wi-Fi security
 	 * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
 	 * connection would have failed before IP configuration.
-	 */
-	if (!may_fail && is_static_wep (connection)) {
-		/* Activation failed, we must have bad encryption key */
-		_LOGW (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get IP configuration for connection '%s'.",
-		       nm_connection_get_id (connection));
+	 *
+	* Activation failed, we must have bad encryption key */
+	_LOGW (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) could not get IP configuration for connection '%s'.",
+	       nm_connection_get_id (connection));
 
-		if (handle_auth_or_fail (self, NULL, TRUE)) {
-			_LOGI (LOGD_DEVICE | LOGD_WIFI,
-			       "Activation: (wifi) asking for new secrets");
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		} else {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
-		}
-	} else {
-		/* Not static WEP or failure allowed; let superclass handle it */
-		*chain_up = TRUE;
+	if (!handle_auth_or_fail (self, NULL, TRUE)) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip4);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip4_config_timeout (device, out_failure_reason);
-
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip6);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip6_config_timeout (device, out_failure_reason);
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) asking for new secrets");
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	return ret;
+call_parent:
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip_config_timeout (device, addr_family, out_failure_reason);
 }
 
 static void
@@ -3314,6 +3333,12 @@ dispose (GObject *object)
 
 	remove_all_aps (self);
 
+	if (priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+	}
+
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object);
 }
 
@@ -3359,10 +3384,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->get_configured_mtu = get_configured_mtu;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	device_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	device_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 	device_class->unmanaged_on_quit = unmanaged_on_quit;
@@ -3426,4 +3449,12 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
 	                  NULL, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+
+	signals[P2P_DEVICE_CREATED] =
+	    g_signal_new (NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__OBJECT,
+	                  G_TYPE_NONE, 1, NM_TYPE_DEVICE);
 }
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 1c555f5f..116ad11e 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -40,6 +40,7 @@
 #define NM_DEVICE_WIFI_LAST_SCAN           "last-scan"
 
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
+#define NM_DEVICE_WIFI_P2P_DEVICE_CREATED     "p2p-device-created"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 736a1a23..d668f0d8 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-iwd-manager.h"
 
-#include <string.h>
 #include <net/if.h>
 
 #include "nm-logging.h"
@@ -137,6 +136,7 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	int ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
+	int errsv;
 
 	/* Be paranoid and check the sender address */
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager));
@@ -172,8 +172,9 @@ agent_dbus_method_cb (GDBusConnection *connection,
 
 	ifindex = if_nametoindex (ifname);
 	if (!ifindex) {
+		errsv = errno;
 		_LOGD ("agent-request: if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, device_path, errno);
+		       ifname, device_path, errsv);
 		goto return_error;
 	}
 
@@ -339,6 +340,7 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	const char *ifname;
 	int ifindex;
 	NMDevice *device;
+	int errsv;
 
 	ifname = get_property_string_or_null (proxy, "Name");
 	if (!ifname) {
@@ -350,8 +352,9 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	ifindex = if_nametoindex (ifname);
 
 	if (!ifindex) {
+		errsv = errno;
 		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, g_dbus_proxy_get_object_path (proxy), errno);
+		       ifname, g_dbus_proxy_get_object_path (proxy), errsv);
 		return;
 	}
 
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index e5573383..87daff59 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -23,7 +23,6 @@
 
 #include "nm-wifi-ap.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-setting-wireless.h"
@@ -425,6 +424,8 @@ security_from_vardict (GVariant *security)
 		    g_strv_contains (array, "wpa-fils-sha256") ||
 		    g_strv_contains (array, "wpa-fils-sha384"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
+		if (g_strv_contains (array, "sae"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
 		g_free (array);
 	}
 
@@ -1390,7 +1391,8 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_GROUP_TKIP \
 	| NM_802_11_AP_SEC_GROUP_CCMP \
 	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
-	| NM_802_11_AP_SEC_KEY_MGMT_802_1X )
+	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
+	| NM_802_11_AP_SEC_KEY_MGMT_SAE )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 4e9d1ecb..9a89bbe9 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -26,6 +26,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-device-wifi.h"
+#include "nm-device-wifi-p2p.h"
 #include "nm-device-olpc-mesh.h"
 #include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
@@ -68,6 +69,18 @@ nm_device_factory_create (GError **error)
 
 /*****************************************************************************/
 
+static void
+p2p_device_created (NMDeviceWifi    *device,
+                    NMDeviceWifiP2P *p2p_device,
+                    NMDeviceFactory *self)
+{
+	nm_log_info (LOGD_PLATFORM | LOGD_WIFI,
+	             "Wi-Fi P2P device controlled by interface %s created",
+	             nm_device_get_iface (NM_DEVICE (device)));
+
+	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device);
+}
+
 static NMDevice *
 create_device (NMDeviceFactory *factory,
                const char *iface,
@@ -98,6 +111,7 @@ create_device (NMDeviceFactory *factory,
 	            NM_PRINT_FMT_QUOTE_STRING (backend),
 	            WITH_IWD ? " (iwd support enabled)" : "");
 	if (!backend || !strcasecmp (backend, "wpa_supplicant")) {
+		NMDevice *device;
 		NMDeviceWifiCapabilities capabilities;
 		NM80211Mode mode;
 
@@ -120,7 +134,14 @@ create_device (NMDeviceFactory *factory,
 			return NULL;
 		}
 
-		return nm_device_wifi_new (iface, capabilities);
+		device = nm_device_wifi_new (iface, capabilities);
+
+		g_signal_connect_object (device, NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+		                         G_CALLBACK (p2p_device_created),
+		                         factory,
+		                         0);
+
+		return device;
 	}
 #if WITH_IWD
 	else if (!strcasecmp (backend, "iwd"))
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
new file mode 100644
index 00000000..4b524623
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -0,0 +1,796 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-p2p-peer.h"
+
+#include <stdlib.h>
+
+#include "nm-setting-wireless.h"
+
+#include "nm-wifi-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
+#include "devices/nm-device.h"
+#include "nm-dbus-manager.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMWifiP2PPeer,
+	PROP_NAME,
+	PROP_MANUFACTURER,
+	PROP_MODEL,
+	PROP_MODEL_NUMBER,
+	PROP_SERIAL,
+	PROP_WFD_IES,
+	PROP_GROUPS,
+	PROP_HW_ADDRESS,
+	PROP_STRENGTH,
+	PROP_LAST_SEEN,
+	PROP_FLAGS,
+);
+
+struct _NMWifiP2PPeerPrivate {
+	char *supplicant_path;   /* D-Bus object path of this Peer from wpa_supplicant */
+
+	/* Scanned or cached values */
+	char *             name;
+	char *             manufacturer;
+	char *             model;
+	char *             model_number;
+	char *             serial;
+
+	char *             address;
+
+	GBytes *           wfd_ies;
+	char **            groups;
+
+	guint8             strength;
+
+	NM80211ApFlags     flags;      /* General flags */
+
+	/* Non-scanned attributes */
+	gint32             last_seen;    /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+};
+
+typedef struct _NMWifiP2PPeerPrivate NMWifiP2PPeerPrivate;
+
+struct _NMWifiP2PPeerClass {
+	NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMWifiP2PPeer, nm_wifi_p2p_peer, NM_TYPE_DBUS_OBJECT)
+
+#define NM_WIFI_P2P_PEER_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiP2PPeer, NM_IS_WIFI_P2P_PEER)
+
+/*****************************************************************************/
+
+const char **
+nm_wifi_p2p_peers_get_paths (const CList *peers_lst_head)
+{
+	NMWifiP2PPeer *peer;
+	const char **list;
+	const char *path;
+	gsize i, n;
+
+	n = c_list_length (peers_lst_head);
+	list = g_new (const char *, n + 1);
+
+	i = 0;
+	if (n > 0) {
+		c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+			nm_assert (i < n);
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (peer));
+			nm_assert (path);
+
+			list[i++] = path;
+		}
+		nm_assert (i <= n);
+	}
+	list[i] = NULL;
+	return list;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                         NMConnection *connection)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (connection, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_wifi_p2p_peer_check_compatible (peer, connection))
+			return peer;
+	}
+	return NULL;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_streq0 (path, nm_wifi_p2p_peer_get_supplicant_path (peer)))
+			return peer;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+	peer = (NMWifiP2PPeer *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                                        exported_path);
+	if (   !peer
+	    || !NM_IS_WIFI_P2P_PEER (peer)
+	    || peer->wifi_device != device)
+		return NULL;
+
+	return peer;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_wifi_p2p_peer_get_supplicant_path (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->supplicant_path;
+}
+
+const char *
+nm_wifi_p2p_peer_get_name (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->name;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_name (NMWifiP2PPeer *peer, const char *name)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (name, priv->name) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->name, g_free);
+	if (name)
+		priv->name = g_strdup (name);
+
+	_notify (peer, PROP_NAME);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_manufacturer (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->manufacturer;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_manufacturer (NMWifiP2PPeer *peer, const char *manufacturer)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (manufacturer, priv->manufacturer) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->manufacturer, g_free);
+	if (manufacturer)
+		priv->manufacturer = g_strdup (manufacturer);
+
+	_notify (peer, PROP_MANUFACTURER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model (NMWifiP2PPeer *peer, const char *model)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model, priv->model) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model, g_free);
+	if (model)
+		priv->model = g_strdup (model);
+
+	_notify (peer, PROP_MODEL);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model_number (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model_number;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model_number (NMWifiP2PPeer *peer, const char *model_number)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model_number, priv->model_number) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model_number, g_free);
+	if (model_number)
+		priv->model_number = g_strdup (model_number);
+
+	_notify (peer, PROP_MODEL_NUMBER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_serial (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->serial;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_serial (NMWifiP2PPeer *peer, const char *serial)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (serial, priv->serial) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->serial, g_free);
+	if (serial)
+		priv->serial = g_strdup (serial);
+
+	_notify (peer, PROP_SERIAL);
+	return TRUE;
+}
+
+GBytes *
+nm_wifi_p2p_peer_get_wfd_ies (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->wfd_ies;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_wfd_ies (NMWifiP2PPeer *peer, GBytes *wfd_ies)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (nm_gbytes_equal0 (priv->wfd_ies, wfd_ies))
+		return FALSE;
+
+	g_bytes_unref (priv->wfd_ies);
+	priv->wfd_ies = wfd_ies ? g_bytes_ref (wfd_ies) : NULL;
+
+	_notify (peer, PROP_WFD_IES);
+	return TRUE;
+}
+
+const char *const*
+nm_wifi_p2p_peer_get_groups (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return (const char * const*) NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->groups;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_groups (NMWifiP2PPeer *peer, const char** groups)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (groups != NULL, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (_nm_utils_strv_equal (priv->groups, (char **) groups))
+		return FALSE;
+
+	g_strfreev (priv->groups);
+	priv->groups = g_strdupv ((char**) groups);
+
+	_notify (peer, PROP_GROUPS);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_address (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->address;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_address_bin (NMWifiP2PPeer *peer, const guint8 addr[static ETH_ALEN])
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (   priv->address
+	    && nm_utils_hwaddr_matches (addr, ETH_ALEN, priv->address, -1))
+		return FALSE;
+
+	g_free (priv->address);
+	priv->address = nm_utils_hwaddr_ntoa (addr, ETH_ALEN);
+	_notify (peer, PROP_HW_ADDRESS);
+	return TRUE;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_address (NMWifiP2PPeer *peer, const char *addr)
+{
+	guint8 addr_buf[ETH_ALEN];
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (   !addr
+	    || !nm_utils_hwaddr_aton (addr, addr_buf, sizeof (addr_buf)))
+		g_return_val_if_reached (FALSE);
+
+	return nm_wifi_p2p_peer_set_address_bin (peer, addr_buf);
+}
+
+gint8
+nm_wifi_p2p_peer_get_strength (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), 0);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->strength;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_strength (NMWifiP2PPeer *peer, const gint8 strength)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->strength != strength) {
+		priv->strength = strength;
+		_notify (peer, PROP_STRENGTH);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+NM80211ApFlags
+nm_wifi_p2p_peer_get_flags (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->flags;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_last_seen (NMWifiP2PPeer *peer, gint32 last_seen)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->last_seen != last_seen) {
+		priv->last_seen = last_seen;
+		_notify (peer, PROP_LAST_SEEN);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+
+/*****************************************************************************/
+
+gboolean
+nm_wifi_p2p_peer_update_from_properties (NMWifiP2PPeer *peer,
+                                         const char *supplicant_path,
+                                         GVariant *properties)
+{
+	NMWifiP2PPeerPrivate *priv;
+	const guint8 *bytes;
+	GVariant *v;
+	gsize len;
+	const char *s;
+	const char **sv;
+	gint32 i32;
+	gboolean changed = FALSE;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (properties, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	g_object_freeze_notify (G_OBJECT (peer));
+
+	if (g_variant_lookup (properties, "level", "i", &i32))
+		changed |= nm_wifi_p2p_peer_set_strength (peer, nm_wifi_utils_level_to_quality (i32));
+
+	if (g_variant_lookup (properties, "DeviceName", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_name (peer, s);
+
+	if (g_variant_lookup (properties, "Manufacturer", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_manufacturer (peer, s);
+
+	if (g_variant_lookup (properties, "Model", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model (peer, s);
+
+	if (g_variant_lookup (properties, "ModelNumber", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model_number (peer, s);
+
+	if (g_variant_lookup (properties, "Serial", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_serial (peer, s);
+
+	v = g_variant_lookup_value (properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		if (   len == ETH_ALEN
+		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
+			changed |= nm_wifi_p2p_peer_set_address_bin (peer, bytes);
+		g_variant_unref (v);
+	}
+
+	/* The IEs property contains the WFD R1 subelements */
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		gs_unref_bytes GBytes *b = NULL;
+
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		b = g_bytes_new (bytes, len);
+		changed |= nm_wifi_p2p_peer_set_wfd_ies (peer, b);
+		g_variant_unref (v);
+	}
+
+	v = g_variant_lookup_value (properties, "Groups", G_VARIANT_TYPE_OBJECT_PATH_ARRAY);
+	if (v) {
+		sv = g_variant_get_objv (v, NULL);
+		changed |= nm_wifi_p2p_peer_set_groups (peer, sv);
+		g_free (sv);
+	}
+
+	/*if (max_rate)
+		changed |= nm_wifi_p2p_peer_set_max_bitrate (peer, max_rate / 1000);*/
+
+	if (!priv->supplicant_path) {
+		priv->supplicant_path = g_strdup (supplicant_path);
+		changed = TRUE;
+	}
+
+	changed |= nm_wifi_p2p_peer_set_last_seen (peer, nm_utils_get_monotonic_timestamp_s ());
+
+	g_object_thaw_notify (G_OBJECT (peer));
+
+	return changed;
+}
+
+const char *
+nm_wifi_p2p_peer_to_string (const NMWifiP2PPeer *self,
+                            char *str_buf,
+                            gsize buf_len,
+                            gint32 now_s)
+{
+	const NMWifiP2PPeerPrivate *priv;
+	const char *supplicant_id = "-";
+	const char* export_path;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), NULL);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	if (priv->supplicant_path)
+		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
+
+	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
+	if (export_path)
+		export_path = strrchr (export_path, '/') ?: export_path;
+	else
+		export_path = "/";
+
+	g_snprintf (str_buf, buf_len,
+	            "%17s [n:%s, m:%s, mod:%s, mod_num:%s, ser:%s] %3us sup:%s [nm:%s]",
+	            priv->address ?: "(none)",
+	            priv->name,
+	            priv->manufacturer,
+	            priv->model,
+	            priv->model_number,
+	            priv->serial,
+	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
+	            supplicant_id,
+	            export_path);
+
+	return str_buf;
+}
+
+gboolean
+nm_wifi_p2p_peer_check_compatible (NMWifiP2PPeer *self,
+                                   NMConnection *connection)
+{
+	NMWifiP2PPeerPrivate *priv;
+	NMSettingWifiP2P *s_wifi_p2p;
+	const char *hwaddr;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	if (s_wifi_p2p == NULL)
+		return FALSE;
+
+	hwaddr = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+	if (   hwaddr
+	    && (   !priv->address
+	        || !nm_utils_hwaddr_matches (hwaddr, -1, priv->address, -1)))
+		return FALSE;
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_FLAGS:
+		g_value_set_uint (value, priv->flags);
+		break;
+	case PROP_NAME:
+		g_value_set_string (value, priv->name);
+		break;
+	case PROP_MANUFACTURER:
+		g_value_set_string (value, priv->manufacturer);
+		break;
+	case PROP_MODEL:
+		g_value_set_string (value, priv->model);
+		break;
+	case PROP_MODEL_NUMBER:
+		g_value_set_string (value, priv->model_number);
+		break;
+	case PROP_SERIAL:
+		g_value_set_string (value, priv->serial);
+		break;
+	case PROP_WFD_IES:
+		g_value_take_variant (value, nm_utils_gbytes_to_variant_ay (priv->wfd_ies));
+		break;
+	case PROP_GROUPS:
+		g_value_set_variant (value,
+		                      g_variant_new_strv (   (const char*const*) priv->groups
+		                                          ?: NM_PTRARRAY_EMPTY (const char *),
+		                                          -1));
+		break;
+	case PROP_HW_ADDRESS:
+		g_value_set_string (value, priv->address);
+		break;
+	case PROP_STRENGTH:
+		g_value_set_uchar (value, priv->strength);
+		break;
+	case PROP_LAST_SEEN:
+		g_value_set_int (value,
+		                 priv->last_seen > 0
+		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NS_PER_SECOND)
+		                     : -1);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_wifi_p2p_peer_init (NMWifiP2PPeer *self)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->peers_lst);
+
+	priv->flags = NM_802_11_AP_FLAGS_NONE;
+	priv->last_seen = -1;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_new_from_properties (const char *supplicant_path, GVariant *properties)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (supplicant_path != NULL, NULL);
+	g_return_val_if_fail (properties != NULL, NULL);
+
+	peer = (NMWifiP2PPeer *) g_object_new (NM_TYPE_WIFI_P2P_PEER, NULL);
+	nm_wifi_p2p_peer_update_from_properties (peer, supplicant_path, properties);
+
+	/* ignore peers with invalid or missing address */
+	if (!nm_wifi_p2p_peer_get_address (peer)) {
+		g_object_unref (peer);
+		return NULL;
+	}
+
+	return peer;
+}
+
+static void
+finalize (GObject *object)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	nm_assert (!self->wifi_device);
+	nm_assert (c_list_is_empty (&self->peers_lst));
+
+	g_free (priv->supplicant_path);
+	g_free (priv->name);
+	g_free (priv->manufacturer);
+	g_free (priv->model);
+	g_free (priv->model_number);
+	g_free (priv->serial);
+	g_free (priv->address);
+	g_bytes_unref (priv->wfd_ies);
+	g_strfreev (priv->groups);
+
+	G_OBJECT_CLASS (nm_wifi_p2p_peer_parent_class)->finalize (object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_WIFI_P2P_PEER,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Flags",        "u",  NM_WIFI_P2P_PEER_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Name",         "s",  NM_WIFI_P2P_PEER_NAME),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Manufacturer", "s",  NM_WIFI_P2P_PEER_MANUFACTURER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Model",        "s",  NM_WIFI_P2P_PEER_MODEL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ModelNumber",  "s",  NM_WIFI_P2P_PEER_MODEL_NUMBER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Serial",       "s",  NM_WIFI_P2P_PEER_SERIAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("WfdIEs",       "ay", NM_WIFI_P2P_PEER_WFD_IES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Groups",       "as", NM_WIFI_P2P_PEER_GROUPS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",    "s",  NM_WIFI_P2P_PEER_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Strength",     "y",  NM_WIFI_P2P_PEER_STRENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("LastSeen",     "i",  NM_WIFI_P2P_PEER_LAST_SEEN),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+static void
+nm_wifi_p2p_peer_class_init (NMWifiP2PPeerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMWifiP2PPeerPrivate));
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_WIFI_P2P_PEER);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_p2p_peer);
+
+	object_class->get_property = get_property;
+	object_class->finalize     = finalize;
+
+	obj_properties[PROP_FLAGS] =
+	    g_param_spec_uint (NM_WIFI_P2P_PEER_FLAGS, "", "",
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       NM_802_11_AP_FLAGS_PRIVACY,
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_NAME] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_NAME, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MANUFACTURER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MANUFACTURER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL_NUMBER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL_NUMBER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SERIAL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_SERIAL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_WFD_IES] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_WFD_IES, "", "",
+	                          G_VARIANT_TYPE ("ay"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_GROUPS] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_GROUPS, "", "",
+	                          G_VARIANT_TYPE ("as"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_HW_ADDRESS] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_HW_ADDRESS, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_STRENGTH] =
+	    g_param_spec_uchar (NM_WIFI_P2P_PEER_STRENGTH, "", "",
+	                        0, G_MAXINT8, 0,
+	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_LAST_SEEN] =
+	    g_param_spec_int (NM_WIFI_P2P_PEER_LAST_SEEN, "", "",
+	                      -1, G_MAXINT, -1,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/devices/wifi/nm-wifi-p2p-peer.h
new file mode 100644
index 00000000..d6ff7abc
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.h
@@ -0,0 +1,114 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_WIFI_P2P_PEER_H__
+#define __NM_WIFI_P2P_PEER_H__
+
+#include "nm-dbus-object.h"
+#include "nm-dbus-interface.h"
+#include "nm-connection.h"
+
+#define NM_TYPE_WIFI_P2P_PEER            (nm_wifi_p2p_peer_get_type ())
+#define NM_WIFI_P2P_PEER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer))
+#define NM_WIFI_P2P_PEER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+#define NM_IS_WIFI_P2P_PEER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_P2P_PEER))
+#define NM_IS_WIFI_P2P_PEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_P2P_PEER))
+#define NM_WIFI_P2P_PEER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+
+#define NM_WIFI_P2P_PEER_FLAGS                "flags"
+#define NM_WIFI_P2P_PEER_NAME                 "name"
+#define NM_WIFI_P2P_PEER_MANUFACTURER         "manufacturer"
+#define NM_WIFI_P2P_PEER_MODEL                "model"
+#define NM_WIFI_P2P_PEER_MODEL_NUMBER         "model-number"
+#define NM_WIFI_P2P_PEER_SERIAL               "serial"
+#define NM_WIFI_P2P_PEER_WFD_IES              "wfd-ies"
+#define NM_WIFI_P2P_PEER_GROUPS               "groups"
+#define NM_WIFI_P2P_PEER_HW_ADDRESS           "hw-address"
+#define NM_WIFI_P2P_PEER_STRENGTH             "strength"
+#define NM_WIFI_P2P_PEER_LAST_SEEN            "last-seen"
+
+typedef struct {
+	NMDBusObject parent;
+	NMDevice *wifi_device;
+	CList peers_lst;
+	struct _NMWifiP2PPeerPrivate *_priv;
+} NMWifiP2PPeer;
+
+typedef struct _NMWifiP2PPeerClass NMWifiP2PPeerClass;
+
+GType nm_wifi_p2p_peer_get_type (void);
+
+NMWifiP2PPeer *   nm_wifi_p2p_peer_new_from_properties      (const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_update_from_properties   (NMWifiP2PPeer *peer,
+                                                             const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_check_compatible         (NMWifiP2PPeer *self,
+                                                             NMConnection *connection);
+
+const char *      nm_wifi_p2p_peer_get_supplicant_path      (NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_name                 (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_name                 (NMWifiP2PPeer *peer,
+                                                             const char *name);
+const char *      nm_wifi_p2p_peer_get_manufacturer         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_manufacturer         (NMWifiP2PPeer *peer,
+                                                             const char *manufacturer);
+const char *      nm_wifi_p2p_peer_get_model                (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model                (NMWifiP2PPeer *peer,
+                                                             const char *model);
+const char *      nm_wifi_p2p_peer_get_model_number         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model_number         (NMWifiP2PPeer *peer,
+                                                             const char *number);
+const char *      nm_wifi_p2p_peer_get_serial               (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_serial               (NMWifiP2PPeer *peer,
+                                                             const char *serial);
+
+GBytes *          nm_wifi_p2p_peer_get_wfd_ies             (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_wfd_ies             (NMWifiP2PPeer *peer,
+                                                            GBytes *bytes);
+
+const char *const*nm_wifi_p2p_peer_get_groups              (const NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_address              (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_address              (NMWifiP2PPeer *peer,
+                                                             const char *addr);
+gint8             nm_wifi_p2p_peer_get_strength             (NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_strength             (NMWifiP2PPeer *peer,
+                                                             gint8 strength);
+NM80211ApFlags    nm_wifi_p2p_peer_get_flags                (const NMWifiP2PPeer *self);
+
+const char       *nm_wifi_p2p_peer_to_string                (const NMWifiP2PPeer *self,
+                                                             char *str_buf,
+                                                             gsize buf_len,
+                                                             gint32 now_s);
+
+const char      **nm_wifi_p2p_peers_get_paths               (const CList *peers_lst_head);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                                           NMConnection *connection);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path);
+
+#endif /* __NM_WIFI_P2P_PEER_H__ */
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 0f7836be..c6e8b3e0 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -22,7 +22,6 @@
 
 #include "nm-wifi-utils.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-utils.h"
@@ -739,6 +738,12 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		 * setting.  Since there's so much configuration required for it, there's
 		 * no way it can be automatically completed.
 		 */
+	} else if (   (key_mgmt && !strcmp (key_mgmt, "sae"))
+	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
+		g_object_set (s_wsec,
+		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+		              NULL);
 	} else if (   (key_mgmt && !strcmp (key_mgmt, "wpa-psk"))
 	           || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
 	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
index ee60f349..895853c4 100644
--- a/src/devices/wifi/tests/meson.build
+++ b/src/devices/wifi/tests/meson.build
@@ -10,4 +10,5 @@ test(
   'devices/wifi/' + test_unit,
   test_script,
   args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index f752bbfc..e0899837 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -20,10 +20,7 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "devices/wifi/nm-wifi-utils.h"
-
 #include "nm-core-internal.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index bd9ee3bb..1e316280 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -22,9 +22,8 @@
 
 #include "nm-device-modem.h"
 
-#include <string.h>
-
 #include "nm-modem.h"
+#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "nm-rfkill-manager.h"
 #include "settings/nm-settings-connection.h"
@@ -85,9 +84,9 @@ ppp_failed (NMModem *modem,
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
-			nm_device_activate_schedule_ip4_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET);
 		else if (nm_device_activate_ip6_state_in_conf (device))
-			nm_device_activate_schedule_ip6_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET6);
 		else if (nm_device_activate_ip4_state_done (device)) {
 			nm_device_ip_method_failed (device,
 			                            AF_INET,
@@ -213,8 +212,8 @@ modem_ip4_config_result (NMModem *modem,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else {
-		nm_device_set_wwan_ip4_config (device, config);
-		nm_device_activate_schedule_ip4_config_result (device, NULL);
+		nm_device_set_dev2_ip_config (device, AF_INET, NM_IP_CONFIG_CAST (config));
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NULL);
 	}
 }
 
@@ -229,7 +228,7 @@ modem_ip6_config_result (NMModem *modem,
 	NMDevice *device = NM_DEVICE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP6Config *ignored = NULL;
+	gs_unref_object NMIP6Config *ignored = NULL;
 	gboolean got_config = !!config;
 
 	g_return_if_fail (nm_device_activate_ip6_state_in_conf (device) == TRUE);
@@ -247,11 +246,11 @@ modem_ip6_config_result (NMModem *modem,
 	nm_device_sysctl_ip_conf_set (device, AF_INET6, "disable_ipv6", "0");
 
 	if (config)
-		nm_device_set_wwan_ip6_config (device, config);
+		nm_device_set_dev2_ip_config (device, AF_INET6, NM_IP_CONFIG_CAST (config));
 
 	if (do_slaac == FALSE) {
 		if (got_config)
-			nm_device_activate_schedule_ip6_config_result (device);
+			nm_device_activate_schedule_ip_config_result (device, AF_INET6, NULL);
 		else {
 			_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: SLAAC not requested and no addresses");
 			nm_device_ip_method_failed (device,
@@ -262,15 +261,17 @@ modem_ip6_config_result (NMModem *modem,
 	}
 
 	/* Start SLAAC now that we have a link-local address from the modem */
-	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage3_ip6_config_start (device, &ignored, &failure_reason);
-	g_assert (ignored == NULL);
+	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage3_ip_config_start (device, AF_INET6, (gpointer *) &ignored, &failure_reason);
+
+	nm_assert (ignored == NULL);
+
 	switch (ret) {
 	case NM_ACT_STAGE_RETURN_FAILURE:
 		nm_device_ip_method_failed (device, AF_INET6, failure_reason);
 		break;
 	case NM_ACT_STAGE_RETURN_IP_FAIL:
 		/* all done */
-		nm_device_activate_schedule_ip6_config_result (device);
+		nm_device_activate_schedule_ip_config_result (device, AF_INET6, NULL);
 		break;
 	case NM_ACT_STAGE_RETURN_POSTPONE:
 		/* let SLAAC run */
@@ -279,7 +280,7 @@ modem_ip6_config_result (NMModem *modem,
 		/* Should never get here since we've assured that the IPv6 method
 		 * will either be "auto" or "ignored" when starting IPv6 configuration.
 		 */
-		g_assert_not_reached ();
+		nm_assert_not_reached ();
 	}
 }
 
@@ -570,14 +571,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
-	return nm_modem_stage3_ip4_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         device,
-	                                         NM_DEVICE_CLASS (nm_device_modem_parent_class),
-	                                         out_failure_reason);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
+
+	nm_assert_addr_family (addr_family);
+
+	if (addr_family == AF_INET) {
+		return nm_modem_stage3_ip4_config_start (priv->modem,
+		                                         device,
+		                                         NM_DEVICE_CLASS (nm_device_modem_parent_class),
+		                                         out_failure_reason);
+	} else {
+		return nm_modem_stage3_ip6_config_start (priv->modem,
+		                                         device,
+		                                         out_failure_reason);
+	}
 }
 
 static void
@@ -586,16 +598,6 @@ ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 	nm_modem_ip4_pre_commit (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, device, config);
 }
 
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return nm_modem_stage3_ip6_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         device,
-	                                         out_failure_reason);
-}
-
 static gboolean
 get_ip_iface_identifier (NMDevice *device, NMUtilsIPv6IfaceId *out_iid)
 {
@@ -825,8 +827,7 @@ nm_device_modem_class_init (NMDeviceModemClass *klass)
 	device_class->deactivate = deactivate;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->ip4_config_pre_commit = ip4_config_pre_commit;
 	device_class->get_enabled = get_enabled;
 	device_class->set_enabled = set_enabled;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 82e9e2f1..1cb549b0 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -22,7 +22,6 @@
 
 #include "nm-modem-broadband.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <libmm-glib.h>
 
@@ -254,16 +253,21 @@ get_bearer_ip_method (MMBearerIpConfig *config)
 static MMSimpleConnectProperties *
 create_cdma_connect_properties (NMConnection *connection)
 {
-	NMSettingCdma *setting;
 	MMSimpleConnectProperties *properties;
-	const char *str;
 
-	setting = nm_connection_get_setting_cdma (connection);
 	properties = mm_simple_connect_properties_new ();
 
-	str = nm_setting_cdma_get_number (setting);
-	if (str)
-		mm_simple_connect_properties_set_number (properties, str);
+#if !MM_CHECK_VERSION (1, 9, 1)
+	{
+		NMSettingCdma *setting;
+		const char *str;
+
+		setting = nm_connection_get_setting_cdma (connection);
+		str = nm_setting_cdma_get_number (setting);
+		if (str)
+			mm_simple_connect_properties_set_number (properties, str);
+	}
+#endif
 
 	return properties;
 }
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index dfc102f3..fac14d69 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -24,7 +24,6 @@
 
 #include "nm-modem-manager.h"
 
-#include <string.h>
 #include <libmm-glib.h>
 
 #if HAVE_LIBSYSTEMD
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 8efe0253..78d9a9f0 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -22,8 +22,6 @@
 
 #include "nm-modem-ofono.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 #include "devices/nm-device-private.h"
 #include "nm-modem.h"
@@ -214,7 +212,6 @@ disconnect (NMModem *modem,
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 	DisconnectContext *ctx;
 	NMModemState state = nm_modem_get_state (NM_MODEM (self));
-	gs_free_error GError *error = NULL;
 
 	_LOGD ("warn: %s modem_state: %s",
 	       warn ? "TRUE" : "FALSE",
@@ -654,7 +651,7 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 		                          OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
 		                          priv->connman_proxy_cancellable,
 		                          _connman_proxy_new_cb,
-		                          NULL);
+		                          self);
 	}
 }
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 11ada549..2217f2a2 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -24,7 +24,6 @@
 #include "nm-modem.h"
 
 #include <fcntl.h>
-#include <string.h>
 #include <termios.h>
 #include <linux/rtnetlink.h>
 
@@ -729,7 +728,7 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
 		_LOGD ("MODEM_IP_METHOD_AUTO");
-		ret = device_class->act_stage3_ip4_config_start (device, NULL, out_failure_reason);
+		ret = device_class->act_stage3_ip_config_start (device, AF_INET, NULL, out_failure_reason);
 		break;
 	default:
 		_LOGI ("IPv4 configuration disabled");
@@ -948,7 +947,7 @@ nm_modem_get_secrets (NMModem *self,
 	                                               FALSE,
 	                                               setting_name,
 	                                               flags,
-	                                               hint,
+	                                               NM_MAKE_STRV (hint),
 	                                               modem_secrets_cb,
 	                                               self);
 	g_return_if_fail (priv->secrets_id);
@@ -986,8 +985,7 @@ nm_modem_act_stage1_prepare (NMModem *self,
 
 	setting_name = nm_connection_need_secrets (connection, &hints);
 	if (!setting_name) {
-		/* Ready to connect */
-		g_assert (!hints);
+		nm_assert (!hints);
 		return NM_MODEM_GET_CLASS (self)->act_stage1_prepare (self, connection, out_failure_reason);
 	}
 
@@ -995,11 +993,14 @@ nm_modem_act_stage1_prepare (NMModem *self,
 	if (priv->secrets_tries++)
 		flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
 	priv->secrets_id = nm_act_request_get_secrets (req,
 	                                               FALSE,
 	                                               setting_name,
 	                                               flags,
-	                                               hints ? g_ptr_array_index (hints, 0) : NULL,
+	                                               hints ? (const char *const*) hints->pdata : NULL,
 	                                               modem_secrets_cb,
 	                                               self);
 	g_return_val_if_fail (priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE);
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index a561b58a..c9ee27ff 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 
 #include "devices/nm-device-factory.h"
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 7ed7a686..a4fccce0 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -21,10 +21,8 @@
 
 #include "nm-dhcp-client.h"
 
-#include <string.h>
 #include <sys/types.h>
 #include <sys/wait.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -627,7 +625,7 @@ out:
 		int errsv = errno;
 
 		nm_log_dbg (LOGD_DHCP, "dhcp: could not remove pid file \"%s\": %s (%d)",
-		            pid_file, g_strerror (errsv), errsv);
+		            pid_file, nm_strerror_native (errsv), errsv);
 	}
 }
 
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index d6da3f5c..cbd706fa 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -21,7 +21,6 @@
 
 #include "nm-dhcp-dhclient-utils.h"
 
-#include <string.h>
 #include <ctype.h>
 #include <arpa/inet.h>
 #include <net/if.h>
@@ -495,7 +494,7 @@ nm_dhcp_dhclient_escape_duid (GBytes *duid)
 	return escaped;
 }
 
-static inline gboolean
+static gboolean
 isoctal (const guint8 *p)
 {
 	return (   p[0] >= '0' && p[0] <= '3'
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 0146c8b4..af702cb4 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -29,9 +29,7 @@
 
 #if WITH_DHCLIENT
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <netinet/in.h>
@@ -593,17 +591,19 @@ stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	int errsv;
 
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release);
 
 	if (priv->conf_file)
-		if (remove (priv->conf_file) == -1)
-			_LOGD ("could not remove dhcp config file \"%s\": %d (%s)", priv->conf_file, errno, g_strerror (errno));
+		if (remove (priv->conf_file) == -1) {
+			errsv = errno;
+			_LOGD ("could not remove dhcp config file \"%s\": %d (%s)", priv->conf_file, errsv, nm_strerror_native (errsv));
+		}
 	if (priv->pid_file) {
 		if (remove (priv->pid_file) == -1) {
-			int errsv = errno;
-
-			_LOGD ("could not remove dhcp pid file \"%s\": %s (%d)", priv->pid_file, g_strerror (errsv), errsv);
+			errsv = errno;
+			_LOGD ("could not remove dhcp pid file \"%s\": %s (%d)", priv->pid_file, nm_strerror_native (errsv), errsv);
 		}
 		nm_clear_g_free (&priv->pid_file);
 	}
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 0f033e22..868cc9dd 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -22,9 +22,7 @@
 
 #if WITH_DHCPCANON
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 
 #include "nm-utils.h"
@@ -205,12 +203,15 @@ stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+	int errsv;
 
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
-		if (remove (priv->pid_file) == -1)
-			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		if (remove (priv->pid_file) == -1) {
+			errsv = errno;
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errsv, nm_strerror_native (errsv));
+		}
 		g_free (priv->pid_file);
 		priv->pid_file = NULL;
 	}
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index e2a1354f..2a7482b1 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -24,9 +24,7 @@
 
 #if WITH_DHCPCD
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <netinet/in.h>
@@ -199,12 +197,15 @@ stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
 	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
+	int errsv;
 
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
-		if (remove (priv->pid_file) == -1)
-			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		if (remove (priv->pid_file) == -1) {
+			errsv = errno;
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errsv, nm_strerror_native (errsv));
+		}
 	}
 
 	/* FIXME: implement release... */
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 83cc4600..8f753a61 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -22,7 +22,6 @@
 
 #include <unistd.h>
 #include <stdlib.h>
-#include <string.h>
 #include <signal.h>
 
 #include "nm-utils/nm-vpn-plugin-macros.h"
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index 1770ead3..049c4e55 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -24,9 +24,7 @@
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 
 #include "nm-dhcp-helper-api.h"
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index c13c3043..7063c82c 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -27,9 +27,7 @@
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <stdio.h>
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index bcbe916f..70ed8715 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -18,9 +18,7 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <netinet/in.h>
@@ -269,28 +267,35 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	gint64 ts_time = time (NULL);
 	struct in_addr a_address;
 	struct in_addr a_netmask;
-	struct in_addr a_router;
+	const struct in_addr *a_router;
 	guint32 a_plen;
 	guint32 a_lifetime;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
-	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
-
-	options = out_options ? create_options_dict () : NULL;
-
 	if (sd_dhcp_lease_get_address (lease, &a_address) < 0) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get address from lease");
 		return NULL;
 	}
-	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
-	LOG_LEASE (LOGD_DHCP4, "address %s", addr_str);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
 
 	if (sd_dhcp_lease_get_netmask (lease, &a_netmask) < 0) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease");
 		return NULL;
 	}
+
+	if (sd_dhcp_lease_get_lifetime (lease, &a_lifetime) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get lifetime from lease");
+		return NULL;
+	}
+
+	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
+
+	options = out_options ? create_options_dict () : NULL;
+
+	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
+	LOG_LEASE (LOGD_DHCP4, "address %s", addr_str);
+	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
+
 	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
 	LOG_LEASE (LOGD_DHCP4, "plen %u", (guint) a_plen);
 	add_option (options,
@@ -298,10 +303,6 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	            SD_DHCP_OPTION_SUBNET_MASK,
 	            nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
 
-	if (sd_dhcp_lease_get_lifetime (lease, &a_lifetime) < 0) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get lifetime from lease");
-		return NULL;
-	}
 	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds (at %lld)",
 	           (guint) a_lifetime,
 	           (long long) (ts_time + a_lifetime));
@@ -325,41 +326,44 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			if (addr_list[i].s_addr) {
-				nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
-				s = nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
-				LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", s);
-				g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
+			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
+
+			if (   addr_list[i].s_addr == 0
+			    || nm_ip4_addr_is_localhost (addr_list[i].s_addr)) {
+				/* Skip localhost addresses, like also networkd does.
+				 * See https://github.com/systemd/systemd/issues/4524. */
+				continue;
 			}
+			nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
 		}
-		if (str->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
 	}
 
 	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
+			g_string_append (nm_gstring_add_space_delimiter (str), search_domains[i]);
 			nm_ip4_config_add_search (ip4_config, search_domains[i]);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", search_domains[i]);
-			LOG_LEASE (LOGD_DHCP4, "domain search '%s'", search_domains[i]);
 		}
+		LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
 	}
 
-	if (   sd_dhcp_lease_get_domainname (lease, &s) >= 0
-	    && s) {
+	if (sd_dhcp_lease_get_domainname (lease, &s) >= 0) {
 		gs_strfreev char **domains = NULL;
 		char **d;
 
+		LOG_LEASE (LOGD_DHCP4, "domain name '%s'", s);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
+
 		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
 		 * As systemd escapes such characters, split them at \\032. */
 		domains = g_strsplit (s, "\\032", 0);
-		for (d = domains; *d; d++) {
-			LOG_LEASE (LOGD_DHCP4, "domain name '%s'", *d);
+		for (d = domains; *d; d++)
 			nm_ip4_config_add_domain (ip4_config, *d);
-		}
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
 	}
 
 	if (sd_dhcp_lease_get_hostname (lease, &s) >= 0) {
@@ -479,45 +483,66 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			add_option (options, dhcp4_requests, SD_DHCP_OPTION_STATIC_ROUTE, str_static->str);
 	}
 
-	/* FIXME: internal client only supports returing the first router. */
-	if (sd_dhcp_lease_get_router (lease, &a_router) >= 0) {
-		s = nm_utils_inet4_ntop (a_router.s_addr, addr_str);
-		LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
-
-		/* If the DHCP server returns both a Classless Static Routes option and a
-		 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
-		 *
-		 * Be more lenient and ignore the Router option only if Classless Static
-		 * Routes contain a default gateway (as other DHCP backends do).
-		 */
-		if (!has_router_from_classless) {
+	num = sd_dhcp_lease_get_router (lease, &a_router);
+	if (num > 0) {
+		guint32 default_route_metric = route_metric;
+
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			guint32 m;
+
+			s = nm_utils_inet4_ntop (a_router[i].s_addr, addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), s);
+
+			if (a_router[i].s_addr == 0) {
+				/* silently skip 0.0.0.0 */
+				continue;
+			}
+
+			if (has_router_from_classless) {
+				/* If the DHCP server returns both a Classless Static Routes option and a
+				 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
+				 *
+				 * Be more lenient and ignore the Router option only if Classless Static
+				 * Routes contain a default gateway (as other DHCP backends do).
+				 */
+				continue;
+			}
+
+			/* if there are multiple default routes, we add them with differing
+			 * metrics. */
+			m = default_route_metric;
+			if (default_route_metric < G_MAXUINT32)
+				default_route_metric++;
+
 			nm_ip4_config_add_route (ip4_config,
 			                         &((const NMPlatformIP4Route) {
 			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
-			                             .gateway       = a_router.s_addr,
+			                             .gateway       = a_router[i].s_addr,
 			                             .table_coerced = nm_platform_route_table_coerce (route_table),
-			                             .metric        = route_metric,
+			                             .metric        = m,
 			                         }),
 			                         NULL);
 		}
+		LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str->str);
 	}
 
 	if (   sd_dhcp_lease_get_mtu (lease, &mtu) >= 0
 	    && mtu) {
-		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
-		add_option_u64 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
 		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
+		add_option_u64 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
 	}
 
 	num = sd_dhcp_lease_get_ntp (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			s = nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
-			LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", s);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
+			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 		}
+		LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
 	}
 
@@ -826,6 +851,7 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 	sd_dhcp6_lease_reset_address_iter (lease);
 	nm_gstring_prepare (&str);
 	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
+		char sbuf[400];
 		const NMPlatformIP6Address address = {
 			.plen        = 128,
 			.address     = tmp_addr,
@@ -838,15 +864,12 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 		nm_ip6_config_add_address (ip6_config, &address);
 
 		nm_utils_inet6_ntop (&tmp_addr, addr_str);
-		if (str->len)
-			g_string_append_c (str, ' ');
-		g_string_append (str, addr_str);
+		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 
 		LOG_LEASE (LOGD_DHCP6,
 		           "address %s",
-		           nm_platform_ip6_address_to_string (&address, NULL, 0));
+		           nm_platform_ip6_address_to_string (&address, sbuf, sizeof (sbuf)));
 	};
-
 	if (str->len)
 		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
 
@@ -863,13 +886,11 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 			nm_utils_inet6_ntop (&dns[i], addr_str);
-			if (str->len)
-				g_string_append_c (str, ' ');
-			g_string_append (str, addr_str);
-			LOG_LEASE (LOGD_DHCP6, "nameserver %s", addr_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
+			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 		}
+		LOG_LEASE (LOGD_DHCP6, "nameserver %s", str->str);
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
 	}
 
@@ -877,10 +898,10 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
+			g_string_append (nm_gstring_add_space_delimiter (str), domains[i]);
 			nm_ip6_config_add_search (ip6_config, domains[i]);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", domains[i]);
-			LOG_LEASE (LOGD_DHCP6, "domain name '%s'", domains[i]);
 		}
+		LOG_LEASE (LOGD_DHCP6, "domain name '%s'", str->str);
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
 	}
 
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 768f9fd7..5227eea7 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -19,8 +19,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 
@@ -546,7 +544,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 
 		errno = 0;
 		int_mtu = strtol (str, NULL, 10);
-		if ((errno == EINVAL) || (errno == ERANGE))
+		if (NM_IN_SET (errno, EINVAL, ERANGE))
 			goto error;
 
 		if (int_mtu > 576)
@@ -732,7 +730,7 @@ nm_dhcp_utils_duid_to_string (GBytes *duid)
 	g_return_val_if_fail (duid, NULL);
 
 	data = g_bytes_get_data (duid, &len);
-	return _nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
+	return nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
 }
 
 /**
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
index d2de4dc4..43b33951 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/dhcp/tests/meson.build
@@ -14,5 +14,6 @@ foreach test_unit: test_units
     'dhcp/' + test_unit,
     test_script,
     args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index ab1f5551..55d712b0 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <linux/rtnetlink.h>
@@ -803,7 +802,7 @@ test_write_duid (void)
 static void
 test_write_existing_duid (void)
 {
-	const guint8 duid[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
+	const guint8 duid[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302 };
 	const char *original_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
 	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n";
 	GError *error = NULL;
@@ -825,7 +824,7 @@ test_write_existing_duid (void)
 	g_assert_cmpstr (expected_contents, ==, contents);
 }
 
-static const guint8 DUID_BIN[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
+static const guint8 DUID_BIN[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '"', 0372, 0214, 0326, 0302 };
 #define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
 
 static void
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index 617a3c6c..240d868c 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -21,7 +21,6 @@
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <string.h>
 #include <linux/rtnetlink.h>
 
 #include "nm-utils/nm-dedup-multi.h"
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 2a30a540..c7c561c4 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -23,7 +23,6 @@
 
 #include "nm-default.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <resolv.h>
 #include <stdlib.h>
@@ -536,6 +535,7 @@ dispatch_netconfig (NMDnsManager *self,
 {
 	GPid pid;
 	int fd;
+	int errsv;
 	int status;
 	gssize l;
 	nm_auto_free_gstring GString *str = NULL;
@@ -566,11 +566,10 @@ again:
 
 	/* Wait until the process exits */
 	if (!nm_utils_kill_child_sync (pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) {
-		int errsv = errno;
-
+		errsv = errno;
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
 		             "Error waiting for netconfig to exit: %s",
-		             strerror (errsv));
+		             nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 	if (!WIFEXITED (status) || WEXITSTATUS (status) != EXIT_SUCCESS) {
@@ -678,7 +677,7 @@ write_resolv_conf_contents (FILE *f,
 		             NM_MANAGER_ERROR,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not write " _PATH_RESCONF ": %s",
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		errno = errsv;
 		return FALSE;
 	}
@@ -709,7 +708,8 @@ dispatch_resolvconf (NMDnsManager *self,
 	gs_free char *cmd = NULL;
 	FILE *f;
 	gboolean success = FALSE;
-	int errnosv, err;
+	int errsv;
+	int err;
 	char *argv[] = { RESOLVCONF_PATH, "-d", "NetworkManager", NULL };
 	int status;
 
@@ -743,12 +743,13 @@ dispatch_resolvconf (NMDnsManager *self,
 
 	cmd = g_strconcat (RESOLVCONF_PATH, " -a ", "NetworkManager", NULL);
 	if ((f = popen (cmd, "w")) == NULL) {
+		errsv = errno;
 		g_set_error (error,
 		             NM_MANAGER_ERROR,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not write to %s: %s",
 		             RESOLVCONF_PATH,
-		             g_strerror (errno));
+		             nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -759,10 +760,10 @@ dispatch_resolvconf (NMDnsManager *self,
 	                             error);
 	err = pclose (f);
 	if (err < 0) {
-		errnosv = errno;
+		errsv = errno;
 		g_clear_error (error);
-		g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errnosv),
-		             "Failed to close pipe to resolvconf: %d", errnosv);
+		g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+		             "Failed to close pipe to resolvconf: %d", errsv);
 		return SR_ERROR;
 	} else if (err > 0) {
 		_LOGW ("resolvconf failed with status %d", err);
@@ -886,9 +887,9 @@ update_resolv_conf (NMDnsManager *self,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not open %s: %s",
 		             MY_RESOLV_CONF_TMP,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: open temporary file %s failed (%s)",
-		       MY_RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -896,7 +897,7 @@ update_resolv_conf (NMDnsManager *self,
 	if (!success) {
 		errsv = errno;
 		_LOGT ("update-resolv-conf: write temporary file %s failed (%s)",
-		       MY_RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF_TMP, nm_strerror_native (errsv));
 	}
 
 	if (fclose (f) < 0) {
@@ -910,9 +911,9 @@ update_resolv_conf (NMDnsManager *self,
 			             NM_MANAGER_ERROR_FAILED,
 			             "Could not close %s: %s",
 			             MY_RESOLV_CONF_TMP,
-			             g_strerror (errsv));
+			             nm_strerror_native (errsv));
 			_LOGT ("update-resolv-conf: close temporary file %s failed (%s)",
-			       MY_RESOLV_CONF_TMP, g_strerror (errsv));
+			       MY_RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		}
 		return SR_ERROR;
 	} else if (!success)
@@ -925,9 +926,9 @@ update_resolv_conf (NMDnsManager *self,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not replace %s: %s",
 		             MY_RESOLV_CONF,
-		             g_strerror (errno));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: failed to rename temporary file %s to %s (%s)",
-		       MY_RESOLV_CONF_TMP, MY_RESOLV_CONF, g_strerror (errsv));
+		       MY_RESOLV_CONF_TMP, MY_RESOLV_CONF, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -962,10 +963,10 @@ update_resolv_conf (NMDnsManager *self,
 		             NM_MANAGER_ERROR_FAILED,
 		             "Could not unlink %s: %s",
 		             RESOLV_CONF_TMP,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: write internal file %s succeeded "
 		       "but canot delete temporary file %s: %s",
-		       MY_RESOLV_CONF, RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF, RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -977,10 +978,10 @@ update_resolv_conf (NMDnsManager *self,
 		             "Could not create symlink %s pointing to %s: %s",
 		             RESOLV_CONF_TMP,
 		             MY_RESOLV_CONF,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: write internal file %s succeeded "
 		       "but failed to symlink %s: %s",
-		       MY_RESOLV_CONF, RESOLV_CONF_TMP, g_strerror (errsv));
+		       MY_RESOLV_CONF, RESOLV_CONF_TMP, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
@@ -992,10 +993,10 @@ update_resolv_conf (NMDnsManager *self,
 		             "Could not rename %s to %s: %s",
 		             RESOLV_CONF_TMP,
 		             _PATH_RESCONF,
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		_LOGT ("update-resolv-conf: write internal file %s succeeded "
 		       "but failed to rename temporary symlink %s to %s: %s",
-		       MY_RESOLV_CONF, RESOLV_CONF_TMP, _PATH_RESCONF, g_strerror (errsv));
+		       MY_RESOLV_CONF, RESOLV_CONF_TMP, _PATH_RESCONF, nm_strerror_native (errsv));
 		return SR_ERROR;
 	}
 
diff --git a/src/dns/nm-dns-plugin.c b/src/dns/nm-dns-plugin.c
index 48f04b00..ce814186 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/dns/nm-dns-plugin.c
@@ -21,7 +21,6 @@
 
 #include "nm-dns-plugin.h"
 
-#include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/types.h>
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 1afc6e0d..43bc66fc 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -25,7 +25,6 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/dnsmasq/nm-dnsmasq-utils.c
index 382b3aeb..ec5545d3 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.c
+++ b/src/dnsmasq/nm-dnsmasq-utils.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-dnsmasq-utils.h"
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index 70f23e17..b84543c4 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -92,6 +92,7 @@ main (int argc, char *argv[])
 	};
 	GOptionContext *option_context;
 	GError *error = NULL;
+	int errsv;
 
 	option_context = g_option_context_new ("-- [ip=...] [rd.route=...] [bridge=...] [bond=...] [team=...] [vlan=...] "
 	                                       "[bootdev=...] [nameserver=...] [rd.peerdns=...] [rd.bootif=...] [BOOTIF=...] ... ");
@@ -121,7 +122,8 @@ main (int argc, char *argv[])
 		g_clear_pointer (&connections_dir, g_free);
 
 	if (connections_dir && g_mkdir_with_parents (connections_dir, 0755) != 0) {
-		_LOGW (LOGD_CORE, "%s: %s", connections_dir, strerror (errno));
+		errsv = errno;
+		_LOGW (LOGD_CORE, "%s: %s", connections_dir, nm_strerror_native (errsv));
 		return 1;
 	}
 
diff --git a/src/initrd/nm-initrd-generator.h b/src/initrd/nm-initrd-generator.h
index 1fa858fc..dab6fb64 100644
--- a/src/initrd/nm-initrd-generator.h
+++ b/src/initrd/nm-initrd-generator.h
@@ -1,18 +1,19 @@
 /* NetworkManager initrd configuration generator
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2014, 2018 Red Hat, Inc.
  */
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index e812b086..b9c75c1b 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -19,12 +19,10 @@
  */
 
 #include "nm-default.h"
-#include "nm-core-internal.h"
 
+#include "nm-core-internal.h"
 #include "nm-initrd-generator.h"
 
-#include <string.h>
-
 /*****************************************************************************/
 
 #define _NMLOG(level, domain, ...) \
@@ -518,8 +516,8 @@ parse_rd_route (GHashTable *connections, char *argument)
 	const char *gateway;
 	const char *interface;
 	int family = AF_UNSPEC;
-	NMIPAddr net_addr = { 0, };
-	NMIPAddr gateway_addr = { 0, };
+	NMIPAddr net_addr = { };
+	NMIPAddr gateway_addr = { };
 	int net_prefix = -1;
 	NMIPRoute *route;
 	NMSettingIPConfig *s_ip;
diff --git a/src/initrd/nmi-ibft-reader.c b/src/initrd/nmi-ibft-reader.c
index c9275467..efac5307 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/initrd/nmi-ibft-reader.c
@@ -1,18 +1,19 @@
 /* NetworkManager initrd configuration generator
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
  *
  * Copyright 2014 - 2018 Red Hat, Inc.
  */
@@ -22,13 +23,11 @@
 #include "nm-initrd-generator.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <sys/wait.h>
 #include <sys/inotify.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 95084e92..1a87505a 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
diff --git a/src/initrd/tests/test-ibft-reader.c b/src/initrd/tests/test-ibft-reader.c
index 340b3896..64362d18 100644
--- a/src/initrd/tests/test-ibft-reader.c
+++ b/src/initrd/tests/test-ibft-reader.c
@@ -1,18 +1,19 @@
 /* NetworkManager initrd configuration generator
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
  *
- * This program is distributed in the hope that it will be useful,
+ * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
  *
  * Copyright 2014 - 2018 Red Hat, Inc.
  */
@@ -22,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
diff --git a/src/main-utils.c b/src/main-utils.c
index a597ede7..f3a2edf5 100644
--- a/src/main-utils.c
+++ b/src/main-utils.c
@@ -22,7 +22,6 @@
 #include "nm-default.h"
 
 #include <stdio.h>
-#include <errno.h>
 #include <fcntl.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -93,21 +92,26 @@ nm_main_utils_write_pidfile (const char *pidfile)
 {
 	char pid[16];
 	int fd;
+	int errsv;
 	gboolean success = FALSE;
 
 	if ((fd = open (pidfile, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 00644)) < 0) {
-		fprintf (stderr, _("Opening %s failed: %s\n"), pidfile, strerror (errno));
+		errsv = errno;
+		fprintf (stderr, _("Opening %s failed: %s\n"), pidfile, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
 	g_snprintf (pid, sizeof (pid), "%d", getpid ());
-	if (write (fd, pid, strlen (pid)) < 0)
-		fprintf (stderr, _("Writing to %s failed: %s\n"), pidfile, strerror (errno));
-	else
+	if (write (fd, pid, strlen (pid)) < 0) {
+		errsv = errno;
+		fprintf (stderr, _("Writing to %s failed: %s\n"), pidfile, nm_strerror_native (errsv));
+	} else
 		success = TRUE;
 
-	if (nm_close (fd))
-		fprintf (stderr, _("Closing %s failed: %s\n"), pidfile, strerror (errno));
+	if (nm_close (fd)) {
+		errsv = errno;
+		fprintf (stderr, _("Closing %s failed: %s\n"), pidfile, nm_strerror_native (errsv));
+	}
 
 	return success;
 }
@@ -126,13 +130,13 @@ nm_main_utils_ensure_statedir ()
 	    && parent[1] != '\0'
 	    && g_mkdir_with_parents (parent, 0755) != 0) {
 		errsv = errno;
-		fprintf (stderr, "Cannot create parents for '%s': %s", NMSTATEDIR, g_strerror (errsv));
+		fprintf (stderr, "Cannot create parents for '%s': %s", NMSTATEDIR, nm_strerror_native (errsv));
 		exit (1);
 	}
 	/* Ensure state directory exists */
 	if (g_mkdir_with_parents (NMSTATEDIR, 0700) != 0) {
 		errsv = errno;
-		fprintf (stderr, "Cannot create '%s': %s", NMSTATEDIR, g_strerror (errsv));
+		fprintf (stderr, "Cannot create '%s': %s", NMSTATEDIR, nm_strerror_native (errsv));
 		exit (1);
 	}
 }
@@ -145,7 +149,7 @@ nm_main_utils_ensure_rundir ()
 	/* Setup runtime directory */
 	if (g_mkdir_with_parents (NMRUNDIR, 0755) != 0) {
 		errsv = errno;
-		fprintf (stderr, _("Cannot create '%s': %s"), NMRUNDIR, g_strerror (errsv));
+		fprintf (stderr, _("Cannot create '%s': %s"), NMRUNDIR, nm_strerror_native (errsv));
 		exit (1);
 	}
 
@@ -156,7 +160,7 @@ nm_main_utils_ensure_rundir ()
 	if (g_mkdir (NM_CONFIG_DEVICE_STATE_DIR, 0755) != 0) {
 		errsv = errno;
 		if (errsv != EEXIST) {
-			fprintf (stderr, _("Cannot create '%s': %s"), NM_CONFIG_DEVICE_STATE_DIR, g_strerror (errsv));
+			fprintf (stderr, _("Cannot create '%s': %s"), NM_CONFIG_DEVICE_STATE_DIR, nm_strerror_native (errsv));
 			exit (1);
 		}
 	}
diff --git a/src/main.c b/src/main.c
index 36356a7a..9f979cf4 100644
--- a/src/main.c
+++ b/src/main.c
@@ -23,15 +23,12 @@
 
 #include <getopt.h>
 #include <locale.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <signal.h>
-#include <pthread.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <string.h>
 #include <sys/resource.h>
 
 #include "main-utils.h"
@@ -233,6 +230,7 @@ main (int argc, char *argv[])
 	guint sd_id = 0;
 	GError *error_invalid_logging_config = NULL;
 	const char *const *warnings;
+	int errsv;
 
 	/* Known to cause a possible deadlock upon GDBus initialization:
 	 * https://bugzilla.gnome.org/show_bug.cgi?id=674885 */
@@ -334,12 +332,10 @@ main (int argc, char *argv[])
 
 	if (global_opt.become_daemon && !nm_config_get_is_debug (config)) {
 		if (daemon (0, 0) < 0) {
-			int saved_errno;
-
-			saved_errno = errno;
+			errsv = errno;
 			fprintf (stderr, _("Could not daemonize: %s [error %u]\n"),
-			         g_strerror (saved_errno),
-			         saved_errno);
+			         nm_strerror_native (errsv),
+			         errsv);
 			exit (1);
 		}
 		wrote_pidfile = nm_main_utils_write_pidfile (global_opt.pidfile);
@@ -355,7 +351,7 @@ main (int argc, char *argv[])
 		                              NM_CONFIG_KEYFILE_GROUP_LOGGING,
 		                              NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
 		                              NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-		nm_logging_syslog_openlog (v, nm_config_get_is_debug (config));
+		nm_logging_init (v, nm_config_get_is_debug (config));
 	}
 
 	nm_log_info (LOGD_CORE, "NetworkManager (version " NM_DIST_VERSION ") is starting... (%s)",
diff --git a/src/meson.build b/src/meson.build
index ff2276e3..06a0dc57 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -45,8 +45,6 @@ sources = files(
   'nm-logging.c',
 )
 
-sources += shared_files_time_utils
-
 deps = [
   libsystemd_dep,
   libudev_dep,
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c
index 6e9a72c6..f4719231 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/ndisc/nm-fake-ndisc.c
@@ -22,7 +22,6 @@
 
 #include "nm-fake-ndisc.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-ndisc-private.h"
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index 53548050..2dd7e7d8 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -22,7 +22,6 @@
 
 #include "nm-lndp-ndisc.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <netinet/icmp6.h>
 /* stdarg.h included because of a bug in ndp.h */
@@ -73,7 +72,6 @@ send_rs (NMNDisc *ndisc, GError **error)
 
 	errsv = ndp_msg_new (&msg, NDP_MSG_RS);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
 		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		                     "cannot create router solicitation");
 		return FALSE;
@@ -83,10 +81,10 @@ send_rs (NMNDisc *ndisc, GError **error)
 	errsv = ndp_msg_send (priv->ndp, msg);
 	ndp_msg_destroy (msg);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
+		errsv = nm_errno_native (errsv);
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "%s (%d)",
-		             g_strerror (errsv), errsv);
+		             nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
@@ -361,7 +359,6 @@ send_ra (NMNDisc *ndisc, GError **error)
 
 	errsv = ndp_msg_new (&msg, NDP_MSG_RA);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
 		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		                     "cannot create a router advertisement");
 		return FALSE;
@@ -469,10 +466,10 @@ send_ra (NMNDisc *ndisc, GError **error)
 
 	ndp_msg_destroy (msg);
 	if (errsv) {
-		errsv = errsv > 0 ? errsv : -errsv;
+		errsv = nm_errno_native (errsv);
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "%s (%d)",
-		             g_strerror (errsv), errsv);
+		             nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
@@ -536,7 +533,7 @@ start (NMNDisc *ndisc)
 
 /*****************************************************************************/
 
-static inline int
+static int
 ipv6_sysctl_get (NMPlatform *platform, const char *ifname, const char *property, int min, int max, int defval)
 {
 	return nm_platform_sysctl_ip_conf_get_int_checked (platform,
@@ -600,10 +597,10 @@ nm_lndp_ndisc_new (NMPlatform *platform,
 	errsv = ndp_open (&priv->ndp);
 
 	if (errsv != 0) {
-		errsv = errsv > 0 ? errsv : -errsv;
+		errsv = nm_errno_native (errsv);
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "failure creating libndp socket: %s (%d)",
-		             g_strerror (errsv), errsv);
+		             nm_strerror_native (errsv), errsv);
 		g_object_unref (ndisc);
 		return NULL;
 	}
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 2da08c5f..dd535630 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -24,7 +24,6 @@
 
 #include <stdlib.h>
 #include <arpa/inet.h>
-#include <string.h>
 
 #include "nm-setting-ip6-config.h"
 
@@ -442,7 +441,7 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 
 		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). */
+			 * (note that all prefixes at this point have implicitly length /64). */
 			if (memcmp (&item->address, &new->address, 8) == 0) {
 				existing = item;
 				break;
diff --git a/src/ndisc/tests/meson.build b/src/ndisc/tests/meson.build
index 0395e406..99cf664e 100644
--- a/src/ndisc/tests/meson.build
+++ b/src/ndisc/tests/meson.build
@@ -10,6 +10,7 @@ test(
   'ndisc/' + test_unit,
   test_script,
   args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
 
 test = 'test-ndisc-linux'
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index 268f3b49..d2291d44 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <syslog.h>
 
 #include "ndisc/nm-ndisc.h"
diff --git a/src/ndisc/tests/test-ndisc-linux.c b/src/ndisc/tests/test-ndisc-linux.c
index 2764b6c0..d47c0018 100644
--- a/src/ndisc/tests/test-ndisc-linux.c
+++ b/src/ndisc/tests/test-ndisc-linux.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <syslog.h>
 
 #include "ndisc/nm-ndisc.h"
diff --git a/src/nm-act-request.c b/src/nm-act-request.c
index cd816964..a79167f2 100644
--- a/src/nm-act-request.c
+++ b/src/nm-act-request.c
@@ -23,7 +23,6 @@
 
 #include "nm-act-request.h"
 
-#include <string.h>
 #include <stdlib.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -166,7 +165,7 @@ nm_act_request_get_secrets (NMActRequest *self,
                             gboolean ref_self,
                             const char *setting_name,
                             NMSecretAgentGetSecretsFlags flags,
-                            const char *hint,
+                            const char *const*hints,
                             NMActRequestSecretsFunc callback,
                             gpointer callback_data)
 {
@@ -175,7 +174,6 @@ nm_act_request_get_secrets (NMActRequest *self,
 	NMSettingsConnectionCallId *call_id_s;
 	NMSettingsConnection *settings_connection;
 	NMConnection *applied_connection;
-	const char *hints[2] = { hint, NULL };
 
 	g_return_val_if_fail (NM_IS_ACT_REQUEST (self), NULL);
 
diff --git a/src/nm-act-request.h b/src/nm-act-request.h
index af2b7490..e16e1ecf 100644
--- a/src/nm-act-request.h
+++ b/src/nm-act-request.h
@@ -69,7 +69,7 @@ NMActRequestGetSecretsCallId *nm_act_request_get_secrets (NMActRequest *req,
                                                           gboolean take_ref,
                                                           const char *setting_name,
                                                           NMSecretAgentGetSecretsFlags flags,
-                                                          const char *hint,
+                                                          const char *const*hints,
                                                           NMActRequestSecretsFunc callback,
                                                           gpointer callback_data);
 
diff --git a/src/nm-audit-manager.c b/src/nm-audit-manager.c
index 64d9bf4a..9f868508 100644
--- a/src/nm-audit-manager.c
+++ b/src/nm-audit-manager.c
@@ -22,8 +22,6 @@
 
 #include "nm-audit-manager.h"
 
-#include <errno.h>
-#include <string.h>
 #if HAVE_LIBAUDIT
 #include <libaudit.h>
 #endif
@@ -337,15 +335,17 @@ init_auditd (NMAuditManager *self)
 {
 	NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);
 	NMConfigData *data = nm_config_get_data (priv->config);
+	int errsv;
 
 	if (nm_config_data_get_value_boolean (data, NM_CONFIG_KEYFILE_GROUP_LOGGING,
 	                                      NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT,
 	                                      NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL)) {
 		if (priv->auditd_fd < 0) {
 			priv->auditd_fd = audit_open ();
-			if (priv->auditd_fd < 0)
-				_LOGE (LOGD_CORE, "failed to open auditd socket: %s", strerror (errno));
-			else
+			if (priv->auditd_fd < 0) {
+				errsv = errno;
+				_LOGE (LOGD_CORE, "failed to open auditd socket: %s", nm_strerror_native (errsv));
+			} else
 				_LOGD (LOGD_CORE, "socket created");
 		}
 	} else {
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
index 9ed65e2d..dff331a8 100644
--- a/src/nm-auth-subject.c
+++ b/src/nm-auth-subject.c
@@ -30,7 +30,6 @@
 
 #include "nm-auth-subject.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-dbus-manager.h"
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index 0c4a4bac..146f6883 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -22,10 +22,7 @@
 
 #include "nm-auth-utils.h"
 
-#include <string.h>
-
 #include "nm-utils/nm-c-list.h"
-
 #include "nm-setting-connection.h"
 #include "nm-auth-subject.h"
 #include "nm-auth-manager.h"
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index 5489ed49..9b1622ec 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -22,8 +22,6 @@
 
 #include "nm-checkpoint.h"
 
-#include <string.h>
-
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
 #include "nm-auth-subject.h"
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 8ef91516..608b7e58 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -23,8 +23,6 @@
 
 #include "nm-config-data.h"
 
-#include <string.h>
-
 #include "nm-config.h"
 #include "devices/nm-device.h"
 #include "nm-core-internal.h"
@@ -218,7 +216,6 @@ nm_config_data_get_value_int64 (const NMConfigData *self, const char *group, con
 	str = nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, NM_CONFIG_GET_VALUE_NONE);
 	val = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
 	if (str) {
-		/* preserve errno from the parsing. */
 		errsv = errno;
 		g_free (str);
 		errno = errsv;
diff --git a/src/nm-config.c b/src/nm-config.c
index d028be67..3e82bdec 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-config.h"
 
-#include <string.h>
 #include <stdio.h>
 
 #include "nm-utils.h"
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 5f0567e9..b72413d2 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -24,8 +24,6 @@
 
 #include "nm-connectivity.h"
 
-#include <string.h>
-
 #if WITH_CONCHECK
 #include <curl/curl.h>
 #endif
@@ -92,7 +90,7 @@ struct _NMConnectivityCheckHandle {
 		struct curl_slist *request_headers;
 		struct curl_slist *hosts;
 
-		GString *recv_msg;
+		gsize response_good_cnt;
 
 		guint curl_timer;
 		int ch_ifindex;
@@ -271,8 +269,6 @@ cb_data_complete (NMConnectivityCheckHandle *cb_data,
 
 #if WITH_CONCHECK
 	_con_config_unref (cb_data->concheck.con_config);
-	if (cb_data->concheck.recv_msg)
-		g_string_free (cb_data->concheck.recv_msg, TRUE);
 #endif
 	g_free (cb_data->ifspec);
 	if (cb_data->completed_log_message_free)
@@ -342,6 +338,7 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 	}
 
 	while ((msg = curl_multi_info_read (mhandle, &m_left))) {
+		const char *response;
 
 		if (msg->msg != CURLMSG_DONE)
 			continue;
@@ -370,25 +367,44 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 			                         g_strdup_printf ("check failed: (%d) %s",
 			                                          msg->data.result,
 			                                          curl_easy_strerror (msg->data.result)));
-		} else if (   !((_con_config_get_response (cb_data->concheck.con_config))[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_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_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_PORTAL,
-			                         "unexpected short response",
-			                         NULL);
+			continue;
+		}
+
+		response = _con_config_get_response (cb_data->concheck.con_config);
+
+		if (   response[0] == '\0'
+		    && (curl_easy_getinfo (msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK)) {
+
+			if (response_code == 204) {
+				/* We expected an empty response, and we got a 204 response code (no content).
+				 * We may or may not have received any content (we would ignore it).
+				 * Anyway, the response_code 204 means we are good. */
+				cb_data_queue_completed (cb_data,
+				                         NM_CONNECTIVITY_FULL,
+				                         "no content, as expected",
+				                         NULL);
+				continue;
+			}
+
+			if (   response_code == 200
+			    && cb_data->concheck.response_good_cnt == 0) {
+				/* we expected no response, and indeed we got an empty reply (with status code 200) */
+				cb_data_queue_completed (cb_data,
+				                         NM_CONNECTIVITY_FULL,
+				                         "empty response, as expected",
+				                         NULL);
+				continue;
+			}
 		}
+
+		/* 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_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_PORTAL,
+		                         "unexpected short response",
+		                         NULL);
 	}
 
 	/* if we return a failure, we don't know what went wrong. It's likely serious, because
@@ -540,6 +556,8 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 {
 	NMConnectivityCheckHandle *cb_data = userdata;
 	size_t len = size * nmemb;
+	size_t response_len;
+	size_t check_len;
 	const char *response;
 
 	if (cb_data->completed_state != NM_CONNECTIVITY_UNKNOWN) {
@@ -547,26 +565,68 @@ easy_write_cb (void *buffer, size_t size, size_t nmemb, void *userdata)
 		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);
+	if (len == 0) {
+		/* no data. That can happen, it's fine. */
+		return len;
+	}
 
 	response = _con_config_get_response (cb_data->concheck.con_config);;
-	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_queue_completed (cb_data,
-			                         NM_CONNECTIVITY_FULL,
-			                         "expected response",
-			                         NULL);
-		} else {
+
+	if (response[0] == '\0') {
+		/* no response expected. We are however graceful and accept any
+		 * extra response that we might receive. We determine the empty
+		 * response based on the status code 204.
+		 *
+		 * Continue receiving... */
+		cb_data->concheck.response_good_cnt += len;
+
+		if (cb_data->concheck.response_good_cnt > (gsize) (100 * 1024)) {
+			/* we expect an empty response. We accept either
+			 * 1) status code 204 and any response
+			 * 2) status code 200 and an empty response.
+			 *
+			 * Here, we want to continue receiving data, to see whether we have
+			 * case 1). Arguably, the server shouldn't send us 204 with a non-empty
+			 * response, but we accept that also with a non-empty response, so
+			 * keep receiving.
+			 *
+			 * However, if we get an excessive amount of data, we put a stop on it
+			 * and fail. */
 			cb_data_queue_completed (cb_data,
 			                         NM_CONNECTIVITY_PORTAL,
-			                         "unexpected response",
+			                         "unexpected non-empty response",
 			                         NULL);
+			return 0;
 		}
+
+		return len;
+	}
+
+	nm_assert (cb_data->concheck.response_good_cnt < strlen (response));
+
+	response_len = strlen (response);
+
+	check_len = NM_MIN (len,
+	                    response_len - cb_data->concheck.response_good_cnt);
+
+	if (strncmp (&response[cb_data->concheck.response_good_cnt],
+	             buffer,
+	             check_len) != 0) {
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_PORTAL,
+		                         "unexpected response",
+		                         NULL);
+		return 0;
+	}
+
+	cb_data->concheck.response_good_cnt += len;
+
+	if (cb_data->concheck.response_good_cnt >= response_len) {
+		/* We already have enough data, and it matched. */
+		cb_data_queue_completed (cb_data,
+		                         NM_CONNECTIVITY_FULL,
+		                         "expected response",
+		                         NULL);
 		return 0;
 	}
 
@@ -1041,8 +1101,8 @@ nm_connectivity_init (NMConnectivity *self)
 #if WITH_CONCHECK
 	ret = curl_global_init (CURL_GLOBAL_ALL);
 	if (ret != CURLE_OK) {
-		 _LOGE ("unable to init cURL, connectivity check will not work: (%d) %s",
-		        ret, curl_easy_strerror (ret));
+		_LOGE ("unable to init cURL, connectivity check will not work: (%d) %s",
+		       ret, curl_easy_strerror (ret));
 	}
 #endif
 
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 3047ebc8..6f55e62a 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -23,10 +23,8 @@
 
 #include "nm-core-utils.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <fnmatch.h>
-#include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <resolv.h>
@@ -246,16 +244,20 @@ nm_ethernet_address_is_valid (gconstpointer addr, gssize len)
 gconstpointer
 nm_utils_ipx_address_clear_host_address (int family, gpointer dst, gconstpointer src, guint8 plen)
 {
-	g_return_val_if_fail (src, NULL);
 	g_return_val_if_fail (dst, NULL);
 
 	switch (family) {
 	case AF_INET:
 		g_return_val_if_fail (plen <= 32, NULL);
+
+		if (!src) {
+			/* allow "self-assignment", by specifying %NULL as source. */
+			src = dst;
+		}
+
 		*((guint32 *) dst) = nm_utils_ip4_address_clear_host_address (*((guint32 *) src), plen);
 		break;
 	case AF_INET6:
-		g_return_val_if_fail (plen <= 128, NULL);
 		nm_utils_ip6_address_clear_host_address (dst, src, plen);
 		break;
 	default:
@@ -441,6 +443,7 @@ nm_utils_modprobe (GError **error, gboolean suppress_error_logging, const char *
 	/* construct the argument list */
 	argv = g_ptr_array_sized_new (4);
 	g_ptr_array_add (argv, "/sbin/modprobe");
+	g_ptr_array_add (argv, "--use-blacklist");
 	g_ptr_array_add (argv, (char *) arg1);
 
 	va_start (ap, arg1);
@@ -575,7 +578,7 @@ _kc_cb_timeout_grace_period (void *user_data)
 		/* ESRCH means, process does not exist or is already a zombie. */
 		if (errsv != ESRCH) {
 			nm_log_err (LOGD_CORE | data->log_domain, "%s: kill(SIGKILL) returned unexpected return value %d: (%s, %d)",
-			            data->log_name, ret, strerror (errsv), errsv);
+			            data->log_name, ret, nm_strerror_native (errsv), errsv);
 		}
 	} else {
 		nm_log_dbg (data->log_domain, "%s: process not terminated after %ld usec. Sending SIGKILL signal",
@@ -661,7 +664,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 		/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
 		if (errsv != ECHILD) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
-			            LOG_NAME_ARGS, strerror (errsv), errsv);
+			            LOG_NAME_ARGS, nm_strerror_native (errsv), errsv);
 			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
 			return;
 		}
@@ -673,7 +676,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 		/* ESRCH means, process does not exist or is already a zombie. */
 		if (errsv != ESRCH) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error sending %s: %s (%d)",
-			            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
 			return;
 		}
@@ -687,7 +690,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 		} else {
 			errsv = errno;
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s",
-			            LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig));
+			            LOG_NAME_ARGS, (long) ret, nm_strerror_native (errsv), errsv, _kc_signal_to_string (sig));
 			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
 		}
 		return;
@@ -709,7 +712,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 	g_child_watch_add (pid, _kc_cb_watch_child, data);
 }
 
-static inline gulong
+static gulong
 _sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec)
 {
 	if (sleep_duration_msec > 0) {
@@ -769,7 +772,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 		/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
 		if (errsv != ECHILD) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
-			            LOG_NAME_ARGS, strerror (errsv), errsv);
+			            LOG_NAME_ARGS, nm_strerror_native (errsv), errsv);
 			goto out;
 		}
 	}
@@ -780,7 +783,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 		/* ESRCH means, process does not exist or is already a zombie. */
 		if (errsv != ESRCH) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send %s: %s (%d)",
-			            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 		} else {
 			/* let's try again with waitpid, probably there was a race... */
 			ret = waitpid (pid, &status, 0);
@@ -791,7 +794,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 			} else {
 				errsv = errno;
 				nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s",
-				            LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig));
+				            LOG_NAME_ARGS, (long) ret, nm_strerror_native (errsv), errsv, _kc_signal_to_string (sig));
 			}
 		}
 		goto out;
@@ -822,7 +825,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 				/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
 				if (errsv != ECHILD) {
 					nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s, waitpid failed with %s (%d)%s",
-					            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv,
+					            LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv,
 					           was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
 					goto out;
 				}
@@ -861,7 +864,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 				/* ESRCH means, process does not exist or is already a zombie. */
 				if (errsv != ESRCH) {
 					nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send SIGKILL (after sending %s), %s (%d)",
-								LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+								LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 					goto out;
 				}
 			}
@@ -879,7 +882,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 
 		if (errsv != EINTR) {
 			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s%s, waitpid failed with %s (%d)%s",
-			            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", strerror (errsv), errsv,
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", nm_strerror_native (errsv), errsv,
 			            _kc_waited_to_string (buf_wait, wait_start_us));
 			goto out;
 		}
@@ -967,7 +970,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 			            LOG_NAME_ARGS, _kc_signal_to_string (sig));
 		} else {
 			nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send %s: %s (%d)",
-			             LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			             LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv);
 		}
 		return;
 	}
@@ -1018,7 +1021,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 				            was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
 			} else {
 				nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to kill(%ld, 0): %s (%d)%s",
-				             LOG_NAME_ARGS, (long int) pid, strerror (errsv), errsv,
+				             LOG_NAME_ARGS, (long int) pid, nm_strerror_native (errsv), errsv,
 				             was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
 			}
 			return;
@@ -1054,7 +1057,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 						            LOG_NAME_ARGS, _kc_waited_to_string (buf_wait, wait_start_us));
 					} else {
 						nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send SIGKILL (after sending %s), %s (%d)%s",
-						             LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv,
+						             LOG_NAME_ARGS, _kc_signal_to_string (sig), nm_strerror_native (errsv), errsv,
 						             _kc_waited_to_string (buf_wait, wait_start_us));
 					}
 					return;
@@ -2393,11 +2396,11 @@ _uuid_data_init (UuidData *uuid_data,
 	uuid_data->is_fake = is_fake;
 	if (packed) {
 		G_STATIC_ASSERT_EXPR (sizeof (uuid_data->str) >= (sizeof (*uuid) * 2 + 1));
-		_nm_utils_bin2hexstr_full (uuid,
-		                           sizeof (*uuid),
-		                           '\0',
-		                           FALSE,
-		                           uuid_data->str);
+		nm_utils_bin2hexstr_full (uuid,
+		                          sizeof (*uuid),
+		                          '\0',
+		                          FALSE,
+		                          uuid_data->str);
 	} else {
 		G_STATIC_ASSERT_EXPR (sizeof (uuid_data->str) >= 37);
 		_nm_utils_uuid_unparse (uuid, uuid_data->str);
@@ -2430,14 +2433,14 @@ again:
 		if (   nm_utils_file_get_contents (-1, "/etc/machine-id", 100*1024, 0, &content, NULL, NULL) >= 0
 		    || nm_utils_file_get_contents (-1, LOCALSTATEDIR"/lib/dbus/machine-id", 100*1024, 0, &content, NULL, NULL) >= 0) {
 			g_strstrip (content);
-			if (_nm_utils_hexstr2bin_full (content,
-			                               FALSE,
-			                               FALSE,
-			                               NULL,
-			                               16,
-			                               (guint8 *) &uuid,
-			                               sizeof (uuid),
-			                               NULL)) {
+			if (nm_utils_hexstr2bin_full (content,
+			                              FALSE,
+			                              FALSE,
+			                              NULL,
+			                              16,
+			                              (guint8 *) &uuid,
+			                              sizeof (uuid),
+			                              NULL)) {
 				if (!nm_utils_uuid_is_null (&uuid)) {
 					/* an all-zero machine-id is not valid. */
 					is_fake = FALSE;
@@ -2459,7 +2462,7 @@ again:
 			if (nm_utils_host_id_get (&seed_bin, &seed_len)) {
 				/* we have no valid machine-id. Generate a fake one by hashing
 				 * the secret-key. This key is commonly persisted, so it should be
-				 * stable accross reboots (despite having a broken system without
+				 * stable across reboots (despite having a broken system without
 				 * proper machine-id). */
 				fake_type = "secret-key";
 				hash_seed = "ab085f06-b629-46d1-a553-84eeba5683b6";
@@ -2556,7 +2559,7 @@ _host_id_read_timestamp (gboolean use_secret_key_file,
 	 * the secret_key) if we are unable to access the secret_key file in the first place.
 	 *
 	 * Pick a random timestamp from the past two years. Yes, this timestamp
-	 * is not stable accross restarts, but apparently neither is the host-id
+	 * is not stable across restarts, but apparently neither is the host-id
 	 * nor the secret_key itself. */
 
 #define EPOCH_TWO_YEARS  (G_GINT64_CONSTANT (2 * 365 * 24 * 3600) * NM_UTILS_NS_PER_SECOND)
@@ -3124,7 +3127,7 @@ nm_utils_stable_id_generated_complete (const char *stable_id_generated)
 	guint8 buf[NM_UTILS_CHECKSUM_LENGTH_SHA1];
 	char *base64;
 
-	/* for NM_UTILS_STABLE_TYPE_GENERATED we genererate a possibly long string
+	/* for NM_UTILS_STABLE_TYPE_GENERATED we generate a possibly long string
 	 * by doing text-substitutions in nm_utils_stable_id_parse().
 	 *
 	 * Let's shorten the (possibly) long stable_id to something more compact. */
@@ -3178,7 +3181,7 @@ nm_utils_stable_id_parse (const char *stable_id,
 	 * of ${...} patterns.
 	 *
 	 * At first, it looks a bit like bash parameter substitution.
-	 * In contrast however, the process is unambigious so that the resulting
+	 * In contrast however, the process is unambiguous so that the resulting
 	 * effective id differs if:
 	 *  - the original, untranslated stable-id differs
 	 *  - or any of the subsitutions differs.
@@ -3581,7 +3584,7 @@ nm_utils_dhcp_client_id_mac (int arp_type,
 /**
  * nm_utils_create_dhcp_iaid:
  * @legacy_unstable_byteorder: legacy behavior is to generate a u32 iaid which
- *   is endianness dependant. This is to preserve backward compatibility.
+ *   is endianness dependent. This is to preserve backward compatibility.
  *   For non-legacy behavior, the returned integer is in stable endianness,
  *   and corresponds to legacy behavior on little endian systems.
  * @interface_id: the seed for hashing when generating the ID. Usually,
@@ -3624,7 +3627,7 @@ nm_utils_create_dhcp_iaid (gboolean legacy_unstable_byteorder,
  * @legacy_unstable_byteorder: historically, the code would generate a iaid
  *   dependent on host endianness. This is undesirable, if backward compatibility
  *   are not a concern, generate stable endianness.
- * @interface_id: a binary identifer that is hashed into the DUID.
+ * @interface_id: a binary identifier that is hashed into the DUID.
  *   Comonly this is the interface-name, but it may be the MAC address.
  * @interface_id_len: the length of @interface_id.
  * @machine_id: the binary identifier for the machine. It is hashed
@@ -4019,7 +4022,7 @@ nm_utils_get_reverse_dns_domains_ip6 (const struct in6_addr *ip, guint8 plen, GP
 		return;
 
 	memcpy (&addr, ip, sizeof (struct in6_addr));
-	nm_utils_ip6_address_clear_host_address (&addr, &addr, plen);
+	nm_utils_ip6_address_clear_host_address (&addr, NULL, plen);
 
 	/* Number of nibbles to include in domains */
 	nibbles = (plen - 1) / 4 + 1;
@@ -4143,7 +4146,7 @@ nm_utils_read_plugin_paths (const char *dirname, const char *prefix)
 			errsv = errno;
 			nm_log_warn (LOGD_CORE,
 			             "plugin: skip invalid file %s (error during stat: %s)",
-			             data.path, strerror (errsv));
+			             data.path, nm_strerror_native (errsv));
 			goto skip;
 		}
 
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index a93854a4..1b0d39ed 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -485,4 +485,8 @@ const char *nm_activation_type_to_string (NMActivationType activation_type);
 
 const char *nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing);
 
+/*****************************************************************************/
+
+#define NM_VPN_ROUTE_METRIC_DEFAULT     50
+
 #endif /* __NM_CORE_UTILS_H__ */
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 9c8d6570..00fa6617 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -26,8 +26,6 @@
 #include <unistd.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <errno.h>
-#include <string.h>
 
 #include "c-list/src/c-list.h"
 #include "nm-dbus-interface.h"
diff --git a/src/nm-dcb.c b/src/nm-dcb.c
index 5a46fa00..a63fdf3d 100644
--- a/src/nm-dcb.c
+++ b/src/nm-dcb.c
@@ -21,7 +21,6 @@
 #include "nm-default.h"
 
 #include <sys/wait.h>
-#include <string.h>
 
 #include "nm-dcb.h"
 #include "platform/nm-platform.h"
diff --git a/src/nm-dhcp4-config.c b/src/nm-dhcp4-config.c
index ceafddf5..fe0df3a7 100644
--- a/src/nm-dhcp4-config.c
+++ b/src/nm-dhcp4-config.c
@@ -22,8 +22,6 @@
 
 #include "nm-dhcp4-config.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-utils.h"
 #include "nm-dbus-object.h"
diff --git a/src/nm-dhcp6-config.c b/src/nm-dhcp6-config.c
index d477521a..59266d55 100644
--- a/src/nm-dhcp6-config.c
+++ b/src/nm-dhcp6-config.c
@@ -22,8 +22,6 @@
 
 #include "nm-dhcp6-config.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-utils.h"
 #include "nm-dbus-object.h"
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index 368b781a..c0ef1bb6 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -23,9 +23,6 @@
 
 #include "nm-dispatcher.h"
 
-#include <string.h>
-#include <errno.h>
-
 #include "nm-dispatcher-api.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
@@ -965,7 +962,7 @@ dispatcher_dir_changed (GFileMonitor *monitor,
 		else if (errsv == 0)
 			_LOGD ("%s script directory '%s' has no scripts", item->description, item->dir);
 		else {
-			_LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, strerror (errsv));
+			_LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, nm_strerror_native (errsv));
 			item->has_scripts = TRUE;
 		}
 	} else {
diff --git a/src/nm-firewall-manager.c b/src/nm-firewall-manager.c
index 5b5e7cfa..a4b079b4 100644
--- a/src/nm-firewall-manager.c
+++ b/src/nm-firewall-manager.c
@@ -22,8 +22,6 @@
 
 #include "nm-firewall-manager.h"
 
-#include <string.h>
-
 #include "NetworkManagerUtils.h"
 #include "c-list/src/c-list.h"
 
diff --git a/src/nm-hostname-manager.c b/src/nm-hostname-manager.c
index 88ff1604..d405a320 100644
--- a/src/nm-hostname-manager.c
+++ b/src/nm-hostname-manager.c
@@ -23,8 +23,6 @@
 #include "nm-hostname-manager.h"
 
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 
 #if HAVE_SELINUX
 #include <selinux/selinux.h>
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index 1229ad35..1ef9f5ae 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -23,11 +23,9 @@
 #include <glib-unix.h>
 #include <getopt.h>
 #include <locale.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <string.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <signal.h>
@@ -389,6 +387,7 @@ main (int argc, char *argv[])
 	gs_unref_bytes GBytes *client_id = NULL;
 	gs_free NMUtilsIPv6IfaceId *iid = NULL;
 	guint sd_id;
+	int errsv;
 
 	c_list_init (&gl.dad_failed_lst_head);
 
@@ -397,11 +396,11 @@ main (int argc, char *argv[])
 	if (!do_early_setup (&argc, &argv))
 		return 1;
 
-	nm_logging_set_syslog_identifier ("nm-iface-helper");
-	nm_logging_set_prefix ("%s[%ld] (%s): ",
-	                       _NMLOG_PREFIX_NAME,
-	                       (long) getpid (),
-	                       global_opt.ifname ?: "???");
+	nm_logging_init_pre ("nm-iface-helper",
+	                     g_strdup_printf ("%s[%ld] (%s): ",
+	                                      _NMLOG_PREFIX_NAME,
+	                                      (long) getpid (),
+	                                      global_opt.ifname ?: "???"));
 
 	if (global_opt.g_fatal_warnings) {
 		GLogLevelFlags fatal_mask;
@@ -425,7 +424,8 @@ main (int argc, char *argv[])
 
 	gl.ifindex = nmp_utils_if_nametoindex (global_opt.ifname);
 	if (gl.ifindex <= 0) {
-		fprintf (stderr, _("Failed to find interface index for %s (%s)\n"), global_opt.ifname, strerror (errno));
+		errsv = errno;
+		fprintf (stderr, _("Failed to find interface index for %s (%s)\n"), global_opt.ifname, nm_strerror_native (errsv));
 		return 1;
 	}
 	pidfile = g_strdup_printf (NMIH_PID_FILE_FMT, gl.ifindex);
@@ -450,12 +450,10 @@ main (int argc, char *argv[])
 
 	if (global_opt.become_daemon && !global_opt.debug) {
 		if (daemon (0, 0) < 0) {
-			int saved_errno;
-
-			saved_errno = errno;
+			errsv = errno;
 			fprintf (stderr, _("Could not daemonize: %s [error %u]\n"),
-			         g_strerror (saved_errno),
-			         saved_errno);
+			         nm_strerror_native (errsv),
+			         errsv);
 			return 1;
 		}
 		if (nm_main_utils_write_pidfile (pidfile))
@@ -466,8 +464,8 @@ main (int argc, char *argv[])
 	gl.main_loop = g_main_loop_new (NULL, FALSE);
 	setup_signals ();
 
-	nm_logging_syslog_openlog (global_opt.logging_backend,
-	                           global_opt.debug);
+	nm_logging_init (global_opt.logging_backend,
+	                 global_opt.debug);
 
 	_LOGI (LOGD_CORE, "nm-iface-helper (version " NM_DIST_VERSION ") is starting...");
 
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 1179a77f..1c06a42c 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-ip4-config.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <resolv.h>
 #include <linux/rtnetlink.h>
@@ -1512,6 +1511,7 @@ nm_ip4_config_subtract (NMIP4Config *dst,
 static gboolean
 _nm_ip4_config_intersect_helper (NMIP4Config *dst,
                                  const NMIP4Config *src,
+                                 gboolean intersect_addresses,
                                  gboolean intersect_routes,
                                  guint32 default_route_metric_penalty,
                                  gboolean update_dst)
@@ -1534,24 +1534,26 @@ _nm_ip4_config_intersect_helper (NMIP4Config *dst,
 		g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	changed = FALSE;
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
-		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
-		                                     &src_priv->idx_ip4_addresses,
-		                                     NMP_OBJECT_UP_CAST (a)))
-			continue;
-
-		if (!update_dst)
-			return TRUE;
-
-		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
-		                                       ipconf_iter.current) != 1)
-			nm_assert_not_reached ();
-		changed = TRUE;
-	}
-	if (changed) {
-		_notify_addresses (dst);
-		result = TRUE;
+	if (intersect_addresses) {
+		changed = FALSE;
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
+			if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+			                                     &src_priv->idx_ip4_addresses,
+			                                     NMP_OBJECT_UP_CAST (a)))
+				continue;
+
+			if (!update_dst)
+				return TRUE;
+
+			if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+			                                       ipconf_iter.current) != 1)
+				nm_assert_not_reached ();
+			changed = TRUE;
+		}
+		if (changed) {
+			_notify_addresses (dst);
+			result = TRUE;
+		}
 	}
 
 	/* ignore nameservers */
@@ -1600,12 +1602,12 @@ _nm_ip4_config_intersect_helper (NMIP4Config *dst,
 		_notify (dst, PROP_GATEWAY);
 	}
 
-skip_routes:
 	if (changed) {
 		_notify_routes (dst);
 		result = TRUE;
 	}
 
+skip_routes:
 	/* ignore domains */
 	/* ignore dns searches */
 	/* ignore dns options */
@@ -1623,6 +1625,8 @@ skip_routes:
  * nm_ip4_config_intersect:
  * @dst: a configuration to be updated
  * @src: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @src and @dst and updates @dst in place
@@ -1631,16 +1635,24 @@ skip_routes:
 void
 nm_ip4_config_intersect (NMIP4Config *dst,
                          const NMIP4Config *src,
+                         gboolean intersect_addresses,
                          gboolean intersect_routes,
                          guint32 default_route_metric_penalty)
 {
-	_nm_ip4_config_intersect_helper (dst, src, intersect_routes, default_route_metric_penalty, TRUE);
+	_nm_ip4_config_intersect_helper (dst,
+	                                 src,
+	                                 intersect_addresses,
+	                                 intersect_routes,
+	                                 default_route_metric_penalty,
+	                                 TRUE);
 }
 
 /**
  * nm_ip4_config_intersect_alloc:
  * @a: a configuration
  * @b: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @a and @b and returns the result in a newly
@@ -1655,17 +1667,24 @@ nm_ip4_config_intersect (NMIP4Config *dst,
 NMIP4Config *
 nm_ip4_config_intersect_alloc (const NMIP4Config *a,
                                const NMIP4Config *b,
+                               gboolean intersect_addresses,
                                gboolean intersect_routes,
                                guint32 default_route_metric_penalty)
 {
 	NMIP4Config *a_copy;
 
 	if (_nm_ip4_config_intersect_helper ((NMIP4Config *) a, b,
+	                                     intersect_addresses,
 	                                     intersect_routes,
-	                                     default_route_metric_penalty, FALSE)) {
+	                                     default_route_metric_penalty,
+	                                     FALSE)) {
 		a_copy = nm_ip4_config_clone (a);
-		_nm_ip4_config_intersect_helper (a_copy, b, intersect_routes,
-		                                 default_route_metric_penalty, TRUE);
+		_nm_ip4_config_intersect_helper (a_copy,
+		                                 b,
+		                                 intersect_addresses,
+		                                 intersect_routes,
+		                                 default_route_metric_penalty,
+		                                 TRUE);
 		return a_copy;
 	} else
 		return NULL;
@@ -2153,7 +2172,7 @@ nm_ip4_config_get_first_address (const NMIP4Config *self)
 const NMPlatformIP4Address *
 _nmtst_ip4_config_get_address (const NMIP4Config *self, guint i)
 {
-	NMDedupMultiIter iter;
+	NMDedupMultiIter iter = { };
 	const NMPlatformIP4Address *a = NULL;
 	guint j;
 
@@ -2261,7 +2280,7 @@ _add_route (NMIP4Config *self,
  * nm_ip4_config_add_route:
  * @self: the #NMIP4Config
  * @new: the new route to add to @self
- * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed
  *   by caller.
  *
  * Adds the new route to @self.  If a route with the same basic properties
@@ -2901,7 +2920,7 @@ nm_ip4_config_nmpobj_remove (NMIP4Config *self,
 
 /*****************************************************************************/
 
-static inline void
+static void
 hash_u32 (GChecksum *sum, guint32 n)
 {
 	g_checksum_update (sum, (const guint8 *) &n, sizeof (n));
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 07fb7f12..6b4bfd64 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -189,10 +189,12 @@ void nm_ip4_config_subtract (NMIP4Config *dst,
                              guint32 default_route_metric_penalty);
 void nm_ip4_config_intersect (NMIP4Config *dst,
                               const NMIP4Config *src,
+                              gboolean intersect_addresses,
                               gboolean intersect_routes,
                               guint32 default_route_metric_penalty);
 NMIP4Config *nm_ip4_config_intersect_alloc (const NMIP4Config *a,
                                             const NMIP4Config *b,
+                                            gboolean intersect_addresses,
                                             gboolean intersect_routes,
                                             guint32 default_route_metric_penalty);
 gboolean nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes);
@@ -543,12 +545,14 @@ nm_ip_config_best_default_route_get (const NMIPConfig *self)
 static inline void
 nm_ip_config_intersect (NMIPConfig *dst,
                         const NMIPConfig *src,
+                        gboolean intersect_addresses,
                         gboolean intersect_routes,
                         guint32 default_route_metric_penalty)
 {
 	_NM_IP_CONFIG_DISPATCH_SET_OP (, dst, src,
 	                               nm_ip4_config_intersect,
 	                               nm_ip6_config_intersect,
+	                               intersect_addresses,
 	                               intersect_routes,
 	                               default_route_metric_penalty);
 }
@@ -591,6 +595,7 @@ nm_ip_config_replace (NMIPConfig *dst,
 static inline NMIPConfig *
 nm_ip_config_intersect_alloc (const NMIPConfig *a,
                               const NMIPConfig *b,
+                              gboolean intersect_addresses,
                               gboolean intersect_routes,
                               guint32 default_route_metric_penalty)
 {
@@ -598,6 +603,7 @@ nm_ip_config_intersect_alloc (const NMIPConfig *a,
 		nm_assert (NM_IS_IP4_CONFIG (b));
 		return (NMIPConfig *) nm_ip4_config_intersect_alloc ((const NMIP4Config *) a,
 		                                                     (const NMIP4Config *) b,
+		                                                     intersect_addresses,
 		                                                     intersect_routes,
 		                                                     default_route_metric_penalty);
 	} else {
@@ -605,6 +611,7 @@ nm_ip_config_intersect_alloc (const NMIPConfig *a,
 		nm_assert (NM_IS_IP6_CONFIG (b));
 		return (NMIPConfig *) nm_ip6_config_intersect_alloc ((const NMIP6Config *) a,
 		                                                     (const NMIP6Config *) b,
+		                                                     intersect_addresses,
 		                                                     intersect_routes,
 		                                                     default_route_metric_penalty);
 	}
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 6d54809e..99a9ff88 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-ip6-config.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <resolv.h>
 #include <linux/rtnetlink.h>
@@ -1087,6 +1086,7 @@ nm_ip6_config_subtract (NMIP6Config *dst,
 static gboolean
 _nm_ip6_config_intersect_helper (NMIP6Config *dst,
                                  const NMIP6Config *src,
+                                 gboolean intersect_addresses,
                                  gboolean intersect_routes,
                                  guint32 default_route_metric_penalty,
                                  gboolean update_dst)
@@ -1109,24 +1109,26 @@ _nm_ip6_config_intersect_helper (NMIP6Config *dst,
 		g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	changed = FALSE;
-	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) {
-		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
-		                                     &src_priv->idx_ip6_addresses,
-		                                     NMP_OBJECT_UP_CAST (a)))
-			continue;
+	if (intersect_addresses) {
+		changed = FALSE;
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) {
+			if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+			                                     &src_priv->idx_ip6_addresses,
+			                                     NMP_OBJECT_UP_CAST (a)))
+				continue;
 
-		if (!update_dst)
-			return TRUE;
+			if (!update_dst)
+				return TRUE;
 
-		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
-		                                       ipconf_iter.current) != 1)
-			nm_assert_not_reached ();
-		changed = TRUE;
-	}
-	if (changed) {
-		_notify_addresses (dst);
-		result = TRUE;
+			if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+			                                       ipconf_iter.current) != 1)
+				nm_assert_not_reached ();
+			changed = TRUE;
+		}
+		if (changed) {
+			_notify_addresses (dst);
+			result = TRUE;
+		}
 	}
 
 	/* ignore nameservers */
@@ -1194,6 +1196,8 @@ skip_routes:
  * nm_ip6_config_intersect:
  * @dst: a configuration to be updated
  * @src: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @src and @dst and updates @dst in place
@@ -1202,16 +1206,24 @@ skip_routes:
 void
 nm_ip6_config_intersect (NMIP6Config *dst,
                          const NMIP6Config *src,
+                         gboolean intersect_addresses,
                          gboolean intersect_routes,
                          guint32 default_route_metric_penalty)
 {
-	_nm_ip6_config_intersect_helper (dst, src, intersect_routes, default_route_metric_penalty, TRUE);
+	_nm_ip6_config_intersect_helper (dst,
+	                                 src,
+	                                 intersect_addresses,
+	                                 intersect_routes,
+	                                 default_route_metric_penalty,
+	                                 TRUE);
 }
 
 /**
  * nm_ip6_config_intersect_alloc:
  * @a: a configuration
  * @b: another configuration
+ * @intersect_addresses: whether addresses should be intersected
+ * @intersect_routes: whether routes should be intersected
  * @default_route_metric_penalty: the default route metric penalty
  *
  * Computes the intersection between @a and @b and returns the result in a newly
@@ -1226,17 +1238,25 @@ nm_ip6_config_intersect (NMIP6Config *dst,
 NMIP6Config *
 nm_ip6_config_intersect_alloc (const NMIP6Config *a,
                                const NMIP6Config *b,
+                               gboolean intersect_addresses,
                                gboolean intersect_routes,
                                guint32 default_route_metric_penalty)
 {
 	NMIP6Config *a_copy;
 
-	if (_nm_ip6_config_intersect_helper ((NMIP6Config *) a, b,
+	if (_nm_ip6_config_intersect_helper ((NMIP6Config *) a,
+	                                     b,
+	                                     intersect_addresses,
 	                                     intersect_routes,
-	                                     default_route_metric_penalty, FALSE)) {
+	                                     default_route_metric_penalty,
+	                                     FALSE)) {
 		a_copy = nm_ip6_config_clone (a);
-		_nm_ip6_config_intersect_helper (a_copy, b, intersect_routes,
-		                                 default_route_metric_penalty, TRUE);
+		_nm_ip6_config_intersect_helper (a_copy,
+		                                 b,
+		                                 intersect_addresses,
+		                                 intersect_routes,
+		                                 default_route_metric_penalty,
+		                                 TRUE);
 		return a_copy;
 	} else
 		return NULL;
@@ -1925,7 +1945,7 @@ _add_route (NMIP6Config *self,
  * nm_ip6_config_add_route:
  * @self: the #NMIP6Config
  * @new: the new route to add to @self
- * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ * @out_obj_new: (allow-none) (out): the added route object. Must be unrefed
  *   by caller.
  *
  * Adds the new route to @self.  If a route with the same basic properties
@@ -2353,13 +2373,13 @@ nm_ip6_config_nmpobj_remove (NMIP6Config *self,
 
 /*****************************************************************************/
 
-static inline void
+static void
 hash_u32 (GChecksum *sum, guint32 n)
 {
 	g_checksum_update (sum, (const guint8 *) &n, sizeof (n));
 }
 
-static inline void
+static void
 hash_in6addr (GChecksum *sum, const struct in6_addr *a)
 {
 	if (a)
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 862f237d..557e3796 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -130,10 +130,12 @@ void nm_ip6_config_subtract (NMIP6Config *dst,
                              guint32 default_route_metric_penalty);
 void nm_ip6_config_intersect (NMIP6Config *dst,
                               const NMIP6Config *src,
+                              gboolean intersect_addresses,
                               gboolean intersect_routes,
                               guint32 default_route_metric_penalty);
 NMIP6Config *nm_ip6_config_intersect_alloc (const NMIP6Config *a,
                                             const NMIP6Config *b,
+                                            gboolean intersect_addresses,
                                             gboolean intersect_routes,
                                             guint32 default_route_metric_penalty);
 gboolean nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes);
diff --git a/src/nm-keep-alive.c b/src/nm-keep-alive.c
index e601483b..cfec138a 100644
--- a/src/nm-keep-alive.c
+++ b/src/nm-keep-alive.c
@@ -23,8 +23,6 @@
 
 #include "nm-keep-alive.h"
 
-#include <string.h>
-
 #include "settings/nm-settings-connection.h"
 
 /*****************************************************************************/
@@ -98,7 +96,7 @@ _is_alive (NMKeepAlive *self)
 		/* the D-Bus client is gone. The only other binding (below) for the connection's
 		 * visibility cannot keep the instance alive.
 		 *
-		 * As such, a D-Bus client watch is authorative and overrules other conditions (that
+		 * As such, a D-Bus client watch is authoritative and overrules other conditions (that
 		 * we have so far). */
 		return FALSE;
 	}
@@ -470,7 +468,7 @@ _nm_keep_alive_set_owner (NMKeepAlive *self,
 	nm_assert (!owner || !priv->owner);
 
 	/* optimally, we would take a reference to @owner. But the
-	 * owner already owns a refrence to the keep-alive, so we cannot
+	 * owner already owns a reference to the keep-alive, so we cannot
 	 * just own a reference back.
 	 *
 	 * We could register a weak-pointer here. But instead, declare that
diff --git a/src/nm-logging.c b/src/nm-logging.c
index 6f912f58..1ee94645 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -28,11 +28,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <strings.h>
-#include <string.h>
 
 #if SYSTEMD_JOURNAL
 #define SD_JOURNAL_SUPPRESS_LOCATION
@@ -42,13 +40,50 @@
 #include "nm-utils/nm-time-utils.h"
 #include "nm-errors.h"
 
-void (*_nm_logging_clear_platform_logging_cache) (void);
+/*****************************************************************************/
 
-static void
-nm_log_handler (const char *log_domain,
-                GLogLevelFlags level,
-                const char *message,
-                gpointer ignored);
+/* Notes about thread-safety:
+ *
+ * NetworkManager generally is single-threaded and uses a (GLib) mainloop.
+ * However, nm-logging is in parts thread-safe. That means:
+ *
+ * - functions that configure logging (nm_logging_init(), nm_logging_setup()) and
+ *   most other functions MUST be called only from the main-thread. These functions
+ *   are expected to be called infrequently, so they may or may not use a mutex
+ *   (but the overhead is negligible here).
+ *
+ * - functions that do the actual logging logging (nm_log(), nm_logging_enabled()) are
+ *   thread-safe and may be used from multiple threads.
+ *    - When called from the not-main-thread, @mt_require_locking must be set to %TRUE.
+ *      In this case, a Mutex will be used for accessing the global state.
+ *    - When called from the main-thread, they may optionally pass @mt_require_locking %FALSE.
+ *      This avoids extra locking and is in particular interesting for nm_logging_enabled(),
+ *      which is expected to be called frequently and from the main-thread.
+ *
+ * Note that the logging macros honor %NM_THREAD_SAFE_ON_MAIN_THREAD define, to automatically
+ * set @mt_require_locking. That means, by default %NM_THREAD_SAFE_ON_MAIN_THREAD is "1",
+ * and code that only runs on the main-thread (which is the majority), can get away
+ * without locking.
+ */
+
+/*****************************************************************************/
+
+/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
+G_STATIC_ASSERT (sizeof (NMLogDomain) >= sizeof (guint64));
+
+/* Combined domains */
+#define LOGD_ALL_STRING     "ALL"
+#define LOGD_DEFAULT_STRING "DEFAULT"
+#define LOGD_DHCP_STRING    "DHCP"
+#define LOGD_IP_STRING      "IP"
+
+/*****************************************************************************/
+
+typedef enum {
+	LOG_BACKEND_GLIB,
+	LOG_BACKEND_SYSLOG,
+	LOG_BACKEND_JOURNAL,
+} LogBackend;
 
 typedef struct {
 	NMLogDomain num;
@@ -71,6 +106,52 @@ typedef struct {
 	GLogLevelFlags g_log_level;
 } LogLevelDesc;
 
+typedef struct {
+	char *logging_domains_to_string;
+} GlobalMain;
+
+typedef struct {
+	NMLogLevel log_level;
+	bool uses_syslog:1;
+	bool init_pre_done:1;
+	bool init_done:1;
+	bool debug_stderr:1;
+	const char *prefix;
+	const char *syslog_identifier;
+
+	/* before we setup syslog (during start), the backend defaults to GLIB, meaning:
+	 * we use g_log() for all logging. At that point, the application is not yet supposed
+	 * to do any logging and doing so indicates a bug.
+	 *
+	 * Afterwards, the backend is either SYSLOG or JOURNAL. From that point, also
+	 * g_log() is redirected to this backend via a logging handler. */
+	LogBackend log_backend;
+} Global;
+
+/*****************************************************************************/
+
+G_LOCK_DEFINE_STATIC (log);
+
+/* This data must only be accessed from the main-thread (and as
+ * such does not need any lock). */
+static GlobalMain gl_main = { };
+
+static union {
+	/* a union with an immutable and a mutable alias for the Global.
+	 * Since nm-logging must be thread-safe, we must take care at which
+	 * places we only read value ("imm") and where we modify them ("mut"). */
+	Global       mut;
+	const Global imm;
+} gl = {
+	.imm = {
+		/* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); */
+		.log_level = LOGL_INFO,
+		.log_backend = LOG_BACKEND_GLIB,
+		.syslog_identifier = "SYSLOG_IDENTIFIER="G_LOG_DOMAIN,
+		.prefix = "",
+	},
+};
+
 NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL] = {
 	/* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL);
 	 *
@@ -81,102 +162,65 @@ NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL] = {
 	[LOGL_ERR]  = LOGD_DEFAULT,
 };
 
-static struct Global {
-	NMLogLevel log_level;
-	bool uses_syslog:1;
-	bool syslog_identifier_initialized:1;
-	bool debug_stderr:1;
-	const char *prefix;
-	const char *syslog_identifier;
-	enum {
-		/* before we setup syslog (during start), the backend defaults to GLIB, meaning:
-		 * we use g_log() for all logging. At that point, the application is not yet supposed
-		 * to do any logging and doing so indicates a bug.
-		 *
-		 * Afterwards, the backend is either SYSLOG or JOURNAL. From that point, also
-		 * g_log() is redirected to this backend via a logging handler. */
-		LOG_BACKEND_GLIB,
-		LOG_BACKEND_SYSLOG,
-		LOG_BACKEND_JOURNAL,
-	} log_backend;
-	char *logging_domains_to_string;
-	const LogLevelDesc level_desc[_LOGL_N];
-
-#define _DOMAIN_DESC_LEN 39
-	/* Would be nice to use C99 flexible array member here,
-	 * but that feature doesn't seem well supported. */
-	const LogDesc domain_desc[_DOMAIN_DESC_LEN];
-} global = {
-	/* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); */
-	.log_level = LOGL_INFO,
-	.log_backend = LOG_BACKEND_GLIB,
-	.syslog_identifier = "SYSLOG_IDENTIFIER="G_LOG_DOMAIN,
-	.prefix = "",
-	.level_desc = {
-		[LOGL_TRACE] = { "TRACE", "<trace>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
-		[LOGL_DEBUG] = { "DEBUG", "<debug>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
-		[LOGL_INFO]  = { "INFO",  "<info>",  LOG_INFO,    G_LOG_LEVEL_INFO,    },
-		[LOGL_WARN]  = { "WARN",  "<warn>",  LOG_WARNING, G_LOG_LEVEL_MESSAGE, },
-		[LOGL_ERR]   = { "ERR",   "<error>", LOG_ERR,     G_LOG_LEVEL_MESSAGE, },
-		[_LOGL_OFF]  = { "OFF",   NULL,      0,           0,                   },
-		[_LOGL_KEEP] = { "KEEP",  NULL,      0,           0,                   },
-	},
-	.domain_desc = {
-		{ LOGD_PLATFORM,  "PLATFORM" },
-		{ LOGD_RFKILL,    "RFKILL" },
-		{ LOGD_ETHER,     "ETHER" },
-		{ LOGD_WIFI,      "WIFI" },
-		{ LOGD_BT,        "BT" },
-		{ LOGD_MB,        "MB" },
-		{ LOGD_DHCP4,     "DHCP4" },
-		{ LOGD_DHCP6,     "DHCP6" },
-		{ LOGD_PPP,       "PPP" },
-		{ LOGD_WIFI_SCAN, "WIFI_SCAN" },
-		{ LOGD_IP4,       "IP4" },
-		{ LOGD_IP6,       "IP6" },
-		{ LOGD_AUTOIP4,   "AUTOIP4" },
-		{ LOGD_DNS,       "DNS" },
-		{ LOGD_VPN,       "VPN" },
-		{ LOGD_SHARING,   "SHARING" },
-		{ LOGD_SUPPLICANT,"SUPPLICANT" },
-		{ LOGD_AGENTS,    "AGENTS" },
-		{ LOGD_SETTINGS,  "SETTINGS" },
-		{ LOGD_SUSPEND,   "SUSPEND" },
-		{ LOGD_CORE,      "CORE" },
-		{ LOGD_DEVICE,    "DEVICE" },
-		{ LOGD_OLPC,      "OLPC" },
-		{ LOGD_INFINIBAND,"INFINIBAND" },
-		{ LOGD_FIREWALL,  "FIREWALL" },
-		{ LOGD_ADSL,      "ADSL" },
-		{ LOGD_BOND,      "BOND" },
-		{ LOGD_VLAN,      "VLAN" },
-		{ LOGD_BRIDGE,    "BRIDGE" },
-		{ LOGD_DBUS_PROPS,"DBUS_PROPS" },
-		{ LOGD_TEAM,      "TEAM" },
-		{ LOGD_CONCHECK,  "CONCHECK" },
-		{ LOGD_DCB,       "DCB" },
-		{ LOGD_DISPATCH,  "DISPATCH" },
-		{ LOGD_AUDIT,     "AUDIT" },
-		{ LOGD_SYSTEMD,   "SYSTEMD" },
-		{ LOGD_VPN_PLUGIN,"VPN_PLUGIN" },
-		{ LOGD_PROXY,     "PROXY" },
-		{ 0, NULL }
-		/* keep _DOMAIN_DESC_LEN in sync */
-	},
-};
+/*****************************************************************************/
 
-/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
-G_STATIC_ASSERT (sizeof (NMLogDomain) >= sizeof (guint64));
+static const LogLevelDesc level_desc[_LOGL_N] = {
+	[LOGL_TRACE] = { "TRACE", "<trace>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
+	[LOGL_DEBUG] = { "DEBUG", "<debug>", LOG_DEBUG,   G_LOG_LEVEL_DEBUG,   },
+	[LOGL_INFO]  = { "INFO",  "<info>",  LOG_INFO,    G_LOG_LEVEL_INFO,    },
+	[LOGL_WARN]  = { "WARN",  "<warn>",  LOG_WARNING, G_LOG_LEVEL_MESSAGE, },
+	[LOGL_ERR]   = { "ERR",   "<error>", LOG_ERR,     G_LOG_LEVEL_MESSAGE, },
+	[_LOGL_OFF]  = { "OFF",   NULL,      0,           0,                   },
+	[_LOGL_KEEP] = { "KEEP",  NULL,      0,           0,                   },
+};
 
-/* Combined domains */
-#define LOGD_ALL_STRING     "ALL"
-#define LOGD_DEFAULT_STRING "DEFAULT"
-#define LOGD_DHCP_STRING    "DHCP"
-#define LOGD_IP_STRING      "IP"
+static const LogDesc domain_desc[] = {
+	{ LOGD_PLATFORM,  "PLATFORM" },
+	{ LOGD_RFKILL,    "RFKILL" },
+	{ LOGD_ETHER,     "ETHER" },
+	{ LOGD_WIFI,      "WIFI" },
+	{ LOGD_BT,        "BT" },
+	{ LOGD_MB,        "MB" },
+	{ LOGD_DHCP4,     "DHCP4" },
+	{ LOGD_DHCP6,     "DHCP6" },
+	{ LOGD_PPP,       "PPP" },
+	{ LOGD_WIFI_SCAN, "WIFI_SCAN" },
+	{ LOGD_IP4,       "IP4" },
+	{ LOGD_IP6,       "IP6" },
+	{ LOGD_AUTOIP4,   "AUTOIP4" },
+	{ LOGD_DNS,       "DNS" },
+	{ LOGD_VPN,       "VPN" },
+	{ LOGD_SHARING,   "SHARING" },
+	{ LOGD_SUPPLICANT,"SUPPLICANT" },
+	{ LOGD_AGENTS,    "AGENTS" },
+	{ LOGD_SETTINGS,  "SETTINGS" },
+	{ LOGD_SUSPEND,   "SUSPEND" },
+	{ LOGD_CORE,      "CORE" },
+	{ LOGD_DEVICE,    "DEVICE" },
+	{ LOGD_OLPC,      "OLPC" },
+	{ LOGD_INFINIBAND,"INFINIBAND" },
+	{ LOGD_FIREWALL,  "FIREWALL" },
+	{ LOGD_ADSL,      "ADSL" },
+	{ LOGD_BOND,      "BOND" },
+	{ LOGD_VLAN,      "VLAN" },
+	{ LOGD_BRIDGE,    "BRIDGE" },
+	{ LOGD_DBUS_PROPS,"DBUS_PROPS" },
+	{ LOGD_TEAM,      "TEAM" },
+	{ LOGD_CONCHECK,  "CONCHECK" },
+	{ LOGD_DCB,       "DCB" },
+	{ LOGD_DISPATCH,  "DISPATCH" },
+	{ LOGD_AUDIT,     "AUDIT" },
+	{ LOGD_SYSTEMD,   "SYSTEMD" },
+	{ LOGD_VPN_PLUGIN,"VPN_PLUGIN" },
+	{ LOGD_PROXY,     "PROXY" },
+	{ 0 },
+};
 
 /*****************************************************************************/
 
-static char *_domains_to_string (gboolean include_level_override);
+static char *_domains_to_string (gboolean include_level_override,
+                                 NMLogLevel log_level,
+                                 const NMLogDomain log_state[static _LOGL_N_REAL]);
 
 /*****************************************************************************/
 
@@ -202,48 +246,30 @@ _syslog_identifier_valid_domain (const char *domain)
 }
 
 static gboolean
-_syslog_identifier_assert (const struct Global *gl)
+_syslog_identifier_assert (const char *syslog_identifier)
 {
-	g_assert (gl);
-	g_assert (gl->syslog_identifier);
-	g_assert (g_str_has_prefix (gl->syslog_identifier, "SYSLOG_IDENTIFIER="));
-	g_assert (_syslog_identifier_valid_domain (&gl->syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")]));
+	g_assert (syslog_identifier);
+	g_assert (g_str_has_prefix (syslog_identifier, "SYSLOG_IDENTIFIER="));
+	g_assert (_syslog_identifier_valid_domain (&syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")]));
 	return TRUE;
 }
 
 static const char *
-syslog_identifier_domain (const struct Global *gl)
+syslog_identifier_domain (const char *syslog_identifier)
 {
-	nm_assert (_syslog_identifier_assert (gl));
-	return &gl->syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")];
+	nm_assert (_syslog_identifier_assert (syslog_identifier));
+	return &syslog_identifier[NM_STRLEN ("SYSLOG_IDENTIFIER=")];
 }
 
 #if SYSTEMD_JOURNAL
 static const char *
-syslog_identifier_full (const struct Global *gl)
+syslog_identifier_full (const char *syslog_identifier)
 {
-	nm_assert (_syslog_identifier_assert (gl));
-	return &gl->syslog_identifier[0];
+	nm_assert (_syslog_identifier_assert (syslog_identifier));
+	return &syslog_identifier[0];
 }
 #endif
 
-void
-nm_logging_set_syslog_identifier (const char *domain)
-{
-	if (global.log_backend != LOG_BACKEND_GLIB)
-		g_return_if_reached ();
-
-	if (!_syslog_identifier_valid_domain (domain))
-		g_return_if_reached ();
-
-	if (global.syslog_identifier_initialized)
-		g_return_if_reached ();
-
-	global.syslog_identifier_initialized = TRUE;
-	global.syslog_identifier = g_strdup_printf ("SYSLOG_IDENTIFIER=%s", domain);
-	nm_assert (_syslog_identifier_assert (&global));
-}
-
 /*****************************************************************************/
 
 static gboolean
@@ -253,8 +279,8 @@ match_log_level (const char  *level,
 {
 	int i;
 
-	for (i = 0; i < G_N_ELEMENTS (global.level_desc); i++) {
-		if (!g_ascii_strcasecmp (global.level_desc[i].name, level)) {
+	for (i = 0; i < G_N_ELEMENTS (level_desc); i++) {
+		if (!g_ascii_strcasecmp (level_desc[i].name, level)) {
 			*out_level = i;
 			return TRUE;
 		}
@@ -272,31 +298,42 @@ nm_logging_setup (const char  *level,
                   GError     **error)
 {
 	GString *unrecognized = NULL;
-	NMLogDomain new_logging[G_N_ELEMENTS (_nm_logging_enabled_state)];
-	NMLogLevel new_log_level = global.log_level;
+	NMLogDomain cur_log_state[_LOGL_N_REAL];
+	NMLogDomain new_log_state[_LOGL_N_REAL];
+	NMLogLevel cur_log_level;
+	NMLogLevel new_log_level;
 	char **tmp, **iter;
 	int i;
 	gboolean had_platform_debug;
 	gs_free char *domains_free = NULL;
 
+	NM_ASSERT_ON_MAIN_THREAD ();
+
 	g_return_val_if_fail (!bad_domains || !*bad_domains, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	/* domains */
-	if (!domains || !*domains)
-		domains = (domains_free = _domains_to_string (FALSE));
+	cur_log_level = gl.imm.log_level;
+	memcpy (cur_log_state, _nm_logging_enabled_state, sizeof (cur_log_state));
 
-	for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-		new_logging[i] = 0;
+	new_log_level = cur_log_level;
+
+	if (!domains || !*domains) {
+		domains_free = _domains_to_string (FALSE,
+		                                   cur_log_level,
+		                                   cur_log_state);
+		domains = domains_free;
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+		new_log_state[i] = 0;
 
-	/* levels */
 	if (level && *level) {
 		if (!match_log_level (level, &new_log_level, error))
 			return FALSE;
 		if (new_log_level == _LOGL_KEEP) {
-			new_log_level = global.log_level;
-			for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-				new_logging[i] = _nm_logging_enabled_state[i];
+			new_log_level = cur_log_level;
+			for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+				new_log_state[i] = cur_log_state[i];
 		}
 	}
 
@@ -353,7 +390,7 @@ nm_logging_setup (const char  *level,
 			continue;
 
 		else {
-			for (diter = &global.domain_desc[0]; diter->name; diter++) {
+			for (diter = &domain_desc[0]; diter->name; diter++) {
 				if (!g_ascii_strcasecmp (diter->name, *iter)) {
 					bits = diter->num;
 					break;
@@ -377,34 +414,37 @@ nm_logging_setup (const char  *level,
 		}
 
 		if (domain_log_level == _LOGL_KEEP) {
-			for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-				new_logging[i] = (new_logging[i] & ~bits) | (_nm_logging_enabled_state[i] & bits);
+			for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+				new_log_state[i] = (new_log_state[i] & ~bits) | (cur_log_state[i] & bits);
 		} else {
-			for (i = 0; i < G_N_ELEMENTS (new_logging); i++) {
+			for (i = 0; i < G_N_ELEMENTS (new_log_state); i++) {
 				if (i < domain_log_level)
-					new_logging[i] &= ~bits;
+					new_log_state[i] &= ~bits;
 				else {
-					new_logging[i] |= bits;
+					new_log_state[i] |= bits;
 					if (   (protect & bits)
 					    && i < LOGL_INFO)
-						new_logging[i] &= ~protect;
+						new_log_state[i] &= ~protect;
 				}
 			}
 		}
 	}
 	g_strfreev (tmp);
 
-	g_clear_pointer (&global.logging_domains_to_string, g_free);
+	g_clear_pointer (&gl_main.logging_domains_to_string, g_free);
+
+	had_platform_debug = _nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_PLATFORM);
+
+	G_LOCK (log);
 
-	had_platform_debug = nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM);
+	gl.mut.log_level = new_log_level;
+	for (i = 0; i < G_N_ELEMENTS (new_log_state); i++)
+		_nm_logging_enabled_state[i] = new_log_state[i];
 
-	global.log_level = new_log_level;
-	for (i = 0; i < G_N_ELEMENTS (new_logging); i++)
-		_nm_logging_enabled_state[i] = new_logging[i];
+	G_UNLOCK (log);
 
 	if (   had_platform_debug
-	    && _nm_logging_clear_platform_logging_cache
-	    && !nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) {
+	    && !_nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_PLATFORM)) {
 		/* when debug logging is enabled, platform will cache all access to
 		 * sysctl. When the user disables debug-logging, we want to clear that
 		 * cache right away. */
@@ -420,7 +460,9 @@ nm_logging_setup (const char  *level,
 const char *
 nm_logging_level_to_string (void)
 {
-	return global.level_desc[global.log_level].name;
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	return level_desc[gl.imm.log_level].name;
 }
 
 const char *
@@ -432,10 +474,10 @@ nm_logging_all_levels_to_string (void)
 		int i;
 
 		str = g_string_new (NULL);
-		for (i = 0; i < G_N_ELEMENTS (global.level_desc); i++) {
+		for (i = 0; i < G_N_ELEMENTS (level_desc); i++) {
 			if (str->len)
 				g_string_append_c (str, ',');
-			g_string_append (str, global.level_desc[i].name);
+			g_string_append (str, level_desc[i].name);
 		}
 	}
 
@@ -445,27 +487,34 @@ nm_logging_all_levels_to_string (void)
 const char *
 nm_logging_domains_to_string (void)
 {
-	if (G_UNLIKELY (!global.logging_domains_to_string))
-		global.logging_domains_to_string = _domains_to_string (TRUE);
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	if (G_UNLIKELY (!gl_main.logging_domains_to_string)) {
+		gl_main.logging_domains_to_string = _domains_to_string (TRUE,
+		                                                        gl.imm.log_level,
+		                                                        _nm_logging_enabled_state);
+	}
 
-	return global.logging_domains_to_string;
+	return gl_main.logging_domains_to_string;
 }
 
 static char *
-_domains_to_string (gboolean include_level_override)
+_domains_to_string (gboolean include_level_override,
+                    NMLogLevel log_level,
+                    const NMLogDomain log_state[static _LOGL_N_REAL])
 {
 	const LogDesc *diter;
 	GString *str;
 	int i;
 
-	/* We don't just return g_strdup (global.log_domains) because we want to expand
-	 * "DEFAULT" and "ALL".
+	/* We don't just return g_strdup() the logging domains that were set during
+	 * nm_logging_setup(), because we want to expand "DEFAULT" and "ALL".
 	 */
 
 	str = g_string_sized_new (75);
-	for (diter = &global.domain_desc[0]; diter->name; diter++) {
+	for (diter = &domain_desc[0]; diter->name; diter++) {
 		/* If it's set for any lower level, it will also be set for LOGL_ERR */
-		if (!(diter->num & _nm_logging_enabled_state[LOGL_ERR]))
+		if (!(diter->num & log_state[LOGL_ERR]))
 			continue;
 
 		if (str->len)
@@ -476,17 +525,17 @@ _domains_to_string (gboolean include_level_override)
 			continue;
 
 		/* Check if it's logging at a lower level than the default. */
-		for (i = 0; i < global.log_level; i++) {
-			if (diter->num & _nm_logging_enabled_state[i]) {
-				g_string_append_printf (str, ":%s", global.level_desc[i].name);
+		for (i = 0; i < log_level; i++) {
+			if (diter->num & log_state[i]) {
+				g_string_append_printf (str, ":%s", level_desc[i].name);
 				break;
 			}
 		}
 		/* Check if it's logging at a higher level than the default. */
-		if (!(diter->num & _nm_logging_enabled_state[global.log_level])) {
-			for (i = global.log_level + 1; i < G_N_ELEMENTS (_nm_logging_enabled_state); i++) {
-				if (diter->num & _nm_logging_enabled_state[i]) {
-					g_string_append_printf (str, ":%s", global.level_desc[i].name);
+		if (!(diter->num & log_state[log_level])) {
+			for (i = log_level + 1; i < _LOGL_N_REAL; i++) {
+				if (diter->num & log_state[i]) {
+					g_string_append_printf (str, ":%s", level_desc[i].name);
 					break;
 				}
 			}
@@ -504,7 +553,7 @@ nm_logging_all_domains_to_string (void)
 		const LogDesc *diter;
 
 		str = g_string_new (LOGD_DEFAULT_STRING);
-		for (diter = &global.domain_desc[0]; diter->name; diter++) {
+		for (diter = &domain_desc[0]; diter->name; diter++) {
 			g_string_append_c (str, ',');
 			g_string_append (str, diter->name);
 			if (diter->num == LOGD_DHCP6)
@@ -534,16 +583,29 @@ nm_logging_get_level (NMLogDomain domain)
 
 	G_STATIC_ASSERT (LOGL_TRACE == 0);
 	while (   sl > LOGL_TRACE
-	       && nm_logging_enabled (sl - 1, domain))
+	       && _nm_logging_enabled_lockfree (sl - 1, domain))
 		sl--;
 	return sl;
 }
 
 gboolean
-_nm_log_enabled (NMLogLevel level,
-                 NMLogDomain domain)
+_nm_logging_enabled_locking (NMLogLevel level,
+                             NMLogDomain domain)
 {
-	return nm_logging_enabled (level, domain);
+	gboolean v;
+
+	G_LOCK (log);
+	v = _nm_logging_enabled_lockfree (level, domain);
+	G_UNLOCK (log);
+	return v;
+}
+
+gboolean
+_nm_log_enabled_impl (gboolean mt_require_locking,
+                      NMLogLevel level,
+                      NMLogDomain domain)
+{
+	return nm_logging_enabled_mt (mt_require_locking, level, domain);
 }
 
 #if SYSTEMD_JOURNAL
@@ -606,6 +668,7 @@ void
 _nm_log_impl (const char *file,
               guint line,
               const char *func,
+              gboolean mt_require_locking,
               NMLogLevel level,
               NMLogDomain domain,
               int error,
@@ -617,15 +680,37 @@ _nm_log_impl (const char *file,
 	va_list args;
 	char *msg;
 	GTimeVal tv;
-	int errno_saved;
-
-	if ((guint) level >= G_N_ELEMENTS (_nm_logging_enabled_state))
-		g_return_if_reached ();
+	int errsv;
+	const NMLogDomain *cur_log_state;
+	NMLogDomain cur_log_state_copy[_LOGL_N_REAL];
+	Global g_copy;
+	const Global *g;
+
+	if (G_UNLIKELY (mt_require_locking)) {
+		G_LOCK (log);
+		/* we evaluate logging-enabled under lock. There is still a race that
+		 * we might log the message below *after* logging was disabled. That means,
+		 * when disabling logging, we might still log messages. */
+		if (!_nm_logging_enabled_lockfree (level, domain)) {
+			G_UNLOCK (log);
+			return;
+		}
+		g_copy = gl.imm;
+		memcpy (cur_log_state_copy, _nm_logging_enabled_state, sizeof (cur_log_state_copy));
+		G_UNLOCK (log);
+		g = &g_copy;
+		cur_log_state = cur_log_state_copy;
+	} else {
+		NM_ASSERT_ON_MAIN_THREAD ();
+		if (!_nm_logging_enabled_lockfree (level, domain))
+			return;
+		g = &gl.imm;
+		cur_log_state = _nm_logging_enabled_state;
+	}
 
-	if (!(_nm_logging_enabled_state[level] & domain))
-		return;
+	(void) cur_log_state;
 
-	errno_saved = errno;
+	errsv = errno;
 
 	/* Make sure that %m maps to the specified error */
 	if (error != 0) {
@@ -639,19 +724,19 @@ _nm_log_impl (const char *file,
 	va_end (args);
 
 #define MESSAGE_FMT "%s%-7s [%ld.%04ld] %s"
-#define MESSAGE_ARG(global, tv, msg) \
-    (global).prefix, \
-    (global).level_desc[level].level_str, \
+#define MESSAGE_ARG(prefix, tv, msg) \
+    prefix, \
+    level_desc[level].level_str, \
     (tv).tv_sec, \
     ((tv).tv_usec / 100), \
     (msg)
 
 	g_get_current_time (&tv);
 
-	if (global.debug_stderr)
-		g_printerr (MESSAGE_FMT"\n", MESSAGE_ARG (global, tv, msg));
+	if (g->debug_stderr)
+		g_printerr (MESSAGE_FMT"\n", MESSAGE_ARG (g->prefix, tv, msg));
 
-	switch (global.log_backend) {
+	switch (g->log_backend) {
 #if SYSTEMD_JOURNAL
 	case LOG_BACKEND_JOURNAL:
 		{
@@ -666,18 +751,18 @@ _nm_log_impl (const char *file,
 			now = nm_utils_get_monotonic_timestamp_ns ();
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 
-			_iovec_set_format_a (iov++, 30, "PRIORITY=%d", global.level_desc[level].syslog_level);
-			_iovec_set_format (iov++, iov_free++, "MESSAGE="MESSAGE_FMT, MESSAGE_ARG (global, tv, msg));
-			_iovec_set_string (iov++, syslog_identifier_full (&global));
+			_iovec_set_format_a (iov++, 30, "PRIORITY=%d", level_desc[level].syslog_level);
+			_iovec_set_format (iov++, iov_free++, "MESSAGE="MESSAGE_FMT, MESSAGE_ARG (g->prefix, tv, msg));
+			_iovec_set_string (iov++, syslog_identifier_full (g->syslog_identifier));
 			_iovec_set_format_a (iov++, 30, "SYSLOG_PID=%ld", (long) getpid ());
 			{
 				const LogDesc *diter;
 				int i_domain = _NUM_MAX_FIELDS_SYSLOG_FACILITY;
 				const char *s_domain_1 = NULL;
 				NMLogDomain dom_all = domain;
-				NMLogDomain dom = dom_all & _nm_logging_enabled_state[level];
+				NMLogDomain dom = dom_all & cur_log_state[level];
 
-				for (diter = &global.domain_desc[0]; diter->name; diter++) {
+				for (diter = &domain_desc[0]; diter->name; diter++) {
 					if (!NM_FLAGS_ANY (dom_all, diter->num))
 						continue;
 
@@ -712,7 +797,7 @@ _nm_log_impl (const char *file,
 				else
 					_iovec_set_format_str_a (iov++, 30, "NM_LOG_DOMAINS=%s", s_domain_1);
 			}
-			_iovec_set_format_str_a (iov++, 15, "NM_LOG_LEVEL=%s", global.level_desc[level].name);
+			_iovec_set_format_str_a (iov++, 15, "NM_LOG_LEVEL=%s", level_desc[level].name);
 			if (func)
 				_iovec_set_format (iov++, iov_free++, "CODE_FUNC=%s", func);
 			_iovec_set_format (iov++, iov_free++, "CODE_FILE=%s", file ?: "");
@@ -737,18 +822,18 @@ _nm_log_impl (const char *file,
 		break;
 #endif
 	case LOG_BACKEND_SYSLOG:
-		syslog (global.level_desc[level].syslog_level,
-		        MESSAGE_FMT, MESSAGE_ARG (global, tv, msg));
+		syslog (level_desc[level].syslog_level,
+		        MESSAGE_FMT, MESSAGE_ARG (g->prefix, tv, msg));
 		break;
 	default:
-		g_log (syslog_identifier_domain (&global), global.level_desc[level].g_log_level,
-		       MESSAGE_FMT, MESSAGE_ARG (global, tv, msg));
+		g_log (syslog_identifier_domain (g->syslog_identifier), level_desc[level].g_log_level,
+		       MESSAGE_FMT, MESSAGE_ARG (g->prefix, tv, msg));
 		break;
 	}
 
 	g_free (msg);
 
-	errno = errno_saved;
+	errno = errsv;
 }
 
 /*****************************************************************************/
@@ -758,7 +843,9 @@ _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp,
                                            gint64 offset_sec,
                                            gboolean is_boottime)
 {
-	if (nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) {
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	if (_nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_CORE)) {
 		time_t now = time (NULL);
 		struct tm tm;
 		char s[255];
@@ -804,10 +891,14 @@ nm_log_handler (const char *log_domain,
 		break;
 	}
 
-	if (global.debug_stderr)
-		g_printerr ("%s%s\n", global.prefix, message ?: "");
+	/* we don't need any locking here. The glib log handler gets only registered
+	 * once during nm_logging_init() and the global data is not modified afterwards. */
+	nm_assert (gl.imm.init_done);
 
-	switch (global.log_backend) {
+	if (gl.imm.debug_stderr)
+		g_printerr ("%s%s\n", gl.imm.prefix, message ?: "");
+
+	switch (gl.imm.log_backend) {
 #if SYSTEMD_JOURNAL
 	case LOG_BACKEND_JOURNAL:
 		{
@@ -817,8 +908,8 @@ nm_log_handler (const char *log_domain,
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 
 			sd_journal_send ("PRIORITY=%d", syslog_priority,
-			                 "MESSAGE=%s%s", global.prefix, message ?: "",
-			                 syslog_identifier_full (&global),
+			                 "MESSAGE=%s%s", gl.imm.prefix, message ?: "",
+			                 syslog_identifier_full (gl.imm.syslog_identifier),
 			                 "SYSLOG_PID=%ld", (long) getpid (),
 			                 "SYSLOG_FACILITY=GLIB",
 			                 "GLIB_DOMAIN=%s", log_domain ?: "",
@@ -830,7 +921,7 @@ nm_log_handler (const char *log_domain,
 		break;
 #endif
 	default:
-		syslog (syslog_priority, "%s%s", global.prefix, message ?: "");
+		syslog (syslog_priority, "%s%s", gl.imm.prefix, message ?: "");
 		break;
 	}
 }
@@ -838,44 +929,63 @@ nm_log_handler (const char *log_domain,
 gboolean
 nm_logging_syslog_enabled (void)
 {
-	return global.uses_syslog;
+	NM_ASSERT_ON_MAIN_THREAD ();
+
+	return gl.imm.uses_syslog;
 }
 
 void
-nm_logging_set_prefix (const char *format, ...)
+nm_logging_init_pre (const char *syslog_identifier,
+                     char *prefix_take)
 {
-	char *prefix;
-	va_list ap;
+	/* this function may be called zero or one times, and only
+	 * - on the main thread
+	 * - not after nm_logging_init(). */
+
+	NM_ASSERT_ON_MAIN_THREAD ();
 
-	/* prefix can only be set once, to a non-empty string. Also, after
-	 * nm_logging_syslog_openlog() the prefix cannot be set either. */
-	if (global.log_backend != LOG_BACKEND_GLIB)
+	if (gl.imm.init_pre_done)
 		g_return_if_reached ();
-	if (global.prefix[0])
+
+	if (gl.imm.init_done)
 		g_return_if_reached ();
 
-	va_start (ap, format);
-	prefix = g_strdup_vprintf (format, ap);
-	va_end (ap);
+	if (!_syslog_identifier_valid_domain (syslog_identifier))
+		g_return_if_reached ();
 
-	if (!prefix || !prefix[0])
+	if (!prefix_take || !prefix_take[0])
 		g_return_if_reached ();
 
+	G_LOCK (log);
+
+	gl.mut.init_pre_done = TRUE;
+
+	gl.mut.syslog_identifier = g_strdup_printf ("SYSLOG_IDENTIFIER=%s", syslog_identifier);
+	nm_assert (_syslog_identifier_assert (gl.imm.syslog_identifier));
+
 	/* we pass the allocated string on and never free it. */
-	global.prefix = prefix;
+	gl.mut.prefix = prefix_take;
+
+	G_UNLOCK (log);
 }
 
 void
-nm_logging_syslog_openlog (const char *logging_backend, gboolean debug)
+nm_logging_init (const char *logging_backend, gboolean debug)
 {
 	gboolean fetch_monotonic_timestamp = FALSE;
 	gboolean obsolete_debug_backend = FALSE;
+	LogBackend x_log_backend;
+
+	/* this function may be called zero or one times, and only on the
+	 * main thread. */
+
+	NM_ASSERT_ON_MAIN_THREAD ();
 
 	nm_assert (NM_IN_STRSET (""NM_CONFIG_DEFAULT_LOGGING_BACKEND,
 	                         NM_LOG_CONFIG_BACKEND_JOURNAL,
 	                         NM_LOG_CONFIG_BACKEND_SYSLOG));
 
-	if (global.log_backend != LOG_BACKEND_GLIB)
+	if (gl.imm.init_done)
 		g_return_if_reached ();
 
 	if (!logging_backend)
@@ -892,26 +1002,36 @@ nm_logging_syslog_openlog (const char *logging_backend, gboolean debug)
 		obsolete_debug_backend = TRUE;
 	}
 
+
+	G_LOCK (log);
+
 #if SYSTEMD_JOURNAL
 	if (!nm_streq (logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) {
-		global.log_backend = LOG_BACKEND_JOURNAL;
-		global.uses_syslog = TRUE;
-		global.debug_stderr = debug;
+		x_log_backend = LOG_BACKEND_JOURNAL;
+
+		/* We only log the monotonic-timestamp with structured logging (journal).
+		 * Only in this case, fetch the timestamp. */
 		fetch_monotonic_timestamp = TRUE;
 	} else
 #endif
 	{
-		global.log_backend = LOG_BACKEND_SYSLOG;
-		global.uses_syslog = TRUE;
-		global.debug_stderr = debug;
-		openlog (syslog_identifier_domain (&global), LOG_PID, LOG_DAEMON);
+		x_log_backend = LOG_BACKEND_SYSLOG;
+		openlog (syslog_identifier_domain (gl.imm.syslog_identifier), LOG_PID, LOG_DAEMON);
 	}
 
-	g_log_set_handler (syslog_identifier_domain (&global),
+	gl.mut.init_done = TRUE;
+	gl.mut.log_backend = x_log_backend;
+	gl.mut.uses_syslog = TRUE;
+	gl.mut.debug_stderr = debug;
+
+	g_log_set_handler (syslog_identifier_domain (gl.imm.syslog_identifier),
 	                   G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
 	                   nm_log_handler,
 	                   NULL);
 
+	G_UNLOCK (log);
+
+
 	if (fetch_monotonic_timestamp) {
 		/* ensure we read a monotonic timestamp. Reading the timestamp the first
 		 * time causes a logging message. We don't want to do that during _nm_log_impl. */
diff --git a/src/nm-logging.h b/src/nm-logging.h
index 21263c7b..a824890a 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -54,10 +54,12 @@ LOGD_IP_from_af (int addr_family)
 /* A wrapper for the _nm_log_impl() function that adds call site information.
  * Contrary to nm_log(), it unconditionally calls the function without
  * checking whether logging for the given level and domain is enabled. */
-#define _nm_log(level, domain, error, ifname, con_uuid, ...) \
+#define _nm_log_mt(mt_require_locking, level, domain, error, ifname, con_uuid, ...) \
     G_STMT_START { \
-        _nm_log_impl (__FILE__, __LINE__, \
+        _nm_log_impl (__FILE__, \
+                      __LINE__, \
                       _NM_LOG_FUNC, \
+                      (mt_require_locking), \
                       (level), \
                       (domain), \
                       (error), \
@@ -66,6 +68,9 @@ LOGD_IP_from_af (int addr_family)
                       ""__VA_ARGS__); \
     } G_STMT_END
 
+#define _nm_log(level, domain, error, ifname, con_uuid, ...) \
+	_nm_log_mt (!(NM_THREAD_SAFE_ON_MAIN_THREAD), level, domain, error, ifname, con_uuid, __VA_ARGS__)
+
 /* nm_log() only evaluates its argument list after checking
  * whether logging for the given level/domain is enabled.  */
 #define nm_log(level, domain, ifname, con_uuid, ...) \
@@ -138,15 +143,35 @@ _nm_log_ptr_is_debug (NMLogLevel level)
 const char *nm_logging_level_to_string (void);
 const char *nm_logging_domains_to_string (void);
 
+/*****************************************************************************/
+
 extern NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL];
+
 static inline gboolean
-nm_logging_enabled (NMLogLevel level, NMLogDomain domain)
+_nm_logging_enabled_lockfree (NMLogLevel level, NMLogDomain domain)
 {
 	nm_assert (((guint) level) < G_N_ELEMENTS (_nm_logging_enabled_state));
 	return    (((guint) level) < G_N_ELEMENTS (_nm_logging_enabled_state))
 	       && !!(_nm_logging_enabled_state[level] & domain);
 }
 
+gboolean _nm_logging_enabled_locking (NMLogLevel level, NMLogDomain domain);
+
+static inline gboolean
+nm_logging_enabled_mt (gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain)
+{
+	if (mt_require_locking)
+		return _nm_logging_enabled_locking (level, domain);
+
+	NM_ASSERT_ON_MAIN_THREAD ();
+	return _nm_logging_enabled_lockfree (level, domain);
+}
+
+#define nm_logging_enabled(level, domain) \
+	nm_logging_enabled_mt (!(NM_THREAD_SAFE_ON_MAIN_THREAD), level, domain)
+
+/*****************************************************************************/
+
 NMLogLevel nm_logging_get_level (NMLogDomain domain);
 
 const char *nm_logging_all_levels_to_string (void);
@@ -157,10 +182,11 @@ gboolean nm_logging_setup (const char  *level,
                            char       **bad_domains,
                            GError     **error);
 
-void nm_logging_set_syslog_identifier (const char *domain);
-void nm_logging_set_prefix (const char *format, ...) _nm_printf (1, 2);
+void nm_logging_init_pre (const char *syslog_identifier,
+                          char *prefix_take);
+
+void     nm_logging_init (const char *logging_backend, gboolean debug);
 
-void     nm_logging_syslog_openlog (const char *logging_backend, gboolean debug);
 gboolean nm_logging_syslog_enabled (void);
 
 /*****************************************************************************/
@@ -271,8 +297,6 @@ gboolean nm_logging_syslog_enabled (void);
 #define _LOG3t_err(errsv, ...) G_STMT_START { if (FALSE) { _NMLOG3_err (errsv, LOGL_TRACE, __VA_ARGS__); } } G_STMT_END
 #endif
 
-extern void (*_nm_logging_clear_platform_logging_cache) (void);
-
 /*****************************************************************************/
 
 #define __NMLOG_DEFAULT(level, domain, prefix, ...) \
@@ -292,4 +316,8 @@ extern void (*_nm_logging_clear_platform_logging_cache) (void);
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
+/*****************************************************************************/
+
+extern void _nm_logging_clear_platform_logging_cache (void);
+
 #endif /* __NETWORKMANAGER_LOGGING_H__ */
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 127d47a3..0bf6a751 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -25,8 +25,6 @@
 
 #include <stdlib.h>
 #include <fcntl.h>
-#include <errno.h>
-#include <string.h>
 #include <unistd.h>
 
 #include "nm-utils/nm-c-list.h"
@@ -2489,7 +2487,12 @@ get_existing_connection (NMManager *self,
 	if (ifindex) {
 		int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex);
 
-		if (master_ifindex) {
+		/* Check that the master is activating before assuming a
+		 * slave connection. However, ignore ovs-system master as
+		 * we never manage it.
+		 */
+		if (   master_ifindex
+		    && nm_platform_link_get_type (priv->platform, master_ifindex) != NM_LINK_TYPE_OPENVSWITCH) {
 			master = nm_manager_get_device_by_ifindex (self, master_ifindex);
 			if (!master) {
 				_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
@@ -2708,7 +2711,7 @@ recheck_assume_connection (NMManager *self,
 
 		subject = nm_auth_subject_new_internal ();
 
-		/* Note: the lifetime of the activation connection is always bound to the profiles visiblity
+		/* Note: the lifetime of the activation connection is always bound to the profiles visibility
 		 * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY.
 		 *
 		 * This only makes a difference, if the profile actually has "connection.permissions"
@@ -3501,7 +3504,7 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 		flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
 	else {
 		/* if the profile is multi-connect=single, we also consider devices which
-		 * are marked as unmanaged. And explicit user-request shows sufficent user
+		 * are marked as unmanaged. And explicit user-request shows sufficient user
 		 * intent to make the device managed.
 		 * That is also, because we expect that such profile is suitably tied
 		 * to the intended device. So when an unmanaged device matches, the user's
@@ -4421,7 +4424,7 @@ _activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject)
 	 *        logs out, the connection becomes invisible and disconnects.
 	 *
 	 *      - the profile at this time could already be invisible (e.g. if the
-	 *        user didn't ceate a proper session (sudo) and manually activates
+	 *        user didn't create a proper session (sudo) and manually activates
 	 *        an invisible profile. In this case, we still want to bind the
 	 *        lifetime, and it will disconnect after the user logs in and logs
 	 *        out again. NMKeepAlive takes care of that.
@@ -4906,7 +4909,7 @@ fail:
  * @activation_type: whether to assume the connection. That is, take over gracefully,
  *   non-destructible.
  * @activation_reason: the reason for activation
- * @initial_state_flags: the inital state flags for the activation.
+ * @initial_state_flags: the initial state flags for the activation.
  * @error: return location for an error
  *
  * Begins a new internally-initiated activation of @sett_conn on @device.
@@ -5312,10 +5315,10 @@ activation_add_done (NMSettings *settings,
 		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
 		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)));
 	} else {
-		result_floating = g_variant_new ("(ooa{sv})",
+		result_floating = g_variant_new ("(oo@a{sv})",
 		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
 		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)),
-		                                 g_variant_new_array (G_VARIANT_TYPE ("a{sv}"), NULL, 0));
+		                                 g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
 	}
 	g_dbus_method_invocation_return_value (context, result_floating);
 
@@ -7186,10 +7189,10 @@ rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean ena
 	int fd;
 	struct rfkill_event event;
 	ssize_t len;
+	int errsv;
 
 	g_return_if_fail (rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN);
 
-	errno = 0;
 	fd = open ("/dev/rfkill", O_RDWR | O_CLOEXEC);
 	if (fd < 0) {
 		if (errno == EACCES)
@@ -7220,8 +7223,9 @@ rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean ena
 
 	len = write (fd, &event, sizeof (event));
 	if (len < 0) {
+		errsv = errno;
 		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s",
-		       desc, errno, g_strerror (errno));
+		       desc, errsv, nm_strerror_native (errsv));
 	} else if (len == sizeof (event)) {
 		_LOGI (LOGD_RFKILL, "rfkill: %s hardware radio set %s",
 		       desc, enabled ? "enabled" : "disabled");
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 77fc929c..540f4065 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -23,9 +23,7 @@
 
 #include "nm-policy.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <netdb.h>
 
 #include "NetworkManagerUtils.h"
@@ -151,6 +149,25 @@ static NMDevice *get_default_device (NMPolicy *self, int addr_family);
 
 /*****************************************************************************/
 
+static void
+_dns_manager_set_ip_config (NMDnsManager *dns_manager,
+                            NMIPConfig *ip_config,
+                            NMDnsIPConfigType ip_config_type,
+                            NMDevice *device)
+{
+	if (   NM_IN_SET (ip_config_type, NM_DNS_IP_CONFIG_TYPE_DEFAULT,
+	                                  NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE)
+	    && device
+	    && nm_device_get_route_metric_default (nm_device_get_device_type (device)) == NM_VPN_ROUTE_METRIC_DEFAULT) {
+		/* some device types are inherently VPN. */
+		ip_config_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+	}
+
+	nm_dns_manager_set_ip_config (dns_manager, ip_config, ip_config_type);
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	NMPlatformIP6Address prefix;
 	NMDevice *device;             /* The requesting ("uplink") device */
@@ -508,15 +525,15 @@ settings_set_hostname_cb (const char *hostname,
 	NMPolicy *self = NM_POLICY (user_data);
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	int ret = 0;
+	int errsv;
 
 	if (!result) {
 		_LOGT (LOGD_DNS, "set-hostname: hostname set via dbus failed, fallback to \"sethostname\"");
 		ret = sethostname (hostname, strlen (hostname));
 		if (ret != 0) {
-			int errsv = errno;
-
+			errsv = errno;
 			_LOGW (LOGD_DNS, "set-hostname: couldn't set the system hostname to '%s': (%d) %s",
-			       hostname, errsv, strerror (errsv));
+			       hostname, errsv, nm_strerror_native (errsv));
 			if (errsv == EPERM)
 				_LOGW (LOGD_DNS, "set-hostname: you should use hostnamed when systemd hardening is in effect!");
 		}
@@ -535,6 +552,7 @@ _get_hostname (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	char *hostname = NULL;
+	int errsv;
 
 	/* If there is an in-progress hostname change, return
 	 * the last hostname set as would be set soon...
@@ -553,10 +571,9 @@ _get_hostname (NMPolicy *self)
 	/* ...or retrieve it by yourself */
 	hostname = g_malloc (HOST_NAME_BUFSIZE);
 	if (gethostname (hostname, HOST_NAME_BUFSIZE -1) != 0) {
-		int errsv = errno;
-
+		errsv = errno;
 		_LOGT (LOGD_DNS, "get-hostname: couldn't get the system hostname: (%d) %s",
-		       errsv, g_strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 		g_free (hostname);
 		return NULL;
 	}
@@ -1092,19 +1109,21 @@ update_ip_dns (NMPolicy *self, int addr_family)
 	gpointer ip_config;
 	const char *ip_iface = NULL;
 	NMVpnConnection *vpn = NULL;
+	NMDevice *device = NULL;
 
 	nm_assert_addr_family (addr_family);
 
-	ip_config = get_best_ip_config (self, addr_family, &ip_iface, NULL, NULL, &vpn);
+	ip_config = get_best_ip_config (self, addr_family, &ip_iface, NULL, &device, &vpn);
 	if (ip_config) {
 		/* Tell the DNS manager this config is preferred by re-adding it with
 		 * a different IP config type.
 		 */
-		nm_dns_manager_set_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
-		                              ip_config,
-		                              vpn
-		                                ? NM_DNS_IP_CONFIG_TYPE_VPN
-		                                : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE);
+		_dns_manager_set_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
+		                            ip_config,
+		                            vpn
+		                            ? NM_DNS_IP_CONFIG_TYPE_VPN
+		                            : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
+		                            device);
 	}
 
 	if (addr_family == AF_INET6)
@@ -1851,10 +1870,10 @@ device_state_changed (NMDevice *device,
 
 		ip4_config = nm_device_get_ip4_config (device);
 		if (ip4_config)
-			nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip4_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 		ip6_config = nm_device_get_ip6_config (device);
 		if (ip6_config)
-			nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 
 		update_routing_and_dns (self, FALSE);
 
@@ -1981,7 +2000,7 @@ device_ip_config_changed (NMDevice *device,
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
 		if (old_config != new_config) {
 			if (new_config)
-				nm_dns_manager_set_ip_config (priv->dns_manager, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+				_dns_manager_set_ip_config (priv->dns_manager, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 			if (old_config)
 				nm_dns_manager_set_ip_config (priv->dns_manager, old_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
 		}
diff --git a/src/nm-rfkill-manager.c b/src/nm-rfkill-manager.c
index 557f4cd9..534ad369 100644
--- a/src/nm-rfkill-manager.c
+++ b/src/nm-rfkill-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-rfkill-manager.h"
 
-#include <string.h>
 #include <libudev.h>
 
 #include "nm-utils/nm-udev-utils.h"
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index b67c537c..e8c25fd6 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -24,8 +24,6 @@
 #include "nm-session-monitor.h"
 
 #include <pwd.h>
-#include <errno.h>
-#include <string.h>
 #include <sys/stat.h>
 
 #if SESSION_TRACKING_SYSTEMD && SESSION_TRACKING_ELOGIND
@@ -206,13 +204,15 @@ static gboolean
 ck_update_cache (NMSessionMonitor *monitor)
 {
 	struct stat statbuf;
+	int errsv;
 
 	if (!monitor->ck.cache)
 		return FALSE;
 
 	/* Check the database file */
 	if (stat (CKDB_PATH, &statbuf) != 0) {
-		_LOGE ("failed to check ConsoleKit timestamp: %s", strerror (errno));
+		errsv = errno;
+		_LOGE ("failed to check ConsoleKit timestamp: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 	if (statbuf.st_mtime == monitor->ck.timestamp)
diff --git a/src/nm-sleep-monitor.c b/src/nm-sleep-monitor.c
index 54d75773..7e0ebe6c 100644
--- a/src/nm-sleep-monitor.c
+++ b/src/nm-sleep-monitor.c
@@ -21,8 +21,6 @@
 
 #include "nm-sleep-monitor.h"
 
-#include <errno.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <gio/gunixfdlist.h>
 
diff --git a/src/nm-types.h b/src/nm-types.h
index 277e0f6c..b6b49028 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -152,6 +152,7 @@ typedef enum {
 	NM_LINK_TYPE_WIMAX,
 	NM_LINK_TYPE_WPAN,
 	NM_LINK_TYPE_6LOWPAN,
+	NM_LINK_TYPE_WIFI_P2P,
 
 	/* Software types */
 	NM_LINK_TYPE_BNEP = 0x10000,   /* Bluetooth Ethernet emulation */
diff --git a/src/org.freedesktop.NetworkManager.conf b/src/org.freedesktop.NetworkManager.conf
index fa74b280..720c090e 100644
--- a/src/org.freedesktop.NetworkManager.conf
+++ b/src/org.freedesktop.NetworkManager.conf
@@ -84,6 +84,8 @@
                        send_interface="org.freedesktop.NetworkManager.WiMax.Nsp"/>
                 <allow send_destination="org.freedesktop.NetworkManager"
                        send_interface="org.freedesktop.NetworkManager.AccessPoint"/>
+                <allow send_destination="org.freedesktop.NetworkManager"
+                       send_interface="org.freedesktop.NetworkManager.WifiP2PPeer"/>
 
                 <!-- Devices (read-only, no security required) -->
                 <allow send_destination="org.freedesktop.NetworkManager"
@@ -93,6 +95,8 @@
                 <allow send_destination="org.freedesktop.NetworkManager"
                        send_interface="org.freedesktop.NetworkManager.Device.Wireless"/>
                 <allow send_destination="org.freedesktop.NetworkManager"
+                       send_interface="org.freedesktop.NetworkManager.Device.WifiP2P"/>
+                <allow send_destination="org.freedesktop.NetworkManager"
                        send_interface="org.freedesktop.NetworkManager.Device"/>
 
                 <!-- Core stuff (read-only properties, no methods) -->
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 814d9cea..30466159 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -22,7 +22,6 @@
 
 #include "nm-fake-platform.h"
 
-#include <errno.h>
 #include <unistd.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 85214ec8..2f5c75b0 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -21,31 +21,30 @@
 
 #include "nm-linux-platform.h"
 
-#include <poll.h>
+#include <arpa/inet.h>
+#include <dlfcn.h>
 #include <endian.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
 #include <fcntl.h>
-#include <dlfcn.h>
-#include <arpa/inet.h>
-#include <netinet/icmp6.h>
-#include <netinet/in.h>
+#include <libudev.h>
 #include <linux/ip.h>
 #include <linux/if_arp.h>
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/ip6_tunnel.h>
-#include <libudev.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
 
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
-#include "nm-utils/nm-errno.h"
 #include "nm-utils/nm-secret-utils.h"
+#include "nm-utils/nm-c-list.h"
 #include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
@@ -322,13 +321,27 @@ typedef enum {
 } DelayedActionType;
 
 #define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \
-	for ((iflags) = (DelayedActionType) 0x1LL; (iflags) <= DELAYED_ACTION_TYPE_MAX; (iflags) <<= 1) \
-		if (NM_FLAGS_ANY (flags_all, iflags))
+	for ((iflags) = (DelayedActionType) 0x1LL; \
+	     ({ \
+	         gboolean _good = FALSE; \
+	         \
+	         nm_assert (nm_utils_is_power_of_two (iflags)); \
+	         \
+	         while ((iflags) <= DELAYED_ACTION_TYPE_MAX) { \
+	             if (NM_FLAGS_ANY ((flags_all), (iflags))) { \
+	                 _good = TRUE; \
+	                 break; \
+	             } \
+	             (iflags) <<= 1; \
+	         } \
+	         _good; \
+	     }); \
+	     (iflags) <<= 1)
 
 typedef enum {
 	/* Negative values are errors from kernel. Add dummy member to
 	 * make enum signed. */
-	_WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = -1,
+	_WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = G_MININT,
 
 	WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0,
 	WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK,
@@ -376,8 +389,8 @@ typedef struct {
 
 	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
 
-	bool sysctl_get_warned;
 	GHashTable *sysctl_get_prev_values;
+	CList sysctl_list;
 
 	NMUdevClient *udev_client;
 
@@ -461,7 +474,7 @@ G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
             _LOG_print (__level, __domain, __errsv, self, \
                         _NM_UTILS_MACRO_FIRST (__VA_ARGS__) ": %s (%d)" \
                         _NM_UTILS_MACRO_REST (__VA_ARGS__), \
-                        g_strerror (__errsv), __errsv); \
+                        nm_strerror_native (__errsv), __errsv); \
         } \
     } G_STMT_END
 
@@ -512,7 +525,7 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result,
 		if (seq_result < 0) {
 			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s%s%s)",
 			                        -((int) seq_result),
-			                        g_strerror (-((int) seq_result)),
+			                        nm_strerror_native (-((int) seq_result)),
 			                        errmsg ? " - " : "",
 			                        errmsg ?: "");
 		}
@@ -577,7 +590,7 @@ _support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
 
 	/* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6,
 	 * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */
-	if (NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family, AF_INET, AF_INET6))
+	if (!NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family, AF_INET, AF_INET6))
 		return;
 
 	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
@@ -1100,7 +1113,7 @@ _linktype_get_type (NMPlatform *platform,
  ******************************************************************/
 
 #define NLMSG_TAIL(nmsg) \
-    ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+    ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN ((nmsg)->nlmsg_len)))
 
 /* copied from iproute2's addattr_l(). */
 static gboolean
@@ -1154,31 +1167,29 @@ _parse_af_inet6 (NMPlatform *platform,
                  guint8 *out_addr_gen_mode_inv,
                  gboolean *out_addr_gen_mode_valid)
 {
-	static const struct nla_policy policy[IFLA_INET6_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
 		[IFLA_INET6_CACHEINFO]          = { .minlen = nm_offsetofend (struct ifla_cacheinfo, retrans_time) },
 		[IFLA_INET6_CONF]               = { .minlen = 4 },
 		[IFLA_INET6_STATS]              = { .minlen = 8 },
 		[IFLA_INET6_ICMP6STATS]         = { .minlen = 8 },
-		[IFLA_INET6_TOKEN]              = { .minlen = sizeof(struct in6_addr) },
+		[IFLA_INET6_TOKEN]              = { .minlen = sizeof (struct in6_addr) },
 		[IFLA_INET6_ADDR_GEN_MODE]      = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_INET6_MAX+1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	struct in6_addr i6_token;
 	gboolean token_valid = FALSE;
 	gboolean addr_gen_mode_valid = FALSE;
 	guint8 i6_addr_gen_mode_inv = 0;
 
-	err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, attr, policy) < 0)
 		return FALSE;
 
-	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
+	if (tb[IFLA_INET6_CONF] && nla_len (tb[IFLA_INET6_CONF]) % 4)
 		return FALSE;
-	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
+	if (tb[IFLA_INET6_STATS] && nla_len (tb[IFLA_INET6_STATS]) % 8)
 		return FALSE;
-	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
+	if (tb[IFLA_INET6_ICMP6STATS] && nla_len (tb[IFLA_INET6_ICMP6STATS]) % 8)
 		return FALSE;
 
 	if (_check_addr_or_return_val (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr), FALSE)) {
@@ -1218,7 +1229,7 @@ _parse_af_inet6 (NMPlatform *platform,
 static NMPObject *
 _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_GRE_LINK]     = { .type = NLA_U32 },
 		[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
 		[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
@@ -1230,13 +1241,13 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 		[IFLA_GRE_TOS]      = { .type = NLA_U8 },
 		[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_GRE_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkGre *props;
 	gboolean is_tap;
 
-	if (!info_data || !kind)
+	if (   !info_data
+	    || !kind)
 		return NULL;
 
 	if (nm_streq (kind, "gretap"))
@@ -1246,8 +1257,7 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 	else
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL);
@@ -1287,25 +1297,25 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPOIB_PKEY]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_MODE]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_UMCAST] = { .type = NLA_U16 },
 	};
-	struct nlattr *tb[IFLA_IPOIB_MAX + 1];
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPlatformLnkInfiniband *info;
 	NMPObject *obj;
-	int err;
 	const char *mode;
 
-	if (!info_data || g_strcmp0 (kind, "ipoib"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "ipoib"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
-	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
+	if (   !tb[IFLA_IPOIB_PKEY]
+	    || !tb[IFLA_IPOIB_MODE])
 		return NULL;
 
 	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
@@ -1333,29 +1343,26 @@ _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPTUN_LINK]        = { .type = NLA_U32 },
-		[IFLA_IPTUN_LOCAL]       = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
-		[IFLA_IPTUN_REMOTE]      = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_LOCAL]       = { .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_REMOTE]      = { .minlen = sizeof (struct in6_addr)},
 		[IFLA_IPTUN_TTL]         = { .type = NLA_U8 },
 		[IFLA_IPTUN_ENCAP_LIMIT] = { .type = NLA_U8 },
 		[IFLA_IPTUN_FLOWINFO]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_PROTO]       = { .type = NLA_U8 },
 		[IFLA_IPTUN_FLAGS]       = { .type = NLA_U32 },
 	};
-	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkIp6Tnl *props;
 	guint32 flowinfo;
 
-	if (!info_data || g_strcmp0 (kind, "ip6tnl"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "ip6tnl"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IP6TNL, NULL);
@@ -1364,9 +1371,9 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 	if (tb[IFLA_IPTUN_LINK])
 		props->parent_ifindex = nla_get_u32 (tb[IFLA_IPTUN_LINK]);
 	if (tb[IFLA_IPTUN_LOCAL])
-		memcpy (&props->local, nla_data (tb[IFLA_IPTUN_LOCAL]), sizeof (props->local));
+		props->local = *nla_data_as (struct in6_addr, tb[IFLA_IPTUN_LOCAL]);
 	if (tb[IFLA_IPTUN_REMOTE])
-		memcpy (&props->remote, nla_data (tb[IFLA_IPTUN_REMOTE]), sizeof (props->remote));
+		props->remote = *nla_data_as (struct in6_addr, tb[IFLA_IPTUN_REMOTE]);
 	if (tb[IFLA_IPTUN_TTL])
 		props->ttl = nla_get_u8 (tb[IFLA_IPTUN_TTL]);
 	if (tb[IFLA_IPTUN_ENCAP_LIMIT])
@@ -1387,23 +1394,22 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_GRE_LINK]        = { .type = NLA_U32 },
 		[IFLA_GRE_IFLAGS]      = { .type = NLA_U16 },
 		[IFLA_GRE_OFLAGS]      = { .type = NLA_U16 },
 		[IFLA_GRE_IKEY]        = { .type = NLA_U32 },
 		[IFLA_GRE_OKEY]        = { .type = NLA_U32 },
 		[IFLA_GRE_LOCAL]       = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
+		                           .minlen = sizeof (struct in6_addr)},
 		[IFLA_GRE_REMOTE]      = { .type = NLA_UNSPEC,
-		                             .minlen = sizeof (struct in6_addr)},
+		                           .minlen = sizeof (struct in6_addr)},
 		[IFLA_GRE_TTL]         = { .type = NLA_U8 },
 		[IFLA_GRE_ENCAP_LIMIT] = { .type = NLA_U8 },
 		[IFLA_GRE_FLOWINFO]    = { .type = NLA_U32 },
 		[IFLA_GRE_FLAGS]       = { .type = NLA_U32 },
 	};
-	struct nlattr *tb[IFLA_GRE_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkIp6Tnl *props;
 	guint32 flowinfo;
@@ -1419,8 +1425,7 @@ _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 	else
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL);
@@ -1439,9 +1444,9 @@ _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 	if (tb[IFLA_GRE_OKEY])
 		props->output_key = ntohl (nla_get_u32 (tb[IFLA_GRE_OKEY]));
 	if (tb[IFLA_GRE_LOCAL])
-		memcpy (&props->local, nla_data (tb[IFLA_GRE_LOCAL]), sizeof (props->local));
+		props->local = *nla_data_as (struct in6_addr, tb[IFLA_GRE_LOCAL]);
 	if (tb[IFLA_GRE_REMOTE])
-		memcpy (&props->remote, nla_data (tb[IFLA_GRE_REMOTE]), sizeof (props->remote));
+		props->remote = *nla_data_as (struct in6_addr, tb[IFLA_GRE_REMOTE]);
 	if (tb[IFLA_GRE_TTL])
 		props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]);
 	if (tb[IFLA_GRE_ENCAP_LIMIT])
@@ -1462,7 +1467,7 @@ _parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1470,16 +1475,15 @@ _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 		[IFLA_IPTUN_TOS]      = { .type = NLA_U8 },
 		[IFLA_IPTUN_PMTUDISC] = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkIpIp *props;
 
-	if (!info_data || g_strcmp0 (kind, "ipip"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "ipip"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IPIP, NULL);
@@ -1500,28 +1504,27 @@ _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
 		[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
 	};
 	NMPlatformLnkMacvlan *props;
-	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	gboolean tap;
 
-	if (!info_data)
+	if (   !info_data
+	    || !kind)
 		return NULL;
 
-	if (!g_strcmp0 (kind, "macvlan"))
+	if (nm_streq (kind, "macvlan"))
 		tap = FALSE;
-	else if (!g_strcmp0 (kind, "macvtap"))
+	else if (nm_streq (kind, "macvtap"))
 		tap = TRUE;
 	else
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	if (!tb[IFLA_MACVLAN_MODE])
@@ -1543,7 +1546,7 @@ _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[__IFLA_MACSEC_MAX] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_MACSEC_SCI]            = { .type = NLA_U64 },
 		[IFLA_MACSEC_ICV_LEN]        = { .type = NLA_U8 },
 		[IFLA_MACSEC_CIPHER_SUITE]   = { .type = NLA_U64 },
@@ -1557,33 +1560,32 @@ _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 		[IFLA_MACSEC_REPLAY_PROTECT] = { .type = NLA_U8 },
 		[IFLA_MACSEC_VALIDATION]     = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[__IFLA_MACSEC_MAX];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkMacsec *props;
 
-	if (!info_data || !nm_streq0 (kind, "macsec"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "macsec"))
 		return NULL;
 
-	err = nla_parse_nested (tb, __IFLA_MACSEC_MAX - 1, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_MACSEC, NULL);
 	props = &obj->lnk_macsec;
 
-	props->sci = tb[IFLA_MACSEC_SCI] ? be64toh (nla_get_u64 (tb[IFLA_MACSEC_SCI])) : 0;
-	props->icv_length = tb[IFLA_MACSEC_ICV_LEN] ? nla_get_u8 (tb[IFLA_MACSEC_ICV_LEN]) : 0;
-	props->cipher_suite = tb [IFLA_MACSEC_CIPHER_SUITE] ? nla_get_u64 (tb[IFLA_MACSEC_CIPHER_SUITE]) : 0;
-	props->window = tb [IFLA_MACSEC_WINDOW] ? nla_get_u32 (tb[IFLA_MACSEC_WINDOW]) : 0;
-	props->encoding_sa = tb[IFLA_MACSEC_ENCODING_SA] ? !!nla_get_u8 (tb[IFLA_MACSEC_ENCODING_SA]) : 0;
-	props->encrypt = tb[IFLA_MACSEC_ENCRYPT] ? !!nla_get_u8 (tb[IFLA_MACSEC_ENCRYPT]) : 0;
-	props->protect = tb[IFLA_MACSEC_PROTECT] ? !!nla_get_u8 (tb[IFLA_MACSEC_PROTECT]) : 0;
-	props->include_sci = tb[IFLA_MACSEC_INC_SCI] ? !!nla_get_u8 (tb[IFLA_MACSEC_INC_SCI]) : 0;
-	props->es = tb[IFLA_MACSEC_ES] ? !!nla_get_u8 (tb[IFLA_MACSEC_ES]) : 0;
-	props->scb = tb[IFLA_MACSEC_SCB] ? !!nla_get_u8 (tb[IFLA_MACSEC_SCB]) : 0;
-	props->replay_protect = tb[IFLA_MACSEC_REPLAY_PROTECT] ? !!nla_get_u8 (tb[IFLA_MACSEC_REPLAY_PROTECT]) : 0;
-	props->validation = tb[IFLA_MACSEC_VALIDATION] ? nla_get_u8 (tb[IFLA_MACSEC_VALIDATION]) : 0;
+	if (tb[IFLA_MACSEC_SCI])            { props->sci            =   nla_get_be64 (tb[IFLA_MACSEC_SCI]);            }
+	if (tb[IFLA_MACSEC_ICV_LEN])        { props->icv_length     =   nla_get_u8   (tb[IFLA_MACSEC_ICV_LEN]);        }
+	if (tb[IFLA_MACSEC_CIPHER_SUITE])   { props->cipher_suite   =   nla_get_u64  (tb[IFLA_MACSEC_CIPHER_SUITE]);   }
+	if (tb[IFLA_MACSEC_WINDOW])         { props->window         =   nla_get_u32  (tb[IFLA_MACSEC_WINDOW]);         }
+	if (tb[IFLA_MACSEC_ENCODING_SA])    { props->encoding_sa    = !!nla_get_u8   (tb[IFLA_MACSEC_ENCODING_SA]);    }
+	if (tb[IFLA_MACSEC_ENCRYPT])        { props->encrypt        = !!nla_get_u8   (tb[IFLA_MACSEC_ENCRYPT]);        }
+	if (tb[IFLA_MACSEC_PROTECT])        { props->protect        = !!nla_get_u8   (tb[IFLA_MACSEC_PROTECT]);        }
+	if (tb[IFLA_MACSEC_INC_SCI])        { props->include_sci    = !!nla_get_u8   (tb[IFLA_MACSEC_INC_SCI]);        }
+	if (tb[IFLA_MACSEC_ES])             { props->es             = !!nla_get_u8   (tb[IFLA_MACSEC_ES]);             }
+	if (tb[IFLA_MACSEC_SCB])            { props->scb            = !!nla_get_u8   (tb[IFLA_MACSEC_SCB]);            }
+	if (tb[IFLA_MACSEC_REPLAY_PROTECT]) { props->replay_protect = !!nla_get_u8   (tb[IFLA_MACSEC_REPLAY_PROTECT]); }
+	if (tb[IFLA_MACSEC_VALIDATION])     { props->validation     =   nla_get_u8   (tb[IFLA_MACSEC_VALIDATION]);     }
 
 	return obj;
 }
@@ -1593,7 +1595,7 @@ _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1603,16 +1605,15 @@ _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 		[IFLA_IPTUN_FLAGS]    = { .type = NLA_U16 },
 		[IFLA_IPTUN_PROTO]    = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkSit *props;
 
-	if (!info_data || g_strcmp0 (kind, "sit"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "sit"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_SIT, NULL);
@@ -1635,7 +1636,7 @@ _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_tun (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_TUN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_TUN_OWNER]               = { .type = NLA_U32 },
 		[IFLA_TUN_GROUP]               = { .type = NLA_U32 },
 		[IFLA_TUN_TYPE]                = { .type = NLA_U8 },
@@ -1646,22 +1647,19 @@ _parse_lnk_tun (const char *kind, struct nlattr *info_data)
 		[IFLA_TUN_NUM_QUEUES]          = { .type = NLA_U32 },
 		[IFLA_TUN_NUM_DISABLED_QUEUES] = { .type = NLA_U32 },
 	};
-	struct nlattr *tb[IFLA_TUN_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 	NMPlatformLnkTun *props;
 
-	if (!info_data || !nm_streq0 (kind, "tun"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "tun"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_TUN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
-	if (!tb[IFLA_TUN_TYPE]) {
-		/* we require at least a type. */
+	if (!tb[IFLA_TUN_TYPE])
 		return NULL;
-	}
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_TUN, NULL);
 	props = &obj->lnk_tun;
@@ -1708,7 +1706,7 @@ _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
 
 	array = g_ptr_array_new ();
 	nla_for_each_nested (nla, nlattr, remaining) {
-		if (nla_len (nla) < sizeof(NMVlanQosMapping))
+		if (nla_len (nla) < sizeof (NMVlanQosMapping))
 			return FALSE;
 		g_ptr_array_add (array, nla_data (nla));
 	}
@@ -1749,22 +1747,22 @@ _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
 static NMPObject *
 _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_VLAN_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_VLAN_ID]          = { .type = NLA_U16 },
 		[IFLA_VLAN_FLAGS]       = { .minlen = nm_offsetofend (struct ifla_vlan_flags, flags) },
 		[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
 		[IFLA_VLAN_EGRESS_QOS]  = { .type = NLA_NESTED },
 		[IFLA_VLAN_PROTOCOL]    = { .type = NLA_U16 },
 	};
-	struct nlattr *tb[IFLA_VLAN_MAX+1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	NMPObject *obj_result;
 
-	if (!info_data || g_strcmp0 (kind, "vlan"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "vlan"))
 		return NULL;
 
-	if ((err = nla_parse_nested (tb, IFLA_VLAN_MAX, info_data, policy)) < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	if (!tb[IFLA_VLAN_ID])
@@ -1776,7 +1774,7 @@ _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 	if (tb[IFLA_VLAN_FLAGS]) {
 		struct ifla_vlan_flags flags;
 
-		nla_memcpy (&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
+		nla_memcpy (&flags, tb[IFLA_VLAN_FLAGS], sizeof (flags));
 
 		obj->lnk_vlan.flags = flags.flags;
 	}
@@ -1834,7 +1832,7 @@ struct nm_ifla_vxlan_port_range {
 static NMPObject *
 _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 {
-	static const struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
@@ -1857,16 +1855,14 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 		[IFLA_VXLAN_PORT]       = { .type = NLA_U16 },
 	};
 	NMPlatformLnkVxlan *props;
-	struct nlattr *tb[IFLA_VXLAN_MAX + 1];
-	struct nm_ifla_vxlan_port_range *range;
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	NMPObject *obj;
 
-	if (!info_data || g_strcmp0 (kind, "vxlan"))
+	if (   !info_data
+	    || !nm_streq0 (kind, "vxlan"))
 		return NULL;
 
-	err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data, policy);
-	if (err < 0)
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
 		return NULL;
 
 	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
@@ -1881,10 +1877,10 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 		props->group = nla_get_u32 (tb[IFLA_VXLAN_GROUP]);
 	if (tb[IFLA_VXLAN_LOCAL])
 		props->local = nla_get_u32 (tb[IFLA_VXLAN_LOCAL]);
-	if (tb[IFLA_VXLAN_GROUP6])
-		memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6));
 	if (tb[IFLA_VXLAN_LOCAL6])
-		memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6));
+		props->local6 = *nla_data_as (struct in6_addr, tb[IFLA_VXLAN_LOCAL6]);
+	if (tb[IFLA_VXLAN_GROUP6])
+		props->group6 = *nla_data_as (struct in6_addr, tb[IFLA_VXLAN_GROUP6]);
 
 	if (tb[IFLA_VXLAN_AGEING])
 		props->ageing = nla_get_u32 (tb[IFLA_VXLAN_AGEING]);
@@ -1899,7 +1895,9 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 		props->dst_port = ntohs (nla_get_u16 (tb[IFLA_VXLAN_PORT]));
 
 	if (tb[IFLA_VXLAN_PORT_RANGE]) {
-		range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]);
+		struct nm_ifla_vxlan_port_range *range;
+
+		range = nla_data_as (struct nm_ifla_vxlan_port_range, tb[IFLA_VXLAN_PORT_RANGE]);
 		props->src_port_min = ntohs (range->low);
 		props->src_port_max = ntohs (range->high);
 	}
@@ -1924,16 +1922,16 @@ static gboolean
 _wireguard_update_from_allowed_ips_nla (NMPWireGuardAllowedIP *allowed_ip,
                                         struct nlattr *nlattr)
 {
-	static const struct nla_policy policy[WGALLOWEDIP_A_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[WGALLOWEDIP_A_FAMILY]    = { .type = NLA_U16 },
 		[WGALLOWEDIP_A_IPADDR]    = { .minlen = sizeof (struct in_addr) },
 		[WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 },
 	};
-	struct nlattr *tb[WGALLOWEDIP_A_MAX + 1];
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	int family;
 	int addr_len;
 
-	if (nla_parse_nested (tb, WGALLOWEDIP_A_MAX, nlattr, policy) < 0)
+	if (nla_parse_nested_arr (tb, nlattr, policy) < 0)
 		return FALSE;
 
 	if (!tb[WGALLOWEDIP_A_FAMILY])
@@ -1973,7 +1971,7 @@ _wireguard_update_from_peers_nla (CList *peers,
                                   GArray **p_allowed_ips,
                                   struct nlattr *peer_attr)
 {
-	static const struct nla_policy policy[WGPEER_A_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[WGPEER_A_PUBLIC_KEY]                    = { .minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN },
 		[WGPEER_A_PRESHARED_KEY]                 = { },
 		[WGPEER_A_FLAGS]                         = { .type = NLA_U32 },
@@ -1984,10 +1982,10 @@ _wireguard_update_from_peers_nla (CList *peers,
 		[WGPEER_A_TX_BYTES]                      = { .type = NLA_U64 },
 		[WGPEER_A_ALLOWEDIPS]                    = { .type = NLA_NESTED },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	WireGuardPeerConstruct *peer_c;
-	struct nlattr *tb[WGPEER_A_MAX + 1];
 
-	if (nla_parse_nested (tb, WGPEER_A_MAX, peer_attr, policy) < 0)
+	if (nla_parse_nested_arr (tb, peer_attr, policy) < 0)
 		return FALSE;
 
 	if (!tb[WGPEER_A_PUBLIC_KEY])
@@ -2017,12 +2015,14 @@ _wireguard_update_from_peers_nla (CList *peers,
 
 		nm_sock_addr_union_cpy_untrusted (&peer_c->data.endpoint,
 		                                  tb[WGPEER_A_ENDPOINT] ? nla_data (tb[WGPEER_A_ENDPOINT]) : NULL,
-		                                  tb[WGPEER_A_ENDPOINT] ? nla_len (tb[WGPEER_A_ENDPOINT])  : 0);
+		                                  tb[WGPEER_A_ENDPOINT] ? nla_len  (tb[WGPEER_A_ENDPOINT]) : 0);
 
 		if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
-			peer_c->data.persistent_keepalive_interval = nla_get_u64 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
-		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
-			nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
+			peer_c->data.persistent_keepalive_interval = nla_get_u16 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
+		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) {
+			if (nla_len (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) >= sizeof (peer_c->data.last_handshake_time))
+				nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
+		}
 		if (tb[WGPEER_A_RX_BYTES])
 			peer_c->data.rx_bytes = nla_get_u64 (tb[WGPEER_A_RX_BYTES]);
 		if (tb[WGPEER_A_TX_BYTES])
@@ -2070,7 +2070,7 @@ typedef struct {
 static int
 _wireguard_get_device_cb (struct nl_msg *msg, void *arg)
 {
-	static const struct nla_policy policy[WGDEVICE_A_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[WGDEVICE_A_IFINDEX]     = { .type = NLA_U32 },
 		[WGDEVICE_A_IFNAME]      = { .type = NLA_NUL_STRING, .maxlen = IFNAMSIZ },
 		[WGDEVICE_A_PRIVATE_KEY] = { },
@@ -2080,12 +2080,10 @@ _wireguard_get_device_cb (struct nl_msg *msg, void *arg)
 		[WGDEVICE_A_FWMARK]      = { .type = NLA_U32 },
 		[WGDEVICE_A_PEERS]       = { .type = NLA_NESTED },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	WireGuardParseData *parse_data = arg;
-	struct nlattr *tb[WGDEVICE_A_MAX + 1];
-	int nlerr;
 
-	nlerr = genlmsg_parse (nlmsg_hdr (msg), 0, tb, WGDEVICE_A_MAX, policy);
-	if (nlerr < 0)
+	if (genlmsg_parse_arr (nlmsg_hdr (msg), 0, tb, policy) < 0)
 		return NL_SKIP;
 
 	if (tb[WGDEVICE_A_IFINDEX]) {
@@ -2164,7 +2162,7 @@ _wireguard_read_info (NMPlatform *platform /* used only as logging context */,
 	nm_assert (wireguard_family_id >= 0);
 	nm_assert (ifindex > 0);
 
-	_LOGT ("wireguard: fetching infomation for ifindex %d (genl-id %d)...", ifindex, wireguard_family_id);
+	_LOGT ("wireguard: fetching information for ifindex %d (genl-id %d)...", ifindex, wireguard_family_id);
 
 	msg = nlmsg_alloc ();
 
@@ -2373,8 +2371,9 @@ _wireguard_create_change_nlmsgs (NMPlatform *platform,
                                  int wireguard_family_id,
                                  const NMPlatformLnkWireGuard *lnk_wireguard,
                                  const NMPWireGuardPeer *peers,
+                                 const NMPlatformWireGuardChangePeerFlags *peer_flags,
                                  guint peers_len,
-                                 gboolean replace_peers,
+                                 NMPlatformWireGuardChangeFlags change_flags,
                                  GPtrArray **out_msgs)
 {
 	gs_unref_ptrarray GPtrArray *msgs = NULL;
@@ -2386,6 +2385,7 @@ _wireguard_create_change_nlmsgs (NMPlatform *platform,
 	struct nlattr *nest_curr_peer;
 	struct nlattr *nest_allowed_ips;
 	struct nlattr *nest_curr_allowed_ip;
+	NMPlatformWireGuardChangePeerFlags p_flags = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT;
 
 #define _nla_nest_end(msg, nest_start) \
 	G_STMT_START { \
@@ -2420,12 +2420,19 @@ again:
 	NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
 
 	if (idx_peer_curr == IDX_NIL) {
-		NLA_PUT (msg, WGDEVICE_A_PRIVATE_KEY, sizeof (lnk_wireguard->private_key), lnk_wireguard->private_key);
-		NLA_PUT_U16 (msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port);
-		NLA_PUT_U32 (msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark);
+		guint32 flags;
+
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY))
+			NLA_PUT (msg, WGDEVICE_A_PRIVATE_KEY, sizeof (lnk_wireguard->private_key), lnk_wireguard->private_key);
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT))
+			NLA_PUT_U16 (msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port);
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK))
+			NLA_PUT_U32 (msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark);
 
-		NLA_PUT_U32 (msg, WGDEVICE_A_FLAGS,
-		             replace_peers ? WGDEVICE_F_REPLACE_PEERS : ((guint32) 0u));
+		flags = 0;
+		if (NM_FLAGS_HAS (change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS))
+			flags |= WGDEVICE_F_REPLACE_PEERS;
+		NLA_PUT_U32 (msg, WGDEVICE_A_FLAGS, flags);
 	}
 
 	if (peers_len == 0)
@@ -2444,6 +2451,21 @@ again:
 	for (; idx_peer_curr < peers_len; idx_peer_curr++) {
 		const NMPWireGuardPeer *p = &peers[idx_peer_curr];
 
+		if (peer_flags) {
+			p_flags = peer_flags[idx_peer_curr];
+			if (!NM_FLAGS_ANY (p_flags,   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
+			                            | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)) {
+				/* no flags set. We take that as indication to skip configuring the peer
+				 * entirely. */
+				nm_assert (p_flags == NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE);
+				continue;
+			}
+		}
+
 		nest_curr_peer = nla_nest_start (msg, 0);
 		if (!nest_curr_peer)
 			goto toobig_peers;
@@ -2451,63 +2473,77 @@ again:
 		if (nla_put (msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0)
 			goto toobig_peers;
 
-		if (idx_allowed_ips_curr == IDX_NIL) {
-
-			if (nla_put (msg, WGPEER_A_PRESHARED_KEY, sizeof (p->preshared_key), p->preshared_key) < 0)
-				goto toobig_peers;
-
-			if (nla_put_uint16 (msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, p->persistent_keepalive_interval) < 0)
+		if (NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME)) {
+			/* all other p_flags are silently ignored. */
+			if (nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME) < 0)
 				goto toobig_peers;
+		} else {
 
-			if (nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0)
-				goto toobig_peers;
+			if (idx_allowed_ips_curr == IDX_NIL) {
+				if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY)
+				    && nla_put (msg, WGPEER_A_PRESHARED_KEY, sizeof (p->preshared_key), p->preshared_key) < 0)
+					goto toobig_peers;
 
-			if (NM_IN_SET (p->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
-				if (nla_put (msg,
-				             WGPEER_A_ENDPOINT,
-				               p->endpoint.sa.sa_family == AF_INET
-				             ? sizeof (p->endpoint.in)
-				             : sizeof (p->endpoint.in6),
-				             &p->endpoint) < 0)
+				if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL)
+				    && nla_put_uint16 (msg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, p->persistent_keepalive_interval) < 0)
 					goto toobig_peers;
-			} else
-				nm_assert (p->endpoint.sa.sa_family == AF_UNSPEC);
-		}
 
-		if (p->allowed_ips_len > 0) {
-			if (idx_allowed_ips_curr == IDX_NIL)
-				idx_allowed_ips_curr = 0;
+				if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)
+				    && nla_put_uint32 (msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0)
+					goto toobig_peers;
 
-			nest_allowed_ips = nla_nest_start (msg, WGPEER_A_ALLOWEDIPS);
-			if (!nest_allowed_ips)
-				goto toobig_allowedips;
+				if (NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT)) {
+					if (NM_IN_SET (p->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
+						if (nla_put (msg,
+						             WGPEER_A_ENDPOINT,
+						               p->endpoint.sa.sa_family == AF_INET
+						             ? sizeof (p->endpoint.in)
+						             : sizeof (p->endpoint.in6),
+						             &p->endpoint) < 0)
+							goto toobig_peers;
+					} else {
+						/* I think there is no way to clear an endpoint, though there shold be. */
+						nm_assert (p->endpoint.sa.sa_family == AF_UNSPEC);
+					}
+				}
+			}
 
-			for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) {
-				const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr];
+			if (   NM_FLAGS_HAS (p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS)
+			    && p->allowed_ips_len > 0) {
+				if (idx_allowed_ips_curr == IDX_NIL)
+					idx_allowed_ips_curr = 0;
 
-				nest_curr_allowed_ip = nla_nest_start (msg, 0);
-				if (!nest_curr_allowed_ip)
+				nest_allowed_ips = nla_nest_start (msg, WGPEER_A_ALLOWEDIPS);
+				if (!nest_allowed_ips)
 					goto toobig_allowedips;
 
-				g_return_val_if_fail (NM_IN_SET (aip->family, AF_INET, AF_INET6), -NME_BUG);
+				for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) {
+					const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr];
 
-				if (nla_put_uint16 (msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0)
-					goto toobig_allowedips;
-				if (nla_put (msg,
-				             WGALLOWEDIP_A_IPADDR,
-				             nm_utils_addr_family_to_size (aip->family),
-				             &aip->addr) < 0)
-					goto toobig_allowedips;
-				if (nla_put_uint8 (msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0)
-					goto toobig_allowedips;
+					nest_curr_allowed_ip = nla_nest_start (msg, 0);
+					if (!nest_curr_allowed_ip)
+						goto toobig_allowedips;
 
-				_nla_nest_end (msg, nest_curr_allowed_ip);
-				nest_curr_allowed_ip = NULL;
-			}
-			idx_allowed_ips_curr = IDX_NIL;
+					g_return_val_if_fail (NM_IN_SET (aip->family, AF_INET, AF_INET6), -NME_BUG);
 
-			_nla_nest_end (msg, nest_allowed_ips);
-			nest_allowed_ips = NULL;
+					if (nla_put_uint16 (msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0)
+						goto toobig_allowedips;
+					if (nla_put (msg,
+					             WGALLOWEDIP_A_IPADDR,
+					             nm_utils_addr_family_to_size (aip->family),
+					             &aip->addr) < 0)
+						goto toobig_allowedips;
+					if (nla_put_uint8 (msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0)
+						goto toobig_allowedips;
+
+					_nla_nest_end (msg, nest_curr_allowed_ip);
+					nest_curr_allowed_ip = NULL;
+				}
+				idx_allowed_ips_curr = IDX_NIL;
+
+				_nla_nest_end (msg, nest_allowed_ips);
+				nest_allowed_ips = NULL;
+			}
 		}
 
 		_nla_nest_end (msg, nest_curr_peer);
@@ -2555,8 +2591,9 @@ link_wireguard_change (NMPlatform *platform,
                        int ifindex,
                        const NMPlatformLnkWireGuard *lnk_wireguard,
                        const NMPWireGuardPeer *peers,
+                       const NMPlatformWireGuardChangePeerFlags *peer_flags,
                        guint peers_len,
-                       gboolean replace_peers)
+                       NMPlatformWireGuardChangeFlags change_flags)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	gs_unref_ptrarray GPtrArray *msgs = NULL;
@@ -2573,8 +2610,9 @@ link_wireguard_change (NMPlatform *platform,
 	                                     wireguard_family_id,
 	                                     lnk_wireguard,
 	                                     peers,
+	                                     peer_flags,
 	                                     peers_len,
-	                                     replace_peers,
+	                                     change_flags,
 	                                     &msgs);
 	if (r < 0) {
 		_LOGW ("wireguard: set-device, cannot construct netlink message: %s", nm_strerror (r));
@@ -2610,7 +2648,7 @@ link_wireguard_change (NMPlatform *platform,
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
 {
-	static const struct nla_policy policy[IFLA_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFLA_IFNAME]           = { .type = NLA_STRING,
 		                            .maxlen = IFNAMSIZ },
 		[IFLA_MTU]              = { .type = NLA_U32 },
@@ -2637,18 +2675,12 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		[IFLA_PHYS_PORT_ID]     = { .type = NLA_UNSPEC },
 		[IFLA_NET_NS_PID]       = { .type = NLA_U32 },
 		[IFLA_NET_NS_FD]        = { .type = NLA_U32 },
-	};
-	static const struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
-		[IFLA_INFO_KIND]        = { .type = NLA_STRING },
-		[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
-		[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
+		[IFLA_LINK_NETNSID]     = { },
 	};
 	const struct ifinfomsg *ifi;
-	struct nlattr *tb[IFLA_MAX+1];
-	struct nlattr *li[IFLA_INFO_MAX+1];
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	struct nlattr *nl_info_data = NULL;
 	const char *nl_info_kind = NULL;
-	int err;
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	gboolean completed_from_cache_val = FALSE;
 	gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL;
@@ -2662,6 +2694,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
 		return NULL;
+
 	ifi = nlmsg_data (nlh);
 
 	if (ifi->ifi_family != AF_UNSPEC)
@@ -2674,13 +2707,12 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	if (id_only)
 		return g_steal_pointer (&obj);
 
-	err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh, sizeof (*ifi), tb, policy) < 0)
 		return NULL;
 
 	if (!tb[IFLA_IFNAME])
 		return NULL;
-	nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
+	nla_strlcpy (obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
 	if (!obj->link.name[0])
 		return NULL;
 
@@ -2702,8 +2734,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]);
 
 	if (tb[IFLA_LINKINFO]) {
-		err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info);
-		if (err < 0)
+		static const struct nla_policy policy_link_info[] = {
+			[IFLA_INFO_KIND]        = { .type = NLA_STRING },
+			[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
+			[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
+		};
+		struct nlattr *li[G_N_ELEMENTS (policy_link_info)];
+
+		if (nla_parse_nested_arr (li, tb[IFLA_LINKINFO], policy_link_info) < 0)
 			return NULL;
 
 		if (li[IFLA_INFO_KIND])
@@ -2713,18 +2751,12 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	}
 
 	if (tb[IFLA_STATS64]) {
-		/* tb[IFLA_STATS64] is only guaranteed to be 32bit-aligned,
-		 * so in general we can't access the rtnl_link_stats64 struct
-		 * members directly on 64bit architectures. */
-		char *stats = nla_data (tb[IFLA_STATS64]);
+		const char *stats = nla_data (tb[IFLA_STATS64]);
 
-#define READ_STAT64(member) \
-	unaligned_read_ne64 (stats + offsetof (struct rtnl_link_stats64, member))
-
-		obj->link.rx_packets = READ_STAT64 (rx_packets);
-		obj->link.rx_bytes   = READ_STAT64 (rx_bytes);
-		obj->link.tx_packets = READ_STAT64 (tx_packets);
-		obj->link.tx_bytes   = READ_STAT64 (tx_bytes);
+		obj->link.rx_packets = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, rx_packets)]);
+		obj->link.rx_bytes   = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, rx_bytes)]);
+		obj->link.tx_packets = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, tx_packets)]);
+		obj->link.tx_bytes   = unaligned_read_ne64 (&stats[G_STRUCT_OFFSET (struct rtnl_link_stats64, tx_bytes)]);
 	}
 
 	obj->link.n_ifi_flags = ifi->ifi_flags;
@@ -2952,14 +2984,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 static NMPObject *
 _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static const struct nla_policy policy[IFA_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[IFA_LABEL]     = { .type = NLA_STRING,
-		                     .maxlen = IFNAMSIZ },
+		                    .maxlen = IFNAMSIZ },
 		[IFA_CACHEINFO] = { .minlen = nm_offsetofend (struct ifa_cacheinfo, tstamp) },
+		[IFA_FLAGS]     = { },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	const struct ifaddrmsg *ifa;
-	struct nlattr *tb[IFA_MAX+1];
-	int err;
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	int addr_len;
@@ -2967,14 +2999,15 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifa)))
 		return NULL;
-	ifa = nlmsg_data(nlh);
+
+	ifa = nlmsg_data (nlh);
 
 	if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6))
 		return NULL;
+
 	is_v4 = ifa->ifa_family == AF_INET;
 
-	err = nlmsg_parse (nlh, sizeof(*ifa), tb, IFA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh, sizeof (*ifa), tb, policy) < 0)
 		return NULL;
 
 	addr_len = is_v4
@@ -3043,8 +3076,9 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	timestamp = 0;
 	/* IPv6 only */
 	if (tb[IFA_CACHEINFO]) {
-		const struct ifa_cacheinfo *ca = nla_data(tb[IFA_CACHEINFO]);
+		const struct ifa_cacheinfo *ca;
 
+		ca = nla_data_as (struct ifa_cacheinfo, tb[IFA_CACHEINFO]);
 		lifetime = ca->ifa_valid;
 		preferred = ca->ifa_prefered;
 		timestamp = ca->tstamp;
@@ -3063,7 +3097,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 static NMPObject *
 _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static const struct nla_policy policy[RTA_MAX+1] = {
+	static const struct nla_policy policy[] = {
 		[RTA_TABLE]     = { .type = NLA_U32 },
 		[RTA_IIF]       = { .type = NLA_U32 },
 		[RTA_OIF]       = { .type = NLA_U32 },
@@ -3075,8 +3109,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		[RTA_MULTIPATH] = { .type = NLA_NESTED },
 	};
 	const struct rtmsg *rtm;
-	struct nlattr *tb[RTA_MAX + 1];
-	int err;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
 	int addr_len;
@@ -3084,13 +3117,21 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		gboolean is_present;
 		int ifindex;
 		NMIPAddr gateway;
-	} nh;
+	} nh = {
+		.is_present = FALSE,
+	};
 	guint32 mss;
-	guint32 window = 0, cwnd = 0, initcwnd = 0, initrwnd = 0, mtu = 0, lock = 0;
+	guint32 window = 0;
+	guint32 cwnd = 0;
+	guint32 initcwnd = 0;
+	guint32 initrwnd = 0;
+	guint32 mtu = 0;
+	guint32 lock = 0;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*rtm)))
 		return NULL;
-	rtm = nlmsg_data(nlh);
+
+	rtm = nlmsg_data (nlh);
 
 	/*****************************************************************
 	 * only handle ~normal~ routes.
@@ -3102,8 +3143,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	if (rtm->rtm_type != RTN_UNICAST)
 		return NULL;
 
-	err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh,
+	                     sizeof (struct rtmsg),
+	                     tb,
+	                     policy) < 0)
 		return NULL;
 
 	/*****************************************************************/
@@ -3120,39 +3163,49 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	 * parse nexthops. Only handle routes with one nh.
 	 *****************************************************************/
 
-	memset (&nh, 0, sizeof (nh));
-
 	if (tb[RTA_MULTIPATH]) {
-		struct rtnexthop *rtnh = nla_data (tb[RTA_MULTIPATH]);
-		size_t tlen = nla_len(tb[RTA_MULTIPATH]);
+		size_t tlen = nla_len (tb[RTA_MULTIPATH]);
+		struct rtnexthop *rtnh;
 
-		while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) {
+		if (tlen < sizeof (*rtnh))
+			goto rta_multipath_done;
+
+		rtnh = nla_data_as (struct rtnexthop, tb[RTA_MULTIPATH]);
+
+		if (tlen < rtnh->rtnh_len)
+			goto rta_multipath_done;
+
+		while (TRUE) {
 
 			if (nh.is_present) {
 				/* we don't support multipath routes. */
 				return NULL;
 			}
-			nh.is_present = TRUE;
 
+			nh.is_present = TRUE;
 			nh.ifindex = rtnh->rtnh_ifindex;
 
-			if (rtnh->rtnh_len > sizeof(*rtnh)) {
-				struct nlattr *ntb[RTA_MAX + 1];
+			if (rtnh->rtnh_len > sizeof (*rtnh)) {
+				struct nlattr *ntb[G_N_ELEMENTS (policy)];
 
-				err = nla_parse (ntb, RTA_MAX, (struct nlattr *)
-				                 RTNH_DATA(rtnh),
-				                 rtnh->rtnh_len - sizeof (*rtnh),
-				                 policy);
-				if (err < 0)
+				if (nla_parse_arr (ntb,
+				                   (struct nlattr *) RTNH_DATA (rtnh),
+				                   rtnh->rtnh_len - sizeof (*rtnh),
+				                   policy) < 0)
 					return NULL;
 
 				if (_check_addr_or_return_null (ntb, RTA_GATEWAY, addr_len))
 					memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len);
 			}
 
-			tlen -= RTNH_ALIGN(rtnh->rtnh_len);
-			rtnh = RTNH_NEXT(rtnh);
+			if (tlen < RTNH_ALIGN (rtnh->rtnh_len) + sizeof (*rtnh))
+				goto rta_multipath_done;
+
+			tlen -= RTNH_ALIGN (rtnh->rtnh_len);
+			rtnh = RTNH_NEXT (rtnh);
 		}
+rta_multipath_done:
+		;
 	}
 
 	if (   tb[RTA_OIF]
@@ -3186,8 +3239,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 	mss = 0;
 	if (tb[RTA_METRICS]) {
-		struct nlattr *mtb[RTAX_MAX + 1];
-		static const struct nla_policy rtax_policy[RTAX_MAX + 1] = {
+		static const struct nla_policy rtax_policy[] = {
 			[RTAX_LOCK]        = { .type = NLA_U32 },
 			[RTAX_ADVMSS]      = { .type = NLA_U32 },
 			[RTAX_WINDOW]      = { .type = NLA_U32 },
@@ -3196,9 +3248,9 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			[RTAX_INITRWND]    = { .type = NLA_U32 },
 			[RTAX_MTU]         = { .type = NLA_U32 },
 		};
+		struct nlattr *mtb[G_N_ELEMENTS (rtax_policy)];
 
-		err = nla_parse_nested (mtb, RTAX_MAX, tb[RTA_METRICS], rtax_policy);
-		if (err < 0)
+		if (nla_parse_nested_arr (mtb, tb[RTA_METRICS], rtax_policy) < 0)
 			return NULL;
 
 		if (mtb[RTAX_LOCK])
@@ -3233,7 +3285,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_route.plen = rtm->rtm_dst_len;
 
 	if (tb[RTA_PRIORITY])
-		obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]);
+		obj->ip_route.metric = nla_get_u32 (tb[RTA_PRIORITY]);
 
 	if (is_v4)
 		obj->ip4_route.gateway = nh.gateway.addr4;
@@ -3290,25 +3342,24 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 static NMPObject *
 _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 {
-	NMPObject *obj = NULL;
-	const struct tcmsg *tcm;
-	struct nlattr *tb[TCA_MAX + 1];
-	int err;
-	static const struct nla_policy policy[TCA_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	const struct tcmsg *tcm;
+	NMPObject *obj;
 
-	if (!nlmsg_valid_hdr (nlh, sizeof (*tcm)))
-		return NULL;
-	tcm = nlmsg_data (nlh);
-
-	err = nlmsg_parse (nlh, sizeof (*tcm), tb, TCA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh,
+	                     sizeof (*tcm),
+	                     tb,
+	                     policy) < 0)
 		return NULL;
 
 	if (!tb[TCA_KIND])
 		return NULL;
 
+	tcm = nlmsg_data (nlh);
+
 	obj = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL);
 
 	obj->qdisc.kind = g_intern_string (nla_get_string (tb[TCA_KIND]));
@@ -3324,25 +3375,21 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 static NMPObject *
 _new_from_nl_tfilter (struct nlmsghdr *nlh, gboolean id_only)
 {
-	NMPObject *obj = NULL;
-	const struct tcmsg *tcm;
-	struct nlattr *tb[TCA_MAX + 1];
-	int err;
-	static const struct nla_policy policy[TCA_MAX + 1] = {
+	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
 	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	NMPObject *obj = NULL;
+	const struct tcmsg *tcm;
 
-	if (!nlmsg_valid_hdr (nlh, sizeof (*tcm)))
-		return NULL;
-	tcm = nlmsg_data (nlh);
-
-	err = nlmsg_parse (nlh, sizeof (*tcm), tb, TCA_MAX, policy);
-	if (err < 0)
+	if (nlmsg_parse_arr (nlh, sizeof (*tcm), tb, policy) < 0)
 		return NULL;
 
 	if (!tb[TCA_KIND])
 		return NULL;
 
+	tcm = nlmsg_data (nlh);
+
 	obj = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL);
 
 	obj->tfilter.kind = g_intern_string (nla_get_string (tb[TCA_KIND]));
@@ -3464,14 +3511,14 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
 
 	if (veth_peer) {
-		struct ifinfomsg ifi = { };
+		const struct ifinfomsg ifi = { };
 		struct nlattr *data, *info_peer;
 
 		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
 			goto nla_put_failure;
 		if (!(info_peer = nla_nest_start (msg, 1 /*VETH_INFO_PEER*/)))
 			goto nla_put_failure;
-		if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+		if (nlmsg_append_struct (msg, &ifi) < 0)
 			goto nla_put_failure;
 		NLA_PUT_STRING (msg, IFLA_IFNAME, veth_peer);
 		nla_nest_end (msg, info_peer);
@@ -3575,7 +3622,7 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 		for (i = 0; i < egress_qos_len; i++) {
 			if (VLAN_XGRESS_PRIO_VALID (egress_qos[i].to)) {
 				if (!qos) {
-					if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
+					if (!(qos = nla_nest_start (msg, IFLA_VLAN_EGRESS_QOS)))
 						goto nla_put_failure;
 				}
 				NLA_PUT (msg, i, sizeof (egress_qos[i]), &egress_qos[i]);
@@ -3583,7 +3630,7 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 		}
 
 		if (qos)
-			nla_nest_end(msg, qos);
+			nla_nest_end (msg, qos);
 	}
 
 	nla_nest_end (msg, data);
@@ -3602,8 +3649,8 @@ _nl_msg_new_link (int nlmsg_type,
                   unsigned flags_mask,
                   unsigned flags_set)
 {
-	struct nl_msg *msg;
-	struct ifinfomsg ifi = {
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const struct ifinfomsg ifi = {
 		.ifi_change = flags_mask,
 		.ifi_flags = flags_set,
 		.ifi_index = ifindex,
@@ -3613,15 +3660,15 @@ _nl_msg_new_link (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
-	if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &ifi) < 0)
 		goto nla_put_failure;
 
 	if (ifname)
 		NLA_PUT_STRING (msg, IFLA_IFNAME, ifname);
 
-	return msg;
+	return g_steal_pointer (&msg);
+
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3640,7 +3687,7 @@ _nl_msg_new_address (int nlmsg_type,
                      guint32 preferred,
                      const char *label)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct ifaddrmsg am = {
 		.ifa_family = family,
 		.ifa_index = ifindex,
@@ -3667,7 +3714,7 @@ _nl_msg_new_address (int nlmsg_type,
 
 	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
 
-	if (nlmsg_append (msg, &am, sizeof (am), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &am) < 0)
 		goto nla_put_failure;
 
 	if (address)
@@ -3698,7 +3745,7 @@ _nl_msg_new_address (int nlmsg_type,
 			.ifa_prefered = preferred,
 		};
 
-		NLA_PUT (msg, IFA_CACHEINFO, sizeof(ca), &ca);
+		NLA_PUT (msg, IFA_CACHEINFO, sizeof (ca), &ca);
 	}
 
 	if (flags & ~((guint32) 0xFF)) {
@@ -3711,10 +3758,9 @@ _nl_msg_new_address (int nlmsg_type,
 		NLA_PUT_U32 (msg, IFA_FLAGS, flags);
 	}
 
-	return msg;
+	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3734,12 +3780,12 @@ _nl_msg_new_route (int nlmsg_type,
                    guint16 nlmsgflags,
                    const NMPObject *obj)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
 	gboolean is_v4 = klass->addr_family == AF_INET;
 	const guint32 lock = ip_route_get_lock_flag (NMP_OBJECT_CAST_IP_ROUTE (obj));
 	const guint32 table = nm_platform_route_table_uncoerce (NMP_OBJECT_CAST_IP_ROUTE (obj)->table_coerced, TRUE);
-	struct rtmsg rtmsg = {
+	const struct rtmsg rtmsg = {
 		.rtm_family = klass->addr_family,
 		.rtm_tos = is_v4
 		           ? obj->ip4_route.tos
@@ -3766,7 +3812,7 @@ _nl_msg_new_route (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags);
 
-	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &rtmsg) < 0)
 		goto nla_put_failure;
 
 	addr_len = is_v4
@@ -3824,7 +3870,7 @@ _nl_msg_new_route (int nlmsg_type,
 		if (lock)
 			NLA_PUT_U32 (msg, RTAX_LOCK, lock);
 
-		nla_nest_end(msg, metrics);
+		nla_nest_end (msg, metrics);
 	}
 
 	/* We currently don't have need for multi-hop routes... */
@@ -3840,10 +3886,9 @@ _nl_msg_new_route (int nlmsg_type,
 	    && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM)
 		NLA_PUT_U8 (msg, RTA_PREF, obj->ip6_route.rt_pref);
 
-	return msg;
+	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3852,8 +3897,8 @@ _nl_msg_new_qdisc (int nlmsg_type,
                    int nlmsg_flags,
                    const NMPlatformQdisc *qdisc)
 {
-	struct nl_msg *msg;
-	struct tcmsg tcm = {
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const struct tcmsg tcm = {
 		.tcm_family = qdisc->addr_family,
 		.tcm_ifindex = qdisc->ifindex,
 		.tcm_handle = qdisc->handle,
@@ -3863,14 +3908,14 @@ _nl_msg_new_qdisc (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
-	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &tcm) < 0)
 		goto nla_put_failure;
 
 	NLA_PUT_STRING (msg, TCA_KIND, qdisc->kind);
 
-	return msg;
+	return g_steal_pointer (&msg);
+
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -3924,10 +3969,10 @@ _nl_msg_new_tfilter (int nlmsg_type,
                      int nlmsg_flags,
                      const NMPlatformTfilter *tfilter)
 {
-	struct nl_msg *msg;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nlattr *tc_options;
 	struct nlattr *act_tab;
-	struct tcmsg tcm = {
+	const struct tcmsg tcm = {
 		.tcm_family = tfilter->addr_family,
 		.tcm_ifindex = tfilter->ifindex,
 		.tcm_handle = tfilter->handle,
@@ -3937,7 +3982,7 @@ _nl_msg_new_tfilter (int nlmsg_type,
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
-	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
+	if (nlmsg_append_struct (msg, &tcm) < 0)
 		goto nla_put_failure;
 
 	NLA_PUT_STRING (msg, TCA_KIND, tfilter->kind);
@@ -3955,9 +4000,9 @@ _nl_msg_new_tfilter (int nlmsg_type,
 
 	nla_nest_end (msg, act_tab);
 
-	return msg;
+	return g_steal_pointer (&msg);
+
 nla_put_failure:
-	nlmsg_free (msg);
 	g_return_val_if_reached (NULL);
 }
 
@@ -4052,10 +4097,10 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			errsv = errno;
 			if (errsv == ENOENT) {
 				_LOGD ("sysctl: failed to open '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			} else {
 				_LOGE ("sysctl: failed to open '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			}
 			errno = errsv;
 			return FALSE;
@@ -4066,10 +4111,10 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			errsv = errno;
 			if (errsv == ENOENT) {
 				_LOGD ("sysctl: failed to openat '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			} else {
 				_LOGE ("sysctl: failed to openat '%s': (%d) %s",
-				       pathid, errsv, strerror (errsv));
+				       pathid, errsv, nm_strerror_native (errsv));
 			}
 			errno = errsv;
 			return FALSE;
@@ -4119,7 +4164,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 		}
 
 		_NMLOG (level, "sysctl: failed to set '%s' to '%s': (%d) %s",
-		        path, value, errsv, strerror (errsv));
+		        path, value, errsv, nm_strerror_native (errsv));
 	} else if (nwrote < len - 1) {
 		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
 		       path, value);
@@ -4146,8 +4191,8 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 
 static GSList *sysctl_clear_cache_list;
 
-static void
-_nm_logging_clear_platform_logging_cache_impl (void)
+void
+_nm_logging_clear_platform_logging_cache (void)
 {
 	while (sysctl_clear_cache_list) {
 		NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (sysctl_clear_cache_list->data);
@@ -4156,42 +4201,71 @@ _nm_logging_clear_platform_logging_cache_impl (void)
 
 		g_hash_table_destroy (priv->sysctl_get_prev_values);
 		priv->sysctl_get_prev_values = NULL;
-		priv->sysctl_get_warned = FALSE;
 	}
 }
 
+typedef struct {
+	const char *path;
+	CList lst;
+	char *value;
+	char path_data[];
+} SysctlCacheEntry;
+
+static void
+sysctl_cache_entry_free (SysctlCacheEntry *entry)
+{
+	c_list_unlink_stale (&entry->lst);
+	g_free (entry->value);
+	g_free (entry);
+}
+
 static void
 _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *pathid, const char *contents)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const char *prev_value = NULL;
+	SysctlCacheEntry *entry = NULL;
 
 	if (!priv->sysctl_get_prev_values) {
-		_nm_logging_clear_platform_logging_cache = _nm_logging_clear_platform_logging_cache_impl;
 		sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform);
-		priv->sysctl_get_prev_values = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
+		c_list_init (&priv->sysctl_list);
+		priv->sysctl_get_prev_values = g_hash_table_new_full (nm_pstr_hash,
+		                                                      nm_pstr_equal,
+		                                                      (GDestroyNotify) sysctl_cache_entry_free,
+		                                                      NULL);
 	} else
-		prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, pathid);
+		entry = g_hash_table_lookup (priv->sysctl_get_prev_values, &pathid);
 
-	if (prev_value) {
-		if (strcmp (prev_value, contents) != 0) {
+	if (entry) {
+		if (!nm_streq (entry->value, contents)) {
 			gs_free char *contents_escaped = g_strescape (contents, NULL);
-			gs_free char *prev_value_escaped = g_strescape (prev_value, NULL);
+			gs_free char *prev_value_escaped = g_strescape (entry->value, NULL);
 
 			_LOGD ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", pathid, contents_escaped, prev_value_escaped);
-			g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (pathid), g_strdup (contents));
+			g_free (entry->value);
+			entry->value = g_strdup (contents);
 		}
+		nm_c_list_move_front (&priv->sysctl_list, &entry->lst);
 	} else {
 		gs_free char *contents_escaped = g_strescape (contents, NULL);
+		SysctlCacheEntry *old;
+		size_t len;
+
+		len = strlen (pathid);
+		entry = g_malloc (sizeof (SysctlCacheEntry) + len + 1);
+		entry->value = g_strdup (contents);
+		entry->path = entry->path_data;
+		memcpy (entry->path_data, pathid, len + 1);
+
+		/* Remove oldest entry when the cache becomes too big */
+		if (g_hash_table_size (priv->sysctl_get_prev_values) > 1000) {
+			old = c_list_last_entry (&priv->sysctl_list, SysctlCacheEntry, lst);
+			g_hash_table_remove (priv->sysctl_get_prev_values, old);
+		}
 
 		_LOGD ("sysctl: reading '%s': '%s'", pathid, contents_escaped);
-		g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (pathid), g_strdup (contents));
 
-		if (   !priv->sysctl_get_warned
-		    && g_hash_table_size (priv->sysctl_get_prev_values) > 50000) {
-			_LOGW ("sysctl: the internal cache for debug-logging of sysctl values grew pretty large. You can clear it by disabling debug-logging: `nmcli general logging level KEEP domains PLATFORM:INFO`.");
-			priv->sysctl_get_warned = TRUE;
-		}
+		g_hash_table_add (priv->sysctl_get_prev_values, entry);
+		c_list_link_front (&priv->sysctl_list, &entry->lst);
 	}
 }
 
@@ -4575,9 +4649,8 @@ delayed_action_handle_one (NMPlatform *platform)
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
 
 		if (_LOGt_ENABLED ()) {
-			FOR_EACH_DELAYED_ACTION (iflags, flags) {
+			FOR_EACH_DELAYED_ACTION (iflags, flags)
 				_LOGt_delayed_action (iflags, NULL, "handle");
-			}
 		}
 
 		delayed_action_handle_REFRESH_ALL (platform, flags);
@@ -4661,9 +4734,8 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 	priv->delayed_action.flags |= action_type;
 
 	if (_LOGt_ENABLED ()) {
-		FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+		FOR_EACH_DELAYED_ACTION (iflags, action_type)
 			_LOGt_delayed_action (iflags, user_data, "schedule");
-		}
 	}
 }
 
@@ -4993,7 +5065,7 @@ _nl_send_nlmsghdr (NMPlatform *platform,
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	guint32 seq;
-	int nle;
+	int errsv;
 
 	nm_assert (nlhdr);
 
@@ -5010,7 +5082,7 @@ _nl_send_nlmsghdr (NMPlatform *platform,
 		};
 		struct msghdr msg = {
 			.msg_name = &nladdr,
-			.msg_namelen = sizeof(nladdr),
+			.msg_namelen = sizeof (nladdr),
 			.msg_iov = &iov,
 			.msg_iovlen = 1,
 		};
@@ -5022,13 +5094,13 @@ _nl_send_nlmsghdr (NMPlatform *platform,
 
 		try_count = 0;
 again:
-		nle = sendmsg (nl_socket_get_fd (priv->nlh), &msg, 0);
-		if (nle < 0) {
-			nle = errno;
-			if (nle == EINTR && try_count++ < 100)
+		errsv = sendmsg (nl_socket_get_fd (priv->nlh), &msg, 0);
+		if (errsv < 0) {
+			errsv = errno;
+			if (errsv == EINTR && try_count++ < 100)
 				goto again;
-			_LOGD ("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", g_strerror (nle), nle);
-			return -nle;
+			_LOGD ("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", nm_strerror_native (errsv), errsv);
+			return -nm_errno_from_native (errsv);
 		}
 	}
 
@@ -5125,6 +5197,59 @@ do_request_link (NMPlatform *platform, int ifindex, const char *name)
 	delayed_action_handle_all (platform, FALSE);
 }
 
+static struct nl_msg *
+_nl_msg_new_dump (NMPObjectType obj_type,
+                  int preferred_addr_family)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	const NMPClass *klass;
+
+	klass = nmp_class_from_type (obj_type);
+
+	nm_assert (klass);
+	nm_assert (klass->rtm_gettype > 0);
+
+	nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+
+	if (klass->addr_family != AF_UNSPEC) {
+		/* if the class specifies a particular address family, then it is preferred. */
+		nm_assert (NM_IN_SET (preferred_addr_family, AF_UNSPEC, klass->addr_family));
+		preferred_addr_family = klass->addr_family;
+	}
+
+	switch (klass->obj_type) {
+	case NMP_OBJECT_TYPE_QDISC:
+	case NMP_OBJECT_TYPE_TFILTER:
+		{
+			const struct tcmsg tcmsg = {
+				.tcm_family = preferred_addr_family,
+			};
+
+			if (nlmsg_append_struct (nlmsg, &tcmsg) < 0)
+				g_return_val_if_reached (NULL);
+		}
+		break;
+	case NMP_OBJECT_TYPE_LINK:
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		{
+			const struct rtgenmsg gmsg = {
+				.rtgen_family = preferred_addr_family,
+			};
+
+			if (nlmsg_append_struct (nlmsg, &gmsg) < 0)
+				g_return_val_if_reached (NULL);
+		}
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+	}
+
+	return g_steal_pointer (&nlmsg);
+}
+
 static void
 do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type)
 {
@@ -5135,16 +5260,18 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+		NMPLookup lookup;
+
 		priv->pruning[delayed_action_refresh_all_to_idx (iflags)] = TRUE;
+		nmp_lookup_init_obj_type (&lookup,
+		                          delayed_action_refresh_to_object_type (iflags));
 		nmp_cache_dirty_set_all (nm_platform_get_cache (platform),
-		                         delayed_action_refresh_to_object_type (iflags));
+		                         &lookup);
 	}
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
 		NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
-		const NMPClass *klass = nmp_class_from_type (obj_type);
 		nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-		int nle;
 		int *out_refresh_all_in_progress;
 
 		out_refresh_all_in_progress = &priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (iflags)];
@@ -5162,31 +5289,23 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 
 		event_handler_read_netlink (platform, FALSE);
 
-		/* reimplement
-		 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
-		 * because we need the sequence number.
-		 */
-		nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+		nlmsg = _nl_msg_new_dump (obj_type, AF_UNSPEC);
+		if (!nlmsg)
+			goto next_after_fail;
 
-		if (   klass->obj_type == NMP_OBJECT_TYPE_QDISC
-		    || klass->obj_type == NMP_OBJECT_TYPE_TFILTER) {
-			struct tcmsg tcmsg = {
-				.tcm_family = AF_UNSPEC,
-			};
-			nle = nlmsg_append (nlmsg, &tcmsg, sizeof (tcmsg), NLMSG_ALIGNTO);
-		} else {
-			struct rtgenmsg gmsg = {
-				.rtgen_family = klass->addr_family,
-			};
-			nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
-		}
-		if (nle < 0)
-			continue;
+		if (_nl_send_nlmsg (platform,
+		                    nlmsg,
+		                    NULL,
+		                    NULL,
+		                    DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS,
+		                    out_refresh_all_in_progress) < 0)
+			goto next_after_fail;
 
-		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progress) < 0) {
-			nm_assert (*out_refresh_all_in_progress > 0);
-			*out_refresh_all_in_progress -= 1;
-		}
+		continue;
+
+next_after_fail:
+		nm_assert (*out_refresh_all_in_progress > 0);
+		*out_refresh_all_in_progress -= 1;
 	}
 }
 
@@ -5273,9 +5392,9 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	NMPCacheOpsType cache_op;
 	struct nlmsghdr *msghdr;
 	char buf_nlmsghdr[400];
-	gboolean id_only = FALSE;
+	gboolean is_del = FALSE;
+	gboolean is_dump = FALSE;
 	NMPCache *cache = nm_platform_get_cache (platform);
-	gboolean is_dump;
 
 	msghdr = nlmsg_hdr (msg);
 
@@ -5286,37 +5405,38 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	if (!handle_events)
 		return;
 
-	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK, RTM_DELADDR, RTM_DELROUTE)) {
+	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK,
+	                                   RTM_DELADDR,
+	                                   RTM_DELROUTE,
+	                                   RTM_DELQDISC,
+	                                   RTM_DELTFILTER)) {
 		/* The event notifies about a deleted object. We don't need to initialize all
 		 * fields of the object. */
-		id_only = TRUE;
+		is_del = TRUE;
 	}
 
-	obj = nmp_object_new_from_nl (platform, cache, msg, id_only);
+	obj = nmp_object_new_from_nl (platform, cache, msg, is_del);
 	if (!obj) {
 		_LOGT ("event-notification: %s: ignore",
 		       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 		return;
 	}
 
-	switch (msghdr->nlmsg_type) {
-	case RTM_NEWADDR:
-	case RTM_NEWLINK:
-	case RTM_NEWROUTE:
-	case RTM_NEWQDISC:
-	case RTM_NEWTFILTER:
+	if (   !is_del
+	    && NM_IN_SET (msghdr->nlmsg_type, RTM_NEWADDR,
+	                                      RTM_NEWLINK,
+	                                      RTM_NEWROUTE,
+	                                      RTM_NEWQDISC,
+	                                      RTM_NEWTFILTER)) {
 		is_dump = delayed_action_refresh_all_in_progress (platform,
 		                                                  delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)));
-		break;
-	default:
-		is_dump = FALSE;
 	}
 
 	_LOGT ("event-notification: %s%s: %s",
 	       nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
 	       is_dump ? ", in-dump" : "",
 	       nmp_object_to_string (obj,
-	                             id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
+	                             is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
 	                             NULL, 0));
 
 	{
@@ -6067,6 +6187,7 @@ link_set_sriov_params (NMPlatform *platform,
 	gint64 current_num;
 	char ifname[IFNAMSIZ];
 	char buf[64];
+	int errsv;
 
 	if (!nm_platform_netns_push (platform, &netns))
 		return FALSE;
@@ -6114,7 +6235,8 @@ link_set_sriov_params (NMPlatform *platform,
 		                                                       ifname,
 		                                                      "device/sriov_numvfs"),
 		                             "0")) {
-			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", strerror (errno));
+			errsv = errno;
+			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", nm_strerror_native (errsv));
 			return FALSE;
 		}
 	}
@@ -6129,7 +6251,8 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                          ifname,
 	                                                          "device/sriov_drivers_autoprobe"),
 	                                nm_sprintf_buf (buf, "%d", (int) autoprobe))) {
-		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, strerror (errno));
+		errsv = errno;
+		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -6138,7 +6261,8 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                       ifname,
 	                                                       "device/sriov_numvfs"),
 	                             nm_sprintf_buf (buf, "%u", num_vfs))) {
-		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, strerror (errno));
+		errsv = errno;
+		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -7373,7 +7497,7 @@ ip6_address_add (NMPlatform *platform,
 	                             ifindex,
 	                             &addr,
 	                             plen,
-	                             &peer_addr,
+	                             IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
 	                             flags,
 	                             RT_SCOPE_UNIVERSE,
 	                             lifetime,
@@ -7548,7 +7672,7 @@ ip_route_get (NMPlatform *platform,
 		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, NULL, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
 		if (nle < 0) {
 			_LOGE ("get-route: failure sending netlink request \"%s\" (%d)",
-			       g_strerror (-nle), -nle);
+			       nm_strerror_native (-nle), -nle);
 			return -NME_UNSPEC;
 		}
 
@@ -7792,20 +7916,22 @@ continue_reading:
 				err = -NME_NL_MSG_TRUNC;
 				abort_parsing = TRUE;
 			} else if (e->error) {
-				int errsv = e->error > 0 ? e->error : -e->error;
+				int errsv = nm_errno_native (e->error);
 
 				if (   NM_FLAGS_HAS (hdr->nlmsg_flags, NLM_F_ACK_TLVS)
 				    && hdr->nlmsg_len >= sizeof (*e) + e->msg.nlmsg_len) {
-					static const struct nla_policy policy[NLMSGERR_ATTR_MAX + 1] = {
+					static const struct nla_policy policy[] = {
 						[NLMSGERR_ATTR_MSG]     = { .type = NLA_STRING },
 						[NLMSGERR_ATTR_OFFS]    = { .type = NLA_U32 },
 					};
-					struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
+					struct nlattr *tb[G_N_ELEMENTS (policy)];
 					struct nlattr *tlvs;
 
 					tlvs = (struct nlattr *) ((char *) e + sizeof (*e) + e->msg.nlmsg_len - NLMSG_HDRLEN);
-					if (!nla_parse (tb, NLMSGERR_ATTR_MAX, tlvs,
-					                hdr->nlmsg_len - sizeof (*e) - e->msg.nlmsg_len, policy)) {
+					if (nla_parse_arr (tb,
+					                   tlvs,
+					                   hdr->nlmsg_len - sizeof (*e) - e->msg.nlmsg_len,
+					                   policy) >= 0) {
 						if (tb[NLMSGERR_ATTR_MSG])
 							extack_msg = nla_get_string (tb[NLMSGERR_ATTR_MSG]);
 					}
@@ -7813,11 +7939,11 @@ continue_reading:
 
 				/* Error message reported back from kernel. */
 				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
-				       strerror (errsv),
+				       nm_strerror_native (errsv),
 				       errsv,
 				       NM_PRINT_FMT_QUOTED (extack_msg, " \"", extack_msg, "\"", ""),
 				       nlmsg_hdr (msg)->nlmsg_seq);
-				seq_result = -errsv;
+				seq_result = -NM_ERRNO_NATIVE (errsv);
 			} else
 				seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 		} else
@@ -7976,7 +8102,7 @@ after_read:
 			int errsv = errno;
 
 			if (errsv != EINTR) {
-				_LOGE ("netlink: read: poll failed with %s", strerror (errsv));
+				_LOGE ("netlink: read: poll failed with %s", nm_strerror_native (errsv));
 				delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL);
 				return any;
 			}
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
index 6abc1c3a..71506a2c 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -25,8 +25,6 @@
 #include <unistd.h>
 #include <fcntl.h>
 
-#include "nm-utils/nm-errno.h"
-
 /*****************************************************************************/
 
 #ifndef SOL_NETLINK
@@ -104,21 +102,30 @@ nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
 	b = buf;
 
 	switch (hdr->nlmsg_type) {
-	case RTM_NEWLINK:    s = "RTM_NEWLINK";  break;
-	case RTM_DELLINK:    s = "RTM_DELLINK";  break;
-	case RTM_NEWADDR:    s = "RTM_NEWADDR";  break;
-	case RTM_DELADDR:    s = "RTM_DELADDR";  break;
-	case RTM_NEWROUTE:   s = "RTM_NEWROUTE"; break;
-	case RTM_DELROUTE:   s = "RTM_DELROUTE"; break;
-	case RTM_NEWQDISC:   s = "RTM_NEWQDISC"; break;
-	case RTM_DELQDISC:   s = "RTM_DELQDISC"; break;
-	case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break;
-	case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break;
-	case NLMSG_NOOP:     s = "NLMSG_NOOP"; break;
-	case NLMSG_ERROR:    s = "NLMSG_ERROR"; break;
-	case NLMSG_DONE:     s = "NLMSG_DONE"; break;
-	case NLMSG_OVERRUN:  s = "NLMSG_OVERRUN"; break;
-	default:             s = NULL;       break;
+	case RTM_GETLINK:    s = "RTM_GETLINK";     break;
+	case RTM_NEWLINK:    s = "RTM_NEWLINK";     break;
+	case RTM_DELLINK:    s = "RTM_DELLINK";     break;
+	case RTM_SETLINK:    s = "RTM_SETLINK";     break;
+	case RTM_GETADDR:    s = "RTM_GETADDR";     break;
+	case RTM_NEWADDR:    s = "RTM_NEWADDR";     break;
+	case RTM_DELADDR:    s = "RTM_DELADDR";     break;
+	case RTM_GETROUTE:   s = "RTM_GETROUTE";    break;
+	case RTM_NEWROUTE:   s = "RTM_NEWROUTE";    break;
+	case RTM_DELROUTE:   s = "RTM_DELROUTE";    break;
+	case RTM_GETRULE:    s = "RTM_GETRULE";     break;
+	case RTM_NEWRULE:    s = "RTM_NEWRULE";     break;
+	case RTM_DELRULE:    s = "RTM_DELRULE";     break;
+	case RTM_GETQDISC:   s = "RTM_GETQDISC";    break;
+	case RTM_NEWQDISC:   s = "RTM_NEWQDISC";    break;
+	case RTM_DELQDISC:   s = "RTM_DELQDISC";    break;
+	case RTM_GETTFILTER: s = "RTM_GETTFILTER";  break;
+	case RTM_NEWTFILTER: s = "RTM_NEWTFILTER";  break;
+	case RTM_DELTFILTER: s = "RTM_DELTFILTER";  break;
+	case NLMSG_NOOP:     s = "NLMSG_NOOP";      break;
+	case NLMSG_ERROR:    s = "NLMSG_ERROR";     break;
+	case NLMSG_DONE:     s = "NLMSG_DONE";      break;
+	case NLMSG_OVERRUN:  s = "NLMSG_OVERRUN";   break;
+	default:             s = NULL;              break;
 	}
 
 	if (s)
@@ -207,6 +214,8 @@ nlmsg_reserve (struct nl_msg *n, size_t len, int pad)
 	size_t nlmsg_len = n->nm_nlh->nlmsg_len;
 	size_t tlen;
 
+	nm_assert (pad >= 0);
+
 	if (len > n->nm_size)
 		return NULL;
 
@@ -261,11 +270,12 @@ nlmsg_alloc_size (size_t len)
 	if (len < sizeof (struct nlmsghdr))
 		len = sizeof (struct nlmsghdr);
 
-	nm = g_slice_new0 (struct nl_msg);
-
-	nm->nm_protocol = -1;
-	nm->nm_size = len;
-	nm->nm_nlh = g_malloc0 (len);
+	nm = g_slice_new (struct nl_msg);
+	*nm = (struct nl_msg) {
+		.nm_protocol = -1,
+		.nm_size = len,
+		.nm_nlh = g_malloc0 (len),
+	};
 	nm->nm_nlh->nlmsg_len = nlmsg_total_size (0);
 	return nm;
 }
@@ -320,10 +330,18 @@ void nlmsg_free (struct nl_msg *msg)
 /*****************************************************************************/
 
 int
-nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
+nlmsg_append (struct nl_msg *n,
+              const void *data,
+              size_t len,
+              int pad)
 {
 	void *tmp;
 
+	nm_assert (n);
+	nm_assert (data);
+	nm_assert (len > 0);
+	nm_assert (pad >= 0);
+
 	tmp = nlmsg_reserve (n, len, pad);
 	if (tmp == NULL)
 		return -ENOMEM;
@@ -367,48 +385,77 @@ nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
 	return nlh;
 }
 
-uint64_t
-nla_get_u64 (const struct nlattr *nla)
-{
-	uint64_t tmp = 0;
-
-	if (nla && nla_len (nla) >= sizeof (tmp))
-		memcpy (&tmp, nla_data (nla), sizeof (tmp));
-
-	return tmp;
-}
-
 size_t
-nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize)
-{
-	size_t srclen = nla_len (nla);
-	const char *src = nla_data (nla);
-
-	if (srclen > 0 && src[srclen - 1] == '\0')
-		srclen--;
+nla_strlcpy (char *dst,
+             const struct nlattr *nla,
+             size_t dstsize)
+{
+	const char *src;
+	size_t srclen;
+	size_t len;
+
+	/* - Always writes @dstsize bytes to @dst
+	 * - Copies the first non-NUL characters to @dst.
+	 *   Any characters after the first NUL bytes in @nla are ignored.
+	 * - If the string @nla is longer than @dstsize, the string
+	 *   gets truncated. @dst will always be NUL terminated. */
+
+	if (G_UNLIKELY (dstsize <= 1)) {
+		if (dstsize == 1)
+			dst[0] = '\0';
+		if (   nla
+		    && (srclen = nla_len (nla)) > 0)
+			return strnlen (nla_data (nla), srclen);
+		return 0;
+	}
 
-	if (dstsize > 0) {
-		size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen;
+	nm_assert (dst);
 
-		memset (dst, 0, dstsize);
-		memcpy (dst, src, len);
+	if (nla) {
+		srclen = nla_len (nla);
+		if (srclen > 0) {
+			src = nla_data (nla);
+			srclen = strnlen (src, srclen);
+			if (srclen > 0) {
+				len = NM_MIN (dstsize - 1, srclen);
+				memcpy (dst, src, len);
+				memset (&dst[len], 0, dstsize - len);
+				return srclen;
+			}
+		}
 	}
 
-	return srclen;
+	memset (dst, 0, dstsize);
+	return 0;
 }
 
-int
-nla_memcpy (void *dest, const struct nlattr *src, int count)
+size_t
+nla_memcpy (void *dst, const struct nlattr *nla, size_t dstsize)
 {
-	int minlen;
+	size_t len;
+	int srclen;
 
-	if (!src)
+	if (!nla)
 		return 0;
 
-	minlen = NM_MIN (count, (int) nla_len (src));
-	memcpy (dest, nla_data (src), minlen);
+	srclen = nla_len (nla);
 
-	return minlen;
+	if (srclen <= 0) {
+		nm_assert (srclen == 0);
+		return 0;
+	}
+
+	len = NM_MIN ((size_t) srclen, dstsize);
+	if (len > 0) {
+		/* there is a crucial difference between nla_strlcpy() and nla_memcpy().
+		 * The former always write @dstsize bytes (akin to strncpy()), here, we only
+		 * write the bytes that we actually have (leaving the remainder undefined). */
+		memcpy (dst,
+		        nla_data (nla),
+		        len);
+	}
+
+	return srclen;
 }
 
 int
@@ -548,7 +595,11 @@ validate_nla (const struct nlattr *nla, int maxtype,
 		return -NME_UNSPEC;
 
 	if (pt->type == NLA_STRING) {
-		const char *data = nla_data (nla);
+		const char *data;
+
+		nm_assert (minlen > 0);
+
+		data = nla_data (nla);
 		if (data[nla_len (nla) - 1] != '\0')
 			return -NME_UNSPEC;
 	}
@@ -574,15 +625,13 @@ nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
 		if (policy) {
 			nmerr = validate_nla (nla, maxtype, policy);
 			if (nmerr < 0)
-				goto errout;
+				return nmerr;
 		}
 
 		tb[type] = nla;
 	}
 
-	nmerr = 0;
-errout:
-	return nmerr;
+	return 0;
 }
 
 /*****************************************************************************/
@@ -721,21 +770,21 @@ genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
 static int
 _genl_parse_getfamily (struct nl_msg *msg, void *arg)
 {
-	static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
+	static const struct nla_policy ctrl_policy[] = {
 		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
 		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
-		                            .maxlen = GENL_NAMSIZ },
+		                             .maxlen = GENL_NAMSIZ },
 		[CTRL_ATTR_VERSION]      = { .type = NLA_U32 },
 		[CTRL_ATTR_HDRSIZE]      = { .type = NLA_U32 },
 		[CTRL_ATTR_MAXATTR]      = { .type = NLA_U32 },
 		[CTRL_ATTR_OPS]          = { .type = NLA_NESTED },
 		[CTRL_ATTR_MCAST_GROUPS] = { .type = NLA_NESTED },
 	};
-	struct nlattr *tb[CTRL_ATTR_MAX+1];
+	struct nlattr *tb[G_N_ELEMENTS (ctrl_policy)];
 	struct nlmsghdr *nlh = nlmsg_hdr (msg);
 	gint32 *response_data = arg;
 
-	if (genlmsg_parse (nlh, 0, tb, CTRL_ATTR_MAX, ctrl_policy))
+	if (genlmsg_parse_arr (nlh, 0, tb, ctrl_policy) < 0)
 		return NL_SKIP;
 
 	if (tb[CTRL_ATTR_FAMILY_ID])
@@ -1061,7 +1110,7 @@ do { \
 		nmerr = _cb->type##_cb ((msg), _cb->type##_arg); \
 		switch (nmerr) { \
 		case NL_OK: \
-			nmerr = 0; \
+			nm_assert (nmerr == 0); \
 			break; \
 		case NL_SKIP: \
 			goto skip; \
@@ -1206,6 +1255,7 @@ skip:
 		/* Multipart message not yet complete, continue reading */
 		nm_clear_g_free (&buf);
 
+		nmerr = 0;
 		goto continue_reading;
 	}
 
@@ -1333,6 +1383,7 @@ nl_recv (struct nl_sock *sk,
 	struct ucred tmpcreds;
 	gboolean tmpcreds_has = FALSE;
 	int retval;
+	int errsv;
 
 	nm_assert (nla);
 	nm_assert (buf && !*buf);
@@ -1361,10 +1412,10 @@ retry:
 	}
 
 	if (n < 0) {
-		if (errno == EINTR)
+		errsv = errno;
+		if (errsv == EINTR)
 			goto retry;
-
-		retval = -nm_errno_from_native (errno);
+		retval = -nm_errno_from_native (errsv);
 		goto abort;
 	}
 
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index 84bbe27b..094a3c6f 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -25,6 +25,8 @@
 #include <linux/rtnetlink.h>
 #include <linux/genetlink.h>
 
+#include "nm-utils/unaligned.h"
+
 /*****************************************************************************/
 
 #define NLMSGERR_ATTR_UNUSED            0
@@ -87,8 +89,26 @@ struct nla_policy {
 
 /*****************************************************************************/
 
+/* static asserts that @tb and @policy are suitable arguments to nla_parse(). */
+#define _nl_static_assert_tb(tb, policy) \
+	G_STMT_START { \
+		\
+		G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (tb) > 0); \
+		\
+		/* we allow @policy to be either NULL or a C array. */ \
+		G_STATIC_ASSERT_EXPR (   sizeof (policy) == sizeof (NULL) \
+		                      || G_N_ELEMENTS (tb) == (sizeof (policy) / sizeof (struct nla_policy))); \
+		\
+		/* For above check to work, we don't support policy being an array with same size as
+		 * sizeof(NULL), otherwise, the compile time check breaks down. */ \
+		G_STATIC_ASSERT_EXPR (sizeof (NULL) != G_N_ELEMENTS (tb) * sizeof (struct nla_policy)); \
+		\
+	} G_STMT_END
+
+/*****************************************************************************/
+
 static inline int
-nla_attr_size(int payload)
+nla_attr_size (int payload)
 {
 	nm_assert (payload >= 0);
 
@@ -104,7 +124,7 @@ nla_total_size (int payload)
 static inline int
 nla_padlen (int payload)
 {
-	return nla_total_size(payload) - nla_attr_size(payload);
+	return nla_total_size (payload) - nla_attr_size (payload);
 }
 
 struct nlattr *nla_reserve (struct nl_msg *msg, int attrtype, int attrlen);
@@ -112,32 +132,56 @@ struct nlattr *nla_reserve (struct nl_msg *msg, int attrtype, int attrlen);
 static inline int
 nla_len (const struct nlattr *nla)
 {
-	return nla->nla_len - NLA_HDRLEN;
+	nm_assert (nla);
+	nm_assert (nla->nla_len >= NLA_HDRLEN);
+
+	return ((int) nla->nla_len) - NLA_HDRLEN;
 }
 
 static inline int
 nla_type (const struct nlattr *nla)
 {
+	nm_assert (nla_len (nla) >= 0);
+
 	return nla->nla_type & NLA_TYPE_MASK;
 }
 
 static inline void *
 nla_data (const struct nlattr *nla)
 {
-	nm_assert (nla);
-	return (char *) nla + NLA_HDRLEN;
+	nm_assert (nla_len (nla) >= 0);
+
+	return &(((char *) nla)[NLA_HDRLEN]);
 }
 
+#define nla_data_as(type, nla) \
+	({ \
+		const struct nlattr *_nla = (nla); \
+		\
+		nm_assert (nla_len (_nla) >= sizeof (type)); \
+		\
+		/* note that casting the pointer is undefined behavior in C, if
+		 * the data has wrong alignment. Netlink data is aligned to 4 bytes,
+		 * that means, if the alignment is larger than 4, this is invalid. */ \
+		G_STATIC_ASSERT_EXPR (_nm_alignof (type) <= NLA_ALIGNTO); \
+		\
+		(type *) nla_data (_nla); \
+	})
+
 static inline uint8_t
 nla_get_u8 (const struct nlattr *nla)
 {
-	return *(const uint8_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (uint8_t));
+
+	return *((const uint8_t *) nla_data (nla));
 }
 
-static inline uint8_t
+static inline int8_t
 nla_get_s8 (const struct nlattr *nla)
 {
-	return *(const int8_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (int8_t));
+
+	return *((const int8_t *) nla_data (nla));
 }
 
 static inline uint8_t
@@ -152,39 +196,80 @@ nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_va
 static inline uint16_t
 nla_get_u16 (const struct nlattr *nla)
 {
-	return *(const uint16_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (uint16_t));
+
+	return *((const uint16_t *) nla_data (nla));
 }
 
 static inline uint32_t
-nla_get_u32(const struct nlattr *nla)
+nla_get_u32 (const struct nlattr *nla)
 {
-	return *(const uint32_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (uint32_t));
+
+	return *((const uint32_t *) nla_data (nla));
 }
 
 static inline int32_t
-nla_get_s32(const struct nlattr *nla)
+nla_get_s32 (const struct nlattr *nla)
 {
-	return *(const int32_t *) nla_data (nla);
+	nm_assert (nla_len (nla) >= sizeof (int32_t));
+
+	return *((const int32_t *) nla_data (nla));
+}
+
+static inline uint64_t
+nla_get_u64 (const struct nlattr *nla)
+{
+	nm_assert (nla_len (nla) >= sizeof (uint64_t));
+
+	return unaligned_read_ne64 (nla_data (nla));
 }
 
-uint64_t nla_get_u64 (const struct nlattr *nla);
+static inline uint64_t
+nla_get_be64 (const struct nlattr *nla)
+{
+	nm_assert (nla_len (nla) >= sizeof (uint64_t));
+
+	return unaligned_read_be64 (nla_data (nla));
+}
 
 static inline char *
 nla_get_string (const struct nlattr *nla)
 {
+	nm_assert (nla_len (nla) >= 0);
+
 	return (char *) nla_data (nla);
 }
 
 size_t nla_strlcpy (char *dst, const struct nlattr *nla, size_t dstsize);
 
-int nla_memcpy (void *dest, const struct nlattr *src, int count);
+size_t nla_memcpy (void *dst, const struct nlattr *nla, size_t dstsize);
+
+#define nla_memcpy_checked_size(dst, nla, dstsize) \
+	G_STMT_START { \
+		void *const _dst = (dst); \
+		const struct nlattr *const _nla = (nla); \
+		const size_t _dstsize = (dstsize); \
+		size_t _srcsize; \
+		\
+		/* assert that, if @nla is given, that it has the exact expected
+		 * size. This implies that the caller previously verified the length
+		 * of the attribute (via minlen/maxlen at nla_parse()). */ \
+		\
+		if (_nla) { \
+			_srcsize = nla_memcpy (_dst, _nla, _dstsize); \
+			nm_assert (_srcsize == _dstsize); \
+		} \
+	} G_STMT_END
 
 int nla_put (struct nl_msg *msg, int attrtype, int datalen, const void *data);
 
 static inline int
 nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
 {
-	return nla_put(msg, attrtype, strlen(str) + 1, str);
+	nm_assert (str);
+
+	return nla_put (msg, attrtype, strlen (str) + 1, str);
 }
 
 static inline int
@@ -206,55 +291,55 @@ nla_put_uint32 (struct nl_msg *msg, int attrtype, uint32_t val)
 }
 
 #define NLA_PUT(msg, attrtype, attrlen, data) \
-	do { \
-		if (nla_put(msg, attrtype, attrlen, data) < 0) \
+	G_STMT_START { \
+		if (nla_put (msg, attrtype, attrlen, data) < 0) \
 			goto nla_put_failure; \
-	} while(0)
+	} G_STMT_END
 
 #define NLA_PUT_TYPE(msg, type, attrtype, value) \
-	do { \
+	G_STMT_START { \
 		type __nla_tmp = value; \
-		NLA_PUT(msg, attrtype, sizeof(type), &__nla_tmp); \
-	} while(0)
+		NLA_PUT (msg, attrtype, sizeof (type), &__nla_tmp); \
+	} G_STMT_END
 
 #define NLA_PUT_U8(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
+	NLA_PUT_TYPE (msg, uint8_t, attrtype, value)
 
 #define NLA_PUT_S8(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, int8_t, attrtype, value)
+	NLA_PUT_TYPE (msg, int8_t, attrtype, value)
 
 #define NLA_PUT_U16(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
+	NLA_PUT_TYPE (msg, uint16_t, attrtype, value)
 
 #define NLA_PUT_U32(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
+	NLA_PUT_TYPE (msg, uint32_t, attrtype, value)
 
 #define NLA_PUT_S32(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, int32_t, attrtype, value)
+	NLA_PUT_TYPE (msg, int32_t, attrtype, value)
 
 #define NLA_PUT_U64(msg, attrtype, value) \
-	NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
+	NLA_PUT_TYPE (msg, uint64_t, attrtype, value)
 
 #define NLA_PUT_STRING(msg, attrtype, value) \
-	NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
+	NLA_PUT (msg, attrtype, (int) strlen (value) + 1, value)
 
 #define NLA_PUT_FLAG(msg, attrtype) \
-	NLA_PUT(msg, attrtype, 0, NULL)
+	NLA_PUT (msg, attrtype, 0, NULL)
 
 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 >= (int) sizeof(*nla) &&
-	       nla->nla_len >= sizeof(*nla) &&
+	return remaining >= (int) sizeof (*nla) &&
+	       nla->nla_len >= sizeof (*nla) &&
 	       nla->nla_len <= remaining;
 }
 
 static inline struct nlattr *
-nla_next(const struct nlattr *nla, int *remaining)
+nla_next (const struct nlattr *nla, int *remaining)
 {
-	int totlen = NLA_ALIGN(nla->nla_len);
+	int totlen = NLA_ALIGN (nla->nla_len);
 
 	*remaining -= totlen;
 	return (struct nlattr *) ((char *) nla + totlen);
@@ -262,28 +347,47 @@ nla_next(const struct nlattr *nla, int *remaining)
 
 #define nla_for_each_attr(pos, head, len, rem) \
 	for (pos = head, rem = len; \
-	     nla_ok(pos, rem); \
-	     pos = nla_next(pos, &(rem)))
+	     nla_ok (pos, rem); \
+	     pos = nla_next (pos, &(rem)))
 
 #define nla_for_each_nested(pos, nla, rem) \
-	for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); \
-	     nla_ok(pos, rem); \
-	     pos = nla_next(pos, &(rem)))
+	for (pos = (struct nlattr *) nla_data (nla), rem = nla_len (nla); \
+	     nla_ok (pos, rem); \
+	     pos = nla_next (pos, &(rem)))
 
 void nla_nest_cancel (struct nl_msg *msg, const struct nlattr *attr);
 struct nlattr *nla_nest_start (struct nl_msg *msg, int attrtype);
 int nla_nest_end (struct nl_msg *msg, struct nlattr *start);
 
-int nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+int nla_parse (struct nlattr *tb[],
+               int maxtype,
+               struct nlattr *head,
+               int len,
                const struct nla_policy *policy);
 
+#define nla_parse_arr(tb, head, len, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		\
+		nla_parse ((tb), G_N_ELEMENTS (tb) - 1, (head), (len), (policy)); \
+	})
+
 static inline int
-nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+nla_parse_nested (struct nlattr *tb[],
+                  int maxtype,
+                  struct nlattr *nla,
                   const struct nla_policy *policy)
 {
-	return nla_parse (tb, maxtype, nla_data(nla), nla_len(nla), policy);
+	return nla_parse (tb, maxtype, nla_data (nla), nla_len (nla), policy);
 }
 
+#define nla_parse_nested_arr(tb, nla, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		\
+		nla_parse_nested ((tb), G_N_ELEMENTS (tb) - 1, (nla), (policy)); \
+	})
+
 /*****************************************************************************/
 
 struct nl_msg *nlmsg_alloc (void);
@@ -296,7 +400,13 @@ struct nl_msg *nlmsg_alloc_simple (int nlmsgtype, int flags);
 
 void *nlmsg_reserve (struct nl_msg *n, size_t len, int pad);
 
-int nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad);
+int nlmsg_append (struct nl_msg *n,
+                  const void *data,
+                  size_t len,
+                  int pad);
+
+#define nlmsg_append_struct(n, data) \
+	nlmsg_append (n, (data), sizeof (*(data)), NLMSG_ALIGNTO)
 
 void nlmsg_free (struct nl_msg *msg);
 
@@ -316,15 +426,15 @@ nlmsg_total_size (int payload)
 static inline int
 nlmsg_ok (const struct nlmsghdr *nlh, int remaining)
 {
-	return (remaining >= (int)sizeof(struct nlmsghdr) &&
-	       nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
+	return (remaining >= (int) sizeof (struct nlmsghdr) &&
+	       nlh->nlmsg_len >= sizeof (struct nlmsghdr) &&
 	       nlh->nlmsg_len <= remaining);
 }
 
 static inline struct nlmsghdr *
 nlmsg_next (struct nlmsghdr *nlh, int *remaining)
 {
-	int totlen = NLMSG_ALIGN(nlh->nlmsg_len);
+	int totlen = NLMSG_ALIGN (nlh->nlmsg_len);
 
 	*remaining -= totlen;
 
@@ -344,7 +454,7 @@ _nm_auto_nl_msg_cleanup (struct nl_msg **ptr)
 {
 	nlmsg_free (*ptr);
 }
-#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
+#define nm_auto_nlmsg nm_auto (_nm_auto_nl_msg_cleanup)
 
 static inline void *
 nlmsg_data (const struct nlmsghdr *nlh)
@@ -355,13 +465,13 @@ nlmsg_data (const struct nlmsghdr *nlh)
 static inline void *
 nlmsg_tail (const struct nlmsghdr *nlh)
 {
-	return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len);
+	return (unsigned char *) nlh + NLMSG_ALIGN (nlh->nlmsg_len);
 }
 
 struct nlmsghdr *nlmsg_hdr (struct nl_msg *n);
 
 static inline int
-nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
+nlmsg_valid_hdr (const struct nlmsghdr *nlh, int hdrlen)
 {
 	if (nlh->nlmsg_len < nlmsg_size (hdrlen))
 		return 0;
@@ -384,8 +494,8 @@ nlmsg_attrlen (const struct nlmsghdr *nlh, int hdrlen)
 static inline struct nlattr *
 nlmsg_attrdata (const struct nlmsghdr *nlh, int hdrlen)
 {
-	unsigned char *data = nlmsg_data(nlh);
-	return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
+	unsigned char *data = nlmsg_data (nlh);
+	return (struct nlattr *) (data + NLMSG_ALIGN (hdrlen));
 }
 
 static inline struct nlattr *
@@ -396,8 +506,19 @@ nlmsg_find_attr (struct nlmsghdr *nlh, int hdrlen, int attrtype)
 	                 attrtype);
 }
 
-int nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-                 int maxtype, const struct nla_policy *policy);
+int nlmsg_parse (struct nlmsghdr *nlh,
+                 int hdrlen,
+                 struct nlattr *tb[],
+                 int maxtype,
+                 const struct nla_policy *policy);
+
+#define nlmsg_parse_arr(nlh, hdrlen, tb, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		G_STATIC_ASSERT_EXPR ((hdrlen) >= 0); \
+		\
+		nlmsg_parse ((nlh), (hdrlen), (tb), G_N_ELEMENTS (tb) - 1, (policy)); \
+	})
 
 struct nlmsghdr *nlmsg_put (struct nl_msg *n, uint32_t pid, uint32_t seq,
                             int type, int payload, int flags);
@@ -499,8 +620,20 @@ struct nlattr *genlmsg_attrdata (const struct genlmsghdr *gnlh, int hdrlen);
 int genlmsg_len (const struct genlmsghdr *gnlh);
 int genlmsg_attrlen (const struct genlmsghdr *gnlh, int hdrlen);
 int genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen);
-int genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-                   int maxtype, const struct nla_policy *policy);
+
+int genlmsg_parse (struct nlmsghdr *nlh,
+                   int hdrlen,
+                   struct nlattr *tb[],
+                   int maxtype,
+                   const struct nla_policy *policy);
+
+#define genlmsg_parse_arr(nlh, hdrlen, tb, policy) \
+	({ \
+		_nl_static_assert_tb ((tb), (policy)); \
+		G_STATIC_ASSERT_EXPR ((hdrlen) >= 0); \
+		\
+		genlmsg_parse ((nlh), (hdrlen), (tb), G_N_ELEMENTS (tb) - 1, (policy)); \
+	})
 
 int genl_ctrl_resolve (struct nl_sock *sk, const char *name);
 
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 772d4667..93cd0b3c 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -22,9 +22,7 @@
 
 #include "nm-platform-utils.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <linux/ethtool.h>
 #include <linux/sockios.h>
@@ -86,7 +84,7 @@ socket_handle_init (SocketHandle *shandle, int ifindex)
 	shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 	if (shandle->fd < 0) {
 		shandle->ifindex = 0;
-		return -errno;
+		return -NM_ERRNO_NATIVE (errno);
 	}
 
 	shandle->ifindex = ifindex;
@@ -159,8 +157,8 @@ ethtool_call_handle (SocketHandle *shandle, gpointer edata)
 		              shandle->ifindex,
 		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
 		              shandle->ifname,
-		              strerror (errsv));
-		return -errsv;
+		              nm_strerror_native (errsv));
+		return -NM_ERRNO_NATIVE (errsv);
 	}
 
 	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
@@ -183,7 +181,7 @@ ethtool_call_ifindex (int ifindex, gpointer edata)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return r;
 	}
 
@@ -489,7 +487,7 @@ nmp_utils_ethtool_get_features (int ifindex)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "get-features",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -620,7 +618,7 @@ nmp_utils_ethtool_set_features (int ifindex,
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "set-features",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -656,7 +654,7 @@ nmp_utils_ethtool_set_features (int ifindex,
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
 		              ifindex,
 		              "set-features",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -766,7 +764,7 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "support-vlans",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -805,7 +803,7 @@ nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              "get-peer-ifindex",
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -894,7 +892,7 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
                          | ADVERTISED_1000baseT_Full \
                          | ADVERTISED_10000baseT_Full )
 
-static inline guint32
+static guint32
 get_baset_mode (guint32 speed, NMPlatformLinkDuplexType duplex)
 {
 	if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
@@ -1045,13 +1043,14 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	int r;
 	struct ifreq ifr;
 	struct mii_ioctl_data *mii;
+	int errsv;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
 	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
 		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s",
 		              ifindex,
-		              g_strerror (-r));
+		              nm_strerror_native (-r));
 		return FALSE;
 	}
 
@@ -1059,7 +1058,8 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ);
 
 	if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, strerror (errno));
+		errsv = errno;
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -1068,7 +1068,8 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	mii->reg_num = MII_BMSR;
 
 	if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, strerror (errno));
+		errsv = errno;
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 4bb31b17..fe0cb662 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -23,13 +23,11 @@
 #include "nm-platform.h"
 
 #include <stdlib.h>
-#include <errno.h>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <netdb.h>
-#include <string.h>
 #include <linux/ip.h>
 #include <linux/if.h>
 #include <linux/if_tun.h>
@@ -41,7 +39,7 @@
 #include "nm-core-internal.h"
 #include "nm-utils/nm-dedup-multi.h"
 #include "nm-utils/nm-udev-utils.h"
-#include "nm-utils/nm-errno.h"
+#include "nm-utils/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
@@ -1985,6 +1983,24 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
 
 /*****************************************************************************/
 
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_flags_to_string, NMPlatformWireGuardChangeFlags,
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE,            "none"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS,   "replace-peers"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY, "has-private-key"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT, "has-listen-port"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK,      "has-fwmark"),
+);
+
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_peer_flags_to_string, NMPlatformWireGuardChangePeerFlags,
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE,                   "none"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME,              "remove"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY,      "psk"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL, "ka"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT,           "ep"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS,         "aips"),
+	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS,     "remove-aips"),
+);
+
 int
 nm_platform_link_wireguard_add (NMPlatform *self,
                                 const char *name,
@@ -1998,8 +2014,9 @@ nm_platform_link_wireguard_change (NMPlatform *self,
                                    int ifindex,
                                    const NMPlatformLnkWireGuard *lnk_wireguard,
                                    const NMPWireGuardPeer *peers,
+                                   const NMPlatformWireGuardChangePeerFlags *peer_flags,
                                    guint peers_len,
-                                   gboolean replace_peers)
+                                   NMPlatformWireGuardChangeFlags change_flags)
 {
 	_CHECK_SELF (self, klass, -NME_BUG);
 
@@ -2008,6 +2025,7 @@ nm_platform_link_wireguard_change (NMPlatform *self,
 	if (_LOGD_ENABLED ()) {
 		char buf_lnk[256];
 		char buf_peers[512];
+		char buf_change_flags[100];
 
 		buf_peers[0] = '\0';
 		if (peers_len > 0) {
@@ -2020,25 +2038,31 @@ nm_platform_link_wireguard_change (NMPlatform *self,
 				nm_utils_strbuf_append_str (&b, &len, " { ");
 				nm_platform_wireguard_peer_to_string (&peers[i], b, len);
 				nm_utils_strbuf_seek_end (&b, &len);
+				if (peer_flags) {
+					nm_utils_strbuf_append (&b, &len,
+					                       " (%s)",
+					                       _wireguard_change_peer_flags_to_string (peer_flags[i], buf_change_flags, sizeof (buf_change_flags)));
+				}
 				nm_utils_strbuf_append_str (&b, &len, " } ");
 			}
 			nm_utils_strbuf_append_str (&b, &len, "}");
 		}
 
-		_LOG3D ("link: change wireguard ifindex %d, %s, %u peers%s%s",
+		_LOG3D ("link: change wireguard ifindex %d, %s, (%s), %u peers%s",
 		        ifindex,
 		        nm_platform_lnk_wireguard_to_string (lnk_wireguard, buf_lnk, sizeof (buf_lnk)),
+		        _wireguard_change_flags_to_string (change_flags, buf_change_flags, sizeof (buf_change_flags)),
 		        peers_len,
-		        buf_peers,
-		        replace_peers ? " (replace-peers)" : " (update-peers)");
+		        buf_peers);
 	}
 
 	return klass->link_wireguard_change (self,
 	                                     ifindex,
 	                                     lnk_wireguard,
 	                                     peers,
+	                                     peer_flags,
 	                                     peers_len,
-	                                     replace_peers);
+	                                     change_flags);
 }
 
 /*****************************************************************************/
@@ -2893,7 +2917,7 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
  * nm_platform_link_tun_get_properties:
  * @self: the #NMPlatform instance
  * @ifindex: the ifindex to look up
- * @out_properties: (out): (allow-none): return the read properties
+ * @out_properties: (out) (allow-none): return the read properties
  *
  * Only recent versions of kernel export tun properties via netlink.
  * So, if that's the case, then we have the NMPlatformLnkTun instance
@@ -4218,7 +4242,7 @@ nm_platform_ip_route_get_prune_list (NMPlatform *self,
  *   at the end of the operation. Note that if @routes contains
  *   the same route, then it will not be deleted. @routes overrules
  *   @routes_prune list.
- * @out_temporary_not_available: (allow-none): (out): routes that could
+ * @out_temporary_not_available: (allow-none) (out): routes that could
  *   currently not be synced. The caller shall keep them and try later again.
  *
  * Returns: %TRUE on success.
@@ -5610,6 +5634,7 @@ nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, g
 	char s_sockaddr[NM_UTILS_INET_ADDRSTRLEN + 100];
 	char s_endpoint[20 + sizeof (s_sockaddr)];
 	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	char s_keepalive[100];
 	guint i;
 
 	nm_utils_to_string_buffer_init (&buf, &len);
@@ -5627,18 +5652,22 @@ nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, g
 
 	nm_utils_strbuf_append (&buf, &len,
 	                        "public-key %s"
-	                        "%s" /* preshared-key */
-	                        "%s" /* endpoint */
+	                        "%s"  /* preshared-key */
+	                        "%s"  /* endpoint */
 	                        " rx %"G_GUINT64_FORMAT
 	                        " tx %"G_GUINT64_FORMAT
+	                        "%s"  /* persistent-keepalive */
 	                        "%s", /* allowed-ips */
 	                        public_key_b64,
-	                        nm_utils_memeqzero (peer->preshared_key, sizeof (peer->preshared_key))
+	                        nm_utils_memeqzero_secret (peer->preshared_key, sizeof (peer->preshared_key))
 	                          ? ""
 	                          : " preshared-key (hidden)",
 	                        s_endpoint,
 	                        peer->rx_bytes,
 	                        peer->tx_bytes,
+	                        peer->persistent_keepalive_interval > 0
+	                          ? nm_sprintf_buf (s_keepalive, " keepalive %u", (guint) peer->persistent_keepalive_interval)
+	                          : "",
 	                        peer->allowed_ips_len > 0
 	                          ? " allowed-ips"
 	                          : "");
@@ -5676,7 +5705,7 @@ nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *bu
 	              ? " public-key "
 	              : "",
 	            public_b64 ?: "",
-	            nm_utils_memeqzero (lnk->private_key, sizeof (lnk->private_key))
+	            nm_utils_memeqzero_secret (lnk->private_key, sizeof (lnk->private_key))
 	              ? ""
 	              : " private-key (hidden)",
 	            lnk->listen_port,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 412ac597..37aa58fd 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -29,7 +29,6 @@
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
 #include "nm-setting-ip-tunnel.h"
-#include "nm-utils/nm-errno.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -749,11 +748,35 @@ typedef enum {
 } NMPlatformLinkDuplexType;
 
 typedef enum {
-	NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS               = (1LL <<  0),
-	NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL                      = (1LL <<  1),
-	NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF                         = (1LL <<  2),
+	NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS                 = (1LL <<  0),
+	NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL                        = (1LL <<  1),
+	NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF                           = (1LL <<  2),
 } NMPlatformKernelSupportFlags;
 
+typedef enum {
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE                        = 0,
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS               = (1LL << 0),
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY             = (1LL << 1),
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT             = (1LL << 2),
+	NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK                  = (1LL << 3),
+} NMPlatformWireGuardChangeFlags;
+
+typedef enum {
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE                   = 0,
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME              = (1LL << 0),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY      = (1LL << 1),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL = (1LL << 2),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT           = (1LL << 3),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS         = (1LL << 4),
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS     = (1LL << 5),
+
+	NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT =   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY
+	                                                 | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL
+	                                                 | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT
+	                                                 | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS,
+
+} NMPlatformWireGuardChangePeerFlags;
+
 /*****************************************************************************/
 
 struct _NMPlatformPrivate;
@@ -831,8 +854,9 @@ typedef struct {
 	                              int ifindex,
 	                              const NMPlatformLnkWireGuard *lnk_wireguard,
 	                              const struct _NMPWireGuardPeer *peers,
+	                              const NMPlatformWireGuardChangePeerFlags *peer_flags,
 	                              guint peers_len,
-	                              gboolean replace_peers);
+	                              NMPlatformWireGuardChangeFlags change_flags);
 
 	gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
@@ -1394,8 +1418,9 @@ int nm_platform_link_wireguard_change (NMPlatform *self,
                                        int ifindex,
                                        const NMPlatformLnkWireGuard *lnk_wireguard,
                                        const struct _NMPWireGuardPeer *peers,
+                                       const NMPlatformWireGuardChangePeerFlags *peer_flags,
                                        guint peers_len,
-                                       gboolean replace_peers);
+                                       NMPlatformWireGuardChangeFlags change_flags);
 
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index f1092fe9..f34dc83d 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -19,15 +19,26 @@
  */
 
 #include "nm-default.h"
+
 #include "nmp-netns.h"
 
 #include <fcntl.h>
-#include <errno.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <pthread.h>
+
+/*****************************************************************************/
 
-#include "NetworkManagerUtils.h"
+/* NOTE: NMPNetns and all code used here must be thread-safe! */
+
+/* we may not call logging functions from the main-thread alone. Hence, we
+ * require locking from nm-logging. Indicate that by setting NM_THREAD_SAFE_ON_MAIN_THREAD
+ * to zero. */
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
+
+/*****************************************************************************/
 
 #define PROC_SELF_NS_MNT "/proc/self/ns/mnt"
 #define PROC_SELF_NS_NET "/proc/self/ns/net"
@@ -116,49 +127,81 @@ typedef struct {
 	int ns_types;
 } NetnsInfo;
 
-static void _stack_push (NMPNetns *netns, int ns_types);
+static void _stack_push (GArray *netns_stack,
+                         NMPNetns *netns,
+                         int ns_types);
 static NMPNetns *_netns_new (GError **error);
 
 /*****************************************************************************/
 
-static GArray *netns_stack = NULL;
+static NMPNetns *
+_netns_get (NetnsInfo *info)
+{
+	nm_assert (!info || NMP_IS_NETNS (info->netns));
+	return info ? info->netns : NULL;
+}
+
+/*****************************************************************************/
+
+static _nm_thread_local GArray *_netns_stack = NULL;
 
 static void
-_stack_ensure_init_impl (void)
+_netns_stack_clear_cb (gpointer data)
 {
-	NMPNetns *netns;
-	GError *error = NULL;
+	NetnsInfo *info = data;
 
-	nm_assert (!netns_stack);
+	nm_assert (NMP_IS_NETNS (info->netns));
+	g_object_unref (info->netns);
+}
 
-	netns_stack = g_array_new (FALSE, FALSE, sizeof (NetnsInfo));
+static GArray *
+_netns_stack_get_impl (void)
+{
+	gs_unref_object NMPNetns *netns = NULL;
+	gs_free_error GError *error = NULL;
+	pthread_key_t key;
+	GArray *s;
+
+	s = g_array_new (FALSE, FALSE, sizeof (NetnsInfo));
+	g_array_set_clear_func (s, _netns_stack_clear_cb);
+	_netns_stack = s;
 
 	/* at the bottom of the stack we must try to create a netns instance
 	 * that we never pop. It's the base to which we need to return. */
-
 	netns = _netns_new (&error);
-
 	if (!netns) {
-		/* don't know how to recover from this error. Netns are not supported. */
 		_LOGE (NULL, "failed to create initial netns: %s", error->message);
-		g_clear_error (&error);
-		return;
+		return s;
 	}
 
-	_stack_push (netns, _CLONE_NS_ALL);
+	/* we leak this instance inside the stack. */
+	_stack_push (s, netns, _CLONE_NS_ALL);
 
-	/* we leak this instance inside netns_stack. It cannot be popped. */
-	g_object_unref (netns);
+	/* finally, register a destructor function to cleanup the array. If we fail
+	 * to do so, we will leak NMPNetns instances (and their file descriptor) when the
+	 * thread exits. */
+	if (pthread_key_create (&key, (void (*) (void *)) g_array_unref) != 0)
+		_LOGE (NULL, "failure to initialize thread-local storage");
+	else if (pthread_setspecific (key, s) != 0)
+		_LOGE (NULL, "failure to set thread-local storage");
+
+	return s;
 }
-#define _stack_ensure_init() \
-	G_STMT_START { \
-		if (G_UNLIKELY (!netns_stack)) { \
-			_stack_ensure_init_impl (); \
-		} \
-	} G_STMT_END
+
+#define _netns_stack_get() \
+	({ \
+		GArray *_s = _netns_stack; \
+		\
+		if (G_UNLIKELY (!_s)) \
+			_s = _netns_stack_get_impl (); \
+		_s; \
+	})
+
+/*****************************************************************************/
 
 static NMPNetns *
-_stack_current_netns (int ns_types)
+_stack_current_netns (GArray *netns_stack,
+                      int ns_types)
 {
 	guint j;
 
@@ -179,7 +222,9 @@ _stack_current_netns (int ns_types)
 }
 
 static int
-_stack_current_ns_types (NMPNetns *netns, int ns_types)
+_stack_current_ns_types (GArray *netns_stack,
+                         NMPNetns *netns,
+                         int ns_types)
 {
 	const int ns_types_check[] = { _CLONE_NS_ALL_V };
 	guint i, j;
@@ -212,27 +257,25 @@ _stack_current_ns_types (NMPNetns *netns, int ns_types)
 }
 
 static NetnsInfo *
-_stack_peek (void)
+_stack_peek (GArray *netns_stack)
 {
-	nm_assert (netns_stack);
-
 	if (netns_stack->len > 0)
 		return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
 	return NULL;
 }
 
 static NetnsInfo *
-_stack_bottom (void)
+_stack_bottom (GArray *netns_stack)
 {
-	nm_assert (netns_stack);
-
 	if (netns_stack->len > 0)
 		return &g_array_index (netns_stack, NetnsInfo, 0);
 	return NULL;
 }
 
 static void
-_stack_push (NMPNetns *netns, int ns_types)
+_stack_push (GArray *netns_stack,
+             NMPNetns *netns,
+             int ns_types)
 {
 	NetnsInfo *info;
 
@@ -244,13 +287,15 @@ _stack_push (NMPNetns *netns, int ns_types)
 	g_array_set_size (netns_stack, netns_stack->len + 1);
 
 	info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
-	info->netns = g_object_ref (netns);
-	info->ns_types = ns_types;
-	info->count = 1;
+	*info = (NetnsInfo) {
+		.netns    = g_object_ref (netns),
+		.ns_types = ns_types,
+		.count    = 1,
+	};
 }
 
 static void
-_stack_pop (void)
+_stack_pop (GArray *netns_stack)
 {
 	NetnsInfo *info;
 
@@ -262,13 +307,11 @@ _stack_pop (void)
 	nm_assert (NMP_IS_NETNS (info->netns));
 	nm_assert (info->count == 1);
 
-	g_object_unref (info->netns);
-
 	g_array_set_size (netns_stack, netns_stack->len - 1);
 }
 
 static guint
-_stack_size (void)
+_stack_size (GArray *netns_stack)
 {
 	nm_assert (netns_stack);
 
@@ -289,7 +332,7 @@ _netns_new (GError **error)
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "Failed opening netns: %s",
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		errno = errsv;
 		return NULL;
 	}
@@ -299,7 +342,7 @@ _netns_new (GError **error)
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "Failed opening mntns: %s",
-		             g_strerror (errsv));
+		             nm_strerror_native (errsv));
 		nm_close (fd_net);
 		errno = errsv;
 		return NULL;
@@ -332,31 +375,33 @@ _setns (NMPNetns *self, int type)
 }
 
 static gboolean
-_netns_switch_push (NMPNetns *self, int ns_types)
+_netns_switch_push (GArray *netns_stack,
+                    NMPNetns *self,
+                    int ns_types)
 {
 	int errsv;
 
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
-	    && !_stack_current_ns_types (self, CLONE_NEWNET)
+	    && !_stack_current_ns_types (netns_stack, self, CLONE_NEWNET)
 	    && _setns (self, CLONE_NEWNET) != 0) {
 		errsv = errno;
-		_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+		_LOGE (self, "failed to switch netns: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
-	    && !_stack_current_ns_types (self, CLONE_NEWNS)
+	    && !_stack_current_ns_types (netns_stack, self, CLONE_NEWNS)
 	    && _setns (self, CLONE_NEWNS) != 0) {
 		errsv = errno;
-		_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+		_LOGE (self, "failed to switch mntns: %s", nm_strerror_native (errsv));
 
 		/* try to fix the mess by returning to the previous netns. */
 		if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
-	        && !_stack_current_ns_types (self, CLONE_NEWNET)) {
-			self = _stack_current_netns (CLONE_NEWNET);
+	        && !_stack_current_ns_types (netns_stack, self, CLONE_NEWNET)) {
+			self = _stack_current_netns (netns_stack, CLONE_NEWNET);
 			if (   self
 			    && _setns (self, CLONE_NEWNET) != 0) {
 				errsv = errno;
-				_LOGE (self, "failed to restore netns: %s", g_strerror (errsv));
+				_LOGE (self, "failed to restore netns: %s", nm_strerror_native (errsv));
 			}
 		}
 		return FALSE;
@@ -366,33 +411,36 @@ _netns_switch_push (NMPNetns *self, int ns_types)
 }
 
 static gboolean
-_netns_switch_pop (NMPNetns *self, int ns_types)
+_netns_switch_pop (GArray *netns_stack,
+                   NMPNetns *self,
+                   int ns_types)
 {
 	int errsv;
 	NMPNetns *current;
 	int success = TRUE;
 
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
-	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNET))) {
-		current = _stack_current_netns (CLONE_NEWNET);
+	    && (   !self
+	        || !_stack_current_ns_types (netns_stack, self, CLONE_NEWNET))) {
+		current = _stack_current_netns (netns_stack, CLONE_NEWNET);
 		if (!current) {
 			g_warn_if_reached ();
 			success = FALSE;
 		} else if (_setns (current, CLONE_NEWNET) != 0) {
 			errsv = errno;
-			_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+			_LOGE (self, "failed to switch netns: %s", nm_strerror_native (errsv));
 			success = FALSE;
 		}
 	}
 	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
-	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNS))) {
-		current = _stack_current_netns (CLONE_NEWNS);
+	    && (!self || !_stack_current_ns_types (netns_stack, self, CLONE_NEWNS))) {
+		current = _stack_current_netns (netns_stack, CLONE_NEWNS);
 		if (!current) {
 			g_warn_if_reached ();
 			success = FALSE;
 		} else if (_setns (current, CLONE_NEWNS) != 0) {
 			errsv = errno;
-			_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+			_LOGE (self, "failed to switch mntns: %s", nm_strerror_native (errsv));
 			success = FALSE;
 		}
 	}
@@ -423,32 +471,31 @@ nmp_netns_get_fd_mnt (NMPNetns *self)
 static gboolean
 _nmp_netns_push_type (NMPNetns *self, int ns_types)
 {
+	GArray *netns_stack = _netns_stack_get ();
 	NetnsInfo *info;
 	char sbuf[100];
 
-	_stack_ensure_init ();
-
-	info = _stack_peek ();
+	info = _stack_peek (netns_stack);
 	g_return_val_if_fail (info, FALSE);
 
 	if (info->netns == self && info->ns_types == ns_types) {
 		info->count++;
 		_LOGt (self, "push#%u* %s (increase count to %d)",
-		       _stack_size () - 1,
+		       _stack_size (netns_stack) - 1,
 		       _ns_types_to_str (ns_types, ns_types, sbuf), info->count);
 		return TRUE;
 	}
 
 	_LOGD (self, "push#%u %s",
-	       _stack_size (),
+	       _stack_size (netns_stack),
 	       _ns_types_to_str (ns_types,
-	                         _stack_current_ns_types (self, ns_types),
+	                         _stack_current_ns_types (netns_stack, self, ns_types),
 	                         sbuf));
 
-	if (!_netns_switch_push (self, ns_types))
+	if (!_netns_switch_push (netns_stack, self, ns_types))
 		return FALSE;
 
-	_stack_push (self, ns_types);
+	_stack_push (netns_stack, self, ns_types);
 	return TRUE;
 }
 
@@ -472,14 +519,13 @@ nmp_netns_push_type (NMPNetns *self, int ns_types)
 NMPNetns *
 nmp_netns_new (void)
 {
+	GArray *netns_stack = _netns_stack_get ();
 	NMPNetns *self;
 	int errsv;
 	GError *error = NULL;
 	unsigned long mountflags = 0;
 
-	_stack_ensure_init ();
-
-	if (!_stack_peek ()) {
+	if (!_stack_peek (netns_stack)) {
 		/* there are no netns instances. We cannot create a new one
 		 * (because after unshare we couldn't return to the original one). */
 		errno = ENOTSUP;
@@ -488,19 +534,19 @@ nmp_netns_new (void)
 
 	if (unshare (_CLONE_NS_ALL) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed to create new net and mnt namespace: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed to create new net and mnt namespace: %s", nm_strerror_native (errsv));
 		return NULL;
 	}
 
 	if (mount ("", "/", "none", MS_SLAVE | MS_REC, NULL) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed mount --make-rslave: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed mount --make-rslave: %s", nm_strerror_native (errsv));
 		goto err_out;
 	}
 
 	if (umount2 ("/sys", MNT_DETACH) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed umount /sys: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed umount /sys: %s", nm_strerror_native (errsv));
 		goto err_out;
 	}
 
@@ -509,7 +555,7 @@ nmp_netns_new (void)
 
 	if (mount ("sysfs", "/sys", "sysfs", mountflags, NULL) != 0) {
 		errsv = errno;
-		_LOGE (NULL, "failed mount /sys: %s", g_strerror (errsv));
+		_LOGE (NULL, "failed mount /sys: %s", nm_strerror_native (errsv));
 		goto err_out;
 	}
 
@@ -521,11 +567,11 @@ nmp_netns_new (void)
 		goto err_out;
 	}
 
-	_stack_push (self, _CLONE_NS_ALL);
+	_stack_push (netns_stack, self, _CLONE_NS_ALL);
 
 	return self;
 err_out:
-	_netns_switch_pop (NULL, _CLONE_NS_ALL);
+	_netns_switch_pop (netns_stack, NULL, _CLONE_NS_ALL);
 	errno = errsv;
 	return NULL;
 }
@@ -533,14 +579,13 @@ err_out:
 gboolean
 nmp_netns_pop (NMPNetns *self)
 {
+	GArray *netns_stack = _netns_stack_get ();
 	NetnsInfo *info;
 	int ns_types;
 
 	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
 
-	_stack_ensure_init ();
-
-	info = _stack_peek ();
+	info = _stack_peek (netns_stack);
 
 	g_return_val_if_fail (info, FALSE);
 	g_return_val_if_fail (info->netns == self, FALSE);
@@ -548,52 +593,42 @@ nmp_netns_pop (NMPNetns *self)
 	if (info->count > 1) {
 		info->count--;
 		_LOGt (self, "pop#%u* (decrease count to %d)",
-		       _stack_size () - 1, info->count);
+		       _stack_size (netns_stack) - 1, info->count);
 		return TRUE;
 	}
 	g_return_val_if_fail (info->count == 1, FALSE);
 
 	/* cannot pop the original netns. */
-	g_return_val_if_fail (_stack_size () > 1, FALSE);
+	g_return_val_if_fail (_stack_size (netns_stack) > 1, FALSE);
 
-	_LOGD (self, "pop#%u", _stack_size () - 1);
+	_LOGD (self, "pop#%u", _stack_size (netns_stack) - 1);
 
 	ns_types = info->ns_types;
 
-	_stack_pop ();
+	_stack_pop (netns_stack);
 
-	return _netns_switch_pop (self, ns_types);
+	return _netns_switch_pop (netns_stack, self, ns_types);
 }
 
 NMPNetns *
 nmp_netns_get_current (void)
 {
-	NetnsInfo *info;
-
-	_stack_ensure_init ();
-
-	info = _stack_peek ();
-	return info ? info->netns : NULL;
+	return _netns_get (_stack_peek (_netns_stack_get ()));
 }
 
 NMPNetns *
 nmp_netns_get_initial (void)
 {
-	NetnsInfo *info;
-
-	_stack_ensure_init ();
-
-	info = _stack_bottom ();
-	return info ? info->netns : NULL;
+	return _netns_get (_stack_bottom (_netns_stack_get ()));
 }
 
 gboolean
 nmp_netns_is_initial (void)
 {
-	if (G_UNLIKELY (!netns_stack))
-		return TRUE;
+	GArray *netns_stack = _netns_stack_get ();
 
-	return nmp_netns_get_current () == nmp_netns_get_initial ();
+	return (   _netns_get (_stack_peek (netns_stack))
+	        == _netns_get (_stack_bottom (netns_stack)));
 }
 
 /*****************************************************************************/
@@ -618,7 +653,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 		errsv = errno;
 		if (errsv != EEXIST) {
 			_LOGE (self, "bind: failed to create directory %s: %s",
-			       dirname, g_strerror (errsv));
+			       dirname, nm_strerror_native (errsv));
 			return FALSE;
 		}
 	}
@@ -626,7 +661,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 	if ((fd = creat (filename, S_IRUSR | S_IRGRP | S_IROTH)) == -1) {
 		errsv = errno;
 		_LOGE (self, "bind: failed to create %s: %s",
-		       filename, g_strerror (errsv));
+		       filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 	nm_close (fd);
@@ -634,7 +669,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 	if (mount (PROC_SELF_NS_NET, filename, "none", MS_BIND, NULL) != 0) {
 		errsv = errno;
 		_LOGE (self, "bind: failed to mount %s to %s: %s",
-		       PROC_SELF_NS_NET, filename, g_strerror (errsv));
+		       PROC_SELF_NS_NET, filename, nm_strerror_native (errsv));
 		unlink (filename);
 		return FALSE;
 	}
@@ -642,7 +677,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 	if (out_fd) {
 		if ((fd = open (filename, O_RDONLY | O_CLOEXEC)) == -1) {
 			errsv = errno;
-			_LOGE (self, "bind: failed to open %s: %s", filename, g_strerror (errsv));
+			_LOGE (self, "bind: failed to open %s: %s", filename, nm_strerror_native (errsv));
 			umount2 (filename, MNT_DETACH);
 			unlink (filename);
 			return FALSE;
@@ -663,12 +698,12 @@ nmp_netns_bind_to_path_destroy (NMPNetns *self, const char *filename)
 
 	if (umount2 (filename, MNT_DETACH) != 0) {
 		errsv = errno;
-		_LOGE (self, "bind: failed to unmount2 %s: %s", filename, g_strerror (errsv));
+		_LOGE (self, "bind: failed to unmount2 %s: %s", filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 	if (unlink (filename) != 0) {
 		errsv = errno;
-		_LOGE (self, "bind: failed to unlink %s: %s", filename, g_strerror (errsv));
+		_LOGE (self, "bind: failed to unlink %s: %s", filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 	return TRUE;
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 80e65b49..6ec6fea1 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -1545,13 +1545,13 @@ _vt_cmd_plobj_id_hash_update (tfilter, NMPlatformTfilter, {
 	                     obj->handle);
 })
 
-static inline void
+static void
 _vt_cmd_plobj_hash_update_ip4_route (const NMPlatformObject *obj, NMHashState *h)
 {
 	return nm_platform_ip4_route_hash_update ((const NMPlatformIP4Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h);
 }
 
-static inline void
+static void
 _vt_cmd_plobj_hash_update_ip6_route (const NMPlatformObject *obj, NMHashState *h)
 {
 	return nm_platform_ip6_route_hash_update ((const NMPlatformIP6Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h);
@@ -2519,10 +2519,10 @@ nmp_cache_remove_netlink (NMPCache *cache,
  *    afterwards. Hence, during a dump, every update should move the object to the
  *    end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
  *    instead of NM_DEDUP_MULTI_IDX_MODE_APPEND.
- * @out_obj_old: (allow-none): (out): return the object with same ID as @obj_hand_over,
+ * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over,
  *    that was in the cache before update. If an object is returned, the caller must
  *    unref it afterwards.
- * @out_obj_new: (allow-none): (out): return the object from the cache after update.
+ * @out_obj_new: (allow-none) (out): return the object from the cache after update.
  *    The caller must unref this object.
  *
  * Returns: how the cache changed.
@@ -2920,15 +2920,15 @@ nmp_cache_update_link_master_connected (NMPCache *cache,
 /*****************************************************************************/
 
 void
-nmp_cache_dirty_set_all (NMPCache *cache, NMPObjectType obj_type)
+nmp_cache_dirty_set_all (NMPCache *cache,
+                         const NMPLookup *lookup)
 {
-	NMPObject obj_needle;
-
 	nm_assert (cache);
+	nm_assert (lookup);
 
 	nm_dedup_multi_index_dirty_set_head (cache->multi_idx,
-	                                     _idx_type_get (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE),
-	                                     _nmp_object_stackinit_from_type (&obj_needle, obj_type));
+	                                     _idx_type_get (cache, lookup->cache_id_type),
+	                                     &lookup->selector_obj);
 }
 
 /*****************************************************************************/
@@ -2977,7 +2977,6 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectLink),
 		.sizeof_public                      = sizeof (NMPlatformLink),
 		.obj_type_name                      = "link",
-		.addr_family                        = AF_UNSPEC,
 		.rtm_gettype                        = RTM_GETLINK,
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_LINK,
 		.signal_type                        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 4649441b..525e1440 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -31,6 +31,18 @@ struct udev_device;
 
 /*****************************************************************************/
 
+/* "struct __kernel_timespec" uses "long long", but we use gint64. In practice,
+ * these are the same types. */
+G_STATIC_ASSERT (sizeof (long long) == sizeof (gint64));
+
+typedef struct {
+	/* like "struct __kernel_timespec". */
+	gint64 tv_sec;
+	gint64 tv_nsec;
+} NMPTimespec64;
+
+/*****************************************************************************/
+
 typedef union {
 	struct sockaddr     sa;
 	struct sockaddr_in  in;
@@ -72,7 +84,8 @@ typedef struct {
 typedef struct _NMPWireGuardPeer {
 	NMSockAddrUnion endpoint;
 
-	struct timespec last_handshake_time;
+	NMPTimespec64 last_handshake_time;
+
 	guint64 rx_bytes;
 	guint64 tx_bytes;
 
@@ -771,7 +784,8 @@ NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache,
                                                         const NMPObject **out_obj_old,
                                                         const NMPObject **out_obj_new);
 
-void nmp_cache_dirty_set_all (NMPCache *cache, NMPObjectType obj_type);
+void nmp_cache_dirty_set_all (NMPCache *cache,
+                              const NMPLookup *lookup);
 
 NMPCache *nmp_cache_new (NMDedupMultiIndex *multi_idx, gboolean use_udev);
 void nmp_cache_free (NMPCache *cache);
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index 3704e2ac..8086a46c 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -1,14 +1,14 @@
 test_units = [
-  ['test-link-fake',     'test-link.c',       test_nm_dep_fake,  30],
-  ['test-link-linux',    'test-link.c',       test_nm_dep_linux, 180],
-  ['test-address-fake',  'test-address.c',    test_nm_dep_fake,  30],
-  ['test-address-linux', 'test-address.c',    test_nm_dep_linux, 30],
-  ['test-general',       'test-general.c',    test_nm_dep,       30],
-  ['test-nmp-object',    'test-nmp-object.c', test_nm_dep,       30],
-  ['test-route-fake',    'test-route.c',      test_nm_dep_fake,  30],
-  ['test-route-linux',   'test-route.c',      test_nm_dep_linux, 30],
-  ['test-cleanup-fake',  'test-cleanup.c',    test_nm_dep_fake,  30],
-  ['test-cleanup-linux', 'test-cleanup.c',    test_nm_dep_linux, 30],
+  ['test-link-fake',     'test-link.c',       test_nm_dep_fake,  default_test_timeout],
+  ['test-link-linux',    'test-link.c',       test_nm_dep_linux, 900],
+  ['test-address-fake',  'test-address.c',    test_nm_dep_fake,  default_test_timeout],
+  ['test-address-linux', 'test-address.c',    test_nm_dep_linux, default_test_timeout],
+  ['test-general',       'test-general.c',    test_nm_dep,       default_test_timeout],
+  ['test-nmp-object',    'test-nmp-object.c', test_nm_dep,       default_test_timeout],
+  ['test-route-fake',    'test-route.c',      test_nm_dep_fake,  default_test_timeout],
+  ['test-route-linux',   'test-route.c',      test_nm_dep_linux, default_test_timeout],
+  ['test-cleanup-fake',  'test-cleanup.c',    test_nm_dep_fake,  default_test_timeout],
+  ['test-cleanup-linux', 'test-cleanup.c',    test_nm_dep_linux, default_test_timeout],
 ]
 
 foreach test_unit: test_units
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 8e29b5ce..c510710b 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1840,7 +1840,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 	if (e != 0) {
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		             "pipe() failed with %d (%s)", errsv, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -1848,7 +1848,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 	if (e != 0) {
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
+		             "pipe() failed with %d (%s)", errsv, nm_strerror_native (errsv));
 		nm_close (pipefd_c2p[0]);
 		nm_close (pipefd_c2p[1]);
 		return FALSE;
@@ -1858,7 +1858,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 	if (pid < 0) {
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "fork() failed with %d (%s)", errsv, strerror (errsv));
+		             "fork() failed with %d (%s)", errsv, nm_strerror_native (errsv));
 		nm_close (pipefd_c2p[0]);
 		nm_close (pipefd_c2p[1]);
 		nm_close (pipefd_p2c[0]);
@@ -1919,7 +1919,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 			             "child process failed for unknown reason");
 		} else {
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "child process signaled failure %d (%s)", errsv, strerror (errsv));
+			             "child process signaled failure %d (%s)", errsv, nm_strerror_native (errsv));
 		}
 		nm_close (pipefd_p2c[1]);
 		kill (pid, SIGKILL);
@@ -2074,14 +2074,14 @@ main (int argc, char **argv)
 
 		if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) {
 			errsv = errno;
-			g_error ("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", strerror (errsv), errsv);
+			g_error ("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", nm_strerror_native (errsv), errsv);
 		}
 
 		/* We need a read-only /sys so that the platform knows there's no udev. */
 		mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL);
 		if (mount ("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) {
 			errsv = errno;
-			g_error ("mount(\"/sys\") failed with %s (%d)", strerror (errsv), errsv);
+			g_error ("mount(\"/sys\") failed with %s (%d)", nm_strerror_native (errsv), errsv);
 		}
 	}
 
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 048fd9dd..aa1f5460 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -19,7 +19,6 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
-#include <string.h>
 #include <arpa/inet.h>
 #include <linux/if.h>
 #include <linux/if_link.h>
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index bfd33058..71324301 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -860,9 +860,9 @@ _test_wireguard_change (NMPlatform *platform,
 			if ((i % 2) == 1) {
 				endpoint = (NMSockAddrUnion) {
 					.in = {
-						.sin_family = AF_INET,
-						.sin_addr   = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "192.168.7.%d", i)),
-						.sin_port   = htons (14000 + i),
+						.sin_family      = AF_INET,
+						.sin_addr.s_addr = nmtst_inet4_from_string (nm_sprintf_buf (s_addr, "192.168.7.%d", i)),
+						.sin_port        = htons (14000 + i),
 					},
 				};
 			} else {
@@ -912,8 +912,12 @@ _test_wireguard_change (NMPlatform *platform,
 	                                       ifindex,
 	                                       &lnk_wireguard,
 	                                       (const NMPWireGuardPeer *) peers->data,
+	                                       NULL,
 	                                       peers->len,
-	                                       TRUE);
+	                                         NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY
+	                                       | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT
+	                                       | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK
+	                                       | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS);
 	g_assert (NMTST_NM_ERR_SUCCESS (r));
 }
 
@@ -2393,7 +2397,7 @@ _test_netns_check_skip (void)
 			support_errsv = errno;
 	}
 	if (!support) {
-		_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (support_errsv));
+		_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", nm_strerror_native (support_errsv));
 		g_test_skip ("No netns support (setns failed)");
 		return TRUE;
 	}
@@ -2983,6 +2987,69 @@ test_sysctl_netns_switch (void)
 
 /*****************************************************************************/
 
+static gpointer
+_test_netns_mt_thread (gpointer data)
+{
+	NMPNetns *netns1 = data;
+	gs_unref_object NMPNetns *netns2 = NULL;
+	NMPNetns *netns_bottom;
+	NMPNetns *initial;
+
+	netns_bottom = nmp_netns_get_initial ();
+	g_assert (netns_bottom);
+
+	/* I don't know why, but we need to create a new netns here at least once.
+	 * Otherwise, setns(, CLONE_NEWNS) below fails with EINVAL (???).
+	 *
+	 * Something is not right here, but what?  */
+	netns2 = nmp_netns_new ();
+	nmp_netns_pop (netns2);
+	g_clear_object (&netns2);
+
+	nmp_netns_push (netns1);
+	nmp_netns_push_type (netns_bottom, CLONE_NEWNET);
+	nmp_netns_push_type (netns_bottom, CLONE_NEWNS);
+	nmp_netns_push_type (netns1, CLONE_NEWNS);
+	nmp_netns_pop (netns1);
+	nmp_netns_pop (netns_bottom);
+	nmp_netns_pop (netns_bottom);
+	nmp_netns_pop (netns1);
+
+	initial = nmp_netns_get_initial ();
+	g_assert (NMP_IS_NETNS (initial));
+	return g_object_ref (initial);
+}
+
+static void
+test_netns_mt (void)
+{
+	gs_unref_object NMPNetns *netns1 = NULL;
+	NMPNetns *initial_from_other_thread;
+	GThread *th;
+
+	if (_test_netns_check_skip ())
+		return;
+
+	netns1 = nmp_netns_new ();
+	g_assert (NMP_NETNS (netns1));
+	nmp_netns_pop (netns1);
+
+	th = g_thread_new ("nm-test-netns-mt", _test_netns_mt_thread, netns1);
+	initial_from_other_thread = g_thread_join (th);
+	g_assert (NMP_IS_NETNS (initial_from_other_thread));
+
+	if (nmtst_get_rand_bool ()) {
+		nmp_netns_push (initial_from_other_thread);
+		nmp_netns_pop (initial_from_other_thread);
+	}
+
+	g_object_add_weak_pointer (G_OBJECT (initial_from_other_thread), (gpointer *) &initial_from_other_thread);
+	g_object_unref (initial_from_other_thread);
+	g_assert (initial_from_other_thread == NULL);
+}
+
+/*****************************************************************************/
+
 static void
 ethtool_features_dump (const NMEthtoolFeatureStates *features)
 {
@@ -3137,6 +3204,8 @@ _nmtstp_setup_tests (void)
 		g_test_add_vtable ("/general/netns/push", 0, NULL, _test_netns_setup, test_netns_push, _test_netns_teardown);
 		g_test_add_vtable ("/general/netns/bind-to-path", 0, NULL, _test_netns_setup, test_netns_bind_to_path, _test_netns_teardown);
 
+		g_test_add_func ("/general/netns/mt", test_netns_mt);
+
 		g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
 		g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
 
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 4c0f686f..2619ec52 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -767,7 +767,7 @@ test_ip (gconstpointer test_data)
 	const int EX_ = -1;
 	struct {
 		int ifindex;
-	} iface_data[10] = { 0 };
+	} iface_data[10] = { { 0 }, };
 	int order_idx[G_N_ELEMENTS (iface_data)] = { 0 };
 	guint order_len;
 	guint try;
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index 0df3d2d8..4f7ede97 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -24,15 +24,12 @@
 
 #include "nm-wifi-utils-nl80211.h"
 
-#include <errno.h>
-#include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
 #include <linux/nl80211.h>
 #include <linux/if.h>
 
-#include "nm-utils/nm-errno.h"
 #include "platform/nm-netlink.h"
 #include "nm-wifi-utils-private.h"
 #include "platform/nm-platform.h"
@@ -184,8 +181,10 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (!tb[NL80211_ATTR_IFTYPE])
@@ -279,16 +278,18 @@ nl80211_get_wake_on_wlan_handler (struct nl_msg *msg, void *arg)
 	struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG];
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 
-	nla_parse (attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
-	           genlmsg_attrlen(gnlh, 0), NULL);
+	nla_parse_arr (attrs,
+	               genlmsg_attrdata(gnlh, 0),
+	               genlmsg_attrlen(gnlh, 0),
+	               NULL);
 
 	if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS])
 		return NL_SKIP;
 
-	nla_parse (trig, MAX_NL80211_WOWLAN_TRIG,
-	           nla_data (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
-	           nla_len (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
-	           NULL);
+	nla_parse_arr (trig,
+	               nla_data (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	               nla_len (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	               NULL);
 
 	*wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
 	if (trig[NL80211_WOWLAN_TRIG_ANY])
@@ -415,33 +416,35 @@ find_ssid (guint8 *ies, guint32 ies_len,
 static int
 nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 {
+	static const struct nla_policy bss_policy[] = {
+		[NL80211_BSS_TSF]                  = { .type = NLA_U64 },
+		[NL80211_BSS_FREQUENCY]            = { .type = NLA_U32 },
+		[NL80211_BSS_BSSID]                = { .minlen = ETH_ALEN },
+		[NL80211_BSS_BEACON_INTERVAL]      = { .type = NLA_U16 },
+		[NL80211_BSS_CAPABILITY]           = { .type = NLA_U16 },
+		[NL80211_BSS_INFORMATION_ELEMENTS] = { },
+		[NL80211_BSS_SIGNAL_MBM]           = { .type = NLA_U32 },
+		[NL80211_BSS_SIGNAL_UNSPEC]        = { .type = NLA_U8 },
+		[NL80211_BSS_STATUS]               = { .type = NLA_U32 },
+	};
 	struct nl80211_bss_info *info = arg;
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct nlattr *bss[NL80211_BSS_MAX + 1];
-	static const struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
-		[NL80211_BSS_TSF] = { .type = NLA_U64 },
-		[NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
-		[NL80211_BSS_BSSID] = { },
-		[NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 },
-		[NL80211_BSS_CAPABILITY] = { .type = NLA_U16 },
-		[NL80211_BSS_INFORMATION_ELEMENTS] = { },
-		[NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 },
-		[NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 },
-		[NL80211_BSS_STATUS] = { .type = NLA_U32 },
-	};
+	struct nlattr *bss[G_N_ELEMENTS (bss_policy)];
 	guint32 status;
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (tb[NL80211_ATTR_BSS] == NULL)
 		return NL_SKIP;
 
-	if (nla_parse_nested (bss, NL80211_BSS_MAX,
-	                      tb[NL80211_ATTR_BSS],
-	                      bss_policy))
+	if (nla_parse_nested_arr (bss,
+	                          tb[NL80211_ATTR_BSS],
+	                          bss_policy))
 		return NL_SKIP;
 
 	if (bss[NL80211_BSS_STATUS] == NULL)
@@ -461,21 +464,22 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 		info->freq = nla_get_u32 (bss[NL80211_BSS_FREQUENCY]);
 
 	if (bss[NL80211_BSS_SIGNAL_UNSPEC])
-		info->beacon_signal =
-			nla_get_u8 (bss[NL80211_BSS_SIGNAL_UNSPEC]);
+		info->beacon_signal = nla_get_u8 (bss[NL80211_BSS_SIGNAL_UNSPEC]);
 
 	if (bss[NL80211_BSS_SIGNAL_MBM])
-		info->beacon_signal =
-			nl80211_xbm_to_percent (nla_get_u32 (bss[NL80211_BSS_SIGNAL_MBM]), 100);
+		info->beacon_signal = nl80211_xbm_to_percent (nla_get_u32 (bss[NL80211_BSS_SIGNAL_MBM]), 100);
 
 	if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) {
 		guint8 *ssid;
 		guint32 ssid_len;
 
 		find_ssid (nla_data (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
-			  nla_len (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
-			  &ssid, &ssid_len);
-		if (ssid && ssid_len && ssid_len <= sizeof (info->ssid)) {
+		           nla_len (bss[NL80211_BSS_INFORMATION_ELEMENTS]),
+		           &ssid,
+		           &ssid_len);
+		if (   ssid
+		    && ssid_len
+		    && ssid_len <= sizeof (info->ssid)) {
 			memcpy (info->ssid, ssid, ssid_len);
 			info->ssid_len = ssid_len;
 		}
@@ -550,49 +554,50 @@ struct nl80211_station_info {
 static int
 nl80211_station_handler (struct nl_msg *msg, void *arg)
 {
-	struct nl80211_station_info *info = arg;
-	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
-	struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
-	struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
-	static const struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
+	static const struct nla_policy stats_policy[] = {
 		[NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
-		[NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 },
-		[NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED },
-		[NL80211_STA_INFO_LLID] = { .type = NLA_U16 },
-		[NL80211_STA_INFO_PLID] = { .type = NLA_U16 },
-		[NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 },
+		[NL80211_STA_INFO_RX_BYTES]      = { .type = NLA_U32 },
+		[NL80211_STA_INFO_TX_BYTES]      = { .type = NLA_U32 },
+		[NL80211_STA_INFO_RX_PACKETS]    = { .type = NLA_U32 },
+		[NL80211_STA_INFO_TX_PACKETS]    = { .type = NLA_U32 },
+		[NL80211_STA_INFO_SIGNAL]        = { .type = NLA_U8 },
+		[NL80211_STA_INFO_TX_BITRATE]    = { .type = NLA_NESTED },
+		[NL80211_STA_INFO_LLID]          = { .type = NLA_U16 },
+		[NL80211_STA_INFO_PLID]          = { .type = NLA_U16 },
+		[NL80211_STA_INFO_PLINK_STATE]   = { .type = NLA_U8 },
 	};
-
-	static const struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
-		[NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 },
-		[NL80211_RATE_INFO_MCS] = { .type = NLA_U8 },
+	static const struct nla_policy rate_policy[] = {
+		[NL80211_RATE_INFO_BITRATE]      = { .type = NLA_U16 },
+		[NL80211_RATE_INFO_MCS]          = { .type = NLA_U8 },
 		[NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG },
-		[NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG },
+		[NL80211_RATE_INFO_SHORT_GI]     = { .type = NLA_FLAG },
 	};
+	struct nlattr *rinfo[G_N_ELEMENTS (rate_policy)];
+	struct nlattr *sinfo[G_N_ELEMENTS (stats_policy)];
+	struct nl80211_station_info *info = arg;
+	struct nlattr *tb[NL80211_ATTR_MAX + 1];
+	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (tb[NL80211_ATTR_STA_INFO] == NULL)
 		return NL_SKIP;
 
-	if (nla_parse_nested (sinfo, NL80211_STA_INFO_MAX,
-	                      tb[NL80211_ATTR_STA_INFO],
-	                      stats_policy))
+	if (nla_parse_nested_arr (sinfo,
+	                          tb[NL80211_ATTR_STA_INFO],
+	                          stats_policy))
 		return NL_SKIP;
 
 	if (sinfo[NL80211_STA_INFO_TX_BITRATE] == NULL)
 		return NL_SKIP;
 
-	if (nla_parse_nested (rinfo, NL80211_RATE_INFO_MAX,
-	                      sinfo[NL80211_STA_INFO_TX_BITRATE],
-	                      rate_policy))
+	if (nla_parse_nested_arr (rinfo,
+	                          sinfo[NL80211_STA_INFO_TX_BITRATE],
+	                          rate_policy))
 		return NL_SKIP;
 
 	if (rinfo[NL80211_RATE_INFO_BITRATE] == NULL)
@@ -717,37 +722,40 @@ struct nl80211_device_info {
 
 static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 {
+	static const struct nla_policy freq_policy[] = {
+		[NL80211_FREQUENCY_ATTR_FREQ]         = { .type = NLA_U32 },
+		[NL80211_FREQUENCY_ATTR_DISABLED]     = { .type = NLA_FLAG },
+#ifdef NL80211_FREQUENCY_ATTR_NO_IR
+		[NL80211_FREQUENCY_ATTR_NO_IR]        = { .type = NLA_FLAG },
+#else
+		[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = { .type = NLA_FLAG },
+		[NL80211_FREQUENCY_ATTR_NO_IBSS]      = { .type = NLA_FLAG },
+#endif
+		[NL80211_FREQUENCY_ATTR_RADAR]        = { .type = NLA_FLAG },
+		[NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 },
+	};
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nl80211_device_info *info = arg;
 	NMWifiUtilsNl80211 *self = info->self;
 	struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1];
-	struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1];
+	struct nlattr *tb_freq[G_N_ELEMENTS (freq_policy)];
 	struct nlattr *nl_band;
 	struct nlattr *nl_freq;
 	int rem_freq;
 	int rem_band;
 	int freq_idx;
-	static const struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
-		[NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 },
-		[NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG },
-#ifdef NL80211_FREQUENCY_ATTR_NO_IR
-		[NL80211_FREQUENCY_ATTR_NO_IR] = { .type = NLA_FLAG },
-#else
-		[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = { .type = NLA_FLAG },
-		[NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG },
-#endif
-		[NL80211_FREQUENCY_ATTR_RADAR] = { .type = NLA_FLAG },
-		[NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 },
-	};
+
 #ifdef NL80211_FREQUENCY_ATTR_NO_IR
-	G_STATIC_ASSERT (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR);
+	G_STATIC_ASSERT_EXPR (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR);
 #else
-	G_STATIC_ASSERT (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS);
+	G_STATIC_ASSERT_EXPR (NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS);
 #endif
 
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   NULL) < 0)
 		return NL_SKIP;
 
 	if (   tb[NL80211_ATTR_WIPHY] == NULL
@@ -793,14 +801,17 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	info->num_freqs = 0;
 
 	nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-		if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band,
-		                      NULL) < 0)
+		if (nla_parse_nested_arr (tb_band,
+		                          nl_band,
+		                          NULL) < 0)
 			return NL_SKIP;
 
-		nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
+		nla_for_each_nested (nl_freq,
+		                     tb_band[NL80211_BAND_ATTR_FREQS],
 		                     rem_freq) {
-			if (nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX,
-			                      nl_freq, freq_policy) < 0)
+			if (nla_parse_nested_arr (tb_freq,
+			                          nl_freq,
+			                          freq_policy) < 0)
 				continue;
 
 			if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
@@ -815,21 +826,22 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 
 	freq_idx = 0;
 	nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-		if (nla_parse_nested (tb_band, NL80211_BAND_ATTR_MAX, nl_band,
-		                      NULL) < 0)
+		if (nla_parse_nested_arr (tb_band,
+		                          nl_band,
+		                          NULL) < 0)
 			return NL_SKIP;
 
 		nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS],
 		                    rem_freq) {
-			if (nla_parse_nested (tb_freq, NL80211_FREQUENCY_ATTR_MAX,
-			                      nl_freq, freq_policy) < 0)
+			if (nla_parse_nested_arr (tb_freq,
+			                          nl_freq,
+			                          freq_policy) < 0)
 				continue;
 
 			if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
 				continue;
 
-			info->freqs[freq_idx] =
-				nla_get_u32 (tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+			info->freqs[freq_idx] = nla_get_u32 (tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
 
 			info->caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID;
 
@@ -844,11 +856,10 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 
 	/* Read security/encryption support */
 	if (tb[NL80211_ATTR_CIPHER_SUITES]) {
-		int num;
-		int i;
-		__u32 *ciphers = nla_data (tb[NL80211_ATTR_CIPHER_SUITES]);
+		guint32 *ciphers = nla_data (tb[NL80211_ATTR_CIPHER_SUITES]);
+		guint i, num;
 
-		num = nla_len (tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof (__u32);
+		num = nla_len (tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof (guint32);
 		for (i = 0; i < num; i++) {
 			switch (ciphers[i]) {
 			case WLAN_CIPHER_SUITE_WEP40:
diff --git a/src/platform/wifi/nm-wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index 597f3152..3aa1720a 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -23,8 +23,6 @@
 
 #include "nm-wifi-utils-wext.h"
 
-#include <errno.h>
-#include <string.h>
 #include <sys/ioctl.h>
 #include <net/ethernet.h>
 #include <unistd.h>
@@ -117,7 +115,7 @@ get_ifname (int ifindex, char *buffer, const char *op)
 		errsv = errno;
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "error getting interface name for ifindex %d, operation '%s': %s (%d)",
-		       ifindex, op, g_strerror (errsv), errsv);
+		       ifindex, op, nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
@@ -129,15 +127,17 @@ wifi_wext_get_mode_ifname (NMWifiUtils *data, const char *ifname)
 {
 	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
+	int errsv;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
 	nm_utils_ifname_cpy (wrq.ifr_name, ifname);
 
 	if (ioctl (wext->fd, SIOCGIWMODE, &wrq) < 0) {
-		if (errno != ENODEV) {
+		errsv = errno;
+		if (errsv != ENODEV) {
 			_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 			       "(%s): error %d getting card mode",
-			       ifname, errno);
+			       ifname, errsv);
 		}
 		return NM_802_11_MODE_UNKNOWN;
 	}
@@ -253,7 +253,7 @@ wifi_wext_get_freq (NMWifiUtils *data)
 	if (ioctl (wext->fd, SIOCGIWFREQ, &wrq) < 0) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): error getting frequency: %s",
-		       ifname, strerror (errno));
+		       ifname, nm_strerror_native (errno));
 		return 0;
 	}
 
@@ -291,7 +291,7 @@ wifi_wext_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 	if (ioctl (wext->fd, SIOCGIWAP, &wrq) < 0) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): error getting associated BSSID: %s",
-		       ifname, strerror (errno));
+		       ifname, nm_strerror_native (errno));
 		return FALSE;
 	}
 	memcpy (out_bssid, &(wrq.u.ap_addr.sa_data), ETH_ALEN);
@@ -429,7 +429,7 @@ wifi_wext_get_qual (NMWifiUtils *data)
 	if (ioctl (wext->fd, SIOCGIWSTATS, &wrq) < 0) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): error getting signal strength: %s",
-		       ifname, strerror (errno));
+		       ifname, nm_strerror_native (errno));
 		return -1;
 	}
 
@@ -476,7 +476,7 @@ wifi_wext_set_mesh_channel (NMWifiUtils *data, guint32 channel)
 	if (ioctl (wext->fd, SIOCSIWFREQ, &wrq) < 0) {
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
 		       "(%s): error setting channel to %d: %s",
-		       ifname, channel, strerror (errno));
+		       ifname, channel, nm_strerror_native (errno));
 		return FALSE;
 	}
 
@@ -506,15 +506,15 @@ wifi_wext_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
 	if (ioctl (wext->fd, SIOCSIWESSID, &wrq) == 0)
 		return TRUE;
 
-	if (errno != ENODEV) {
+	errsv = errno;
+	if (errsv != ENODEV) {
 		gs_free char *ssid_str = NULL;
 
-		errsv = errno;
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
 		       "(%s): error setting SSID to %s: %s",
 		       ifname,
 		       (ssid_str = _nm_utils_ssid_to_string_arr (ssid, len)),
-		       strerror (errsv));
+		       nm_strerror_native (errsv));
 	}
 
 	return FALSE;
@@ -545,6 +545,7 @@ wext_get_range_ifname (NMWifiUtilsWext *wext,
 	int i = 26;
 	gboolean success = FALSE;
 	struct iwreq wrq;
+	int errsv;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
 	nm_utils_ifname_cpy (wrq.ifr_name, ifname);
@@ -561,11 +562,14 @@ wext_get_range_ifname (NMWifiUtilsWext *wext,
 				*response_len = wrq.u.data.length;
 			success = TRUE;
 			break;
-		} else if (errno != EAGAIN) {
-			_LOGE (LOGD_PLATFORM | LOGD_WIFI,
-			       "(%s): couldn't get driver range information (%d).",
-			       ifname, errno);
-			break;
+		} else {
+			errsv = errno;
+			if (errsv != EAGAIN) {
+				_LOGE (LOGD_PLATFORM | LOGD_WIFI,
+				       "(%s): couldn't get driver range information (%d).",
+				       ifname, errsv);
+				break;
+			}
 		}
 
 		g_usleep (G_USEC_PER_SEC / 4);
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/platform/wifi/nm-wifi-utils.c
index 25d71c6a..96071faa 100644
--- a/src/platform/wifi/nm-wifi-utils.c
+++ b/src/platform/wifi/nm-wifi-utils.c
@@ -25,7 +25,6 @@
 
 #include <sys/stat.h>
 #include <stdio.h>
-#include <string.h>
 #include <fcntl.h>
 
 #include "nm-wifi-utils-private.h"
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
index f8708bc6..b7a51e9b 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -23,7 +23,6 @@
 
 #include <linux/if.h>
 
-#include "nm-utils/nm-errno.h"
 #include "platform/linux/nl802154.h"
 #include "platform/nm-netlink.h"
 #include "platform/nm-platform-utils.h"
@@ -154,17 +153,19 @@ struct nl802154_interface {
 static int
 nl802154_get_interface_handler (struct nl_msg *msg, void *arg)
 {
+	static const struct nla_policy nl802154_policy[] = {
+		[NL802154_ATTR_PAN_ID]     = { .type = NLA_U16 },
+		[NL802154_ATTR_SHORT_ADDR] = { .type = NLA_U16 },
+	};
+	struct nlattr *tb[G_N_ELEMENTS (nl802154_policy)];
 	struct nl802154_interface *info = arg;
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
-	struct nlattr *tb[NL802154_ATTR_MAX + 1] = { 0, };
-	static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX + 1] = {
-		[NL802154_ATTR_PAN_ID] =            { .type = NLA_U16 },
-		[NL802154_ATTR_SHORT_ADDR] =        { .type = NLA_U16 },
-	};
 
-	if (nla_parse (tb, NL802154_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), nl802154_policy) < 0)
-	return NL_SKIP;
+	if (nla_parse_arr (tb,
+	                   genlmsg_attrdata (gnlh, 0),
+	                   genlmsg_attrlen (gnlh, 0),
+	                   nl802154_policy) < 0)
+		return NL_SKIP;
 
 	if (tb[NL802154_ATTR_PAN_ID])
 		info->pan_id = le16toh (nla_get_u16 (tb[NL802154_ATTR_PAN_ID]));
diff --git a/src/ppp/nm-ppp-manager-call.c b/src/ppp/nm-ppp-manager-call.c
index 8f658416..d951e4fd 100644
--- a/src/ppp/nm-ppp-manager-call.c
+++ b/src/ppp/nm-ppp-manager-call.c
@@ -24,7 +24,6 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <errno.h>
 
 #include "nm-manager.h"
 #include "nm-core-utils.h"
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index d8fdbab2..04c14dfb 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -26,11 +26,9 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <signal.h>
-#include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
 #include <asm/types.h>
@@ -182,6 +180,7 @@ monitor_cb (gpointer user_data)
 	NMPPPManager *self = NM_PPP_MANAGER (user_data);
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
 	const char *ifname;
+	int errsv;
 
 	ifname = nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex);
 
@@ -193,8 +192,9 @@ monitor_cb (gpointer user_data)
 
 		nm_utils_ifname_cpy (req.ifr_name, ifname);
 		if (ioctl (priv->monitor_fd, SIOCGPPPSTATS, &req) < 0) {
-			if (errno != ENODEV)
-				_LOGW ("could not read ppp stats: %s", strerror (errno));
+			errsv = errno;
+			if (errsv != ENODEV)
+				_LOGW ("could not read ppp stats: %s", nm_strerror_native (errsv));
 		} else {
 			g_signal_emit (self, signals[STATS], 0,
 			               (guint) stats.p.ppp_ibytes,
@@ -209,19 +209,23 @@ static void
 monitor_stats (NMPPPManager *self)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (self);
+	int errsv;
 
 	/* already monitoring */
 	if (priv->monitor_fd >= 0)
 		return;
 
 	priv->monitor_fd = socket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	if (priv->monitor_fd >= 0) {
-		g_warn_if_fail (priv->monitor_id == 0);
-		if (priv->monitor_id)
-			g_source_remove (priv->monitor_id);
-		priv->monitor_id = g_timeout_add_seconds (5, monitor_cb, self);
-	} else
-		_LOGW ("could not monitor PPP stats: %s", strerror (errno));
+	if (priv->monitor_fd < 0) {
+		errsv = errno;
+		_LOGW ("could not monitor PPP stats: %s", nm_strerror_native (errsv));
+		return;
+	}
+
+	g_warn_if_fail (priv->monitor_id == 0);
+	if (priv->monitor_id)
+		g_source_remove (priv->monitor_id);
+	priv->monitor_id = g_timeout_add_seconds (5, monitor_cb, self);
 }
 
 /*****************************************************************************/
@@ -363,7 +367,7 @@ impl_ppp_manager_need_secrets (NMDBusObject *obj,
 	const char *username = NULL;
 	const char *password = NULL;
 	guint32 tries;
-	GPtrArray *hints = NULL;
+	gs_unref_ptrarray GPtrArray *hints = NULL;
 	GError *error = NULL;
 	NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
@@ -393,18 +397,18 @@ impl_ppp_manager_need_secrets (NMDBusObject *obj,
 	if (tries > 1)
 		flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
 	priv->secrets_id = nm_act_request_get_secrets (priv->act_req,
 	                                               FALSE,
 	                                               priv->secrets_setting_name,
 	                                               flags,
-	                                               hints ? g_ptr_array_index (hints, 0) : NULL,
+	                                               hints ? (const char *const*) hints->pdata : NULL,
 	                                               ppp_secrets_cb,
 	                                               self);
 	g_object_set_qdata (G_OBJECT (applied_connection), ppp_manager_secret_tries_quark (), GUINT_TO_POINTER (++tries));
 	priv->pending_secrets_context = invocation;
-
-	if (hints)
-		g_ptr_array_free (hints, TRUE);
 }
 
 static void
@@ -1049,8 +1053,8 @@ _ppp_manager_start (NMPPPManager *self,
 
 	_LOGI ("starting PPP connection");
 
-	_LOGD ("command line: %s", cmd_str);
-	       (cmd_str = g_strjoinv (" ", (char **) ppp_cmd->pdata));
+	_LOGD ("command line: %s",
+	       (cmd_str = g_strjoinv (" ", (char **) ppp_cmd->pdata)));
 
 	priv->pid = 0;
 	if (!g_spawn_async (NULL,
diff --git a/src/ppp/nm-pppd-plugin.c b/src/ppp/nm-pppd-plugin.c
index 5e99be41..a8d6749a 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/ppp/nm-pppd-plugin.c
@@ -22,7 +22,6 @@
 #include <config.h>
 #define ___CONFIG_H__
 
-#include <string.h>
 #include <pppd/pppd.h>
 #include <pppd/fsm.h>
 #include <pppd/ipcp.h>
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index edadee14..8924c39f 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-agent-manager.h"
 
-#include <string.h>
 #include <pwd.h>
 
 #include "nm-common-macros.h"
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 0beb5ea7..8d1f9583 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -23,8 +23,6 @@
 
 #include "nm-settings-connection.h"
 
-#include <string.h>
-
 #include "c-list/src/c-list.h"
 
 #include "nm-common-macros.h"
@@ -208,141 +206,6 @@ nm_settings_connection_get_last_secret_agent_version_id (NMSettingsConnection *s
 
 /*****************************************************************************/
 
-/* Return TRUE to keep, FALSE to drop */
-typedef gboolean (*ForEachSecretFunc) (NMSettingSecretFlags flags,
-                                       gpointer user_data);
-
-/* Returns always a non-NULL, non-floating variant that must
- * be unrefed by the caller. */
-static GVariant *
-for_each_secret (NMConnection *self,
-                 GVariant *secrets,
-                 gboolean remove_non_secrets,
-                 ForEachSecretFunc callback,
-                 gpointer callback_data)
-{
-	GVariantBuilder secrets_builder, setting_builder;
-	GVariantIter secrets_iter, *setting_iter;
-	const char *setting_name;
-
-	/* This function, given a dict of dicts representing new secrets of
-	 * an NMConnection, walks through each toplevel dict (which represents a
-	 * NMSetting), and for each setting, walks through that setting dict's
-	 * properties.  For each property that's a secret, it will check that
-	 * secret's flags in the backing NMConnection object, and call a supplied
-	 * callback.
-	 *
-	 * The one complexity is that the VPN setting's 'secrets' property is
-	 * *also* a dict (since the key/value pairs are arbitrary and known
-	 * only to the VPN plugin itself).  That means we have three levels of
-	 * dicts that we potentially have to traverse here.  When we hit the
-	 * VPN setting's 'secrets' property, we special-case that and iterate over
-	 * each item in that 'secrets' dict, calling the supplied callback
-	 * each time.
-	 */
-
-	g_return_val_if_fail (callback, NULL);
-
-	g_variant_iter_init (&secrets_iter, secrets);
-	g_variant_builder_init (&secrets_builder, NM_VARIANT_TYPE_CONNECTION);
-	while (g_variant_iter_next (&secrets_iter, "{&sa{sv}}", &setting_name, &setting_iter)) {
-		NMSetting *setting;
-		const char *secret_name;
-		GVariant *val;
-
-		setting = nm_connection_get_setting_by_name (self, setting_name);
-		if (setting == NULL) {
-			g_variant_iter_free (setting_iter);
-			continue;
-		}
-
-		g_variant_builder_init (&setting_builder, NM_VARIANT_TYPE_SETTING);
-		while (g_variant_iter_next (setting_iter, "{&sv}", &secret_name, &val)) {
-			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-			/* VPN secrets need slightly different treatment here since the
-			 * "secrets" property is actually a hash table of secrets.
-			 */
-			if (NM_IS_SETTING_VPN (setting) && !g_strcmp0 (secret_name, NM_SETTING_VPN_SECRETS)) {
-				GVariantBuilder vpn_secrets_builder;
-				GVariantIter vpn_secrets_iter;
-				const char *vpn_secret_name, *secret;
-
-				/* Iterate through each secret from the VPN dict in the overall secrets dict */
-				g_variant_builder_init (&vpn_secrets_builder, G_VARIANT_TYPE ("a{ss}"));
-				g_variant_iter_init (&vpn_secrets_iter, val);
-				while (g_variant_iter_next (&vpn_secrets_iter, "{&s&s}", &vpn_secret_name, &secret)) {
-					if (!nm_setting_get_secret_flags (setting, vpn_secret_name, &secret_flags, NULL)) {
-						if (!remove_non_secrets)
-							g_variant_builder_add (&vpn_secrets_builder, "{ss}", vpn_secret_name, secret);
-						continue;
-					}
-
-					if (callback (secret_flags, callback_data))
-						g_variant_builder_add (&vpn_secrets_builder, "{ss}", vpn_secret_name, secret);
-				}
-
-				g_variant_builder_add (&setting_builder, "{sv}",
-				                       secret_name, g_variant_builder_end (&vpn_secrets_builder));
-			} else {
-				if (!nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL)) {
-					if (!remove_non_secrets)
-						g_variant_builder_add (&setting_builder, "{sv}", secret_name, val);
-					continue;
-				}
-				if (callback (secret_flags, callback_data))
-					g_variant_builder_add (&setting_builder, "{sv}", secret_name, val);
-			}
-			g_variant_unref (val);
-		}
-
-		g_variant_iter_free (setting_iter);
-		g_variant_builder_add (&secrets_builder, "{sa{sv}}", setting_name, &setting_builder);
-	}
-
-	return g_variant_ref_sink (g_variant_builder_end (&secrets_builder));
-}
-
-typedef gboolean (*FindSecretFunc) (NMSettingSecretFlags flags,
-                                    gpointer user_data);
-
-typedef struct {
-	FindSecretFunc find_func;
-	gpointer find_func_data;
-	gboolean found;
-} FindSecretData;
-
-static gboolean
-find_secret_for_each_func (NMSettingSecretFlags flags,
-                           gpointer user_data)
-{
-	FindSecretData *data = user_data;
-
-	if (!data->found)
-		data->found = data->find_func (flags, data->find_func_data);
-	return FALSE;
-}
-
-static gboolean
-find_secret (NMConnection *self,
-             GVariant *secrets,
-             FindSecretFunc callback,
-             gpointer callback_data)
-{
-	FindSecretData data;
-	GVariant *dummy;
-
-	data.find_func = callback;
-	data.find_func_data = callback_data;
-	data.found = FALSE;
-
-	dummy = for_each_secret (self, secrets, FALSE, find_secret_for_each_func, &data);
-	g_variant_unref (dummy);
-	return data.found;
-}
-
-/*****************************************************************************/
-
 static void
 set_visible (NMSettingsConnection *self, gboolean new_visible)
 {
@@ -938,8 +801,8 @@ typedef struct {
 } ForEachSecretFlags;
 
 static gboolean
-validate_secret_flags (NMSettingSecretFlags flags,
-                       gpointer user_data)
+validate_secret_flags_cb (NMSettingSecretFlags flags,
+                          gpointer user_data)
 {
 	ForEachSecretFlags *cmp_flags = user_data;
 
@@ -950,6 +813,18 @@ validate_secret_flags (NMSettingSecretFlags flags,
 	return TRUE;
 }
 
+static GVariant *
+validate_secret_flags (NMConnection *connection,
+                       GVariant *secrets,
+                       ForEachSecretFlags *cmp_flags)
+{
+	return g_variant_ref_sink (_nm_connection_for_each_secret (connection,
+	                                                           secrets,
+	                                                           TRUE,
+	                                                           validate_secret_flags_cb,
+	                                                           cmp_flags));
+}
+
 static gboolean
 secret_is_system_owned (NMSettingSecretFlags flags,
                         gpointer user_data)
@@ -992,7 +867,7 @@ get_cmp_flags (NMSettingsConnection *self, /* only needed for logging */
 		 * save those system-owned secrets.  If not, discard them and use the
 		 * existing secrets, or fail the connection.
 		 */
-		*agent_had_system = find_secret (connection, secrets, secret_is_system_owned, NULL);
+		*agent_had_system = _nm_connection_find_secret (connection, secrets, secret_is_system_owned, NULL);
 		if (*agent_had_system) {
 			if (flags == NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE) {
 				/* No user interaction was allowed when requesting secrets; the
@@ -1151,14 +1026,14 @@ get_secrets_done_cb (NMAgentManager *manager,
 	/* Update the connection with our existing secrets from backing storage */
 	nm_connection_clear_secrets (nm_settings_connection_get_connection (self));
 	if (!dict || nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, dict, &local)) {
-		GVariant *filtered_secrets;
+		gs_unref_variant GVariant *filtered_secrets = NULL;
 
 		/* Update the connection with the agent's secrets; by this point if any
 		 * system-owned secrets exist in 'secrets' the agent that provided them
 		 * will have been authenticated, so those secrets can replace the existing
 		 * system secrets.
 		 */
-		filtered_secrets = for_each_secret (nm_settings_connection_get_connection (self), secrets, TRUE, validate_secret_flags, &cmp_flags);
+		filtered_secrets = validate_secret_flags (nm_settings_connection_get_connection (self), secrets, &cmp_flags);
 		if (nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, filtered_secrets, &local)) {
 			/* Now that all secrets are updated, copy and cache new secrets,
 			 * then save them to backing storage.
@@ -1194,7 +1069,6 @@ get_secrets_done_cb (NMAgentManager *manager,
 			       call_id,
 			       local->message);
 		}
-		g_variant_unref (filtered_secrets);
 	} else {
 		_LOGD ("(%s:%p) failed to update with existing secrets: %s",
 		       setting_name,
@@ -1218,11 +1092,10 @@ get_secrets_done_cb (NMAgentManager *manager,
 		nm_connection_clear_secrets (applied_connection);
 
 		if (!dict || nm_connection_update_secrets (applied_connection, setting_name, dict, NULL)) {
-			GVariant *filtered_secrets;
+			gs_unref_variant GVariant *filtered_secrets = NULL;
 
-			filtered_secrets = for_each_secret (applied_connection, secrets, TRUE, validate_secret_flags, &cmp_flags);
+			filtered_secrets = validate_secret_flags (applied_connection, secrets, &cmp_flags);
 			nm_connection_update_secrets (applied_connection, setting_name, filtered_secrets, NULL);
-			g_variant_unref (filtered_secrets);
 		}
 	}
 
@@ -1878,6 +1751,9 @@ settings_connection_update (NMSettingsConnection *self,
 			                                           &error);
 			if (!tmp)
 				goto error;
+
+			if (!nm_connection_verify_secrets (tmp, &error))
+				goto error;
 		}
 	}
 
@@ -2777,7 +2653,7 @@ _autoconnect_retries_set (NMSettingsConnection *self,
 		/* NOTE: the blocked time must be identical for all connections, otherwise
 		 * the tracking of resetting the retry count in NMPolicy needs adjustment
 		 * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
-		 * the next-timeout everytime a connection gets blocked). */
+		 * the next-timeout every time a connection gets blocked). */
 		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_s () + AUTOCONNECT_RESET_RETRIES_TIMER;
 	}
 }
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 74de6cc2..fd1d316a 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -29,8 +29,6 @@
 
 #include <unistd.h>
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 #include <gmodule.h>
 #include <pwd.h>
 
@@ -375,7 +373,7 @@ _clear_connections_cached_list (NMSettingsPrivate *priv)
 /**
  * nm_settings_get_connections:
  * @self: the #NMSettings
- * @out_len: (out): (allow-none): returns the number of returned
+ * @out_len: (out) (allow-none): returns the number of returned
  *   connections.
  *
  * Returns: (transfer none): a list of NMSettingsConnections. The list is
@@ -638,7 +636,7 @@ add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
 
 	if (stat (path, &st) != 0) {
 		errsv = errno;
-		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, strerror (errsv));
+		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
 		return TRUE;
 	}
 	if (!S_ISREG (st.st_mode)) {
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.c b/src/settings/plugins/ibft/nms-ibft-connection.c
index fb7f18f8..a36d8a31 100644
--- a/src/settings/plugins/ibft/nms-ibft-connection.c
+++ b/src/settings/plugins/ibft/nms-ibft-connection.c
@@ -22,7 +22,6 @@
 
 #include "nms-ibft-connection.h"
 
-#include <string.h>
 #include <net/ethernet.h>
 #include <netinet/ether.h>
 #include <glib/gstdio.h>
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
index 69dd3733..00b25068 100644
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ b/src/settings/plugins/ibft/nms-ibft-plugin.c
@@ -22,9 +22,7 @@
 
 #include "nms-ibft-plugin.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <gmodule.h>
 
 #include "nm-setting-connection.h"
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
index ac5824a1..c6c14376 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ b/src/settings/plugins/ibft/nms-ibft-reader.c
@@ -23,13 +23,11 @@
 #include "nms-ibft-reader.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <sys/wait.h>
 #include <sys/inotify.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
diff --git a/src/settings/plugins/ibft/tests/test-ibft.c b/src/settings/plugins/ibft/tests/test-ibft.c
index 5e46be2e..4c45f574 100644
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ b/src/settings/plugins/ibft/tests/test-ibft.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
index e7a74a1a..04cbb5bc 100644
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
@@ -23,9 +23,7 @@
 #include "nm-inotify-helper.h"
 
 #include <unistd.h>
-#include <string.h>
 #include <sys/inotify.h>
-#include <errno.h>
 
 #include "NetworkManagerUtils.h"
 
@@ -143,12 +141,12 @@ init_inotify (NMInotifyHelper *self)
 {
 	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
 	GIOChannel *channel;
+	int errsv;
 
 	priv->ifd = inotify_init1 (IN_CLOEXEC);
 	if (priv->ifd == -1) {
-		int errsv = errno;
-
-		nm_log_warn (LOGD_SETTINGS, "couldn't initialize inotify: %s (%d)", strerror (errsv), errsv);
+		errsv = errno;
+		nm_log_warn (LOGD_SETTINGS, "couldn't initialize inotify: %s (%d)", nm_strerror_native (errsv), errsv);
 		return FALSE;
 	}
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
index ca319ddc..4f769c5f 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
@@ -22,7 +22,6 @@
 
 #include "nms-ifcfg-rh-connection.h"
 
-#include <string.h>
 #include <sys/inotify.h>
 #include <glib/gstdio.h>
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 05d4d738..89272edb 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -25,9 +25,7 @@
 
 #include "nms-ifcfg-rh-plugin.h"
 
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <gmodule.h>
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 6eb99d3b..7c1db225 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -23,13 +23,11 @@
 #include "nms-ifcfg-rh-reader.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <sys/wait.h>
 #include <sys/inotify.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
@@ -189,7 +187,7 @@ _secret_password_raw_to_bytes (const char *ifcfg_key,
 		password_raw += 2;
 
 	secret = nm_secret_buf_new (strlen (password_raw) / 2 + 3);
-	if (!_nm_utils_hexstr2bin_full (password_raw, FALSE, FALSE, ":", 0, secret->bin, secret->len, &len)) {
+	if (!nm_utils_hexstr2bin_full (password_raw, FALSE, FALSE, ":", 0, secret->bin, secret->len, &len)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Invalid hex password in %s",
 		             ifcfg_key);
@@ -801,7 +799,7 @@ enum {
  * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created
  *   route object that is completed with the settings from options. Otherwise,
  *   it shall point to %NULL and a new route is created and returned.
- * @out_route: (out): (transfer-full): (allow-none): the parsed %NMIPRoute instance.
+ * @out_route: (out) (transfer-full) (allow-none): the parsed %NMIPRoute instance.
  *   In case a @options_route is passed in, it returns the input route that was modified
  *   in-place. But the caller must unref the returned route in either case.
  * @error: the failure description.
@@ -1427,8 +1425,8 @@ make_user_setting (shvarFile *ifcfg)
 			has_user_data = TRUE;
 	}
 
-	return has_user_data
-	       ? g_steal_pointer (&s_user)
+	return   has_user_data
+	       ? NM_SETTING (g_steal_pointer (&s_user))
 	       : NULL;
 }
 
@@ -1613,7 +1611,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	              NULL);
 
 	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		return g_steal_pointer (&s_ip4);
+		return NM_SETTING (g_steal_pointer (&s_ip4));
 
 	/* Handle DHCP settings */
 	nm_clear_g_free (&value);
@@ -1804,7 +1802,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	}
 	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (int) timeout, NULL);
 
-	return g_steal_pointer (&s_ip4);
+	return NM_SETTING (g_steal_pointer (&s_ip4));
 }
 
 static void
@@ -2946,7 +2944,7 @@ make_wep_setting (shvarFile *ifcfg,
 		return NULL;
 	}
 
-	return g_steal_pointer (&s_wsec);
+	return NM_SETTING (g_steal_pointer (&s_wsec));
 }
 
 static gboolean
@@ -3612,7 +3610,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingWirelessSecurity *wsec = NULL;
 	gs_free char *value = NULL;
 	const char *v;
-	gboolean wpa_psk = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
+	gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
 	int i_val;
 	GError *local = NULL;
 
@@ -3620,9 +3618,10 @@ make_wpa_setting (shvarFile *ifcfg,
 
 	v = svGetValueStr (ifcfg, "KEY_MGMT", &value);
 	wpa_psk = nm_streq0 (v, "WPA-PSK");
+	wpa_sae = nm_streq0 (v, "SAE");
 	wpa_eap = nm_streq0 (v, "WPA-EAP");
 	ieee8021x = nm_streq0 (v, "IEEE8021X");
-	if (!wpa_psk && !wpa_eap && !ieee8021x)
+	if (!wpa_psk && !wpa_sae && !wpa_eap && !ieee8021x)
 		return NULL; /* Not WPA or Dynamic WEP */
 
 	/* WPS */
@@ -3636,7 +3635,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	              NULL);
 
 	/* Pairwise and Group ciphers (only relevant for WPA/RSN) */
-	if (wpa_psk || wpa_eap) {
+	if (wpa_psk || wpa_sae || wpa_eap) {
 		fill_wpa_ciphers (ifcfg, wsec, FALSE, adhoc);
 		fill_wpa_ciphers (ifcfg, wsec, TRUE, adhoc);
 	}
@@ -3659,7 +3658,7 @@ make_wpa_setting (shvarFile *ifcfg,
 			nm_setting_wireless_security_add_proto (wsec, "rsn");
 	}
 
-	if (wpa_psk) {
+	if (wpa_psk || wpa_sae) {
 		NMSettingSecretFlags psk_flags;
 
 		psk_flags = _secret_read_ifcfg_flags (ifcfg, "WPA_PSK_FLAGS");
@@ -3680,8 +3679,12 @@ make_wpa_setting (shvarFile *ifcfg,
 
 		if (adhoc)
 			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-none", NULL);
-		else
+		else if (wpa_psk)
 			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", NULL);
+		else if (wpa_sae)
+			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
+		else
+			g_assert_not_reached ();
 	} else if (wpa_eap || ieee8021x) {
 		/* Adhoc mode is mutually exclusive with any 802.1x-based authentication */
 		if (adhoc) {
@@ -3946,9 +3949,8 @@ make_wireless_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr_cp (ifcfg, "CHANNEL");
 	if (value) {
-		errno = 0;
 		chan = _nm_utils_ascii_str_to_int64 (value, 10, 1, 196, 0);
-		if (errno || (chan == 0)) {
+		if (chan == 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid wireless channel '%s'", value);
 			g_free (value);
@@ -4992,7 +4994,7 @@ handle_bridge_option (NMSetting *setting,
 			} else {
 				v = _nm_utils_ascii_str_to_int64 (value, 10, 0, 1, -1);
 				if (v == -1) {
-					error_message = g_strerror (errno);
+					error_message = nm_strerror_native (errno);
 					goto warn;
 				}
 			}
@@ -5004,7 +5006,7 @@ handle_bridge_option (NMSetting *setting,
 		case G_TYPE_UINT:
 			v = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT, -1);
 			if (v == -1) {
-				error_message = g_strerror (errno);
+				error_message = nm_strerror_native (errno);
 				goto warn;
 			}
 			if (!nm_g_object_set_property_uint (G_OBJECT (setting), m[i].property_name, v, NULL)) {
@@ -5229,16 +5231,14 @@ is_vlan_device (const char *name, shvarFile *parsed)
 static gboolean
 is_wifi_device (const char *name, shvarFile *parsed)
 {
-	int ifindex;
+	const NMPlatformLink *pllink;
 
 	g_return_val_if_fail (name != NULL, FALSE);
 	g_return_val_if_fail (parsed != NULL, FALSE);
 
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, name);
-	if (ifindex == 0)
-		return FALSE;
-
-	return nm_platform_link_get_type (NM_PLATFORM_GET, ifindex) == NM_LINK_TYPE_WIFI;
+	pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+	return    pllink
+	       && pllink->type == NM_LINK_TYPE_WIFI;
 }
 
 static void
@@ -5384,7 +5384,7 @@ make_vlan_setting (shvarFile *ifcfg,
 	parse_prio_map_list (s_vlan, ifcfg, "VLAN_INGRESS_PRIORITY_MAP", NM_VLAN_INGRESS_MAP);
 	parse_prio_map_list (s_vlan, ifcfg, "VLAN_EGRESS_PRIORITY_MAP", NM_VLAN_EGRESS_MAP);
 
-	return g_steal_pointer (&s_vlan);
+	return NM_SETTING (g_steal_pointer (&s_vlan));
 }
 
 static NMConnection *
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 49096d26..22c9061b 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -23,7 +23,6 @@
 #include "nms-ifcfg-rh-utils.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index f5be7520..ee7fd161 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -22,11 +22,9 @@
 
 #include "nms-ifcfg-rh-writer.h"
 
-#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -603,6 +601,10 @@ write_wireless_security_setting (NMConnection *connection,
 		svSetValueStr (ifcfg, "KEY_MGMT", "WPA-PSK");
 		wpa = TRUE;
 		*no_8021x = TRUE;
+	} else if (!strcmp (key_mgmt, "sae")) {
+		svSetValueStr (ifcfg, "KEY_MGMT", "SAE");
+		wpa = TRUE;
+		*no_8021x = TRUE;
 	} else if (!strcmp (key_mgmt, "ieee8021x")) {
 		svSetValueStr (ifcfg, "KEY_MGMT", "IEEE8021X");
 		dynamic_wep = TRUE;
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 3259d936..f3d58e26 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -27,11 +27,9 @@
 
 #include "shvar.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -215,9 +213,9 @@ _escape_ansic (const char *source)
 
 /*****************************************************************************/
 
-#define _char_req_escape(ch)        NM_IN_SET (ch,      '\"', '\\',       '$', '`')
-#define _char_req_escape_old(ch)    NM_IN_SET (ch,      '\"', '\\', '\'', '$', '`', '~')
-#define _char_req_quotes(ch)        NM_IN_SET (ch, ' ',             '\'',           '~', '\t', '|', '&', ';', '(', ')', '<', '>')
+#define _char_req_escape(ch)        NM_IN_SET (ch,      '"', '\\',       '$', '`')
+#define _char_req_escape_old(ch)    NM_IN_SET (ch,      '"', '\\', '\'', '$', '`', '~')
+#define _char_req_quotes(ch)        NM_IN_SET (ch, ' ',            '\'',           '~', '\t', '|', '&', ';', '(', ')', '<', '>')
 
 const char *
 svEscape (const char *s, char **to_free)
@@ -815,7 +813,7 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 
 		g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 		             "Could not read file '%s': %s",
-		             name, strerror (errsv));
+		             name, nm_strerror_native (errsv));
 		return NULL;
 	}
 
@@ -1317,34 +1315,32 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 	FILE *f;
 	int tmpfd;
 	CList *current;
+	int errsv;
 
 	if (s->modified) {
 		if (s->fd == -1)
 			s->fd = open (s->fileName, O_WRONLY | O_CREAT | O_CLOEXEC, mode);
 		if (s->fd == -1) {
-			int errsv = errno;
-
+			errsv = errno;
 			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 			             "Could not open file '%s' for writing: %s",
-			             s->fileName, strerror (errsv));
+			             s->fileName, nm_strerror_native (errsv));
 			return FALSE;
 		}
 		if (ftruncate (s->fd, 0) < 0) {
-			int errsv = errno;
-
+			errsv = errno;
 			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 			             "Could not overwrite file '%s': %s",
-			             s->fileName, strerror (errsv));
+			             s->fileName, nm_strerror_native (errsv));
 			return FALSE;
 		}
 
 		tmpfd = fcntl (s->fd, F_DUPFD_CLOEXEC, 0);
 		if (tmpfd == -1) {
-			int errsv = errno;
-
+			errsv = errno;
 			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 			             "Internal error writing file '%s': %s",
-			             s->fileName, strerror (errsv));
+			             s->fileName, nm_strerror_native (errsv));
 			return FALSE;
 		}
 		f = fdopen (tmpfd, "w");
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
new file mode 100644
index 00000000..68afbe97
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
@@ -0,0 +1,5 @@
+TYPE=Wireless
+DEVICE=wlan1
+ESSID=blahblah
+MODE=Managed
+KEY_MGMT=SAE
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae b/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
new file mode 100644
index 00000000..5a9569ed
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
@@ -0,0 +1 @@
+WPA_PSK="The king is dead."
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index d135ea43..b352fbfc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <linux/pkt_sched.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -123,7 +122,7 @@ _assert_reread_same_FIXME (NMConnection *connection, NMConnection *reread)
 
 /* dummy path for an "expected" file, meaning: don't check for expected
  * written ifcfg file. */
-static const char const NO_EXPECTED[1];
+static const char NO_EXPECTED[1];
 
 static void
 _assert_expected_content (NMConnection *connection, const char *filename, const char *expected)
@@ -2967,6 +2966,45 @@ test_read_wifi_wpa_psk (void)
 }
 
 static void
+test_read_wifi_sae (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wireless;
+	NMSettingWirelessSecurity *s_wsec;
+	GBytes *ssid;
+	const char *expected_ssid = "blahblah";
+
+	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-wifi-sae",
+	                                    NULL, TYPE_WIRELESS, NULL);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "System blahblah (test-wifi-sae)");
+
+	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
+	g_assert (nm_setting_connection_get_autoconnect (s_con));
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_assert (s_wireless);
+
+	g_assert_cmpint (nm_setting_wireless_get_mtu (s_wireless), ==, 0);
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	g_assert (ssid);
+	g_assert_cmpmem (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), expected_ssid, strlen (expected_ssid));
+
+	g_assert (!nm_setting_wireless_get_bssid (s_wireless));
+	g_assert_cmpstr (nm_setting_wireless_get_mode (s_wireless), ==, "infrastructure");
+
+	s_wsec = nm_connection_get_setting_wireless_security (connection);
+	g_assert (s_wsec);
+	g_assert_cmpstr (nm_setting_wireless_security_get_key_mgmt (s_wsec), ==, "sae");
+	g_assert_cmpstr (nm_setting_wireless_security_get_psk (s_wsec), ==, "The king is dead.");
+	g_assert (!nm_setting_wireless_security_get_auth_alg (s_wsec));
+}
+
+static void
 test_read_wifi_wpa_psk_2 (void)
 {
 	NMConnection *connection;
@@ -9981,10 +10019,14 @@ NMTST_DEFINE ();
 
 int main (int argc, char **argv)
 {
+	int errsv;
+
 	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
 
-	if (g_mkdir_with_parents (TEST_SCRATCH_DIR_TMP, 0755) != 0)
-		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR_TMP, g_strerror (errno));
+	if (g_mkdir_with_parents (TEST_SCRATCH_DIR_TMP, 0755) != 0) {
+		errsv = errno;
+		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR_TMP, nm_strerror_native (errsv));
+	}
 
 	g_test_add_func (TPATH "svUnescape", test_svUnescape);
 
@@ -10081,6 +10123,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wifi/read/wpa-psk/unquoted2", test_read_wifi_wpa_psk_unquoted2);
 	g_test_add_func (TPATH "wifi/read/wpa-psk/adhoc", test_read_wifi_wpa_psk_adhoc);
 	g_test_add_func (TPATH "wifi/read/wpa-psk/hex", test_read_wifi_wpa_psk_hex);
+	g_test_add_func (TPATH "wifi/read/sae", test_read_wifi_sae);
 	g_test_add_func (TPATH "wifi/read/dynamic-wep/leap", test_read_wifi_dynamic_wep_leap);
 	g_test_add_func (TPATH "wifi/read/wpa/eap/tls", test_read_wifi_wpa_eap_tls);
 	g_test_add_func (TPATH "wifi/read/wpa/eap/ttls/tls", test_read_wifi_wpa_eap_ttls_tls);
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
index 1b817044..d06078a9 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
@@ -24,7 +24,6 @@
 
 #include "nms-ifupdown-connection.h"
 
-#include <string.h>
 #include <glib/gstdio.h>
 
 #include "nm-dbus-interface.h"
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 73ecc2f9..6587fc84 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -26,7 +26,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <wordexp.h>
 #include <libgen.h>
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 369fa70d..fd5561ae 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -25,10 +25,8 @@
 
 #include "nms-ifupdown-parser.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <ctype.h>
 
 #include "nm-core-internal.h"
@@ -63,7 +61,7 @@ _ifupdownplugin_guess_connection_type (if_block *block)
 {
 	const char *ret_type = NULL;
 
-	if(nm_streq0 (ifparser_getkey (block, "inet"), "ppp"))
+	if (nm_streq0 (ifparser_getkey (block, "inet"), "ppp"))
 		ret_type = NM_SETTING_PPP_SETTING_NAME;
 	else {
 		if_data *ifb;
@@ -75,7 +73,7 @@ _ifupdownplugin_guess_connection_type (if_block *block)
 				break;
 			}
 		}
-		if(!ret_type)
+		if (!ret_type)
 			ret_type = NM_SETTING_WIRED_SETTING_NAME;
 	}
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index b66eedc7..99a59477 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -26,7 +26,6 @@
 
 #include "nms-ifupdown-plugin.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <gmodule.h>
 
@@ -282,8 +281,10 @@ initialize (NMSettingsPlugin *plugin)
 
 		g_hash_table_iter_init (&iter, priv->eni_ifaces);
 		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) conn)) {
-			_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-			                                                  NM_SETTINGS_CONNECTION (conn));
+			if (conn) {
+				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+				                                                  NM_SETTINGS_CONNECTION (conn));
+			}
 		}
 	}
 }
diff --git a/src/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
index 82ee1c4a..674cb19c 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 
 #include "settings/plugins/ifupdown/nms-ifupdown-interface-parser.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
index 7511f206..3b362978 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-connection.c
@@ -23,7 +23,6 @@
 
 #include "nms-keyfile-connection.h"
 
-#include <string.h>
 #include <glib/gstdio.h>
 
 #include "nm-dbus-interface.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index ae9bea13..c13cc1ff 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -26,8 +26,6 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <string.h>
-
 #include <glib/gstdio.h>
 
 #include "nm-connection.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index 314b1033..5778f13c 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -23,7 +23,6 @@
 #include "nms-keyfile-reader.h"
 
 #include <sys/stat.h>
-#include <string.h>
 
 #include "nm-keyfile-internal.h"
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index 8d4ec943..3c4b0288 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -23,7 +23,6 @@
 #include "nms-keyfile-utils.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 
 #include "nm-keyfile-internal.h"
@@ -277,14 +276,14 @@ nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
 		if (stat (filename, &st) != 0) {
 			errsv = errno;
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "cannot access file: %s", g_strerror (errsv));
+			             "cannot access file: %s", nm_strerror_native (errsv));
 			return FALSE;
 		}
 	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
 		if (lstat (filename, &st) != 0) {
 			errsv = errno;
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "cannot access file: %s", g_strerror (errsv));
+			             "cannot access file: %s", nm_strerror_native (errsv));
 			return FALSE;
 		}
 	} else
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 23a6a77c..8c75d8c7 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -26,8 +26,6 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
-#include <string.h>
 
 #include "nm-keyfile-internal.h"
 
@@ -177,27 +175,30 @@ _internal_write_connection (NMConnection *connection,
                             uid_t owner_uid,
                             pid_t owner_grp,
                             const char *existing_path,
+                            gboolean existing_path_read_only,
                             gboolean force_rename,
                             char **out_path,
                             NMConnection **out_reread,
                             gboolean *out_reread_same,
                             GError **error)
 {
-	gs_unref_keyfile GKeyFile *key_file = NULL;
-	gs_free char *data = NULL;
-	gsize len;
+	gs_unref_keyfile GKeyFile *kf_file = NULL;
+	gs_free char *kf_content_buf = NULL;
+	gsize kf_content_len;
 	gs_free char *path = NULL;
 	const char *id;
 	WriteInfo info = { 0 };
 	GError *local_err = NULL;
 	int errsv;
-	gboolean rename = force_rename;
+	gboolean rename;
 
 	g_return_val_if_fail (!out_path || !*out_path, FALSE);
 	g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE);
 
-	if (existing_path && !g_str_has_prefix (existing_path, keyfile_dir))
-		rename = TRUE;
+	rename =    force_rename
+	         || existing_path_read_only
+	         || (   existing_path
+	             && !nm_utils_file_is_in_path (existing_path, keyfile_dir));
 
 	switch (_nm_connection_verify (connection, error)) {
 	case NM_SETTING_VERIFY_NORMALIZABLE:
@@ -214,11 +215,11 @@ _internal_write_connection (NMConnection *connection,
 
 	info.keyfile_dir = keyfile_dir;
 
-	key_file = nm_keyfile_write (connection, _handler_write, &info, error);
-	if (!key_file)
+	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
+	if (!kf_file)
 		return FALSE;
-	data = g_key_file_to_data (key_file, &len, error);
-	if (!data)
+	kf_content_buf = g_key_file_to_data (kf_file, &kf_content_len, error);
+	if (!kf_content_buf)
 		return FALSE;
 
 	if (!g_file_test (keyfile_dir, G_FILE_TEST_IS_DIR))
@@ -227,13 +228,14 @@ _internal_write_connection (NMConnection *connection,
 	/* If we have existing file path, use it. Else generate one from
 	 * connection's ID.
 	 */
-	if (existing_path != NULL && !rename) {
+	if (   existing_path
+	    && !rename)
 		path = g_strdup (existing_path);
-	} else {
-		char *filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
+	else {
+		gs_free char *filename_escaped = NULL;
 
+		filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
 		path = g_build_filename (keyfile_dir, filename_escaped, NULL);
-		g_free (filename_escaped);
 	}
 
 	/* If a file with this path already exists (but isn't the existing path
@@ -243,13 +245,15 @@ _internal_write_connection (NMConnection *connection,
 	 * there's a race here, but there's not a lot we can do about it, and
 	 * we shouldn't get more than one connection with the same UUID either.
 	 */
-	if (g_strcmp0 (path, existing_path) != 0 && g_file_test (path, G_FILE_TEST_EXISTS)) {
+	if (   !nm_streq0 (path, existing_path)
+	    && g_file_test (path, G_FILE_TEST_EXISTS)) {
 		guint i;
 		gboolean name_found = FALSE;
 
 		/* A keyfile with this connection's ID already exists. Pick another name. */
 		for (i = 0; i < 100; i++) {
-			char *filename, *filename_escaped;
+			gs_free char *filename_escaped = NULL;
+			gs_free char *filename = NULL;
 
 			if (i == 0)
 				filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (connection));
@@ -260,15 +264,15 @@ _internal_write_connection (NMConnection *connection,
 
 			g_free (path);
 			path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
-			g_free (filename);
-			g_free (filename_escaped);
-			if (g_strcmp0 (path, existing_path) == 0 || !g_file_test (path, G_FILE_TEST_EXISTS)) {
+
+			if (   nm_streq0 (path, existing_path)
+			    || !g_file_test (path, G_FILE_TEST_EXISTS)) {
 				name_found = TRUE;
 				break;
 			}
 		}
 		if (!name_found) {
-			if (existing_path == NULL) {
+			if (existing_path_read_only || !existing_path) {
 				/* this really should not happen, we tried hard to find an unused name... bail out. */
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 				                    "could not find suitable keyfile file name (%s already used)", path);
@@ -281,13 +285,7 @@ _internal_write_connection (NMConnection *connection,
 		}
 	}
 
-	/* In case of updating the connection and changing the file path,
-	 * we need to remove the old one, not to end up with two connections.
-	 */
-	if (existing_path != NULL && strcmp (path, existing_path) != 0)
-		unlink (existing_path);
-
-	nm_utils_file_set_contents (path, data, len, 0600, &local_err);
+	nm_utils_file_set_contents (path, kf_content_buf, kf_content_len, 0600, &local_err);
 	if (local_err) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error writing to file '%s': %s",
@@ -300,17 +298,24 @@ _internal_write_connection (NMConnection *connection,
 		errsv = errno;
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error chowning '%s': %s (%d)",
-		             path, g_strerror (errsv), errsv);
+		             path, nm_strerror_native (errsv), errsv);
 		unlink (path);
 		return FALSE;
 	}
 
-	if (out_reread || out_reread_same)
-	{
+	/* In case of updating the connection and changing the file path,
+	 * we need to remove the old one, not to end up with two connections.
+	 */
+	if (   existing_path
+	    && !existing_path_read_only
+	    && !nm_streq (path, existing_path))
+		unlink (existing_path);
+
+	if (out_reread || out_reread_same) {
 		gs_unref_object NMConnection *reread = NULL;
 		gboolean reread_same = FALSE;
 
-		reread = nms_keyfile_reader_from_keyfile (key_file, path, NULL, profile_dir, FALSE, NULL);
+		reread = nms_keyfile_reader_from_keyfile (kf_file, path, NULL, profile_dir, FALSE, NULL);
 
 		nm_assert (NM_IS_CONNECTION (reread));
 
@@ -365,6 +370,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	                                   0,
 	                                   0,
 	                                   existing_path,
+	                                   FALSE,
 	                                   force_rename,
 	                                   out_path,
 	                                   out_reread,
@@ -390,6 +396,7 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	                                   owner_grp,
 	                                   NULL,
 	                                   FALSE,
+	                                   FALSE,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 4253fe3c..752b6d7b 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -12,4 +12,5 @@ test(
   'keyfile/' + test_unit,
   test_script,
   args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index cdc9bfb0..baecac13 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -2615,11 +2614,16 @@ NMTST_DEFINE ();
 
 int main (int argc, char **argv)
 {
+	int errsv;
+
 	_nm_utils_set_testing (NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK);
+
 	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
 
-	if (g_mkdir_with_parents (TEST_SCRATCH_DIR, 0755) != 0)
-		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR, g_strerror (errno));
+	if (g_mkdir_with_parents (TEST_SCRATCH_DIR, 0755) != 0) {
+		errsv = errno;
+		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR, nm_strerror_native (errsv));
+	}
 
 	/* The tests */
 	g_test_add_func ("/keyfile/test_read_valid_wired_connection", test_read_valid_wired_connection);
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 4acb634e..7708224b 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-supplicant-config.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-core-internal.h"
@@ -404,11 +403,11 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
 
 		value = nm_setting_macsec_get_mka_cak (setting);
 		if (   !value
-		    || !_nm_utils_hexstr2bin_buf (value,
-		                                  FALSE,
-		                                  FALSE,
-		                                  NULL,
-		                                  buffer_cak)) {
+		    || !nm_utils_hexstr2bin_buf (value,
+		                                 FALSE,
+		                                 FALSE,
+		                                 NULL,
+		                                 buffer_cak)) {
 			g_set_error_literal (error,
 			                     NM_SUPPLICANT_ERROR,
 			                     NM_SUPPLICANT_ERROR_CONFIG,
@@ -425,11 +424,11 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
 
 		value = nm_setting_macsec_get_mka_ckn (setting);
 		if (   !value
-		    || !_nm_utils_hexstr2bin_buf (value,
-		                                  FALSE,
-		                                  FALSE,
-		                                  NULL,
-		                                  buffer_ckn)) {
+		    || !nm_utils_hexstr2bin_buf (value,
+		                                 FALSE,
+		                                 FALSE,
+		                                 NULL,
+		                                 buffer_ckn)) {
 			g_set_error_literal (error,
 			                     NM_SUPPLICANT_ERROR,
 			                     NM_SUPPLICANT_ERROR_CONFIG,
@@ -705,14 +704,14 @@ add_wep_key (NMSupplicantConfig *self,
 		if ((key_len == 10) || (key_len == 26)) {
 			guint8 buffer[26/2];
 
-			if (!_nm_utils_hexstr2bin_full (key,
-			                                FALSE,
-			                                FALSE,
-			                                NULL,
-			                                key_len / 2,
-			                                buffer,
-			                                sizeof (buffer),
-			                                NULL)) {
+			if (!nm_utils_hexstr2bin_full (key,
+			                               FALSE,
+			                               FALSE,
+			                               NULL,
+			                               key_len / 2,
+			                               buffer,
+			                               sizeof (buffer),
+			                               NULL)) {
 				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 				             "cannot add wep-key %s to suplicant config because key is not hex",
 				             name);
@@ -807,15 +806,30 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	if (psk) {
 		size_t psk_len = strlen (psk);
 
-		if (psk_len == 64) {
+
+		if (psk_len >= 8 && psk_len <= 63) {
+			/* Use TYPE_STRING here so that it gets pushed to the
+			 * supplicant as a string, and therefore gets quoted,
+			 * and therefore the supplicant will interpret it as a
+			 * passphrase and not a hex key.
+			 */
+			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
+				return FALSE;
+		} else if (nm_streq (key_mgmt, "sae")) {
+			/* If the SAE password doesn't comply with WPA-PSK limitation,
+			 * we need to call it "sae_password" instead of "psk".
+			 */
+			if (!nm_supplicant_config_add_option_with_type (self, "sae_password", psk, -1, TYPE_STRING, "<hidden>", error))
+				return FALSE;
+		} else if (psk_len == 64) {
 			guint8 buffer[32];
 
 			/* Hex PSK */
-			if (!_nm_utils_hexstr2bin_buf (psk,
-			                               FALSE,
-			                               FALSE,
-			                               NULL,
-			                               buffer)) {
+			if (!nm_utils_hexstr2bin_buf (psk,
+			                              FALSE,
+			                              FALSE,
+			                              NULL,
+			                              buffer)) {
 				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 				             "Cannot add psk to supplicant config due to invalid hex");
 				return FALSE;
@@ -827,14 +841,6 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 			                                      "<hidden>",
 			                                      error))
 				return FALSE;
-		} else if (psk_len >= 8 && psk_len <= 63) {
-			/* Use TYPE_STRING here so that it gets pushed to the
-			 * supplicant as a string, and therefore gets quoted,
-			 * and therefore the supplicant will interpret it as a
-			 * passphrase and not a hex key.
-			 */
-			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
-				return FALSE;
 		} else {
 			g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			             "Cannot add psk to supplicant config due to invalid PSK length %u (not between 8 and 63 characters)",
@@ -861,7 +867,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	/* Only WPA-specific things when using WPA */
 	if (   !strcmp (key_mgmt, "wpa-none")
 	    || !strcmp (key_mgmt, "wpa-psk")
-	    || !strcmp (key_mgmt, "wpa-eap")) {
+	    || !strcmp (key_mgmt, "wpa-eap")
+	    || !strcmp (key_mgmt, "sae")) {
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, proto, protos, "proto", ' ', TRUE, NULL, error))
 			return FALSE;
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, pairwise, pairwise, "pairwise", ' ', TRUE, NULL, error))
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index c30adc58..f46689eb 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -25,7 +25,6 @@
 #include "nm-supplicant-manager.h"
 
 #include <stdio.h>
-#include <string.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-supplicant-config.h"
@@ -729,9 +728,8 @@ iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 
-	/* This can fail if the supplicant doesn't support PMF */
 	if (error)
-		_LOGD ("failed to set Pmf=1: %s", error->message);
+		_LOGW ("failed to set Pmf=1: %s", error->message);
 
 	iface_check_ready (self);
 }
@@ -1452,7 +1450,7 @@ p2p_props_changed_cb (GDBusProxy *proxy,
 			/* We already have the proxy, nothing to do. */
 		} else if (path && g_strcmp0 (path, "/") != 0) {
 			if (priv->group_proxy != NULL) {
-				_LOGW ("P2P: Unexpected udpate of the group object path");
+				_LOGW ("P2P: Unexpected update of the group object path");
 				priv->group_proxy_acquired = FALSE;
 				_notify (self, PROP_P2P_GROUP_JOINED);
 				_notify (self, PROP_P2P_GROUP_PATH);
@@ -1548,7 +1546,7 @@ p2p_group_started (GDBusProxy *proxy,
 		}
 	}
 
-	/* Signal existance of the (new) interface. */
+	/* Signal existence of the (new) interface. */
 	g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
 	g_object_unref (iface);
 }
@@ -1639,21 +1637,9 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   NULL,
 	                   NULL);
 
-	/* Initialize global PMF setting to 'optional' */
-	priv->ready_count++;
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "Pmf",
-	                                  g_variant_new_string ("1")),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) iface_set_pmf_cb,
-	                   self);
-
-	/* Check whether NetworkReply and AP mode are supported */
+	/* Check whether NetworkReply and AP mode are supported.
+	 * ready_count was initialized to 1 in interface_add_done().
+	 */
 	g_dbus_proxy_call (priv->iface_proxy,
 	                   "NetworkReply",
 	                   g_variant_new ("(oss)",
@@ -1666,6 +1652,22 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   (GAsyncReadyCallback) iface_check_netreply_cb,
 	                   self);
 
+	if (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) {
+		/* Initialize global PMF setting to 'optional' */
+		priv->ready_count++;
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   DBUS_INTERFACE_PROPERTIES ".Set",
+		                   g_variant_new ("(ssv)",
+		                                  WPAS_DBUS_IFACE_INTERFACE,
+		                                  "Pmf",
+		                                  g_variant_new_string ("1")),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) iface_set_pmf_cb,
+		                   self);
+	}
+
 	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
 		/* If the global supplicant capabilities property is not present, we can
 		 * fall back to checking whether the ProbeRequest method is supported.  If
@@ -2482,7 +2484,6 @@ nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	/* Find parameters */
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&builder, "{sv}", "Timeout", g_variant_new_int32 (timeout));
 
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index 64a057f3..104aeee7 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -23,8 +23,6 @@
 
 #include "nm-supplicant-manager.h"
 
-#include <string.h>
-
 #include "nm-supplicant-interface.h"
 #include "nm-supplicant-types.h"
 #include "nm-core-internal.h"
@@ -71,7 +69,7 @@ NM_CACHED_QUARK_FCN ("nm-supplicant-error-quark", nm_supplicant_error_quark)
 
 /*****************************************************************************/
 
-static inline gboolean
+static gboolean
 die_count_exceeded (guint32 count)
 {
 	return count > 2;
@@ -125,6 +123,72 @@ _sup_iface_last_ref (gpointer data,
 	g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self);
 }
 
+static void
+on_supplicant_wfd_ies_set (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (!result)
+		_LOGW ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
+}
+
+/**
+ * nm_supplicant_manager_set_wfd_ies:
+ * @self: the #NMSupplicantManager
+ * @wfd_ies: a #GBytes with the WFD IEs or %NULL
+ *
+ * This function sets the global WFD IEs on wpa_supplicant. Note that
+ * it would make more sense if this was per-device, but wpa_supplicant
+ * simply does not work that way.
+ * */
+void
+nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
+                                   GBytes *wfd_ies)
+{
+	NMSupplicantManagerPrivate *priv;
+	GVariantBuilder params;
+	GVariant *val;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	_LOGD ("setting WFD IEs for P2P operation");
+
+	if (wfd_ies)
+		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                 g_bytes_get_data (wfd_ies, NULL),
+		                                 g_bytes_get_size (wfd_ies),
+		                                 sizeof (guint8));
+	else
+		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                 NULL, 0, sizeof (guint8));
+
+	g_variant_builder_init (&params, G_VARIANT_TYPE ("(ssv)"));
+
+	g_variant_builder_add (&params, "s", g_dbus_proxy_get_interface_name (priv->proxy));
+	g_variant_builder_add (&params, "s", "WFDIEs");
+	g_variant_builder_add_value (&params, g_variant_new_variant (val));
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        g_dbus_proxy_get_name (priv->proxy),
+	                        g_dbus_proxy_get_object_path (priv->proxy),
+	                        "org.freedesktop.DBus.Properties",
+	                        "Set",
+	                        g_variant_builder_end (&params),
+	                        G_VARIANT_TYPE_UNIT,
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        1000,
+	                        NULL,
+	                        on_supplicant_wfd_ies_set,
+	                        NULL);
+}
+
 /**
  * nm_supplicant_manager_create_interface:
  * @self: the #NMSupplicantManager
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
index 7225a36b..058745fb 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -38,6 +38,9 @@ GType nm_supplicant_manager_get_type (void);
 
 NMSupplicantManager *nm_supplicant_manager_get (void);
 
+void nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
+                                        GBytes *wfd_ies);
+
 NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr,
                                                                const char *ifname,
                                                                NMSupplicantDriver driver);
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 1e25675d..f10bbb04 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -24,8 +24,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <errno.h>
 
 struct Opt {
 	const char *     key;
@@ -72,7 +70,7 @@ const char * proto_allowed[] =    { "WPA", "RSN", NULL };
 const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256",
                                     "WPA-EAP", "WPA-EAP-SHA256",
                                     "FILS-SHA256", "FILS-SHA384",
-                                    "IEEE8021X", "WPA-NONE",
+                                    "IEEE8021X", "WPA-NONE", "SAE",
                                     "NONE", NULL };
 const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
 const char * eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
@@ -159,23 +157,13 @@ validate_type_int (const struct Opt * opt,
                    const char * value,
                    const guint32 len)
 {
-	long int intval;
+	gint64 v;
 
 	g_return_val_if_fail (opt != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
 
-	errno = 0;
-	intval = strtol (value, NULL, 10);
-	if (errno != 0)
-		return FALSE;
-
-	/* strtol returns a long, but we are dealing with ints */
-	if (intval > INT_MAX || intval < INT_MIN)
-		return FALSE;
-	if (intval > opt->int_high || intval < opt->int_low)
-		return FALSE;
-
-	return TRUE;
+	v = _nm_utils_ascii_str_to_int64 (value, 10, opt->int_low, opt->int_high, G_MININT64);
+	return v != G_MININT64 || errno == 0;
 }
 
 static gboolean
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
index 7cc9d6af..fbccb313 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -10,4 +10,5 @@ test(
   'supplicant/' + test_unit,
   test_script,
   args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index d7ec1fe2..2c7a71a3 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -389,6 +388,76 @@ test_wifi_wpa_psk (const char *detail,
 }
 
 static void
+test_wifi_sae_psk (const char *psk)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingWirelessSecurity *s_wsec;
+	gboolean success;
+	GError *error = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bssid_str = "11:22:33:44:55:66";
+	int short_psk = strlen (psk) < 8;
+
+	connection = new_basic_connection ("Test Wifi SAE", ssid, bssid_str);
+
+	/* Wifi Security setting */
+	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+	g_object_set (s_wsec,
+	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+	              NM_SETTING_WIRELESS_SECURITY_PSK, psk,
+	              NULL);
+	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
+	nm_setting_wireless_security_add_group (s_wsec, "tkip");
+	nm_setting_wireless_security_add_group (s_wsec, "ccmp");
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'SAE'");
+	if (short_psk)
+		NMTST_EXPECT_NM_INFO ("Config: added 'sae_password' value *");
+	else
+		NMTST_EXPECT_NM_INFO ("Config: added 'psk' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '0'");
+	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+
+	g_test_assert_expected_messages ();
+	g_assert (config_dict);
+
+	validate_opt ("wifi-sae", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-sae", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-sae", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-sae", config_dict, "key_mgmt", TYPE_KEYWORD, "SAE");
+	validate_opt ("wifi-sae", config_dict, "proto", TYPE_KEYWORD, "RSN");
+	validate_opt ("wifi-sae", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-sae", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	if (short_psk)
+		validate_opt ("wifi-sae", config_dict, "sae_password", TYPE_KEYWORD, psk);
+	else
+		validate_opt ("wifi-sae", config_dict, "psk", TYPE_KEYWORD, psk);
+}
+
+static void
+test_wifi_sae (void)
+{
+	test_wifi_sae_psk ("Moo");
+	test_wifi_sae_psk ("Hello World!");
+}
+
+static void
 test_wifi_wpa_psk_types (void)
 {
 	const char *key1 = "d4721e911461d3cdef9793858e977fcda091779243abb7316c2f11605a160893";
@@ -580,6 +649,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/supplicant-config/wifi-eap/locked-bssid", test_wifi_eap_locked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/fils-disabled", test_wifi_eap_fils_disabled);
+	g_test_add_func ("/supplicant-config/wifi-sae", test_wifi_sae);
 
 	return g_test_run ();
 }
diff --git a/src/systemd/sd-adapt-core/device-util.h b/src/systemd/sd-adapt-core/device-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt-core/device-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index 91f5e1b0..7fc8248a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -13,6 +13,7 @@
 #include "network-internal.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
+#include "stdio-util.h"
 #include "virt.h"
 
 #define SYSTEMD_PEN    43793
@@ -176,10 +177,10 @@ int dhcp_identifier_set_iaid(
 
         if (detect_container() <= 0) {
                 /* not in a container, udev will be around */
-                char ifindex_str[2 + DECIMAL_STR_MAX(int)];
+                char ifindex_str[1 + DECIMAL_STR_MAX(int)];
                 int r;
 
-                sprintf(ifindex_str, "n%d", ifindex);
+                xsprintf(ifindex_str, "n%d", ifindex);
                 if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) {
                         r = sd_device_get_is_initialized(device);
                         if (r < 0)
@@ -202,7 +203,7 @@ int dhcp_identifier_set_iaid(
 
         if (legacy_unstable_byteorder)
                 /* for historical reasons (a bug), the bits were swapped and thus
-                 * the result was endianness dependant. Preserve that behavior. */
+                 * the result was endianness dependent. Preserve that behavior. */
                 id32 = __bswap_32(id32);
         else
                 /* the fixed behavior returns a stable byte order. Since LE is expected
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 9d245a90..122042ab 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -41,7 +41,6 @@ struct sd_dhcp_lease {
         /* each 0 if unset */
         be32_t address;
         be32_t server_address;
-        be32_t router;
         be32_t next_server;
 
         bool have_subnet_mask;
@@ -50,6 +49,9 @@ struct sd_dhcp_lease {
         bool have_broadcast;
         be32_t broadcast;
 
+        struct in_addr *router;
+        size_t router_size;
+
         struct in_addr *dns;
         size_t dns_size;
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index ba596908..810a2633 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if.h>
 #include <net/if_arp.h>
@@ -52,12 +51,16 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)),  /* A <- DHCP header type */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0),                   /* header type == arp_type ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
-                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)),   /* A <- MAC address length */
-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0),                  /* address length == dhcp_hlen ? */
-                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)),    /* A <- client identifier */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0),                        /* client identifier == xid ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)),   /* A <- MAC address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0),                  /* address length == dhcp_hlen ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
+
+                /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally
+                 * compare chaddr for ETH_ALEN bytes. */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 12),                                  /* A (the MAC address length) == ETH_ALEN ? */
                 BPF_STMT(BPF_LD + BPF_IMM, unaligned_read_be32(&eth_mac->ether_addr_octet[0])),        /* A <- 4 bytes of client's MAC */
                 BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                       /* X <- A */
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)),                 /* A <- 4 bytes of MAC from dhcp.chaddr */
@@ -70,6 +73,7 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                /* A xor X */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0),                                          /* A == 0 ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                                          /* ignore */
+
                 BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)),  /* A <- DHCP magic cookie */
                 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0),          /* cookie == DHCP magic cookie ? */
                 BPF_STMT(BPF_RET + BPF_K, 0),                                          /* ignore */
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/systemd/src/libsystemd-network/dhcp-option.c
index b065ae49..c5fbe749 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp-option.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 9e565e28..91e8a54e 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <string.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-network.c b/src/systemd/src/libsystemd-network/dhcp6-network.c
index 73c195a7..d786756e 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-network.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <netinet/in.h>
 #include <netinet/ip6.h>
 #include <stdio.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index 5a83aaaa..3d5abe64 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <netinet/in.h>
 #include <string.h>
 
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index d33c4feb..f85db47b 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -12,6 +12,7 @@
 #include "alloc-util.h"
 #include "condition.h"
 #include "conf-parser.h"
+#include "device-util.h"
 #include "dhcp-lease-internal.h"
 #include "ether-addr-util.h"
 #include "hexdecoct.h"
@@ -43,31 +44,35 @@ const char *net_get_name(sd_device *device) {
 
 int net_get_unique_predictable_data(sd_device *device, uint64_t *result) {
         size_t l, sz = 0;
-        const char *name = NULL;
+        const char *name;
         int r;
         uint8_t *v;
 
         assert(device);
 
+        /* net_get_name() will return one of the device names based on stable information about the
+         * device. If this is not available, we fall back to using the device name. */
         name = net_get_name(device);
         if (!name)
-                return -ENOENT;
+                (void) sd_device_get_sysname(device, &name);
+        if (!name)
+                return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENODATA),
+                                              "No stable identifying information found");
 
+        log_device_debug(device, "Using \"%s\" as stable identifying information", name);
         l = strlen(name);
         sz = sizeof(sd_id128_t) + l;
-        v = alloca(sz);
+        v = newa(uint8_t, sz);
 
-        /* fetch some persistent data unique to this machine */
+        /* Fetch some persistent data unique to this machine */
         r = sd_id128_get_machine((sd_id128_t*) v);
         if (r < 0)
                  return r;
         memcpy(v + sizeof(sd_id128_t), name, l);
 
-        /* Let's hash the machine ID plus the device name. We
-        * use a fixed, but originally randomly created hash
-        * key here. */
+        /* Let's hash the machine ID plus the device name. We use
+         * a fixed, but originally randomly created hash key here. */
         *result = htole64(siphash24(v, sz, HASH_KEY.bytes));
-
         return 0;
 }
 
@@ -105,7 +110,6 @@ bool net_match_config(Set *match_mac,
                       Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
-                      const char *dev_parent_driver,
                       const char *dev_driver,
                       const char *dev_type,
                       const char *dev_name) {
@@ -413,16 +417,33 @@ int config_parse_bridge_port_priority(
 }
 #endif /* NM_IGNORED */
 
-void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) {
-        unsigned i;
+size_t serialize_in_addrs(FILE *f,
+                          const struct in_addr *addresses,
+                          size_t size,
+                          bool with_leading_space,
+                          bool (*predicate)(const struct in_addr *addr)) {
+        size_t count;
+        size_t i;
 
         assert(f);
         assert(addresses);
-        assert(size);
 
-        for (i = 0; i < size; i++)
-                fprintf(f, "%s%s", inet_ntoa(addresses[i]),
-                        (i < (size - 1)) ? " ": "");
+        count = 0;
+
+        for (i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
+
+                if (predicate && !predicate(&addresses[i]))
+                        continue;
+                if (with_leading_space)
+                        fputc(' ', f);
+                else
+                        with_leading_space = true;
+                fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
+                count++;
+        }
+
+        return count;
 }
 
 int deserialize_in_addrs(struct in_addr **ret, const char *string) {
@@ -456,7 +477,7 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
                 size++;
         }
 
-        *ret = TAKE_PTR(addresses);
+        *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
 
         return size;
 }
@@ -525,6 +546,7 @@ void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, siz
         fprintf(f, "%s=", key);
 
         for (i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
                 struct in_addr dest, gw;
                 uint8_t length;
 
@@ -532,8 +554,8 @@ void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, siz
                 assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0);
                 assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0);
 
-                fprintf(f, "%s/%" PRIu8, inet_ntoa(dest), length);
-                fprintf(f, ",%s%s", inet_ntoa(gw), (i < (size - 1)) ? " ": "");
+                fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof(sbuf)), length);
+                fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof(sbuf)), (i < (size - 1)) ? " ": "");
         }
 
         fputs("\n", f);
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index dfe4c424..9119d9a4 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -8,7 +8,9 @@
 
 #include "condition.h"
 #include "conf-parser.h"
+#include "def.h"
 #include "set.h"
+#include "strv.h"
 
 #define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
 #define LINK_BRIDGE_PORT_PRIORITY_MAX 63
@@ -25,7 +27,6 @@ bool net_match_config(Set *match_mac,
                       Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
-                      const char *dev_parent_driver,
                       const char *dev_driver,
                       const char *dev_type,
                       const char *dev_name);
@@ -42,7 +43,11 @@ int net_get_unique_predictable_data(sd_device *device, uint64_t *result);
 const char *net_get_name(sd_device *device);
 #endif /* NM_IGNORED */
 
-void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size);
+size_t serialize_in_addrs(FILE *f,
+                          const struct in_addr *addresses,
+                          size_t size,
+                          bool with_leading_space,
+                          bool (*predicate)(const struct in_addr *addr));
 int deserialize_in_addrs(struct in_addr **addresses, const char *string);
 void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
                          size_t size);
@@ -56,3 +61,5 @@ int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t
 
 /* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
 int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size);
+
+#define NETWORK_DIRS ((const char* const*) CONF_PATHS_STRV("systemd/network"))
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 2f9244d8..27f79638 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <stdio.h>
@@ -1681,8 +1680,7 @@ static int client_receive_message_udp(
 
         sd_dhcp_client *client = userdata;
         _cleanup_free_ DHCPMessage *message = NULL;
-        const struct ether_addr zero_mac = {};
-        const struct ether_addr *expected_chaddr = NULL;
+        const uint8_t *expected_chaddr = NULL;
         uint8_t expected_hlen = 0;
         ssize_t len, buflen;
 
@@ -1690,6 +1688,12 @@ static int client_receive_message_udp(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN) {
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        }
         if (buflen < 0)
                 return buflen;
 
@@ -1699,7 +1703,8 @@ static int client_receive_message_udp(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp_client_errno(client, errno,
@@ -1727,11 +1732,7 @@ static int client_receive_message_udp(
 
         if (client->arp_type == ARPHRD_ETHER) {
                 expected_hlen = ETH_ALEN;
-                expected_chaddr = (const struct ether_addr *) &client->mac_addr;
-        } else {
-               /* Non-Ethernet links expect zero chaddr */
-               expected_hlen = 0;
-               expected_chaddr = &zero_mac;
+                expected_chaddr = &client->mac_addr[0];
         }
 
         if (message->hlen != expected_hlen) {
@@ -1739,7 +1740,7 @@ static int client_receive_message_udp(
                 return 0;
         }
 
-        if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) {
+        if (expected_hlen > 0 && memcmp(&message->chaddr[0], expected_chaddr, expected_hlen)) {
                 log_dhcp_client(client, "Received chaddr does not match expected: ignoring");
                 return 0;
         }
@@ -1781,6 +1782,8 @@ static int client_receive_message_raw(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN)
+                return 0;
         if (buflen < 0)
                 return buflen;
 
@@ -1792,7 +1795,7 @@ static int client_receive_message_raw(
 
         len = recvmsg(fd, &msg, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp_client_errno(client, errno,
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index fc5077c2..39d2a6d0 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -6,7 +6,6 @@
 #include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
@@ -153,15 +152,15 @@ int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) {
         return 0;
 }
 
-int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr) {
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) {
         assert_return(lease, -EINVAL);
         assert_return(addr, -EINVAL);
 
-        if (lease->router == 0)
+        if (lease->router_size <= 0)
                 return -ENODATA;
 
-        addr->s_addr = lease->router;
-        return 0;
+        *addr = lease->router;
+        return (int) lease->router_size;
 }
 
 int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) {
@@ -263,6 +262,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
         }
 
         free(lease->root_path);
+        free(lease->router);
         free(lease->timezone);
         free(lease->hostname);
         free(lease->domainname);
@@ -372,23 +372,6 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
         return 0;
 }
 
-static void filter_bogus_addresses(struct in_addr *addresses, size_t *n) {
-        size_t i, j;
-
-        /* Silently filter DNS/NTP servers supplied to us that do not make outside of the local scope. */
-
-        for (i = 0, j = 0; i < *n; i ++) {
-
-                if (in4_addr_is_null(addresses+i) ||
-                    in4_addr_is_localhost(addresses+i))
-                        continue;
-
-                addresses[j++] = addresses[i];
-        }
-
-        *n = j;
-}
-
 static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
         assert(option);
         assert(ret);
@@ -410,8 +393,6 @@ static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_add
                 if (!addresses)
                         return -ENOMEM;
 
-                filter_bogus_addresses(addresses, &n_addresses);
-
                 free(*ret);
                 *ret = addresses;
                 *n_ret = n_addresses;
@@ -556,11 +537,9 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                 break;
 
         case SD_DHCP_OPTION_ROUTER:
-                if (len >= 4) {
-                        r = lease_parse_be32(option, 4, &lease->router);
-                        if (r < 0)
-                                log_debug_errno(r, "Failed to parse router address, ignoring: %m");
-                }
+                r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse router addresses, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
@@ -822,7 +801,6 @@ int dhcp_lease_new(sd_dhcp_lease **ret) {
         if (!lease)
                 return -ENOMEM;
 
-        lease->router = INADDR_ANY;
         lease->n_ref = 1;
 
         *ret = lease;
@@ -837,6 +815,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         const struct in_addr *addresses;
         const void *client_id, *data;
         size_t client_id_len, data_len;
+        char sbuf[INET_ADDRSTRLEN];
         const char *string;
         uint16_t mtu;
         _cleanup_free_ sd_dhcp_route **routes = NULL;
@@ -859,27 +838,30 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
 
         r = sd_dhcp_lease_get_address(lease, &address);
         if (r >= 0)
-                fprintf(f, "ADDRESS=%s\n", inet_ntoa(address));
+                fprintf(f, "ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_netmask(lease, &address);
         if (r >= 0)
-                fprintf(f, "NETMASK=%s\n", inet_ntoa(address));
+                fprintf(f, "NETMASK=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
-        r = sd_dhcp_lease_get_router(lease, &address);
-        if (r >= 0)
-                fprintf(f, "ROUTER=%s\n", inet_ntoa(address));
+        r = sd_dhcp_lease_get_router(lease, &addresses);
+        if (r > 0) {
+                fputs("ROUTER=", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
+        }
 
         r = sd_dhcp_lease_get_server_identifier(lease, &address);
         if (r >= 0)
-                fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntoa(address));
+                fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_next_server(lease, &address);
         if (r >= 0)
-                fprintf(f, "NEXT_SERVER=%s\n", inet_ntoa(address));
+                fprintf(f, "NEXT_SERVER=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_broadcast(lease, &address);
         if (r >= 0)
-                fprintf(f, "BROADCAST=%s\n", inet_ntoa(address));
+                fprintf(f, "BROADCAST=%s\n", inet_ntop(AF_INET, &address, sbuf, sizeof(sbuf)));
 
         r = sd_dhcp_lease_get_mtu(lease, &mtu);
         if (r >= 0)
@@ -900,15 +882,15 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         r = sd_dhcp_lease_get_dns(lease, &addresses);
         if (r > 0) {
                 fputs("DNS=", f);
-                serialize_in_addrs(f, addresses, r);
-                fputs("\n", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
         }
 
         r = sd_dhcp_lease_get_ntp(lease, &addresses);
         if (r > 0) {
                 fputs("NTP=", f);
-                serialize_in_addrs(f, addresses, r);
-                fputs("\n", f);
+                serialize_in_addrs(f, addresses, r, false, NULL);
+                fputc('\n', f);
         }
 
         r = sd_dhcp_lease_get_domainname(lease, &string);
@@ -919,7 +901,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         if (r > 0) {
                 fputs("DOMAIN_SEARCH_LIST=", f);
                 fputstrv(f, search_domains, NULL, NULL);
-                fputs("\n", f);
+                fputc('\n', f);
         }
 
         r = sd_dhcp_lease_get_hostname(lease, &string);
@@ -1082,9 +1064,11 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         }
 
         if (router) {
-                r = inet_pton(AF_INET, router, &lease->router);
-                if (r <= 0)
-                        log_debug("Failed to parse router %s, ignoring.", router);
+                r = deserialize_in_addrs(&lease->router, router);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router);
+                else
+                        lease->router_size = r;
         }
 
         if (netmask) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index 6b55083e..b72cd82d 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <linux/if_infiniband.h>
@@ -1118,6 +1117,12 @@ static int client_receive_message(
         assert(client->event);
 
         buflen = next_datagram_size_fd(fd);
+        if (buflen == -ENETDOWN) {
+                /* the link is down. Don't return an error or the I/O event
+                   source will be disconnected and we won't be able to receive
+                   packets again when the link comes back. */
+                return 0;
+        }
         if (buflen < 0)
                 return buflen;
 
@@ -1127,7 +1132,8 @@ static int client_receive_message(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR))
+                /* see comment above for why we shouldn't error out on ENETDOWN. */
+                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
                         return 0;
 
                 return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m");
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 48188bf3..7263c96f 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -5,7 +5,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 
 #include "alloc-util.h"
 #include "dhcp6-lease-internal.h"
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index b43da6b4..6de4adb5 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -6,7 +6,6 @@
 #include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index f1b94829..8b1e9665 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -6,7 +6,6 @@
 #include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
diff --git a/src/systemd/src/libsystemd/sd-event/event-util.c b/src/systemd/src/libsystemd/sd-event/event-util.c
index e8384cfd..2afacfe6 100644
--- a/src/systemd/src/libsystemd/sd-event/event-util.c
+++ b/src/systemd/src/libsystemd/sd-event/event-util.c
@@ -2,7 +2,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 
 #include "event-source.h"
 #include "event-util.h"
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index e49cc9d8..6f77421b 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -472,6 +472,17 @@ static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) {
         }
 }
 
+static void event_free_signal_data(sd_event *e, struct signal_data *d) {
+        assert(e);
+
+        if (!d)
+                return;
+
+        hashmap_remove(e->signal_data, &d->priority);
+        safe_close(d->fd);
+        free(d);
+}
+
 static int event_make_signal_data(
                 sd_event *e,
                 int sig,
@@ -561,11 +572,8 @@ static int event_make_signal_data(
         return 0;
 
 fail:
-        if (added) {
-                d->fd = safe_close(d->fd);
-                hashmap_remove(e->signal_data, &d->priority);
-                free(d);
-        }
+        if (added)
+                event_free_signal_data(e, d);
 
         return r;
 }
@@ -584,11 +592,8 @@ static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig
         assert_se(sigdelset(&d->sigset, sig) >= 0);
 
         if (sigisemptyset(&d->sigset)) {
-
                 /* If all the mask is all-zero we can get rid of the structure */
-                hashmap_remove(e->signal_data, &d->priority);
-                safe_close(d->fd);
-                free(d);
+                event_free_signal_data(e, d);
                 return;
         }
 
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index f8f0883c..6245e9df 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -2,7 +2,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index a476017b..13a28291 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -2,7 +2,6 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
index 88b3eb11..17db7c52 100644
--- a/src/systemd/src/shared/dns-domain.h
+++ b/src/systemd/src/shared/dns-domain.h
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-#include <errno.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 4875f105..d299c791 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -39,7 +39,7 @@ int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1);
 int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2);
 int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr);
-int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
diff --git a/src/tests/config/meson.build b/src/tests/config/meson.build
index dc0a022c..f65f90bb 100644
--- a/src/tests/config/meson.build
+++ b/src/tests/config/meson.build
@@ -17,4 +17,5 @@ test(
   'config/' + test_unit,
   test_script,
   args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/tests/config/nm-test-device.c b/src/tests/config/nm-test-device.c
index 49631583..3a8ef266 100644
--- a/src/tests/config/nm-test-device.c
+++ b/src/tests/config/nm-test-device.c
@@ -22,8 +22,6 @@
 
 #include "nm-test-device.h"
 
-#include <string.h>
-
 #include "devices/nm-device-private.h"
 #include "nm-utils.h"
 
@@ -77,6 +75,7 @@ nm_test_device_new (const char *hwaddr)
 	return g_object_new (NM_TYPE_TEST_DEVICE,
 	                     NM_DEVICE_IFACE, "dummy",
 	                     NM_DEVICE_PERM_HW_ADDRESS, hwaddr,
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_ETHERNET,
 	                     NULL);
 }
 
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 9c51e8d4..153128f2 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -21,6 +21,7 @@ foreach test_unit: test_units
     'src/' + test_unit,
     test_script,
     args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
 
diff --git a/src/tests/test-dcb.c b/src/tests/test-dcb.c
index 2ab0f890..6291f254 100644
--- a/src/tests/test-dcb.c
+++ b/src/tests/test-dcb.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-dcb.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-general-with-expect.c
index ba8e3ce4..8339fa5d 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-general-with-expect.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <time.h>
 #include <netinet/ether.h>
 #include <sys/types.h>
@@ -42,7 +40,8 @@ test_nm_utils_monotonic_timestamp_as_boottime (void)
 	clockid_t clockid;
 	guint i;
 
-	if (clock_gettime (CLOCK_BOOTTIME, &tp) != 0 && errno == EINVAL)
+	if (   clock_gettime (CLOCK_BOOTTIME, &tp) != 0
+	    && errno == EINVAL)
 		clockid = CLOCK_MONOTONIC;
 	else
 		clockid = CLOCK_BOOTTIME;
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index f065d127..0dee566e 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <net/if.h>
 #include <byteswap.h>
 
@@ -1666,7 +1664,7 @@ test_duplicate_decl_specifier (void)
 	/* have some static variables, so that the result is certainly not optimized out. */
 	static const int v_const[1] = { 1 };
 	static int v_result[1] = { };
-	const const int v2 = 3;
+	const int v2 = 3;
 
 	/* Test that we don't get a compiler warning about duplicate const specifier.
 	 * C99 allows that and it can easily happen in macros. */
@@ -2014,11 +2012,11 @@ test_machine_id_read (void)
 	nmtst_logging_reenable (logstate);
 
 	g_assert (machine_id);
-	g_assert (_nm_utils_bin2hexstr_full (machine_id,
-	                                     sizeof (NMUuid),
-	                                     '\0',
-	                                     FALSE,
-	                                     machine_id_str) == machine_id_str);
+	g_assert (nm_utils_bin2hexstr_full (machine_id,
+	                                    sizeof (NMUuid),
+	                                    '\0',
+	                                    FALSE,
+	                                    machine_id_str) == machine_id_str);
 	g_assert (strlen (machine_id_str) == 32);
 	g_assert_cmpstr (machine_id_str, ==, nm_utils_machine_id_str ());
 
@@ -2055,14 +2053,14 @@ test_nm_utils_dhcp_client_id_systemd_node_specific (gconstpointer test_data)
 		guint64 duid_id;
 	} d_array[] = {
 		[0] = {
-			.machine_id = { 0xcb, 0xc2, 0x2e, 0x47, 0x41, 0x8e, 0x40, 0x2a, 0xa7, 0xb3, 0x0d, 0xea, 0x92, 0x83, 0x94, 0xef },
+			.machine_id.uuid = { 0xcb, 0xc2, 0x2e, 0x47, 0x41, 0x8e, 0x40, 0x2a, 0xa7, 0xb3, 0x0d, 0xea, 0x92, 0x83, 0x94, 0xef },
 			.ifname = "lo",
 			.ifname_hash_1 = 0x7297085c2b12c911llu,
 			.iaid_ifname = htobe32 (0x5985c14du),
 			.duid_id = htobe64 (0x3d769bb2c14d29e1u),
 		},
 		[1] = {
-			.machine_id = { 0x11, 0x4e, 0xb4, 0xda, 0xd3, 0x22, 0x4a, 0xff, 0x9f, 0xc3, 0x30, 0x83, 0x38, 0xa0, 0xeb, 0xb7 },
+			.machine_id.uuid = { 0x11, 0x4e, 0xb4, 0xda, 0xd3, 0x22, 0x4a, 0xff, 0x9f, 0xc3, 0x30, 0x83, 0x38, 0xa0, 0xeb, 0xb7 },
 			.ifname = "eth0",
 			.ifname_hash_1 = 0x9e1cb083b54cd7b6llu,
 			.iaid_ifname = htobe32 (0x2b506735u),
diff --git a/src/tests/test-ip4-config.c b/src/tests/test-ip4-config.c
index 9fea6af5..f98c84c3 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/tests/test-ip4-config.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-ip4-config.h"
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index 51807dea..8f5f41d7 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <linux/if_addr.h>
 
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index 05e22776..91edcab2 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -49,8 +49,9 @@ nm_utils_get_monotonic_timestamp_s (void)
 NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL];
 
 gboolean
-_nm_log_enabled (NMLogLevel level,
-                 NMLogDomain domain)
+_nm_log_enabled_impl (gboolean mt_require_locking,
+                      NMLogLevel level,
+                      NMLogDomain domain)
 {
 	return FALSE;
 }
@@ -59,6 +60,7 @@ void
 _nm_log_impl (const char *file,
               guint line,
               const char *func,
+              gboolean mt_require_locking,
               NMLogLevel level,
               NMLogDomain domain,
               int error,
@@ -78,6 +80,12 @@ nm_logging_setup (const char  *level,
 	return TRUE;
 }
 
+const char *
+nm_strerror_native (int errsv)
+{
+	return g_strerror (errsv);
+}
+
 /*****************************************************************************/
 
 static void
diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c
index 9572ec7a..c326f790 100644
--- a/src/tests/test-utils.c
+++ b/src/tests/test-utils.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <errno.h>
 #include <arpa/inet.h>
 
 #include "nm-test-utils-core.h"
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index 6626f64a..5acf491a 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -23,11 +23,9 @@
 
 #include "nm-vpn-connection.h"
 
-#include <string.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
@@ -729,15 +727,23 @@ add_ip4_vpn_gateway_route (NMIP4Config *config,
 		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (route_resolved);
 
 		if (r->ifindex == ifindex) {
+			const NMPObject *obj;
+
 			/* `ip route get` always resolves the route, even if the destination is unreachable.
 			 * In which case, it pretends the destination is directly reachable.
 			 *
-			 * So, only accept direct routes, if @vpn_gw is a private network. */
-			if (   nm_platform_route_table_is_main (r->table_coerced)
-			    && (   r->gateway
-			        || nm_utils_ip_is_site_local (AF_INET, &vpn_gw))) {
-				parent_gw = r->gateway;
-				has_parent_gw = TRUE;
+			 * So, only accept direct routes if @vpn_gw is a private network
+			 * or if the parent device also has a direct default route */
+			if (nm_platform_route_table_is_main (r->table_coerced)) {
+				if (r->gateway) {
+					parent_gw = r->gateway;
+					has_parent_gw = TRUE;
+				} else if (nm_utils_ip_is_site_local (AF_INET, &vpn_gw)) {
+					has_parent_gw = TRUE;
+				} else if (   (obj = nm_device_get_best_default_route (parent_device, AF_INET))
+				           && !NMP_OBJECT_CAST_IP4_ROUTE (obj)->gateway) {
+					has_parent_gw = TRUE;
+				}
 			}
 		}
 	}
@@ -803,15 +809,23 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (route_resolved);
 
 		if (r->ifindex == ifindex) {
+			const NMPObject *obj;
+
 			/* `ip route get` always resolves the route, even if the destination is unreachable.
 			 * In which case, it pretends the destination is directly reachable.
 			 *
-			 * So, only accept direct routes, if @vpn_gw is a private network. */
-			if (   nm_platform_route_table_is_main (r->table_coerced)
-			    && (   !IN6_IS_ADDR_UNSPECIFIED (&r->gateway)
-			        || nm_utils_ip_is_site_local (AF_INET6, &vpn_gw))) {
-				parent_gw = &r->gateway;
-				has_parent_gw = TRUE;
+			 * So, only accept direct routes if @vpn_gw is a private network
+			 * or if the parent device also has a direct default route */
+			if (nm_platform_route_table_is_main (r->table_coerced)) {
+				if (!IN6_IS_ADDR_UNSPECIFIED (&r->gateway)) {
+					parent_gw = &r->gateway;
+					has_parent_gw = TRUE;
+				} else if (nm_utils_ip_is_site_local (AF_INET6, &vpn_gw)) {
+					has_parent_gw = TRUE;
+				} else if (   (obj = nm_device_get_best_default_route (parent_device, AF_INET6))
+				           && IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (obj)->gateway)) {
+					has_parent_gw = TRUE;
+				}
 			}
 		}
 	}
@@ -1442,11 +1456,7 @@ get_route_table (NMVpnConnection *self,
 
 	connection = _get_applied_connection (self);
 	if (connection) {
-		if (addr_family == AF_INET)
-			s_ip = nm_connection_get_setting_ip4_config (connection);
-		else
-			s_ip = nm_connection_get_setting_ip6_config (connection);
-
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip)
 			route_table = nm_setting_ip_config_get_route_table  (s_ip);
 	}
diff --git a/src/vpn/nm-vpn-connection.h b/src/vpn/nm-vpn-connection.h
index 5482fb0d..e70590b2 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/vpn/nm-vpn-connection.h
@@ -28,8 +28,6 @@
 #include "nm-active-connection.h"
 #include "nm-vpn-plugin-info.h"
 
-#define NM_VPN_ROUTE_METRIC_DEFAULT     50
-
 #define NM_TYPE_VPN_CONNECTION            (nm_vpn_connection_get_type ())
 #define NM_VPN_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VPN_CONNECTION, NMVpnConnection))
 #define NM_VPN_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_VPN_CONNECTION, NMVpnConnectionClass))
diff --git a/src/vpn/nm-vpn-manager.c b/src/vpn/nm-vpn-manager.c
index d0639168..0b27b9de 100644
--- a/src/vpn/nm-vpn-manager.c
+++ b/src/vpn/nm-vpn-manager.c
@@ -23,8 +23,6 @@
 
 #include "nm-vpn-manager.h"
 
-#include <string.h>
-
 #include "nm-vpn-plugin-info.h"
 #include "nm-vpn-connection.h"
 #include "nm-setting-vpn.h"