summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-04-21 21:09:51 +0200
committerMichael Biebl <biebl@debian.org>2019-04-21 21:09:51 +0200
commit85563b7fc7ec2cd21e38debb9b28db342e2e8e7c (patch)
treecce7b0b02d28fae2df9fdf2c1804cacd1500f2d7 /src
parent9a6dcbf895f9da01768e64b73cec88c16157d91e (diff)
New upstream version 1.18.0 upstream/1.18.0
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c48
-rw-r--r--src/NetworkManagerUtils.h8
-rw-r--r--src/devices/adsl/nm-atm-manager.c2
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c2
-rw-r--r--src/devices/nm-acd-manager.c48
-rw-r--r--src/devices/nm-acd-manager.h7
-rw-r--r--src/devices/nm-device-bond.c18
-rw-r--r--src/devices/nm-device-bridge.c174
-rw-r--r--src/devices/nm-device-ethernet.c2
-rw-r--r--src/devices/nm-device-private.h3
-rw-r--r--src/devices/nm-device-wireguard.c2
-rw-r--r--src/devices/nm-device.c197
-rw-r--r--src/devices/nm-device.h2
-rw-r--r--src/devices/nm-lldp-listener.c357
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c6
-rw-r--r--src/devices/ovs/nm-ovs-factory.c2
-rw-r--r--src/devices/ovs/nm-ovsdb.c2
-rw-r--r--src/devices/team/nm-device-team.c8
-rw-r--r--src/devices/tests/test-acd.c26
-rw-r--r--src/devices/tests/test-lldp.c76
-rw-r--r--src/devices/wifi/nm-device-iwd.c4
-rw-r--r--src/devices/wifi/nm-device-wifi.c2
-rw-r--r--src/devices/wifi/nm-iwd-manager.c2
-rw-r--r--src/devices/wwan/nm-modem-broadband.c13
-rw-r--r--src/devices/wwan/nm-modem-manager.c2
-rw-r--r--src/devices/wwan/nm-modem.c2
-rw-r--r--src/dhcp/meson.build2
-rw-r--r--src/dhcp/nm-dhcp-client.c4
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c181
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c2
-rw-r--r--src/dhcp/nm-dhcp-manager.c2
-rw-r--r--src/dhcp/nm-dhcp-systemd.c4
-rw-r--r--src/dhcp/nm-dhcp-utils.c103
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c2
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c2
-rw-r--r--src/dns/nm-dns-manager.c11
-rw-r--r--src/dns/nm-dns-systemd-resolved.c283
-rw-r--r--src/dns/nm-dns-systemd-resolved.h2
-rw-r--r--src/initrd/meson.build4
-rw-r--r--src/initrd/nm-initrd-generator.c2
-rw-r--r--src/meson.build11
-rw-r--r--src/nm-active-connection.c2
-rw-r--r--src/nm-auth-subject.c7
-rw-r--r--src/nm-auth-utils.c2
-rw-r--r--src/nm-checkpoint.c299
-rw-r--r--src/nm-connectivity.c23
-rw-r--r--src/nm-core-utils.c8
-rw-r--r--src/nm-core-utils.h2
-rw-r--r--src/nm-dbus-manager.c296
-rw-r--r--src/nm-dbus-manager.h6
-rw-r--r--src/nm-dbus-utils.c2
-rw-r--r--src/nm-dcb.c38
-rw-r--r--src/nm-dispatcher.c2
-rw-r--r--src/nm-hostname-manager.c2
-rw-r--r--src/nm-iface-helper.c8
-rw-r--r--src/nm-ip4-config.c19
-rw-r--r--src/nm-ip4-config.h2
-rw-r--r--src/nm-ip6-config.c2
-rw-r--r--src/nm-ip6-config.h2
-rw-r--r--src/nm-logging.c42
-rw-r--r--src/nm-logging.h2
-rw-r--r--src/nm-manager.c31
-rw-r--r--src/nm-netns.c30
-rw-r--r--src/nm-netns.h2
-rw-r--r--src/nm-policy.c2
-rw-r--r--src/nm-rfkill-manager.c2
-rw-r--r--src/nm-test-utils-core.h4
-rw-r--r--src/nm-types.h20
-rw-r--r--src/platform/nm-fake-platform.c15
-rw-r--r--src/platform/nm-linux-platform.c1203
-rw-r--r--src/platform/nm-netlink.h2
-rw-r--r--src/platform/nm-platform-utils.c2
-rw-r--r--src/platform/nm-platform-utils.h2
-rw-r--r--src/platform/nm-platform.c774
-rw-r--r--src/platform/nm-platform.h182
-rw-r--r--src/platform/nmp-object.c156
-rw-r--r--src/platform/nmp-object.h148
-rw-r--r--src/platform/nmp-rules-manager.c737
-rw-r--r--src/platform/nmp-rules-manager.h67
-rw-r--r--src/platform/tests/monitor.c2
-rw-r--r--src/platform/tests/test-common.c2
-rw-r--r--src/platform/tests/test-common.h52
-rw-r--r--src/platform/tests/test-link.c36
-rw-r--r--src/platform/tests/test-nmp-object.c4
-rw-r--r--src/platform/tests/test-route.c864
-rw-r--r--src/ppp/meson.build2
-rw-r--r--src/settings/nm-agent-manager.c2
-rw-r--r--src/settings/nm-settings-connection.c2
-rw-r--r--src/settings/nm-settings.c12
-rw-r--r--src/settings/plugins/README33
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c23
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c66
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.h8
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c17
-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.c401
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c164
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c79
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h8
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected19
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c130
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c32
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c12
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c2
-rw-r--r--src/supplicant/nm-supplicant-interface.c2
-rw-r--r--src/systemd/meson.build2
-rw-r--r--src/systemd/sd-adapt-core/udev-util.h (renamed from src/systemd/sd-adapt-core/stat-util.h)0
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c8
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c8
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-option.c6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c2
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c2
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c41
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c3
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c32
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c2
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c4
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.h3
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c45
-rw-r--r--src/systemd/src/systemd/sd-event.h2
-rw-r--r--src/systemd/src/systemd/sd-id128.h3
-rw-r--r--src/tests/meson.build2
-rw-r--r--src/tests/test-systemd.c6
129 files changed, 6240 insertions, 1697 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 71bfbf7c..13cb1dae 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -23,9 +23,11 @@
 
 #include "NetworkManagerUtils.h"
 
-#include "nm-utils/nm-c-list.h"
+#include <linux/fib_rules.h>
 
-#include "nm-common-macros.h"
+#include "nm-glib-aux/nm-c-list.h"
+
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-utils.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-ip4-config.h"
@@ -908,6 +910,48 @@ nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
 
 /*****************************************************************************/
 
+NMPlatformRoutingRule *
+nm_ip_routing_rule_to_platform (const NMIPRoutingRule *rule,
+                                NMPlatformRoutingRule *out_pl)
+{
+	nm_assert (rule);
+	nm_assert (nm_ip_routing_rule_validate (rule, NULL));
+	nm_assert (out_pl);
+
+	*out_pl = (NMPlatformRoutingRule) {
+		.addr_family = nm_ip_routing_rule_get_addr_family (rule),
+		.flags       = (  nm_ip_routing_rule_get_invert (rule)
+		                ? FIB_RULE_INVERT
+		                : 0),
+		.priority    = nm_ip_routing_rule_get_priority (rule),
+		.tos         = nm_ip_routing_rule_get_tos (rule),
+		.ip_proto    = nm_ip_routing_rule_get_ipproto (rule),
+		.fwmark      = nm_ip_routing_rule_get_fwmark (rule),
+		.fwmask      = nm_ip_routing_rule_get_fwmask (rule),
+		.sport_range = {
+		    .start   = nm_ip_routing_rule_get_source_port_start (rule),
+		    .end     = nm_ip_routing_rule_get_source_port_end (rule),
+		},
+		.dport_range = {
+		    .start   = nm_ip_routing_rule_get_destination_port_start (rule),
+		    .end     = nm_ip_routing_rule_get_destination_port_end (rule),
+		},
+		.src         = *(nm_ip_routing_rule_get_from_bin (rule) ?: &nm_ip_addr_zero),
+		.dst         = *(nm_ip_routing_rule_get_to_bin (rule)   ?: &nm_ip_addr_zero),
+		.src_len     = nm_ip_routing_rule_get_from_len (rule),
+		.dst_len     = nm_ip_routing_rule_get_to_len (rule),
+		.action      = nm_ip_routing_rule_get_action (rule),
+		.table       = nm_ip_routing_rule_get_table (rule),
+	};
+
+	nm_ip_routing_rule_get_xifname_bin (rule, TRUE,  out_pl->iifname);
+	nm_ip_routing_rule_get_xifname_bin (rule, FALSE, out_pl->oifname);
+
+	return out_pl;
+}
+
+/*****************************************************************************/
+
 struct _NMShutdownWaitObjHandle {
 	CList lst;
 	GObject *watched_obj;
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index 5e701224..2b1f5ed8 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -24,6 +24,9 @@
 
 #include "nm-core-utils.h"
 
+#include "nm-setting-ip-config.h"
+#include "platform/nm-platform.h"
+
 /*****************************************************************************/
 
 const char *nm_utils_get_ip_config_method (NMConnection *connection,
@@ -60,6 +63,11 @@ int nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
 
 /*****************************************************************************/
 
+NMPlatformRoutingRule *nm_ip_routing_rule_to_platform (const NMIPRoutingRule *rule,
+                                                       NMPlatformRoutingRule *out_pl);
+
+/*****************************************************************************/
+
 /* during shutdown, there are two relevant timeouts. One is
  * NM_SHUTDOWN_TIMEOUT_MS which is plenty of time, that we give for all
  * actions to complete. Of course, during shutdown components should hurry
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index dddb8342..f312b5a3 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -27,7 +27,7 @@
 #include "nm-device-adsl.h"
 #include "devices/nm-device-factory.h"
 #include "platform/nm-platform.h"
-#include "nm-utils/nm-udev-utils.h"
+#include "nm-udev-aux/nm-udev-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index dc85a8b2..9cd3a519 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -35,7 +35,7 @@
 #include "nm-device-bt.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 
 /*****************************************************************************/
 
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index a8f7a63a..f437ce3a 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -113,6 +113,30 @@ acd_error_to_string (int error)
 	g_return_val_if_reached (NULL);
 }
 
+static int
+acd_error_to_nmerr (int error, gboolean always_fail)
+{
+	if (error < 0)
+		return -nm_errno_native (error);
+
+	if (always_fail) {
+		if (NM_IN_SET (error, N_ACD_E_PREEMPTED,
+		                      N_ACD_E_INVALID_ARGUMENT))
+			return -NME_UNSPEC;
+		g_return_val_if_reached (-NME_UNSPEC);
+	}
+
+	/* so, @error is either zero (indicating success) or one
+	 * of the special status codes like N_ACD_E_*. In both cases,
+	 * return the positive value here. */
+	if (NM_IN_SET (error, _N_ACD_E_SUCCESS,
+	                      N_ACD_E_PREEMPTED,
+	                      N_ACD_E_INVALID_ARGUMENT))
+		return error;
+
+	g_return_val_if_reached (error);
+}
+
 /*****************************************************************************/
 
 /**
@@ -291,9 +315,9 @@ acd_init (NMAcdManager *self)
  * Start probing IP addresses for duplicates; when the probe terminates a
  * PROBE_TERMINATED signal is emitted.
  *
- * Returns: %TRUE if at least one probe could be started, %FALSE otherwise
+ * Returns: 0 on success or a negative NetworkManager error code (NME_*).
  */
-gboolean
+int
 nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 {
 	GHashTableIter iter;
@@ -309,7 +333,7 @@ nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 		_LOGW ("couldn't init ACD for probing on interface '%s': %s",
 		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
-		return FALSE;
+		return acd_error_to_nmerr (r, TRUE);
 	}
 
 	self->completed = 0;
@@ -325,7 +349,7 @@ nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 	self->channel = g_io_channel_unix_new (fd);
 	self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
 
-	return success;
+	return success ? 0 : -NME_UNSPEC;
 }
 
 /**
@@ -357,20 +381,23 @@ nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address)
  * @self: a #NMAcdManager
  *
  * Start announcing addresses.
+ *
+ * Returns: a negative NetworkManager error number or zero on success.
  */
-void
+int
 nm_acd_manager_announce_addresses (NMAcdManager *self)
 {
 	GHashTableIter iter;
 	AddressInfo *info;
 	int r;
+	gboolean success = TRUE;
 
 	r = acd_init (self);
 	if (r) {
 		_LOGW ("couldn't init ACD for announcing addresses on interface '%s': %s",
 		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
-		return;
+		return acd_error_to_nmerr (r, TRUE);
 	}
 
 	if (self->state == STATE_INIT) {
@@ -378,8 +405,10 @@ nm_acd_manager_announce_addresses (NMAcdManager *self)
 		 * start a fake probe with zero timeout and then perform
 		 * the announcement. */
 		g_hash_table_iter_init (&iter, self->addresses);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
-			acd_probe_add (self, info, 0);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
+			if (!acd_probe_add (self, info, 0))
+				success = FALSE;
+		}
 		self->state = STATE_ANNOUNCING;
 	} else if (self->state == STATE_ANNOUNCING) {
 		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
@@ -394,10 +423,13 @@ nm_acd_manager_announce_addresses (NMAcdManager *self)
 				       nm_utils_inet4_ntop (info->address, sbuf),
 				       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 				       acd_error_to_string (r));
+				success = FALSE;
 			} else
 				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, sbuf));
 		}
 	}
+
+	return success ? 0 : -NME_UNSPEC;
 }
 
 static void
diff --git a/src/devices/nm-acd-manager.h b/src/devices/nm-acd-manager.h
index 75884846..08c0b798 100644
--- a/src/devices/nm-acd-manager.h
+++ b/src/devices/nm-acd-manager.h
@@ -36,8 +36,11 @@ NMAcdManager *nm_acd_manager_new (int ifindex,
 void nm_acd_manager_free (NMAcdManager *self);
 
 gboolean nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address);
-gboolean nm_acd_manager_start_probe (NMAcdManager *self, guint timeout);
+int nm_acd_manager_start_probe (NMAcdManager *self, guint timeout);
 gboolean nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address);
-void nm_acd_manager_announce_addresses (NMAcdManager *self);
+int nm_acd_manager_announce_addresses (NMAcdManager *self);
+
+NM_AUTO_DEFINE_FCN0 (NMAcdManager *, _nm_auto_free_acdmgr, nm_acd_manager_free);
+#define nm_auto_free_acdmgr nm_auto (_nm_auto_free_acdmgr)
 
 #endif /* __NM_ACD_MANAGER__ */
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 6dabdfe8..37159fca 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -185,20 +185,18 @@ set_arp_targets (NMDevice *device,
                  const char *delim,
                  const char *prefix)
 {
-	char **items, **iter, *tmp;
+	gs_free const char **value_v = NULL;
+	gsize i;
 
-	if (!value || !*value)
+	value_v = nm_utils_strsplit_set (value, delim);
+	if (!value_v)
 		return;
+	for (i = 0; value_v[i]; i++) {
+		gs_free char *tmp = NULL;
 
-	items = g_strsplit_set (value, delim, 0);
-	for (iter = items; iter && *iter; iter++) {
-		if (*iter[0]) {
-			tmp = g_strdup_printf ("%s%s", prefix, *iter);
-			set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, tmp);
-			g_free (tmp);
-		}
+		tmp = g_strdup_printf ("%s%s", prefix, value_v[i]);
+		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, tmp);
 	}
-	g_strfreev (items);
 }
 
 static void
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 4c8921c0..4275af91 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -37,6 +37,7 @@ _LOG_DECLARE_SELF(NMDeviceBridge);
 
 struct _NMDeviceBridge {
 	NMDevice parent;
+	bool vlan_configured:1;
 };
 
 struct _NMDeviceBridgeClass {
@@ -267,21 +268,45 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 		nm_platform_sysctl_master_set_option (nm_device_get_platform (device), ifindex, option->sysname, value);
 }
 
-static void
-commit_master_options (NMDevice *device, NMSettingBridge *setting)
+static const NMPlatformBridgeVlan **
+setting_vlans_to_platform (GPtrArray *array)
 {
-	const Option *option;
-	NMSetting *s = NM_SETTING (setting);
-
-	for (option = master_options; option->name; option++)
-		commit_option (device, s, option, FALSE);
+	NMPlatformBridgeVlan **arr;
+	NMPlatformBridgeVlan *p_data;
+	guint i;
+
+	if (!array || !array->len)
+		return NULL;
+
+	G_STATIC_ASSERT_EXPR (_nm_alignof (NMPlatformBridgeVlan *) >= _nm_alignof (NMPlatformBridgeVlan));
+	arr = g_malloc (  (sizeof (NMPlatformBridgeVlan *) * (array->len + 1))
+	                + (sizeof (NMPlatformBridgeVlan  ) * (array->len    )));
+	p_data = (NMPlatformBridgeVlan *) &arr[array->len + 1];
+
+	for (i = 0; i < array->len; i++) {
+		NMBridgeVlan *vlan = array->pdata[i];
+		guint16 vid_start, vid_end;
+
+		nm_bridge_vlan_get_vid_range (vlan, &vid_start, &vid_end);
+
+		p_data[i] = (NMPlatformBridgeVlan) {
+			.vid_start = vid_start,
+			.vid_end   = vid_end,
+			.pvid      = nm_bridge_vlan_is_pvid (vlan),
+			.untagged  = nm_bridge_vlan_is_untagged (vlan),
+		};
+		arr[i] = &p_data[i];
+	}
+	arr[i] = NULL;
+	return (const NMPlatformBridgeVlan **) arr;
 }
 
 static void
 commit_slave_options (NMDevice *device, NMSettingBridgePort *setting)
 {
 	const Option *option;
-	NMSetting *s, *s_clear = NULL;
+	NMSetting *s;
+	gs_unref_object NMSetting *s_clear = NULL;
 
 	if (setting)
 		s = NM_SETTING (setting);
@@ -290,8 +315,6 @@ commit_slave_options (NMDevice *device, NMSettingBridgePort *setting)
 
 	for (option = slave_options; option->name; option++)
 		commit_option (device, s, option, TRUE);
-
-	g_clear_object (&s_clear);
 }
 
 static void
@@ -396,22 +419,112 @@ master_update_slave_connection (NMDevice *device,
 	return TRUE;
 }
 
+static gboolean
+bridge_set_vlan_options (NMDevice *device, NMSettingBridge *s_bridge)
+{
+	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
+	gconstpointer hwaddr;
+	size_t length;
+	gboolean enabled;
+	guint16 pvid;
+	NMPlatform *plat;
+	int ifindex;
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	gs_free const NMPlatformBridgeVlan **plat_vlans = NULL;
+
+	if (self->vlan_configured)
+		return TRUE;
+
+	plat = nm_device_get_platform (device);
+	ifindex = nm_device_get_ifindex (device);
+	enabled = nm_setting_bridge_get_vlan_filtering (s_bridge);
+
+	if (!enabled) {
+		nm_platform_sysctl_master_set_option (plat, ifindex, "vlan_filtering", "0");
+		nm_platform_sysctl_master_set_option (plat, ifindex, "default_pvid", "1");
+		nm_platform_link_set_bridge_vlans (plat, ifindex, FALSE, NULL);
+		return TRUE;
+	}
+
+	hwaddr = nm_platform_link_get_address (plat, ifindex, &length);
+	g_return_val_if_fail (length == ETH_ALEN, FALSE);
+	if (nm_utils_hwaddr_matches (hwaddr, ETH_ALEN, nm_ip_addr_zero.addr_eth, ETH_ALEN)) {
+		/* We need a non-zero MAC address to set the default pvid.
+		 * Retry later. */
+		return TRUE;
+	}
+
+	self->vlan_configured = TRUE;
+
+	/* Filtering must be disabled to change the default PVID */
+	if (!nm_platform_sysctl_master_set_option (plat, ifindex, "vlan_filtering", "0"))
+		return FALSE;
+
+	/* Clear the default PVID so that we later can force the re-creation of
+	 * default PVID VLANs by writing the option again. */
+	if (!nm_platform_sysctl_master_set_option (plat, ifindex, "default_pvid", "0"))
+		return FALSE;
+
+	/* Clear all existing VLANs */
+	if (!nm_platform_link_set_bridge_vlans (plat, ifindex, FALSE, NULL))
+		return FALSE;
+
+	/* Now set the default PVID. After this point the kernel creates
+	 * a PVID VLAN on each port, including the bridge itself. */
+	pvid = nm_setting_bridge_get_vlan_default_pvid (s_bridge);
+	if (pvid) {
+		char value[32];
+
+		nm_sprintf_buf (value, "%u", pvid);
+		if (!nm_platform_sysctl_master_set_option (plat, ifindex, "default_pvid", value))
+			return FALSE;
+	}
+
+	/* Create VLANs only after setting the default PVID, so that
+	 * any PVID VLAN overrides the bridge's default PVID. */
+	g_object_get (s_bridge, NM_SETTING_BRIDGE_VLANS, &vlans, NULL);
+	plat_vlans = setting_vlans_to_platform (vlans);
+	if (   plat_vlans
+	    && !nm_platform_link_set_bridge_vlans (plat, ifindex, FALSE, plat_vlans))
+		return FALSE;
+
+	if (!nm_platform_sysctl_master_set_option (plat, ifindex, "vlan_filtering", "1"))
+		return FALSE;
+
+	return TRUE;
+}
+
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMActStageReturn ret;
-	NMConnection *connection = nm_device_get_applied_connection (device);
+	NMConnection *connection;
+	NMSetting *s_bridge;
+	const Option *option;
 
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	NM_DEVICE_BRIDGE (device)->vlan_configured = FALSE;
 
 	ret = NM_DEVICE_CLASS (nm_device_bridge_parent_class)->act_stage1_prepare (device, out_failure_reason);
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
+	connection = nm_device_get_applied_connection (device);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	s_bridge = (NMSetting *) nm_connection_get_setting_bridge (connection);
+	g_return_val_if_fail (s_bridge, NM_ACT_STAGE_RETURN_FAILURE);
+
+	if (!nm_device_hw_addr_set_cloned (device, connection, FALSE)) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
-	commit_master_options (device, nm_connection_get_setting_bridge (connection));
+	for (option = master_options; option->name; option++)
+		commit_option (device, s_bridge, option, FALSE);
+
+	if (!bridge_set_vlan_options (device, (NMSettingBridge *) s_bridge)) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
@@ -457,12 +570,43 @@ enslave_slave (NMDevice *device,
                gboolean configure)
 {
 	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
+	NMConnection *master_connection;
+	NMSettingBridge *s_bridge;
+	NMSettingBridgePort *s_port;
 
 	if (configure) {
 		if (!nm_platform_link_enslave (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), nm_device_get_ip_ifindex (slave)))
 			return FALSE;
 
-		commit_slave_options (slave, nm_connection_get_setting_bridge_port (connection));
+		master_connection = nm_device_get_applied_connection (device);
+		nm_assert (master_connection);
+		s_bridge = nm_connection_get_setting_bridge (master_connection);
+		nm_assert (s_bridge);
+		s_port = nm_connection_get_setting_bridge_port (connection);
+
+		bridge_set_vlan_options (device, s_bridge);
+
+		if (nm_setting_bridge_get_vlan_filtering (s_bridge)) {
+			gs_free const NMPlatformBridgeVlan **plat_vlans = NULL;
+			gs_unref_ptrarray GPtrArray *vlans = NULL;
+
+			if (s_port)
+				g_object_get (s_port, NM_SETTING_BRIDGE_PORT_VLANS, &vlans, NULL);
+
+			plat_vlans = setting_vlans_to_platform (vlans);
+
+			/* Since the link was just enslaved, there are no existing VLANs
+			 * (except for the default one) and so there's no need to flush. */
+
+			if (   plat_vlans
+			    && !nm_platform_link_set_bridge_vlans (nm_device_get_platform (slave),
+			                                           nm_device_get_ifindex (slave),
+			                                           TRUE,
+			                                           plat_vlans))
+				return FALSE;
+		}
+
+		commit_slave_options (slave, s_port);
 
 		_LOGI (LOGD_BRIDGE, "attached bridge port %s",
 		       nm_device_get_ip_iface (slave));
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 24c99f76..170c7e28 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -48,7 +48,7 @@
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
-#include "nm-utils/nm-udev-utils.h"
+#include "nm-udev-aux/nm-udev-utils.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceEthernet);
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 6e2372ab..dd98e92f 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -168,7 +168,8 @@ NMIPConfig *nm_device_ip_config_new (NMDevice *self, int addr_family);
 /*****************************************************************************/
 
 gint64 nm_device_get_configured_mtu_from_connection_default (NMDevice *self,
-                                                             const char *property_name);
+                                                             const char *property_name,
+                                                             guint32 max_mtu);
 
 guint32 nm_device_get_configured_mtu_from_connection (NMDevice *device,
                                                       GType setting_type,
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index a9eb1ab4..f8c7d1c3 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -23,7 +23,7 @@
 
 #include "nm-setting-wireguard.h"
 #include "nm-core-internal.h"
-#include "nm-utils/nm-secret-utils.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-device-private.h"
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 7514fa78..bd4fbcc3 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -36,17 +36,18 @@
 #include <linux/rtnetlink.h>
 #include <linux/pkt_sched.h>
 
-#include "nm-utils/nm-dedup-multi.h"
-#include "nm-utils/nm-random-utils.h"
-#include "nm-utils/unaligned.h"
+#include "nm-std-aux/unaligned.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-random-utils.h"
 
-#include "nm-ethtool-utils.h"
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-device-private.h"
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
+#include "platform/nmp-rules-manager.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 #include "dhcp/nm-dhcp-manager.h"
@@ -1545,8 +1546,7 @@ _set_ip_ifindex (NMDevice *self,
 		                                        priv->ip_ifindex,
 		                                        priv->ip_iface);
 
-		if (nm_platform_check_kernel_support (platform,
-		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
+		if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
 			nm_platform_link_set_user_ipv6ll_enabled (platform, priv->ip_ifindex, TRUE);
 
 		if (!nm_platform_link_is_up (platform, priv->ip_ifindex))
@@ -2787,8 +2787,10 @@ nm_device_check_connectivity_update_interval (NMDevice *self)
 }
 
 static void
-concheck_update_state (NMDevice *self, int addr_family,
-                       NMConnectivityState state, gboolean allow_periodic_bump)
+concheck_update_state (NMDevice *self,
+                       int addr_family,
+                       NMConnectivityState state,
+                       gboolean allow_periodic_bump)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const gboolean IS_IPv4 = (addr_family == AF_INET);
@@ -2821,6 +2823,14 @@ concheck_update_state (NMDevice *self, int addr_family,
 				state = NM_CONNECTIVITY_LIMITED;
 		} else
 			state = NM_CONNECTIVITY_NONE;
+	} else if (state == NM_CONNECTIVITY_LIMITED) {
+		/* NMConnectivity cannot distinguish between NONE and LIMITED connectivity. In both
+		 * cases, it just failed to fetch the URL.
+		 *
+		 * NMDevice coerces a LIMITED state to NONE here, if the logical state of the device
+		 * is disconnected. */
+		if (priv->state <= NM_DEVICE_STATE_DISCONNECTED)
+			state = NM_CONNECTIVITY_NONE;
 	}
 
 	if (priv->concheck_x[IS_IPv4].state == state) {
@@ -4319,8 +4329,7 @@ realize_start_setup (NMDevice *self,
 		if (priv->firmware_version)
 			_notify (self, PROP_FIRMWARE_VERSION);
 
-		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
-		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
+		if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
 			priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
 
 		if (nm_platform_link_supports_sriov (nm_device_get_platform (self), priv->ifindex))
@@ -5064,7 +5073,6 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection = nm_device_get_applied_connection (self);
-	NMDeviceState new_state;
 	const char *master_status;
 
 	g_return_if_fail (priv->master);
@@ -5073,16 +5081,17 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 	    && priv->state <= NM_DEVICE_STATE_ACTIVATED) {
 		switch (nm_device_state_reason_check (reason)) {
 		case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
-			new_state = NM_DEVICE_STATE_FAILED;
 			master_status = "failed";
 			break;
 		case NM_DEVICE_STATE_REASON_USER_REQUESTED:
-			new_state = NM_DEVICE_STATE_DEACTIVATING;
 			reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
 			master_status = "deactivated by user request";
 			break;
+		case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED:
+			reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
+			master_status = "deactivated because master was removed";
+			break;
 		default:
-			new_state = NM_DEVICE_STATE_DISCONNECTED;
 			master_status = "deactivated";
 			break;
 		}
@@ -5093,7 +5102,7 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 
 		/* Cancel any pending activation sources */
 		_cancel_activation (self);
-		nm_device_queue_state (self, new_state, reason);
+		nm_device_queue_state (self, NM_DEVICE_STATE_DEACTIVATING, reason);
 	} else
 		_LOGI (LOGD_DEVICE, "released from master device %s", nm_device_get_iface (priv->master));
 
@@ -5304,7 +5313,8 @@ nm_device_autoconnect_allowed (NMDevice *self)
 		if (priv->state < NM_DEVICE_STATE_DISCONNECTED)
 			return FALSE;
 	} else {
-		/* Unrealized devices can always autoconnect. */
+		if (!nm_device_check_unrealized_device_managed (self))
+			return FALSE;
 	}
 
 	/* The 'autoconnect-allowed' signal is emitted on a device to allow
@@ -6403,6 +6413,84 @@ lldp_init (NMDevice *self, gboolean restart)
 	}
 }
 
+/* set-mode can be:
+ *  - TRUE: sync with new rules.
+ *  - FALSE: sync, but remove all rules (== flush)
+ *  - DEFAULT: forget about all the rules that we previously tracked,
+ *       but don't actually remove them. This is when quitting NM
+ *       we want to keep the rules.
+ *       The problem is, after restart of NM, the rule manager will
+ *       no longer remember that NM added these rules and treat them
+ *       as externally added ones. Don't restart NetworkManager if
+ *       you care about that.
+ */
+static void
+_routing_rules_sync (NMDevice *self,
+                     NMTernary set_mode)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMPRulesManager *rules_manager = nm_netns_get_rules_manager (nm_device_get_netns (self));
+	gboolean untrack_only_dirty = FALSE;
+	gboolean keep_deleted_rules;
+	gpointer user_tag;
+
+	user_tag = priv;
+
+	if (set_mode == NM_TERNARY_TRUE) {
+		NMConnection *applied_connection;
+		NMSettingIPConfig *s_ip;
+		guint i, num;
+		int is_ipv4;
+
+		untrack_only_dirty = TRUE;
+		nmp_rules_manager_set_dirty (rules_manager, user_tag);
+
+		applied_connection = nm_device_get_applied_connection (self);
+
+		for (is_ipv4 = 0; applied_connection && is_ipv4 < 2; is_ipv4++) {
+			int addr_family = is_ipv4 ? AF_INET : AF_INET6;
+
+			s_ip = nm_connection_get_setting_ip_config (applied_connection, addr_family);
+			if (!s_ip)
+				continue;
+
+			num = nm_setting_ip_config_get_num_routing_rules (s_ip);
+			for (i = 0; i < num; i++) {
+				NMPlatformRoutingRule plrule;
+				NMIPRoutingRule *rule;
+
+				rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
+				nm_ip_routing_rule_to_platform (rule, &plrule);
+				nmp_rules_manager_track (rules_manager,
+				                         &plrule,
+				                         10,
+				                         user_tag);
+			}
+		}
+	}
+
+	nmp_rules_manager_untrack_all (rules_manager, user_tag, !untrack_only_dirty);
+
+	keep_deleted_rules = FALSE;
+	if (set_mode == NM_TERNARY_DEFAULT) {
+		/* when exiting NM, we leave the device up and the rules configured.
+		 * We just all nmp_rules_manager_sync() to forget about the synced rules,
+		 * but we don't actually delete them.
+		 *
+		 * FIXME: that is a problem after restart of NetworkManager, because these
+		 * rules will look like externally added, and NM will no longer remove
+		 * them.
+		 * To fix that, we could during "assume" mark the rules of the profile
+		 * as owned (and "added" by the device). The problem with that is that it
+		 * wouldn't cover rules that devices add by internal decision (not because
+		 * of a setting in the profile, e.g. WireGuard could setup policy routing).
+		 * Maybe it would be better to remember these orphaned rules at exit in a
+		 * file and track them after restart again. */
+		keep_deleted_rules = TRUE;
+	}
+	nmp_rules_manager_sync (rules_manager, keep_deleted_rules);
+}
+
 static gboolean
 tc_commit (NMDevice *self)
 {
@@ -6514,6 +6602,8 @@ activate_stage2_device_config (NMDevice *self)
 		}
 	}
 
+	_routing_rules_sync (self, NM_TERNARY_TRUE);
+
 	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
 			if (no_firmware)
@@ -6747,7 +6837,8 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 	NMDedupMultiIter ipconf_iter;
 	AcdData *data;
 	guint timeout;
-	gboolean ret, addr_found;
+	gboolean addr_found;
+	int r;
 	const guint8 *hwaddr_arr;
 	size_t length;
 	guint i;
@@ -6801,9 +6892,8 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 			nm_acd_manager_add_address (acd_manager, address->address);
 	}
 
-	ret = nm_acd_manager_start_probe (acd_manager, timeout);
-
-	if (!ret) {
+	r = nm_acd_manager_start_probe (acd_manager, timeout);
+	if (r < 0) {
 		_LOGW (LOGD_DEVICE, "acd probe failed");
 
 		/* DAD could not be started, signal success */
@@ -8830,12 +8920,13 @@ linklocal6_start (NMDevice *self)
 
 gint64
 nm_device_get_configured_mtu_from_connection_default (NMDevice *self,
-                                                      const char *property_name)
+                                                      const char *property_name,
+                                                      guint32 max_mtu)
 {
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                    property_name,
 	                                                    self,
-	                                                    0, G_MAXUINT32, -1);
+	                                                    0, max_mtu, -1);
 }
 
 guint32
@@ -8848,6 +8939,7 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 	NMSetting *setting;
 	gint64 mtu_default;
 	guint32 mtu = 0;
+	guint32 max_mtu = G_MAXUINT32;
 
 	nm_assert (NM_IS_DEVICE (self));
 	nm_assert (out_source);
@@ -8870,6 +8962,7 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 		if (setting)
 			mtu = nm_setting_infiniband_get_mtu (NM_SETTING_INFINIBAND (setting));
 		global_property_name = NM_CON_DEFAULT ("infiniband.mtu");
+		max_mtu = NM_INFINIBAND_MAX_MTU;
 	} else if (setting_type == NM_TYPE_SETTING_IP_TUNNEL) {
 		if (setting)
 			mtu = nm_setting_ip_tunnel_get_mtu (NM_SETTING_IP_TUNNEL (setting));
@@ -8881,13 +8974,12 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 	} else
 		g_return_val_if_reached (0);
 
-
 	if (mtu) {
 		*out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
 		return mtu;
 	}
 
-	mtu_default = nm_device_get_configured_mtu_from_connection_default (self, global_property_name);
+	mtu_default = nm_device_get_configured_mtu_from_connection_default (self, global_property_name, max_mtu);
 	if (mtu_default >= 0) {
 		*out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
 		return (guint32) mtu_default;
@@ -9140,8 +9232,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		 * addresses as /128. The reason for the /128 is to prevent the kernel
 		 * from adding a prefix route for this address. */
 		ifa_flags = 0;
-		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
-		                                      NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+		if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
 			ifa_flags |= IFA_F_NOPREFIXROUTE;
 			if (NM_IN_SET (priv->ndisc_use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
 			                                         NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
@@ -9173,8 +9264,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		                                  rdata->routes_n,
 		                                  nm_device_get_route_table (self, AF_INET6, TRUE),
 		                                  nm_device_get_route_metric (self, AF_INET6),
-		                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
-		                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+		                                  nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
 		if (priv->ac_ip6_config.current) {
 			nm_ip6_config_reset_routes_ndisc ((NMIP6Config *) priv->ac_ip6_config.current,
 			                                  rdata->gateways,
@@ -9183,8 +9273,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 			                                  rdata->routes_n,
 			                                  nm_device_get_route_table (self, AF_INET6, TRUE),
 			                                  nm_device_get_route_metric (self, AF_INET6),
-			                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
-			                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+			                                  nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
 		}
 
 	}
@@ -9372,8 +9461,7 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	priv->ndisc_use_tempaddr = use_tempaddr;
 
 	if (   NM_IN_SET (use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-	    && !nm_platform_check_kernel_support (nm_device_get_platform (self),
-	                                          NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+	    && !nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
 		_LOGW (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for "
 		                 "IPv6 private addresses. This feature is not available");
 	}
@@ -9480,8 +9568,7 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ip_ifindex (self);
 
-	if (!nm_platform_check_kernel_support (nm_device_get_platform (self),
-	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
+	if (!nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
 		return;
 
 	priv->ipv6ll_handle = enable;
@@ -9854,7 +9941,7 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP6Config *ip6_config = NULL;
+	gs_unref_object NMIP6Config *ip6_config = NULL;
 
 	g_assert (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT);
 
@@ -13424,6 +13511,32 @@ nm_device_set_unmanaged_by_flags_queue (NMDevice *self,
 	_set_unmanaged_flags (self, flags, set_op, TRUE, FALSE, reason);
 }
 
+/**
+ * nm_device_check_unrealized_device_managed:
+ *
+ * Checks if a unrealized device is managed from user settings
+ * or user configuration.
+ */
+gboolean
+nm_device_check_unrealized_device_managed (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (!nm_device_is_real (self));
+
+	if (!nm_config_data_get_device_config_boolean (NM_CONFIG_GET_DATA,
+	                                               NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
+	                                               self,
+	                                               TRUE,
+	                                               TRUE))
+		return FALSE;
+
+	if (nm_device_spec_match_list (self, nm_settings_get_unmanaged_specs (priv->settings)))
+		return FALSE;
+
+	return TRUE;
+}
+
 void
 nm_device_set_unmanaged_by_user_settings (NMDevice *self)
 {
@@ -14345,6 +14458,11 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		}
 	}
 
+	_routing_rules_sync (self,
+	                       cleanup_type == CLEANUP_TYPE_KEEP
+	                     ? NM_TERNARY_DEFAULT
+	                     : NM_TERNARY_FALSE);
+
 	if (ifindex > 0)
 		nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self), ifindex, NULL);
 
@@ -15879,17 +15997,22 @@ nm_device_spec_match_list_full (NMDevice *self, const GSList *specs, int no_matc
 {
 	NMDeviceClass *klass;
 	NMMatchSpecMatchType m;
+	const char *hw_address = NULL;
+	gboolean is_fake;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	klass = NM_DEVICE_GET_CLASS (self);
+	hw_address = nm_device_get_permanent_hw_address_full (self,
+	                                                      !nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT),
+	                                                      &is_fake);
 
 	m = nm_match_spec_device (specs,
 	                          nm_device_get_iface (self),
 	                          nm_device_get_type_description (self),
 	                          nm_device_get_driver (self),
 	                          nm_device_get_driver_version (self),
-	                          nm_device_get_permanent_hw_address (self),
+	                          is_fake ? NULL : hw_address,
 	                          klass->get_s390_subchannels ? klass->get_s390_subchannels (self) : NULL,
 	                          nm_dhcp_manager_get_config (nm_dhcp_manager_get ()));
 
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 45c9dda0..d11c33ab 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -691,6 +691,8 @@ void nm_device_set_unmanaged_by_user_udev (NMDevice *self);
 void nm_device_set_unmanaged_by_user_conf (NMDevice *self);
 void nm_device_set_unmanaged_by_quitting (NMDevice *device);
 
+gboolean nm_device_check_unrealized_device_managed (NMDevice *self);
+
 gboolean nm_device_is_nm_owned (NMDevice *device);
 
 gboolean nm_device_has_capability (NMDevice *self, NMDeviceCapabilities caps);
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index cb3b5907..831054ba 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -24,7 +24,9 @@
 
 #include <net/ethernet.h>
 
+#include "nm-std-aux/unaligned.h"
 #include "platform/nm-platform.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-utils.h"
 
 #include "systemd/nm-sd.h"
@@ -40,6 +42,8 @@ typedef enum {
 	LLDP_ATTR_TYPE_NONE,
 	LLDP_ATTR_TYPE_UINT32,
 	LLDP_ATTR_TYPE_STRING,
+	LLDP_ATTR_TYPE_VARDICT,
+	LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS,
 } LldpAttrType;
 
 typedef enum {
@@ -49,12 +53,18 @@ typedef enum {
 	LLDP_ATTR_ID_SYSTEM_NAME,
 	LLDP_ATTR_ID_SYSTEM_DESCRIPTION,
 	LLDP_ATTR_ID_SYSTEM_CAPABILITIES,
+	LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,
 	LLDP_ATTR_ID_IEEE_802_1_PVID,
 	LLDP_ATTR_ID_IEEE_802_1_PPVID,
 	LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,
+	LLDP_ATTR_ID_IEEE_802_1_PPVIDS,
 	LLDP_ATTR_ID_IEEE_802_1_VID,
 	LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
-	_LLDP_PROP_ID_COUNT,
+	LLDP_ATTR_ID_IEEE_802_1_VLANS,
+	LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,
+	LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI,
+	LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,
+	_LLDP_ATTR_ID_COUNT,
 } LldpAttrId;
 
 typedef struct {
@@ -62,6 +72,8 @@ typedef struct {
 	union {
 		guint32 v_uint32;
 		char *v_string;
+		GVariant *v_variant;
+		CList v_variant_list;
 	};
 } LldpAttrData;
 
@@ -109,7 +121,7 @@ typedef struct {
 
 	bool valid:1;
 
-	LldpAttrData attrs[_LLDP_PROP_ID_COUNT];
+	LldpAttrData attrs[_LLDP_ATTR_ID_COUNT];
 
 	GVariant *variant;
 } LldpNeighbor;
@@ -155,35 +167,26 @@ ether_addr_equal (const struct ether_addr *a1, const struct ether_addr *a2)
 	return memcmp (a1, a2, ETH_ALEN) == 0;
 }
 
-static guint32
-_access_uint8 (const void *data)
-{
-	return *((const guint8 *) data);
-}
-
-static guint32
-_access_uint16 (const void *data)
-{
-	guint16 v;
-
-	memcpy (&v, data, sizeof (v));
-	return ntohs (v);
-}
-
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_lldp_attr_id_to_name, LldpAttrId,
 	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,        NM_LLDP_ATTR_PORT_DESCRIPTION),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,             NM_LLDP_ATTR_SYSTEM_NAME),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,      NM_LLDP_ATTR_SYSTEM_DESCRIPTION),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,     NM_LLDP_ATTR_SYSTEM_CAPABILITIES),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,         NM_LLDP_ATTR_IEEE_802_1_PVID),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,        NM_LLDP_ATTR_IEEE_802_1_PPVID),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,  NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,          NM_LLDP_ATTR_IEEE_802_1_VID),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,    NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_PROP_ID_COUNT),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,         NM_LLDP_ATTR_PORT_DESCRIPTION),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,              NM_LLDP_ATTR_SYSTEM_NAME),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,       NM_LLDP_ATTR_SYSTEM_DESCRIPTION),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,      NM_LLDP_ATTR_SYSTEM_CAPABILITIES),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,     NM_LLDP_ATTR_MANAGEMENT_ADDRESSES),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,          NM_LLDP_ATTR_IEEE_802_1_PVID),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,         NM_LLDP_ATTR_IEEE_802_1_PPVID),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,   NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVIDS,        NM_LLDP_ATTR_IEEE_802_1_PPVIDS),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,           NM_LLDP_ATTR_IEEE_802_1_VID),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,     NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLANS,         NM_LLDP_ATTR_IEEE_802_1_VLANS),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,  NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI, NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI),
+	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_ATTR_ID_COUNT),
 );
 
 _NM_UTILS_LOOKUP_DEFINE (static, _lldp_attr_id_to_type, LldpAttrId, LldpAttrType,
@@ -192,12 +195,18 @@ _NM_UTILS_LOOKUP_DEFINE (static, _lldp_attr_id_to_type, LldpAttrId, LldpAttrType
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,                 LLDP_ATTR_TYPE_STRING),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,          LLDP_ATTR_TYPE_STRING),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,         LLDP_ATTR_TYPE_UINT32),
+	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,        LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,             LLDP_ATTR_TYPE_UINT32),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,            LLDP_ATTR_TYPE_UINT32),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,      LLDP_ATTR_TYPE_UINT32),
+	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVIDS,           LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,              LLDP_ATTR_TYPE_UINT32),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,        LLDP_ATTR_TYPE_STRING),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_PROP_ID_COUNT),
+	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLANS,            LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS),
+	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,     LLDP_ATTR_TYPE_VARDICT),
+	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI,    LLDP_ATTR_TYPE_VARDICT),
+	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,   LLDP_ATTR_TYPE_UINT32),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_ATTR_ID_COUNT),
 );
 
 static void
@@ -216,13 +225,8 @@ _lldp_attr_set_str (LldpAttrData *pdata, LldpAttrId attr_id, const char *v_strin
 }
 
 static void
-_lldp_attr_set_str_ptr (LldpAttrData *pdata, LldpAttrId attr_id, const void *str, gsize len)
+_lldp_attr_take_str_ptr (LldpAttrData *pdata, LldpAttrId attr_id, char *str)
 {
-	const char *s = str;
-	const char *tmp;
-	gsize len0 = len;
-	gs_free char *str_free = NULL;
-
 	nm_assert (pdata);
 	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_STRING);
 
@@ -233,23 +237,7 @@ _lldp_attr_set_str_ptr (LldpAttrData *pdata, LldpAttrId attr_id, const void *str
 		return;
 
 	pdata->attr_type = LLDP_ATTR_TYPE_STRING;
-
-	/* truncate at first NUL, including removing trailing NULs*/
-	tmp = memchr (s, '\0', len);
-	if (tmp)
-		len = tmp - s;
-
-	if (!len) {
-		pdata->v_string = g_strdup ("");
-		return;
-	}
-
-	if (len0 <= len || s[len] != '\0') {
-		/* hmpf, g_strescape needs a trailing NUL. Need to clone */
-		s = str_free = g_strndup (s, len);
-	}
-
-	pdata->v_string = g_strescape (s, NULL);
+	pdata->v_string = str;
 }
 
 static void
@@ -267,6 +255,44 @@ _lldp_attr_set_uint32 (LldpAttrData *pdata, LldpAttrId attr_id, guint32 v_uint32
 	pdata->v_uint32 = v_uint32;
 }
 
+static void
+_lldp_attr_set_vardict (LldpAttrData *pdata, LldpAttrId attr_id, GVariant *variant)
+{
+
+	nm_assert (pdata);
+	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_VARDICT);
+
+	pdata = &pdata[attr_id];
+
+	/* we ignore duplicate fields silently */
+	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE) {
+		if (g_variant_is_floating (variant))
+			g_variant_unref (variant);
+		return;
+	}
+
+	pdata->attr_type = LLDP_ATTR_TYPE_VARDICT;
+	pdata->v_variant = g_variant_ref_sink (variant);
+}
+
+static void
+_lldp_attr_add_vardict (LldpAttrData *pdata, LldpAttrId attr_id, GVariant *variant)
+{
+	nm_assert (pdata);
+	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS);
+
+	g_variant_ref_sink (variant);
+	pdata = &pdata[attr_id];
+
+	if (pdata->attr_type == LLDP_ATTR_TYPE_NONE) {
+		c_list_init (&pdata->v_variant_list);
+		pdata->attr_type = LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS;
+	} else
+		nm_assert (pdata->attr_type == LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS);
+
+	c_list_link_tail (&pdata->v_variant_list, &nm_c_list_elem_new_stale (variant)->lst);
+}
+
 /*****************************************************************************/
 
 static guint
@@ -312,13 +338,28 @@ static void
 lldp_neighbor_free (LldpNeighbor *neighbor)
 {
 	LldpAttrId attr_id;
+	LldpAttrType attr_type;
 
 	if (neighbor) {
 		g_free (neighbor->chassis_id);
 		g_free (neighbor->port_id);
-		for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
-			if (neighbor->attrs[attr_id].attr_type == LLDP_ATTR_TYPE_STRING)
+		for (attr_id = 0; attr_id < _LLDP_ATTR_ID_COUNT; attr_id++) {
+			attr_type = neighbor->attrs[attr_id].attr_type;
+
+			switch (attr_type) {
+			case LLDP_ATTR_TYPE_STRING:
 				g_free (neighbor->attrs[attr_id].v_string);
+				break;
+			case LLDP_ATTR_TYPE_VARDICT:
+				g_variant_unref (neighbor->attrs[attr_id].v_variant);
+				break;
+			case LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS:
+				nm_c_list_elem_free_all (&neighbor->attrs[attr_id].v_variant_list,
+				                         (GDestroyNotify) g_variant_unref);
+				break;
+			default:
+				;
+			}
 		}
 		g_clear_pointer (&neighbor->variant, g_variant_unref);
 		g_slice_free (LldpNeighbor, neighbor);
@@ -346,7 +387,7 @@ lldp_neighbor_equal (LldpNeighbor *a, LldpNeighbor *b)
 	    || !nm_streq0 (a->port_id, b->port_id))
 		return FALSE;
 
-	for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
+	for (attr_id = 0; attr_id < _LLDP_ATTR_ID_COUNT; attr_id++) {
 		if (a->attrs[attr_id].attr_type != b->attrs[attr_id].attr_type)
 			return FALSE;
 		switch (a->attrs[attr_id].attr_type) {
@@ -367,6 +408,75 @@ lldp_neighbor_equal (LldpNeighbor *a, LldpNeighbor *b)
 	return TRUE;
 }
 
+static GVariant *
+parse_management_address_tlv (uint8_t *data, gsize len)
+{
+	GVariantDict dict;
+	GVariant *variant;
+	gsize addr_len, oid_len;
+
+	/* 802.1AB-2009 - Figure 8-11
+	 *
+	 * - TLV type / length        (2 bytes)
+	 * - address string length    (1 byte)
+	 * - address subtype          (1 byte)
+	 * - address                  (1 to 31 bytes)
+	 * - interface number subtype (1 byte)
+	 * - interface number         (4 bytes)
+	 * - OID string length        (1 byte)
+	 * - OID                      (0 to 128 bytes)
+	 */
+
+	if (len < 11)
+		goto err;
+
+	nm_assert ((data[0] >> 1) == SD_LLDP_TYPE_MGMT_ADDRESS);
+	nm_assert ((((data[0] & 1) << 8) + data[1]) + 2 == len);
+
+	data += 2;
+	len -= 2;
+	addr_len = *data; /* length of (address subtype + address) */
+
+	if (addr_len < 2 || addr_len > 32)
+		goto err;
+	if (len < (  1         /* address stringth length */
+	           + addr_len  /* address subtype + address */
+	           + 5         /* interface */
+	           + 1))       /* oid */
+		goto err;
+
+	g_variant_dict_init (&dict, NULL);
+
+	data++;
+	len--;
+	g_variant_dict_insert (&dict, "address-subtype", "u", (guint32) *data);
+	variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, data + 1, addr_len - 1, 1);
+	g_variant_dict_insert_value (&dict, "address", variant);
+
+	data += addr_len;
+	len -= addr_len;
+	g_variant_dict_insert (&dict, "interface-number-subtype", "u", (guint32) *data);
+
+	data++;
+	len--;
+	g_variant_dict_insert (&dict, "interface-number", "u", unaligned_read_be32 (data));
+
+	data += 4;
+	len -= 4;
+	oid_len = *data;
+
+	if (len < (1 + oid_len))
+		goto err;
+
+	data++;
+	variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, data, oid_len, 1);
+	g_variant_dict_insert_value (&dict, "object-id", variant);
+	return g_variant_dict_end (&dict);
+err:
+	g_variant_dict_clear (&dict);
+	return NULL;
+}
+
 static LldpNeighbor *
 lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 {
@@ -468,7 +578,29 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	}
 	do {
 		guint8 oui[3];
-		guint8 subtype;
+		guint8 type, subtype;
+		GVariant *variant;
+
+		if (sd_lldp_neighbor_tlv_get_type (neighbor_sd, &type) < 0)
+			continue;
+
+		if (sd_lldp_neighbor_tlv_get_raw (neighbor_sd, (void *) &data8, &len) < 0)
+			continue;
+
+		switch (type) {
+		case SD_LLDP_TYPE_MGMT_ADDRESS:
+			variant = parse_management_address_tlv (data8, len);
+			if (variant) {
+				_lldp_attr_add_vardict (neigh->attrs,
+				                        LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,
+				                        variant);
+			}
+			continue;
+		case SD_LLDP_TYPE_PRIVATE:
+			break;
+		default:
+			continue;
+		}
 
 		r = sd_lldp_neighbor_tlv_get_oui (neighbor_sd, oui, &subtype);
 		if (r < 0) {
@@ -479,18 +611,12 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 			goto out;
 		}
 
-		if (!(   memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0
-		      && NM_IN_SET (subtype,
-		                    SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID,
-		                    SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID,
-		                    SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME)))
-			continue;
-
-		if (sd_lldp_neighbor_tlv_get_raw (neighbor_sd, (void *) &data8, &len) < 0)
+		if (   memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) != 0
+		    && memcmp (oui, SD_LLDP_OUI_802_3, sizeof (oui)) != 0)
 			continue;
 
 		/* skip over leading TLV, OUI and subtype */
-#ifdef WITH_MORE_ASSERTS
+#if NM_MORE_ASSERTS > 5
 		{
 			guint8 check_hdr[] = {
 				0xfe | (((len - 2) >> 8) & 0x01), ((len - 2) & 0xFF),
@@ -507,25 +633,37 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 		data8 += 6;
 		len -= 6;
 
-		/*if (memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0)*/
-		{
+		if (memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0) {
+			GVariantDict dict;
+
 			switch (subtype) {
 			case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID:
 				if (len != 2)
 					continue;
 				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PVID,
-				                       _access_uint16 (data8));
+				                       unaligned_read_be16 (data8));
 				break;
 			case SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID:
 				if (len != 3)
 					continue;
 				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,
-				                       _access_uint8 (&data8[0]));
+				                       data8[0]);
 				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID,
-				                       _access_uint16 (&data8[1]));
+				                       unaligned_read_be16 (&data8[1]));
+
+				g_variant_dict_init (&dict, NULL);
+				g_variant_dict_insert (&dict, "ppvid", "u", (guint32) unaligned_read_be16 (&data8[1]));
+				g_variant_dict_insert (&dict, "flags", "u", (guint32) data8[0]);
+
+				_lldp_attr_add_vardict (neigh->attrs,
+				                        LLDP_ATTR_ID_IEEE_802_1_PPVIDS,
+				                        g_variant_dict_end (&dict));
 				break;
 			case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME: {
 				int l;
+				guint32 vid;
+				const char *name;
+				char *name_to_free;
 
 				if (len <= 3)
 					continue;
@@ -536,14 +674,62 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 				if (l > 32)
 					continue;
 
-				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID,
-				                       _access_uint16 (&data8[0]));
-				_lldp_attr_set_str_ptr (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
-				                        &data8[3], l);
+				name = nm_utils_buf_utf8safe_escape (&data8[3], l, 0, &name_to_free);
+				vid = unaligned_read_be16 (&data8[0]);
+
+				g_variant_dict_init (&dict, NULL);
+				g_variant_dict_insert (&dict, "vid", "u", vid);
+				g_variant_dict_insert (&dict, "name", "s", name);
+
+				_lldp_attr_add_vardict (neigh->attrs,
+				                        LLDP_ATTR_ID_IEEE_802_1_VLANS,
+				                        g_variant_dict_end (&dict));
+
+				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID, vid);
+				_lldp_attr_take_str_ptr (neigh->attrs,
+				                         LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
+				                         name_to_free ?: g_strdup (name));
 				break;
 			}
 			default:
-				g_assert_not_reached ();
+				continue;
+			}
+		} else if (memcmp (oui, SD_LLDP_OUI_802_3, sizeof (oui)) == 0) {
+			GVariantDict dict;
+
+			switch (subtype) {
+			case SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS:
+				if (len != 5)
+					continue;
+
+				g_variant_dict_init (&dict, NULL);
+				g_variant_dict_insert (&dict, "autoneg", "u", (guint32) data8[0]);
+				g_variant_dict_insert (&dict, "pmd-autoneg-cap", "u", (guint32) unaligned_read_be16 (&data8[1]));
+				g_variant_dict_insert (&dict, "operational-mau-type", "u", (guint32) unaligned_read_be16 (&data8[3]));
+
+				_lldp_attr_set_vardict (neigh->attrs,
+				                        LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,
+				                        g_variant_dict_end (&dict));
+				break;
+			case SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI:
+				if (len != 3)
+					continue;
+
+				g_variant_dict_init (&dict, NULL);
+				g_variant_dict_insert (&dict, "mdi-power-support", "u", (guint32) data8[0]);
+				g_variant_dict_insert (&dict, "pse-power-pair", "u", (guint32) data8[1]);
+				g_variant_dict_insert (&dict, "power-class", "u", (guint32) data8[2]);
+
+				_lldp_attr_set_vardict (neigh->attrs,
+				                        LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI,
+				                        g_variant_dict_end (&dict));
+				break;
+			case SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE:
+				if (len != 2)
+					continue;
+				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,
+				                       unaligned_read_be16 (data8));
+				break;
 			}
 		}
 	} while (sd_lldp_neighbor_tlv_next (neighbor_sd) > 0);
@@ -593,7 +779,7 @@ lldp_neighbor_to_variant (LldpNeighbor *neigh)
 		                       g_variant_new_string (dest_str));
 	}
 
-	for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
+	for (attr_id = 0; attr_id < _LLDP_ATTR_ID_COUNT; attr_id++) {
 		const LldpAttrData *data = &neigh->attrs[attr_id];
 
 		nm_assert (NM_IN_SET (data->attr_type, _lldp_attr_id_to_type (attr_id), LLDP_ATTR_TYPE_NONE));
@@ -608,7 +794,26 @@ lldp_neighbor_to_variant (LldpNeighbor *neigh)
 			                       _lldp_attr_id_to_name (attr_id),
 			                       g_variant_new_string (data->v_string));
 			break;
-		default:
+		case LLDP_ATTR_TYPE_VARDICT:
+			g_variant_builder_add (&builder, "{sv}",
+			                       _lldp_attr_id_to_name (attr_id),
+			                       data->v_variant);
+			break;
+		case LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS: {
+			NMCListElem *elem;
+			GVariantBuilder builder2;
+
+			g_variant_builder_init (&builder2, G_VARIANT_TYPE ("aa{sv}"));
+
+			c_list_for_each_entry (elem, &data->v_variant_list, lst)
+				g_variant_builder_add_value (&builder2, elem->data);
+
+			g_variant_builder_add (&builder, "{sv}",
+			                       _lldp_attr_id_to_name (attr_id),
+			                       g_variant_builder_end (&builder2));
+			break;
+		}
+		case LLDP_ATTR_TYPE_NONE:
 			break;
 		}
 	}
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index b96eba68..35eb739f 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -20,6 +20,7 @@
 #include "nm-default.h"
 
 #include "nm-device-ovs-port.h"
+#include "nm-device-ovs-interface.h"
 #include "nm-ovsdb.h"
 
 #include "devices/nm-device-private.h"
@@ -141,6 +142,11 @@ release_slave (NMDevice *device, NMDevice *slave, gboolean configure)
 {
 	nm_ovsdb_del_interface (nm_ovsdb_get (), nm_device_get_iface (slave),
 	                        del_iface_cb, g_object_ref (slave));
+
+	/* Open VSwitch is going to delete this one. We must ignore what happens
+	 * next with the interface. */
+	if (NM_IS_DEVICE_OVS_INTERFACE (slave))
+		nm_device_update_from_platform_link (slave, NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/devices/ovs/nm-ovs-factory.c
index 766c650b..2124b2a0 100644
--- a/src/devices/ovs/nm-ovs-factory.c
+++ b/src/devices/ovs/nm-ovs-factory.c
@@ -132,7 +132,7 @@ ovsdb_device_removed (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type
 	    && device_state < NM_DEVICE_STATE_DEACTIVATING) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_DEACTIVATING,
-		                         NM_DEVICE_STATE_REASON_REMOVED);
+		                         NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
 	} else if (device_state == NM_DEVICE_STATE_UNMANAGED) {
 		nm_device_unrealize (device, TRUE, NULL);
 	}
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 9d73c3ac..5b50f840 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -24,7 +24,7 @@
 #include <gmodule.h>
 #include <gio/gunixsocketaddress.h>
 
-#include "nm-utils/nm-jansson.h"
+#include "nm-glib-aux/nm-jansson.h"
 #include "devices/nm-device.h"
 #include "platform/nm-platform.h"
 #include "nm-core-internal.h"
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 38a6dd8c..287f4d1b 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -30,14 +30,14 @@
 #include <teamdctl.h>
 #include <stdlib.h>
 
-#include "nm-utils/nm-jansson.h"
+#include "nm-glib-aux/nm-jansson.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
 #include "nm-config.h"
 #include "nm-core-internal.h"
 #include "nm-ip4-config.h"
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceTeam);
@@ -408,8 +408,10 @@ teamd_dbus_appeared (GDBusConnection *connection,
 			success = teamd_read_config (device);
 		if (success)
 			nm_device_activate_schedule_stage2_device_config (device);
-		else if (!nm_device_sys_iface_state_is_external_or_assume (device))
+		else if (!nm_device_sys_iface_state_is_external_or_assume (device)) {
+			teamd_cleanup (device, TRUE);
 			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		}
 	}
 }
 
diff --git a/src/devices/tests/test-acd.c b/src/devices/tests/test-acd.c
index aff71825..8b52ee2c 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/devices/tests/test-acd.c
@@ -111,8 +111,8 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, gpointer user_data)
 static void
 test_acd_common (test_fixture *fixture, TestInfo *info)
 {
-	NMAcdManager *manager;
-	GMainLoop *loop;
+	nm_auto_free_acdmgr NMAcdManager *manager = NULL;
+	nm_auto_unref_gmainloop GMainLoop *loop = NULL;
 	int i;
 	const guint WAIT_TIME_OPTIMISTIC = 50;
 	guint wait_time;
@@ -120,6 +120,7 @@ test_acd_common (test_fixture *fixture, TestInfo *info)
 		.probe_terminated_callback = acd_manager_probe_terminated,
 		.user_data_destroy         = (GDestroyNotify) g_main_loop_unref,
 	};
+	int r;
 
 	if (_skip_acd_test ())
 		return;
@@ -131,8 +132,10 @@ test_acd_common (test_fixture *fixture, TestInfo *info)
 	wait_time = WAIT_TIME_OPTIMISTIC;
 again:
 
+	nm_clear_pointer (&loop, g_main_loop_unref);
 	loop = g_main_loop_new (NULL, FALSE);
 
+	nm_clear_pointer (&manager, nm_acd_manager_free);
 	manager = nm_acd_manager_new (fixture->ifindex0,
 	                              fixture->hwaddr0,
 	                              fixture->hwaddr0_len,
@@ -148,9 +151,10 @@ again:
 		                        24, 0, 3600, 1800, 0, NULL);
 	}
 
-	g_assert (nm_acd_manager_start_probe (manager, wait_time));
+	r = nm_acd_manager_start_probe (manager, wait_time);
+	g_assert_cmpint (r, ==, 0);
+
 	g_assert (nmtst_main_loop_run (loop, 2000));
-	g_main_loop_unref (loop);
 
 	for (i = 0; info->addresses[i]; i++) {
 		gboolean val;
@@ -164,7 +168,6 @@ again:
 			/* probably we just had a glitch and the system took longer than
 			 * expected. Re-verify with a large timeout this time. */
 			wait_time = 1000;
-			nm_clear_pointer (&manager, nm_acd_manager_free);
 			goto again;
 		}
 
@@ -172,8 +175,6 @@ again:
 		         i, nm_utils_inet4_ntop (info->addresses[i], sbuf),
 		         info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
 	}
-
-	nm_acd_manager_free (manager);
 }
 
 static void
@@ -199,8 +200,9 @@ test_acd_probe_2 (test_fixture *fixture, gconstpointer user_data)
 static void
 test_acd_announce (test_fixture *fixture, gconstpointer user_data)
 {
-	NMAcdManager *manager;
-	GMainLoop *loop;
+	nm_auto_free_acdmgr NMAcdManager *manager = NULL;
+	nm_auto_unref_gmainloop GMainLoop *loop = NULL;
+	int r;
 
 	if (_skip_acd_test ())
 		return;
@@ -216,11 +218,9 @@ test_acd_announce (test_fixture *fixture, gconstpointer user_data)
 	g_assert (nm_acd_manager_add_address (manager, ADDR2));
 
 	loop = g_main_loop_new (NULL, FALSE);
-	nm_acd_manager_announce_addresses (manager);
+	r = nm_acd_manager_announce_addresses (manager);
+	g_assert_cmpint (r, ==, 0);
 	g_assert (!nmtst_main_loop_run (loop, 200));
-	g_main_loop_unref (loop);
-
-	nm_acd_manager_free (manager);
 }
 
 static void
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index 7227d082..7b135f58 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -231,8 +231,10 @@ TEST_RECV_FRAME_DEFINE (_test_recv_data1_frame0,
 static void
 _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 {
-	GVariant *neighbors, *attr;
+	GVariant *neighbors, *attr, *child;
 	gs_unref_variant GVariant *neighbor = NULL;
+	guint v_uint = 0;
+	const char *v_str = NULL;
 
 	neighbors = nm_lldp_listener_get_neighbors (listener);
 	nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}"));
@@ -242,7 +244,7 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	                              SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:30:F9:AD:A0",
 	                              SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/1");
 	g_assert (neighbor);
-	g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 10);
+	g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 16);
 
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING);
 	nmtst_assert_variant_string (attr, NM_LLDP_DEST_NEAREST_BRIDGE);
@@ -270,11 +272,49 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	nmtst_assert_variant_uint32 (attr, 20);
 	nm_clear_g_variant (&attr);
 
-	/* unsupported: Management Address */
-	/* unsupported: IEEE 802.3 - Power Via MDI */
-	/* unsupported: IEEE 802.3 - MAC/PHY Configuration/Status */
+	/* Management Address */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_MANAGEMENT_ADDRESSES, G_VARIANT_TYPE ("aa{sv}"));
+	g_assert (attr);
+	g_assert_cmpuint (g_variant_n_children (attr), ==, 1);
+	child = g_variant_get_child_value (attr, 0);
+	g_assert (child);
+	g_variant_lookup (child, "interface-number", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 1001);
+	g_variant_lookup (child, "interface-number-subtype", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 2);
+	g_variant_lookup (child, "address-subtype", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 6);
+	nm_clear_g_variant (&child);
+	nm_clear_g_variant (&attr);
+
+	/* IEEE 802.3 - Power Via MDI */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI, G_VARIANT_TYPE_VARDICT);
+	g_assert (attr);
+	g_variant_lookup (attr, "mdi-power-support", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 7);
+	g_variant_lookup (attr, "pse-power-pair", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 1);
+	g_variant_lookup (attr, "power-class", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 0);
+	nm_clear_g_variant (&attr);
+
+	/* IEEE 802.3 - MAC/PHY Configuration/Status */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF, G_VARIANT_TYPE_VARDICT);
+	g_assert (attr);
+	g_variant_lookup (attr, "autoneg", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 3);
+	g_variant_lookup (attr, "pmd-autoneg-cap", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 0x6c00);
+	g_variant_lookup (attr, "operational-mau-type", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 16);
+	nm_clear_g_variant (&attr);
+
 	/* unsupported: IEEE 802.3 - Link Aggregation */
-	/* unsupported: IEEE 802.3 - Maximum Frame Size*/
+
+	/* Maximum Frame Size */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE, G_VARIANT_TYPE_UINT32);
+	nmtst_assert_variant_uint32 (attr, 1522);
+	nm_clear_g_variant (&attr);
 
 	/* IEEE 802.1 - Port VLAN ID */
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PVID, G_VARIANT_TYPE_UINT32);
@@ -289,6 +329,18 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	nmtst_assert_variant_uint32 (attr, 1);
 	nm_clear_g_variant (&attr);
 
+	/* new PPVID attributes */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVIDS, G_VARIANT_TYPE ("aa{sv}"));
+	g_assert_cmpuint (g_variant_n_children (attr), ==, 1);
+	child = g_variant_get_child_value (attr, 0);
+	g_assert (child);
+	g_variant_lookup (child, "ppvid", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 0);
+	g_variant_lookup (child, "flags", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 1);
+	nm_clear_g_variant (&child);
+	nm_clear_g_variant (&attr);
+
 	/* IEEE 802.1 - VLAN Name */
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, G_VARIANT_TYPE_STRING);
 	nmtst_assert_variant_string (attr, "v2-0488-03-0505");
@@ -297,6 +349,18 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	nmtst_assert_variant_uint32 (attr, 488);
 	nm_clear_g_variant (&attr);
 
+	/* new VLAN attributes */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VLANS, G_VARIANT_TYPE ("aa{sv}"));
+	g_assert_cmpuint (g_variant_n_children (attr), ==, 1);
+	child = g_variant_get_child_value (attr, 0);
+	g_assert (child);
+	g_variant_lookup (child, "vid", "u", &v_uint);
+	g_assert_cmpint (v_uint, ==, 488);
+	g_variant_lookup (child, "name", "&s", &v_str);
+	g_assert_cmpstr (v_str, ==, "v2-0488-03-0505");
+	nm_clear_g_variant (&child);
+	nm_clear_g_variant (&attr);
+
 	/* unsupported: IEEE 802.1 - Protocol Identity */
 }
 
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index dcc161d2..da7d0685 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -22,7 +22,7 @@
 
 #include "nm-device-iwd.h"
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
 #include "nm-utils.h"
@@ -39,7 +39,7 @@
 #include "nm-config.h"
 #include "nm-iwd-manager.h"
 #include "nm-dbus-manager.h"
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceIwd);
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 64869672..3a58c620 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -28,7 +28,7 @@
 
 #include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
 #include "nm-dbus-manager.h"
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index d668f0d8..494fca32 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -29,7 +29,7 @@
 #include "nm-manager.h"
 #include "nm-device-iwd.h"
 #include "nm-wifi-utils.h"
-#include "nm-utils/nm-random-utils.h"
+#include "nm-glib-aux/nm-random-utils.h"
 #include "settings/nm-settings.h"
 
 /*****************************************************************************/
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 1cb549b0..ffdc61a6 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -679,19 +679,6 @@ complete_connection (NMModem *_self,
 	}
 
 	if (MODEM_CAPS_3GPP (modem_caps)) {
-		NMSettingGsm *s_gsm;
-
-		s_gsm = nm_connection_get_setting_gsm (connection);
-		if (!s_gsm) {
-			/* Need a GSM setting at least */
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_MISSING_SETTING,
-			                     _("GSM mobile broadband connection requires a 'gsm' setting"));
-			g_prefix_error (error, "%s: ", NM_SETTING_GSM_SETTING_NAME);
-			return FALSE;
-		}
-
 		nm_utils_complete_generic (NM_PLATFORM_GET,
 		                           connection,
 		                           NM_SETTING_GSM_SETTING_NAME,
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index fac14d69..4be089c0 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -32,7 +32,7 @@
 #define sd_booted() FALSE
 #endif
 
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 #include "nm-modem.h"
 #include "nm-modem-broadband.h"
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 2217f2a2..17794229 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -885,7 +885,7 @@ nm_modem_get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source)
 		}
 
 		property_name = NM_IS_SETTING_GSM (setting) ? "gsm.mtu" : "cdma.mtu";
-		mtu_default = nm_device_get_configured_mtu_from_connection_default (self, property_name);
+		mtu_default = nm_device_get_configured_mtu_from_connection_default (self, property_name, G_MAXUINT32);
 		if (mtu_default >= 0) {
 			*out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
 			return (guint32) mtu_default;
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
index a5dd3151..c1f28be0 100644
--- a/src/dhcp/meson.build
+++ b/src/dhcp/meson.build
@@ -8,7 +8,7 @@ cflags = [
 executable(
   name,
   name + '.c',
-  dependencies: nm_core_dep,
+  dependencies: libnm_core_dep,
   c_args: cflags,
   link_args: ldflags_linker_script_binary,
   link_depends: linker_script_binary,
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index a4fccce0..d494eff0 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -28,8 +28,8 @@
 #include <stdlib.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils/nm-dedup-multi.h"
-#include "nm-utils/nm-random-utils.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-random-utils.h"
 
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index cbd706fa..85ca3704 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -25,7 +25,7 @@
 #include <arpa/inet.h>
 #include <net/if.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "nm-dhcp-utils.h"
 #include "nm-ip4-config.h"
@@ -50,10 +50,10 @@
 static void
 add_request (GPtrArray *array, const char *item)
 {
-	int i;
+	guint i;
 
 	for (i = 0; i < array->len; i++) {
-		if (!strcmp (g_ptr_array_index (array, i), item))
+		if (nm_streq (array->pdata[i], item))
 			return;
 	}
 	g_ptr_array_add (array, g_strdup (item));
@@ -62,37 +62,48 @@ add_request (GPtrArray *array, const char *item)
 static gboolean
 grab_request_options (GPtrArray *store, const char* line)
 {
-	char **areq, **aiter;
-	gboolean end = FALSE;
+	gs_free const char **line_v = NULL;
+	gsize i;
 
 	/* Grab each 'request' or 'also request'  option and save for later */
-	areq = g_strsplit_set (line, "\t ,", -1);
-	for (aiter = areq; aiter && *aiter; aiter++) {
-		if (!strlen (g_strstrip (*aiter)))
-			continue;
+	line_v = nm_utils_strsplit_set (line, "\t ,");
+	for (i = 0; line_v && line_v[i]; i++) {
+		const char *ss = nm_str_skip_leading_spaces (line_v[i]);
+		gsize l;
+		gboolean end = FALSE;
 
-		if (*aiter[0] == ';') {
+		if (!ss[0])
+			continue;
+		if (ss[0] == ';') {
 			/* all done */
-			end = TRUE;
-			break;
+			return TRUE;
 		}
 
-		if (!g_ascii_isalnum ((*aiter)[0]))
+		if (!g_ascii_isalnum (ss[0]))
 			continue;
 
-		if ((*aiter)[strlen (*aiter) - 1] == ';') {
+		l = strlen (ss);
+
+		while (   l > 0
+		       && g_ascii_isspace (ss[l - 1])) {
+			((char *) ss)[l - 1] = '\0';
+			l--;
+		}
+		if (   l > 0
+		    && ss[l - 1] == ';') {
 			/* Remove the EOL marker */
-			(*aiter)[strlen (*aiter) - 1] = '\0';
+			((char *) ss)[l - 1] = '\0';
 			end = TRUE;
 		}
 
-		add_request (store, *aiter);
-	}
+		if (ss[0])
+			add_request (store, ss);
 
-	if (areq)
-		g_strfreev (areq);
+		if (end)
+			return TRUE;
+	}
 
-	return end;
+	return FALSE;
 }
 
 static void
@@ -278,8 +289,9 @@ nm_dhcp_dhclient_create_config (const char *interface,
                                 const char *orig_contents,
                                 GBytes **out_new_client_id)
 {
-	GString *new_contents;
-	GPtrArray *fqdn_opts, *reqs;
+	nm_auto_free_gstring GString *new_contents = NULL;
+	gs_unref_ptrarray GPtrArray *fqdn_opts = NULL;
+	gs_unref_ptrarray GPtrArray *reqs = NULL;
 	gboolean reset_reqlist = FALSE;
 	int i;
 
@@ -288,11 +300,11 @@ nm_dhcp_dhclient_create_config (const char *interface,
 	nm_assert (!out_new_client_id || !*out_new_client_id);
 
 	new_contents = g_string_new (_("# Created by NetworkManager\n"));
-	fqdn_opts = g_ptr_array_sized_new (5);
 	reqs = g_ptr_array_new_full (5, g_free);
 
 	if (orig_contents) {
-		char **lines, **line;
+		gs_free const char **lines = NULL;
+		gsize line_i;
 		int nest = 0;
 		gboolean in_alsoreq = FALSE;
 		gboolean in_req = FALSE;
@@ -301,19 +313,23 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		g_string_append_printf (new_contents, _("# Merged from %s\n\n"), orig_path);
 		intf[0] = '\0';
 
-		lines = g_strsplit_set (orig_contents, "\n\r", 0);
-		for (line = lines; lines && *line; line++) {
-			char *p = *line;
+		lines = nm_utils_strsplit_set (orig_contents, "\n\r");
+		for (line_i = 0; lines && lines[line_i]; line_i++) {
+			const char *line = nm_str_skip_leading_spaces (lines[line_i]);
+			const char *p;
 
-			if (!strlen (g_strstrip (p)))
+			if (line[0] == '\0')
 				continue;
 
+			g_strchomp ((char *) line);
+
+			p = line;
 			if (in_req) {
 				/* pass */
 			} else if (strchr (p, '{')) {
 				nest++;
 				if (   !intf[0]
-				    && g_str_has_prefix (p, "interface"))
+				    && NM_STR_HAS_PREFIX (p, "interface"))
 					if (read_interface (p, intf, sizeof (intf)))
 						continue;
 			} else if (strchr (p, '}')) {
@@ -363,6 +379,8 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			 * default ones set by NM, add them later
 			 */
 			if (!strncmp (p, FQDN_TAG_PREFIX, NM_STRLEN (FQDN_TAG_PREFIX))) {
+				if (!fqdn_opts)
+					fqdn_opts = g_ptr_array_new_full (5, g_free);
 				g_ptr_array_add (fqdn_opts, g_strdup (p + NM_STRLEN (FQDN_TAG_PREFIX)));
 				continue;
 			}
@@ -397,12 +415,9 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			}
 
 			/* Existing configuration line is OK, add it to new configuration */
-			g_string_append (new_contents, *line);
+			g_string_append (new_contents, line);
 			g_string_append_c (new_contents, '\n');
 		}
-
-		if (lines)
-			g_strfreev (lines);
 	} else
 		g_string_append_c (new_contents, '\n');
 
@@ -436,17 +451,16 @@ nm_dhcp_dhclient_create_config (const char *interface,
 	/* And add it to the dhclient configuration */
 	for (i = 0; i < reqs->len; i++)
 		g_string_append_printf (new_contents, "also request %s;\n", (char *) reqs->pdata[i]);
-	g_ptr_array_free (reqs, TRUE);
 
-	for (i = 0; i < fqdn_opts->len; i++) {
-		char *t = g_ptr_array_index (fqdn_opts, i);
+	if (fqdn_opts) {
+		for (i = 0; i < fqdn_opts->len; i++) {
+			const char *t = fqdn_opts->pdata[i];
 
-		if (i == 0)
-			g_string_append_printf (new_contents, "\n# FQDN options from %s\n", orig_path);
-		g_string_append_printf (new_contents, FQDN_TAG_PREFIX "%s\n", t);
-		g_free (t);
+			if (i == 0)
+				g_string_append_printf (new_contents, "\n# FQDN options from %s\n", orig_path);
+			g_string_append_printf (new_contents, FQDN_TAG_PREFIX "%s\n", t);
+		}
 	}
-	g_ptr_array_free (fqdn_opts, TRUE);
 
 	g_string_append_c (new_contents, '\n');
 
@@ -458,7 +472,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		                        interface, anycast_addr);
 	}
 
-	return g_string_free (new_contents, FALSE);
+	return g_string_free (g_steal_pointer (&new_contents), FALSE);
 }
 
 /* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
@@ -553,9 +567,9 @@ error:
 GBytes *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
-	GBytes *duid = NULL;
-	char *contents;
-	char **line, **split, *p, *e;
+	gs_free char *contents = NULL;
+	gs_free const char **contents_v = NULL;
+	gsize i;
 
 	if (!g_file_test (leasefile, G_FILE_TEST_EXISTS))
 		return NULL;
@@ -563,25 +577,29 @@ nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 	if (!g_file_get_contents (leasefile, &contents, NULL, error))
 		return NULL;
 
-	split = g_strsplit_set (contents, "\n\r", -1);
-	for (line = split; line && *line && (duid == NULL); line++) {
-		p = g_strstrip (*line);
-		if (g_str_has_prefix (p, DUID_PREFIX)) {
-			p += strlen (DUID_PREFIX);
+	contents_v = nm_utils_strsplit_set (contents, "\n\r");
+	for (i = 0; contents_v && contents_v[i]; i++) {
+		const char *p = nm_str_skip_leading_spaces (contents_v[i]);
+		GBytes *duid;
 
-			/* look for trailing "; */
-			e = p + strlen (p) - 2;
-			if (strcmp (e, "\";") != 0)
-				continue;
-			*e = '\0';
+		if (!NM_STR_HAS_PREFIX (p, DUID_PREFIX))
+			continue;
 
-			duid = nm_dhcp_dhclient_unescape_duid (p);
-		}
+		p += NM_STRLEN (DUID_PREFIX);
+
+		g_strchomp ((char *) p);
+
+		if (!NM_STR_HAS_SUFFIX (p, "\";"))
+			continue;
+
+		((char *) p)[strlen (p) - 2] = '\0';
+
+		duid = nm_dhcp_dhclient_unescape_duid (p);
+		if (duid)
+			return duid;
 	}
-	g_free (contents);
-	g_strfreev (split);
 
-	return duid;
+	return NULL;
 }
 
 gboolean
@@ -590,14 +608,12 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
                             GError **error)
 {
 	gs_free char *escaped_duid = NULL;
-	gs_strfreev char **lines = NULL;
-	char **iter, *l;
-	GString *s;
-	gboolean success;
+	gs_free const char **lines = NULL;
+	nm_auto_free_gstring GString *s = NULL;
+	const char *const*iter;
 	gsize len = 0;
 
 	g_return_val_if_fail (leasefile != NULL, FALSE);
-
 	if (!duid) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
 		                            "missing duid");
@@ -605,19 +621,17 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 	}
 
 	escaped_duid = nm_dhcp_dhclient_escape_duid (duid);
-	g_return_val_if_fail (escaped_duid != NULL, FALSE);
+	nm_assert (escaped_duid);
 
 	if (g_file_test (leasefile, G_FILE_TEST_EXISTS)) {
-		char *contents = NULL;
+		gs_free char *contents = NULL;
 
 		if (!g_file_get_contents (leasefile, &contents, &len, error)) {
 			g_prefix_error (error, "failed to read lease file %s: ", leasefile);
 			return FALSE;
 		}
 
-		g_assert (contents);
-		lines = g_strsplit_set (contents, "\n\r", -1);
-		g_free (contents);
+		lines = nm_utils_strsplit_set_with_empty (contents, "\n\r");
 	}
 
 	s = g_string_sized_new (len + 50);
@@ -625,37 +639,38 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 
 	/* Preserve existing leasefile contents */
 	if (lines) {
-		for (iter = lines; iter && *iter; iter++) {
-			l = *iter;
-			while (g_ascii_isspace (*l))
-				l++;
+		for (iter = lines; *iter; iter++) {
+			const char *str = *iter;
+			const char *l;
+
 			/* If we find an uncommented DUID in the file, check if
 			 * equal to the one we are going to write: if so, no need
 			 * to update the lease file, otherwise skip the old DUID.
 			 */
+			l = nm_str_skip_leading_spaces (str);
 			if (g_str_has_prefix (l, DUID_PREFIX)) {
 				gs_strfreev char **split = NULL;
 
 				split = g_strsplit (l, "\"", -1);
-				if (nm_streq0 (split[1], escaped_duid)) {
-					g_string_free (s, TRUE);
+				if (   split[0]
+				    && nm_streq0 (split[1], escaped_duid))
 					return TRUE;
-				}
+
 				continue;
 			}
 
-			if (*iter[0])
-				g_string_append (s, *iter);
+			if (str)
+				g_string_append (s, str);
 			/* avoid to add an extra '\n' at the end of file */
 			if ((iter[1]) != NULL)
 				g_string_append_c (s, '\n');
 		}
 	}
 
-	success = g_file_set_contents (leasefile, s->str, -1, error);
-	if (!success)
+	if (!g_file_set_contents (leasefile, s->str, -1, error)) {
 		g_prefix_error (error, "failed to set DUID in lease file %s: ", leasefile);
+		return FALSE;
+	}
 
-	g_string_free (s, TRUE);
-	return success;
+	return TRUE;
 }
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index af702cb4..b655a1eb 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -36,7 +36,7 @@
 #include <arpa/inet.h>
 #include <ctype.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "nm-utils.h"
 #include "nm-config.h"
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 7063c82c..fe843a2c 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -32,7 +32,7 @@
 #include <fcntl.h>
 #include <stdio.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 70ed8715..1cd5ba27 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -26,8 +26,8 @@
 #include <ctype.h>
 #include <net/if_arp.h>
 
-#include "nm-utils/nm-dedup-multi.h"
-#include "nm-utils/unaligned.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-std-aux/unaligned.h"
 
 #include "nm-utils.h"
 #include "nm-config.h"
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 5227eea7..7aa867c0 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -22,7 +22,7 @@
 #include <unistd.h>
 #include <arpa/inet.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "nm-dhcp-utils.h"
 #include "nm-utils.h"
@@ -100,67 +100,56 @@ out:
 	return have_routes;
 }
 
-static const char **
-process_dhclient_rfc3442_route (const char **octets,
-                                NMPlatformIP4Route *route,
-                                gboolean *success)
+static gboolean
+process_dhclient_rfc3442_route (const char *const**p_octets,
+                                NMPlatformIP4Route *route)
 {
-	const char **o = octets;
-	int addr_len = 0, i = 0;
-	long int tmp;
-	char *next_hop;
-	guint32 tmp_addr;
-
-	*success = FALSE;
-
-	if (!*o)
-		return o; /* no prefix */
-
-	tmp = strtol (*o, NULL, 10);
-	if (tmp < 0 || tmp > 32)  /* 32 == max IP4 prefix length */
-		return o;
-
-	memset (route, 0, sizeof (*route));
-	route->plen = tmp;
+	const char *const*o = *p_octets;
+	gs_free char *next_hop = NULL;
+	int addr_len;
+	int v_plen;
+	in_addr_t tmp_addr;
+	in_addr_t v_network = 0;
+
+	v_plen = _nm_utils_ascii_str_to_int64 (*o, 10, 0, 32, -1);
+	if (v_plen == -1)
+		return FALSE;
 	o++;
 
-	if (tmp > 0)
-		addr_len = ((tmp - 1) / 8) + 1;
+	addr_len =   v_plen > 0
+	           ? ((v_plen - 1) / 8) + 1
+	           : 0;
 
 	/* ensure there's at least the address + next hop left */
-	if (g_strv_length ((char **) o) < addr_len + 4)
-		goto error;
+	if (NM_PTRARRAY_LEN (o) < addr_len + 4)
+		return FALSE;
 
-	if (tmp) {
+	if (v_plen > 0) {
 		const char *addr[4] = { "0", "0", "0", "0" };
-		char *str_addr;
+		gs_free char *str_addr = NULL;
+		int i;
 
 		for (i = 0; i < addr_len; i++)
 			addr[i] = *o++;
 
 		str_addr = g_strjoin (".", addr[0], addr[1], addr[2], addr[3], NULL);
-		if (inet_pton (AF_INET, str_addr, &tmp_addr) <= 0) {
-			g_free (str_addr);
-			goto error;
-		}
-		g_free (str_addr);
-		route->network = nm_utils_ip4_address_clear_host_address (tmp_addr, tmp);
+		if (inet_pton (AF_INET, str_addr, &tmp_addr) <= 0)
+			return FALSE;
+		v_network = nm_utils_ip4_address_clear_host_address (tmp_addr, v_plen);
 	}
 
-	/* Handle next hop */
 	next_hop = g_strjoin (".", o[0], o[1], o[2], o[3], NULL);
-	if (inet_pton (AF_INET, next_hop, &tmp_addr) <= 0) {
-		g_free (next_hop);
-		goto error;
-	}
-	route->gateway = tmp_addr;
-	g_free (next_hop);
-
-	*success = TRUE;
-	return o + 4; /* advance to past the next hop */
+	o += 4;
+	if (inet_pton (AF_INET, next_hop, &tmp_addr) <= 0)
+		return FALSE;
 
-error:
-	return o;
+	*route = (NMPlatformIP4Route) {
+		.network = v_network,
+		.plen    = v_plen,
+		.gateway = tmp_addr,
+	};
+	*p_octets = o;
+	return TRUE;
 }
 
 static gboolean
@@ -171,23 +160,23 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
                                      NMIP4Config *ip4_config,
                                      guint32 *gwaddr)
 {
-	char **octets, **o;
+	gs_free const char **octets = NULL;
+	const char *const*o;
 	gboolean have_routes = FALSE;
-	NMPlatformIP4Route route;
-	gboolean success;
 
-	o = octets = g_strsplit_set (str, " .", 0);
-	if (g_strv_length (octets) < 5) {
+	octets = nm_utils_strsplit_set_with_empty (str, " .");
+	if (NM_PTRARRAY_LEN (octets) < 5) {
 		_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str);
-		goto out;
+		return FALSE;
 	}
 
+	o = octets;
 	while (*o) {
-		memset (&route, 0, sizeof (route));
-		o = (char **) process_dhclient_rfc3442_route ((const char **) o, &route, &success);
-		if (!success) {
+		NMPlatformIP4Route route;
+
+		if (!process_dhclient_rfc3442_route (&o, &route)) {
 			_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes");
-			break;
+			return have_routes;
 		}
 
 		have_routes = TRUE;
@@ -211,8 +200,6 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 		}
 	}
 
-out:
-	g_strfreev (octets);
 	return have_routes;
 }
 
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 55d712b0..1eac3643 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -24,7 +24,7 @@
 #include <arpa/inet.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "NetworkManagerUtils.h"
 #include "dhcp/nm-dhcp-dhclient-utils.h"
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index 240d868c..118082a5 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -23,7 +23,7 @@
 #include <arpa/inet.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-utils.h"
 
 #include "dhcp/nm-dhcp-utils.h"
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index c7c561c4..27c3e710 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -347,13 +347,20 @@ gboolean
 nm_dns_manager_has_systemd_resolved (NMDnsManager *self)
 {
 	NMDnsManagerPrivate *priv;
+	NMDnsSystemdResolved *plugin = NULL;
 
 	g_return_val_if_fail (NM_IS_DNS_MANAGER (self), FALSE);
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 
-	return    priv->sd_resolve_plugin
-	       || NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin);
+	if (priv->sd_resolve_plugin) {
+		nm_assert (!NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin));
+		plugin = NM_DNS_SYSTEMD_RESOLVED (priv->sd_resolve_plugin);
+	} else if (NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin))
+		plugin = NM_DNS_SYSTEMD_RESOLVED (priv->plugin);
+
+	return    plugin
+	       && nm_dns_systemd_resolved_is_running (plugin);
 }
 
 /*****************************************************************************/
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 10797b86..5d262ba3 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -31,7 +31,7 @@
 #include <sys/stat.h>
 #include <linux/if.h>
 
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
@@ -42,9 +42,11 @@
 #include "nm-setting-connection.h"
 #include "devices/nm-device.h"
 #include "NetworkManagerUtils.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 
-#define SYSTEMD_RESOLVED_DBUS_SERVICE "org.freedesktop.resolve1"
-#define SYSTEMD_RESOLVED_DBUS_PATH "/org/freedesktop/resolve1"
+#define SYSTEMD_RESOLVED_DBUS_SERVICE   "org.freedesktop.resolve1"
+#define SYSTEMD_RESOLVED_MANAGER_IFACE  "org.freedesktop.resolve1.Manager"
+#define SYSTEMD_RESOLVED_DBUS_PATH      "/org/freedesktop/resolve1"
 
 /*****************************************************************************/
 
@@ -62,10 +64,14 @@ typedef struct {
 /*****************************************************************************/
 
 typedef struct {
-	GDBusProxy *resolve;
-	GCancellable *init_cancellable;
-	GCancellable *update_cancellable;
+	GDBusConnection *dbus_connection;
+	GCancellable *cancellable;
 	CList request_queue_lst_head;
+	guint name_owner_changed_id;
+	bool send_updates_warn_ratelimited:1;
+	bool try_start_blocked:1;
+	bool dbus_has_owner:1;
+	bool dbus_initied:1;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -121,17 +127,26 @@ _interface_config_free (InterfaceConfig *config)
 static void
 call_done (GObject *source, GAsyncResult *r, gpointer user_data)
 {
-	GVariant *v;
-	GError *error = NULL;
+	gs_unref_variant GVariant *v = NULL;
+	gs_free_error GError *error = NULL;
 	NMDnsSystemdResolved *self = (NMDnsSystemdResolved *) user_data;
+	NMDnsSystemdResolvedPrivate *priv;
+
+	v = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), r, &error);
+	if (   !v
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
 
-	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), r, &error);
 	if (!v) {
-		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			return;
-		_LOGW ("Failed: %s", error->message);
-		g_error_free (error);
-	}
+		if (!priv->send_updates_warn_ratelimited) {
+			priv->send_updates_warn_ratelimited = TRUE;
+			_LOGW ("send-updates failed to update systemd-resolved: %s", error->message);
+		} else
+			_LOGD ("send-updates failed: %s", error->message);
+	} else
+		priv->send_updates_warn_ratelimited = FALSE;
 }
 
 static void
@@ -175,12 +190,11 @@ static void
 free_pending_updates (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	RequestItem *request_item, *request_item_safe;
+	RequestItem *request_item;
 
-	c_list_for_each_entry_safe (request_item,
-	                            request_item_safe,
-	                            &priv->request_queue_lst_head,
-	                            request_queue_lst)
+	while ((request_item = c_list_first_entry (&priv->request_queue_lst_head,
+	                                           RequestItem,
+	                                           request_queue_lst)))
 		_request_item_free (request_item);
 }
 
@@ -267,27 +281,74 @@ static void
 send_updates (NMDnsSystemdResolved *self)
 {
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	RequestItem *request_item, *request_item_safe;
+	RequestItem *request_item;
+
+	if (c_list_is_empty (&priv->request_queue_lst_head)) {
+		/* nothing to do. */
+		return;
+	}
+
+	if (!priv->dbus_initied) {
+		_LOGT ("send-updates: D-Bus connection not ready");
+		return;
+	}
 
-	nm_clear_g_cancellable (&priv->update_cancellable);
+	if (!priv->dbus_has_owner) {
+		if (priv->try_start_blocked) {
+			/* we have no name owner and we already tried poking the service to
+			 * autostart. */
+			_LOGT ("send-updates: no name owner");
+			return;
+		}
 
-	if (!priv->resolve)
+		_LOGT ("send-updates: no name owner. Try start service...");
+		priv->try_start_blocked = TRUE;
+
+		g_dbus_connection_call (priv->dbus_connection,
+		                        DBUS_SERVICE_DBUS,
+		                        DBUS_PATH_DBUS,
+		                        DBUS_INTERFACE_DBUS,
+		                        "StartServiceByName",
+		                        g_variant_new ("(su)", SYSTEMD_RESOLVED_DBUS_SERVICE, 0u),
+		                        G_VARIANT_TYPE ("(u)"),
+		                        G_DBUS_CALL_FLAGS_NONE,
+		                        -1,
+		                        NULL,
+		                        NULL,
+		                        NULL);
 		return;
+	}
 
-	priv->update_cancellable = g_cancellable_new ();
-
-	c_list_for_each_entry_safe (request_item,
-	                            request_item_safe,
-	                            &priv->request_queue_lst_head,
-	                            request_queue_lst) {
-		g_dbus_proxy_call (priv->resolve,
-		                   request_item->operation,
-		                   request_item->argument,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->update_cancellable,
-		                   call_done,
-		                   self);
+	_LOGT ("send-updates: start %lu requests",
+	       c_list_length (&priv->request_queue_lst_head));
+
+	nm_clear_g_cancellable (&priv->cancellable);
+
+	priv->cancellable = g_cancellable_new ();
+
+	while ((request_item = c_list_first_entry (&priv->request_queue_lst_head,
+	                                           RequestItem,
+	                                           request_queue_lst))) {
+		/* Above we explicitly call "StartServiceByName" trying to avoid D-Bus activating systmd-resolved
+		 * multiple times. There is still a race, were we might hit this line although actually
+		 * the service just quit this very moment. In that case, we would try to D-Bus activate the
+		 * service multiple times during each call (something we wanted to avoid).
+		 *
+		 * But this is hard to avoid, because we'd have to check the error failure to detect the reason
+		 * and retry. The race is not critical, because at worst it results in logging a warning
+		 * about failure to start systemd.resolved. */
+		g_dbus_connection_call (priv->dbus_connection,
+		                        SYSTEMD_RESOLVED_DBUS_SERVICE,
+		                        SYSTEMD_RESOLVED_DBUS_PATH,
+		                        SYSTEMD_RESOLVED_MANAGER_IFACE,
+		                        request_item->operation,
+		                        request_item->argument,
+		                        NULL,
+		                        G_DBUS_CALL_FLAGS_NONE,
+		                        -1,
+		                        priv->cancellable,
+		                        call_done,
+		                        self);
 		_request_item_free (request_item);
 	}
 }
@@ -361,28 +422,100 @@ get_name (NMDnsPlugin *plugin)
 /*****************************************************************************/
 
 static void
-resolved_proxy_created (GObject *source, GAsyncResult *r, gpointer user_data)
+name_owner_changed (NMDnsSystemdResolved *self,
+                    const char *owner)
 {
-	NMDnsSystemdResolved *self = (NMDnsSystemdResolved *) user_data;
+	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
+
+	owner = nm_str_not_empty (owner);
+
+	if (!owner)
+		_LOGT ("D-Bus name for systemd-resolved has no owner");
+	else
+		_LOGT ("D-Bus name for systemd-resolved has owner %s", owner);
+
+	priv->dbus_has_owner = !!owner;
+	if (owner)
+		priv->try_start_blocked = FALSE;
+
+	send_updates (self);
+}
+
+static void
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char *sender_name,
+                       const char *object_path,
+                       const char *interface_name,
+                       const char *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
+{
+	NMDnsSystemdResolved *self = user_data;
+	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
+	const char *new_owner;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
+		return;
+
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &new_owner);
+
+	if (!priv->dbus_initied) {
+		/* There was a race and we got a NameOwnerChanged signal before GetNameOwner
+		 * returns. */
+		priv->dbus_initied = TRUE;
+		nm_clear_g_cancellable (&priv->cancellable);
+	}
+
+	name_owner_changed (user_data, new_owner);
+}
+
+static void
+get_name_owner_cb (GObject *source,
+                   GAsyncResult *res,
+                   gpointer user_data)
+{
+	NMDnsSystemdResolved *self;
 	NMDnsSystemdResolvedPrivate *priv;
+	gs_unref_variant GVariant *ret = NULL;
 	gs_free_error GError *error = NULL;
-	GDBusProxy *resolve;
+	const char *owner = NULL;
 
-	resolve = g_dbus_proxy_new_finish (r, &error);
-	if (   !resolve
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
+	if (   !ret
 	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
+	if (ret)
+		g_variant_get (ret, "(&s)", &owner);
+
+	self = user_data;
 	priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
-	g_clear_object (&priv->init_cancellable);
-	if (!resolve) {
-		_LOGW ("failed to connect to resolved via DBus: %s", error->message);
-		g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
-		return;
-	}
 
-	priv->resolve = resolve;
-	send_updates (self);
+	g_clear_object (&priv->cancellable);
+
+	priv->dbus_initied = TRUE;
+
+	name_owner_changed (self, owner);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_dns_systemd_resolved_is_running (NMDnsSystemdResolved *self)
+{
+	NMDnsSystemdResolvedPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DNS_SYSTEMD_RESOLVED (self), FALSE);
+
+	priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
+
+	return    priv->dbus_initied
+	       && (   priv->dbus_has_owner
+	           || !priv->try_start_blocked);
 }
 
 /*****************************************************************************/
@@ -394,17 +527,35 @@ nm_dns_systemd_resolved_init (NMDnsSystemdResolved *self)
 
 	c_list_init (&priv->request_queue_lst_head);
 
-	priv->init_cancellable = g_cancellable_new ();
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
-	                          NULL,
-	                          SYSTEMD_RESOLVED_DBUS_SERVICE,
-	                          SYSTEMD_RESOLVED_DBUS_PATH,
-	                          SYSTEMD_RESOLVED_DBUS_SERVICE ".Manager",
-	                          priv->init_cancellable,
-	                          resolved_proxy_created,
-	                          self);
+	priv->dbus_connection = nm_g_object_ref (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ()));
+	if (!priv->dbus_connection) {
+		_LOGD ("no D-Bus connection");
+		return;
+	}
+
+	priv->name_owner_changed_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                                  DBUS_SERVICE_DBUS,
+	                                                                  DBUS_INTERFACE_DBUS,
+	                                                                  "NameOwnerChanged",
+	                                                                  DBUS_PATH_DBUS,
+	                                                                  SYSTEMD_RESOLVED_DBUS_SERVICE,
+	                                                                  G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                                  name_owner_changed_cb,
+	                                                                  self,
+	                                                                  NULL);
+	priv->cancellable = g_cancellable_new ();
+	g_dbus_connection_call (priv->dbus_connection,
+	                        DBUS_SERVICE_DBUS,
+	                        DBUS_PATH_DBUS,
+	                        DBUS_INTERFACE_DBUS,
+	                        "GetNameOwner",
+	                        g_variant_new ("(s)", SYSTEMD_RESOLVED_DBUS_SERVICE),
+	                        G_VARIANT_TYPE ("(s)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1,
+	                        priv->cancellable,
+	                        get_name_owner_cb,
+	                        self);
 }
 
 NMDnsPlugin *
@@ -420,9 +571,15 @@ dispose (GObject *object)
 	NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
 
 	free_pending_updates (self);
-	g_clear_object (&priv->resolve);
-	nm_clear_g_cancellable (&priv->init_cancellable);
-	nm_clear_g_cancellable (&priv->update_cancellable);
+
+	if (priv->name_owner_changed_id != 0) {
+		g_dbus_connection_signal_unsubscribe (priv->dbus_connection,
+		                                      nm_steal_int (&priv->name_owner_changed_id));
+	}
+
+	nm_clear_g_cancellable (&priv->cancellable);
+
+	g_clear_object (&priv->dbus_connection);
 
 	G_OBJECT_CLASS (nm_dns_systemd_resolved_parent_class)->dispose (object);
 }
diff --git a/src/dns/nm-dns-systemd-resolved.h b/src/dns/nm-dns-systemd-resolved.h
index 800a60c1..b79ff5e4 100644
--- a/src/dns/nm-dns-systemd-resolved.h
+++ b/src/dns/nm-dns-systemd-resolved.h
@@ -36,4 +36,6 @@ GType nm_dns_systemd_resolved_get_type (void);
 
 NMDnsPlugin *nm_dns_systemd_resolved_new (void);
 
+gboolean nm_dns_systemd_resolved_is_running (NMDnsSystemdResolved *self);
+
 #endif /* __NETWORKMANAGER_DNS_SYSTEMD_RESOLVED_H__ */
diff --git a/src/initrd/meson.build b/src/initrd/meson.build
index a12b718a..fa8c016b 100644
--- a/src/initrd/meson.build
+++ b/src/initrd/meson.build
@@ -10,7 +10,7 @@ libnmi_core = static_library(
   c_args: nm_cflags,
   sources: sources,
   include_directories: src_inc,
-  dependencies: nm_core_dep,
+  dependencies: libnm_core_dep,
 )
 
 name = 'nm-initrd-generator'
@@ -19,7 +19,7 @@ executable(
   name + '.c',
   c_args: nm_cflags,
   include_directories: src_inc,
-  dependencies: [ nm_core_dep ],
+  dependencies: [ libnm_core_dep ],
   link_with: [libnetwork_manager_base, libnmi_core],
   link_args: ldflags_linker_script_binary,
   link_depends: linker_script_binary,
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index b84543c4..4c1c4037 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -23,7 +23,7 @@
 #include "nm-core-internal.h"
 #include "nm-keyfile-internal.h"
 #include "nm-initrd-generator.h"
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/meson.build b/src/meson.build
index 06a0dc57..a3ac4625 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -13,7 +13,7 @@ nm_cflags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON'
 
 nm_dep = declare_dependency(
   include_directories: src_inc,
-  dependencies: nm_core_dep,
+  dependencies: libnm_core_dep,
   compile_args: nm_cflags,
 )
 
@@ -35,6 +35,7 @@ sources = files(
   'platform/nm-platform-utils.c',
   'platform/nmp-netns.c',
   'platform/nmp-object.c',
+  'platform/nmp-rules-manager.c',
   'main-utils.c',
   'NetworkManagerUtils.c',
   'nm-core-utils.c',
@@ -48,7 +49,7 @@ sources = files(
 deps = [
   libsystemd_dep,
   libudev_dep,
-  nm_core_dep,
+  libnm_core_dep,
 ]
 
 if enable_wext
@@ -148,7 +149,7 @@ nm_deps = [
   dl_dep,
   libndp_dep,
   libudev_dep,
-  nm_core_dep,
+  libnm_core_dep,
   shared_n_acd_dep,
   logind_dep,
 ]
@@ -185,7 +186,7 @@ deps = [
   dl_dep,
   libndp_dep,
   libudev_dep,
-  nm_core_dep,
+  libnm_core_dep,
 ]
 
 name = 'nm-iface-helper'
@@ -215,7 +216,7 @@ if enable_tests
 
   deps = [
     libudev_dep,
-    nm_core_dep,
+    libnm_core_dep,
   ]
 
   test_cflags = ['-DNETWORKMANAGER_COMPILATION_TEST']
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 7b9f2cc7..17568c30 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -22,7 +22,7 @@
 
 #include "nm-active-connection.h"
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-interface.h"
 #include "devices/nm-device.h"
 #include "settings/nm-settings-connection.h"
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
index dff331a8..5599201e 100644
--- a/src/nm-auth-subject.c
+++ b/src/nm-auth-subject.c
@@ -173,9 +173,10 @@ _new_unix_process (GDBusMethodInvocation *context,
                    GDBusMessage *message)
 {
 	NMAuthSubject *self;
-	gboolean success = FALSE;
-	gulong pid = 0, uid = 0;
-	gs_free char *dbus_sender = NULL;
+	const char *dbus_sender = NULL;
+	gulong uid = 0;
+	gulong pid = 0;
+	gboolean success;
 
 	g_return_val_if_fail (context || (connection && message), NULL);
 
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index 146f6883..a0ad84c1 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -22,7 +22,7 @@
 
 #include "nm-auth-utils.h"
 
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/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 9b1622ec..f715eea6 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -38,15 +38,19 @@
 
 typedef struct {
 	char *original_dev_path;
+	char *original_dev_name;
+	NMDeviceType dev_type;
 	NMDevice *device;
 	NMConnection *applied_connection;
 	NMConnection *settings_connection;
 	guint64 ac_version_id;
 	NMDeviceState state;
+	bool is_software:1;
 	bool realized:1;
 	bool activation_lifetime_bound_to_profile_visiblity:1;
 	NMUnmanFlagOp unmanaged_explicit;
 	NMActivationReason activation_reason;
+	gulong dev_exported_change_id;
 } DeviceCheckpoint;
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMCheckpoint,
@@ -58,14 +62,15 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMCheckpoint,
 struct _NMCheckpointPrivate {
 	/* properties */
 	GHashTable *devices;
+	GPtrArray *removed_devices;
 	gint64 created_at_ms;
 	guint32 rollback_timeout_s;
 	guint timeout_id;
 	/* private members */
-	/* private members */
 	NMManager *manager;
 	NMCheckpointCreateFlags flags;
 	GHashTable *connection_uuids;
+	gulong dev_removed_id;
 
 	NMCheckpointTimeoutCallback timeout_cb;
 	gpointer timeout_data;
@@ -204,28 +209,140 @@ find_settings_connection (NMCheckpoint *self,
 	return sett_conn;
 }
 
+static gboolean
+restore_and_activate_connection (NMCheckpoint *self,
+                                 DeviceCheckpoint *dev_checkpoint)
+{
+	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
+	NMSettingsConnection *connection;
+	gs_unref_object NMAuthSubject *subject = NULL;
+	GError *local_error = NULL;
+	gboolean need_update, need_activation;
+
+	connection = find_settings_connection (self,
+	                                       dev_checkpoint,
+	                                       &need_update,
+	                                       &need_activation);
+	if (connection) {
+		if (need_update) {
+			_LOGD ("rollback: updating connection %s",
+			       nm_settings_connection_get_uuid (connection));
+			nm_settings_connection_update (connection,
+			                               dev_checkpoint->settings_connection,
+			                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
+			                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
+			                               "checkpoint-rollback",
+			                               NULL);
+		}
+	} else {
+		/* The connection was deleted, recreate it */
+		_LOGD ("rollback: adding connection %s again",
+		       nm_connection_get_uuid (dev_checkpoint->settings_connection));
+
+		connection = nm_settings_add_connection (nm_settings_get (),
+		                                         dev_checkpoint->settings_connection,
+		                                         TRUE,
+		                                         &local_error);
+		if (!connection) {
+			_LOGD ("rollback: connection add failure: %s", local_error->message);
+			g_clear_error (&local_error);
+			return FALSE;
+		}
+
+		/* If the device is software, a brand new NMDevice may have been created */
+		if (   dev_checkpoint->is_software
+		    && !dev_checkpoint->device) {
+			dev_checkpoint->device = nm_manager_get_device (priv->manager,
+			                                                dev_checkpoint->original_dev_name,
+			                                                dev_checkpoint->dev_type);
+			nm_g_object_ref (dev_checkpoint->device);
+		}
+		need_activation = TRUE;
+	}
+
+	if (!dev_checkpoint->device) {
+		_LOGD ("rollback: device cannot be restored");
+		return FALSE;
+	}
+
+	if (need_activation) {
+		_LOGD ("rollback: reactivating connection %s",
+		       nm_settings_connection_get_uuid (connection));
+		subject = nm_auth_subject_new_internal ();
+
+		/* Disconnect the device if needed. This necessary because now
+		 * the manager prevents the reactivation of the same connection by
+		 * an internal subject. */
+		if (   nm_device_get_state (dev_checkpoint->device) > NM_DEVICE_STATE_DISCONNECTED
+		    && nm_device_get_state (dev_checkpoint->device) < NM_DEVICE_STATE_DEACTIVATING) {
+			nm_device_state_changed (dev_checkpoint->device,
+			                         NM_DEVICE_STATE_DEACTIVATING,
+			                         NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
+		}
+
+		if (!nm_manager_activate_connection (priv->manager,
+		                                     connection,
+		                                     dev_checkpoint->applied_connection,
+		                                     NULL,
+		                                     dev_checkpoint->device,
+		                                     subject,
+		                                     NM_ACTIVATION_TYPE_MANAGED,
+		                                     dev_checkpoint->activation_reason,
+		                                       dev_checkpoint->activation_lifetime_bound_to_profile_visiblity
+		                                     ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
+		                                     : NM_ACTIVATION_STATE_FLAG_NONE,
+		                                     &local_error)) {
+			_LOGW ("rollback: reactivation of connection %s/%s failed: %s",
+			       nm_settings_connection_get_id (connection),
+			       nm_settings_connection_get_uuid (connection),
+			       local_error->message);
+			g_clear_error (&local_error);
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
 GVariant *
 nm_checkpoint_rollback (NMCheckpoint *self)
 {
 	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
 	DeviceCheckpoint *dev_checkpoint;
 	GHashTableIter iter;
-	NMSettingsConnection *connection;
 	NMDevice *device;
-	GError *local_error = NULL;
 	GVariantBuilder builder;
+	uint i;
 
 	_LOGI ("rollback of %s", nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
 	 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{su}"));
 
+	/* Start creating removed devices (if any and if possible) */
+	if (priv->removed_devices) {
+		for (i = 0; i < priv->removed_devices->len; i++) {
+			guint32 result = NM_ROLLBACK_RESULT_OK;
+
+			dev_checkpoint = priv->removed_devices->pdata[i];
+			_LOGD ("rollback: restoring removed device %s (state %d, realized %d, explicitly unmanaged %d)",
+			       dev_checkpoint->original_dev_name,
+			       (int) dev_checkpoint->state,
+			       dev_checkpoint->realized,
+			       dev_checkpoint->unmanaged_explicit);
+
+			if (dev_checkpoint->applied_connection) {
+				if (!restore_and_activate_connection (self, dev_checkpoint))
+					result = NM_ROLLBACK_RESULT_ERR_FAILED;
+			}
+			g_variant_builder_add (&builder, "{su}", dev_checkpoint->original_dev_path, result);
+		}
+	}
+
 	/* Start rolling-back each device */
 	g_hash_table_iter_init (&iter, priv->devices);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &device, (gpointer *) &dev_checkpoint)) {
-		gs_unref_object NMAuthSubject *subject = NULL;
 		guint32 result = NM_ROLLBACK_RESULT_OK;
 
 		_LOGD ("rollback: restoring device %s (state %d, realized %d, explicitly unmanaged %d)",
-		       nm_device_get_iface (device),
+		       dev_checkpoint->original_dev_name,
 		       (int) dev_checkpoint->state,
 		       dev_checkpoint->realized,
 		       dev_checkpoint->unmanaged_explicit);
@@ -241,7 +358,7 @@ nm_checkpoint_rollback (NMCheckpoint *self)
 			}
 		} else {
 			if (dev_checkpoint->realized) {
-				if (nm_device_is_software (device)) {
+				if (dev_checkpoint->is_software) {
 					/* try to recreate software device */
 					_LOGD ("rollback: software device not realized, will re-activate");
 					goto activate;
@@ -253,7 +370,6 @@ nm_checkpoint_rollback (NMCheckpoint *self)
 			goto next_dev;
 		}
 
-activate:
 		/* Manage the device again if needed */
 		if (   nm_device_get_unmanaged_flags (device, NM_UNMANAGED_USER_EXPLICIT)
 		    && dev_checkpoint->unmanaged_explicit != NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
@@ -276,76 +392,11 @@ activate:
 			goto next_dev;
 		}
 
+activate:
 		if (dev_checkpoint->applied_connection) {
-			gboolean need_update, need_activation;
-
-			/* The device had an active connection: check if the
-			 * connection still exists, is active and was changed */
-			connection = find_settings_connection (self, dev_checkpoint, &need_update, &need_activation);
-			if (connection) {
-				if (need_update) {
-					_LOGD ("rollback: updating connection %s",
-					        nm_settings_connection_get_uuid (connection));
-					nm_settings_connection_update (connection,
-					                               dev_checkpoint->settings_connection,
-					                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-					                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-					                               "checkpoint-rollback",
-					                               NULL);
-				}
-			} else {
-				/* The connection was deleted, recreate it */
-				_LOGD ("rollback: adding connection %s again",
-				       nm_connection_get_uuid (dev_checkpoint->settings_connection));
-
-				connection = nm_settings_add_connection (nm_settings_get (),
-				                                         dev_checkpoint->settings_connection,
-				                                         TRUE,
-				                                         &local_error);
-				if (!connection) {
-					_LOGD ("rollback: connection add failure: %s", local_error->message);
-					g_clear_error (&local_error);
-					result = NM_ROLLBACK_RESULT_ERR_FAILED;
-					goto next_dev;
-				}
-				need_activation = TRUE;
-			}
-
-			if (need_activation) {
-				_LOGD ("rollback: reactivating connection %s",
-				       nm_settings_connection_get_uuid (connection));
-				subject = nm_auth_subject_new_internal ();
-
-				/* Disconnect the device if needed. This necessary because now
-				 * the manager prevents the reactivation of the same connection by
-				 * an internal subject. */
-				if (   nm_device_get_state (device) > NM_DEVICE_STATE_DISCONNECTED
-				    && nm_device_get_state (device) < NM_DEVICE_STATE_DEACTIVATING) {
-					nm_device_state_changed (device,
-					                         NM_DEVICE_STATE_DEACTIVATING,
-					                         NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
-				}
-
-				if (!nm_manager_activate_connection (priv->manager,
-				                                     connection,
-				                                     dev_checkpoint->applied_connection,
-				                                     NULL,
-				                                     device,
-				                                     subject,
-				                                     NM_ACTIVATION_TYPE_MANAGED,
-				                                     dev_checkpoint->activation_reason,
-				                                       dev_checkpoint->activation_lifetime_bound_to_profile_visiblity
-				                                     ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
-				                                     : NM_ACTIVATION_STATE_FLAG_NONE,
-				                                     &local_error)) {
-					_LOGW ("rollback: reactivation of connection %s/%s failed: %s",
-					       nm_settings_connection_get_id (connection),
-					       nm_settings_connection_get_uuid (connection),
-					       local_error->message);
-					g_clear_error (&local_error);
-					result = NM_ROLLBACK_RESULT_ERR_FAILED;
-					goto next_dev;
-				}
+			if (!restore_and_activate_connection (self, dev_checkpoint)) {
+				result = NM_ROLLBACK_RESULT_ERR_FAILED;
+				goto next_dev;
 			}
 		} else {
 			/* The device was initially disconnected, deactivate any existing connection */
@@ -366,7 +417,6 @@ next_dev:
 	if (NM_FLAGS_HAS (priv->flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
 		NMSettingsConnection *con;
 		gs_free NMSettingsConnection **list = NULL;
-		guint i;
 
 		g_return_val_if_fail (priv->connection_uuids, NULL);
 		list = nm_settings_get_connections_clone (nm_settings_get (), NULL,
@@ -406,8 +456,56 @@ next_dev:
 	return g_variant_new ("(a{su})", &builder);
 }
 
+static void
+device_checkpoint_destroy (gpointer data)
+{
+	DeviceCheckpoint *dev_checkpoint = data;
+
+	nm_clear_g_signal_handler (dev_checkpoint->device, &dev_checkpoint->dev_exported_change_id);
+	g_clear_object (&dev_checkpoint->applied_connection);
+	g_clear_object (&dev_checkpoint->settings_connection);
+	g_clear_object (&dev_checkpoint->device);
+	g_free (dev_checkpoint->original_dev_path);
+	g_free (dev_checkpoint->original_dev_name);
+
+	g_slice_free (DeviceCheckpoint, dev_checkpoint);
+}
+
+static void
+_move_dev_to_removed_devices (NMDevice *device,
+                              NMCheckpoint *checkpoint)
+{
+	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (checkpoint);
+	DeviceCheckpoint *dev_checkpoint;
+
+	g_return_if_fail (device);
+
+	dev_checkpoint = g_hash_table_lookup (priv->devices, device);
+	if (!dev_checkpoint)
+		return;
+
+	g_hash_table_steal (priv->devices, dev_checkpoint->device);
+	nm_clear_g_signal_handler (dev_checkpoint->device,
+	                           &dev_checkpoint->dev_exported_change_id);
+	g_clear_object (&dev_checkpoint->device);
+
+	if (!priv->removed_devices)
+		priv->removed_devices = g_ptr_array_new_with_free_func ((GDestroyNotify) device_checkpoint_destroy);
+	g_ptr_array_add (priv->removed_devices, dev_checkpoint);
+
+	_notify (checkpoint, PROP_DEVICES);
+}
+
+static void
+_dev_exported_changed (NMDBusObject *obj,
+                       NMCheckpoint *checkpoint)
+{
+
+	_move_dev_to_removed_devices (NM_DEVICE (obj), checkpoint);
+}
+
 static DeviceCheckpoint *
-device_checkpoint_create (NMDevice *device)
+device_checkpoint_create (NMCheckpoint *checkpoint, NMDevice *device)
 {
 	DeviceCheckpoint *dev_checkpoint;
 	NMConnection *applied_connection;
@@ -423,8 +521,15 @@ device_checkpoint_create (NMDevice *device)
 	dev_checkpoint = g_slice_new0 (DeviceCheckpoint);
 	dev_checkpoint->device = g_object_ref (device);
 	dev_checkpoint->original_dev_path = g_strdup (path);
+	dev_checkpoint->original_dev_name = g_strdup (nm_device_get_iface (device));
+	dev_checkpoint->dev_type = nm_device_get_device_type (device);
 	dev_checkpoint->state = nm_device_get_state (device);
+	dev_checkpoint->is_software = nm_device_is_software (device);
 	dev_checkpoint->realized = nm_device_is_real (device);
+	dev_checkpoint->dev_exported_change_id = g_signal_connect (device,
+	                                                           NM_DBUS_OBJECT_EXPORTED_CHANGED,
+	                                                           G_CALLBACK (_dev_exported_changed),
+	                                                           checkpoint);
 
 	if (nm_device_get_unmanaged_mask (device, NM_UNMANAGED_USER_EXPLICIT)) {
 		dev_checkpoint->unmanaged_explicit = !!nm_device_get_unmanaged_flags (device,
@@ -448,19 +553,6 @@ device_checkpoint_create (NMDevice *device)
 	return dev_checkpoint;
 }
 
-static void
-device_checkpoint_destroy (gpointer data)
-{
-	DeviceCheckpoint *dev_checkpoint = data;
-
-	g_clear_object (&dev_checkpoint->applied_connection);
-	g_clear_object (&dev_checkpoint->settings_connection);
-	g_clear_object (&dev_checkpoint->device);
-	g_free (dev_checkpoint->original_dev_path);
-
-	g_slice_free (DeviceCheckpoint, dev_checkpoint);
-}
-
 static gboolean
 _timeout_cb (gpointer user_data)
 {
@@ -558,6 +650,12 @@ nm_checkpoint_init (NMCheckpoint *self)
 	                                       NULL, device_checkpoint_destroy);
 }
 
+static void
+_device_removed (NMManager *manager, NMDevice *device, gpointer user_data)
+{
+	_move_dev_to_removed_devices (device, NM_CHECKPOINT (user_data));
+}
+
 NMCheckpoint *
 nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_timeout_s,
                    NMCheckpointCreateFlags flags)
@@ -575,7 +673,7 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time
 	self = g_object_new (NM_TYPE_CHECKPOINT, NULL);
 
 	priv = NM_CHECKPOINT_GET_PRIVATE (self);
-	priv->manager = manager;
+	priv->manager = g_object_ref (manager);
 	priv->rollback_timeout_s = rollback_timeout_s;
 	priv->created_at_ms = nm_utils_get_monotonic_timestamp_ms ();
 	priv->flags = flags;
@@ -598,15 +696,18 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time
 	for (i = 0; i < devices->len; i++) {
 		NMDevice *device = devices->pdata[i];
 
-		/* FIXME: as long as the check point instance exists, it won't let go
-		 *        of the device. That is a bug, for example, if you have a ethernet
-		 *        device that gets removed (rmmod), the checkpoint will reference
-		 *        a non-existing D-Bus path of a device. */
+		/* As long as the check point instance exists, it will keep a reference
+		 * to the device also if the device gets removed (by rmmod or by deleting
+		 * a connection profile for a software device). */
 		g_hash_table_insert (priv->devices,
 		                     device,
-		                     device_checkpoint_create (device));
+		                     device_checkpoint_create (self, device));
 	}
 
+	priv->dev_removed_id = g_signal_connect (priv->manager,
+	                                         NM_MANAGER_DEVICE_REMOVED,
+	                                         G_CALLBACK (_device_removed),
+	                                         self);
 	return self;
 }
 
@@ -620,6 +721,10 @@ dispose (GObject *object)
 
 	g_clear_pointer (&priv->devices, g_hash_table_unref);
 	g_clear_pointer (&priv->connection_uuids, g_hash_table_unref);
+	nm_clear_pointer (&priv->removed_devices, g_ptr_array_unref);
+
+	nm_clear_g_signal_handler (priv->manager, &priv->dev_removed_id);
+	g_clear_object (&priv->manager);
 
 	nm_clear_g_source (&priv->timeout_id);
 
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index b72413d2..2816e76a 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -167,6 +167,7 @@ NM_DEFINE_SINGLETON_GETTER (NMConnectivity, nm_connectivity_get, NM_TYPE_CONNECT
 
 /*****************************************************************************/
 
+#if WITH_CONCHECK
 static ConConfig *
 _con_config_ref (ConConfig *con_config)
 {
@@ -176,6 +177,7 @@ _con_config_ref (ConConfig *con_config)
 	}
 	return con_config;
 }
+#endif
 
 static void
 _con_config_unref (ConConfig *con_config)
@@ -195,11 +197,13 @@ _con_config_unref (ConConfig *con_config)
 	g_slice_free (ConConfig, con_config);
 }
 
+#if WITH_CONCHECK
 static const char *
 _con_config_get_response (const ConConfig *con_config)
 {
 	return con_config->response ?: NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
 }
+#endif
 
 /*****************************************************************************/
 
@@ -673,6 +677,7 @@ _idle_cb (gpointer user_data)
 	return G_SOURCE_REMOVE;
 }
 
+#if WITH_CONCHECK
 static void
 do_curl_request (NMConnectivityCheckHandle *cb_data)
 {
@@ -791,6 +796,7 @@ resolve_cb (GObject *object, GAsyncResult *res, gpointer user_data)
 
 	do_curl_request (cb_data);
 }
+#endif
 
 #define SD_RESOLVED_DNS ((guint64) (1LL << 0))
 
@@ -819,13 +825,13 @@ nm_connectivity_check_start (NMConnectivity *self,
 	cb_data->user_data = user_data;
 	cb_data->completed_state = NM_CONNECTIVITY_UNKNOWN;
 	cb_data->addr_family = addr_family;
-	cb_data->concheck.con_config = _con_config_ref (priv->con_config);
-
 	if (iface)
 		cb_data->ifspec = g_strdup_printf ("if!%s", iface);
 
 #if WITH_CONCHECK
 
+	cb_data->concheck.con_config = _con_config_ref (priv->con_config);
+
 	if (   iface
 	    && ifindex > 0
 	    && priv->enabled
@@ -840,7 +846,18 @@ nm_connectivity_check_start (NMConnectivity *self,
 		 *
 		 * Yes, this makes NMConnectivity singleton dependent on NMDnsManager singleton.
 		 * Well, not really: it makes connectivity-check-start dependent on NMDnsManager
-		 * which merely means, not to start a connectivity check, late during shutdown. */
+		 * which merely means, not to start a connectivity check, late during shutdown.
+		 *
+		 * NMDnsSystemdResolved tries to D-Bus activate systemd-resolved only once,
+		 * to not spam syslog with failures messages from dbus-daemon.
+		 * Note that unless NMDnsSystemdResolved tried and failed to start systemd-resolved,
+		 * it guesses that systemd-resolved is activatable and returns %TRUE here. That
+		 * means, while NMDnsSystemdResolved would not try to D-Bus activate systemd-resolved
+		 * more than once, NMConnectivity might -- until NMDnsSystemdResolved tried itself
+		 * and noticed that systemd-resolved is not available.
+		 * This is relatively cumbersome to avoid, because we would have to go through
+		 * NMDnsSystemdResolved trying to asynchronously start the service, to ensure there
+		 * is only one attempt to start the service. */
 		has_systemd_resolved = nm_dns_manager_has_systemd_resolved (nm_dns_manager_get ());
 
 		if (has_systemd_resolved) {
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 6f55e62a..b0cc914e 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -37,10 +37,10 @@
 #include <net/if_arp.h>
 #include <net/ethernet.h>
 
-#include "nm-utils/nm-random-utils.h"
-#include "nm-utils/nm-io-utils.h"
-#include "nm-utils/unaligned.h"
-#include "nm-utils/nm-secret-utils.h"
+#include "nm-std-aux/unaligned.h"
+#include "nm-glib-aux/nm-random-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-connection.h"
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 1b0d39ed..1f9996ca 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -27,7 +27,7 @@
 
 #include "nm-connection.h"
 
-#include "nm-utils/nm-time-utils.h"
+#include "nm-glib-aux/nm-time-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 00fa6617..1a455c42 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -28,9 +28,10 @@
 #include <sys/types.h>
 
 #include "c-list/src/c-list.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-dbus-interface.h"
 #include "nm-core-internal.h"
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 #include "nm-dbus-object.h"
 #include "NetworkManagerUtils.h"
 
@@ -44,6 +45,17 @@
 /*****************************************************************************/
 
 typedef struct {
+	CList caller_info_lst;
+	gulong uid;
+	gulong pid;
+	gint64 uid_checked_at;
+	gint64 pid_checked_at;
+	bool uid_valid:1;
+	bool pid_valid:1;
+	char sender[0];
+} CallerInfo;
+
+typedef struct {
 	GVariant *value;
 } PropertyCacheData;
 
@@ -78,8 +90,10 @@ typedef struct {
 	NMDBusManagerSetPropertyHandler set_property_handler;
 	gpointer set_property_handler_data;
 
-	GDBusConnection *connection;
-	GDBusProxy *proxy;
+	GDBusConnection *main_dbus_connection;
+
+	CList caller_info_lst_head;
+
 	guint objmgr_registration_id;
 	bool started:1;
 	bool shutting_down:1;
@@ -441,21 +455,33 @@ private_server_get_connection_by_owner (PrivateServer *s, const char *owner)
 
 /*****************************************************************************/
 
+static void
+_caller_info_free (CallerInfo *caller_info)
+{
+	c_list_unlink_stale (&caller_info->caller_info_lst);
+	g_free (caller_info);
+}
+
 static gboolean
 _bus_get_unix_pid (NMDBusManager *self,
                    const char *sender,
-                   gulong *out_pid,
-                   GError **error)
+                   gulong *out_pid)
 {
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 	guint32 unix_pid = G_MAXUINT32;
 	gs_unref_variant GVariant *ret = NULL;
 
-	ret = _nm_dbus_proxy_call_sync (NM_DBUS_MANAGER_GET_PRIVATE (self)->proxy,
-	                                "GetConnectionUnixProcessID",
-	                                g_variant_new ("(s)", sender),
-	                                G_VARIANT_TYPE ("(u)"),
-	                                G_DBUS_CALL_FLAGS_NONE, 2000,
-	                                NULL, error);
+	ret = g_dbus_connection_call_sync (priv->main_dbus_connection,
+	                                   DBUS_SERVICE_DBUS,
+	                                   DBUS_PATH_DBUS,
+	                                   DBUS_INTERFACE_DBUS,
+	                                   "GetConnectionUnixProcessID",
+	                                   g_variant_new ("(s)", sender),
+	                                   G_VARIANT_TYPE ("(u)"),
+	                                   G_DBUS_CALL_FLAGS_NONE,
+	                                   2000,
+	                                   NULL,
+	                                   NULL);
 	if (!ret)
 		return FALSE;
 
@@ -468,18 +494,23 @@ _bus_get_unix_pid (NMDBusManager *self,
 static gboolean
 _bus_get_unix_user (NMDBusManager *self,
                     const char *sender,
-                    gulong *out_user,
-                    GError **error)
+                    gulong *out_user)
 {
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 	guint32 unix_uid = G_MAXUINT32;
 	gs_unref_variant GVariant *ret = NULL;
 
-	ret = _nm_dbus_proxy_call_sync (NM_DBUS_MANAGER_GET_PRIVATE (self)->proxy,
-	                                "GetConnectionUnixUser",
-	                                g_variant_new ("(s)", sender),
-	                                G_VARIANT_TYPE ("(u)"),
-	                                G_DBUS_CALL_FLAGS_NONE, 2000,
-	                                NULL, error);
+	ret = g_dbus_connection_call_sync (priv->main_dbus_connection,
+	                                   DBUS_SERVICE_DBUS,
+	                                   DBUS_PATH_DBUS,
+	                                   DBUS_INTERFACE_DBUS,
+	                                   "GetConnectionUnixUser",
+	                                   g_variant_new ("(s)", sender),
+	                                   G_VARIANT_TYPE ("(u)"),
+	                                   G_DBUS_CALL_FLAGS_NONE,
+	                                   2000,
+	                                   NULL,
+	                                   NULL);
 	if (!ret)
 		return FALSE;
 
@@ -489,34 +520,102 @@ _bus_get_unix_user (NMDBusManager *self,
 	return TRUE;
 }
 
-/**
- * _get_caller_info():
- *
- * Given a GDBus method invocation, or a GDBusConnection + GDBusMessage,
- * return the sender and the UID of the sender.
- */
+static const CallerInfo *
+_get_caller_info_ensure (NMDBusManager *self,
+                         const char *sender,
+                         gboolean ensure_uid,
+                         gboolean ensure_pid)
+{
+	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	CallerInfo *caller_info;
+	CallerInfo *ci;
+	gint64 now_ns;
+	gsize num;
+
+#define CALLER_INFO_MAX_AGE   (NM_UTILS_NS_PER_SECOND * 1)
+
+	/* Linear search the cache for the sender.
+	 *
+	 * The number of cached caller-infos is limited. Hence, it's O(1) and
+	 * the list is reasonably short.
+	 * Also, the entire caching assumes that we repeatedly ask for the
+	 * same sender. That means, we expect to find the right caller info
+	 * at the front of the list. */
+	num = 1;
+	caller_info = NULL;
+	c_list_for_each_entry (ci, &priv->caller_info_lst_head, caller_info_lst) {
+		if (nm_streq (sender, ci->sender)) {
+			caller_info = ci;
+			break;
+		}
+		num++;
+	}
+
+	if (caller_info)
+		nm_c_list_move_front (&priv->caller_info_lst_head, &caller_info->caller_info_lst);
+	else {
+		gsize l = strlen (sender) + 1;
+
+		caller_info = g_malloc (sizeof (CallerInfo) + l);
+		*caller_info = (CallerInfo) {
+			.uid_checked_at = - CALLER_INFO_MAX_AGE,
+			.pid_checked_at = - CALLER_INFO_MAX_AGE,
+		};
+		memcpy (caller_info->sender, sender, l);
+		c_list_link_front (&priv->caller_info_lst_head, &caller_info->caller_info_lst);
+
+		/* only cache the last few entries. */
+		while (TRUE) {
+			nm_assert (num > 0 && num == c_list_length (&priv->caller_info_lst_head));
+			if (num-- <= 5)
+				break;
+			_caller_info_free (c_list_last_entry (&priv->caller_info_lst_head, CallerInfo, caller_info_lst));
+		}
+	}
+
+	now_ns = nm_utils_get_monotonic_timestamp_ns ();
+
+	if (   ensure_uid
+	    && (now_ns - caller_info->uid_checked_at) > CALLER_INFO_MAX_AGE) {
+		caller_info->uid_checked_at = now_ns;
+		if (!(caller_info->uid_valid = _bus_get_unix_user (self, sender, &caller_info->uid)))
+			caller_info->uid = G_MAXULONG;
+	}
+
+	if (   ensure_pid
+	    && (now_ns - caller_info->pid_checked_at) > CALLER_INFO_MAX_AGE) {
+		caller_info->pid_checked_at = now_ns;
+		if (!(caller_info->pid_valid = _bus_get_unix_pid (self, sender, &caller_info->pid)))
+			caller_info->pid = G_MAXULONG;
+	}
+
+	return caller_info;
+}
+
 static gboolean
 _get_caller_info (NMDBusManager *self,
                   GDBusMethodInvocation *context,
                   GDBusConnection *connection,
                   GDBusMessage *message,
-                  char **out_sender,
+                  const char **out_sender,
                   gulong *out_uid,
                   gulong *out_pid)
 {
 	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	const CallerInfo *caller_info;
 	const char *sender;
 
 	if (context) {
+		nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 		connection = g_dbus_method_invocation_get_connection (context);
 
 		/* only bus connections will have a sender */
 		sender = g_dbus_method_invocation_get_sender (context);
 	} else {
-		g_assert (message);
+		nm_assert (G_IS_DBUS_MESSAGE (message));
 		sender = g_dbus_message_get_sender (message);
 	}
-	g_assert (connection);
+	nm_assert (G_IS_DBUS_CONNECTION (connection));
 
 	if (!sender) {
 		PrivateServer *s;
@@ -525,10 +624,8 @@ _get_caller_info (NMDBusManager *self,
 		c_list_for_each_entry (s, &priv->private_servers_lst_head, private_servers_lst) {
 			sender = private_server_get_connection_owner (s, connection);
 			if (sender) {
-				if (out_uid)
-					*out_uid = 0;
-				if (out_sender)
-					*out_sender = g_strdup (sender);
+				NM_SET_OUT (out_uid, 0);
+				NM_SET_OUT (out_sender, sender);
 				if (out_pid) {
 					GCredentials *creds;
 
@@ -547,35 +644,29 @@ _get_caller_info (NMDBusManager *self,
 				return TRUE;
 			}
 		}
+		NM_SET_OUT (out_sender, NULL);
+		NM_SET_OUT (out_uid, G_MAXULONG);
+		NM_SET_OUT (out_pid, G_MAXULONG);
 		return FALSE;
 	}
 
-	/* Bus connections always have a sender */
-	g_assert (sender);
-	if (out_uid) {
-		if (!_bus_get_unix_user (self, sender, out_uid, NULL)) {
-			*out_uid = G_MAXULONG;
-			return FALSE;
-		}
-	}
-
-	if (out_pid) {
-		if (!_bus_get_unix_pid (self, sender, out_pid, NULL)) {
-			*out_pid = G_MAXULONG;
-			return FALSE;
-		}
-	}
+	caller_info = _get_caller_info_ensure (self, sender, !!out_uid, !!out_pid);
 
-	if (out_sender)
-		*out_sender = g_strdup (sender);
+	NM_SET_OUT (out_sender, caller_info->sender);
+	NM_SET_OUT (out_uid, caller_info->uid);
+	NM_SET_OUT (out_pid, caller_info->pid);
 
+	if (out_uid && !caller_info->uid_valid)
+		return FALSE;
+	if (out_pid && !caller_info->pid_valid)
+		return FALSE;
 	return TRUE;
 }
 
 gboolean
 nm_dbus_manager_get_caller_info (NMDBusManager *self,
                                  GDBusMethodInvocation *context,
-                                 char **out_sender,
+                                 const char **out_sender,
                                  gulong *out_uid,
                                  gulong *out_pid)
 {
@@ -586,7 +677,7 @@ gboolean
 nm_dbus_manager_get_caller_info_from_message (NMDBusManager *self,
                                               GDBusConnection *connection,
                                               GDBusMessage *message,
-                                              char **out_sender,
+                                              const char **out_sender,
                                               gulong *out_uid,
                                               gulong *out_pid)
 {
@@ -647,8 +738,8 @@ nm_dbus_manager_get_unix_user (NMDBusManager *self,
                                gulong *out_uid)
 {
 	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+	const CallerInfo *caller_info;
 	PrivateServer *s;
-	GError *error = NULL;
 
 	g_return_val_if_fail (sender != NULL, FALSE);
 	g_return_val_if_fail (out_uid != NULL, FALSE);
@@ -665,13 +756,12 @@ nm_dbus_manager_get_unix_user (NMDBusManager *self,
 	}
 
 	/* Otherwise, a bus connection */
-	if (!_bus_get_unix_user (self, sender, out_uid, &error)) {
-		_LOGW ("failed to get unix user for dbus sender '%s': %s",
-		       sender, error->message);
-		g_error_free (error);
+	caller_info = _get_caller_info_ensure (self, sender, TRUE, FALSE);
+	*out_uid = caller_info->uid;
+	if (!caller_info->uid_valid) {
+		_LOGW ("failed to get unix user for dbus sender '%s'", sender);
 		return FALSE;
 	}
-
 	return TRUE;
 }
 
@@ -757,16 +847,6 @@ nm_dbus_manager_new_proxy (NMDBusManager *self,
 
 /*****************************************************************************/
 
-GDBusConnection *
-nm_dbus_manager_get_connection (NMDBusManager *self)
-{
-	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL);
-
-	return NM_DBUS_MANAGER_GET_PRIVATE (self)->connection;
-}
-
-/*****************************************************************************/
-
 static const NMDBusInterfaceInfoExtended *
 _reg_data_get_interface_info (RegistrationData *reg_data)
 {
@@ -943,7 +1023,7 @@ _obj_register (NMDBusManager *self,
 	GVariantBuilder builder;
 
 	nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
-	nm_assert (priv->connection);
+	nm_assert (priv->main_dbus_connection);
 	nm_assert (priv->started);
 
 	n_klasses = 0;
@@ -980,7 +1060,7 @@ _obj_register (NMDBusManager *self,
 
 			reg_data = g_malloc0 (sizeof (RegistrationData) + (sizeof (PropertyCacheData) * prop_len));
 
-			registration_id = g_dbus_connection_register_object (priv->connection,
+			registration_id = g_dbus_connection_register_object (priv->main_dbus_connection,
 			                                                     obj->internal.path,
 			                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info->parent),
 			                                                     &dbus_vtable,
@@ -1019,7 +1099,7 @@ _obj_register (NMDBusManager *self,
 	 *
 	 * In general, it's ok to export an object with frozen signals. But you better make sure
 	 * that all properties are in a self-consistent state when exporting the object. */
-	g_dbus_connection_emit_signal (priv->connection,
+	g_dbus_connection_emit_signal (priv->main_dbus_connection,
 	                               NULL,
 	                               OBJECT_MANAGER_SERVER_BASE_PATH,
 	                               interface_info_objmgr.name,
@@ -1040,7 +1120,7 @@ _obj_unregister (NMDBusManager *self,
 
 	nm_assert (NM_IS_DBUS_OBJECT (obj));
 
-	if (!priv->connection) {
+	if (!priv->main_dbus_connection) {
 		/* nothing to do for the moment. */
 		nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
 		return;
@@ -1059,7 +1139,7 @@ _obj_unregister (NMDBusManager *self,
 		                       "s",
 		                       interface_info->parent.name);
 		c_list_unlink_stale (&reg_data->registration_lst);
-		if (!g_dbus_connection_unregister_object (priv->connection, reg_data->registration_id))
+		if (!g_dbus_connection_unregister_object (priv->main_dbus_connection, reg_data->registration_id))
 			nm_assert_not_reached ();
 
 		if (interface_info->parent.properties) {
@@ -1071,7 +1151,7 @@ _obj_unregister (NMDBusManager *self,
 		g_free (reg_data);
 	}
 
-	g_dbus_connection_emit_signal (priv->connection,
+	g_dbus_connection_emit_signal (priv->main_dbus_connection,
 	                               NULL,
 	                               OBJECT_MANAGER_SERVER_BASE_PATH,
 	                               interface_info_objmgr.name,
@@ -1122,7 +1202,7 @@ _nm_dbus_manager_obj_export (NMDBusObject *obj)
 		nm_assert_not_reached ();
 	c_list_link_tail (&priv->objects_lst_head, &obj->internal.objects_lst);
 
-	if (priv->connection && priv->started)
+	if (priv->main_dbus_connection && priv->started)
 		_obj_register (self, obj);
 }
 
@@ -1239,7 +1319,7 @@ _nm_dbus_manager_obj_notify (NMDBusObject *obj,
 		}
 
 		g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
-		g_dbus_connection_emit_signal (priv->connection,
+		g_dbus_connection_emit_signal (priv->main_dbus_connection,
 		                               NULL,
 		                               obj->internal.path,
 		                               "org.freedesktop.DBus.Properties",
@@ -1255,7 +1335,7 @@ _nm_dbus_manager_obj_notify (NMDBusObject *obj,
 		/* this is a special interface: it has a legacy PropertiesChanged signal,
 		 * however, contrary to other interfaces with ~regular~ legacy signals,
 		 * we only notify about properties that actually belong to this interface. */
-		g_dbus_connection_emit_signal (priv->connection,
+		g_dbus_connection_emit_signal (priv->main_dbus_connection,
 		                               NULL,
 		                               obj->internal.path,
 		                               nm_interface_info_device_statistics.parent.name,
@@ -1292,7 +1372,7 @@ _nm_dbus_manager_obj_notify (NMDBusObject *obj,
 			const NMDBusInterfaceInfoExtended *interface_info = _reg_data_get_interface_info (reg_data);
 
 			if (interface_info->legacy_property_changed) {
-				g_dbus_connection_emit_signal (priv->connection,
+				g_dbus_connection_emit_signal (priv->main_dbus_connection,
 				                               NULL,
 				                               obj->internal.path,
 				                               interface_info->parent.name,
@@ -1321,12 +1401,12 @@ _nm_dbus_manager_obj_emit_signal (NMDBusObject *obj,
 	self = obj->internal.bus_manager;
 	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 
-	if (!priv->connection || !priv->started) {
+	if (!priv->main_dbus_connection || !priv->started) {
 		nm_g_variant_unref_floating (args);
 		return;
 	}
 
-	g_dbus_connection_emit_signal (priv->connection,
+	g_dbus_connection_emit_signal (priv->main_dbus_connection,
 	                               NULL,
 	                               obj->internal.path,
 	                               interface_info->parent.name,
@@ -1473,7 +1553,7 @@ nm_dbus_manager_get_dbus_connection (NMDBusManager *self)
 {
 	g_return_val_if_fail (NM_IS_DBUS_MANAGER (self), NULL);
 
-	return NM_DBUS_MANAGER_GET_PRIVATE (self)->connection;
+	return NM_DBUS_MANAGER_GET_PRIVATE (self)->main_dbus_connection;
 }
 
 void
@@ -1487,7 +1567,7 @@ nm_dbus_manager_start (NMDBusManager *self,
 	g_return_if_fail (NM_IS_DBUS_MANAGER (self));
 	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 
-	if (!priv->connection) {
+	if (!priv->main_dbus_connection) {
 		/* Do nothing. We're presumably in the configure-and-quit mode. */
 		return;
 	}
@@ -1507,7 +1587,6 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *ret = NULL;
 	gs_unref_object GDBusConnection *connection = NULL;
-	gs_unref_object GDBusProxy *proxy = NULL;
 	guint32 result;
 	guint registration_id;
 
@@ -1530,20 +1609,6 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 
 	g_dbus_connection_set_exit_on_close (connection, FALSE);
 
-	proxy = g_dbus_proxy_new_sync (connection,
-	                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
-	                               | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
-	                               NULL,
-	                               DBUS_SERVICE_DBUS,
-	                               DBUS_PATH_DBUS,
-	                               DBUS_INTERFACE_DBUS,
-	                               NULL,
-	                               &error);
-	if (!proxy) {
-		_LOGE ("fatal failure to initialize D-Bus: %s", error->message);
-		return FALSE;
-	}
-
 	registration_id = g_dbus_connection_register_object (connection,
 	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
 	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
@@ -1556,15 +1621,22 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 		return FALSE;
 	}
 
-	ret = _nm_dbus_proxy_call_sync (proxy,
-	                                "RequestName",
-	                                g_variant_new ("(su)",
-	                                               NM_DBUS_SERVICE,
-	                                               DBUS_NAME_FLAG_DO_NOT_QUEUE),
-	                                G_VARIANT_TYPE ("(u)"),
-	                                G_DBUS_CALL_FLAGS_NONE, -1,
-	                                NULL,
-	                                &error);
+	ret = g_dbus_connection_call_sync (connection,
+	                                   DBUS_SERVICE_DBUS,
+	                                   DBUS_PATH_DBUS,
+	                                   DBUS_INTERFACE_DBUS,
+	                                   "RequestName",
+	                                   g_variant_new ("(su)",
+	                                                  NM_DBUS_SERVICE,
+	                                                  DBUS_NAME_FLAG_DO_NOT_QUEUE),
+	                                   G_VARIANT_TYPE ("(u)"),
+	                                   G_DBUS_CALL_FLAGS_NONE,
+	                                   -1,
+	                                   NULL,
+	                                   &error);
+	if (!ret)
+		return FALSE;
+
 	if (!ret) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s"": %s",
 		       NM_DBUS_SERVICE, error->message);
@@ -1581,8 +1653,7 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	}
 
 	priv->objmgr_registration_id = registration_id;
-	priv->connection = g_steal_pointer (&connection);
-	priv->proxy = g_steal_pointer (&proxy);
+	priv->main_dbus_connection = g_steal_pointer (&connection);
 
 	_LOGI ("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE);
 
@@ -1620,6 +1691,8 @@ nm_dbus_manager_init (NMDBusManager *self)
 	c_list_init (&priv->private_servers_lst_head);
 	c_list_init (&priv->objects_lst_head);
 	priv->objects_by_path = g_hash_table_new ((GHashFunc) _objects_by_path_hash, (GEqualFunc) _objects_by_path_equal);
+
+	c_list_init (&priv->caller_info_lst_head);
 }
 
 static void
@@ -1628,6 +1701,7 @@ dispose (GObject *object)
 	NMDBusManager *self = NM_DBUS_MANAGER (object);
 	NMDBusManagerPrivate *priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 	PrivateServer *s, *s_safe;
+	CallerInfo *caller_info;
 
 	/* All exported NMDBusObject instances keep the manager alive, so we don't
 	 * expect any remaining objects. */
@@ -1640,14 +1714,16 @@ dispose (GObject *object)
 		private_server_free (s);
 
 	if (priv->objmgr_registration_id) {
-		g_dbus_connection_unregister_object (priv->connection,
+		g_dbus_connection_unregister_object (priv->main_dbus_connection,
 		                                     nm_steal_int (&priv->objmgr_registration_id));
 	}
 
-	g_clear_object (&priv->proxy);
-	g_clear_object (&priv->connection);
+	g_clear_object (&priv->main_dbus_connection);
 
 	G_OBJECT_CLASS (nm_dbus_manager_parent_class)->dispose (object);
+
+	while ((caller_info = c_list_first_entry (&priv->caller_info_lst_head, CallerInfo, caller_info_lst)))
+		_caller_info_free (caller_info);
 }
 
 static void
diff --git a/src/nm-dbus-manager.h b/src/nm-dbus-manager.h
index 89acd7c8..2de6ff0b 100644
--- a/src/nm-dbus-manager.h
+++ b/src/nm-dbus-manager.h
@@ -61,8 +61,6 @@ void nm_dbus_manager_stop (NMDBusManager *self);
 
 gboolean nm_dbus_manager_is_stopping (NMDBusManager *self);
 
-GDBusConnection *nm_dbus_manager_get_connection (NMDBusManager *self);
-
 gpointer nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path);
 
 void _nm_dbus_manager_obj_export (NMDBusObject *obj);
@@ -77,7 +75,7 @@ void _nm_dbus_manager_obj_emit_signal (NMDBusObject *obj,
 
 gboolean nm_dbus_manager_get_caller_info (NMDBusManager *self,
                                           GDBusMethodInvocation *context,
-                                          char **out_sender,
+                                          const char **out_sender,
                                           gulong *out_uid,
                                           gulong *out_pid);
 
@@ -97,7 +95,7 @@ gboolean nm_dbus_manager_get_unix_user (NMDBusManager *self,
 gboolean nm_dbus_manager_get_caller_info_from_message (NMDBusManager *self,
                                                        GDBusConnection *connection,
                                                        GDBusMessage *message,
-                                                       char **out_sender,
+                                                       const char **out_sender,
                                                        gulong *out_uid,
                                                        gulong *out_pid);
 
diff --git a/src/nm-dbus-utils.c b/src/nm-dbus-utils.c
index 8e7dd122..7b9c56b6 100644
--- a/src/nm-dbus-utils.c
+++ b/src/nm-dbus-utils.c
@@ -154,7 +154,7 @@ nm_dbus_utils_g_value_set_object_path_from_hash (GValue *value,
 	while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
 		const char *path;
 
-		path = nm_dbus_object_get_path (obj);
+		path = nm_dbus_object_get_path_still_exported (obj);
 		if (!path) {
 			nm_assert (!expect_all_exported);
 			continue;
diff --git a/src/nm-dcb.c b/src/nm-dcb.c
index a63fdf3d..fdcdd8dc 100644
--- a/src/nm-dcb.c
+++ b/src/nm-dcb.c
@@ -37,9 +37,11 @@ do_helper (const char *iface,
            const char *fmt,
            ...)
 {
-	char **argv = NULL, **split = NULL, *cmdline, *errmsg = NULL;
-	gboolean success = FALSE;
-	guint i, u;
+	gs_free const char **split = NULL;
+	gs_free char *cmdline = NULL;
+	gs_free const char **argv = NULL;
+	gsize i;
+	gsize u;
 	va_list args;
 
 	g_return_val_if_fail (fmt != NULL, FALSE);
@@ -48,35 +50,31 @@ do_helper (const char *iface,
 	cmdline = g_strdup_vprintf (fmt, args);
 	va_end (args);
 
-	split = g_strsplit_set (cmdline, " ", 0);
+	split = nm_utils_strsplit_set_with_empty (cmdline, " ");
 	if (!split) {
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
 		             "failure parsing %s command line", helper_names[which]);
-		goto out;
+		return FALSE;
 	}
 
 	/* Allocate space for path, custom arg, interface name, arguments, and NULL */
-	i = u = 0;
-	argv = g_new0 (char *, g_strv_length (split) + 4);
+	i = 0;
+	argv = g_new (const char *, NM_PTRARRAY_LEN (split) + 4);
 	argv[i++] = NULL;  /* Placeholder for dcbtool path */
 	if (which == DCBTOOL) {
 		argv[i++] = "sc";
 		argv[i++] = (char *) iface;
 	}
-	while (u < g_strv_length (split))
-		argv[i++] = split[u++];
+	for (u = 0; split[u]; u++)
+		argv[i++] = split[u];
 	argv[i++] = NULL;
-	success = run_func (argv, which, user_data, error);
-	if (!success && error)
-		g_assert (*error);
-
-out:
-	if (split)
-		g_strfreev (split);
-	g_free (argv);
-	g_free (cmdline);
-	g_free (errmsg);
-	return success;
+
+	if (!run_func ((char **) argv, which, user_data, error)) {
+		g_assert (!error || *error);
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
 gboolean
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index c0ef1bb6..d656edc8 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -23,7 +23,7 @@
 
 #include "nm-dispatcher.h"
 
-#include "nm-dispatcher-api.h"
+#include "nm-libnm-core-aux/nm-dispatcher-api.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-connectivity.h"
diff --git a/src/nm-hostname-manager.c b/src/nm-hostname-manager.c
index d405a320..3fcd8482 100644
--- a/src/nm-hostname-manager.c
+++ b/src/nm-hostname-manager.c
@@ -28,7 +28,7 @@
 #include <selinux/selinux.h>
 #endif
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-utils.h"
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index 1ef9f5ae..812ab83e 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -31,7 +31,7 @@
 #include <signal.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/nm-c-list.h"
 
 #include "main-utils.h"
 #include "NetworkManagerUtils.h"
@@ -186,8 +186,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		 * addresses as /128. The reason for the /128 is to prevent the kernel
 		 * from adding a prefix route for this address. */
 		ifa_flags = 0;
-		if (nm_platform_check_kernel_support (NM_PLATFORM_GET,
-		                                      NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+		if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
 			ifa_flags |= IFA_F_NOPREFIXROUTE;
 			if (NM_IN_SET (global_opt.tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
 			                                    NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
@@ -212,8 +211,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		                                  rdata->routes_n,
 		                                  RT_TABLE_MAIN,
 		                                  global_opt.priority_v6,
-		                                  nm_platform_check_kernel_support (NM_PLATFORM_GET,
-		                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
+		                                  nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
 	}
 
 	if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 1c06a42c..15088c60 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -27,7 +27,7 @@
 #include <resolv.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "nm-utils.h"
 #include "platform/nmp-object.h"
@@ -115,13 +115,13 @@ _nm_ip_config_add_obj (NMDedupMultiIndex *multi_idx,
 	if (!obj_new) {
 		nm_assert (pl_new);
 		obj_new = nmp_object_stackinit (&obj_new_stackinit, idx_type->obj_type, pl_new);
-		obj_new_stackinit.object.ifindex = ifindex;
+		NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (&obj_new_stackinit)->ifindex = ifindex;
 	} else {
 		nm_assert (!pl_new);
 		nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
-		if (obj_new->object.ifindex != ifindex) {
+		if (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_new)->ifindex != ifindex) {
 			obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
-			obj_new_stackinit.object.ifindex = ifindex;
+			NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (&obj_new_stackinit)->ifindex = ifindex;
 		}
 	}
 	nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
@@ -859,15 +859,12 @@ _nm_ip_config_merge_route_attributes (int addr_family,
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TABLE, table, UINT32, uint32, 0);
 	r->table_coerced = nm_platform_route_table_coerce (table ?: (route_table ?: RT_TABLE_MAIN));
 
-	if (addr_family == AF_INET) {
+	if (addr_family == AF_INET)
 		GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TOS,        r4->tos,           BYTE,     byte, 0);
-		GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_ONLINK,     onlink,            BOOLEAN,  boolean, FALSE);
-	} else
-		onlink = FALSE;
 
-	r->r_rtm_flags = 0;
-	if (onlink)
-		r->r_rtm_flags = RTNH_F_ONLINK;
+	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_ONLINK,         onlink,            BOOLEAN,  boolean, FALSE);
+
+	r->r_rtm_flags = ((onlink) ? (unsigned) RTNH_F_ONLINK : 0u);
 
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW,         r->window,         UINT32,   uint32, 0);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_CWND,           r->cwnd,           UINT32,   uint32, 0);
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 6b4bfd64..6d89903f 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -25,7 +25,7 @@
 
 #include "nm-setting-ip4-config.h"
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 #include "platform/nmp-object.h"
 
 /*****************************************************************************/
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 99a9ff88..baef60bd 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -27,7 +27,7 @@
 #include <resolv.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 #include "nm-utils.h"
 #include "platform/nmp-object.h"
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 557e3796..86786ea8 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -25,7 +25,7 @@
 
 #include "nm-setting-ip6-config.h"
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 #include "platform/nmp-object.h"
 
 /*****************************************************************************/
diff --git a/src/nm-logging.c b/src/nm-logging.c
index 1ee94645..48e51421 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -37,7 +37,7 @@
 #include <systemd/sd-journal.h>
 #endif
 
-#include "nm-utils/nm-time-utils.h"
+#include "nm-glib-aux/nm-time-utils.h"
 #include "nm-errors.h"
 
 /*****************************************************************************/
@@ -302,7 +302,8 @@ nm_logging_setup (const char  *level,
 	NMLogDomain new_log_state[_LOGL_N_REAL];
 	NMLogLevel cur_log_level;
 	NMLogLevel new_log_level;
-	char **tmp, **iter;
+	gs_free const char **domains_v = NULL;
+	gsize i_d;
 	int i;
 	gboolean had_platform_debug;
 	gs_free char *domains_free = NULL;
@@ -337,28 +338,24 @@ nm_logging_setup (const char  *level,
 		}
 	}
 
-	tmp = g_strsplit_set (domains, ", ", 0);
-	for (iter = tmp; iter && *iter; iter++) {
+	domains_v = nm_utils_strsplit_set (domains, ", ");
+	for (i_d = 0; domains_v && domains_v[i_d]; i_d++) {
+		const char *s = domains_v[i_d];
+		const char *p;
 		const LogDesc *diter;
 		NMLogLevel domain_log_level;
 		NMLogDomain bits;
-		char *p;
 
 		/* LOGD_VPN_PLUGIN is protected, that is, when setting ALL or DEFAULT,
 		 * it does not enable the verbose levels DEBUG and TRACE, because that
 		 * may expose sensitive data. */
 		NMLogDomain protect = LOGD_NONE;
 
-		if (!strlen (*iter))
-			continue;
-
-		p = strchr (*iter, ':');
+		p = strchr (s, ':');
 		if (p) {
-			*p = '\0';
-			if (!match_log_level (p + 1, &domain_log_level, error)) {
-				g_strfreev (tmp);
+			*((char *) p) = '\0';
+			if (!match_log_level (p + 1, &domain_log_level, error))
 				return FALSE;
-			}
 		} else
 			domain_log_level = new_log_level;
 
@@ -372,26 +369,26 @@ nm_logging_setup (const char  *level,
 		}
 
 		/* Check for combined domains */
-		if (!g_ascii_strcasecmp (*iter, LOGD_ALL_STRING)) {
+		if (!g_ascii_strcasecmp (s, LOGD_ALL_STRING)) {
 			bits = LOGD_ALL;
 			protect = LOGD_VPN_PLUGIN;
-		} else if (!g_ascii_strcasecmp (*iter, LOGD_DEFAULT_STRING)) {
+		} else if (!g_ascii_strcasecmp (s, LOGD_DEFAULT_STRING)) {
 			bits = LOGD_DEFAULT;
 			protect = LOGD_VPN_PLUGIN;
-		} else if (!g_ascii_strcasecmp (*iter, LOGD_DHCP_STRING))
+		} else if (!g_ascii_strcasecmp (s, LOGD_DHCP_STRING))
 			bits = LOGD_DHCP;
-		else if (!g_ascii_strcasecmp (*iter, LOGD_IP_STRING))
+		else if (!g_ascii_strcasecmp (s, LOGD_IP_STRING))
 			bits = LOGD_IP;
 
 		/* Check for compatibility domains */
-		else if (!g_ascii_strcasecmp (*iter, "HW"))
+		else if (!g_ascii_strcasecmp (s, "HW"))
 			bits = LOGD_PLATFORM;
-		else if (!g_ascii_strcasecmp (*iter, "WIMAX"))
+		else if (!g_ascii_strcasecmp (s, "WIMAX"))
 			continue;
 
 		else {
 			for (diter = &domain_desc[0]; diter->name; diter++) {
-				if (!g_ascii_strcasecmp (diter->name, *iter)) {
+				if (!g_ascii_strcasecmp (diter->name, s)) {
 					bits = diter->num;
 					break;
 				}
@@ -400,7 +397,7 @@ nm_logging_setup (const char  *level,
 			if (!bits) {
 				if (!bad_domains) {
 					g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN,
-					             _("Unknown log domain '%s'"), *iter);
+					             _("Unknown log domain '%s'"), s);
 					return FALSE;
 				}
 
@@ -408,7 +405,7 @@ nm_logging_setup (const char  *level,
 					g_string_append (unrecognized, ", ");
 				else
 					unrecognized = g_string_new (NULL);
-				g_string_append (unrecognized, *iter);
+				g_string_append (unrecognized, s);
 				continue;
 			}
 		}
@@ -429,7 +426,6 @@ nm_logging_setup (const char  *level,
 			}
 		}
 	}
-	g_strfreev (tmp);
 
 	g_clear_pointer (&gl_main.logging_domains_to_string, g_free);
 
diff --git a/src/nm-logging.h b/src/nm-logging.h
index a824890a..a0f4ad73 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -26,7 +26,7 @@
 #error nm-test-utils.h must be included as last header
 #endif
 
-#include "nm-utils/nm-logging-fwd.h"
+#include "nm-glib-aux/nm-logging-fwd.h"
 
 #define NM_LOG_CONFIG_BACKEND_DEBUG   "debug"
 #define NM_LOG_CONFIG_BACKEND_SYSLOG  "syslog"
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 0bf6a751..40e068ac 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -27,9 +27,9 @@
 #include <fcntl.h>
 #include <unistd.h>
 
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/nm-c-list.h"
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-manager.h"
 #include "vpn/nm-vpn-manager.h"
 #include "devices/nm-device.h"
@@ -54,7 +54,7 @@
 #include "nm-core-internal.h"
 #include "nm-config.h"
 #include "nm-audit-manager.h"
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 #include "nm-checkpoint.h"
 #include "nm-checkpoint-manager.h"
 #include "nm-dbus-object.h"
@@ -303,6 +303,8 @@ static const GDBusSignalInfo signal_info_state_changed;
 static const GDBusSignalInfo signal_info_device_added;
 static const GDBusSignalInfo signal_info_device_removed;
 
+static void update_connectivity_value (NMManager *self);
+
 static gboolean add_device (NMManager *self, NMDevice *device, GError **error);
 
 static void _emit_device_added_removed (NMManager *self,
@@ -1700,6 +1702,8 @@ remove_device (NMManager *self,
 	g_signal_emit (self, signals[INTERNAL_DEVICE_REMOVED], 0, device);
 	_notify (self, PROP_ALL_DEVICES);
 
+	update_connectivity_value (self);
+
 	nm_dbus_object_clear_and_unexport (&device);
 
 	check_if_startup_complete (self);
@@ -1991,6 +1995,13 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 		g_object_unref (device);
 	}
 
+	if (!nm_device_check_unrealized_device_managed (device)) {
+		_LOG3D (LOGD_DEVICE, connection,
+		        "skip activation because virtual device '%s' is unmanaged",
+		        nm_device_get_iface (device));
+		return device;
+	}
+
 	/* Create backing resources if the device has any autoconnect connections */
 	connections = nm_settings_get_connections_clone (priv->settings, NULL,
 	                                                 NULL, NULL,
@@ -2924,6 +2935,12 @@ device_connectivity_changed (NMDevice *device,
                              GParamSpec *pspec,
                              NMManager *self)
 {
+	update_connectivity_value (self);
+}
+
+static void
+update_connectivity_value (NMManager *self)
+{
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMConnectivityState best_state;
 
@@ -8283,8 +8300,8 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	signals[INTERNAL_DEVICE_ADDED] =
 	    g_signal_new (NM_MANAGER_INTERNAL_DEVICE_ADDED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST, 0,
-	                  NULL, NULL, NULL,
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
 	/* emitted only for realized devices when a device
@@ -8300,8 +8317,8 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	signals[INTERNAL_DEVICE_REMOVED] =
 	    g_signal_new (NM_MANAGER_INTERNAL_DEVICE_REMOVED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST, 0,
-	                  NULL, NULL, NULL,
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
 	signals[ACTIVE_CONNECTION_ADDED] =
diff --git a/src/nm-netns.c b/src/nm-netns.c
index 5952a490..650578af 100644
--- a/src/nm-netns.c
+++ b/src/nm-netns.c
@@ -22,12 +22,13 @@
 
 #include "nm-netns.h"
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
+#include "NetworkManagerUtils.h"
+#include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "platform/nmp-netns.h"
-#include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
+#include "platform/nmp-rules-manager.h"
 
 /*****************************************************************************/
 
@@ -38,6 +39,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 typedef struct {
 	NMPlatform *platform;
 	NMPNetns *platform_netns;
+	NMPRulesManager *rules_manager;
 } NMNetnsPrivate;
 
 struct _NMNetns {
@@ -71,6 +73,12 @@ nm_netns_get_platform (NMNetns *self)
 	return NM_NETNS_GET_PRIVATE (self)->platform;
 }
 
+NMPRulesManager *
+nm_netns_get_rules_manager (NMNetns *self)
+{
+	return NM_NETNS_GET_PRIVATE (self)->rules_manager;
+}
+
 NMDedupMultiIndex *
 nm_netns_get_multi_idx (NMNetns *self)
 {
@@ -118,6 +126,20 @@ constructed (GObject *object)
 
 	priv->platform_netns = nm_platform_netns_get (priv->platform);
 
+	priv->rules_manager = nmp_rules_manager_new (priv->platform);
+
+	/* Weakly track the default rules and rules that were added
+	 * outside of NetworkManager. */
+	nmp_rules_manager_track_default (priv->rules_manager,
+	                                 AF_UNSPEC,
+	                                 0,
+	                                 nm_netns_parent_class /* static dummy user-tag */);
+	nmp_rules_manager_track_from_platform (priv->rules_manager,
+	                                       NULL,
+	                                       AF_UNSPEC,
+	                                       0,
+	                                       nm_netns_parent_class /* static dummy user-tag */);
+
 	G_OBJECT_CLASS (nm_netns_parent_class)->constructed (object);
 }
 
@@ -137,6 +159,8 @@ dispose (GObject *object)
 
 	g_clear_object (&priv->platform);
 
+	nm_clear_pointer (&priv->rules_manager, nmp_rules_manager_unref);
+
 	G_OBJECT_CLASS (nm_netns_parent_class)->dispose (object);
 }
 
diff --git a/src/nm-netns.h b/src/nm-netns.h
index ae343cce..250d1149 100644
--- a/src/nm-netns.h
+++ b/src/nm-netns.h
@@ -40,6 +40,8 @@ NMNetns *nm_netns_new (NMPlatform *platform);
 NMPlatform *nm_netns_get_platform (NMNetns *self);
 NMPNetns *nm_netns_get_platform_netns (NMNetns *self);
 
+struct _NMPRulesManager *nm_netns_get_rules_manager (NMNetns *self);
+
 struct _NMDedupMultiIndex *nm_netns_get_multi_idx (NMNetns *self);
 
 #define NM_NETNS_GET (nm_netns_get ())
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 540f4065..1faba5c7 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -2588,7 +2588,7 @@ static void
 nm_policy_init (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	const char *hostname_mode;
+	gs_free char *hostname_mode = NULL;
 
 	c_list_init (&priv->pending_activation_checks);
 
diff --git a/src/nm-rfkill-manager.c b/src/nm-rfkill-manager.c
index 534ad369..cf2e7fe9 100644
--- a/src/nm-rfkill-manager.c
+++ b/src/nm-rfkill-manager.c
@@ -24,7 +24,7 @@
 
 #include <libudev.h>
 
-#include "nm-utils/nm-udev-utils.h"
+#include "nm-udev-aux/nm-udev-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index dbfe71ad..7f8b3327 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -310,7 +310,7 @@ nmtst_platform_ip6_routes_equal_aptr (const NMPObject *const*a, const NMPlatform
 
 #ifdef __NETWORKMANAGER_IP4_CONFIG_H__
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 static inline NMIP4Config *
 nmtst_ip4_config_new (int ifindex)
@@ -324,7 +324,7 @@ nmtst_ip4_config_new (int ifindex)
 
 #ifdef __NETWORKMANAGER_IP6_CONFIG_H__
 
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 
 static inline NMIP6Config *
 nmtst_ip6_config_new (int ifindex)
diff --git a/src/nm-types.h b/src/nm-types.h
index b6b49028..03ee9929 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -119,15 +119,16 @@ NM_IS_IP_CONFIG_SOURCE_RTPROT (NMIPConfigSource source)
 }
 
 /* platform */
-typedef struct _NMPlatform           NMPlatform;
-typedef struct _NMPlatformObject     NMPlatformObject;
-typedef struct _NMPlatformIP4Address NMPlatformIP4Address;
-typedef struct _NMPlatformIP4Route   NMPlatformIP4Route;
-typedef struct _NMPlatformIP6Address NMPlatformIP6Address;
-typedef struct _NMPlatformIP6Route   NMPlatformIP6Route;
-typedef struct _NMPlatformLink       NMPlatformLink;
-typedef struct _NMPNetns             NMPNetns;
-typedef struct _NMPObject            NMPObject;
+typedef struct _NMPlatform               NMPlatform;
+typedef struct _NMPlatformObject         NMPlatformObject;
+typedef struct _NMPlatformObjWithIfindex NMPlatformObjWithIfindex;
+typedef struct _NMPlatformIP4Address     NMPlatformIP4Address;
+typedef struct _NMPlatformIP4Route       NMPlatformIP4Route;
+typedef struct _NMPlatformIP6Address     NMPlatformIP6Address;
+typedef struct _NMPlatformIP6Route       NMPlatformIP6Route;
+typedef struct _NMPlatformLink           NMPlatformLink;
+typedef struct _NMPNetns                 NMPNetns;
+typedef struct _NMPObject                NMPObject;
 
 typedef enum {
 	/* Please don't interpret type numbers outside nm-platform and use functions
@@ -192,6 +193,7 @@ typedef enum {
 	NMP_OBJECT_TYPE_IP6_ADDRESS,
 	NMP_OBJECT_TYPE_IP4_ROUTE,
 	NMP_OBJECT_TYPE_IP6_ROUTE,
+	NMP_OBJECT_TYPE_ROUTING_RULE,
 
 	NMP_OBJECT_TYPE_QDISC,
 
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 30466159..40a85390 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -154,10 +154,17 @@ static char *
 sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *path)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	const char *v;
 
 	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
 
-	return g_strdup (g_hash_table_lookup (priv->options, path));
+	v = g_hash_table_lookup (priv->options, path);
+	if (!v) {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	return g_strdup (v);
 }
 
 static NMFakePlatformLink *
@@ -1119,7 +1126,7 @@ ipx_route_delete (NMPlatform *platform,
 		g_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
 		                                                NMP_OBJECT_TYPE_IP6_ROUTE));
 		g_assert (ifindex == -1);
-		ifindex = obj->object.ifindex;
+		ifindex = NMP_OBJECT_CAST_IP_ROUTE (obj)->ifindex;
 		obj_type = NMP_OBJECT_GET_TYPE (obj);
 	} else {
 		g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
@@ -1394,6 +1401,10 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMPlatformClass *platform_class = NM_PLATFORM_CLASS (klass);
+	NMPlatformKernelSupportType kernel_support;
+
+	for (kernel_support = 0; kernel_support < _NM_PLATFORM_KERNEL_SUPPORT_NUM; kernel_support++)
+		_nm_platform_kernel_support_init (kernel_support, -1);
 
 	object_class->finalize = finalize;
 
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 2f5c75b0..d4b01152 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -26,8 +26,10 @@
 #include <endian.h>
 #include <fcntl.h>
 #include <libudev.h>
+#include <linux/fib_rules.h>
 #include <linux/ip.h>
 #include <linux/if_arp.h>
+#include <linux/if_bridge.h>
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
@@ -39,12 +41,14 @@
 #include <sys/socket.h>
 #include <unistd.h>
 
+#include "nm-std-aux/unaligned.h"
+
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
-#include "nm-utils/nm-secret-utils.h"
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/nm-secret-utils.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
@@ -54,9 +58,8 @@
 #include "wifi/nm-wifi-utils.h"
 #include "wifi/nm-wifi-utils-wext.h"
 #include "wpan/nm-wpan-utils.h"
-#include "nm-utils/unaligned.h"
-#include "nm-utils/nm-io-utils.h"
-#include "nm-utils/nm-udev-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
+#include "nm-udev-aux/nm-udev-utils.h"
 
 /*****************************************************************************/
 
@@ -140,8 +143,6 @@ enum {
 #define __IFLA_TUN_MAX                  10
 #define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1)
 
-static const gboolean RTA_PREF_SUPPORTED_AT_COMPILETIME = (RTA_MAX >= 20 /* RTA_PREF */);
-
 G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 #define RTA_PREF                        20
 #undef  RTA_MAX
@@ -164,6 +165,19 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
+#define FRA_TUN_ID              12
+#define FRA_SUPPRESS_IFGROUP    13
+#define FRA_SUPPRESS_PREFIXLEN  14
+#define FRA_PAD                 18
+#define FRA_L3MDEV              19
+#define FRA_UID_RANGE           20
+#define FRA_PROTOCOL            21
+#define FRA_IP_PROTO            22
+#define FRA_SPORT_RANGE         23
+#define FRA_DPORT_RANGE         24
+
+/*****************************************************************************/
+
 #define IFLA_MACSEC_UNSPEC              0
 #define IFLA_MACSEC_SCI                 1
 #define IFLA_MACSEC_PORT                2
@@ -263,6 +277,14 @@ struct _ifla_vf_vlan_info {
 
 /*****************************************************************************/
 
+/* Appeared in in kernel 4.0 dated April 12, 2015 */
+#ifndef BRIDGE_VLAN_INFO_RANGE_BEGIN
+#define BRIDGE_VLAN_INFO_RANGE_BEGIN    (1 << 3) /* VLAN is start of vlan range */
+#define BRIDGE_VLAN_INFO_RANGE_END      (1 << 4) /* VLAN is end of vlan range */
+#endif
+
+/*****************************************************************************/
+
 typedef enum {
 	INFINIBAND_ACTION_CREATE_CHILD,
 	INFINIBAND_ACTION_DELETE_CHILD,
@@ -283,41 +305,64 @@ typedef struct {
 	};
 } ChangeLinkData;
 
-enum {
-	DELAYED_ACTION_IDX_REFRESH_ALL_LINKS,
-	DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES,
-	DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES,
-	DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES,
-	DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES,
-	DELAYED_ACTION_IDX_REFRESH_ALL_QDISCS,
-	DELAYED_ACTION_IDX_REFRESH_ALL_TFILTERS,
-	_DELAYED_ACTION_IDX_REFRESH_ALL_NUM,
-};
+typedef enum {
+	_REFRESH_ALL_TYPE_FIRST            = 0,
+
+	REFRESH_ALL_TYPE_LINKS             = 0,
+	REFRESH_ALL_TYPE_IP4_ADDRESSES     = 1,
+	REFRESH_ALL_TYPE_IP6_ADDRESSES     = 2,
+	REFRESH_ALL_TYPE_IP4_ROUTES        = 3,
+	REFRESH_ALL_TYPE_IP6_ROUTES        = 4,
+	REFRESH_ALL_TYPE_ROUTING_RULES_IP4 = 5,
+	REFRESH_ALL_TYPE_ROUTING_RULES_IP6 = 6,
+	REFRESH_ALL_TYPE_QDISCS            = 7,
+	REFRESH_ALL_TYPE_TFILTERS          = 8,
+
+	_REFRESH_ALL_TYPE_NUM,
+} RefreshAllType;
+
+typedef struct {
+	NMPObjectType obj_type;
+
+	/* for NLM_F_DUMP, which address family to request. */
+	int addr_family;
+} RefreshAllInfo;
 
 typedef enum {
-	DELAYED_ACTION_TYPE_NONE                        = 0,
-	DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS           = (1LL << /* 0 */ DELAYED_ACTION_IDX_REFRESH_ALL_LINKS),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES   = (1LL << /* 1 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES   = (1LL << /* 2 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES      = (1LL << /* 3 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES      = (1LL << /* 4 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS          = (1LL << /* 5 */ DELAYED_ACTION_IDX_REFRESH_ALL_QDISCS),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS        = (1LL << /* 6 */ DELAYED_ACTION_IDX_REFRESH_ALL_TFILTERS),
-	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL <<    7),
-	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL <<   11),
-	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL <<   12),
-	DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE        = (1LL <<   13),
+	DELAYED_ACTION_TYPE_NONE                          = 0,
+
+#define F(val, name) ((sizeof (char[(((val)) == (name)) ? 1 : -1]) * 0) + (val))
+	DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS             = 1 << F (0, REFRESH_ALL_TYPE_LINKS),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES     = 1 << F (1, REFRESH_ALL_TYPE_IP4_ADDRESSES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES     = 1 << F (2, REFRESH_ALL_TYPE_IP6_ADDRESSES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES        = 1 << F (3, REFRESH_ALL_TYPE_IP4_ROUTES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES        = 1 << F (4, REFRESH_ALL_TYPE_IP6_ROUTES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 = 1 << F (5, REFRESH_ALL_TYPE_ROUTING_RULES_IP4),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6 = 1 << F (6, REFRESH_ALL_TYPE_ROUTING_RULES_IP6),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS            = 1 << F (7, REFRESH_ALL_TYPE_QDISCS),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS          = 1 << F (8, REFRESH_ALL_TYPE_TFILTERS),
+#undef F
+
+	DELAYED_ACTION_TYPE_REFRESH_LINK                  = 1 <<  9,
+	DELAYED_ACTION_TYPE_MASTER_CONNECTED              = 1 << 10,
+	DELAYED_ACTION_TYPE_READ_NETLINK                  = 1 << 11,
+	DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE          = 1 << 12,
+
 	__DELAYED_ACTION_TYPE_MAX,
 
-	DELAYED_ACTION_TYPE_REFRESH_ALL                 = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
+	DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL = DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
 
-	DELAYED_ACTION_TYPE_MAX                         = __DELAYED_ACTION_TYPE_MAX -1,
+	DELAYED_ACTION_TYPE_REFRESH_ALL                   = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
+	                                                    DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
+
+	DELAYED_ACTION_TYPE_MAX                           = __DELAYED_ACTION_TYPE_MAX -1,
 } DelayedActionType;
 
 #define FOR_EACH_DELAYED_ACTION(iflags, flags_all) \
@@ -387,7 +432,7 @@ typedef struct {
 	GIOChannel *event_channel;
 	guint event_id;
 
-	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+	guint32 pruning[_REFRESH_ALL_TYPE_NUM];
 
 	GHashTable *sysctl_get_prev_values;
 	CList sysctl_list;
@@ -401,7 +446,7 @@ typedef struct {
 
 		/* counter that a refresh all action is in progress, separated
 		 * by type. */
-		int refresh_all_in_progress[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+		int refresh_all_in_progress[_REFRESH_ALL_TYPE_NUM];
 
 		GPtrArray *list_master_connected;
 		GPtrArray *list_refresh_link;
@@ -536,119 +581,6 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result,
 	return buf0;
 }
 
-/*****************************************************************************
- * Support IFLA_INET6_ADDR_GEN_MODE
- *****************************************************************************/
-
-static int _support_user_ipv6ll = 0;
-#define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0))
-
-static void
-_support_user_ipv6ll_detect (struct nlattr **tb)
-{
-	gboolean supported;
-
-	nm_assert (_support_user_ipv6ll_still_undecided ());
-
-	/* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */
-	supported = !!tb[IFLA_INET6_ADDR_GEN_MODE];
-	_support_user_ipv6ll = supported ? 1 : -1;
-	_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s",
-	        supported ? "detected" : "not detected");
-}
-
-static gboolean
-_support_user_ipv6ll_get (void)
-{
-	if (_support_user_ipv6ll_still_undecided ()) {
-		_support_user_ipv6ll = 1;
-		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s", "failed to detect; assume support");
-	}
-	return _support_user_ipv6ll >= 0;
-}
-
-/*****************************************************************************
- * extended IFA_FLAGS support
- *****************************************************************************/
-
-static int _support_kernel_extended_ifa_flags = 0;
-
-#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == 0))
-
-static void
-_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
-{
-	struct nlmsghdr *msg_hdr;
-	gboolean support;
-
-	nm_assert (_support_kernel_extended_ifa_flags_still_undecided ());
-	nm_assert (msg);
-
-	msg_hdr = nlmsg_hdr (msg);
-
-	nm_assert (msg_hdr && msg_hdr->nlmsg_type == RTM_NEWADDR);
-
-	/* 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))
-		return;
-
-	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
-	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
-	 * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14,
-	 * dated 30 March, 2014.
-	 *
-	 * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy
-	 * way to detect kernel support. */
-	support = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), IFA_FLAGS);
-	_support_kernel_extended_ifa_flags = support ? 1 : -1;
-	_LOG2D ("kernel-support: extended-ifa-flags: %s", support ? "detected" : "not detected");
-}
-
-static gboolean
-_support_kernel_extended_ifa_flags_get (void)
-{
-	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
-		_LOG2D ("kernel-support: extended-ifa-flags: %s", "unable to detect kernel support for handling IPv6 temporary addresses. Assume support");
-		_support_kernel_extended_ifa_flags = 1;
-	}
-	return _support_kernel_extended_ifa_flags >= 0;
-}
-
-/*****************************************************************************
- * Support RTA_PREF
- *****************************************************************************/
-
-static int _support_rta_pref = 0;
-#define _support_rta_pref_still_undecided() (G_UNLIKELY (_support_rta_pref == 0))
-
-static void
-_support_rta_pref_detect (struct nlattr **tb)
-{
-	gboolean supported;
-
-	nm_assert (_support_rta_pref_still_undecided ());
-
-	/* RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */
-	supported = !!tb[RTA_PREF];
-	_support_rta_pref = supported ? 1 : -1;
-	_LOG2D ("kernel-support: RTA_PREF: ability to set router preference for IPv6 routes: %s",
-	        supported ? "detected" : "not detected");
-}
-
-static gboolean
-_support_rta_pref_get (void)
-{
-	if (_support_rta_pref_still_undecided ()) {
-		/* if we couldn't detect support, we fallback on compile-time check, whether
-		 * RTA_PREF is present in the kernel headers. */
-		_support_rta_pref = RTA_PREF_SUPPORTED_AT_COMPILETIME ? 1 : -1;
-		_LOG2D ("kernel-support: RTA_PREF: ability to set router preference for IPv6 routes: %s",
-		        RTA_PREF_SUPPORTED_AT_COMPILETIME ? "assume support" : "assume no support");
-	}
-	return _support_rta_pref >= 0;
-}
-
 /******************************************************************
  * Various utilities
  ******************************************************************/
@@ -1200,8 +1132,11 @@ _parse_af_inet6 (NMPlatform *platform,
 	/* Hack to detect support addrgenmode of the kernel. We only parse
 	 * netlink messages that we receive from kernel, hence this check
 	 * is valid. */
-	if (_support_user_ipv6ll_still_undecided ())
-		_support_user_ipv6ll_detect (tb);
+	if (!_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
+		/* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */
+		_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL,
+		                                  tb[IFLA_INET6_ADDR_GEN_MODE] ? 1 : -1);
+	}
 
 	if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
 		i6_addr_gen_mode_inv = _nm_platform_uint8_inv (nla_get_u8 (tb[IFLA_INET6_ADDR_GEN_MODE]));
@@ -3325,9 +3260,13 @@ rta_multipath_done:
 	obj->ip_route.lock_mtu      = NM_FLAGS_HAS (lock, 1 << RTAX_MTU);
 
 	if (!is_v4) {
-		/* Detect support for RTA_PREF by inspecting the netlink message. */
-		if (_support_rta_pref_still_undecided ())
-			_support_rta_pref_detect (tb);
+
+		if (!_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)) {
+			/* Detect support for RTA_PREF by inspecting the netlink message.
+			 * RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */
+			_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF,
+			                                  tb[RTA_PREF] ? 1 : -1);
+		}
 
 		if (tb[RTA_PREF])
 			obj->ip6_route.rt_pref = nla_get_u8 (tb[RTA_PREF]);
@@ -3340,6 +3279,204 @@ rta_multipath_done:
 }
 
 static NMPObject *
+_new_from_nl_routing_rule (struct nlmsghdr *nlh, gboolean id_only)
+{
+	static const struct nla_policy policy[] = {
+		[FRA_UNSPEC]             = { },
+		[FRA_DST]                = { /* struct in_addr, struct in6_addr */ },
+		[FRA_SRC]                = { /* struct in_addr, struct in6_addr */ },
+		[FRA_IIFNAME]            = { .type   = NLA_STRING,
+		                             .maxlen = IFNAMSIZ, },
+		[FRA_GOTO]               = { .type   = NLA_U32, },
+		[FRA_UNUSED2]            = { },
+		[FRA_PRIORITY]           = { .type   = NLA_U32, },
+		[FRA_UNUSED3]            = { },
+		[FRA_UNUSED4]            = { },
+		[FRA_UNUSED5]            = { },
+		[FRA_FWMARK]             = { .type   = NLA_U32, },
+		[FRA_FLOW]               = { .type   = NLA_U32, },
+		[FRA_TUN_ID]             = { .type   = NLA_U64, },
+		[FRA_SUPPRESS_IFGROUP]   = { .type   = NLA_U32, },
+		[FRA_SUPPRESS_PREFIXLEN] = { .type   = NLA_U32, },
+		[FRA_TABLE]              = { .type   = NLA_U32, },
+		[FRA_FWMASK]             = { .type   = NLA_U32, },
+		[FRA_OIFNAME]            = { .type   = NLA_STRING,
+		                             .maxlen = IFNAMSIZ, },
+		[FRA_PAD]                = { .type   = NLA_U32, },
+		[FRA_L3MDEV]             = { .type   = NLA_U8, },
+		[FRA_UID_RANGE]          = { .minlen = sizeof(NMFibRuleUidRange),
+		                             .maxlen = sizeof(NMFibRuleUidRange), },
+		[FRA_PROTOCOL]           = { .type   = NLA_U8, },
+		[FRA_IP_PROTO]           = { .type   = NLA_U8, },
+		[FRA_SPORT_RANGE]        = { .minlen = sizeof(NMFibRulePortRange),
+		                             .maxlen = sizeof(NMFibRulePortRange), },
+		[FRA_DPORT_RANGE]        = { .minlen = sizeof(NMFibRulePortRange),
+		                             .maxlen = sizeof(NMFibRulePortRange), },
+	};
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	const struct fib_rule_hdr *frh;
+	NMPlatformRoutingRule *props;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	int addr_family;
+	guint8 addr_size;
+
+	if (nlmsg_parse_arr (nlh, sizeof (*frh), tb, policy) < 0)
+		return NULL;
+
+	frh = nlmsg_data (nlh);
+
+	addr_family = frh->family;
+
+	if (!NM_IN_SET (addr_family, AF_INET, AF_INET6)) {
+		/* we don't care about other address families. */
+		return NULL;
+	}
+
+	addr_size = nm_utils_addr_family_to_size (addr_family);
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE, NULL);
+	props = &obj->routing_rule;
+
+	props->addr_family = addr_family;
+	props->action      = frh->action;
+	props->flags       = frh->flags;
+	props->tos         = frh->tos;
+
+	props->table =   tb[FRA_TABLE]
+	               ? nla_get_u32 (tb[FRA_TABLE])
+	               : frh->table;
+
+	if (tb[FRA_SUPPRESS_PREFIXLEN])
+		props->suppress_prefixlen_inverse = ~nla_get_u32 (tb[FRA_SUPPRESS_PREFIXLEN]);
+
+	if (tb[FRA_SUPPRESS_IFGROUP])
+		props->suppress_ifgroup_inverse = ~nla_get_u32 (tb[FRA_SUPPRESS_IFGROUP]);
+
+	if (tb[FRA_IIFNAME])
+		nla_strlcpy (props->iifname, tb[FRA_IIFNAME], sizeof (props->iifname));
+
+	if (tb[FRA_OIFNAME])
+		nla_strlcpy (props->oifname, tb[FRA_OIFNAME], sizeof (props->oifname));
+
+	if (tb[FRA_PRIORITY])
+		props->priority = nla_get_u32 (tb[FRA_PRIORITY]);
+
+	if (tb[FRA_FWMARK])
+		props->fwmark = nla_get_u32 (tb[FRA_FWMARK]);
+
+	if (tb[FRA_FWMASK])
+		props->fwmask = nla_get_u32 (tb[FRA_FWMASK]);
+
+	if (tb[FRA_GOTO])
+		props->goto_target = nla_get_u32 (tb[FRA_GOTO]);
+
+	props->src_len = frh->src_len;
+	if (props->src_len > addr_size * 8)
+		return NULL;
+	if (!tb[FRA_SRC]) {
+		if (props->src_len > 0)
+			return NULL;
+	} else if (!nm_ip_addr_set_from_untrusted (addr_family,
+	                                           &props->src,
+	                                           nla_data (tb[FRA_SRC]),
+	                                           nla_len (tb[FRA_SRC]),
+	                                           NULL))
+		return NULL;
+
+	props->dst_len = frh->dst_len;
+	if (props->dst_len > addr_size * 8)
+		return NULL;
+	if (!tb[FRA_DST]) {
+		if (props->dst_len > 0)
+			return NULL;
+	} else if (!nm_ip_addr_set_from_untrusted (addr_family,
+	                                           &props->dst,
+	                                           nla_data (tb[FRA_DST]),
+	                                           nla_len (tb[FRA_DST]),
+	                                           NULL))
+		return NULL;
+
+	if (tb[FRA_FLOW])
+		props->flow = nla_get_u32 (tb[FRA_FLOW]);
+
+	if (tb[FRA_TUN_ID])
+		props->tun_id = nla_get_be64 (tb[FRA_TUN_ID]);
+
+	if (tb[FRA_L3MDEV]) {
+
+		if (!_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) {
+			/* support for FRA_L3MDEV was added in 96c63fa7393d0a346acfe5a91e0c7d4c7782641b,
+			 * kernel 4.8, 3 October 2017.
+			 *
+			 * We can only detect support if the attribute is present. A missing attribute
+			 * is not conclusive. */
+			_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV, 1);
+		}
+
+		/* actually, kernel only allows this attribute to be missing or
+		 * "1". Still, encode it as full uint8.
+		 *
+		 * Note that FRA_L3MDEV and FRA_TABLE are mutally exclusive. */
+		props->l3mdev = nla_get_u8 (tb[FRA_L3MDEV]);
+	}
+
+	if (tb[FRA_PROTOCOL])
+		props->protocol = nla_get_u8 (tb[FRA_PROTOCOL]);
+	else
+		nm_assert (props->protocol == RTPROT_UNSPEC);
+
+	if (!_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)) {
+		/* FRA_PROTOCOL was added in kernel 4.17, dated 3 June, 2018.
+		 * See commit 1b71af6053af1bd2f849e9fda4f71c1e3f145dcf. */
+		_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL,
+		                                  tb[FRA_PROTOCOL] ? 1 : -1);
+	}
+
+	if (tb[FRA_IP_PROTO])
+		props->ip_proto = nla_get_u8 (tb[FRA_IP_PROTO]);
+
+	G_STATIC_ASSERT_EXPR (sizeof (NMFibRulePortRange) == 4);
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMFibRulePortRange, start) == 0);
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMFibRulePortRange, end)   == 2);
+
+	nla_memcpy_checked_size (&props->sport_range, tb[FRA_SPORT_RANGE], sizeof (props->sport_range));
+	nla_memcpy_checked_size (&props->dport_range, tb[FRA_DPORT_RANGE], sizeof (props->dport_range));
+
+	if (!_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) {
+		/* support for FRA_IP_PROTO, FRA_SPORT_RANGE, and FRA_DPORT_RANGE was added together
+		 * by bfff4862653bb96001ab57c1edd6d03f48e5f035, kernel 4.17, 4 June 2018.
+		 *
+		 * Unfortunately, a missing attribute does not tell us anything about support.
+		 * We can only tell for sure when we have support, but not when we don't have.  */
+		if (   tb[FRA_IP_PROTO]
+		    || tb[FRA_SPORT_RANGE]
+		    || tb[FRA_DPORT_RANGE])
+			_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO, 1);
+	}
+
+	G_STATIC_ASSERT_EXPR (sizeof (NMFibRuleUidRange) == 8);
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMFibRuleUidRange, start) == 0);
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMFibRuleUidRange, end) == 4);
+
+	if (tb[FRA_UID_RANGE]) {
+
+		if (!_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) {
+			/* support for FRA_UID_RANGE was added in 622ec2c9d52405973c9f1ca5116eb1c393adfc7d,
+			 * kernel 4.10, 19 February 2017.
+			 *
+			 * We can only detect support if the attribute is present. A missing attribute
+			 * is not conclusive. */
+			_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE, 1);
+		}
+
+		nla_memcpy_checked_size (&props->uid_range, tb[FRA_UID_RANGE], sizeof (props->uid_range));
+		props->uid_range_has = TRUE;
+	}
+
+	return g_steal_pointer (&obj);
+}
+
+static NMPObject *
 _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 {
 	static const struct nla_policy policy[] = {
@@ -3438,6 +3575,10 @@ nmp_object_new_from_nl (NMPlatform *platform, const NMPCache *cache, struct nl_m
 	case RTM_DELROUTE:
 	case RTM_GETROUTE:
 		return _new_from_nl_route (msghdr, id_only);
+	case RTM_NEWRULE:
+	case RTM_DELRULE:
+	case RTM_GETRULE:
+		return _new_from_nl_routing_rule (msghdr, id_only);
 	case RTM_NEWQDISC:
 	case RTM_DELQDISC:
 	case RTM_GETQDISC:
@@ -3642,21 +3783,23 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-_nl_msg_new_link (int nlmsg_type,
-                  int nlmsg_flags,
-                  int ifindex,
-                  const char *ifname,
-                  unsigned flags_mask,
-                  unsigned flags_set)
+_nl_msg_new_link_full (int nlmsg_type,
+                       int nlmsg_flags,
+                       int ifindex,
+                       const char *ifname,
+                       guint8 family,
+                       unsigned flags_mask,
+                       unsigned flags_set)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	const struct ifinfomsg ifi = {
+		.ifi_family = family,
 		.ifi_change = flags_mask,
 		.ifi_flags = flags_set,
 		.ifi_index = ifindex,
 	};
 
-	nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK));
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK, RTM_SETLINK));
 
 	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
 
@@ -3672,6 +3815,15 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
+static struct nl_msg *
+_nl_msg_new_link (int nlmsg_type,
+                  int nlmsg_flags,
+                  int ifindex,
+                  const char *ifname)
+{
+	return _nl_msg_new_link_full (nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0);
+}
+
 /* Copied and modified from libnl3's build_addr_msg(). */
 static struct nl_msg *
 _nl_msg_new_address (int nlmsg_type,
@@ -3796,9 +3948,7 @@ _nl_msg_new_route (int nlmsg_type,
 		             ? nm_platform_route_scope_inv (obj->ip4_route.scope_inv)
 		             : RT_SCOPE_NOWHERE,
 		.rtm_type = RTN_UNICAST,
-		.rtm_flags = obj->ip_route.r_rtm_flags & (is_v4
-		                                          ? (unsigned) (RTNH_F_ONLINK)
-		                                          : (unsigned) 0),
+		.rtm_flags = obj->ip_route.r_rtm_flags & ((unsigned) (RTNH_F_ONLINK)),
 		.rtm_dst_len = obj->ip_route.plen,
 		.rtm_src_len = is_v4
 		               ? 0
@@ -3893,6 +4043,119 @@ nla_put_failure:
 }
 
 static struct nl_msg *
+_nl_msg_new_routing_rule (int nlmsg_type,
+                          int nlmsg_flags,
+                          const NMPlatformRoutingRule *routing_rule)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	const guint8 addr_size = nm_utils_addr_family_to_size (routing_rule->addr_family);
+	guint32 table;
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+
+	table = routing_rule->table;
+
+	if (   NM_IN_SET (routing_rule->addr_family, AF_INET, AF_INET6)
+	    && routing_rule->action == FR_ACT_TO_TBL
+	    && routing_rule->l3mdev == 0
+	    && table == RT_TABLE_UNSPEC) {
+		/* for IPv6, this setting is invalid and rejected by kernel. That's fine.
+		 *
+		 * for IPv4, kernel will automatically assign an unused table. That's not
+		 * fine, because we don't know what we will get.
+		 *
+		 * The caller must not allow that to happen. */
+		nm_assert_not_reached ();
+	}
+
+	{
+		const struct fib_rule_hdr frh = {
+			.family  = routing_rule->addr_family,
+			.src_len = routing_rule->src_len,
+			.dst_len = routing_rule->dst_len,
+			.tos     = routing_rule->tos,
+			.table   = table < 0x100u ? (guint8) table : (guint8) RT_TABLE_UNSPEC,
+			.action  = routing_rule->action,
+
+			/* we only allow setting the "not" flag. */
+			.flags   = routing_rule->flags & ((guint32) FIB_RULE_INVERT),
+		};
+
+		if (nlmsg_append_struct (msg, &frh) < 0)
+			goto nla_put_failure;
+	}
+
+	if (table > G_MAXINT8)
+		NLA_PUT_U32 (msg, FRA_TABLE, table);
+
+	if (routing_rule->suppress_prefixlen_inverse != 0)
+		NLA_PUT_U32 (msg, FRA_SUPPRESS_PREFIXLEN, ~routing_rule->suppress_prefixlen_inverse);
+
+	if (routing_rule->suppress_ifgroup_inverse != 0)
+		NLA_PUT_U32 (msg, FRA_SUPPRESS_IFGROUP, ~routing_rule->suppress_ifgroup_inverse);
+
+	if (routing_rule->iifname[0] != '\0')
+		NLA_PUT_STRING (msg, FRA_IIFNAME, routing_rule->iifname);
+
+	if (routing_rule->oifname[0] != '\0')
+		NLA_PUT_STRING (msg, FRA_OIFNAME, routing_rule->oifname);
+
+	/* we always set the priority and don't support letting kernel pick one. */
+	NLA_PUT_U32 (msg, FRA_PRIORITY, routing_rule->priority);
+
+	if (   routing_rule->fwmark != 0
+	    || routing_rule->fwmask != 0) {
+		NLA_PUT_U32 (msg, FRA_FWMARK, routing_rule->fwmark);
+		NLA_PUT_U32 (msg, FRA_FWMASK, routing_rule->fwmask);
+	}
+
+	if (routing_rule->src_len > 0)
+		NLA_PUT (msg, FRA_SRC, addr_size, &routing_rule->src);
+
+	if (routing_rule->dst_len > 0)
+		NLA_PUT (msg, FRA_DST, addr_size, &routing_rule->dst);
+
+	if (routing_rule->flow != 0) {
+		/* only relevant for IPv4. */
+		NLA_PUT_U32 (msg, FRA_FLOW, routing_rule->flow);
+	}
+
+	if (routing_rule->tun_id != 0)
+		NLA_PUT_U64 (msg, FRA_TUN_ID, htobe64 (routing_rule->tun_id));
+
+	if (routing_rule->l3mdev)
+		NLA_PUT_U8 (msg, FRA_L3MDEV, routing_rule->l3mdev);
+
+	if (routing_rule->protocol != RTPROT_UNSPEC)
+		NLA_PUT_U8 (msg, FRA_PROTOCOL, routing_rule->protocol);
+
+	if (routing_rule->ip_proto != 0)
+		NLA_PUT_U8 (msg, FRA_IP_PROTO, routing_rule->ip_proto);
+
+	if (   routing_rule->sport_range.start
+	    || routing_rule->sport_range.end)
+		NLA_PUT (msg, FRA_SPORT_RANGE, sizeof (routing_rule->sport_range), &routing_rule->sport_range);
+
+	if (   routing_rule->dport_range.start
+	    || routing_rule->dport_range.end)
+		NLA_PUT (msg, FRA_DPORT_RANGE, sizeof (routing_rule->dport_range), &routing_rule->dport_range);
+
+	if (routing_rule->uid_range_has)
+		NLA_PUT (msg, FRA_UID_RANGE, sizeof (routing_rule->uid_range), &routing_rule->uid_range);
+
+	switch (routing_rule->action) {
+	case FR_ACT_GOTO:
+		NLA_PUT_U32 (msg, FRA_GOTO, routing_rule->goto_target);
+		break;
+	}
+
+	return g_steal_pointer (&msg);
+
+nla_put_failure:
+	g_return_val_if_reached (NULL);
+}
+
+static struct nl_msg *
 _nl_msg_new_qdisc (int nlmsg_type,
                    int nlmsg_flags,
                    const NMPlatformQdisc *qdisc)
@@ -4285,22 +4548,31 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
 
 	if (dirfd < 0) {
-		if (!nm_platform_netns_push (platform, &netns))
+		if (!nm_platform_netns_push (platform, &netns)) {
+			errno = EBUSY;
 			return NULL;
+		}
 		pathid = path;
 	}
 
 	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
 	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
 	                                &contents, NULL, &error) < 0) {
-		/* We assume FAILED means EOPNOTSUP */
-		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
-		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NODEV)
-		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED))
-			_LOGD ("error reading %s: %s", pathid, error->message);
-		else
-			_LOGE ("error reading %s: %s", pathid, error->message);
+		NMLogLevel log_level = LOGL_ERR;
+		int errsv = EBUSY;
+
+		if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+			errsv = ENOENT;
+			log_level = LOGL_DEBUG;
+		} else if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NODEV)
+		           || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED)) {
+			/* We assume FAILED means EOPNOTSUP and don't log a error message. */
+			log_level = LOGL_DEBUG;
+		}
+
+		_NMLOG (log_level, "error reading %s: %s", pathid, error->message);
 		g_clear_error (&error);
+		errno = errsv;
 		return NULL;
 	}
 
@@ -4308,96 +4580,145 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 
 	_log_dbg_sysctl_get (platform, pathid, contents);
 
+	/* errno is left undefined (as we don't return NULL). */
 	return contents;
 }
 
 /*****************************************************************************/
 
-static NMPlatformKernelSupportFlags
-check_kernel_support (NMPlatform *platform,
-                      NMPlatformKernelSupportFlags request_flags)
+static void
+process_events (NMPlatform *platform)
 {
-	NMPlatformKernelSupportFlags response = 0;
+	delayed_action_handle_all (platform, TRUE);
+}
 
-	nm_assert (NM_IS_LINUX_PLATFORM (platform));
+/*****************************************************************************/
 
-	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
-		if (_support_kernel_extended_ifa_flags_get ())
-			response |= NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS;
-	}
+static const RefreshAllInfo *
+refresh_all_type_get_info (RefreshAllType refresh_all_type)
+{
+	static const RefreshAllInfo infos[] = {
+#define R(_refresh_all_type, _obj_type, _addr_family) [_refresh_all_type] = { .obj_type = _obj_type, .addr_family = _addr_family, }
+		R (REFRESH_ALL_TYPE_LINKS,             NMP_OBJECT_TYPE_LINK,         AF_UNSPEC),
+		R (REFRESH_ALL_TYPE_IP4_ADDRESSES,     NMP_OBJECT_TYPE_IP4_ADDRESS,  AF_UNSPEC),
+		R (REFRESH_ALL_TYPE_IP6_ADDRESSES,     NMP_OBJECT_TYPE_IP6_ADDRESS,  AF_UNSPEC),
+		R (REFRESH_ALL_TYPE_IP4_ROUTES,        NMP_OBJECT_TYPE_IP4_ROUTE,    AF_UNSPEC),
+		R (REFRESH_ALL_TYPE_IP6_ROUTES,        NMP_OBJECT_TYPE_IP6_ROUTE,    AF_UNSPEC),
+		R (REFRESH_ALL_TYPE_ROUTING_RULES_IP4, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET),
+		R (REFRESH_ALL_TYPE_ROUTING_RULES_IP6, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET6),
+		R (REFRESH_ALL_TYPE_QDISCS,            NMP_OBJECT_TYPE_QDISC,        AF_UNSPEC),
+		R (REFRESH_ALL_TYPE_TFILTERS,          NMP_OBJECT_TYPE_TFILTER,      AF_UNSPEC),
+#undef R
+	};
 
-	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
-		if (_support_user_ipv6ll_get ())
-			response |= NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL;
-	}
+	nm_assert (_NM_INT_NOT_NEGATIVE (refresh_all_type));
+	nm_assert (refresh_all_type < G_N_ELEMENTS (infos));
+	nm_assert (nmp_class_from_type (infos[refresh_all_type].obj_type));
 
-	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF)) {
-		if (_support_rta_pref_get ())
-			response |= NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF;
-	}
+	return &infos[refresh_all_type];
+}
+
+_NM_UTILS_LOOKUP_DEFINE (static, delayed_action_type_to_refresh_all_type, DelayedActionType, RefreshAllType,
+	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (0),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,             REFRESH_ALL_TYPE_LINKS),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES,     REFRESH_ALL_TYPE_IP4_ADDRESSES),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES,     REFRESH_ALL_TYPE_IP6_ADDRESSES),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,        REFRESH_ALL_TYPE_IP4_ROUTES),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,        REFRESH_ALL_TYPE_IP6_ROUTES),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4, REFRESH_ALL_TYPE_ROUTING_RULES_IP4),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, REFRESH_ALL_TYPE_ROUTING_RULES_IP6),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,            REFRESH_ALL_TYPE_QDISCS),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,          REFRESH_ALL_TYPE_TFILTERS),
+	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
+);
+
+static DelayedActionType
+delayed_action_type_from_refresh_all_type (RefreshAllType refresh_all_type)
+{
+	DelayedActionType t;
+
+	nm_assert (refresh_all_type_get_info (refresh_all_type));
 
-	return response;
+	t = (((DelayedActionType) 1) << refresh_all_type);
+
+	nm_assert (refresh_all_type == delayed_action_type_to_refresh_all_type (t));
+
+	return t;
 }
 
-static void
-process_events (NMPlatform *platform)
+static RefreshAllType
+refresh_all_type_from_needle_object (const NMPObject *obj_needle)
 {
-	delayed_action_handle_all (platform, TRUE);
+	switch (NMP_OBJECT_GET_TYPE (obj_needle)) {
+	case NMP_OBJECT_TYPE_LINK:        return REFRESH_ALL_TYPE_LINKS;
+	case NMP_OBJECT_TYPE_IP4_ADDRESS: return REFRESH_ALL_TYPE_IP4_ADDRESSES;
+	case NMP_OBJECT_TYPE_IP6_ADDRESS: return REFRESH_ALL_TYPE_IP6_ADDRESSES;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:   return REFRESH_ALL_TYPE_IP4_ROUTES;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:   return REFRESH_ALL_TYPE_IP6_ROUTES;
+	case NMP_OBJECT_TYPE_QDISC:       return REFRESH_ALL_TYPE_QDISCS;
+	case NMP_OBJECT_TYPE_TFILTER:     return REFRESH_ALL_TYPE_TFILTERS;
+	case NMP_OBJECT_TYPE_ROUTING_RULE:
+		switch (NMP_OBJECT_CAST_ROUTING_RULE (obj_needle)->addr_family) {
+		case AF_INET:  return REFRESH_ALL_TYPE_ROUTING_RULES_IP4;
+		case AF_INET6: return REFRESH_ALL_TYPE_ROUTING_RULES_IP6;
+		}
+		nm_assert_not_reached ();
+		return 0;
+	default:
+		nm_assert_not_reached ();
+		return 0;
+	}
 }
 
-/*****************************************************************************/
+static const NMPLookup *
+refresh_all_type_init_lookup (RefreshAllType refresh_all_type,
+                              NMPLookup *lookup)
+{
+	const RefreshAllInfo *refresh_all_info;
 
-_NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_from_object_type, NMPObjectType, DelayedActionType,
-	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (DELAYED_ACTION_TYPE_NONE),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_LINK,        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP4_ADDRESS, DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP6_ADDRESS, DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP4_ROUTE,   DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP6_ROUTE,   DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_QDISC,       DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS),
-	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_TFILTER,     DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS),
-	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
-);
+	nm_assert (lookup);
 
-_NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_to_object_type, DelayedActionType, NMPObjectType,
-	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (NMP_OBJECT_TYPE_UNKNOWN),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,         NMP_OBJECT_TYPE_LINK),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, NMP_OBJECT_TYPE_IP4_ADDRESS),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,    NMP_OBJECT_TYPE_IP4_ROUTE),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,    NMP_OBJECT_TYPE_IP6_ROUTE),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,        NMP_OBJECT_TYPE_QDISC),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,      NMP_OBJECT_TYPE_TFILTER),
-	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
-);
+	refresh_all_info = refresh_all_type_get_info (refresh_all_type);
 
-_NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_all_to_idx, DelayedActionType, guint,
-	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (0),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,         DELAYED_ACTION_IDX_REFRESH_ALL_LINKS),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,    DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,    DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,        DELAYED_ACTION_IDX_REFRESH_ALL_QDISCS),
-	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,      DELAYED_ACTION_IDX_REFRESH_ALL_TFILTERS),
-	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
-);
+	nm_assert (refresh_all_info);
+
+	if (NM_IN_SET (refresh_all_info->obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)) {
+		return nmp_lookup_init_object_by_addr_family (lookup,
+		                                              refresh_all_info->obj_type,
+		                                              refresh_all_info->addr_family);
+	}
+
+	/* not yet implemented. */
+	nm_assert (refresh_all_info->addr_family == AF_UNSPEC);
+
+	return nmp_lookup_init_obj_type (lookup,
+	                                 refresh_all_info->obj_type);
+}
+
+static DelayedActionType
+delayed_action_refresh_from_needle_object (const NMPObject *obj_needle)
+{
+	return delayed_action_type_from_refresh_all_type (refresh_all_type_from_needle_object (obj_needle));
+}
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (delayed_action_to_string, DelayedActionType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,         "refresh-all-links"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES, "refresh-all-ip4-addresses"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, "refresh-all-ip6-addresses"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,    "refresh-all-ip4-routes"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,    "refresh-all-ip6-routes"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,        "refresh-all-qdiscs"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,      "refresh-all-tfilters"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_LINK,              "refresh-link"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_MASTER_CONNECTED,          "master-connected"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_READ_NETLINK,              "read-netlink"),
-	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE,      "wait-for-nl-response"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,             "refresh-all-links"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES,     "refresh-all-ip4-addresses"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES,     "refresh-all-ip6-addresses"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,        "refresh-all-ip4-routes"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,        "refresh-all-ip6-routes"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4, "refresh-all-routing-rules-ip4"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6, "refresh-all-routing-rules-ip6"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,            "refresh-all-qdiscs"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,          "refresh-all-tfilters"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_LINK,                  "refresh-link"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_MASTER_CONNECTED,              "master-connected"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_READ_NETLINK,                  "read-netlink"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE,          "wait-for-nl-response"),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (DELAYED_ACTION_TYPE_NONE),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (DELAYED_ACTION_TYPE_REFRESH_ALL),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (__DELAYED_ACTION_TYPE_MAX),
 );
 
@@ -4455,6 +4776,7 @@ static gboolean
 delayed_action_refresh_all_in_progress (NMPlatform *platform, DelayedActionType action_type)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	RefreshAllType refresh_all_type;
 
 	nm_assert (nm_utils_is_power_of_two (action_type));
 	nm_assert (NM_FLAGS_ANY (action_type, DELAYED_ACTION_TYPE_REFRESH_ALL));
@@ -4463,10 +4785,8 @@ delayed_action_refresh_all_in_progress (NMPlatform *platform, DelayedActionType
 	if (NM_FLAGS_ANY (priv->delayed_action.flags, action_type))
 		return TRUE;
 
-	if (priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (action_type)] > 0)
-		return TRUE;
-
-	return FALSE;
+	refresh_all_type = delayed_action_type_to_refresh_all_type (action_type);
+	return (priv->delayed_action.refresh_all_in_progress[refresh_all_type] > 0);
 }
 
 static void
@@ -4764,25 +5084,33 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 /*****************************************************************************/
 
 static void
-cache_prune_one_type (NMPlatform *platform, NMPObjectType obj_type)
+cache_prune_one_type (NMPlatform *platform,
+                      const NMPLookup *lookup)
 {
 	NMDedupMultiIter iter;
 	const NMPObject *obj;
 	NMPCacheOpsType cache_op;
-	NMPLookup lookup;
 	NMPCache *cache = nm_platform_get_cache (platform);
 
-	nmp_lookup_init_obj_type (&lookup,
-	                          obj_type);
 	nm_dedup_multi_iter_init (&iter,
 	                          nmp_cache_lookup (cache,
-	                                            &lookup));
+	                                            lookup));
 	while (nm_dedup_multi_iter_next (&iter)) {
-		if (iter.current->dirty) {
+		const NMDedupMultiEntry *main_entry;
+
+		/* we only track the dirty flag for the OBJECT-TYPE index. That means,
+		 * for other lookup types we need to check the dirty flag of the main-entry. */
+		main_entry = nmp_cache_reresolve_main_entry (cache, iter.current, lookup);
+		if (!main_entry->dirty)
+			continue;
+
+		obj = main_entry->obj;
+
+		_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+
+		{
 			nm_auto_nmpobj const NMPObject *obj_old = NULL;
 
-			obj = iter.current->obj;
-			_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
 			cache_op = nmp_cache_remove (cache, obj, TRUE, TRUE, &obj_old);
 			nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
 			cache_on_change (platform, cache_op, obj_old, NULL);
@@ -4795,16 +5123,19 @@ static void
 cache_prune_all (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	DelayedActionType iflags, action_type;
+	RefreshAllType refresh_all_type;
 
-	action_type = DELAYED_ACTION_TYPE_REFRESH_ALL;
-	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
-		bool *p = &priv->pruning[delayed_action_refresh_all_to_idx (iflags)];
+	for (refresh_all_type = _REFRESH_ALL_TYPE_FIRST; refresh_all_type < _REFRESH_ALL_TYPE_NUM; refresh_all_type++) {
+		NMPLookup lookup;
 
-		if (*p) {
-			*p = FALSE;
-			cache_prune_one_type (platform, delayed_action_refresh_to_object_type (iflags));
-		}
+		if (priv->pruning[refresh_all_type] == 0)
+			continue;
+		priv->pruning[refresh_all_type] -= 1;
+		if (priv->pruning[refresh_all_type] > 0)
+			continue;
+		refresh_all_type_init_lookup (refresh_all_type,
+		                              &lookup);
+		cache_prune_one_type (platform, &lookup);
 	}
 }
 
@@ -4876,6 +5207,7 @@ cache_on_change (NMPlatform *platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 				                         NULL);
@@ -5166,7 +5498,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 
 		entry = nmp_cache_lookup_entry_link (nm_platform_get_cache (platform), ifindex);
 		if (entry) {
-			priv->pruning[DELAYED_ACTION_IDX_REFRESH_ALL_LINKS] = TRUE;
+			priv->pruning[REFRESH_ALL_TYPE_LINKS] += 1;
 			nm_dedup_multi_entry_set_dirty (entry, TRUE);
 		}
 	}
@@ -5176,9 +5508,7 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 	nlmsg = _nl_msg_new_link (RTM_GETLINK,
 	                          0,
 	                          ifindex,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (nlmsg) {
 		nle = _nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 		if (nle < 0) {
@@ -5234,6 +5564,7 @@ _nl_msg_new_dump (NMPObjectType obj_type,
 	case NMP_OBJECT_TYPE_IP6_ADDRESS:
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
+	case NMP_OBJECT_TYPE_ROUTING_RULE:
 		{
 			const struct rtgenmsg gmsg = {
 				.rtgen_family = preferred_addr_family,
@@ -5254,42 +5585,68 @@ static void
 do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	DelayedActionType action_type_prune;
 	DelayedActionType iflags;
 
 	nm_assert (!NM_FLAGS_ANY (action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
 	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
 
-	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+	action_type_prune = action_type;
+
+	/* calling nmp_cache_dirty_set_all_main() with a non-main lookup-index requires an extra
+	 * cache lookup for every entry.
+	 *
+	 * Avoid that, by special casing routing-rules here. */
+	if (NM_FLAGS_ALL (action_type_prune, DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL)) {
+		NMPLookup lookup;
+
+		priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP4] += 1;
+		priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP6] += 1;
+		nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+		nmp_cache_dirty_set_all_main (nm_platform_get_cache (platform),
+		                              &lookup);
+		action_type_prune &= ~DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL;
+	}
+
+	FOR_EACH_DELAYED_ACTION (iflags, action_type_prune) {
+		RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type (iflags);
 		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),
-		                         &lookup);
+		priv->pruning[refresh_all_type] += 1;
+		refresh_all_type_init_lookup (refresh_all_type,
+		                              &lookup);
+		nmp_cache_dirty_set_all_main (nm_platform_get_cache (platform),
+		                              &lookup);
 	}
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
-		NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
+		RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type (iflags);
+		const RefreshAllInfo *refresh_all_info = refresh_all_type_get_info (refresh_all_type);
 		nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 		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)];
+		out_refresh_all_in_progress = &priv->delayed_action.refresh_all_in_progress[refresh_all_type];
 		nm_assert (*out_refresh_all_in_progress >= 0);
 		*out_refresh_all_in_progress += 1;
 
 		/* clear any delayed action that request a refresh of this object type. */
 		priv->delayed_action.flags &= ~iflags;
 		_LOGt_delayed_action (iflags, NULL, "handle (do-request-all)");
-		if (obj_type == NMP_OBJECT_TYPE_LINK) {
-			priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
-			g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
-			_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, NULL, "clear (do-request-all)");
+
+		if (refresh_all_type == REFRESH_ALL_TYPE_LINKS) {
+			nm_assert (   (priv->delayed_action.list_refresh_link->len > 0)
+			           == NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK));
+			if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
+				_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, NULL, "clear (do-request-all)");
+				priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+				g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
+			}
 		}
 
 		event_handler_read_netlink (platform, FALSE);
 
-		nlmsg = _nl_msg_new_dump (obj_type, AF_UNSPEC);
+		nlmsg = _nl_msg_new_dump (refresh_all_info->obj_type,
+		                          refresh_all_info->addr_family);
 		if (!nlmsg)
 			goto next_after_fail;
 
@@ -5310,9 +5667,9 @@ next_after_fail:
 }
 
 static void
-do_request_one_type (NMPlatform *platform, NMPObjectType obj_type)
+do_request_one_type_by_needle_object (NMPlatform *platform, const NMPObject *obj_needle)
 {
-	do_request_all_no_delayed_actions (platform, delayed_action_refresh_from_object_type (obj_type));
+	do_request_all_no_delayed_actions (platform, delayed_action_refresh_from_needle_object (obj_needle));
 	delayed_action_handle_all (platform, FALSE);
 }
 
@@ -5398,9 +5755,27 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 
 	msghdr = nlmsg_hdr (msg);
 
-	if (   _support_kernel_extended_ifa_flags_still_undecided ()
-	    && msghdr->nlmsg_type == RTM_NEWADDR)
-		_support_kernel_extended_ifa_flags_detect (msg);
+	if (   !_nm_platform_kernel_support_detected (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)
+	    && msghdr->nlmsg_type == RTM_NEWADDR) {
+		/* 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 (   nlmsg_valid_hdr (msghdr, sizeof (struct ifaddrmsg))
+		    && NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msghdr))->ifa_family,
+		                  AF_INET,
+		                  AF_INET6)) {
+			/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
+			 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
+			 * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14,
+			 * dated 30 March, 2014.
+			 *
+			 * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy
+			 * way to detect kernel support. */
+			_nm_platform_kernel_support_init (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS,
+			                                    !!nlmsg_find_attr (msghdr, sizeof (struct ifaddrmsg), IFA_FLAGS)
+			                                  ? 1
+			                                  : -1);
+		}
+	}
 
 	if (!handle_events)
 		return;
@@ -5408,6 +5783,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK,
 	                                   RTM_DELADDR,
 	                                   RTM_DELROUTE,
+	                                   RTM_DELRULE,
 	                                   RTM_DELQDISC,
 	                                   RTM_DELTFILTER)) {
 		/* The event notifies about a deleted object. We don't need to initialize all
@@ -5426,10 +5802,11 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	    && NM_IN_SET (msghdr->nlmsg_type, RTM_NEWADDR,
 	                                      RTM_NEWLINK,
 	                                      RTM_NEWROUTE,
+	                                      RTM_NEWRULE,
 	                                      RTM_NEWQDISC,
 	                                      RTM_NEWTFILTER)) {
 		is_dump = delayed_action_refresh_all_in_progress (platform,
-		                                                  delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)));
+		                                                  delayed_action_refresh_from_needle_object (obj));
 	}
 
 	_LOGT ("event-notification: %s%s: %s",
@@ -5445,10 +5822,11 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 
 		switch (msghdr->nlmsg_type) {
 
-		case RTM_NEWLINK:
-		case RTM_NEWADDR:
 		case RTM_GETLINK:
+		case RTM_NEWADDR:
+		case RTM_NEWLINK:
 		case RTM_NEWQDISC:
+		case RTM_NEWRULE:
 		case RTM_NEWTFILTER:
 			cache_op = nmp_cache_update_netlink (cache, obj, is_dump, &obj_old, &obj_new);
 			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
@@ -5537,16 +5915,17 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 				 * netlink events. This needs investigation. */
 				_LOGT ("schedule resync of routes after RTM_NEWROUTE");
 				delayed_action_schedule (platform,
-				                         delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)),
+				                         delayed_action_refresh_from_needle_object (obj),
 				                         NULL);
 			}
 			break;
 		}
 
-		case RTM_DELLINK:
 		case RTM_DELADDR:
-		case RTM_DELROUTE:
+		case RTM_DELLINK:
 		case RTM_DELQDISC:
+		case RTM_DELROUTE:
+		case RTM_DELRULE:
 		case RTM_DELTFILTER:
 			cache_op = nmp_cache_remove_netlink (cache, obj, &obj_old, &obj_new);
 			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
@@ -5657,7 +6036,7 @@ do_add_addrroute (NMPlatform *platform,
 		 *
 		 * rh#1484434 */
 		if (!nmp_cache_lookup_obj (nm_platform_get_cache (platform), obj_id))
-			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
+			do_request_one_type_by_needle_object (platform, obj_id);
 	}
 
 	return wait_for_nl_response_to_nmerr (seq_result);
@@ -5722,7 +6101,7 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 		 *
 		 * rh#1484434 */
 		if (nmp_cache_lookup_obj (nm_platform_get_cache (platform), obj_id))
-			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
+			do_request_one_type_by_needle_object (platform, obj_id);
 	}
 
 	return success;
@@ -5845,9 +6224,7 @@ link_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return -NME_UNSPEC;
 
@@ -5876,9 +6253,7 @@ link_delete (NMPlatform *platform, int ifindex)
 	nlmsg = _nl_msg_new_link (RTM_DELLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 
 	nmp_object_stackinit_id_link (&obj_id, ifindex);
 	return do_delete_object (platform, &obj_id, nlmsg);
@@ -5891,12 +6266,6 @@ link_refresh (NMPlatform *platform, int ifindex)
 	return !!nm_platform_link_get_obj (platform, ifindex, TRUE);
 }
 
-static void
-refresh_all (NMPlatform *platform, NMPObjectType obj_type)
-{
-	do_request_one_type (platform, obj_type);
-}
-
 static gboolean
 link_set_netns (NMPlatform *platform,
                 int ifindex,
@@ -5907,9 +6276,7 @@ link_set_netns (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (!nlmsg)
 		return FALSE;
 
@@ -5936,12 +6303,13 @@ link_change_flags (NMPlatform *platform,
 	       nm_platform_link_flags2str (flags_set, s_flags, sizeof (s_flags)),
 	       nm_platform_link_flags2str (flags_mask, NULL, 0));
 
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          0,
-	                          ifindex,
-	                          NULL,
-	                          flags_mask,
-	                          flags_set);
+	nlmsg = _nl_msg_new_link_full (RTM_NEWLINK,
+	                               0,
+	                               ifindex,
+	                               NULL,
+	                               AF_UNSPEC,
+	                               flags_mask,
+	                               flags_set);
 	if (!nlmsg)
 		return -NME_UNSPEC;
 	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
@@ -5997,7 +6365,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	       ifindex,
 	       nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0));
 
-	if (!_support_user_ipv6ll_get ()) {
+	if (!nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
 		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
 		return -NME_PL_OPNOTSUPP;
 	}
@@ -6005,9 +6373,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (   !nlmsg
 	    || !_nl_msg_new_link_set_afspec (nlmsg, mode, NULL))
 		g_return_val_if_reached (-NME_BUG);
@@ -6024,7 +6390,7 @@ link_set_token (NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
 	_LOGD ("link: change %d: token: set IPv6 address generation token to %s",
 	       ifindex, nm_utils_inet6_interface_identifier_to_token (iid, sbuf));
 
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK, 0, ifindex, NULL, 0, 0);
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK, 0, ifindex, NULL);
 	if (!nlmsg || !_nl_msg_new_link_set_afspec (nlmsg, -1, &iid))
 		g_return_val_if_reached (FALSE);
 
@@ -6105,9 +6471,7 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (-NME_BUG);
 
@@ -6126,9 +6490,7 @@ link_set_name (NMPlatform *platform, int ifindex, const char *name)
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (-NME_BUG);
 
@@ -6161,9 +6523,7 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6224,6 +6584,14 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                                                  ifname,
 	                                                                                  "device/sriov_drivers_autoprobe"),
 	                                                        10, 0, 1, -1);
+
+	if (   current_autoprobe == -1
+	    && errno == ENOENT) {
+		/* older kernel versions don't have this sysctl. Assume the value is
+		 * "1". */
+		current_autoprobe = 1;
+	}
+
 	if (   current_num == num_vfs
 	    && (autoprobe == NM_TERNARY_DEFAULT || current_autoprobe == autoprobe))
 		return TRUE;
@@ -6279,9 +6647,7 @@ link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (-NME_BUG);
 
@@ -6364,6 +6730,74 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
+static gboolean
+link_set_bridge_vlans (NMPlatform *platform,
+                       int ifindex,
+                       gboolean on_master,
+                       const NMPlatformBridgeVlan *const *vlans)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *list;
+	struct bridge_vlan_info vinfo = { };
+	guint i;
+
+	nlmsg = _nl_msg_new_link_full (vlans ? RTM_SETLINK : RTM_DELLINK,
+	                               0,
+	                               ifindex,
+	                               NULL,
+	                               AF_BRIDGE,
+	                               0,
+	                               0);
+	if (!nlmsg)
+		g_return_val_if_reached (-NME_BUG);
+
+	if (!(list = nla_nest_start (nlmsg, IFLA_AF_SPEC)))
+		goto nla_put_failure;
+
+	NLA_PUT_U16 (nlmsg,
+	             IFLA_BRIDGE_FLAGS,
+	             on_master ? BRIDGE_FLAGS_MASTER : BRIDGE_FLAGS_SELF);
+
+	if (vlans) {
+		/* Add VLANs */
+		for (i = 0; vlans[i]; i++) {
+			const NMPlatformBridgeVlan *vlan = vlans[i];
+			gboolean is_range = vlan->vid_start != vlan->vid_end;
+
+			vinfo.vid = vlan->vid_start;
+			vinfo.flags = is_range ? BRIDGE_VLAN_INFO_RANGE_BEGIN : 0;
+
+			if (vlan->untagged)
+				vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
+			if (vlan->pvid)
+				vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
+
+			NLA_PUT (nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof (vinfo), &vinfo);
+
+			if (is_range) {
+				vinfo.vid = vlan->vid_end;
+				vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
+				NLA_PUT (nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof (vinfo), &vinfo);
+			}
+		}
+	} else {
+		/* Flush existing VLANs */
+		vinfo.vid = 1;
+		vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
+		NLA_PUT (nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof (vinfo), &vinfo);
+
+		vinfo.vid = 4094;
+		vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
+		NLA_PUT (nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof (vinfo), &vinfo);
+	}
+
+	nla_nest_end (nlmsg, list);
+
+	return (do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
 static char *
 link_get_physical_port_id (NMPlatform *platform, int ifindex)
 {
@@ -6409,9 +6843,7 @@ vlan_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6445,9 +6877,7 @@ link_gre_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6497,9 +6927,7 @@ link_ip6tnl_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6553,9 +6981,7 @@ link_ip6gre_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6612,9 +7038,7 @@ link_ipip_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6656,9 +7080,7 @@ link_macsec_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6713,9 +7135,7 @@ link_macvlan_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6755,9 +7175,7 @@ link_sit_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6857,9 +7275,7 @@ link_vxlan_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -6923,9 +7339,7 @@ link_6lowpan_add (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
-	                          name,
-	                          0,
-	                          0);
+	                          name);
 	if (!nlmsg)
 		return FALSE;
 
@@ -7073,9 +7487,7 @@ link_vlan_change (NMPlatform *platform,
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (   !nlmsg
 	    || !_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
 	                                            -1,
@@ -7099,9 +7511,7 @@ link_enslave (NMPlatform *platform, int master, int slave)
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
-	                          NULL,
-	                          0,
-	                          0);
+	                          NULL);
 	if (!nlmsg)
 		return FALSE;
 
@@ -7606,6 +8016,9 @@ object_delete (NMPlatform *platform,
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
 		nlmsg = _nl_msg_new_route (RTM_DELROUTE, 0, obj);
 		break;
+	case NMP_OBJECT_TYPE_ROUTING_RULE:
+		nlmsg = _nl_msg_new_routing_rule (RTM_DELRULE, 0, NMP_OBJECT_CAST_ROUTING_RULE (obj));
+		break;
 	case NMP_OBJECT_TYPE_QDISC:
 		nlmsg = _nl_msg_new_qdisc (RTM_DELQDISC, 0, NMP_OBJECT_CAST_QDISC (obj));
 		break;
@@ -7703,6 +8116,47 @@ ip_route_get (NMPlatform *platform,
 /*****************************************************************************/
 
 static int
+routing_rule_add (NMPlatform *platform,
+                  NMPNlmFlags flags,
+                  const NMPlatformRoutingRule *routing_rule)
+{
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	gs_free char *errmsg = NULL;
+	char s_buf[256];
+	int nle;
+
+	msg = _nl_msg_new_routing_rule (RTM_NEWRULE, flags, routing_rule);
+
+	event_handler_read_netlink (platform, FALSE);
+
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, &errmsg, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	if (nle < 0) {
+		_LOGE ("do-add-rule: failed sending netlink request \"%s\" (%d)",
+		      nm_strerror (nle), -nle);
+		return -NME_PL_NETLINK;
+	}
+
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_WARN,
+	        "do-add-rule: %s",
+	        wait_for_nl_response_to_string (seq_result, errmsg, s_buf, sizeof (s_buf)));
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
+		return 0;
+	if (seq_result < 0)
+		return seq_result;
+	return -NME_UNSPEC;
+}
+
+/*****************************************************************************/
+
+static int
 qdisc_add (NMPlatform *platform,
            NMPNlmFlags flags,
            const NMPlatformQdisc *qdisc)
@@ -7736,7 +8190,8 @@ qdisc_add (NMPlatform *platform,
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
 		return 0;
-
+	if (seq_result < 0)
+		return seq_result;
 	return -NME_UNSPEC;
 }
 
@@ -8053,6 +8508,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 					                         NULL);
@@ -8318,9 +8774,13 @@ constructed (GObject *_object)
 	g_assert (!nle);
 
 	nle = nl_socket_add_memberships (priv->nlh,
+	                                 RTNLGRP_IPV4_IFADDR,
+	                                 RTNLGRP_IPV4_ROUTE,
+	                                 RTNLGRP_IPV4_RULE,
+	                                 RTNLGRP_IPV6_RULE,
+	                                 RTNLGRP_IPV6_IFADDR,
+	                                 RTNLGRP_IPV6_ROUTE,
 	                                 RTNLGRP_LINK,
-	                                 RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR,
-	                                 RTNLGRP_IPV4_ROUTE,  RTNLGRP_IPV6_ROUTE,
 	                                 RTNLGRP_TC,
 	                                 0);
 	g_assert (!nle);
@@ -8347,6 +8807,7 @@ constructed (GObject *_object)
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL |
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 	                         NULL);
@@ -8457,7 +8918,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
 
-	platform_class->refresh_all = refresh_all;
 	platform_class->link_refresh = link_refresh;
 
 	platform_class->link_set_netns = link_set_netns;
@@ -8478,6 +8938,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_name = link_set_name;
 	platform_class->link_set_sriov_params = link_set_sriov_params;
 	platform_class->link_set_sriov_vfs = link_set_sriov_vfs;
+	platform_class->link_set_bridge_vlans = link_set_bridge_vlans;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
 	platform_class->link_get_dev_id = link_get_dev_id;
@@ -8543,11 +9004,11 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->ip_route_add = ip_route_add;
 	platform_class->ip_route_get = ip_route_get;
 
+	platform_class->routing_rule_add = routing_rule_add;
+
 	platform_class->qdisc_add = qdisc_add;
 	platform_class->tfilter_add = tfilter_add;
 
-	platform_class->check_kernel_support = check_kernel_support;
-
 	platform_class->process_events = process_events;
 }
 
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index 094a3c6f..b12fd243 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -25,7 +25,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/genetlink.h>
 
-#include "nm-utils/unaligned.h"
+#include "nm-std-aux/unaligned.h"
 
 /*****************************************************************************/
 
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 93cd0b3c..456871a7 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -35,7 +35,7 @@
 
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "nm-core-utils.h"
 
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index bad77aee..f5491679 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -23,7 +23,7 @@
 
 #include "nm-platform.h"
 #include "nm-setting-wired.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index fe0cb662..1fc0ccb7 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -28,6 +28,7 @@
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <netdb.h>
+#include <linux/fib_rules.h>
 #include <linux/ip.h>
 #include <linux/if.h>
 #include <linux/if_tun.h>
@@ -37,9 +38,9 @@
 
 #include "nm-utils.h"
 #include "nm-core-internal.h"
-#include "nm-utils/nm-dedup-multi.h"
-#include "nm-utils/nm-udev-utils.h"
-#include "nm-utils/nm-secret-utils.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-udev-aux/nm-udev-utils.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
@@ -118,9 +119,6 @@ typedef struct _NMPlatformPrivate {
 	bool use_udev:1;
 	bool log_with_ptr:1;
 
-	NMPlatformKernelSupportFlags support_checked;
-	NMPlatformKernelSupportFlags support_present;
-
 	guint ip4_dev_route_blacklist_check_id;
 	guint ip4_dev_route_blacklist_gc_timeout_id;
 	GHashTable *ip4_dev_route_blacklist_hash;
@@ -276,40 +274,99 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
 
 /*****************************************************************************/
 
-NMPlatformKernelSupportFlags
-nm_platform_check_kernel_support (NMPlatform *self,
-                                  NMPlatformKernelSupportFlags request_flags)
+volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = { };
+
+static const struct {
+	bool compile_time_default;
+	const char *name;
+	const char *desc;
+} _nm_platform_kernel_support_info[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = {
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS] = {
+		.compile_time_default = TRUE,
+		.name                 = "EXTENDED_IFA_FLAGS",
+		.desc                 = "IPv6 temporary addresses support",
+	},
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL] = {
+		.compile_time_default = TRUE,
+		.name                 = "USER_IPV6LL",
+		.desc                 = "IFLA_INET6_ADDR_GEN_MODE support",
+	},
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF] = {
+		.compile_time_default = (RTA_MAX >= 20 /* RTA_PREF */),
+		.name                 = "RTA_PREF",
+		.desc                 = "ability to set router preference for IPv6 routes",
+	},
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV] = {
+		.compile_time_default = (FRA_MAX >= 19 /* FRA_L3MDEV */),
+		.name                 = "FRA_L3MDEV",
+		.desc                 = "FRA_L3MDEV attribute for policy routing rules",
+	},
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE] = {
+		.compile_time_default = (FRA_MAX >= 20 /* FRA_UID_RANGE */),
+		.name                 = "FRA_UID_RANGE",
+		.desc                 = "FRA_UID_RANGE attribute for policy routing rules",
+	},
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL] = {
+		.compile_time_default = (FRA_MAX >= 21 /* FRA_PROTOCOL */),
+		.name                 = "FRA_PROTOCOL",
+		.desc                 = "FRA_PROTOCOL attribute for policy routing rules",
+	},
+	[NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO] = {
+		.compile_time_default = (FRA_MAX >= 22 /* FRA_IP_PROTO */),
+		.name                 = "FRA_IP_PROTO",
+		.desc                 = "FRA_IP_PROTO, FRA_SPORT_RANGE, FRA_DPORT_RANGE attributes for policy routing rules",
+	},
+};
+
+int
+_nm_platform_kernel_support_init (NMPlatformKernelSupportType type,
+                                  int value)
 {
-	NMPlatformPrivate *priv;
+	volatile int *p_state;
+	gboolean set_default = FALSE;
 
-	_CHECK_SELF (self, klass, TRUE);
+	nm_assert (_NM_INT_NOT_NEGATIVE (type) && type < G_N_ELEMENTS (_nm_platform_kernel_support_state));
 
-	priv = NM_PLATFORM_GET_PRIVATE (self);
+	p_state = &_nm_platform_kernel_support_state[type];
 
-	/* we cache the response from subclasses and only request it once.
-	 * This probably gives better performance, but more importantly,
-	 * we are guaranteed that the answer for a certain request_flag
-	 * is always the same. */
-	if (G_UNLIKELY (!NM_FLAGS_ALL (priv->support_checked, request_flags))) {
-		NMPlatformKernelSupportFlags checked, response;
+	if (value == 0) {
+		set_default = TRUE;
+		value =   _nm_platform_kernel_support_info[type].compile_time_default
+		        ? 1
+		        : -1;
+	}
 
-		checked = request_flags & ~priv->support_checked;
-		nm_assert (checked);
+	nm_assert (NM_IN_SET (value, -1, 1));
 
-		if (klass->check_kernel_support)
-			response = klass->check_kernel_support (self, checked);
-		else {
-			/* fake platform. Pretend no support for anything. */
-			response = 0;
-		}
+	if (!g_atomic_int_compare_and_exchange (p_state, 0, value)) {
+		value = g_atomic_int_get (p_state);
+		nm_assert (NM_IN_SET (value, -1, 1));
+		return value;
+	}
 
-		priv->support_checked |= checked;
-		priv->support_present = (priv->support_present & ~checked) | (response & checked);
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
+
+	if (set_default) {
+		nm_log_dbg (LOGD_PLATFORM, "platform: kernel-support for %s (%s) not detected: assume %ssupported",
+		             _nm_platform_kernel_support_info[type].name,
+		             _nm_platform_kernel_support_info[type].desc,
+		             value >= 0 ? "" : "not ");
+	} else {
+		nm_log_dbg (LOGD_PLATFORM, "platform: kernel-support for %s (%s) detected: %ssupported",
+		            _nm_platform_kernel_support_info[type].name,
+		            _nm_platform_kernel_support_info[type].desc,
+		            value >= 0 ? "" : "not ");
 	}
 
-	return priv->support_present & request_flags;
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
+
+	return value;
 }
 
+/*****************************************************************************/
+
 /**
  * nm_platform_process_events:
  * @self: platform instance
@@ -468,6 +525,8 @@ nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
  * @path: Absolute path to sysctl
  *
  * Returns: (transfer full): Contents of the virtual sysctl file.
+ *
+ * If the path does not exist, %NULL is returned and %errno set to %ENOENT.
  */
 char *
 nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, const char *path)
@@ -517,6 +576,8 @@ nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, c
  * value. On success, %errno will be set to zero. The returned value
  * will always be in the range between @min and @max
  * (inclusive) or @fallback.
+ * If the file does not exist, the fallback is returned and %errno
+ * is set to ENOENT.
  */
 gint64
 nm_platform_sysctl_get_int_checked (NMPlatform *self,
@@ -536,11 +597,17 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self,
 
 	g_return_val_if_fail (path, fallback);
 
-	if (path)
-		value = nm_platform_sysctl_get (self, pathid, dirfd, path);
+	if (!path) {
+		errno = EINVAL;
+		return fallback;
+	}
 
+	value = nm_platform_sysctl_get (self, pathid, dirfd, path);
 	if (!value) {
-		errno = EINVAL;
+		/* nm_platform_sysctl_get() set errno to ENOENT if the file does not exist.
+		 * Propagate/preserve that. */
+		if (errno != ENOENT)
+			errno = EINVAL;
 		return fallback;
 	}
 
@@ -1189,21 +1256,6 @@ nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
 }
 
 /**
- * nm_platform_refresh_all:
- * @self: platform instance
- * @obj_type: The object type to request.
- *
- * Resync and re-request all objects from kernel of a certain @obj_type.
- */
-void
-nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type)
-{
-	_CHECK_SELF_VOID (self, klass);
-
-	klass->refresh_all (self, obj_type);
-}
-
-/**
  * nm_platform_link_refresh:
  * @self: platform instance
  * @ifindex: Interface index
@@ -1532,6 +1584,28 @@ nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformV
 	return klass->link_set_sriov_vfs (self, ifindex, vfs);
 }
 
+gboolean
+nm_platform_link_set_bridge_vlans (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans)
+{
+	guint i;
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	_LOG3D ("link: %s bridge VLANs on %s",
+	        vlans ? "setting" : "clearing",
+	        on_master ? "master" : "self");
+	if (vlans) {
+		for (i = 0; vlans[i]; i++) {
+			const NMPlatformBridgeVlan *vlan = vlans[i];
+
+			_LOG3D ("link:   bridge VLAN %s", nm_platform_bridge_vlan_to_string (vlan, NULL, 0));
+		}
+	}
+
+	return klass->link_set_bridge_vlans (self, ifindex, on_master, vlans);
+}
+
 /**
  * nm_platform_link_set_up:
  * @self: platform instance
@@ -3894,7 +3968,7 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 	if (!known_addresses)
 		return TRUE;
 
-	ifa_flags =   nm_platform_check_kernel_support (self, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)
+	ifa_flags =   nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)
 	            ? IFA_F_NOPREFIXROUTE
 	            : 0;
 
@@ -4094,7 +4168,7 @@ next_plat:
 	if (!known_addresses)
 		return TRUE;
 
-	ifa_flags =   nm_platform_check_kernel_support (self, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)
+	ifa_flags =   nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)
 	            ? IFA_F_NOPREFIXROUTE
 	            : 0;
 
@@ -4264,14 +4338,13 @@ nm_platform_ip_route_sync (NMPlatform *self,
 	gboolean success = TRUE;
 	char sbuf1[sizeof (_nm_utils_to_string_buffer)];
 	char sbuf2[sizeof (_nm_utils_to_string_buffer)];
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	nm_assert (NM_IS_PLATFORM (self));
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 	nm_assert (ifindex > 0);
 
-	vt = addr_family == AF_INET
-	     ? &nm_platform_vtable_route_v4
-	     : &nm_platform_vtable_route_v6;
+	vt = &nm_platform_vtable_route.vx[IS_IPv4];
 
 	for (i_type = 0; routes && i_type < 2; i_type++) {
 		for (i = 0; i < routes->len; i++) {
@@ -4578,7 +4651,7 @@ _ip_route_add (NMPlatform *self,
 	nm_assert (route);
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	ifindex = ((NMPlatformObject *)route)->ifindex;
+	ifindex = ((const NMPlatformIPRoute *) route)->ifindex;
 	_LOG3D ("route: %-10s IPv%c route: %s",
 	        _nmp_nlm_flag_to_string (flags & NMP_NLM_FLAG_FMASK),
 	        nm_utils_addr_family_to_char (addr_family),
@@ -4630,18 +4703,28 @@ gboolean
 nm_platform_object_delete (NMPlatform *self,
                            const NMPObject *obj)
 {
-	int ifindex = obj->object.ifindex;
+	int ifindex;
+
 	_CHECK_SELF (self, klass, FALSE);
 
-	if (!NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                           NMP_OBJECT_TYPE_IP6_ROUTE,
-	                                           NMP_OBJECT_TYPE_QDISC,
-	                                           NMP_OBJECT_TYPE_TFILTER))
+	switch (NMP_OBJECT_GET_TYPE (obj)) {
+	case NMP_OBJECT_TYPE_ROUTING_RULE:
+		_LOGD ("%s: delete %s",
+		       NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
+		       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+	case NMP_OBJECT_TYPE_QDISC:
+	case NMP_OBJECT_TYPE_TFILTER:
+		ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj)->ifindex;
+		_LOG3D ("%s: delete %s",
+		        NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
+		        nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+		break;
+	default:
 		g_return_val_if_reached (FALSE);
-
-	_LOG3D ("%s: delete %s",
-	        NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
-	        nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	}
 
 	return klass->object_delete (self, obj);
 }
@@ -4971,6 +5054,21 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 /*****************************************************************************/
 
 int
+nm_platform_routing_rule_add (NMPlatform *self,
+                              NMPNlmFlags flags,
+                              const NMPlatformRoutingRule *routing_rule)
+{
+	_CHECK_SELF (self, klass, -NME_BUG);
+
+	g_return_val_if_fail (routing_rule, -NME_BUG);
+
+	_LOGD ("routing-rule: adding or updating: %s", nm_platform_routing_rule_to_string (routing_rule, NULL, 0));
+	return klass->routing_rule_add (self, flags, routing_rule);
+}
+
+/*****************************************************************************/
+
+int
 nm_platform_qdisc_add (NMPlatform *self,
                        NMPNlmFlags flags,
                        const NMPlatformQdisc *qdisc)
@@ -5276,7 +5374,7 @@ nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len
 		return buf;
 
 	g_snprintf (buf, len,
-	            lnk->is_tap ? "gretap" : "gre"
+	            "gre%s" /* is_tap */
 	            "%s" /* remote */
 	            "%s" /* local */
 	            "%s" /* parent_ifindex */
@@ -5288,6 +5386,7 @@ nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len
 	            "%s" /* ikey */
 	            "%s" /* okey */
 	            "",
+	            lnk->is_tap ? "tap" : "",
 	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
 	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
@@ -6076,6 +6175,253 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	return buf;
 }
 
+static void
+_routing_rule_addr_to_string (char **buf,
+                              gsize *len,
+                              int addr_family,
+                              const NMIPAddr *addr,
+                              guint8 plen,
+                              gboolean is_src)
+{
+	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	gboolean is_zero;
+	gsize addr_size;
+
+	nm_assert_addr_family (addr_family);
+	nm_assert (addr);
+
+	addr_size = nm_utils_addr_family_to_size (addr_family);
+
+	is_zero = nm_utils_memeqzero (addr, addr_size);
+
+	if (   plen == 0
+	    && is_zero) {
+		if (is_src)
+			nm_utils_strbuf_append_str (buf, len, " from all");
+		else
+			nm_utils_strbuf_append_str (buf, len, "");
+		return;
+	}
+
+	nm_utils_strbuf_append_str (buf, len, is_src ? " from " : " to ");
+
+	nm_utils_strbuf_append_str (buf, len, nm_utils_inet_ntop (addr_family, addr, s_addr));
+
+	if (plen != (addr_size * 8))
+		nm_utils_strbuf_append (buf, len, "/%u", plen);
+}
+
+static void
+_routing_rule_port_range_to_string (char **buf,
+                                    gsize *len,
+                                    const NMFibRulePortRange *port_range,
+                                    const char *name)
+{
+	if (   port_range->start == 0
+	    && port_range->end == 0)
+		nm_utils_strbuf_append_str (buf, len, "");
+	else {
+		nm_utils_strbuf_append (buf, len, " %s %u", name, port_range->start);
+		if (port_range->start != port_range->end)
+			nm_utils_strbuf_append (buf, len, "-%u", port_range->end);
+	}
+}
+
+const char *
+nm_platform_routing_rule_to_string (const NMPlatformRoutingRule *routing_rule, char *buf, gsize len)
+{
+	const char *buf0;
+	guint32 rr_flags;
+
+	if (!nm_utils_to_string_buffer_init_null (routing_rule, &buf, &len))
+		return buf;
+
+	if (!NM_IN_SET (routing_rule->addr_family, AF_INET, AF_INET6)) {
+		/* invalid addr-family. The other fields are undefined. */
+		if (routing_rule->addr_family == AF_UNSPEC)
+			g_snprintf (buf, len, "[routing-rule]");
+		else
+			g_snprintf (buf, len, "[routing-rule family:%u]", routing_rule->addr_family);
+		return buf;
+	}
+
+	buf0 = buf;
+
+	rr_flags = routing_rule->flags;
+
+	rr_flags = NM_FLAGS_UNSET (rr_flags, FIB_RULE_INVERT);
+	nm_utils_strbuf_append (&buf, &len,
+	                        "[%c] " /* addr-family */
+	                        "%u:"  /* priority */
+	                        "%s",   /* not/FIB_RULE_INVERT */
+	                        nm_utils_addr_family_to_char (routing_rule->addr_family),
+	                        routing_rule->priority,
+	                        (  NM_FLAGS_HAS (routing_rule->flags, FIB_RULE_INVERT)
+	                         ? " not"
+	                         : ""));
+
+	_routing_rule_addr_to_string (&buf, &len,
+	                              routing_rule->addr_family,
+	                              &routing_rule->src,
+	                              routing_rule->src_len,
+	                              TRUE);
+
+	_routing_rule_addr_to_string (&buf, &len,
+	                              routing_rule->addr_family,
+	                              &routing_rule->dst,
+	                              routing_rule->dst_len,
+	                              FALSE);
+
+	if (routing_rule->tos)
+		nm_utils_strbuf_append (&buf, &len, " tos 0x%02x", routing_rule->tos);
+
+	if (   routing_rule->fwmark != 0
+	    || routing_rule->fwmask != 0) {
+		nm_utils_strbuf_append (&buf, &len, " fwmark %#x", (unsigned) routing_rule->fwmark);
+		if (routing_rule->fwmark != 0xFFFFFFFFu)
+			nm_utils_strbuf_append (&buf, &len, "/%#x", (unsigned) routing_rule->fwmask);
+	}
+
+	if (routing_rule->iifname[0]) {
+		nm_utils_strbuf_append (&buf, &len, " iif %s", routing_rule->iifname);
+		rr_flags = NM_FLAGS_UNSET (rr_flags, FIB_RULE_IIF_DETACHED);
+		if (NM_FLAGS_HAS (routing_rule->flags, FIB_RULE_IIF_DETACHED))
+			nm_utils_strbuf_append_str (&buf, &len, " [detached]");
+	}
+
+	if (routing_rule->oifname[0]) {
+		nm_utils_strbuf_append (&buf, &len, " oif %s", routing_rule->oifname);
+		rr_flags = NM_FLAGS_UNSET (rr_flags, FIB_RULE_OIF_DETACHED);
+		if (NM_FLAGS_HAS (routing_rule->flags, FIB_RULE_OIF_DETACHED))
+			nm_utils_strbuf_append_str (&buf, &len, " [detached]");
+	}
+
+	if (routing_rule->l3mdev != 0) {
+		if (routing_rule->l3mdev == 1)
+			nm_utils_strbuf_append_str (&buf, &len, " lookup [l3mdev-table]");
+		else {
+			nm_utils_strbuf_append (&buf, &len, " lookup [l3mdev-table/%u]", (unsigned) routing_rule->l3mdev);
+		}
+	}
+
+	if (   routing_rule->uid_range_has
+	    || routing_rule->uid_range.start
+	    || routing_rule->uid_range.end) {
+		nm_utils_strbuf_append (&buf, &len,
+		                        " uidrange %u-%u%s",
+		                        routing_rule->uid_range.start,
+		                        routing_rule->uid_range.end,
+		                        routing_rule->uid_range_has ? "" : "(?)");
+	}
+
+	if (routing_rule->ip_proto != 0) {
+		/* we don't call getprotobynumber(), just print the numeric value.
+		 * This differs from what ip-rule prints. */
+		nm_utils_strbuf_append (&buf, &len,
+		                        " ipproto %u",
+		                        routing_rule->ip_proto);
+	}
+
+	_routing_rule_port_range_to_string (&buf, &len,
+	                                    &routing_rule->sport_range,
+	                                    "sport");
+
+	_routing_rule_port_range_to_string (&buf, &len,
+	                                    &routing_rule->dport_range,
+	                                    "dport");
+
+	if (routing_rule->tun_id != 0) {
+		nm_utils_strbuf_append (&buf, &len,
+		                        " tun_id %"G_GUINT64_FORMAT,
+		                        routing_rule->tun_id);
+	}
+
+	if (routing_rule->table != 0) {
+		nm_utils_strbuf_append (&buf, &len,
+		                        " lookup %u",
+		                        routing_rule->table);
+	}
+
+	if (routing_rule->suppress_prefixlen_inverse != 0) {
+		nm_utils_strbuf_append (&buf, &len,
+		                        " suppress_prefixlen %d",
+		                        (int) (~routing_rule->suppress_prefixlen_inverse));
+	}
+
+	if (routing_rule->suppress_ifgroup_inverse != 0) {
+		nm_utils_strbuf_append (&buf, &len,
+		                        " suppress_ifgroup %d",
+		                        (int) (~routing_rule->suppress_ifgroup_inverse));
+	}
+
+	if (routing_rule->flow) {
+		/* FRA_FLOW is only for IPv4, but we want to print the value for all address-families,
+		 * to see when it is set. In practice, this should not be set except for IPv4.
+		 *
+		 * We don't follow the style how ip-rule prints flow/realms. It's confusing. Just
+		 * print the value hex. */
+		nm_utils_strbuf_append (&buf, &len,
+		                        " realms 0x%08x",
+		                        routing_rule->flow);
+	}
+
+	if (routing_rule->action == RTN_NAT) {
+		G_STATIC_ASSERT_EXPR (RTN_NAT == 10);
+
+		/* NAT is deprecated for many years. We don't support RTA_GATEWAY/FRA_UNUSED2
+		 * for the gateway, and so do recent kernels ignore that parameter. */
+		nm_utils_strbuf_append_str (&buf, &len, " masquerade");
+	} else if (routing_rule->action == FR_ACT_GOTO) {
+		if (routing_rule->goto_target != 0)
+			nm_utils_strbuf_append (&buf, &len, " goto %u", routing_rule->goto_target);
+		else
+			nm_utils_strbuf_append_str (&buf, &len, " goto none");
+		rr_flags = NM_FLAGS_UNSET (rr_flags, FIB_RULE_UNRESOLVED);
+		if (NM_FLAGS_HAS (routing_rule->flags, FIB_RULE_UNRESOLVED))
+			nm_utils_strbuf_append_str (&buf, &len, " unresolved");
+	} else if (routing_rule->action != FR_ACT_TO_TBL) {
+		const char *ss;
+		char ss_buf[60];
+
+#define _V(v1, v2) ((sizeof (char[(((int) (v1)) == ((int) (v2))) ? 1 : -1]) * 0) + (v1))
+		switch (routing_rule->action) {
+		case _V (FR_ACT_UNSPEC,      RTN_UNSPEC)      : ss = "none";        break;
+		case _V (FR_ACT_TO_TBL,      RTN_UNICAST)     : ss = "unicast";     break;
+		case _V (FR_ACT_GOTO,        RTN_LOCAL)       : ss = "local";       break;
+		case _V (FR_ACT_NOP,         RTN_BROADCAST)   : ss = "nop";         break;
+		case _V (FR_ACT_RES3,        RTN_ANYCAST)     : ss = "anycast";     break;
+		case _V (FR_ACT_RES4,        RTN_MULTICAST)   : ss = "multicast";   break;
+		case _V (FR_ACT_BLACKHOLE,   RTN_BLACKHOLE)   : ss = "blackhole";   break;
+		case _V (FR_ACT_UNREACHABLE, RTN_UNREACHABLE) : ss = "unreachable"; break;
+		case _V (FR_ACT_PROHIBIT,    RTN_PROHIBIT)    : ss = "prohibit";    break;
+		case RTN_THROW                                : ss = "throw";       break;
+		case RTN_NAT                                  : ss = "nat";         break;
+		case RTN_XRESOLVE                             : ss = "xresolve";    break;
+		default:
+			ss = nm_sprintf_buf (ss_buf, "action-%u", routing_rule->action);
+			break;
+		}
+#undef _V
+		nm_utils_strbuf_append (&buf, &len, " %s", ss);
+	}
+
+	if (routing_rule->protocol != RTPROT_UNSPEC)
+		nm_utils_strbuf_append (&buf, &len, " protocol %u", routing_rule->protocol);
+
+	if (   routing_rule->goto_target != 0
+	    && routing_rule->action != FR_ACT_GOTO) {
+		/* a trailing target is set for an unexpected action. Print it. */
+		nm_utils_strbuf_append (&buf, &len, " goto-target %u", routing_rule->goto_target);
+	}
+
+	if (rr_flags != 0) {
+		/* we have some flags we didn't print about yet. */
+		nm_utils_strbuf_append (&buf, &len, " remaining-flags %x", rr_flags);
+	}
+
+	return buf0;
+}
+
 const char *
 nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len)
 {
@@ -6249,6 +6595,28 @@ nm_platform_vf_to_string (const NMPlatformVF *vf, char *buf, gsize len)
 	return buf;
 }
 
+
+const char *
+nm_platform_bridge_vlan_to_string (const NMPlatformBridgeVlan *vlan, char *buf, gsize len)
+{
+	char str_vid_end[64];
+
+	if (!nm_utils_to_string_buffer_init_null (vlan, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len,
+	            "%u"
+	            "%s"
+	            "%s"
+	            "%s",
+	            vlan->vid_start,
+	            vlan->vid_start != vlan->vid_end ? nm_sprintf_buf (str_vid_end, "-%u", vlan->vid_end) : "",
+	            vlan->pvid ? " PVID" : "",
+	            vlan->untagged ? " untagged" : "");
+
+	return buf;
+}
+
 void
 nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
 {
@@ -7034,6 +7402,216 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 	return 0;
 }
 
+#define _ROUTING_RULE_FLAGS_IGNORE (  FIB_RULE_UNRESOLVED \
+                                    | FIB_RULE_IIF_DETACHED \
+                                    | FIB_RULE_OIF_DETACHED)
+
+#define _routing_rule_compare(cmp_type, kernel_support_type) \
+	(   (cmp_type) == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL \
+	 || nm_platform_kernel_support_get (kernel_support_type))
+
+void
+nm_platform_routing_rule_hash_update (const NMPlatformRoutingRule *obj,
+                                      NMPlatformRoutingRuleCmpType cmp_type,
+                                      NMHashState *h)
+{
+	gboolean cmp_full = TRUE;
+	gsize addr_size;
+	guint32 flags_mask = G_MAXUINT32;
+
+	if (G_UNLIKELY (!NM_IN_SET (obj->addr_family, AF_INET, AF_INET6))) {
+		/* the address family is not one of the supported ones. That means, the
+		 * instance will only compare equal to itself (pointer-equality). */
+		nm_hash_update_val (h, (gconstpointer) obj);
+		return;
+	}
+
+	switch (cmp_type) {
+
+	case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID:
+
+		flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE;
+
+		/* fall-through */
+	case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY:
+
+		cmp_full = FALSE;
+
+		/* fall-through */
+	case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL:
+
+		nm_hash_update_vals (h,
+		                     obj->addr_family,
+		                     obj->tun_id,
+		                     obj->table,
+		                     obj->flags & flags_mask,
+		                     obj->priority,
+		                     obj->fwmark,
+		                     obj->fwmask,
+		                     (  (   cmp_full
+		                         || (   cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY
+		                             && obj->action == FR_ACT_GOTO))
+		                      ? obj->goto_target
+		                      : (guint32) 0u),
+		                     (  (   cmp_full
+		                         || obj->addr_family == AF_INET)
+		                      ? obj->flow
+		                      : (guint32) 0u),
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            (  _routing_rule_compare (cmp_type,
+		                                                                      NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)
+		                                             ? obj->uid_range_has
+		                                             : FALSE)),
+		                     obj->suppress_prefixlen_inverse,
+		                     obj->suppress_ifgroup_inverse,
+		                     (  _routing_rule_compare (cmp_type,
+		                                               NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)
+		                      ? (  cmp_full
+		                         ? (guint16) obj->l3mdev
+		                         : (guint16) !!obj->l3mdev)
+		                      : G_MAXUINT16),
+		                     obj->action,
+		                     obj->tos,
+		                     obj->src_len,
+		                     obj->dst_len,
+		                     (  _routing_rule_compare (cmp_type,
+		                                               NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)
+		                      ? (guint16) obj->protocol
+		                      : G_MAXUINT16));
+		addr_size = nm_utils_addr_family_to_size (obj->addr_family);
+		if (cmp_full || obj->src_len > 0)
+			nm_hash_update (h, &obj->src, addr_size);
+		if (cmp_full || obj->dst_len > 0)
+			nm_hash_update (h, &obj->dst, addr_size);
+		if (_routing_rule_compare (cmp_type,
+		                           NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) {
+			if (cmp_full || obj->uid_range_has)
+				nm_hash_update_valp (h, &obj->uid_range);
+		}
+		if (_routing_rule_compare (cmp_type,
+		                           NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) {
+			nm_hash_update_val (h, obj->ip_proto);
+			nm_hash_update_valp (h, &obj->sport_range);
+			nm_hash_update_valp (h, &obj->dport_range);
+		}
+		nm_hash_update_str (h, obj->iifname);
+		nm_hash_update_str (h, obj->oifname);
+		return;
+	}
+
+	nm_assert_not_reached ();
+}
+
+int
+nm_platform_routing_rule_cmp (const NMPlatformRoutingRule *a,
+                              const NMPlatformRoutingRule *b,
+                              NMPlatformRoutingRuleCmpType cmp_type)
+{
+	gboolean cmp_full = TRUE;
+	gsize addr_size;
+	bool valid;
+	guint32 flags_mask = G_MAXUINT32;
+
+	NM_CMP_SELF (a, b);
+
+	valid = NM_IN_SET (a->addr_family, AF_INET, AF_INET6);
+	NM_CMP_DIRECT (valid,
+	               (bool) NM_IN_SET (b->addr_family, AF_INET, AF_INET6));
+
+	if (G_UNLIKELY (!valid)) {
+		/* the address family is not one of the supported ones. That means, the
+		 * instance will only compare equal to itself. */
+		NM_CMP_DIRECT ((uintptr_t) a, (uintptr_t) b);
+		nm_assert_not_reached ();
+		return 0;
+	}
+
+	switch (cmp_type) {
+
+	case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID:
+
+		flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE;
+
+		/* fall-through */
+	case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY:
+
+		cmp_full = FALSE;
+
+		/* fall-through */
+	case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL:
+		NM_CMP_FIELD (a, b, addr_family);
+		NM_CMP_FIELD (a, b, action);
+		NM_CMP_FIELD (a, b, priority);
+		NM_CMP_FIELD (a, b, tun_id);
+
+		if (_routing_rule_compare (cmp_type,
+		                           NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) {
+			if (cmp_full)
+				NM_CMP_FIELD (a, b, l3mdev);
+			else
+				NM_CMP_FIELD_BOOL (a, b, l3mdev);
+		}
+
+		NM_CMP_FIELD (a, b, table);
+
+		NM_CMP_DIRECT (a->flags & flags_mask, b->flags & flags_mask);
+
+		NM_CMP_FIELD (a, b, fwmark);
+		NM_CMP_FIELD (a, b, fwmask);
+
+		if (   cmp_full
+		    || (   cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY
+		        && a->action == FR_ACT_GOTO))
+			NM_CMP_FIELD (a, b, goto_target);
+
+		NM_CMP_FIELD (a, b, suppress_prefixlen_inverse);
+		NM_CMP_FIELD (a, b, suppress_ifgroup_inverse);
+		NM_CMP_FIELD (a, b, tos);
+
+		if (cmp_full || a->addr_family == AF_INET)
+			NM_CMP_FIELD (a, b, flow);
+
+		if (_routing_rule_compare (cmp_type,
+		                           NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL))
+			NM_CMP_FIELD (a, b, protocol);
+
+		if (_routing_rule_compare (cmp_type,
+		                           NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) {
+			NM_CMP_FIELD (a, b, ip_proto);
+			NM_CMP_FIELD (a, b, sport_range.start);
+			NM_CMP_FIELD (a, b, sport_range.end);
+			NM_CMP_FIELD (a, b, dport_range.start);
+			NM_CMP_FIELD (a, b, dport_range.end);
+		}
+
+		addr_size = nm_utils_addr_family_to_size (a->addr_family);
+
+		NM_CMP_FIELD (a, b, src_len);
+		if (cmp_full || a->src_len > 0)
+			NM_CMP_FIELD_MEMCMP_LEN (a, b, src, addr_size);
+
+		NM_CMP_FIELD (a, b, dst_len);
+		if (cmp_full || a->dst_len > 0)
+			NM_CMP_FIELD_MEMCMP_LEN (a, b, dst, addr_size);
+
+		if (_routing_rule_compare (cmp_type,
+		                           NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) {
+			NM_CMP_FIELD_UNSAFE (a, b, uid_range_has);
+			if (cmp_full || a->uid_range_has) {
+				NM_CMP_FIELD (a, b, uid_range.start);
+				NM_CMP_FIELD (a, b, uid_range.end);
+			}
+		}
+
+		NM_CMP_FIELD_STR (a, b, iifname);
+		NM_CMP_FIELD_STR (a, b, oifname);
+		return 0;
+	}
+
+	nm_assert_not_reached ();
+	return 0;
+}
+
 /**
  * nm_platform_ip_address_cmp_expiry:
  * @a: a NMPlatformIPAddress to compare
@@ -7131,6 +7709,13 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform
 }
 
 static void
+log_routing_rule (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformRoutingRule *routing_rule, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	/* routing rules don't have an ifindex. We probably should refactor the signals that are emitted for platform changes. */
+	_LOG3D ("signal: rt-rule %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_routing_rule_to_string (routing_rule, NULL, 0));
+}
+
+static void
 log_qdisc (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformQdisc *qdisc, NMPlatformSignalChangeType change_type, gpointer user_data)
 {
 	_LOG3D ("signal: qdisc %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_qdisc_to_string (qdisc, NULL, 0));
@@ -7196,9 +7781,13 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 		return;
 	}
 
-	ifindex = o->object.ifindex;
 	klass = NMP_OBJECT_GET_CLASS (o);
 
+	if (klass->obj_type == NMP_OBJECT_TYPE_ROUTING_RULE)
+		ifindex = 0;
+	else
+		ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (o)->ifindex;
+
 	if (   klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
 	    && NM_PLATFORM_GET_PRIVATE (self)->ip4_dev_route_blacklist_gc_timeout_id
 	    && NM_IN_SET (cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED))
@@ -7214,7 +7803,7 @@ nm_platform_cache_update_emit_signal (NMPlatform *self,
 	               _nm_platform_signal_id_get (klass->signal_type_id),
 	               0,
 	               (int) klass->obj_type,
-	               o->object.ifindex,
+	               ifindex,
 	               &o->object,
 	               (int) cache_op);
 	nmp_object_unref (o);
@@ -7261,24 +7850,25 @@ _vtr_v4_metric_normalize (guint32 metric)
 
 /*****************************************************************************/
 
-const NMPlatformVTableRoute nm_platform_vtable_route_v4 = {
-	.is_ip4                         = TRUE,
-	.obj_type                       = NMP_OBJECT_TYPE_IP4_ROUTE,
-	.addr_family                    = AF_INET,
-	.sizeof_route                   = sizeof (NMPlatformIP4Route),
-	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip4_route_cmp,
-	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip4_route_to_string,
-	.metric_normalize               = _vtr_v4_metric_normalize,
-};
-
-const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
-	.is_ip4                         = FALSE,
-	.obj_type                       = NMP_OBJECT_TYPE_IP6_ROUTE,
-	.addr_family                    = AF_INET6,
-	.sizeof_route                   = sizeof (NMPlatformIP6Route),
-	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip6_route_cmp,
-	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip6_route_to_string,
-	.metric_normalize               = nm_utils_ip6_route_metric_normalize,
+const _NMPlatformVTableRouteUnion nm_platform_vtable_route = {
+	.v4 = {
+		.is_ip4                         = TRUE,
+		.obj_type                       = NMP_OBJECT_TYPE_IP4_ROUTE,
+		.addr_family                    = AF_INET,
+		.sizeof_route                   = sizeof (NMPlatformIP4Route),
+		.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip4_route_cmp,
+		.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip4_route_to_string,
+		.metric_normalize               = _vtr_v4_metric_normalize,
+	},
+	.v6 = {
+		.is_ip4                         = FALSE,
+		.obj_type                       = NMP_OBJECT_TYPE_IP6_ROUTE,
+		.addr_family                    = AF_INET6,
+		.sizeof_route                   = sizeof (NMPlatformIP6Route),
+		.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip6_route_cmp,
+		.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip6_route_to_string,
+		.metric_normalize               = nm_utils_ip6_route_metric_normalize,
+	},
 };
 
 /*****************************************************************************/
@@ -7338,8 +7928,9 @@ constructor (GType type,
 
 	priv->multi_idx = nm_dedup_multi_index_new ();
 
-	priv->cache = nmp_cache_new (nm_platform_get_multi_idx (self),
+	priv->cache = nmp_cache_new (priv->multi_idx,
 	                             priv->use_udev);
+
 	return object;
 }
 
@@ -7411,11 +8002,12 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	} G_STMT_END
 
 	/* Signals */
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_LINK,        NM_PLATFORM_SIGNAL_LINK_CHANGED,        log_link);
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, log_ip4_address);
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, log_ip6_address);
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,   NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,   log_ip4_route);
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,   NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,   log_ip6_route);
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_QDISC,       NM_PLATFORM_SIGNAL_QDISC_CHANGED,       log_qdisc);
-	SIGNAL (NM_PLATFORM_SIGNAL_ID_TFILTER,     NM_PLATFORM_SIGNAL_TFILTER_CHANGED,     log_tfilter);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_LINK,         NM_PLATFORM_SIGNAL_LINK_CHANGED,         log_link);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,  NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,  log_ip4_address);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,  NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,  log_ip6_address);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,    NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,    log_ip4_route);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,    NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,    log_ip6_route);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED, log_routing_rule);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_QDISC,        NM_PLATFORM_SIGNAL_QDISC_CHANGED,        log_qdisc);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_TFILTER,      NM_PLATFORM_SIGNAL_TFILTER_CHANGED,      log_tfilter);
 }
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 37aa58fd..97be8832 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -152,6 +152,14 @@ typedef enum {
 } NMPlatformIPRouteCmpType;
 
 typedef enum {
+	NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
+
+	NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY,
+
+	NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL,
+} NMPlatformRoutingRuleCmpType;
+
+typedef enum {
 
 	/* match-flags are strictly inclusive. That means,
 	 * by default nothing is matched, but if you enable a particular
@@ -181,12 +189,22 @@ typedef enum {
 
 #define NM_PLATFORM_LINK_OTHER_NETNS    (-1)
 
-#define __NMPlatformObject_COMMON \
+struct _NMPlatformObject {
+	/* the object type has no fields of its own, it is only used to having
+	 * a special pointer type that can be used to indicate "any" type. */
+	char _dummy_don_t_use_me;
+};
+
+#define __NMPlatformObjWithIfindex_COMMON \
 	int ifindex; \
 	;
 
+struct _NMPlatformObjWithIfindex {
+	__NMPlatformObjWithIfindex_COMMON;
+};
+
 struct _NMPlatformLink {
-	__NMPlatformObject_COMMON;
+	__NMPlatformObjWithIfindex_COMMON;
 	char name[NMP_IFNAMSIZ];
 	NMLinkType type;
 
@@ -246,6 +264,7 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
 	NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
 	NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
+	NM_PLATFORM_SIGNAL_ID_ROUTING_RULE,
 	NM_PLATFORM_SIGNAL_ID_QDISC,
 	NM_PLATFORM_SIGNAL_ID_TFILTER,
 	_NM_PLATFORM_SIGNAL_ID_LAST,
@@ -260,15 +279,11 @@ typedef enum {
 	NM_PLATFORM_SIGNAL_REMOVED,
 } NMPlatformSignalChangeType;
 
-struct _NMPlatformObject {
-	__NMPlatformObject_COMMON;
-};
-
 #define NM_PLATFORM_IP_ADDRESS_CAST(address) \
 	NM_CONSTCAST (NMPlatformIPAddress, (address), NMPlatformIPXAddress, NMPlatformIP4Address, NMPlatformIP6Address)
 
 #define __NMPlatformIPAddress_COMMON \
-	__NMPlatformObject_COMMON; \
+	__NMPlatformObjWithIfindex_COMMON; \
 	NMIPConfigSource addr_source; \
 	\
 	/* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*().
@@ -370,7 +385,7 @@ typedef union {
 #define NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE 0
 
 #define __NMPlatformIPRoute_COMMON \
-	__NMPlatformObject_COMMON; \
+	__NMPlatformObjWithIfindex_COMMON; \
 	\
 	/* The NMIPConfigSource. For routes that we receive from cache this corresponds
 	 * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values).
@@ -414,7 +429,7 @@ typedef union {
 	 * Such a route is not alive, according to nmp_object_is_alive().
 	 *
 	 * NOTE: currently we ignore all flags except RTM_F_CLONED
-	 * and RTNH_F_ONLINK for IPv4.
+	 * and RTNH_F_ONLINK.
 	 * We also may not properly consider the flags as part of the ID
 	 * in route-cmp. */ \
 	unsigned r_rtm_flags; \
@@ -540,7 +555,49 @@ typedef union {
 #undef __NMPlatformIPRoute_COMMON
 
 typedef struct {
-	__NMPlatformObject_COMMON;
+	/* struct fib_rule_uid_range */
+	guint32 start;
+	guint32 end;
+} NMFibRuleUidRange;
+
+typedef struct {
+	/* struct fib_rule_port_range */
+	guint16 start;
+	guint16 end;
+} NMFibRulePortRange;
+
+typedef struct {
+	NMIPAddr src;                        /* FRA_SRC */
+	NMIPAddr dst;                        /* FRA_DST */
+	guint64  tun_id;                     /* betoh64(FRA_TUN_ID) */
+	guint32  table;                      /* (struct fib_rule_hdr).table, FRA_TABLE */
+	guint32  flags;                      /* (struct fib_rule_hdr).flags */
+	guint32  priority;                   /* RA_PRIORITY */
+	guint32  fwmark;                     /* FRA_FWMARK */
+	guint32  fwmask;                     /* FRA_FWMASK */
+	guint32  goto_target;                /* FRA_GOTO */
+	guint32  flow;                       /* FRA_FLOW */
+	guint32  suppress_prefixlen_inverse; /* ~(FRA_SUPPRESS_PREFIXLEN) */
+	guint32  suppress_ifgroup_inverse;   /* ~(FRA_SUPPRESS_IFGROUP) */
+	NMFibRuleUidRange uid_range;         /* FRA_UID_RANGE */
+	NMFibRulePortRange sport_range;      /* FRA_SPORT_RANGE */
+	NMFibRulePortRange dport_range;      /* FRA_DPORT_RANGE */
+	char     iifname[NMP_IFNAMSIZ];      /* FRA_IIFNAME */
+	char     oifname[NMP_IFNAMSIZ];      /* FRA_OIFNAME */
+	guint8   addr_family;                /* (struct fib_rule_hdr).family */
+	guint8   action;                     /* (struct fib_rule_hdr).action */
+	guint8   tos;                        /* (struct fib_rule_hdr).tos */
+	guint8   src_len;                    /* (struct fib_rule_hdr).src_len */
+	guint8   dst_len;                    /* (struct fib_rule_hdr).dst_len */
+	guint8   l3mdev;                     /* FRA_L3MDEV */
+	guint8   protocol;                   /* FRA_PROTOCOL */
+	guint8   ip_proto;                   /* FRA_IP_PROTO */
+
+	bool     uid_range_has:1;            /* has(FRA_UID_RANGE) */
+} NMPlatformRoutingRule;
+
+typedef struct {
+	__NMPlatformObjWithIfindex_COMMON;
 	const char *kind;
 	int addr_family;
 	guint32 handle;
@@ -562,7 +619,7 @@ typedef struct {
 #define NM_PLATFORM_ACTION_KIND_SIMPLE "simple"
 
 typedef struct {
-	__NMPlatformObject_COMMON;
+	__NMPlatformObjWithIfindex_COMMON;
 	const char *kind;
 	int addr_family;
 	guint32 handle;
@@ -571,7 +628,7 @@ typedef struct {
 	NMPlatformAction action;
 } NMPlatformTfilter;
 
-#undef __NMPlatformObject_COMMON
+#undef __NMPlatformObjWithIfindex_COMMON
 
 typedef struct {
 	gboolean is_ip4;
@@ -583,8 +640,15 @@ typedef struct {
 	guint32 (*metric_normalize) (guint32 metric);
 } NMPlatformVTableRoute;
 
-extern const NMPlatformVTableRoute nm_platform_vtable_route_v4;
-extern const NMPlatformVTableRoute nm_platform_vtable_route_v6;
+typedef union {
+	struct {
+		NMPlatformVTableRoute v6;
+		NMPlatformVTableRoute v4;
+	};
+	NMPlatformVTableRoute vx[2];
+} _NMPlatformVTableRouteUnion;
+
+extern const _NMPlatformVTableRouteUnion nm_platform_vtable_route;
 
 typedef struct {
 	guint16 id;
@@ -607,6 +671,13 @@ typedef struct {
 } NMPlatformVF;
 
 typedef struct {
+	guint16 vid_start;
+	guint16 vid_end;
+	bool untagged:1;
+	bool pvid:1;
+} NMPlatformBridgeVlan;
+
+typedef struct {
 	in_addr_t local;
 	in_addr_t remote;
 	int parent_ifindex;
@@ -748,12 +819,6 @@ 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),
-} 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),
@@ -779,6 +844,51 @@ typedef enum {
 
 /*****************************************************************************/
 
+typedef enum {
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS,
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL,
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF,
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV,
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE,
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL,
+
+	/* this also includes FRA_SPORT_RANGE and FRA_DPORT_RANGE which
+	 * were added at the same time. */
+	NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO,
+
+	_NM_PLATFORM_KERNEL_SUPPORT_NUM,
+} NMPlatformKernelSupportType;
+
+extern volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM];
+
+int _nm_platform_kernel_support_init (NMPlatformKernelSupportType type,
+                                      int value);
+
+#define _nm_platform_kernel_support_detected(type) \
+	G_LIKELY (({ \
+		const NMPlatformKernelSupportType _type = (type); \
+		\
+		nm_assert (_NM_INT_NOT_NEGATIVE (_type) && _type < G_N_ELEMENTS (_nm_platform_kernel_support_state)); \
+		\
+		(_nm_platform_kernel_support_state[_type] != 0); \
+	}))
+
+#define nm_platform_kernel_support_get(type) \
+	({ \
+		const NMPlatformKernelSupportType _type = (type); \
+		int _v; \
+		\
+		nm_assert (_NM_INT_NOT_NEGATIVE (_type) && _type < G_N_ELEMENTS (_nm_platform_kernel_support_state)); \
+		\
+		_v = _nm_platform_kernel_support_state[_type]; \
+		if (G_UNLIKELY (_v == 0)) \
+			_v = _nm_platform_kernel_support_init (_type, 0); \
+		\
+		(_v >= 0); \
+	})
+
+/*****************************************************************************/
+
 struct _NMPlatformPrivate;
 
 struct _NMPlatform {
@@ -793,8 +903,6 @@ typedef struct {
 	gboolean (*sysctl_set) (NMPlatform *, const char *pathid, int dirfd, const char *path, const char *value);
 	char * (*sysctl_get) (NMPlatform *, const char *pathid, int dirfd, const char *path);
 
-	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
-
 	int (*link_add) (NMPlatform *,
 	                 const char *name,
 	                 NMLinkType type,
@@ -831,6 +939,7 @@ typedef struct {
 	gboolean (*link_set_name) (NMPlatform *, int ifindex, const char *name);
 	gboolean (*link_set_sriov_params) (NMPlatform *, int ifindex, guint num_vfs, int autoprobe);
 	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
+	gboolean (*link_set_bridge_vlans) (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans);
 
 	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
 	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
@@ -973,6 +1082,10 @@ typedef struct {
 	                     int oif_ifindex,
 	                     NMPObject **out_route);
 
+	int (*routing_rule_add) (NMPlatform *self,
+	                         NMPNlmFlags flags,
+	                         const NMPlatformRoutingRule *routing_rule);
+
 	int (*qdisc_add)   (NMPlatform *self,
 	                    NMPNlmFlags flags,
 	                    const NMPlatformQdisc *qdisc);
@@ -980,9 +1093,6 @@ typedef struct {
 	int (*tfilter_add)   (NMPlatform *self,
 	                      NMPNlmFlags flags,
 	                      const NMPlatformTfilter *tfilter);
-
-	NMPlatformKernelSupportFlags (*check_kernel_support) (NMPlatform * self,
-	                                                      NMPlatformKernelSupportFlags request_flags);
 } NMPlatformClass;
 
 /* NMPlatform signals
@@ -1001,6 +1111,7 @@ typedef struct {
 #define NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED "ip6-address-changed"
 #define NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED "ip4-route-changed"
 #define NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED "ip6-route-changed"
+#define NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED "routing-rule-changed"
 #define NM_PLATFORM_SIGNAL_QDISC_CHANGED "qdisc-changed"
 #define NM_PLATFORM_SIGNAL_TFILTER_CHANGED "tfilter-changed"
 
@@ -1167,8 +1278,6 @@ gboolean nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
 const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */);
 int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname);
 
-void nm_platform_refresh_all (NMPlatform *self, NMPObjectType obj_type);
-
 const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
                                            int ifindex,
                                            gboolean visible_only);
@@ -1261,6 +1370,7 @@ int nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
 gboolean nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name);
 gboolean nm_platform_link_set_sriov_params (NMPlatform *self, int ifindex, guint num_vfs, int autoprobe);
 gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
+gboolean nm_platform_link_set_bridge_vlans (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans);
 
 char    *nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex);
 guint    nm_platform_link_get_dev_id (NMPlatform *self, int ifindex);
@@ -1482,6 +1592,10 @@ int nm_platform_ip_route_get (NMPlatform *self,
                               int oif_ifindex,
                               NMPObject **out_route);
 
+int nm_platform_routing_rule_add (NMPlatform *self,
+                                  NMPNlmFlags flags,
+                                  const NMPlatformRoutingRule *routing_rule);
+
 int nm_platform_qdisc_add   (NMPlatform *self,
                              NMPNlmFlags flags,
                              const NMPlatformQdisc *qdisc);
@@ -1512,9 +1626,11 @@ const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *addre
 const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len);
 const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len);
 const char *nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len);
+const char *nm_platform_routing_rule_to_string (const NMPlatformRoutingRule *routing_rule, char *buf, gsize len);
 const char *nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len);
 const char *nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsize len);
 const char *nm_platform_vf_to_string (const NMPlatformVF *vf, char *buf, gsize len);
+const char *nm_platform_bridge_vlan_to_string (const NMPlatformBridgeVlan *vlan, char *buf, gsize len);
 
 const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     const NMVlanQosMapping *map,
@@ -1556,6 +1672,14 @@ nm_platform_ip6_route_cmp_full (const NMPlatformIP6Route *a, const NMPlatformIP6
 	return nm_platform_ip6_route_cmp (a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
 }
 
+int nm_platform_routing_rule_cmp (const NMPlatformRoutingRule *a, const NMPlatformRoutingRule *b, NMPlatformRoutingRuleCmpType cmp_type);
+
+static inline int
+nm_platform_routing_rule_cmp_full (const NMPlatformRoutingRule *a, const NMPlatformRoutingRule *b)
+{
+	return nm_platform_routing_rule_cmp (a, b, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL);
+}
+
 int nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b);
 int nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b);
 
@@ -1564,6 +1688,7 @@ void nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHas
 void nm_platform_ip6_address_hash_update (const NMPlatformIP6Address *obj, NMHashState *h);
 void nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRouteCmpType cmp_type, NMHashState *h);
 void nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRouteCmpType cmp_type, NMHashState *h);
+void nm_platform_routing_rule_hash_update (const NMPlatformRoutingRule *obj, NMPlatformRoutingRuleCmpType cmp_type, NMHashState *h);
 void nm_platform_lnk_gre_hash_update (const NMPlatformLnkGre *obj, NMHashState *h);
 void nm_platform_lnk_infiniband_hash_update (const NMPlatformLnkInfiniband *obj, NMHashState *h);
 void nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState *h);
@@ -1579,9 +1704,6 @@ void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, N
 void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
 void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
 
-NMPlatformKernelSupportFlags nm_platform_check_kernel_support (NMPlatform *self,
-                                                               NMPlatformKernelSupportFlags request_flags);
-
 const char *nm_platform_link_flags2str (unsigned flags, char *buf, gsize len);
 const char *nm_platform_link_inet6_addrgenmode2str (guint8 mode, char *buf, gsize len);
 const char *nm_platform_addr_flags2str (unsigned flags, char *buf, gsize len);
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 6ec6fea1..0d20100c 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -28,7 +28,7 @@
 #include <libudev.h>
 
 #include "nm-utils.h"
-#include "nm-utils/nm-secret-utils.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
@@ -389,16 +389,16 @@ _idx_obj_part (const DedupMultiIdxType *idx_type,
 				nm_hash_update_val (h, obj_a);
 			return 0;
 		}
-		nm_assert (obj_a->object.ifindex > 0);
+		nm_assert (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex > 0);
 		if (obj_b) {
 			return    NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b)
-			       && obj_a->object.ifindex == obj_b->object.ifindex
+			       && NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex == NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_b)->ifindex
 			       && nmp_object_is_visible (obj_b);
 		}
 		if (h) {
 			nm_hash_update_vals (h,
 			                     idx_type->cache_id_type,
-			                     obj_a->object.ifindex);
+			                     NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex);
 		}
 		return 1;
 
@@ -406,14 +406,14 @@ _idx_obj_part (const DedupMultiIdxType *idx_type,
 		obj_type = NMP_OBJECT_GET_TYPE (obj_a);
 		if (   !NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE,
 		                             NMP_OBJECT_TYPE_IP6_ROUTE)
-		    || obj_a->object.ifindex <= 0) {
+		    || NMP_OBJECT_CAST_IP_ROUTE (obj_a)->ifindex <= 0) {
 			if (h)
 				nm_hash_update_val (h, obj_a);
 			return 0;
 		}
 		if (obj_b) {
 			return    obj_type == NMP_OBJECT_GET_TYPE (obj_b)
-			       && obj_b->object.ifindex > 0
+			       && NMP_OBJECT_CAST_IP_ROUTE (obj_b)->ifindex > 0
 			       && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
 			           ? (nm_platform_ip4_route_cmp (&obj_a->ip4_route, &obj_b->ip4_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) == 0)
 			           : (nm_platform_ip6_route_cmp (&obj_a->ip6_route, &obj_b->ip6_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) == 0));
@@ -427,6 +427,25 @@ _idx_obj_part (const DedupMultiIdxType *idx_type,
 		}
 		return 1;
 
+	case NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY:
+		obj_type = NMP_OBJECT_GET_TYPE (obj_a);
+		/* currently, only routing rules are supported for this cache-id-type. */
+		if (   obj_type != NMP_OBJECT_TYPE_ROUTING_RULE
+		    || !NM_IN_SET (obj_a->routing_rule.addr_family, AF_INET, AF_INET6)) {
+			if (h)
+				nm_hash_update_val (h, obj_a);
+			return 0;
+		}
+		if (obj_b) {
+			return    NMP_OBJECT_GET_TYPE (obj_b) == NMP_OBJECT_TYPE_ROUTING_RULE
+			       && obj_a->routing_rule.addr_family == obj_b->routing_rule.addr_family;
+		}
+		if (h) {
+			nm_hash_update_vals (h, idx_type->cache_id_type,
+			                        obj_a->routing_rule.addr_family);
+		}
+		return 1;
+
 	case NMP_CACHE_ID_TYPE_NONE:
 	case __NMP_CACHE_ID_TYPE_MAX:
 		break;
@@ -712,16 +731,6 @@ _nmp_object_fixup_link_master_connected (NMPObject **obj_new, NMPObject *obj_ori
 
 /*****************************************************************************/
 
-const NMPClass *
-nmp_class_from_type (NMPObjectType obj_type)
-{
-	g_return_val_if_fail (obj_type > NMP_OBJECT_TYPE_UNKNOWN && obj_type <= NMP_OBJECT_TYPE_MAX, NULL);
-
-	return &_nmp_classes[obj_type - 1];
-}
-
-/*****************************************************************************/
-
 static void
 _vt_cmd_obj_dispose_link (NMPObject *obj)
 {
@@ -1369,6 +1378,10 @@ _vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, {
 	*dst = *src;
 	nm_assert (nm_platform_ip6_route_cmp (dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0);
 });
+_vt_cmd_plobj_id_copy (routing_rule, NMPlatformRoutingRule, {
+	*dst = *src;
+	nm_assert (nm_platform_routing_rule_cmp (dst, src, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0);
+});
 
 /* Uses internally nmp_object_copy(), hence it also violates the const
  * promise for @obj.
@@ -1469,6 +1482,12 @@ _vt_cmd_plobj_id_cmp_ip6_route (const NMPlatformObject *obj1, const NMPlatformOb
 	return nm_platform_ip6_route_cmp ((NMPlatformIP6Route *) obj1, (NMPlatformIP6Route *) obj2, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
 }
 
+static int
+_vt_cmd_plobj_id_cmp_routing_rule (const NMPlatformObject *obj1, const NMPlatformObject *obj2)
+{
+	return nm_platform_routing_rule_cmp ((NMPlatformRoutingRule *) obj1, (NMPlatformRoutingRule *) obj2, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID);
+}
+
 void
 nmp_object_id_hash_update (const NMPObject *obj, NMHashState *h)
 {
@@ -1534,6 +1553,9 @@ _vt_cmd_plobj_id_hash_update (ip4_route, NMPlatformIP4Route, {
 _vt_cmd_plobj_id_hash_update (ip6_route, NMPlatformIP6Route, {
 	nm_platform_ip6_route_hash_update (obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
 })
+_vt_cmd_plobj_id_hash_update (routing_rule, NMPlatformRoutingRule, {
+	nm_platform_routing_rule_hash_update (obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, h);
+})
 _vt_cmd_plobj_id_hash_update (qdisc, NMPlatformQdisc, {
 	nm_hash_update_vals (h,
 	                     obj->ifindex,
@@ -1557,6 +1579,12 @@ _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);
 }
 
+static void
+_vt_cmd_plobj_hash_update_routing_rule (const NMPlatformObject *obj, NMHashState *h)
+{
+	return nm_platform_routing_rule_hash_update ((const NMPlatformRoutingRule *) obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, h);
+}
+
 gboolean
 nmp_object_is_alive (const NMPObject *obj)
 {
@@ -1574,13 +1602,14 @@ nmp_object_is_alive (const NMPObject *obj)
 static gboolean
 _vt_cmd_obj_is_alive_link (const NMPObject *obj)
 {
-	return obj->object.ifindex > 0 && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device);
+	return    NMP_OBJECT_CAST_LINK (obj)->ifindex > 0
+	       && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device);
 }
 
 static gboolean
 _vt_cmd_obj_is_alive_ipx_address (const NMPObject *obj)
 {
-	return obj->object.ifindex > 0;
+	return NMP_OBJECT_CAST_IP_ADDRESS (obj)->ifindex > 0;
 }
 
 static gboolean
@@ -1601,20 +1630,26 @@ _vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
 	 * Instead we create a dead object, and nmp_cache_update_netlink()
 	 * will remove the old version of the update.
 	 **/
-	return    obj->object.ifindex > 0
+	return    NMP_OBJECT_CAST_IP_ROUTE (obj)->ifindex > 0
 	       && !NM_FLAGS_HAS (obj->ip_route.r_rtm_flags, RTM_F_CLONED);
 }
 
 static gboolean
+_vt_cmd_obj_is_alive_routing_rule (const NMPObject *obj)
+{
+	return NM_IN_SET (obj->routing_rule.addr_family, AF_INET, AF_INET6);
+}
+
+static gboolean
 _vt_cmd_obj_is_alive_qdisc (const NMPObject *obj)
 {
-	return obj->object.ifindex > 0;
+	return NMP_OBJECT_CAST_QDISC (obj)->ifindex > 0;
 }
 
 static gboolean
 _vt_cmd_obj_is_alive_tfilter (const NMPObject *obj)
 {
-	return obj->object.ifindex > 0;
+	return NMP_OBJECT_CAST_TFILTER (obj)->ifindex > 0;
 }
 
 gboolean
@@ -1672,6 +1707,12 @@ static const guint8 _supported_cache_ids_ipx_route[] = {
 	0,
 };
 
+static const guint8 _supported_cache_ids_routing_rules[] = {
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+	NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY,
+	0,
+};
+
 /*****************************************************************************/
 
 static void
@@ -1949,6 +1990,7 @@ nmp_lookup_init_obj_type (NMPLookup *lookup,
 	case NMP_OBJECT_TYPE_IP6_ADDRESS:
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
+	case NMP_OBJECT_TYPE_ROUTING_RULE:
 	case NMP_OBJECT_TYPE_QDISC:
 	case NMP_OBJECT_TYPE_TFILTER:
 		_nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
@@ -1998,7 +2040,7 @@ nmp_lookup_init_object (NMPLookup *lookup,
 	}
 
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
-	o->object.ifindex = ifindex;
+	o->obj_with_ifindex.ifindex = ifindex;
 	lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX;
 	return _L (lookup);
 }
@@ -2014,7 +2056,7 @@ nmp_lookup_init_route_default (NMPLookup *lookup,
 	                                NMP_OBJECT_TYPE_IP6_ROUTE));
 
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
-	o->object.ifindex = 1;
+	o->ip_route.ifindex = 1;
 	lookup->cache_id_type = NMP_CACHE_ID_TYPE_DEFAULT_ROUTES;
 	return _L (lookup);
 }
@@ -2062,9 +2104,9 @@ nmp_lookup_init_ip4_route_by_weak_id (NMPLookup *lookup,
 	nm_assert (lookup);
 
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_IP4_ROUTE);
-	o->object.ifindex = 1;
-	o->ip_route.plen = plen;
-	o->ip_route.metric = metric;
+	o->ip4_route.ifindex = 1;
+	o->ip4_route.plen = plen;
+	o->ip4_route.metric = metric;
 	if (network)
 		o->ip4_route.network = network;
 	o->ip4_route.tos = tos;
@@ -2085,9 +2127,9 @@ nmp_lookup_init_ip6_route_by_weak_id (NMPLookup *lookup,
 	nm_assert (lookup);
 
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_IP6_ROUTE);
-	o->object.ifindex = 1;
-	o->ip_route.plen = plen;
-	o->ip_route.metric = metric;
+	o->ip6_route.ifindex = 1;
+	o->ip6_route.plen = plen;
+	o->ip6_route.metric = metric;
 	if (network)
 		o->ip6_route.network = *network;
 	if (src)
@@ -2097,6 +2139,23 @@ nmp_lookup_init_ip6_route_by_weak_id (NMPLookup *lookup,
 	return _L (lookup);
 }
 
+const NMPLookup *
+nmp_lookup_init_object_by_addr_family (NMPLookup *lookup,
+                                       NMPObjectType obj_type,
+                                       int addr_family)
+{
+	NMPObject *o;
+
+	nm_assert (lookup);
+	nm_assert_addr_family (addr_family);
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_ROUTING_RULE));
+
+	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
+	NMP_OBJECT_CAST_ROUTING_RULE (o)->addr_family = addr_family;
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY;
+	return _L (lookup);
+}
+
 /*****************************************************************************/
 
 GArray *
@@ -2920,15 +2979,25 @@ nmp_cache_update_link_master_connected (NMPCache *cache,
 /*****************************************************************************/
 
 void
-nmp_cache_dirty_set_all (NMPCache *cache,
-                         const NMPLookup *lookup)
+nmp_cache_dirty_set_all_main (NMPCache *cache,
+                              const NMPLookup *lookup)
 {
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+
 	nm_assert (cache);
 	nm_assert (lookup);
 
-	nm_dedup_multi_index_dirty_set_head (cache->multi_idx,
-	                                     _idx_type_get (cache, lookup->cache_id_type),
-	                                     &lookup->selector_obj);
+	head_entry = nmp_cache_lookup (cache, lookup);
+
+	nm_dedup_multi_iter_init (&iter, head_entry);
+	while (nm_dedup_multi_iter_next (&iter)) {
+		const NMDedupMultiEntry *main_entry;
+
+		main_entry = nmp_cache_reresolve_main_entry (cache, iter.current, lookup);
+
+		nm_dedup_multi_entry_set_dirty (main_entry, TRUE);
+	}
 }
 
 /*****************************************************************************/
@@ -3076,6 +3145,25 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = _vt_cmd_plobj_hash_update_ip6_route,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp_full,
 	},
+	[NMP_OBJECT_TYPE_ROUTING_RULE - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_ROUTING_RULE,
+		.sizeof_data                        = sizeof (NMPObjectRoutingRule),
+		.sizeof_public                      = sizeof (NMPlatformRoutingRule),
+		.obj_type_name                      = "routing-rule",
+		.rtm_gettype                        = RTM_GETRULE,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_ROUTING_RULE,
+		.signal_type                        = NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED,
+		.supported_cache_ids                = _supported_cache_ids_routing_rules,
+		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_routing_rule,
+		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_routing_rule,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_routing_rule,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_routing_rule,
+		.cmd_plobj_to_string_id             = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_routing_rule_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_routing_rule_to_string,
+		.cmd_plobj_hash_update              = _vt_cmd_plobj_hash_update_routing_rule,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_routing_rule_cmp_full,
+	},
 	[NMP_OBJECT_TYPE_QDISC - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_QDISC,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 525e1440..3ce9db36 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -23,8 +23,8 @@
 
 #include <netinet/in.h>
 
-#include "nm-utils/nm-obj.h"
-#include "nm-utils/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-obj.h"
+#include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-platform.h"
 
 struct udev_device;
@@ -174,6 +174,11 @@ typedef enum { /*< skip >*/
 	 * cache-resync. */
 	NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
 
+	/* a filter for objects that track an explicit address family.
+	 *
+	 * Note that currently on NMPObjectRoutingRule is indexed by this filter. */
+	NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY,
+
 	__NMP_CACHE_ID_TYPE_MAX,
 	NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1,
 } NMPCacheIdType;
@@ -325,6 +330,10 @@ typedef struct {
 } NMPObjectIP6Route;
 
 typedef struct {
+	NMPlatformRoutingRule _public;
+} NMPObjectRoutingRule;
+
+typedef struct {
 	NMPlatformQdisc _public;
 } NMPObjectQdisc;
 
@@ -340,6 +349,8 @@ struct _NMPObject {
 	union {
 		NMPlatformObject        object;
 
+		NMPlatformObjWithIfindex obj_with_ifindex;
+
 		NMPlatformLink          link;
 		NMPObjectLink           _link;
 
@@ -390,6 +401,9 @@ struct _NMPObject {
 		NMPObjectIP4Route       _ip4_route;
 		NMPObjectIP6Route       _ip6_route;
 
+		NMPlatformRoutingRule   routing_rule;
+		NMPObjectRoutingRule    _routing_rule;
+
 		NMPlatformQdisc         qdisc;
 		NMPObjectQdisc          _qdisc;
 		NMPlatformTfilter       tfilter;
@@ -397,6 +411,8 @@ struct _NMPObject {
 	};
 };
 
+/*****************************************************************************/
+
 static inline gboolean
 NMP_CLASS_IS_VALID (const NMPClass *klass)
 {
@@ -405,6 +421,17 @@ NMP_CLASS_IS_VALID (const NMPClass *klass)
 	    && ((((char *) klass) - ((char *) _nmp_classes)) % (sizeof (_nmp_classes[0]))) == 0;
 }
 
+static inline const NMPClass *
+nmp_class_from_type (NMPObjectType obj_type)
+{
+	nm_assert (obj_type > 0);
+	nm_assert (obj_type <= G_N_ELEMENTS (_nmp_classes));
+	nm_assert (_nmp_classes[obj_type - 1].obj_type == obj_type);
+	nm_assert (NMP_CLASS_IS_VALID (&_nmp_classes[obj_type - 1]));
+
+	return &_nmp_classes[obj_type - 1];
+}
+
 static inline NMPObject *
 NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj)
 {
@@ -454,11 +481,65 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	return obj ? obj->_class->obj_type : NMP_OBJECT_TYPE_UNKNOWN;
 }
 
+static inline gboolean
+_NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMPObjectType obj_type)
+{
+	switch (obj_type) {
+	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:
+
+	case NMP_OBJECT_TYPE_QDISC:
+
+	case NMP_OBJECT_TYPE_TFILTER:
+
+	case NMP_OBJECT_TYPE_LNK_GRE:
+	case NMP_OBJECT_TYPE_LNK_GRETAP:
+	case NMP_OBJECT_TYPE_LNK_INFINIBAND:
+	case NMP_OBJECT_TYPE_LNK_IP6TNL:
+	case NMP_OBJECT_TYPE_LNK_IP6GRE:
+	case NMP_OBJECT_TYPE_LNK_IP6GRETAP:
+	case NMP_OBJECT_TYPE_LNK_IPIP:
+	case NMP_OBJECT_TYPE_LNK_MACSEC:
+	case NMP_OBJECT_TYPE_LNK_MACVLAN:
+	case NMP_OBJECT_TYPE_LNK_MACVTAP:
+	case NMP_OBJECT_TYPE_LNK_SIT:
+	case NMP_OBJECT_TYPE_LNK_TUN:
+	case NMP_OBJECT_TYPE_LNK_VLAN:
+	case NMP_OBJECT_TYPE_LNK_VXLAN:
+	case NMP_OBJECT_TYPE_LNK_WIREGUARD:
+		return TRUE;
+	default:
+		nm_assert (nmp_class_from_type (obj_type));
+		return FALSE;
+	}
+}
+
+#define NMP_OBJECT_CAST_OBJECT(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (   !_obj \
+		           || nmp_class_from_type (NMP_OBJECT_GET_TYPE (_obj)))); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->object : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (   !_obj \
+		           || _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMP_OBJECT_GET_TYPE (_obj))); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->obj_with_ifindex : NULL; \
+	})
+
 #define NMP_OBJECT_CAST_LINK(obj) \
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_LINK); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_LINK); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->link : NULL; \
 	})
 
@@ -482,7 +563,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip4_address : NULL; \
 	})
 
@@ -490,7 +571,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP6_ADDRESS); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP6_ADDRESS); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_address : NULL; \
 	})
 
@@ -514,7 +595,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP4_ROUTE); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP4_ROUTE); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip4_route : NULL; \
 	})
 
@@ -522,15 +603,23 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP6_ROUTE); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_IP6_ROUTE); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_route : NULL; \
 	})
 
+#define NMP_OBJECT_CAST_ROUTING_RULE(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_ROUTING_RULE); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->routing_rule : NULL; \
+	})
+
 #define NMP_OBJECT_CAST_QDISC(obj) \
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_QDISC); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_QDISC); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->qdisc : NULL; \
 	})
 
@@ -538,12 +627,10 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_TFILTER); \
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_TFILTER); \
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->tfilter : NULL; \
 	})
 
-const NMPClass *nmp_class_from_type (NMPObjectType obj_type);
-
 static inline const NMPObject *
 nmp_object_ref (const NMPObject *obj)
 {
@@ -565,9 +652,11 @@ nmp_object_ref (const NMPObject *obj)
 static inline void
 nmp_object_unref (const NMPObject *obj)
 {
-	nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
+	if (obj) {
+		nm_assert (NMP_OBJECT_IS_VALID (obj));
 
-	nm_dedup_multi_obj_unref ((const NMDedupMultiObj *) obj);
+		nm_dedup_multi_obj_unref ((const NMDedupMultiObj *) obj);
+	}
 }
 
 #define nm_clear_nmp_object(ptr) \
@@ -695,6 +784,9 @@ const NMPLookup *nmp_lookup_init_ip6_route_by_weak_id (NMPLookup *lookup,
                                                        guint32 metric,
                                                        const struct in6_addr *src,
                                                        guint8 src_plen);
+const NMPLookup *nmp_lookup_init_object_by_addr_family (NMPLookup *lookup,
+                                                        NMPObjectType obj_type,
+                                                        int addr_family);
 
 GArray *nmp_cache_lookup_to_array (const NMDedupMultiHeadEntry *head_entry,
                                    NMPObjectType obj_type,
@@ -784,8 +876,34 @@ 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,
-                              const NMPLookup *lookup);
+static inline const NMDedupMultiEntry *
+nmp_cache_reresolve_main_entry (NMPCache *cache,
+                                const NMDedupMultiEntry *entry,
+                                const NMPLookup *lookup)
+{
+	const NMDedupMultiEntry *main_entry;
+
+	nm_assert (cache);
+	nm_assert (entry);
+	nm_assert (lookup);
+
+	if (lookup->cache_id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) {
+		nm_assert (entry == nmp_cache_lookup_entry (cache, entry->obj));
+		return entry;
+	}
+
+	/* we only track the dirty flag for the OBJECT-TYPE index. That means,
+	 * for other lookup types we need to check the dirty flag of the main-entry. */
+	main_entry = nmp_cache_lookup_entry (cache, entry->obj);
+
+	nm_assert (main_entry);
+	nm_assert (main_entry->obj == entry->obj);
+
+	return main_entry;
+}
+
+void nmp_cache_dirty_set_all_main (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/nmp-rules-manager.c b/src/platform/nmp-rules-manager.c
new file mode 100644
index 00000000..970afcde
--- /dev/null
+++ b/src/platform/nmp-rules-manager.c
@@ -0,0 +1,737 @@
+/*
+ * 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.
+ */
+
+#include "nm-default.h"
+
+#include "nmp-rules-manager.h"
+
+#include <linux/fib_rules.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-std-aux/c-list-util.h"
+#include "nmp-object.h"
+
+/*****************************************************************************/
+
+struct _NMPRulesManager {
+	NMPlatform *platform;
+	GHashTable *by_obj;
+	GHashTable *by_user_tag;
+	GHashTable *by_data;
+	guint ref_count;
+};
+
+/*****************************************************************************/
+
+static void _rules_init (NMPRulesManager *self);
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN           LOGD_PLATFORM
+#define _NMLOG_PREFIX_NAME      "rules-manager"
+
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        \
+        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+            _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \
+                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME \
+                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static gboolean
+NMP_IS_RULES_MANAGER (gpointer self)
+{
+	return    self
+	       && ((NMPRulesManager *) self)->ref_count > 0
+	       && NM_IS_PLATFORM (((NMPRulesManager *) self)->platform);
+}
+
+#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr (1240261787u, (user_tag))
+
+/*****************************************************************************/
+
+typedef struct {
+	const NMPObject *obj;
+	gconstpointer user_tag;
+	CList obj_lst;
+	CList user_tag_lst;
+
+	/* track_priority_val zero is special: those are weakly tracked rules.
+	 * That means: NetworkManager will restore them only if it removed them earlier.
+	 * But it will not remove or add them otherwise.
+	 *
+	 * Otherwise, the track_priority_val goes together with track_priority_present.
+	 * In case of one rule being tracked multile times (with different priorities),
+	 * the one with higher priority wins. See _rules_obj_get_best_data().
+	 * Then, the winning present state either enforces that the rule is present
+	 * or absent.
+	 *
+	 * If a rules is not tracked at all, it is ignored by NetworkManager. Assuming
+	 * that it was added externally by the user. But unlike weakly tracked rules,
+	 * NM will *not* restore such rules if NetworkManager themself removed them. */
+	guint32 track_priority_val;
+	bool track_priority_present:1;
+
+	bool dirty:1;
+} RulesData;
+
+typedef enum {
+	CONFIG_STATE_NONE          = 0,
+	CONFIG_STATE_ADDED_BY_US   = 1,
+	CONFIG_STATE_REMOVED_BY_US = 2,
+} ConfigState;
+
+typedef struct {
+	const NMPObject *obj;
+	CList obj_lst_head;
+
+	/* indicates whether we configured/removed the rule (during sync()). We need that, so
+	 * if the rule gets untracked, that we know to remove/restore it.
+	 *
+	 * This makes NMPRulesManager stateful (beyond the configuration that indicates
+	 * which rules are tracked).
+	 * After a restart, NetworkManager would no longer remember which rules were added
+	 * by us. That would need to be fixed by persisting the state and reloading it after
+	 * restart. */
+	ConfigState config_state;
+} RulesObjData;
+
+typedef struct {
+	gconstpointer user_tag;
+	CList user_tag_lst_head;
+} RulesUserTagData;
+
+static void
+_rules_data_assert (const RulesData *rules_data, gboolean linked)
+{
+	nm_assert (rules_data);
+	nm_assert (NMP_OBJECT_GET_TYPE (rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
+	nm_assert (nmp_object_is_visible (rules_data->obj));
+	nm_assert (rules_data->user_tag);
+	nm_assert (!linked || !c_list_is_empty (&rules_data->obj_lst));
+	nm_assert (!linked || !c_list_is_empty (&rules_data->user_tag_lst));
+}
+
+static guint
+_rules_data_hash (gconstpointer data)
+{
+	const RulesData *rules_data = data;
+	NMHashState h;
+
+	_rules_data_assert (rules_data, FALSE);
+
+	nm_hash_init (&h, 269297543u);
+	nm_platform_routing_rule_hash_update (NMP_OBJECT_CAST_ROUTING_RULE (rules_data->obj),
+	                                      NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
+	                                      &h);
+	nm_hash_update_val (&h, rules_data->user_tag);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_rules_data_equal (gconstpointer data_a, gconstpointer data_b)
+{
+	const RulesData *rules_data_a = data_a;
+	const RulesData *rules_data_b = data_b;
+
+	_rules_data_assert (rules_data_a, FALSE);
+	_rules_data_assert (rules_data_b, FALSE);
+
+	return    rules_data_a->user_tag == rules_data_b->user_tag
+	       && (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (rules_data_a->obj),
+	                                         NMP_OBJECT_CAST_ROUTING_RULE (rules_data_b->obj),
+	                                         NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0);
+}
+
+static void
+_rules_data_destroy (gpointer data)
+{
+	RulesData *rules_data = data;
+
+	_rules_data_assert (rules_data, FALSE);
+
+	c_list_unlink_stale (&rules_data->obj_lst);
+	c_list_unlink_stale (&rules_data->user_tag_lst);
+	nmp_object_unref (rules_data->obj);
+	g_slice_free (RulesData, rules_data);
+}
+
+static const RulesData *
+_rules_obj_get_best_data (RulesObjData *obj_data)
+{
+	RulesData *rules_data;
+	const RulesData *rd_best = NULL;
+
+	c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) {
+
+		_rules_data_assert (rules_data, TRUE);
+
+		if (rd_best) {
+			if (rd_best->track_priority_val > rules_data->track_priority_val)
+				continue;
+			if (rd_best->track_priority_val == rules_data->track_priority_val) {
+				if (   rd_best->track_priority_present
+				    || !rules_data->track_priority_present) {
+					/* if the priorities are identical, then "present" wins over
+					 * "!present" (absent). */
+					continue;
+				}
+			}
+		}
+
+		rd_best = rules_data;
+	}
+
+	return rd_best;
+}
+
+static guint
+_rules_obj_hash (gconstpointer data)
+{
+	const RulesObjData *obj_data = data;
+	NMHashState h;
+
+	nm_hash_init (&h, 432817559u);
+	nm_platform_routing_rule_hash_update (NMP_OBJECT_CAST_ROUTING_RULE (obj_data->obj),
+	                                      NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
+	                                      &h);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_rules_obj_equal (gconstpointer data_a, gconstpointer data_b)
+{
+	const RulesObjData *obj_data_a = data_a;
+	const RulesObjData *obj_data_b = data_b;
+
+	return (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (obj_data_a->obj),
+	                                      NMP_OBJECT_CAST_ROUTING_RULE (obj_data_b->obj),
+	                                      NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0);
+}
+
+static void
+_rules_obj_destroy (gpointer data)
+{
+	RulesObjData *obj_data = data;
+
+	c_list_unlink_stale (&obj_data->obj_lst_head);
+	nmp_object_unref (obj_data->obj);
+	g_slice_free (RulesObjData, obj_data);
+}
+
+static guint
+_rules_user_tag_hash (gconstpointer data)
+{
+	const RulesUserTagData *user_tag_data = data;
+
+	return nm_hash_val (644693447u, user_tag_data->user_tag);
+}
+
+static gboolean
+_rules_user_tag_equal (gconstpointer data_a, gconstpointer data_b)
+{
+	const RulesUserTagData *user_tag_data_a = data_a;
+	const RulesUserTagData *user_tag_data_b = data_b;
+
+	return user_tag_data_a->user_tag == user_tag_data_b->user_tag;
+}
+
+static void
+_rules_user_tag_destroy (gpointer data)
+{
+	RulesUserTagData *user_tag_data = data;
+
+	c_list_unlink_stale (&user_tag_data->user_tag_lst_head);
+	g_slice_free (RulesUserTagData, user_tag_data);
+}
+
+static RulesData *
+_rules_data_lookup (GHashTable *by_data,
+                    const NMPObject *obj,
+                    gconstpointer user_tag)
+{
+	RulesData rules_data_needle = {
+		.obj      = obj,
+		.user_tag = user_tag,
+	};
+
+	return g_hash_table_lookup (by_data, &rules_data_needle);
+}
+
+void
+nmp_rules_manager_track (NMPRulesManager *self,
+                         const NMPlatformRoutingRule *routing_rule,
+                         gint32 track_priority,
+                         gconstpointer user_tag)
+{
+	NMPObject obj_stack;
+	const NMPObject *p_obj_stack;
+	RulesData *rules_data;
+	RulesObjData *obj_data;
+	RulesUserTagData *user_tag_data;
+	gboolean changed = FALSE;
+	guint32 track_priority_val;
+	gboolean track_priority_present;
+
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+	g_return_if_fail (routing_rule);
+	g_return_if_fail (user_tag);
+	nm_assert (track_priority != G_MININT32);
+
+	_rules_init (self);
+
+	p_obj_stack = nmp_object_stackinit (&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule);
+
+	nm_assert (nmp_object_is_visible (p_obj_stack));
+
+	if (track_priority >= 0) {
+		track_priority_val = track_priority;
+		track_priority_present = TRUE;
+	} else {
+		track_priority_val = -track_priority;
+		track_priority_present = FALSE;
+	}
+
+	rules_data = _rules_data_lookup (self->by_data, p_obj_stack, user_tag);
+
+	if (!rules_data) {
+		rules_data = g_slice_new (RulesData);
+		*rules_data = (RulesData) {
+			.obj                    = nm_dedup_multi_index_obj_intern (nm_platform_get_multi_idx (self->platform),
+			                                                           p_obj_stack),
+			.user_tag               = user_tag,
+			.track_priority_val     = track_priority_val,
+			.track_priority_present = track_priority_present,
+			.dirty                  = FALSE,
+		};
+		g_hash_table_add (self->by_data, rules_data);
+
+		obj_data = g_hash_table_lookup (self->by_obj, &rules_data->obj);
+		if (!obj_data) {
+			obj_data = g_slice_new (RulesObjData);
+			*obj_data = (RulesObjData) {
+				.obj          = nmp_object_ref (rules_data->obj),
+				.obj_lst_head = C_LIST_INIT (obj_data->obj_lst_head),
+				.config_state = CONFIG_STATE_NONE,
+			};
+			g_hash_table_add (self->by_obj, obj_data);
+		}
+		c_list_link_tail (&obj_data->obj_lst_head, &rules_data->obj_lst);
+
+		user_tag_data = g_hash_table_lookup (self->by_user_tag, &rules_data->user_tag);
+		if (!user_tag_data) {
+			user_tag_data = g_slice_new (RulesUserTagData);
+			*user_tag_data = (RulesUserTagData) {
+				.user_tag          = user_tag,
+				.user_tag_lst_head = C_LIST_INIT (user_tag_data->user_tag_lst_head),
+			};
+			g_hash_table_add (self->by_user_tag, user_tag_data);
+		}
+		c_list_link_tail (&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst);
+		changed = TRUE;
+	} else {
+		rules_data->dirty = FALSE;
+		if (   rules_data->track_priority_val != track_priority_val
+		    || rules_data->track_priority_present != track_priority_present) {
+			rules_data->track_priority_val = track_priority_val;
+			rules_data->track_priority_present = track_priority_present;
+			changed = TRUE;
+		}
+	}
+
+	_rules_data_assert (rules_data, TRUE);
+
+	if (changed) {
+		_LOGD ("routing-rule: track ["NM_HASH_OBFUSCATE_PTR_FMT",%s%u] \"%s\")",
+		       _USER_TAG_LOG (rules_data->user_tag),
+		       ( rules_data->track_priority_val == 0
+		        ? ""
+		        : (  rules_data->track_priority_present
+		           ? "+"
+		           : "-")),
+		       (guint) rules_data->track_priority_val,
+		       nmp_object_to_string (rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+	}
+}
+
+static void
+_rules_data_untrack (NMPRulesManager *self,
+                     RulesData *rules_data,
+                     gboolean remove_user_tag_data)
+{
+	RulesObjData *obj_data;
+
+	nm_assert (NMP_IS_RULES_MANAGER (self));
+	_rules_data_assert (rules_data, TRUE);
+	nm_assert (self->by_data);
+	nm_assert (g_hash_table_lookup (self->by_data, rules_data) == rules_data);
+
+	_LOGD ("routing-rule: untrack ["NM_HASH_OBFUSCATE_PTR_FMT"] \"%s\"",
+	       _USER_TAG_LOG (rules_data->user_tag),
+	       nmp_object_to_string (rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+
+#if NM_MORE_ASSERTS
+	{
+		RulesUserTagData *user_tag_data;
+
+		user_tag_data = g_hash_table_lookup (self->by_user_tag, &rules_data->user_tag);
+		nm_assert (user_tag_data);
+		nm_assert (c_list_contains (&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst));
+	}
+#endif
+
+	nm_assert (!c_list_is_empty (&rules_data->user_tag_lst));
+	if (   remove_user_tag_data
+	    && c_list_length_is (&rules_data->user_tag_lst, 1))
+		g_hash_table_remove (self->by_user_tag, &rules_data->user_tag);
+
+	obj_data = g_hash_table_lookup (self->by_obj, &rules_data->obj);
+	nm_assert (obj_data);
+	nm_assert (c_list_contains (&obj_data->obj_lst_head, &rules_data->obj_lst));
+	nm_assert (obj_data == g_hash_table_lookup (self->by_obj, &rules_data->obj));
+
+	/* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry
+	 * around for the next sync -- so that we can undo what we did earlier. */
+	if (   obj_data->config_state == CONFIG_STATE_NONE
+	    && c_list_length_is (&rules_data->obj_lst, 1))
+		g_hash_table_remove (self->by_obj, &rules_data->obj);
+
+	g_hash_table_remove (self->by_data, rules_data);
+}
+
+void
+nmp_rules_manager_untrack (NMPRulesManager *self,
+                           const NMPlatformRoutingRule *routing_rule,
+                           gconstpointer user_tag)
+{
+	NMPObject obj_stack;
+	const NMPObject *p_obj_stack;
+	RulesData *rules_data;
+
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+	g_return_if_fail (routing_rule);
+	g_return_if_fail (user_tag);
+
+	_rules_init (self);
+
+	p_obj_stack = nmp_object_stackinit (&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule);
+
+	nm_assert (nmp_object_is_visible (p_obj_stack));
+
+	rules_data = _rules_data_lookup (self->by_data, p_obj_stack, user_tag);
+	if (rules_data)
+		_rules_data_untrack (self, rules_data, TRUE);
+}
+
+void
+nmp_rules_manager_set_dirty (NMPRulesManager *self,
+                             gconstpointer user_tag)
+{
+	RulesData *rules_data;
+	RulesUserTagData *user_tag_data;
+
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+	g_return_if_fail (user_tag);
+
+	if (!self->by_data)
+		return;
+
+	user_tag_data = g_hash_table_lookup (self->by_user_tag, &user_tag);
+	if (!user_tag_data)
+		return;
+
+	c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst)
+		rules_data->dirty = TRUE;
+}
+
+void
+nmp_rules_manager_untrack_all (NMPRulesManager *self,
+                               gconstpointer user_tag,
+                               gboolean all /* or only dirty */)
+{
+	RulesData *rules_data;
+	RulesData *rules_data_safe;
+	RulesUserTagData *user_tag_data;
+
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+	g_return_if_fail (user_tag);
+
+	if (!self->by_data)
+		return;
+
+	user_tag_data = g_hash_table_lookup (self->by_user_tag, &user_tag);
+	if (!user_tag_data)
+		return;
+
+	c_list_for_each_entry_safe (rules_data, rules_data_safe, &user_tag_data->user_tag_lst_head, user_tag_lst) {
+		if (   all
+		    || rules_data->dirty)
+			_rules_data_untrack (self, rules_data, FALSE);
+	}
+	if (c_list_is_empty (&user_tag_data->user_tag_lst_head))
+		g_hash_table_remove (self->by_user_tag, user_tag_data);
+}
+
+void
+nmp_rules_manager_sync (NMPRulesManager *self,
+                        gboolean keep_deleted_rules)
+{
+	const NMDedupMultiHeadEntry *pl_head_entry;
+	NMDedupMultiIter pl_iter;
+	const NMPObject *plobj;
+	gs_unref_ptrarray GPtrArray *rules_to_delete = NULL;
+	RulesObjData *obj_data;
+	GHashTableIter h_iter;
+	guint i;
+	const RulesData *rd_best;
+
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+
+	if (!self->by_data)
+		return;
+
+	_LOGD ("sync%s", keep_deleted_rules ? " (don't remove any rules)" : "");
+
+	pl_head_entry = nm_platform_lookup_obj_type (self->platform, NMP_OBJECT_TYPE_ROUTING_RULE);
+	if (pl_head_entry) {
+		nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) {
+			obj_data = g_hash_table_lookup (self->by_obj, &plobj);
+
+			if (!obj_data) {
+				/* this rule is not tracked. It was externally added, hence we
+				 * ignore it. */
+				continue;
+			}
+
+			rd_best = _rules_obj_get_best_data (obj_data);
+			if (rd_best) {
+				if (rd_best->track_priority_present)
+					continue;
+				if (rd_best->track_priority_val == 0) {
+					if (obj_data->config_state != CONFIG_STATE_ADDED_BY_US)
+						continue;
+					obj_data->config_state = CONFIG_STATE_NONE;
+				}
+			}
+
+			if (keep_deleted_rules) {
+				_LOGD ("forget/leak rule added by us: %s", nmp_object_to_string (plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+				continue;
+			}
+
+			if (!rules_to_delete)
+				rules_to_delete = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+			g_ptr_array_add (rules_to_delete, (gpointer) nmp_object_ref (plobj));
+
+			obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
+		}
+	}
+
+	if (rules_to_delete) {
+		for (i = 0; i < rules_to_delete->len; i++)
+			nm_platform_object_delete (self->platform, rules_to_delete->pdata[i]);
+	}
+
+	g_hash_table_iter_init (&h_iter, self->by_obj);
+	while (g_hash_table_iter_next (&h_iter, (gpointer *) &obj_data, NULL)) {
+
+		rd_best = _rules_obj_get_best_data (obj_data);
+
+		if (!rd_best) {
+			g_hash_table_iter_remove (&h_iter);
+			continue;
+		}
+
+		if (!rd_best->track_priority_present)
+			continue;
+		if (rd_best->track_priority_val == 0) {
+			if (obj_data->config_state != CONFIG_STATE_REMOVED_BY_US)
+				continue;
+			obj_data->config_state = CONFIG_STATE_NONE;
+		}
+
+		plobj = nm_platform_lookup_obj (self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj);
+		if (plobj)
+			continue;
+
+		obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
+		nm_platform_routing_rule_add (self->platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj_data->obj));
+	}
+}
+
+void
+nmp_rules_manager_track_from_platform (NMPRulesManager *self,
+                                       NMPlatform *platform,
+                                       int addr_family,
+                                       gint32 tracking_priority,
+                                       gconstpointer user_tag)
+{
+	NMPLookup lookup;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *o;
+
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+
+	if (!platform)
+		platform = self->platform;
+	else
+		g_return_if_fail (NM_IS_PLATFORM (platform));
+
+	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	head_entry = nm_platform_lookup (platform, &lookup);
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (o);
+
+		if (   addr_family != AF_UNSPEC
+		    && rr->addr_family != addr_family)
+			continue;
+
+		nmp_rules_manager_track (self, rr, tracking_priority, user_tag);
+	}
+}
+
+/*****************************************************************************/
+
+void
+nmp_rules_manager_track_default (NMPRulesManager *self,
+                                 int addr_family,
+                                 gint32 track_priority,
+                                 gconstpointer user_tag)
+{
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+
+	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	/* track the default rules. See also `man ip-rule`. */
+
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) {
+		nmp_rules_manager_track (self,
+		                         &((NMPlatformRoutingRule) {
+		                             .addr_family = AF_INET,
+		                             .priority    = 0,
+		                             .table       = RT_TABLE_LOCAL,
+		                             .action      = FR_ACT_TO_TBL,
+		                             .protocol    = RTPROT_KERNEL,
+		                         }),
+		                         track_priority,
+		                         user_tag);
+		nmp_rules_manager_track (self,
+		                         &((NMPlatformRoutingRule) {
+		                             .addr_family = AF_INET,
+		                             .priority    = 32766,
+		                             .table       = RT_TABLE_MAIN,
+		                             .action      = FR_ACT_TO_TBL,
+		                             .protocol    = RTPROT_KERNEL,
+		                         }),
+		                         track_priority,
+		                         user_tag);
+		nmp_rules_manager_track (self,
+		                         &((NMPlatformRoutingRule) {
+		                             .addr_family = AF_INET,
+		                             .priority    = 32767,
+		                             .table       = RT_TABLE_DEFAULT,
+		                             .action      = FR_ACT_TO_TBL,
+		                             .protocol    = RTPROT_KERNEL,
+		                         }),
+		                         track_priority,
+		                         user_tag);
+	}
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
+		nmp_rules_manager_track (self,
+		                         &((NMPlatformRoutingRule) {
+		                             .addr_family = AF_INET6,
+		                             .priority    = 0,
+		                             .table       = RT_TABLE_LOCAL,
+		                             .action      = FR_ACT_TO_TBL,
+		                             .protocol    = RTPROT_KERNEL,
+		                         }),
+		                         track_priority,
+		                         user_tag);
+		nmp_rules_manager_track (self,
+		                         &((NMPlatformRoutingRule) {
+		                             .addr_family = AF_INET6,
+		                             .priority    = 32766,
+		                             .table       = RT_TABLE_MAIN,
+		                             .action      = FR_ACT_TO_TBL,
+		                             .protocol    = RTPROT_KERNEL,
+		                         }),
+		                         track_priority,
+		                         user_tag);
+	}
+}
+
+static void
+_rules_init (NMPRulesManager *self)
+{
+	if (self->by_data)
+		return;
+
+	self->by_data      = g_hash_table_new_full (_rules_data_hash,      _rules_data_equal,      NULL, _rules_data_destroy);
+	self->by_obj       = g_hash_table_new_full (_rules_obj_hash,       _rules_obj_equal,       NULL, _rules_obj_destroy);
+	self->by_user_tag  = g_hash_table_new_full (_rules_user_tag_hash,  _rules_user_tag_equal,  NULL, _rules_user_tag_destroy);
+}
+
+/*****************************************************************************/
+
+NMPRulesManager *
+nmp_rules_manager_new (NMPlatform *platform)
+{
+	NMPRulesManager *self;
+
+	g_return_val_if_fail (NM_IS_PLATFORM (platform), NULL);
+
+	self = g_slice_new (NMPRulesManager);
+	*self = (NMPRulesManager) {
+		.ref_count     = 1,
+		.platform      = g_object_ref (platform),
+	};
+	return self;
+}
+
+void
+nmp_rules_manager_ref (NMPRulesManager *self)
+{
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+
+	self->ref_count++;
+}
+
+void nmp_rules_manager_unref (NMPRulesManager *self)
+{
+	g_return_if_fail (NMP_IS_RULES_MANAGER (self));
+
+	if (--self->ref_count > 0)
+		return;
+
+	if (self->by_data) {
+		g_hash_table_destroy (self->by_user_tag);
+		g_hash_table_destroy (self->by_obj);
+		g_hash_table_destroy (self->by_data);
+	}
+	g_object_unref (self->platform);
+	g_slice_free (NMPRulesManager, self);
+}
diff --git a/src/platform/nmp-rules-manager.h b/src/platform/nmp-rules-manager.h
new file mode 100644
index 00000000..310c7971
--- /dev/null
+++ b/src/platform/nmp-rules-manager.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef __NMP_RULES_MANAGER_H__
+#define __NMP_RULES_MANAGER_H__
+
+#include "nm-platform.h"
+
+/*****************************************************************************/
+
+typedef struct _NMPRulesManager NMPRulesManager;
+
+NMPRulesManager *nmp_rules_manager_new (NMPlatform *platform);
+
+void nmp_rules_manager_ref (NMPRulesManager *self);
+void nmp_rules_manager_unref (NMPRulesManager *self);
+
+#define nm_auto_unref_rules_manager nm_auto (_nmp_rules_manager_unref)
+NM_AUTO_DEFINE_FCN0 (NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_manager_unref)
+
+void nmp_rules_manager_track (NMPRulesManager *self,
+                              const NMPlatformRoutingRule *routing_rule,
+                              gint32 track_priority,
+                              gconstpointer user_tag);
+
+void nmp_rules_manager_track_default (NMPRulesManager *self,
+                                      int addr_family,
+                                      gint32 track_priority,
+                                      gconstpointer user_tag);
+
+void nmp_rules_manager_track_from_platform (NMPRulesManager *self,
+                                            NMPlatform *platform,
+                                            int addr_family,
+                                            gint32 tracking_priority,
+                                            gconstpointer user_tag);
+
+void nmp_rules_manager_untrack (NMPRulesManager *self,
+                                const NMPlatformRoutingRule *routing_rule,
+                                gconstpointer user_tag);
+
+void nmp_rules_manager_set_dirty (NMPRulesManager *self,
+                                  gconstpointer user_tag);
+
+void nmp_rules_manager_untrack_all (NMPRulesManager *self,
+                                    gconstpointer user_tag,
+                                    gboolean all /* or only dirty */);
+
+void nmp_rules_manager_sync (NMPRulesManager *self,
+                             gboolean keep_deleted_rules);
+
+/*****************************************************************************/
+
+#endif /* __NMP_RULES_MANAGER_H__ */
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index f0e3e6cf..fff5967b 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -77,8 +77,6 @@ main (int argc, char **argv)
 
 	nm_linux_platform_setup ();
 
-	nm_platform_check_kernel_support (NM_PLATFORM_GET, ~((NMPlatformKernelSupportFlags) 0));
-
 	if (global_opt.persist)
 		g_main_loop_run (loop);
 
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index c510710b..7ebda9ff 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -900,7 +900,7 @@ _ip_address_add (NMPlatform *platform,
 	}
 
 	/* Let's wait until we see the address. */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 500;
 	do {
 
 		if (external_command)
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index aa1f5460..e4eaec29 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -273,6 +273,58 @@ nmtstp_ip6_route_get_all (NMPlatform *platform,
 GArray *nmtstp_platform_ip4_address_get_all (NMPlatform *self, int ifindex);
 GArray *nmtstp_platform_ip6_address_get_all (NMPlatform *self, int ifindex);
 
+/*****************************************************************************/
+
+static inline gboolean
+_nmtstp_platform_routing_rules_get_all_predicate (const NMPObject *obj,
+                                                  gpointer user_data)
+{
+	int addr_family = GPOINTER_TO_INT (user_data);
+
+	g_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
+
+	return    addr_family == AF_UNSPEC
+	       || NMP_OBJECT_CAST_ROUTING_RULE (obj)->addr_family == addr_family;
+}
+
+static inline GPtrArray *
+nmtstp_platform_routing_rules_get_all (NMPlatform *platform, int addr_family)
+{
+	NMPLookup lookup;
+
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	return nm_platform_lookup_clone (platform,
+	                                 &lookup,
+	                                 _nmtstp_platform_routing_rules_get_all_predicate,
+	                                 GINT_TO_POINTER (addr_family));
+}
+
+static inline guint
+nmtstp_platform_routing_rules_get_count (NMPlatform *platform, int addr_family)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *obj;
+	NMPLookup lookup;
+	guint n;
+
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	head_entry = nm_platform_lookup (platform, &lookup);
+
+	n = 0;
+	nmp_cache_iter_for_each (&iter, head_entry, &obj) {
+		if (_nmtstp_platform_routing_rules_get_all_predicate (obj, GINT_TO_POINTER (addr_family)))
+			n++;
+	}
+	return n;
+}
+
 gboolean nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
 gboolean nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 71324301..a0650cc1 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -25,7 +25,7 @@
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nmp-netns.h"
 #include "platform/nm-platform-utils.h"
@@ -521,8 +521,7 @@ test_bridge_addr (void)
 	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 	g_assert (plink);
 
-	if (nm_platform_check_kernel_support (NM_PLATFORM_GET,
-	                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
+	if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
@@ -2963,6 +2962,37 @@ test_sysctl_netns_switch (void)
 	g_assert_cmpint (ifindex, ==, (gint32) nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_NETDIR (dirfd, s ?: "<unknown>", "ifindex"), -1));
 	g_assert_cmpint (ifindex, ==, (gint32) nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (nm_sprintf_bufa (100, "/sys/class/net/%s/ifindex", IFNAME)), -1));
 
+	/* also test that nm_platform_sysctl_get() sets errno to ENOENT for non-existing paths. */
+	{
+		gint64 i64;
+		int errsv;
+		char *v;
+
+		errno = ESRCH;
+		v = nm_platform_sysctl_get (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/not-existing/ifindex"));
+		errsv = errno;
+		g_assert (!v);
+		g_assert_cmpint (errsv, ==, ENOENT);
+
+		errno = ESRCH;
+		i64 = nm_platform_sysctl_get_int_checked (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/not-existing/ifindex"), 10, 1, G_MAXINT, -1);
+		errsv = errno;
+		g_assert_cmpint (i64, ==, -1);
+		g_assert_cmpint (errsv, ==, ENOENT);
+
+		errno = ESRCH;
+		v = nm_platform_sysctl_get (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/lo/not-existing"));
+		errsv = errno;
+		g_assert (!v);
+		g_assert_cmpint (errsv, ==, ENOENT);
+
+		errno = ESRCH;
+		i64 = nm_platform_sysctl_get_int_checked (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/lo/not-existing"), 10, 1, G_MAXINT, -1);
+		errsv = errno;
+		g_assert_cmpint (i64, ==, -1);
+		g_assert_cmpint (errsv, ==, ENOENT);
+	}
+
 	/* accessing the path directly, only succeeds iff the current namespace happens to be the namespace
 	 * in which we created the link. */
 	{
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 12acf4a5..e2019e96 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -24,7 +24,7 @@
 #include <linux/pkt_sched.h>
 
 #include "platform/nmp-object.h"
-#include "nm-utils/nm-udev-utils.h"
+#include "nm-udev-aux/nm-udev-utils.h"
 
 #include "nm-test-utils-core.h"
 
@@ -223,7 +223,7 @@ _nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, const NMPObject **ou
 	g_assert (cache);
 	g_assert (NMP_OBJECT_IS_VALID (obj));
 
-	obj_prev = nmp_cache_lookup_link (cache, obj->object.ifindex);
+	obj_prev = nmp_cache_lookup_link (cache, NMP_OBJECT_CAST_LINK (obj)->ifindex);
 	obj_new_expected = nmp_object_clone (obj, FALSE);
 	if (obj_prev && obj_prev->_link.udev.device)
 		obj_new_expected->_link.udev.device = udev_device_ref (obj_prev->_link.udev.device);
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 2619ec52..e6ffe1ae 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -20,9 +20,11 @@
 #include "nm-default.h"
 
 #include <linux/rtnetlink.h>
+#include <linux/fib_rules.h>
 
 #include "nm-core-utils.h"
 #include "platform/nm-platform-utils.h"
+#include "platform/nmp-rules-manager.h"
 
 #include "test-common.h"
 
@@ -873,6 +875,861 @@ again_find_idx:
 
 /*****************************************************************************/
 
+#define FRA_SUPPRESS_IFGROUP         13
+#define FRA_SUPPRESS_PREFIXLEN       14
+#define FRA_L3MDEV                   19
+#define FRA_UID_RANGE                20
+#define FRA_PROTOCOL                 21
+#define FRA_IP_PROTO                 22
+#define FRA_SPORT_RANGE              23
+#define FRA_DPORT_RANGE              24
+
+static const NMPObject *
+_rule_find_by_priority (NMPlatform *platform,
+                        guint32 priority)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *o;
+	const NMPObject *obj = NULL;
+	NMPLookup lookup;
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	head_entry = nm_platform_lookup (platform, &lookup);
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		if (NMP_OBJECT_CAST_ROUTING_RULE (o)->priority != priority)
+			continue;
+		g_assert (!obj);
+		obj = o;
+	}
+	return obj;
+}
+
+static const NMPObject *
+_rule_check_kernel_support_one (NMPlatform *platform,
+                                const NMPlatformRoutingRule *rr)
+{
+	nm_auto_nmpobj const NMPObject *obj = NULL;
+	int r;
+
+	g_assert (!_rule_find_by_priority (platform, rr->priority));
+
+	r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, rr);
+	g_assert_cmpint (r, ==, 0);
+
+	obj = nmp_object_ref (_rule_find_by_priority (platform, rr->priority));
+	g_assert (obj);
+
+	r = nm_platform_object_delete (platform, obj);
+	g_assert_cmpint (r, ==, TRUE);
+
+	g_assert (!_rule_find_by_priority (platform, rr->priority));
+
+	return g_steal_pointer (&obj);
+}
+
+static gboolean
+_rule_check_kernel_support (NMPlatform *platform,
+                            int attribute)
+{
+	static int support[] = {
+		[FRA_SUPPRESS_IFGROUP]   = 0,
+		[FRA_SUPPRESS_PREFIXLEN] = 0,
+		[FRA_L3MDEV]             = 0,
+		[FRA_UID_RANGE]          = 0,
+		[FRA_PROTOCOL]           = 0,
+		[FRA_IP_PROTO]           = 0,
+		[FRA_SPORT_RANGE]        = 0,
+		[FRA_DPORT_RANGE]        = 0,
+	};
+	const guint32 PROBE_PRORITY = 12033;
+	gboolean sup;
+	int i;
+
+	g_assert (NM_IS_PLATFORM (platform));
+
+	if (attribute == -1) {
+		for (i = 0; i < G_N_ELEMENTS (support); i++) {
+			if (support[i] < 0) {
+				/* indicate that some test was skipped. */
+				return FALSE;
+			}
+		}
+		return TRUE;
+	}
+
+	g_assert (attribute >= 0 && attribute < G_N_ELEMENTS (support));
+
+	if (support[attribute] != 0)
+		return support[attribute] >= 0;
+
+	switch (attribute) {
+	case FRA_SUPPRESS_IFGROUP: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family              = AF_INET,
+			.priority                 = PROBE_PRORITY,
+			.suppress_ifgroup_inverse = ~((guint32) 1245),
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->suppress_prefixlen_inverse == rr.suppress_ifgroup_inverse;
+		break;
+	}
+	case FRA_SUPPRESS_PREFIXLEN: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family                = AF_INET,
+			.priority                   = PROBE_PRORITY,
+			.suppress_prefixlen_inverse = ~((guint32) 1245),
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->suppress_prefixlen_inverse == rr.suppress_prefixlen_inverse;
+		break;
+	}
+	case FRA_L3MDEV: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family = AF_INET,
+			.priority    = PROBE_PRORITY,
+			.l3mdev      = TRUE,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->l3mdev != 0;
+		break;
+	}
+	case FRA_UID_RANGE: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family  = AF_INET,
+			.priority     = PROBE_PRORITY,
+			.uid_range    = {
+				.start = 0,
+				.end   = 0,
+			},
+			.uid_range_has = TRUE,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->uid_range_has;
+		break;
+	}
+	case FRA_PROTOCOL: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family = AF_INET,
+			.priority    = PROBE_PRORITY,
+			.protocol    = 30,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->protocol == 30;
+		break;
+	}
+	case FRA_IP_PROTO: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family = AF_INET,
+			.priority    = PROBE_PRORITY,
+			.ip_proto    = 30,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->ip_proto == 30;
+		break;
+	}
+	case FRA_SPORT_RANGE:
+	case FRA_DPORT_RANGE:
+		/* these were added at the same time as FRA_IP_PROTO. */
+		sup = _rule_check_kernel_support (platform, FRA_IP_PROTO);
+		break;
+	default:
+		g_assert_not_reached ();
+		return FALSE;
+	}
+
+	support[attribute] = sup ? 1 : -1;
+
+	_LOGD ("kernel support for routing rule attribute #%d %s", attribute, sup ? "detected" : "not detected");
+	return sup;
+}
+
+static const NMPObject *
+_platform_has_routing_rule (NMPlatform *platform,
+                            const NMPObject *obj)
+{
+	const NMPObject *o;
+
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (NMP_OBJECT_IS_VALID (obj));
+	g_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
+
+	o = nm_platform_lookup_obj (platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj);
+	if (o)
+		g_assert (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (obj), NMP_OBJECT_CAST_ROUTING_RULE (o), NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0);
+
+	return o;
+}
+
+static guint32
+_rr_rand_choose_u32 (guint32 p)
+{
+	/* mostly, we just return zero. We want that each rule only has few
+	 * fields set -- having most fields at zero. */
+	if ((p % 10000u) < 7500u)
+		return 0;
+
+	/* give 0xFFFFFFFFu extra probability. */
+	if ((p % 10000u) < 8250u)
+		return 0xFFFFFFFFu;
+
+	/* choose a small number. */
+	if ((p % 10000u) < 9125u)
+		return (~p) % 10;
+
+	/* finally, full random number. */
+	return ~p;
+}
+
+#define _rr_rand_choose_u8(p)  ((guint8) _rr_rand_choose_u32 ((p)))
+
+static const NMPObject *
+_rule_create_random (NMPlatform *platform)
+{
+	NMPObject *obj;
+	NMPlatformRoutingRule *rr;
+	guint32 p;
+	int addr_size;
+	guint i;
+	char saddr[NM_UTILS_INET_ADDRSTRLEN];
+	static struct {
+		guint32 uid;
+		guint32 euid;
+		bool initialized;
+	} uids;
+
+	if (G_UNLIKELY (!uids.initialized)) {
+		uids.uid = getuid ();
+		uids.euid = geteuid ();
+		uids.initialized = TRUE;
+	}
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE, NULL);
+	rr = NMP_OBJECT_CAST_ROUTING_RULE (obj);
+
+	rr->addr_family = nmtst_rand_select (AF_INET, AF_INET6);
+
+	addr_size = nm_utils_addr_family_to_size (rr->addr_family);
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 50)
+		rr->priority = 10000 + ((~p) % 20u);
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 40)
+		nm_sprintf_buf (rr->iifname, "t-iif-%u", (~p) % 20);
+	else if ((p % 1000u) < 80)
+		nm_sprintf_buf (rr->iifname, "%s", DEVICE_NAME);
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 40)
+		nm_sprintf_buf (rr->oifname, "t-oif-%d", (~p) % 20);
+	else if ((p % 1000u) < 80)
+		nm_sprintf_buf (rr->oifname, "%s", DEVICE_NAME);
+
+	for (i = 0; i < 2; i++) {
+		NMIPAddr *p_addr = i ? &rr->src     : &rr->dst;
+		guint8 *p_len    = i ? &rr->src_len : &rr->dst_len;
+
+		p = nmtst_get_rand_int ();
+		if ((p % 1000u) < 100) {
+			/* if we set src_len/dst_len to zero, the src/dst is actually ignored.
+			 *
+			 * For fuzzying, still set the address. It shall have no further effect.
+			 * */
+			*p_len = (~p) % (addr_size * 8 + 1);
+			p = nmtst_get_rand_int ();
+			if ((p % 3u) == 0) {
+				if (rr->addr_family == AF_INET)
+					p_addr->addr4 = nmtst_inet4_from_string (nm_sprintf_buf (saddr, "192.192.5.%u", (~p) % 256u));
+				else
+					p_addr->addr6 = *nmtst_inet6_from_string (nm_sprintf_buf (saddr, "1:2:3:4::f:%02x", (~p) % 256u));
+			} else if ((p % 3u) == 1)
+				nmtst_rand_buf (NULL, p_addr, addr_size);
+		}
+	}
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 50)
+		rr->tun_id = 10000 + ((~p) % 20);
+
+again_action:
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 500)
+		rr->action = FR_ACT_UNSPEC;
+	else if ((p % 1000u) < 750)
+		rr->action = (~p) % 12u;
+	else
+		rr->action = (~p) % 0x100u;
+
+	rr->priority = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (   rr->action == FR_ACT_GOTO
+	    && rr->priority == G_MAXINT32)
+		goto again_action;
+
+	p = nmtst_get_rand_int ();
+	if ((p % 10000u) < 100)
+		rr->goto_target = rr->priority + 1;
+	else
+		rr->goto_target = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	if (   rr->action == FR_ACT_GOTO
+	    && rr->goto_target <= rr->priority)
+		goto again_action;
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 25) {
+		if (_rule_check_kernel_support (platform, FRA_L3MDEV)) {
+			rr->l3mdev = TRUE;
+			rr->table = RT_TABLE_UNSPEC;
+		}
+	}
+
+again_table:
+	if (!rr->l3mdev) {
+		p = nmtst_get_rand_int ();
+		if ((p % 1000u) < 700)
+			rr->table = RT_TABLE_UNSPEC;
+		else if ((p % 1000u) < 850)
+			rr->table = RT_TABLE_MAIN;
+		else
+			rr->table = 10000 + ((~p) % 10);
+		if (   rr->action == FR_ACT_TO_TBL
+		    && rr->table == RT_TABLE_UNSPEC)
+			goto again_table;
+	}
+
+	rr->fwmark = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->fwmask = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	rr->flow = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_PROTOCOL))
+		rr->protocol = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+
+#define IPTOS_TOS_MASK 0x1E
+
+again_tos:
+	rr->tos = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+	if (   rr->addr_family == AF_INET
+	    && rr->tos & ~IPTOS_TOS_MASK)
+		goto again_tos;
+
+	if (_rule_check_kernel_support (platform, FRA_IP_PROTO))
+		rr->ip_proto = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_PREFIXLEN))
+		rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_IFGROUP))
+		rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_UID_RANGE)) {
+		p = nmtst_get_rand_int ();
+		rr->uid_range_has = (p % 10000u) < 200;
+	}
+
+again_uid_range:
+	rr->uid_range.start = nmtst_rand_select (0u, uids.uid, uids.euid);
+	rr->uid_range.end   = nmtst_rand_select (0u, uids.uid, uids.euid);
+	if (rr->uid_range_has) {
+		if (rr->uid_range.end < rr->uid_range.start)
+			NMTST_SWAP (rr->uid_range.start, rr->uid_range.end);
+		if (   rr->uid_range.start == ((guint32) -1)
+		    || rr->uid_range.end   == ((guint32) -1))
+			goto again_uid_range;
+	}
+
+	for (i = 0; i < 2; i++) {
+		NMFibRulePortRange *range = i ? &rr->sport_range : &rr->dport_range;
+		int attribute             = i ? FRA_SPORT_RANGE  : FRA_DPORT_RANGE;
+
+		if (!_rule_check_kernel_support (platform, attribute))
+			continue;
+
+		p = nmtst_get_rand_int ();
+		if ((p % 10000u) < 300) {
+			while (range->start == 0) {
+				p = p ^ nmtst_get_rand_int ();
+				range->start = nmtst_rand_select (1u, 0xFFFEu, ((p      ) % 0xFFFEu) + 1);
+				range->end   = nmtst_rand_select (1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start);
+				if (range->end < range->start)
+					NMTST_SWAP (range->start, range->end);
+			}
+		}
+	}
+
+	p = nmtst_get_rand_int () % 1000u;
+	if (p < 100)
+		rr->flags |= FIB_RULE_INVERT;
+
+	return obj;
+}
+
+static gboolean
+_rule_fuzzy_equal (const NMPObject *obj,
+                   const NMPObject *obj_comp,
+                   int op_type)
+{
+	const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (obj);
+	NMPlatformRoutingRule rr_co = *NMP_OBJECT_CAST_ROUTING_RULE (obj_comp);
+
+	switch (op_type) {
+	case RTM_NEWRULE:
+		/* when adding rules with RTM_NEWRULE, kernel checks whether an existing
+		 * rule already exists and may fail with EEXIST. This check has issues
+		 * and reject legitimate rules (rh#1686075).
+		 *
+		 * Work around that. */
+		if (rr->src_len == 0)
+			rr_co.src_len = 0;
+		if (rr->dst_len == 0)
+			rr_co.dst_len = 0;
+		if (rr->flow == 0)
+			rr_co.flow = 0;
+		if (rr->tos == 0)
+			rr_co.tos = 0;
+		rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse;
+		rr_co.suppress_ifgroup_inverse = rr->suppress_ifgroup_inverse;
+		if (!NM_FLAGS_HAS (rr->flags, FIB_RULE_INVERT))
+			rr_co.flags &= ~((guint32) FIB_RULE_INVERT);
+		else
+			rr_co.flags |= ((guint32) FIB_RULE_INVERT);
+		break;
+	case RTM_DELRULE:
+		/* when deleting a rule with RTM_DELRULE, kernel tries to find the
+		 * cadidate to delete. It might delete the wrong rule (rh#1685816). */
+		if (rr->action == FR_ACT_UNSPEC)
+			rr_co.action = FR_ACT_UNSPEC;
+		if (rr->iifname[0] == '\0')
+			rr_co.iifname[0] = '\0';
+		if (rr->oifname[0] == '\0')
+			rr_co.oifname[0] = '\0';
+		if (rr->src_len == 0)
+			rr_co.src_len = 0;
+		if (rr->dst_len == 0)
+			rr_co.dst_len = 0;
+		if (rr->tun_id == 0)
+			rr_co.tun_id = 0;
+		if (rr->fwmark == 0)
+			rr_co.fwmark = 0;
+		if (rr->fwmask == 0)
+			rr_co.fwmask = 0;
+		if (rr->flow == 0)
+			rr_co.flow = 0;
+		if (rr->protocol == 0)
+			rr_co.protocol = 0;
+		if (rr->table == RT_TABLE_UNSPEC)
+			rr_co.table = RT_TABLE_UNSPEC;
+		if (rr->l3mdev == 0)
+			rr_co.l3mdev = 0;
+		if (rr->tos == 0)
+			rr_co.tos = 0;
+		if (rr->ip_proto == 0)
+			rr_co.ip_proto = 0;
+		if (rr->suppress_prefixlen_inverse == 0)
+			rr_co.suppress_prefixlen_inverse = 0;
+		if (rr->suppress_ifgroup_inverse == 0)
+			rr_co.suppress_ifgroup_inverse = 0;
+		if (!rr->uid_range_has)
+			rr_co.uid_range_has = FALSE;
+		if (rr->sport_range.start == 0 && rr->sport_range.end == 0) {
+			rr_co.sport_range.start = 0;
+			rr_co.sport_range.end   = 0;
+		}
+		if (rr->dport_range.start == 0 && rr->dport_range.end == 0) {
+			rr_co.dport_range.start = 0;
+			rr_co.dport_range.end   = 0;
+		}
+		if (!NM_FLAGS_HAS (rr->flags, FIB_RULE_INVERT))
+			rr_co.flags &= ~((guint32) FIB_RULE_INVERT);
+		else
+			rr_co.flags |= ((guint32) FIB_RULE_INVERT);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	return nm_platform_routing_rule_cmp (rr, &rr_co, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0;
+}
+
+static void
+test_rule (gconstpointer test_data)
+{
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const gboolean TEST_SYNC = (TEST_IDX == 4);
+	gs_unref_ptrarray GPtrArray *objs = NULL;
+	gs_unref_ptrarray GPtrArray *objs_initial = NULL;
+	NMPlatform *platform = NM_PLATFORM_GET;
+	guint i, j, n;
+	int r;
+	gboolean had_an_issue_exist = FALSE;
+
+	nm_platform_process_events (platform);
+
+	objs_initial = nmtstp_platform_routing_rules_get_all (platform, AF_UNSPEC);
+	g_assert (objs_initial);
+	g_assert_cmpint (objs_initial->len, ==, 5);
+
+	nmtstp_run_command_check ("ip rule add table 766");
+	nm_platform_process_events (platform);
+
+	for (i = 6; i > 0; i--) {
+		gs_unref_ptrarray GPtrArray *objs_extern = NULL;
+		const NMPObject *obj;
+
+		objs_extern = nmtstp_platform_routing_rules_get_all (platform, AF_UNSPEC);
+
+		g_assert (objs_extern);
+		g_assert_cmpint (objs_extern->len, ==, i);
+
+		if (TEST_IDX != 1)
+			nmtst_rand_perm (NULL, objs_extern->pdata, NULL, sizeof (gpointer), objs_extern->len);
+
+		obj = objs_extern->pdata[0];
+
+		r = nm_platform_object_delete (platform, obj);
+		g_assert_cmpint (r, ==, TRUE);
+
+		g_assert (!_platform_has_routing_rule (platform, obj));
+	}
+
+	g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, 0);
+
+#define RR(...) \
+	nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE, \
+	                (const NMPlatformObject *) &((NMPlatformRoutingRule) { \
+	                  __VA_ARGS__ \
+	                }))
+
+	objs = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 10,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 400,
+		.action      = FR_ACT_GOTO,
+		.goto_target = 10000,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.action      = FR_ACT_TO_TBL,
+		.table       = RT_TABLE_MAIN,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.priority    = 30,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.priority    = 50,
+		.iifname     = "t-iif-1",
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.priority    = 50,
+		.iifname     = "t-oif-1",
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 50,
+		.iifname     = "t-oif-2",
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 51,
+		.iifname     = DEVICE_NAME,
+	));
+
+	if (TEST_IDX == 1) {
+		g_ptr_array_add (objs, RR (
+			.addr_family = AF_INET,
+			.table       = 10000,
+		));
+	}
+
+	if (TEST_IDX != 1) {
+		nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
+		g_ptr_array_set_size (objs, nmtst_get_rand_int () % (objs->len + 1));
+	}
+
+	n = (TEST_IDX != 1) ? nmtst_get_rand_int () % 50u : 0u;
+	for (i = 0; i < n; i++) {
+		nm_auto_nmpobj const NMPObject *o = NULL;
+		guint try = 0;
+
+again:
+		o = _rule_create_random (platform);
+		for (j = 0; j < objs->len; j++) {
+			if (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (o),
+			                                  NMP_OBJECT_CAST_ROUTING_RULE (objs->pdata[j]),
+			                                  NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0)  {
+				try++;
+				g_assert (try < 200);
+				nm_clear_pointer (&o, nmp_object_unref);
+				goto again;
+			}
+		}
+		g_ptr_array_add (objs, (gpointer) g_steal_pointer (&o));
+	}
+
+	if (TEST_IDX != 1)
+		nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
+
+	if (TEST_SYNC) {
+		gs_unref_hashtable GHashTable *unique_priorities = g_hash_table_new (NULL, NULL);
+		nm_auto_unref_rules_manager NMPRulesManager *rules_manager = nmp_rules_manager_new (platform);
+		gs_unref_ptrarray GPtrArray *objs_sync = NULL;
+		gconstpointer USER_TAG_1 = &platform;
+		gconstpointer USER_TAG_2 = &unique_priorities;
+
+		objs_sync = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+		/* ensure that priorities are unique. Otherwise it confuses the test, because
+		 * kernel may wrongly be unable to add/delete routes based on a wrong match
+		 * (rh#1685816, rh#1685816). */
+		for (i = 0; i < objs->len; i++) {
+			const NMPObject *obj = objs->pdata[i];
+			guint32 prio = NMP_OBJECT_CAST_ROUTING_RULE (obj)->priority;
+
+			if (   !NM_IN_SET (prio, 0, 32766, 32767)
+			    && !g_hash_table_contains (unique_priorities, GUINT_TO_POINTER (prio))) {
+				g_hash_table_add (unique_priorities, GUINT_TO_POINTER (prio));
+				g_ptr_array_add (objs_sync, (gpointer) nmp_object_ref (obj));
+			}
+		}
+
+		for (i = 0; i < objs_sync->len; i++) {
+			nmp_rules_manager_track (rules_manager,
+			                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+			                         1,
+			                         USER_TAG_1);
+			if (nmtst_get_rand_bool ()) {
+				/* this has no effect, because a negative priority (of same absolute value)
+				 * has lower priority than the positive priority above. */
+				nmp_rules_manager_track (rules_manager,
+				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                         -1,
+				                         USER_TAG_2);
+			}
+			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+				nmp_rules_manager_sync (rules_manager, FALSE);
+				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1);
+			}
+		}
+
+		nmp_rules_manager_sync (rules_manager, FALSE);
+		g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len);
+
+		for (i = 0; i < objs_sync->len; i++) {
+			switch (nmtst_get_rand_int () % 3) {
+			case 0:
+				nmp_rules_manager_untrack (rules_manager,
+				                           NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                           USER_TAG_1);
+				nmp_rules_manager_untrack (rules_manager,
+				                           NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                           USER_TAG_1);
+				break;
+			case 1:
+				nmp_rules_manager_track (rules_manager,
+				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                         -1,
+				                         USER_TAG_1);
+				break;
+			case 2:
+				nmp_rules_manager_track (rules_manager,
+				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                         -2,
+				                         USER_TAG_2);
+				break;
+			}
+			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+				nmp_rules_manager_sync (rules_manager, FALSE);
+				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len - i - 1);
+			}
+		}
+
+		nmp_rules_manager_sync (rules_manager, FALSE);
+
+	} else {
+		for (i = 0; i < objs->len;) {
+			const NMPObject *obj = objs->pdata[i];
+
+			for (j = 0; j < objs->len; j++)
+				g_assert ((j < i) == (!!_platform_has_routing_rule (platform, objs->pdata[j])));
+
+			r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj));
+
+			if (r == -EEXIST) {
+				g_assert (!_platform_has_routing_rule (platform, obj));
+				/* this should not happen, but there are bugs in kernel (rh#1686075). */
+				for (j = 0; j < i; j++) {
+					const NMPObject *obj2 = objs->pdata[j];
+
+					g_assert (_platform_has_routing_rule (platform, obj2));
+
+					if (_rule_fuzzy_equal (obj, obj2, RTM_NEWRULE)) {
+						r = 0;
+						break;
+					}
+				}
+				if (r == 0) {
+					/* OK, the rule is shadowed by another rule, and kernel does not allow
+					 * us to add this one (rh#1686075). Drop this from the test. */
+					g_ptr_array_remove_index (objs, i);
+					had_an_issue_exist = TRUE;
+					continue;
+				}
+			}
+
+			if (r != 0) {
+				NMPLookup lookup;
+				const NMDedupMultiHeadEntry *head_entry;
+				NMDedupMultiIter iter;
+				const NMPObject *o;
+
+				g_print (">>> failing... errno=%d, rule=%s\n", r, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+
+				nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+				head_entry = nm_platform_lookup (platform, &lookup);
+				nmp_cache_iter_for_each (&iter, head_entry, &o) {
+					char ch = ' ';
+
+					if (   NMP_OBJECT_CAST_ROUTING_RULE (o)->addr_family == NMP_OBJECT_CAST_ROUTING_RULE (obj)->addr_family
+					    && NMP_OBJECT_CAST_ROUTING_RULE (o)->priority == NMP_OBJECT_CAST_ROUTING_RULE (obj)->priority)
+						ch = '*';
+					g_print (">>> existing rule: %c %s\n", ch, nmp_object_to_string (o, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+				}
+
+				nmtstp_run_command_check ("ip rule");
+				nmtstp_run_command_check ("ip -6 rule");
+				g_assert_cmpint (r, ==, 0);
+			}
+
+			g_assert (_platform_has_routing_rule (platform, obj));
+
+			g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1);
+
+			i++;
+		}
+
+		if (TEST_IDX != 1)
+			nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
+
+		if (_LOGD_ENABLED ()) {
+			nmtstp_run_command_check ("ip rule");
+			nmtstp_run_command_check ("ip -6 rule");
+		}
+
+		for (i = 0; i < objs->len; i++) {
+			const NMPObject *obj = objs->pdata[i];
+			const NMPObject *obj2;
+
+			for (j = 0; j < objs->len; j++)
+				g_assert ((j < i) == (!_platform_has_routing_rule (platform, objs->pdata[j])));
+
+			g_assert (_platform_has_routing_rule (platform, obj));
+
+			r = nm_platform_object_delete (platform, obj);
+			g_assert_cmpint (r, ==, TRUE);
+
+			obj2 = _platform_has_routing_rule (platform, obj);
+
+			if (obj2) {
+				guint k;
+
+				/* When deleting a rule, kernel does a fuzzy match, ignoring for example:
+				 *  - action, if it is FR_ACT_UNSPEC
+				 *  - iifname,oifname if it is unspecified
+				 * rh#1685816
+				 *
+				 * That means, we may have deleted the wrong rule. Which one? */
+				k = i;
+				for (j = i + 1; j < objs->len; j++) {
+					if (!_platform_has_routing_rule (platform, objs->pdata[j])) {
+						g_assert_cmpint (k, ==, i);
+						k = j;
+					}
+				}
+				g_assert_cmpint (k, >, i);
+
+				if (!_rule_fuzzy_equal (obj, objs->pdata[k], RTM_DELRULE)) {
+					g_print (">>> failing...\n");
+					g_print (">>> no fuzzy match between: %s\n", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+					g_print (">>>                    and: %s\n", nmp_object_to_string (objs->pdata[k], NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+					g_assert_not_reached ();
+				}
+
+				objs->pdata[i] = objs->pdata[k];
+				objs->pdata[k] = (gpointer) obj;
+				obj2 = NULL;
+			}
+
+			g_assert (!obj2);
+
+			g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs->len -i - 1);
+		}
+	}
+
+	g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, 0);
+
+	for (i = 0; i < objs_initial->len; i++) {
+		const NMPObject *obj = objs_initial->pdata[i];
+
+		for (j = 0; j < objs_initial->len; j++)
+			g_assert ((j < i) == (!!_platform_has_routing_rule (platform, objs_initial->pdata[j])));
+
+		r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj));
+		g_assert_cmpint (r, ==, 0);
+	}
+	for (j = 0; j < objs_initial->len; j++)
+		g_assert (_platform_has_routing_rule (platform, objs_initial->pdata[j]));
+	g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_initial->len);
+
+	/* the tests passed as good as we could (as good as we implemented workarounds for them).
+	 * Still, with this kernel, not all features were fully tested. Mark the test as skipped. */
+	if (had_an_issue_exist)
+		g_test_skip ("adding a rule failed with EEXIST although it should not (rh#1686075)");
+	else if (!_rule_check_kernel_support (platform, -1))
+		g_test_skip ("some kernel features were not available and skipped for the test");
+}
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
 
 void
@@ -902,4 +1759,11 @@ _nmtstp_setup_tests (void)
 		add_test_func ("/route/ip6_route_get", test_ip6_route_get);
 		add_test_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
 	}
+
+	if (nmtstp_is_root_test ()) {
+		add_test_func_data ("/route/rule/1", test_rule, GINT_TO_POINTER (1));
+		add_test_func_data ("/route/rule/2", test_rule, GINT_TO_POINTER (2));
+		add_test_func_data ("/route/rule/3", test_rule, GINT_TO_POINTER (3));
+		add_test_func_data ("/route/rule/4", test_rule, GINT_TO_POINTER (4));
+	}
 }
diff --git a/src/ppp/meson.build b/src/ppp/meson.build
index a0f2df50..04c539e3 100644
--- a/src/ppp/meson.build
+++ b/src/ppp/meson.build
@@ -2,7 +2,7 @@ name = 'nm-pppd-plugin'
 
 deps = [
   dl_dep,
-  nm_core_dep,
+  libnm_core_dep,
 ]
 
 nm_pppd_plugin = shared_module(
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 8924c39f..814dee85 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -24,7 +24,7 @@
 
 #include <pwd.h>
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-interface.h"
 #include "nm-secret-agent.h"
 #include "nm-auth-utils.h"
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 8d1f9583..3fdaa598 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -25,7 +25,7 @@
 
 #include "c-list/src/c-list.h"
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-config.h"
 #include "nm-config-data.h"
 #include "nm-dbus-interface.h"
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index fd1d316a..8e18a33e 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -36,7 +36,7 @@
 #include <selinux/selinux.h>
 #endif
 
-#include "nm-common-macros.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
@@ -60,7 +60,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 
-#include "nm-utils/nm-c-list.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-dbus-object.h"
 #include "devices/nm-device-ethernet.h"
 #include "nm-settings-connection.h"
@@ -1138,7 +1138,7 @@ pk_add_cb (NMAuthChain *chain,
 	NMAuthCallResult result;
 	GError *error = NULL;
 	NMConnection *connection = NULL;
-	NMSettingsConnection *added = NULL;
+	gs_unref_object NMSettingsConnection *added = NULL;
 	NMSettingsAddCallback callback;
 	gpointer callback_data;
 	NMAuthSubject *subject;
@@ -1169,6 +1169,12 @@ pk_add_cb (NMAuthChain *chain,
 
 		save_to_disk = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "save-to-disk"));
 		added = nm_settings_add_connection (self, connection, save_to_disk, &error);
+
+		/* The callback may remove the connection from the settings manager (e.g.
+		 * because it's found to be incompatible with the device on AddAndActivate).
+		 * But we need to keep it alive for a bit longer, precisely to check wehther
+		 * it's still known to the setting manager. */
+		g_object_ref (added);
 	}
 
 	callback = nm_auth_chain_get_data (chain, "callback");
diff --git a/src/settings/plugins/README b/src/settings/plugins/README
deleted file mode 100644
index 307d0bb1..00000000
--- a/src/settings/plugins/README
+++ /dev/null
@@ -1,33 +0,0 @@
-Plugins generally have three components:
-
-1) plugin object: manages the individual "connections", which are
-  just objects wrapped around on-disk config data.  The plugin handles requests
-  to add new connections via the NM D-Bus API, and also watches config
-  directories for changes to configuration data.  Plugins implement the
-  NMSettingsPlugin interface.  See plugin.c.
-
-2) "connections": subclasses of NMSettingsConnection.  They handle updates to
-  configuration data, deletion, etc.  See NMKeyfileConnection.
-
-3) reader/writer code: typically a separate static library that gets linked
-  into the main plugin shared object, so they can be unit tested separately
-  from the plugin.  This code should read config data from disk and create
-  an NMConnection from it, and be capable of taking an NMConnection and writing
-  out appropriate configuration data to disk.
-
-NM will first call the "factory" function that every module must provide, which
-is nm_settings_plugin_factory().  That function creates and returns a singleton
-instance of the plugin's main object, which implements NMSettingsPlugin.
-That interface is implemented via the object definition in G_DEFINE_TYPE_EXTENDED
-in plugin.c, which registers the interface setup function
-settings_plugin_interface_init(), which when called actually sets up the vtables
-for the functions defined by NMSettingsPluginInterface.  Thus there are two
-entry points into the plugin:  nm_settings_plugin_factory() and
-the NMSettingsPluginInterface methods.
-
-The plugin also emits various signals (defined by NMSettingsPluginInterface)
-which NetworkManager listens for.  These include notifications of new
-connections if they were created via changes to the on-disk files.  The
-"connection" objects can also emit signals (defined by the NMSettingsConnection
-and NMConnection superclasses) when the connections' backing storage gets
-changed or deleted.
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
index 00b25068..47051995 100644
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ b/src/settings/plugins/ibft/nms-ibft-plugin.c
@@ -64,31 +64,30 @@ static void
 read_connections (NMSIbftPlugin *self)
 {
 	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-	GSList *blocks = NULL, *iter;
-	GError *error = NULL;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
+	GSList *iter;
+	gs_free_error GError *error = NULL;
 	NMSIbftConnection *connection;
 
 	if (!nms_ibft_reader_load_blocks ("/sbin/iscsiadm", &blocks, &error)) {
 		nm_log_dbg (LOGD_SETTINGS, "ibft: failed to read iscsiadm records: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
 	for (iter = blocks; iter; iter = iter->next) {
 		connection = nms_ibft_connection_new (iter->data, &error);
-		if (connection) {
-			nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
-			             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
-			g_hash_table_insert (priv->connections,
-			                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
-			                     connection);
-		} else {
+		if (!connection) {
 			nm_log_warn (LOGD_SETTINGS, "ibft: failed to read iscsiadm record: %s", error->message);
 			g_clear_error (&error);
+			continue;
 		}
-	}
 
-	g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
+		nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
+		             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
+		g_hash_table_insert (priv->connections,
+		                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
+		                     connection);
+	}
 }
 
 static GSList *
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
index c6c14376..fde30383 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ b/src/settings/plugins/ibft/nms-ibft-reader.c
@@ -46,8 +46,7 @@ remove_most_whitespace (const char *src)
 	char *s_new, *s2;
 	const char *svalue;
 
-	while (*src && g_ascii_isspace (*src))
-		src++;
+	src = nm_str_skip_leading_spaces (src);
 
 	svalue = strchr (src, '=');
 	if (!svalue || svalue == src)
@@ -94,24 +93,25 @@ nms_ibft_reader_load_blocks (const char *iscsiadm_path,
 {
 	const char *argv[4] = { iscsiadm_path, "-m", "fw", NULL };
 	const char *envp[1] = { NULL };
-	GSList *blocks = NULL;
-	char *out = NULL, *err = NULL;
-	int status = 0;
-	char **lines = NULL, **iter;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
+	gs_free char *out = NULL;
+	gs_free char *err = NULL;
+	gs_free const char **lines = NULL;
 	GPtrArray *block_lines = NULL;
-	gboolean success = FALSE;
+	gsize i;
+	int status = 0;
 
 	g_return_val_if_fail (iscsiadm_path != NULL, FALSE);
 	g_return_val_if_fail (out_blocks != NULL && *out_blocks == NULL, FALSE);
 
 	if (!g_spawn_sync ("/", (char **) argv, (char **) envp, 0,
 	                   NULL, NULL, &out, &err, &status, error))
-		goto done;
+		return FALSE;
 
 	if (!WIFEXITED (status)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "iBFT: %s exited abnormally.", iscsiadm_path);
-		goto done;
+		return FALSE;
 	}
 
 	if (WEXITSTATUS (status) != 0) {
@@ -127,59 +127,47 @@ nms_ibft_reader_load_blocks (const char *iscsiadm_path,
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "iBFT: %s exited with error %d.  Message: '%s'",
 		             iscsiadm_path, WEXITSTATUS (status), err ?: "(none)");
-		goto done;
+		return FALSE;
 	}
 
 	nm_log_dbg (LOGD_SETTINGS, "iBFT records:\n%s", out);
 
-	lines = g_strsplit_set (out, "\n\r", -1);
-	for (iter = lines; iter && *iter; iter++) {
-		if (!*iter[0])
-			continue;
+	lines = nm_utils_strsplit_set (out, "\n\r");
+	for (i = 0; lines && lines[i]; i++) {
+		const char *ss = lines[i];
 
-		if (!g_ascii_strncasecmp (*iter, TAG_BEGIN, NM_STRLEN (TAG_BEGIN))) {
+		if (!g_ascii_strncasecmp (ss, TAG_BEGIN, NM_STRLEN (TAG_BEGIN))) {
 			if (block_lines) {
 				PARSE_WARNING ("malformed iscsiadm record: missing END RECORD.");
-				g_ptr_array_unref (block_lines);
+				nm_clear_pointer (&block_lines, g_ptr_array_unref);
 			}
 			/* Start new record */
 			block_lines = g_ptr_array_new_full (15, g_free);
-		} else if (!g_ascii_strncasecmp (*iter, TAG_END, NM_STRLEN (TAG_END))) {
+		} else if (!g_ascii_strncasecmp (ss, TAG_END, NM_STRLEN (TAG_END))) {
 			if (block_lines) {
 				if (block_lines->len)
-					blocks = g_slist_prepend (blocks, block_lines);
+					blocks = g_slist_prepend (blocks, g_steal_pointer (&block_lines));
 				else
-					g_ptr_array_unref (block_lines);
-				block_lines = NULL;
+					g_ptr_array_unref (g_steal_pointer (&block_lines));
 			}
 		} else if (block_lines) {
-			char *s = remove_most_whitespace (*iter);
+			char *s = remove_most_whitespace (ss);
 
-			if (s)
+			if (!s) {
+				PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", ss);
+				nm_clear_pointer (&block_lines, g_ptr_array_unref);
+			} else
 				g_ptr_array_add (block_lines, s);
-			else {
-				PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", *iter);
-				g_clear_pointer (&block_lines, g_ptr_array_unref);
-			}
 		}
 	}
 
 	if (block_lines) {
 		PARSE_WARNING ("malformed iscsiadm record: missing # END RECORD.");
-		g_clear_pointer (&block_lines, g_ptr_array_unref);
+		nm_clear_pointer (&block_lines, g_ptr_array_unref);
 	}
-	success = TRUE;
-
-done:
-	if (lines)
-		g_strfreev (lines);
-	g_free (out);
-	g_free (err);
-	if (success)
-		*out_blocks = blocks;
-	else
-		g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
-	return success;
+
+	*out_blocks = g_steal_pointer (&blocks);
+	return TRUE;
 }
 
 #define ISCSI_HWADDR_TAG     "iface.hwaddress"
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.h b/src/settings/plugins/ibft/nms-ibft-reader.h
index 27500cc5..baa81e99 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.h
+++ b/src/settings/plugins/ibft/nms-ibft-reader.h
@@ -23,6 +23,14 @@
 
 #include "nm-connection.h"
 
+static inline void
+_nm_auto_free_ibft_blocks (GSList **p_blocks)
+{
+	if (*p_blocks)
+		g_slist_free_full (*p_blocks, (GDestroyNotify) g_ptr_array_unref);
+}
+#define nm_auto_free_ibft_blocks nm_auto (_nm_auto_free_ibft_blocks)
+
 gboolean nms_ibft_reader_load_blocks (const char *iscsiadm_path,
                                       GSList **out_blocks,
                                       GError **error);
diff --git a/src/settings/plugins/ibft/tests/test-ibft.c b/src/settings/plugins/ibft/tests/test-ibft.c
index 4c45f574..f5b584a1 100644
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ b/src/settings/plugins/ibft/tests/test-ibft.c
@@ -40,16 +40,16 @@
 static GPtrArray *
 read_block (const char *iscsiadm_path, const char *expected_mac)
 {
-	GSList *blocks = NULL, *iter;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
+	GSList *iter;
 	GPtrArray *block = NULL;
 	GError *error = NULL;
 	gboolean success;
 
 	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-	g_assert (blocks);
+	nmtst_assert_success (success, error);
 
+	g_assert (blocks);
 	for (iter = blocks; iter; iter = iter->next) {
 		const char *s_hwaddr = NULL;
 
@@ -63,7 +63,6 @@ read_block (const char *iscsiadm_path, const char *expected_mac)
 	}
 	g_assert (block);
 
-	g_slist_free_full (blocks, (GDestroyNotify) g_ptr_array_unref);
 	return block;
 }
 
@@ -176,7 +175,7 @@ static void
 test_read_ibft_malformed (gconstpointer user_data)
 {
 	const char *iscsiadm_path = user_data;
-	GSList *blocks = NULL;
+	nm_auto_free_ibft_blocks GSList *blocks = NULL;
 	GError *error = NULL;
 	gboolean success;
 
@@ -185,9 +184,9 @@ test_read_ibft_malformed (gconstpointer user_data)
 	NMTST_EXPECT_NM_WARN ("*malformed iscsiadm record*");
 
 	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-	g_assert (blocks == NULL);
+	nmtst_assert_success (success, error);
+
+	g_assert (!blocks);
 
 	g_test_assert_expected_messages ();
 }
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 89272edb..5160dbf0 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -30,7 +30,7 @@
 #include <sys/stat.h>
 #include <gmodule.h>
 
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 #include "nm-setting-connection.h"
 #include "settings/nm-settings-plugin.h"
 #include "nm-config.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 7c1db225..e5423b18 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -31,7 +31,7 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
-#include "nm-utils/nm-secret-utils.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-connection.h"
 #include "nm-dbus-interface.h"
 #include "nm-setting-connection.h"
@@ -53,7 +53,7 @@
 #include "nm-setting-generic.h"
 #include "nm-core-internal.h"
 #include "nm-utils.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "platform/nm-platform.h"
 #include "NetworkManagerUtils.h"
@@ -82,7 +82,8 @@ static char *
 get_full_file_path (const char *ifcfg_path, const char *file_path)
 {
 	const char *base = file_path;
-	char *p, *ret, *dirname;
+	gs_free char *dirname = NULL;
+	char *p;
 
 	g_return_val_if_fail (ifcfg_path != NULL, NULL);
 	g_return_val_if_fail (file_path != NULL, NULL);
@@ -95,9 +96,7 @@ get_full_file_path (const char *ifcfg_path, const char *file_path)
 		base = p + 1;
 
 	dirname = g_path_get_dirname (ifcfg_path);
-	ret = g_build_path ("/", dirname, base, NULL);
-	g_free (dirname);
-	return ret;
+	return g_build_path ("/", dirname, base, NULL);
 }
 
 /*****************************************************************************/
@@ -207,7 +206,7 @@ _cert_get_cert_bytes (const char *ifcfg_path,
 {
 	gs_free char *path = NULL;
 
-	if (g_str_has_prefix (value, "pkcs11:"))
+	if (NM_STR_HAS_PREFIX (value, "pkcs11:"))
 		return _nm_setting_802_1x_cert_value_to_bytes (NM_SETTING_802_1X_CK_SCHEME_PKCS11, (guint8 *) value, -1, error);
 
 	path = get_full_file_path (ifcfg_path, value);
@@ -379,7 +378,7 @@ make_connection_setting (const char *file,
 	NMSettingConnection *s_con;
 	NMSettingConnectionLldp lldp;
 	const char *ifcfg_name = NULL;
-	char *new_id;
+	gs_free char *new_id = NULL;
 	const char *uuid;
 	gs_free char *uuid_free = NULL;
 	gs_free char *value = NULL;
@@ -396,7 +395,6 @@ make_connection_setting (const char *file,
 
 	new_id = make_connection_name (ifcfg, ifcfg_name, suggested, prefix);
 	g_object_set (s_con, NM_SETTING_CONNECTION_ID, new_id, NULL);
-	g_free (new_id);
 
 	/* Try for a UUID key before falling back to hashing the file name */
 	uuid = svGetValueStr (ifcfg, "UUID", &uuid_free);
@@ -457,7 +455,7 @@ make_connection_setting (const char *file,
 	if (v) {
 		gs_free const char **items = NULL;
 
-		items = nm_utils_strsplit_set (v, " ", FALSE);
+		items = nm_utils_strsplit_set (v, " ");
 		for (iter = items; iter && *iter; iter++) {
 			if (!nm_setting_connection_add_permission (s_con, "user", *iter, NULL))
 				PARSE_WARNING ("invalid USERS item '%s'", *iter);
@@ -473,7 +471,7 @@ make_connection_setting (const char *file,
 	if (v) {
 		gs_free const char **items = NULL;
 
-		items = nm_utils_strsplit_set (v, " \t", FALSE);
+		items = nm_utils_strsplit_set (v, " \t");
 		for (iter = items; iter && *iter; iter++) {
 			if (!nm_setting_connection_add_secondary (s_con, *iter))
 				PARSE_WARNING ("secondary connection UUID '%s' already added", *iter);
@@ -562,7 +560,6 @@ make_connection_setting (const char *file,
 	return NM_SETTING (s_con);
 }
 
-/* Returns TRUE on missing address or valid address */
 static gboolean
 read_ip4_address (shvarFile *ifcfg,
                   const char *tag,
@@ -572,7 +569,7 @@ read_ip4_address (shvarFile *ifcfg,
 {
 	gs_free char *value_to_free = NULL;
 	const char *value;
-	guint32 a;
+	in_addr_t a;
 
 	nm_assert (ifcfg);
 	nm_assert (tag);
@@ -820,7 +817,7 @@ parse_route_line (const char *line,
                   NMIPRoute **out_route,
                   GError **error)
 {
-	nm_auto_ip_route_unref NMIPRoute *route = NULL;
+	nm_auto_unref_ip_route NMIPRoute *route = NULL;
 	gs_free const char **words_free = NULL;
 	const char *const*words;
 	const char *s;
@@ -889,7 +886,7 @@ parse_route_line (const char *line,
 	 * Maybe later we want to support some form of quotation here.
 	 * Which of course, would be incompatible with initscripts.
 	 */
-	words_free = nm_utils_strsplit_set (line, " \t\n", FALSE);
+	words_free = nm_utils_strsplit_set (line, " \t\n");
 
 	words = words_free ?: NM_PTRARRAY_EMPTY (const char *);
 
@@ -1284,7 +1281,7 @@ read_route_file (int addr_family,
 	for (line = strtok_r (contents, "\n", &contents_rest);
 	     line;
 	     line = strtok_r (NULL, "\n", &contents_rest)) {
-		nm_auto_ip_route_unref NMIPRoute *route = NULL;
+		nm_auto_unref_ip_route NMIPRoute *route = NULL;
 		gs_free_error GError *local = NULL;
 		int e;
 
@@ -1327,7 +1324,7 @@ parse_dns_options (NMSettingIPConfig *ip_config, const char *value)
 	if (!nm_setting_ip_config_has_dns_options (ip_config))
 		nm_setting_ip_config_clear_dns_options (ip_config, TRUE);
 
-	options = nm_utils_strsplit_set (value, " ", FALSE);
+	options = nm_utils_strsplit_set (value, " ");
 	if (options) {
 		for (item = options; *item; item++) {
 			if (!nm_setting_ip_config_add_dns_option (ip_config, *item))
@@ -1343,46 +1340,33 @@ parse_full_ip6_address (shvarFile *ifcfg,
                         NMIPAddress **out_address,
                         GError **error)
 {
-	char **list;
-	char *ip_val, *prefix_val;
+	NMIPAddress *addr;
+	NMIPAddr addr_bin;
 	int prefix;
-	gboolean success = FALSE;
 
-	g_return_val_if_fail (addr_str != NULL, FALSE);
-	g_return_val_if_fail (out_address != NULL, FALSE);
-	g_return_val_if_fail (*out_address == NULL, FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
+	nm_assert (addr_str);
+	nm_assert (out_address && !*out_address);
+	nm_assert (!error || !*error);
 
-	/* Split the address and prefix */
-	list = g_strsplit_set (addr_str, "/", 2);
-	if (g_strv_length (list) < 1) {
+	if (!nm_utils_parse_inaddr_prefix_bin (AF_INET6,
+	                                       addr_str,
+	                                       NULL,
+	                                       &addr_bin,
+	                                       &prefix)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Invalid IP6 address '%s'", addr_str);
-		goto error;
+		return FALSE;
 	}
 
-	ip_val = list[0];
-
-	prefix_val = list[1];
-	if (prefix_val) {
-		prefix = _nm_utils_ascii_str_to_int64 (prefix_val, 10, 0, 128, -1);
-		if (prefix < 0) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IP6 prefix '%s'", prefix_val);
-			goto error;
-		}
-	} else {
-		/* Missing prefix is treated as prefix of 64 */
+	if (prefix < 0)
 		prefix = 64;
-	}
 
-	*out_address = nm_ip_address_new (AF_INET6, ip_val, prefix, error);
-	if (*out_address)
-		success = TRUE;
+	addr = nm_ip_address_new_binary (AF_INET6, &addr_bin, prefix, error);
+	if (!addr)
+		return FALSE;
 
-error:
-	g_strfreev (list);
-	return success;
+	*out_address = addr;
+	return TRUE;
 }
 
 static NMSetting *
@@ -1443,13 +1427,12 @@ make_match_setting (shvarFile *ifcfg)
 	if (!v)
 		return NULL;
 
-	strv = nm_utils_strsplit_set (v, " \t", TRUE);
+	strv = nm_utils_escaped_tokens_split (v, NM_ASCII_SPACES);
 	if (strv) {
 		for (i = 0; strv[i]; i++) {
 			if (!s_match)
 				s_match = (NMSettingMatch *) nm_setting_match_new ();
-			nm_setting_match_add_interface_name (s_match,
-			                                     _nm_utils_unescape_spaces ((char *) strv[i]));
+			nm_setting_match_add_interface_name (s_match, strv[i]);
 		}
 	}
 
@@ -1722,7 +1705,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (v) {
 			gs_free const char **searches = NULL;
 
-			searches = nm_utils_strsplit_set (v, " ", FALSE);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
@@ -1783,7 +1766,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (v) {
 			gs_free const char **searches = NULL;
 
-			searches = nm_utils_strsplit_set (v, " ", FALSE);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
@@ -1809,7 +1792,8 @@ static void
 read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *filename)
 {
 	GDir *dir;
-	char *dirname, *base;
+	gs_free char *dirname  = NULL;
+	gs_free char *base  = NULL;
 	NMIPAddress *base_addr = NULL;
 	GError *err = NULL;
 
@@ -1820,9 +1804,9 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 		base_addr = nm_setting_ip_config_get_address (s_ip4, 0);
 
 	dirname = g_path_get_dirname (filename);
-	g_return_if_fail (dirname != NULL);
+	nm_assert (dirname != NULL);
 	base = g_path_get_basename (filename);
-	g_return_if_fail (base != NULL);
+	nm_assert (base != NULL);
 
 	dir = g_dir_open (dirname, 0, &err);
 	if (dir) {
@@ -1911,9 +1895,6 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 		PARSE_WARNING ("can not read directory '%s': %s", dirname, err->message);
 		g_error_free (err);
 	}
-
-	g_free (base);
-	g_free (dirname);
 }
 
 static NMSetting *
@@ -1922,10 +1903,9 @@ make_ip6_setting (shvarFile *ifcfg,
                   gboolean routes_read,
                   GError **error)
 {
-	NMSettingIPConfig *s_ip6 = NULL;
+	gs_unref_object NMSettingIPConfig *s_ip6 = NULL;
 	const char *v;
 	gs_free char *value = NULL;
-	char *route6_path = NULL;
 	gboolean ipv6init, ipv6forwarding, dhcp6 = FALSE;
 	char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
 	const char *ipv6addr, *ipv6addr_secondaries;
@@ -2060,7 +2040,7 @@ make_ip6_setting (shvarFile *ifcfg,
 
 	/* Don't bother to read IP, DNS and routes when IPv6 is disabled */
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
-		return NM_SETTING (s_ip6);
+		return NM_SETTING (g_steal_pointer (&s_ip6));
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "DHCPV6_DUID", &value);
@@ -2083,7 +2063,7 @@ make_ip6_setting (shvarFile *ifcfg,
 		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL);
 
 	g_object_set (s_ip6, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
-		      svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL);
+	              svGetValueBoolean (ifcfg, "DHCPV6_SEND_HOSTNAME", TRUE), NULL);
 
 	/* Read static IP addresses.
 	 * Read them even for AUTO and DHCP methods - in this case the addresses are
@@ -2099,12 +2079,12 @@ make_ip6_setting (shvarFile *ifcfg,
 	                   ipv6addr_secondaries ?: "",
 	                   NULL);
 
-	list = nm_utils_strsplit_set (value, " ", FALSE);
+	list = nm_utils_strsplit_set (value, " ");
 	for (iter = list, i = 0; iter && *iter; iter++, i++) {
 		NMIPAddress *addr = NULL;
 
 		if (!parse_full_ip6_address (ifcfg, *iter, i, &addr, error))
-			goto error;
+			return NULL;
 
 		if (!nm_setting_ip_config_add_address (s_ip6, addr))
 			PARSE_WARNING ("duplicate IP6 address");
@@ -2129,7 +2109,7 @@ make_ip6_setting (shvarFile *ifcfg,
 			if (!nm_utils_ipaddr_valid (AF_INET6, v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IP6 address '%s'", v);
-				goto error;
+				return NULL;
 			}
 
 			g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL);
@@ -2172,18 +2152,19 @@ make_ip6_setting (shvarFile *ifcfg,
 			/* Ignore IPv4 addresses */
 		} else {
 			PARSE_WARNING ("invalid DNS server address %s", v);
-			goto error;
+			return NULL;
 		}
 	}
 
 	if (!routes_read) {
 		/* NOP */
 	} else {
+		gs_free char *route6_path = NULL;
+
 		/* Read static routes from route6-<interface> file */
 		route6_path = utils_get_route6_path (svFileGetName (ifcfg));
 		if (!read_route_file (AF_INET6, route6_path, s_ip6, error))
-			goto error;
-		g_free (route6_path);
+			return NULL;
 	}
 
 	/* DNS searches */
@@ -2192,7 +2173,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	if (v) {
 		gs_free const char **searches = NULL;
 
-		searches = nm_utils_strsplit_set (v, " ", FALSE);
+		searches = nm_utils_strsplit_set (v, " ");
 		if (searches) {
 			for (iter = searches; *iter; iter++) {
 				if (!nm_setting_ip_config_add_dns_search (s_ip6, *iter))
@@ -2212,12 +2193,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	              priority,
 	              NULL);
 
-	return NM_SETTING (s_ip6);
-
-error:
-	g_free (route6_path);
-	g_object_unref (s_ip6);
-	return NULL;
+	return NM_SETTING (g_steal_pointer (&s_ip6));
 }
 
 static NMSetting *
@@ -2551,7 +2527,7 @@ read_dcb_percent_array (shvarFile *ifcfg,
 		return TRUE;
 	}
 
-	split = nm_utils_strsplit_set (val, ",", FALSE);
+	split = nm_utils_strsplit_set (val, ",");
 	if (NM_PTRARRAY_LEN (split) != 8) {
 		PARSE_WARNING ("invalid %s percentage list value '%s'", prop, val);
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -2588,10 +2564,9 @@ make_dcb_setting (shvarFile *ifcfg,
                   NMSetting **out_setting,
                   GError **error)
 {
-	NMSettingDcb *s_dcb = NULL;
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
 	gboolean dcb_on;
 	NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE;
-	char *val;
 
 	g_return_val_if_fail (out_setting != NULL, FALSE);
 
@@ -2600,31 +2575,28 @@ make_dcb_setting (shvarFile *ifcfg,
 		return TRUE;
 
 	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
 
 	/* FCOE */
 	if (!read_dcb_app (ifcfg, s_dcb, "FCOE",
 	                   &dcb_flags_props[DCB_APP_FCOE_FLAGS],
 	                   NM_SETTING_DCB_APP_FCOE_PRIORITY,
 	                   error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 	if (nm_setting_dcb_get_app_fcoe_flags (s_dcb) & NM_SETTING_DCB_FLAG_ENABLE) {
+		gs_free char *val = NULL;
+
 		val = svGetValueStr_cp (ifcfg, KEY_DCB_APP_FCOE_MODE);
 		if (val) {
-			if (strcmp (val, NM_SETTING_DCB_FCOE_MODE_FABRIC) == 0 ||
-			    strcmp (val, NM_SETTING_DCB_FCOE_MODE_VN2VN) == 0)
+			if (NM_IN_STRSET (val, NM_SETTING_DCB_FCOE_MODE_FABRIC,
+			                       NM_SETTING_DCB_FCOE_MODE_VN2VN))
 				g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_FCOE_MODE, val, NULL);
 			else {
 				PARSE_WARNING ("invalid FCoE mode '%s'", val);
 				g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				                     "invalid FCoE mode");
-				g_free (val);
-				g_object_unref (s_dcb);
 				return FALSE;
 			}
-			g_free (val);
 		}
 	}
 
@@ -2633,7 +2605,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                   &dcb_flags_props[DCB_APP_ISCSI_FLAGS],
 	                   NM_SETTING_DCB_APP_ISCSI_PRIORITY,
 	                   error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2642,7 +2613,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                   &dcb_flags_props[DCB_APP_FIP_FLAGS],
 	                   NM_SETTING_DCB_APP_FIP_PRIORITY,
 	                   error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2657,7 +2627,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          "PFC",
 	                          nm_setting_dcb_set_priority_flow_control,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2673,7 +2642,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          TRUE,
 	                          nm_setting_dcb_set_priority_group_id,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2686,7 +2654,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                             TRUE,
 	                             nm_setting_dcb_set_priority_group_bandwidth,
 	                             error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2699,7 +2666,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                             FALSE,
 	                             nm_setting_dcb_set_priority_bandwidth,
 	                             error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2711,7 +2677,6 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          "STRICT",
 	                          nm_setting_dcb_set_priority_strict_bandwidth,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
@@ -2723,11 +2688,10 @@ make_dcb_setting (shvarFile *ifcfg,
 	                          FALSE,
 	                          nm_setting_dcb_set_priority_traffic_class,
 	                          error)) {
-		g_object_unref (s_dcb);
 		return FALSE;
 	}
 
-	*out_setting = NM_SETTING (s_dcb);
+	*out_setting = NM_SETTING (g_steal_pointer (&s_dcb));
 	return TRUE;
 }
 
@@ -2834,7 +2798,7 @@ make_wep_setting (shvarFile *ifcfg,
                   GError **error)
 {
 	gs_unref_object NMSettingWirelessSecurity *s_wsec = NULL;
-	char *value;
+	gs_free char *value = NULL;
 	shvarFile *keys_ifcfg = NULL;
 	int default_key_idx = 0;
 	gboolean has_default_key = FALSE;
@@ -2849,13 +2813,12 @@ make_wep_setting (shvarFile *ifcfg,
 		if (default_key_idx == 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid default WEP key '%s'", value);
-			g_free (value);
 			return NULL;
 		}
 		has_default_key = TRUE;
 		default_key_idx--;  /* convert to [0...3] */
 		g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, (guint) default_key_idx, NULL);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	/* Read WEP key flags */
@@ -2902,23 +2865,21 @@ make_wep_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr_cp (ifcfg, "SECURITYMODE");
 	if (value) {
-		char *lcase;
+		gs_free char *lcase = NULL;
 
 		lcase = g_ascii_strdown (value, -1);
-		g_free (value);
+		nm_clear_g_free (&value);
 
-		if (!strcmp (lcase, "open")) {
+		if (nm_streq (lcase, "open")) {
 			g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", NULL);
-		} else if (!strcmp (lcase, "restricted")) {
+		} else if (nm_streq (lcase, "restricted")) {
 			g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", NULL);
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid WEP authentication algorithm '%s'",
 			             lcase);
-			g_free (lcase);
 			return NULL;
 		}
-		g_free (lcase);
 	}
 
 	/* If no WEP keys were given, and the keys are not agent-owned, and no
@@ -2963,7 +2924,7 @@ fill_wpa_ciphers (shvarFile *ifcfg,
 	if (!p)
 		return TRUE;
 
-	list = nm_utils_strsplit_set (p, " ", FALSE);
+	list = nm_utils_strsplit_set (p, " ");
 	for (iter = list; iter && *iter; iter++, i++) {
 		/* Ad-Hoc configurations cannot have pairwise ciphers, and can only
 		 * have one group cipher.  Ignore any additional group ciphers and
@@ -3233,7 +3194,7 @@ eap_peap_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (v, " ", FALSE);
+	list = nm_utils_strsplit_set (v, " ");
 	iter = list;
 	if (iter) {
 		if (NM_IN_STRSET (*iter, "MSCHAPV2",
@@ -3311,7 +3272,7 @@ eap_ttls_reader (const char *eap_method,
 	inner_auth = g_ascii_strdown (v, -1);
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (inner_auth, " ", FALSE);
+	list = nm_utils_strsplit_set (inner_auth, " ");
 	iter = list;
 	if (iter) {
 		if (NM_IN_STRSET (*iter, "mschapv2",
@@ -3372,7 +3333,7 @@ eap_fast_reader (const char *eap_method,
 	if (fast_provisioning) {
 		gs_free const char **list1 = NULL;
 
-		list1 = nm_utils_strsplit_set (fast_provisioning, " \t", FALSE);
+		list1 = nm_utils_strsplit_set (fast_provisioning, " \t");
 		for (iter = list1; iter && *iter; iter++) {
 			if (strcmp (*iter, "allow-unauth") == 0)
 				allow_unauth = TRUE;
@@ -3406,7 +3367,7 @@ eap_fast_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (inner_auth, " ", FALSE);
+	list = nm_utils_strsplit_set (inner_auth, " ");
 	iter = list;
 	if (iter) {
 		if (   !strcmp (*iter, "MSCHAPV2")
@@ -3486,7 +3447,7 @@ read_8021x_list_value (shvarFile *ifcfg,
 	if (!v)
 		return;
 
-	strv = nm_utils_strsplit_set (v, " \t", FALSE);
+	strv = nm_utils_strsplit_set (v, " \t");
 	if (strv)
 		g_object_set (setting, prop_name, strv, NULL);
 }
@@ -3515,7 +3476,7 @@ fill_8021x (shvarFile *ifcfg,
 		return NULL;
 	}
 
-	list = nm_utils_strsplit_set (v, " ", FALSE);
+	list = nm_utils_strsplit_set (v, " ");
 
 	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
 
@@ -3829,7 +3790,7 @@ transform_hwaddr_blacklist (const char *blacklist)
 	const char **strv;
 	gsize i, j;
 
-	strv = nm_utils_strsplit_set (blacklist, " \t", FALSE);
+	strv = nm_utils_strsplit_set (blacklist, " \t");
 	if (!strv)
 		return NULL;
 	for (i = 0, j = 0; strv[j]; j++) {
@@ -4147,7 +4108,7 @@ parse_ethtool_option (const char *value,
 	gs_free const char **words = NULL;
 	guint i;
 
-	words = nm_utils_strsplit_set (value, NULL, FALSE);
+	words = nm_utils_strsplit_set (value, " \t\n");
 	if (!words)
 		return;
 
@@ -4316,6 +4277,84 @@ parse_ethtool_option (const char *value,
 	}
 }
 
+static GPtrArray *
+read_routing_rules_parse (shvarFile *ifcfg,
+                          gboolean routes_read)
+{
+	gs_unref_ptrarray GPtrArray *arr = NULL;
+	gs_free const char **keys = NULL;
+	guint i, len;
+
+	keys = svGetKeysSorted (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6, &len);
+	if (len == 0)
+		return NULL;
+
+	if (!routes_read) {
+		PARSE_WARNING ("'rule-' or 'rule6-' files are present; Policy routing rules (ROUTING_RULE*) settings are ignored");
+		return NULL;
+	}
+
+	arr = g_ptr_array_new_full (len, (GDestroyNotify) nm_ip_routing_rule_unref);
+	for (i = 0; i < len; i++) {
+		const char *key = keys[i];
+		nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+		gs_free_error GError *local = NULL;
+		gs_free char *value_to_free = NULL;
+		const char *value;
+		gboolean key_is_ipv4;
+
+		key_is_ipv4 = (key[NM_STRLEN ("ROUTING_RULE")] == '_');
+		nm_assert (  key_is_ipv4  == NM_STR_HAS_PREFIX (key, "ROUTING_RULE_"));
+		nm_assert ((!key_is_ipv4) == NM_STR_HAS_PREFIX (key, "ROUTING_RULE6_"));
+
+		value = svGetValueStr (ifcfg, key, &value_to_free);
+		if (!value)
+			continue;
+
+		rule = nm_ip_routing_rule_from_string (value,
+		                                       NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
+		                                       | (key_is_ipv4
+		                                          ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET
+		                                          : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6),
+		                                       NULL,
+		                                       &local);
+		if (!rule) {
+			PARSE_WARNING ("invalid routing rule %s=\"%s\": %s", key, value, local->message);
+			continue;
+		}
+
+		g_ptr_array_add (arr, g_steal_pointer (&rule));
+	}
+
+	if (arr->len == 0)
+		return NULL;
+
+	return g_steal_pointer (&arr);
+}
+
+static void
+read_routing_rules (shvarFile *ifcfg,
+                    gboolean routes_read,
+                    NMSettingIPConfig *s_ip4,
+                    NMSettingIPConfig *s_ip6)
+{
+	gs_unref_ptrarray GPtrArray *routing_rules = NULL;
+	guint i;
+
+	routing_rules = read_routing_rules_parse (ifcfg, routes_read);
+	if (!routing_rules)
+		return;
+
+	for (i = 0; i < routing_rules->len; i++) {
+		NMIPRoutingRule *rule = routing_rules->pdata[i];
+
+		nm_setting_ip_config_add_routing_rule (  (nm_ip_routing_rule_get_addr_family (rule) == AF_INET)
+		                                       ? s_ip4
+		                                       : s_ip6,
+		                                       rule);
+	}
+}
+
 static void
 parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 {
@@ -4340,7 +4379,7 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 			gs_free const char **opts = NULL;
 			const char *const *iter;
 
-			opts = nm_utils_strsplit_set (ethtool_opts, ";", FALSE);
+			opts = nm_utils_strsplit_set (ethtool_opts, ";");
 			for (iter = opts; iter && iter[0]; iter++) {
 				/* in case of repeated wol_passwords, parse_ethtool_option()
 				 * will do the right thing and clear wol_password before resetting. */
@@ -4436,7 +4475,7 @@ make_wired_setting (shvarFile *ifcfg,
 			gs_free const char **chans = NULL;
 			guint32 num_chans;
 
-			chans = nm_utils_strsplit_set (value, ",", FALSE);
+			chans = nm_utils_strsplit_set (value, ",");
 			num_chans = NM_PTRARRAY_LEN (chans);
 			if (num_chans < 2 || num_chans > 3) {
 				PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
@@ -4470,22 +4509,23 @@ make_wired_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr_cp (ifcfg, "OPTIONS");
 	if (value) {
-		char **options, **iter;
+		gs_free const char **options = NULL;
+		gsize i;
 
-		iter = options = g_strsplit_set (value, " ", 0);
-		while (iter && *iter) {
-			char *equals = strchr (*iter, '=');
+		options = nm_utils_strsplit_set_with_empty (value, " ");
+		for (i = 0; options && options[i]; i++) {
+			const char *line = options[i];
+			const char *equals;
 			gboolean valid = FALSE;
 
+			equals = strchr (line, '=');
 			if (equals) {
-				*equals = '\0';
-				valid = nm_setting_wired_add_s390_option (s_wired, *iter, equals + 1);
+				((char *) equals)[0] = '\0';
+				valid = nm_setting_wired_add_s390_option (s_wired, line, equals + 1);
 			}
 			if (!valid)
-				PARSE_WARNING ("invalid s390 OPTION '%s'", *iter);
-			iter++;
+				PARSE_WARNING ("invalid s390 OPTION '%s'", line);
 		}
-		g_strfreev (options);
 		nm_clear_g_free (&value);
 	}
 
@@ -4759,18 +4799,18 @@ make_bond_setting (shvarFile *ifcfg,
 		gs_free const char **items = NULL;
 		const char *const *iter;
 
-		items = nm_utils_strsplit_set (v, " ", FALSE);
+		items = nm_utils_strsplit_set (v, " ");
 		for (iter = items; iter && *iter; iter++) {
-			gs_strfreev char **keys = NULL;
-			const char *key, *val;
-
-			keys = g_strsplit_set (*iter, "=", 2);
-			if (keys && *keys) {
-				key = *keys;
-				val = *(keys + 1);
-				if (val && key[0] && val[0])
-					handle_bond_option (s_bond, key, val);
-			}
+			gs_free char *key = NULL;
+			const char *val;
+
+			val = strchr (*iter, '=');
+			if (!val)
+				continue;
+			key = g_strndup (*iter, val - *iter);
+			val++;
+			if (key[0] && val[0])
+				handle_bond_option (s_bond, key, val);
 		}
 	}
 
@@ -4958,6 +4998,8 @@ handle_bridge_option (NMSetting *setting,
 		{ "max_age",            NM_SETTING_BRIDGE_MAX_AGE,            BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE },
 		{ "ageing_time",        NM_SETTING_BRIDGE_AGEING_TIME,        BRIDGE_OPT_TYPE_OPTION },
 		{ "multicast_snooping", NM_SETTING_BRIDGE_MULTICAST_SNOOPING, BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_filtering",     NM_SETTING_BRIDGE_VLAN_FILTERING,     BRIDGE_OPT_TYPE_OPTION },
+		{ "default_pvid",       NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,  BRIDGE_OPT_TYPE_OPTION },
 		{ "group_fwd_mask",     NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, BRIDGE_OPT_TYPE_OPTION },
 		{ "priority",           NM_SETTING_BRIDGE_PORT_PRIORITY,      BRIDGE_OPT_TYPE_PORT_OPTION },
 		{ "path_cost",          NM_SETTING_BRIDGE_PORT_PATH_COST,     BRIDGE_OPT_TYPE_PORT_OPTION },
@@ -5037,19 +5079,56 @@ handle_bridging_opts (NMSetting *setting,
 	gs_free const char **items = NULL;
 	const char *const *iter;
 
-	items = nm_utils_strsplit_set (value, " ", FALSE);
+	items = nm_utils_strsplit_set (value, " ");
 	for (iter = items; iter && *iter; iter++) {
-		gs_strfreev char **keys = NULL;
-		const char *key, *val;
-
-		keys = g_strsplit_set (*iter, "=", 2);
-		if (keys && *keys) {
-			key = *keys;
-			val = *(keys + 1);
-			if (val && key[0] && val[0])
-				func (setting, stp, key, val, opt_type);
+		gs_free char *key = NULL;
+		const char *val;
+
+		val = strchr (*iter, '=');
+		if (!val)
+			continue;
+		key = g_strndup (*iter, val - *iter);
+		val++;
+		if (key[0] && val[0])
+			func (setting, stp, key, val, opt_type);
+	}
+}
+
+static void
+read_bridge_vlans (shvarFile *ifcfg,
+                   const char *key,
+                   NMSetting *setting,
+                   const char *property)
+{
+	gs_unref_ptrarray GPtrArray *array = NULL;
+	gs_free char *value_to_free = NULL;
+	const char *value;
+
+	value = svGetValueStr (ifcfg, key, &value_to_free);
+	if (value) {
+		gs_free const char **strv = NULL;
+		const char *const *iter;
+		GError *local = NULL;
+		NMBridgeVlan *vlan;
+
+		array = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+
+		strv = nm_utils_escaped_tokens_split (value, ",");
+		if (strv) {
+			for (iter = strv; *iter; iter++) {
+				vlan = nm_bridge_vlan_from_str (*iter, &local);
+				if (!vlan) {
+					PARSE_WARNING ("invalid bridge VLAN: %s", local->message);
+					g_clear_error (&local);
+					continue;
+				}
+				g_ptr_array_add (array, vlan);
+			}
 		}
+		nm_clear_g_free (&value_to_free);
 	}
+
+	g_object_set (setting, property, array, NULL);
 }
 
 static NMSetting *
@@ -5110,6 +5189,11 @@ make_bridge_setting (shvarFile *ifcfg,
 		nm_clear_g_free (&value_to_free);
 	}
 
+	read_bridge_vlans (ifcfg,
+	                   "BRIDGE_VLANS",
+	                   NM_SETTING (s_bridge),
+	                   NM_SETTING_BRIDGE_VLANS);
+
 	return (NMSetting *) g_steal_pointer (&s_bridge);
 }
 
@@ -5179,6 +5263,11 @@ make_bridge_port_setting (shvarFile *ifcfg)
 			handle_bridging_opts (s_port, FALSE, value, handle_bridge_option, BRIDGE_OPT_TYPE_PORT_OPTION);
 			nm_clear_g_free (&value_to_free);
 		}
+
+		read_bridge_vlans (ifcfg,
+		                   "BRIDGE_PORT_VLANS",
+		                   s_port,
+		                   NM_SETTING_BRIDGE_PORT_VLANS);
 	}
 
 	return s_port;
@@ -5255,7 +5344,7 @@ parse_prio_map_list (NMSettingVlan *s_vlan,
 	v = svGetValueStr (ifcfg, key, &value);
 	if (!v)
 		return;
-	list = nm_utils_strsplit_set (v, ",", FALSE);
+	list = nm_utils_strsplit_set (v, ",");
 
 	for (iter = list; iter && *iter; iter++) {
 		if (!strchr (*iter, ':'))
@@ -5360,7 +5449,7 @@ make_vlan_setting (shvarFile *ifcfg,
 		gs_free const char **strv = NULL;
 		const char *const *ptr;
 
-		strv = nm_utils_strsplit_set (v, ", ", FALSE);
+		strv = nm_utils_strsplit_set (v, ", ");
 		for (ptr = strv; ptr && *ptr; ptr++) {
 			if (nm_streq (*ptr, "GVRP") && gvrp == -1)
 				vlan_flags |= NM_VLAN_FLAG_GVRP;
@@ -5502,7 +5591,7 @@ check_dns_search_domains (shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
 			gs_free const char **searches = NULL;
 			const char *const *item;
 
-			searches = nm_utils_strsplit_set (v, " ", FALSE);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (s_ip6), *item))
@@ -5768,8 +5857,7 @@ connection_from_file_full (const char *filename,
 	                          error);
 	if (!s_ip6)
 		return NULL;
-	else
-		nm_connection_add_setting (connection, s_ip6);
+	nm_connection_add_setting (connection, s_ip6);
 
 	s_ip4 = make_ip4_setting (main_ifcfg,
 	                          network_ifcfg,
@@ -5778,12 +5866,15 @@ connection_from_file_full (const char *filename,
 	                          error);
 	if (!s_ip4)
 		return NULL;
-	else {
-		read_aliases (NM_SETTING_IP_CONFIG (s_ip4),
-		              !has_ip4_defroute && !nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (s_ip4)),
-		              filename);
-		nm_connection_add_setting (connection, s_ip4);
-	}
+	read_aliases (NM_SETTING_IP_CONFIG (s_ip4),
+	              !has_ip4_defroute && !nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (s_ip4)),
+	              filename);
+	nm_connection_add_setting (connection, s_ip4);
+
+	read_routing_rules (main_ifcfg,
+	                    !has_complex_routes_v4 && !has_complex_routes_v6,
+	                    NM_SETTING_IP_CONFIG (s_ip4),
+	                    NM_SETTING_IP_CONFIG (s_ip6));
 
 	s_sriov = make_sriov_setting (main_ifcfg);
 	if (s_sriov)
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 84c22094..c7729df5 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -22,7 +22,7 @@
 #define _UTILS_H_
 
 #include "nm-connection.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "shvar.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 ee7fd161..80b1bffe 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -30,8 +30,8 @@
 #include <unistd.h>
 #include <stdio.h>
 
-#include "nm-utils/nm-enum-utils.h"
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "nm-manager.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
@@ -50,7 +50,7 @@
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-meta-setting.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "nms-ifcfg-rh-common.h"
 #include "nms-ifcfg-rh-reader.h"
@@ -1464,6 +1464,43 @@ get_setting_default_boolean (NMSetting *setting, const char *prop)
 }
 
 static gboolean
+write_bridge_vlans (NMSetting *setting,
+                    const char *property_name,
+                    shvarFile *ifcfg,
+                    const char *key,
+                    GError **error)
+{
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	NMBridgeVlan *vlan;
+	GString *string;
+	guint i;
+
+	g_object_get (setting, property_name, &vlans, NULL);
+
+	if (!vlans || !vlans->len) {
+		svUnsetValue (ifcfg, key);
+		return TRUE;
+	}
+
+	string = g_string_new ("");
+	for (i = 0; i < vlans->len; i++) {
+		gs_free char *vlan_str = NULL;
+
+		vlan = vlans->pdata[i];
+		vlan_str = nm_bridge_vlan_to_str (vlan, error);
+		if (!vlan_str)
+			return FALSE;
+		if (string->len > 0)
+			g_string_append (string, ",");
+		nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string);
+	}
+
+	svSetValueStr (ifcfg, key, string->str);
+	g_string_free (string, TRUE);
+	return TRUE;
+}
+
+static gboolean
 write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingBridge *s_bridge;
@@ -1534,10 +1571,31 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "multicast_snooping=%u", (guint32) b);
 	}
 
+	b = nm_setting_bridge_get_vlan_filtering (s_bridge);
+	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_FILTERING)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "vlan_filtering=%u", (guint32) b);
+	}
+
+	i = nm_setting_bridge_get_vlan_default_pvid (s_bridge);
+	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "default_pvid=%u", i);
+	}
+
 	if (opts->len)
 		svSetValueStr (ifcfg, "BRIDGING_OPTS", opts->str);
 	g_string_free (opts, TRUE);
 
+	if (!write_bridge_vlans ((NMSetting *) s_bridge,
+	                         NM_SETTING_BRIDGE_VLANS,
+	                         ifcfg,
+	                         "BRIDGE_VLANS",
+	                         error))
+		return FALSE;
+
 	svSetValueStr (ifcfg, "TYPE", TYPE_BRIDGE);
 
 	*wired = write_wired_for_virtual (connection, ifcfg);
@@ -1550,7 +1608,7 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 {
 	NMSettingBridgePort *s_port;
 	guint32 i;
-	GString *opts;
+	GString *string;
 
 	s_port = nm_connection_get_setting_bridge_port (connection);
 	if (!s_port)
@@ -1559,28 +1617,35 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 	svUnsetValue (ifcfg, "BRIDGING_OPTS");
 
 	/* Bridge options */
-	opts = g_string_sized_new (32);
+	string = g_string_sized_new (32);
 
 	i = nm_setting_bridge_port_get_priority (s_port);
 	if (i != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PRIORITY))
-		g_string_append_printf (opts, "priority=%u", i);
+		g_string_append_printf (string, "priority=%u", i);
 
 	i = nm_setting_bridge_port_get_path_cost (s_port);
 	if (i != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PATH_COST)) {
-		if (opts->len)
-			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "path_cost=%u", i);
+		if (string->len)
+			g_string_append_c (string, ' ');
+		g_string_append_printf (string, "path_cost=%u", i);
 	}
 
 	if (nm_setting_bridge_port_get_hairpin_mode (s_port)) {
-		if (opts->len)
-			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "hairpin_mode=1");
+		if (string->len)
+			g_string_append_c (string, ' ');
+		g_string_append_printf (string, "hairpin_mode=1");
 	}
 
-	if (opts->len)
-		svSetValueStr (ifcfg, "BRIDGING_OPTS", opts->str);
-	g_string_free (opts, TRUE);
+	if (string->len)
+		svSetValueStr (ifcfg, "BRIDGING_OPTS", string->str);
+	g_string_free (string, TRUE);
+
+	if (!write_bridge_vlans ((NMSetting *) s_port,
+	                         NM_SETTING_BRIDGE_PORT_VLANS,
+	                         ifcfg,
+	                         "BRIDGE_PORT_VLANS",
+	                         error))
+		return FALSE;
 
 	return TRUE;
 }
@@ -2310,15 +2375,17 @@ write_match_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 
 	num = nm_setting_match_get_num_interface_names (s_match);
 	for (i = 0; i < num; i++) {
-		gs_free char *to_free = NULL;
 		const char *name;
 
-		if (i == 0)
+		name = nm_setting_match_get_interface_name (s_match, i);
+		if (!name || !name[0])
+			continue;
+
+		if (!str)
 			str = g_string_new ("");
 		else
 			g_string_append_c (str, ' ');
-		name = nm_setting_match_get_interface_name (s_match, i);
-		g_string_append (str, _nm_utils_escape_spaces (name, &to_free));
+		nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
 	}
 
 	if (str)
@@ -2889,6 +2956,52 @@ write_ip6_setting (NMConnection *connection,
 	return TRUE;
 }
 
+static void
+write_ip_routing_rules (NMConnection *connection,
+                        shvarFile *ifcfg,
+                        gboolean route_ignore)
+{
+	gsize idx;
+	int is_ipv4;
+
+	svUnsetAll (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6);
+
+	if (route_ignore)
+		return;
+
+	idx = 0;
+
+	for (is_ipv4 = 1; is_ipv4 >= 0; is_ipv4--) {
+		const int addr_family = is_ipv4 ? AF_INET : AF_INET6;
+		NMSettingIPConfig *s_ip;
+		guint i, num;
+
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+		if (!s_ip)
+			continue;
+
+		num = nm_setting_ip_config_get_num_routing_rules (s_ip);
+		for (i = 0; i < num; i++) {
+			NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
+			gs_free const char *s = NULL;
+			char key[64];
+
+			s = nm_ip_routing_rule_to_string (rule,
+			                                  NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE,
+			                                  NULL,
+			                                  NULL);
+			if (!s)
+				continue;
+
+			if (is_ipv4)
+				numbered_tag (key, "ROUTING_RULE_", ++idx);
+			else
+				numbered_tag (key, "ROUTING_RULE6_", ++idx);
+			svSetValueStr (ifcfg, key, s);
+		}
+	}
+}
+
 static char *
 escape_id (const char *id)
 {
@@ -3111,6 +3224,15 @@ do_write_construct (NMConnection *connection,
 			             has_complex_routes_v4 ? "" : "6");
 			return FALSE;
 		}
+		if (   (   s_ip4
+		        && nm_setting_ip_config_get_num_routing_rules (s_ip4) > 0)
+		    || (   s_ip6
+		        && nm_setting_ip_config_get_num_routing_rules (s_ip6) > 0)) {
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+			             "Cannot configure routing rules on a connection that has an associated 'rule%s-' file",
+			             has_complex_routes_v4 ? "" : "6");
+			return FALSE;
+		}
 		route_ignore = TRUE;
 	} else
 		route_ignore = FALSE;
@@ -3128,6 +3250,10 @@ do_write_construct (NMConnection *connection,
 	                        error))
 		return FALSE;
 
+	write_ip_routing_rules (connection,
+	                        ifcfg,
+	                        route_ignore);
+
 	write_connection_setting (s_con, ifcfg);
 
 	NM_SET_OUT (out_ifcfg, g_steal_pointer (&ifcfg));
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index f3d58e26..b399a17f 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -36,8 +36,8 @@
 
 #include "nm-core-internal.h"
 #include "nm-core-utils.h"
-#include "nm-utils/nm-enum-utils.h"
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "c-list/src/c-list.h"
 
 /*****************************************************************************/
@@ -879,10 +879,25 @@ _is_all_digits (const char *str)
 
 #define IS_NUMBERED_TAG(key, tab_name) \
 	({ \
+		const char *_key2 = (key); \
+		\
+		(   (strncmp (_key2, tab_name, NM_STRLEN (tab_name)) == 0) \
+		 && _is_all_digits (&_key2[NM_STRLEN (tab_name)])); \
+	})
+
+#define IS_NUMBERED_TAG_PARSE(key, tab_name, out_idx) \
+	({ \
 		const char *_key = (key); \
+		gint64 _idx; \
+		gboolean _good = FALSE; \
+		gint64 *_out_idx = (out_idx); \
 		\
-		(   (strncmp (_key, tab_name, NM_STRLEN (tab_name)) == 0) \
-		 && _is_all_digits (&_key[NM_STRLEN (tab_name)])); \
+		if (    IS_NUMBERED_TAG (_key, ""tab_name"") \
+		    && (_idx = _nm_utils_ascii_str_to_int64 (&_key[NM_STRLEN (tab_name)], 10, 0, G_MAXINT64, -1)) != -1) { \
+			NM_SET_OUT (_out_idx, _idx); \
+			_good = TRUE; \
+		} \
+		_good; \
 	})
 
 static gboolean
@@ -919,10 +934,30 @@ _svKeyMatchesType (const char *key, SvKeyType match_key_type)
 		if (IS_NUMBERED_TAG (key, "SRIOV_VF"))
 			return TRUE;
 	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) {
+		if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", NULL))
+			return TRUE;
+	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) {
+		if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", NULL))
+			return TRUE;
+	}
 
 	return FALSE;
 }
 
+gint64
+svNumberedParseKey (const char *key)
+{
+	gint64 idx;
+
+	if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", &idx))
+		return idx;
+	if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", &idx))
+		return idx;
+	return -1;
+}
+
 GHashTable *
 svGetKeys (shvarFile *s, SvKeyType match_key_type)
 {
@@ -947,6 +982,42 @@ svGetKeys (shvarFile *s, SvKeyType match_key_type)
 	return keys;
 }
 
+static int
+_get_keys_sorted_cmp (gconstpointer a,
+                      gconstpointer b,
+                      gpointer user_data)
+{
+	const char *k_a = *((const char *const*) a);
+	const char *k_b = *((const char *const*) b);
+	gint64 n_a;
+	gint64 n_b;
+
+	n_a = svNumberedParseKey (k_a);
+	n_b = svNumberedParseKey (k_b);
+	NM_CMP_DIRECT (n_a, n_b);
+	NM_CMP_RETURN (strcmp (k_a, k_b));
+	nm_assert_not_reached ();
+	return 0;
+}
+
+const char **
+svGetKeysSorted (shvarFile *s,
+                 SvKeyType match_key_type,
+                 guint *out_len)
+{
+	gs_unref_hashtable GHashTable *keys_hash = NULL;
+
+	keys_hash = svGetKeys (s, match_key_type);
+	if (!keys_hash) {
+		NM_SET_OUT (out_len, 0);
+		return NULL;
+	}
+	return (const char **) nm_utils_hash_keys_to_array (keys_hash,
+	                                                    _get_keys_sorted_cmp,
+	                                                    NULL,
+	                                                    out_len);
+}
+
 /*****************************************************************************/
 
 const char *
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index 622bb474..b38a8557 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -40,6 +40,8 @@ typedef enum {
 	SV_KEY_TYPE_TC                      = (1LL << 3),
 	SV_KEY_TYPE_USER                    = (1LL << 4),
 	SV_KEY_TYPE_SRIOV_VF                = (1LL << 5),
+	SV_KEY_TYPE_ROUTING_RULE4           = (1LL << 6),
+	SV_KEY_TYPE_ROUTING_RULE6           = (1LL << 7),
 } SvKeyType;
 
 const char *svFileGetName (const shvarFile *s);
@@ -67,8 +69,14 @@ char *svGetValueStr_cp (shvarFile *s, const char *key);
 
 int svParseBoolean (const char *value, int def);
 
+gint64 svNumberedParseKey (const char *key);
+
 GHashTable *svGetKeys (shvarFile *s, SvKeyType match_key_type);
 
+const char **svGetKeysSorted (shvarFile *s,
+                              SvKeyType match_key_type,
+                              guint *out_len);
+
 /* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
  * return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
  * return <def> otherwise
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
index c478db38..a8ff8df3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
@@ -2,6 +2,7 @@ HWADDR=31:33:33:37:BE:CD
 MTU=1492
 TYPE=Ethernet
 BRIDGING_OPTS="priority=50 path_cost=33"
+BRIDGE_PORT_VLANS="1 untagged,2 pvid,4-4094 untagged"
 NAME="Test Write Bridge Component"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
new file mode 100644
index 00000000..0c2fa035
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
@@ -0,0 +1,19 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+ROUTING_RULE_1="priority 10 from 0.0.0.0/0 table 1"
+ROUTING_RULE_2="priority 10 to 192.167.8.0/24 table 2"
+ROUTING_RULE6_3="priority 10 from ::/0 table 10"
+ROUTING_RULE6_4="priority 10 to 1:2:3::5/24 table 22"
+ROUTING_RULE6_5="priority 10 to 1:3:3::5 table 55"
+NAME="Test Write Routing Rules"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
index 2bc987c2..1788efe2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
@@ -4,5 +4,5 @@ TYPE=Bridge
 BOOTPROTO=dhcp
 STP=on
 DELAY=2
-BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24"
+BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24 vlan_filtering=1 default_pvid=99"
 MACADDR=00:16:41:11:22:33
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 b352fbfc..49ab04d4 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -49,7 +49,7 @@
 #include "nm-setting-vlan.h"
 #include "nm-setting-dcb.h"
 #include "nm-core-internal.h"
-#include "nm-ethtool-utils.h"
+#include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
 #include "NetworkManagerUtils.h"
 
@@ -4465,6 +4465,101 @@ test_write_wired_dhcp (void)
 	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
 }
 
+static NMIPRoutingRule *
+_ip_routing_rule_new (int addr_family,
+                      const char *str)
+{
+	NMIPRoutingRuleAsStringFlags flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE;
+	gs_free_error GError *local = NULL;
+	NMIPRoutingRule *rule;
+
+	if (addr_family != AF_UNSPEC) {
+		if (addr_family == AF_INET)
+			flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET;
+		else {
+			g_assert (addr_family == AF_INET6);
+			flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6;
+		}
+	}
+
+	rule = nm_ip_routing_rule_from_string (str,
+	                                         NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
+	                                       | flags,
+	                                       NULL,
+	                                       nmtst_get_rand_bool () ? &local : NULL);
+	nmtst_assert_success (rule, local);
+
+	if (addr_family != AF_UNSPEC)
+		g_assert_cmpint (nm_ip_routing_rule_get_addr_family (rule), ==, addr_family);
+	return rule;
+}
+
+static void
+_ip_routing_rule_add_to_setting (NMSettingIPConfig *s_ip,
+                                 const char *str)
+{
+	nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+
+	rule = _ip_routing_rule_new (nm_setting_ip_config_get_addr_family (s_ip), str);
+	nm_setting_ip_config_add_routing_rule (s_ip, rule);
+}
+
+static void
+test_write_routing_rules (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+
+	connection = nm_simple_connection_new ();
+
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Write Routing Rules",
+	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME,
+	              NULL);
+
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+	              NULL);
+
+	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+
+	g_object_set (s_ip6,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	              NULL);
+
+	_ip_routing_rule_add_to_setting (s_ip4, "pref 10 from 0.0.0.0/0 table 1");
+	_ip_routing_rule_add_to_setting (s_ip4, "priority 10 to 192.167.8.0/24 table 2");
+	_ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 table 10");
+	_ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 to 1:2:3::5/24 table 22");
+	_ip_routing_rule_add_to_setting (s_ip6, "pref 10 from ::/0 to 1:3:3::5/128 table 55");
+
+	nmtst_assert_connection_verifies (connection);
+
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR,
+	                        TEST_IFCFG_DIR"/ifcfg-Test_Write_Routing_Rules.cexpected",
+	                        &testfile);
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
 static void
 test_write_wired_match (void)
 {
@@ -7507,6 +7602,8 @@ test_read_bridge_main (void)
 	g_assert_cmpuint (nm_setting_bridge_get_ageing_time (s_bridge), ==, 235352);
 	g_assert_cmpuint (nm_setting_bridge_get_group_forward_mask (s_bridge), ==, 24);
 	g_assert (!nm_setting_bridge_get_multicast_snooping (s_bridge));
+	g_assert_cmpint (nm_setting_bridge_get_vlan_filtering (s_bridge), ==, TRUE);
+	g_assert_cmpint (nm_setting_bridge_get_vlan_default_pvid (s_bridge), ==, 99);
 
 	/* MAC address */
 	s_wired = nm_connection_get_setting_wired (connection);
@@ -7531,6 +7628,8 @@ test_write_bridge_main (void)
 	NMIPAddress *addr;
 	static const char *mac = "31:33:33:37:be:cd";
 	GError *error = NULL;
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	NMBridgeVlan *vlan;
 
 	connection = nm_simple_connection_new ();
 	g_assert (connection);
@@ -7551,9 +7650,23 @@ test_write_bridge_main (void)
 	s_bridge = (NMSettingBridge *) nm_setting_bridge_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_bridge));
 
+	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+	vlan = nm_bridge_vlan_new (10, 16);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (22, 22);
+	nm_bridge_vlan_set_pvid (vlan, TRUE);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (44, 0);
+	g_ptr_array_add (vlans, vlan);
+
 	g_object_set (s_bridge,
 	              NM_SETTING_BRIDGE_MAC_ADDRESS, mac,
 	              NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, 19008,
+	              NM_SETTING_BRIDGE_VLAN_FILTERING, TRUE,
+	              NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, 4000,
+	              NM_SETTING_BRIDGE_VLANS, vlans,
 	              NULL);
 
 	/* IP4 setting */
@@ -7631,6 +7744,8 @@ test_write_bridge_component (void)
 	NMSetting *s_port;
 	static const char *mac = "31:33:33:37:be:cd";
 	guint32 mtu = 1492;
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	NMBridgeVlan *vlan;
 
 	connection = nm_simple_connection_new ();
 	g_assert (connection);
@@ -7658,11 +7773,23 @@ test_write_bridge_component (void)
 	              NULL);
 
 	/* Bridge port */
+	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+	vlan = nm_bridge_vlan_new (1, 0);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (4, 4094);
+	nm_bridge_vlan_set_untagged (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+	vlan = nm_bridge_vlan_new (2, 2);
+	nm_bridge_vlan_set_pvid (vlan, TRUE);
+	g_ptr_array_add (vlans, vlan);
+
 	s_port = nm_setting_bridge_port_new ();
 	nm_connection_add_setting (connection, s_port);
 	g_object_set (s_port,
 	              NM_SETTING_BRIDGE_PORT_PRIORITY, 50,
 	              NM_SETTING_BRIDGE_PORT_PATH_COST, 33,
+	              NM_SETTING_BRIDGE_PORT_VLANS, vlans,
 	              NULL);
 
 	nmtst_assert_connection_verifies (connection);
@@ -10168,6 +10295,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wired/write-dhcp-plus-ip", test_write_wired_dhcp_plus_ip);
 	g_test_add_func (TPATH "wired/write/dhcp-8021x-peap-mschapv2", test_write_wired_dhcp_8021x_peap_mschapv2);
 	g_test_add_func (TPATH "wired/write/match", test_write_wired_match);
+	g_test_add_func (TPATH "wired/write/routing-rules", test_write_routing_rules);
 
 #define _add_test_write_wired_8021x_tls(testpath, scheme, flags) \
 	nmtst_add_test_func (testpath, test_write_wired_8021x_tls, GINT_TO_POINTER (scheme), GINT_TO_POINTER (flags))
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index fd5561ae..239f6415 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -419,17 +419,15 @@ update_wired_setting_from_if_block (NMConnection *connection,
 static void
 ifupdown_ip4_add_dns (NMSettingIPConfig *s_ip4, const char *dns)
 {
+	gs_free const char **list = NULL;
+	const char **iter;
 	guint32 addr;
-	gs_strfreev char **list = NULL;
-	char **iter;
 
 	if (dns == NULL)
 		return;
 
-	list = g_strsplit_set (dns, " \t", -1);
+	list = nm_utils_strsplit_set (dns, " \t");
 	for (iter = list; iter && *iter; iter++) {
-		if ((*iter)[0] == '\0')
-			continue;
 		if (!inet_pton (AF_INET, *iter, &addr)) {
 			_LOGW ("    ignoring invalid nameserver '%s'", *iter);
 			continue;
@@ -524,13 +522,11 @@ update_ip4_setting_from_if_block (NMConnection *connection,
 		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
-			gs_strfreev char **list = NULL;
-			char **iter;
+			gs_free const char **list = NULL;
+			const char **iter;
 
-			list = g_strsplit_set (search_v, " \t", -1);
+			list = nm_utils_strsplit_set (search_v, " \t");
 			for (iter = list; iter && *iter; iter++) {
-				if ((*iter)[0] == '\0')
-					continue;
 				if (!nm_setting_ip_config_add_dns_search (s_ip4, *iter))
 					_LOGW ("    duplicate DNS domain '%s'", *iter);
 			}
@@ -546,17 +542,15 @@ update_ip4_setting_from_if_block (NMConnection *connection,
 static void
 ifupdown_ip6_add_dns (NMSettingIPConfig *s_ip6, const char *dns)
 {
+	gs_free const char **list = NULL;
+	const char **iter;
 	struct in6_addr addr;
-	gs_strfreev char **list = NULL;
-	char **iter;
 
 	if (dns == NULL)
 		return;
 
-	list = g_strsplit_set (dns, " \t", -1);
+	list = nm_utils_strsplit_set (dns, " \t");
 	for (iter = list; iter && *iter; iter++) {
-		if ((*iter)[0] == '\0')
-			continue;
 		if (!inet_pton (AF_INET6, *iter, &addr)) {
 			_LOGW ("    ignoring invalid nameserver '%s'", *iter);
 			continue;
@@ -640,13 +634,11 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
-			gs_strfreev char **list = NULL;
-			char **iter;
+			gs_free const char **list = NULL;
+			const char **iter;
 
-			list = g_strsplit_set (search_v, " \t", -1);
+			list = nm_utils_strsplit_set (search_v, " \t");
 			for (iter = list; iter && *iter; iter++) {
-				if ((*iter)[0] == '\0')
-					continue;
 				if (!nm_setting_ip_config_add_dns_search (s_ip6, *iter))
 					_LOGW ("    duplicate DNS domain '%s'", *iter);
 			}
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 99a59477..04281f00 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -175,14 +175,14 @@ initialize (NMSettingsPlugin *plugin)
 				const char *ports = ifparser_getkey (block, "bridge-ports");
 
 				if (ports) {
-					guint i;
 					int state = 0;
-					gs_strfreev char **port_ifaces = NULL;
+					gs_free const char **port_ifaces = NULL;
+					gsize i;
 
 					_LOGD ("parse: found bridge ports %s for %s", ports, block->name);
 
-					port_ifaces = g_strsplit_set (ports, " \t", -1);
-					for (i = 0; port_ifaces[i]; i++) {
+					port_ifaces = nm_utils_strsplit_set (ports, " \t");
+					for (i = 0; port_ifaces && port_ifaces[i]; i++) {
 						const char *token = port_ifaces[i];
 
 						/* Skip crazy stuff like regex or all */
@@ -200,7 +200,7 @@ initialize (NMSettingsPlugin *plugin)
 						}
 						if (nm_streq (token, "none"))
 							continue;
-						if (state == 0 && strlen (token) > 0) {
+						if (state == 0) {
 							conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
 							if (!conn) {
 								_LOGD ("parse: adding bridge port \"%s\"", token);
@@ -280,7 +280,7 @@ initialize (NMSettingsPlugin *plugin)
 		GHashTableIter iter;
 
 		g_hash_table_iter_init (&iter, priv->eni_ifaces);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) conn)) {
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &conn)) {
 			if (conn) {
 				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
 				                                                  NM_SETTINGS_CONNECTION (conn));
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 8c75d8c7..2e06aeb9 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -32,7 +32,7 @@
 #include "nms-keyfile-utils.h"
 #include "nms-keyfile-reader.h"
 
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index f46689eb..12d21aba 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -29,7 +29,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-supplicant-config.h"
 #include "nm-core-internal.h"
-#include "nm-dbus-compat.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 
 #define WPAS_DBUS_IFACE_INTERFACE             WPAS_DBUS_INTERFACE ".Interface"
 #define WPAS_DBUS_IFACE_INTERFACE_WPS         WPAS_DBUS_INTERFACE ".Interface.WPS"
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
index 9dea4fb5..257a9462 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -38,7 +38,7 @@ libnm_systemd_core = static_library(
     )
   ],
   dependencies: [
-    nm_core_dep,
+    libnm_core_dep,
   ],
   c_args: [
     '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD',
diff --git a/src/systemd/sd-adapt-core/stat-util.h b/src/systemd/sd-adapt-core/udev-util.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/stat-util.h
+++ b/src/systemd/sd-adapt-core/udev-util.h
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index 7fc8248a..e3af362d 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -14,6 +14,7 @@
 #include "siphash24.h"
 #include "sparse-endian.h"
 #include "stdio-util.h"
+#include "udev-util.h"
 #include "virt.h"
 
 #define SYSTEMD_PEN    43793
@@ -189,6 +190,13 @@ int dhcp_identifier_set_iaid(
                                 /* not yet ready */
                                 return -EBUSY;
 
+                        r = device_is_renaming(device);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                /* device is under renaming */
+                                return -EBUSY;
+
                         name = net_get_name(device);
                 }
         }
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index 810a2633..45d2ed89 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -154,7 +154,6 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) {
                 .in.sin_addr.s_addr = address,
         };
         _cleanup_close_ int s = -1;
-        char ifname[IF_NAMESIZE] = "";
         int r;
 
         s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
@@ -170,12 +169,9 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) {
                 return r;
 
         if (ifindex > 0) {
-                if (if_indextoname(ifindex, ifname) == 0)
-                        return -errno;
-
-                r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname));
+                r = socket_bind_to_ifindex(s, ifindex);
                 if (r < 0)
-                        return -errno;
+                        return r;
         }
 
         if (address == INADDR_ANY) {
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/systemd/src/libsystemd-network/dhcp-option.c
index c5fbe749..eaf044bf 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp-option.c
@@ -10,10 +10,10 @@
 #include <string.h>
 
 #include "alloc-util.h"
-#include "utf8.h"
-#include "strv.h"
-
 #include "dhcp-internal.h"
+#include "memory-util.h"
+#include "strv.h"
+#include "utf8.h"
 
 static int option_append(uint8_t options[], size_t size, size_t *offset,
                          uint8_t code, size_t optlen, const void *optval) {
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index 3d5abe64..6172c368 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -15,10 +15,10 @@
 #include "dhcp6-lease-internal.h"
 #include "dhcp6-protocol.h"
 #include "dns-domain.h"
+#include "memory-util.h"
 #include "sparse-endian.h"
 #include "strv.h"
 #include "unaligned.h"
-#include "util.h"
 
 typedef struct DHCP6StatusOption {
         struct DHCP6Option option;
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index bbe7bf9d..530af034 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -9,9 +9,9 @@
 #include "in-addr-util.h"
 #include "lldp-internal.h"
 #include "lldp-neighbor.h"
+#include "memory-util.h"
 #include "missing.h"
 #include "unaligned.h"
-#include "util.h"
 
 static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
         siphash24_compress(id->chassis_id, id->chassis_id_size, state);
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index f85db47b..de219a6d 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -103,32 +103,12 @@ bool net_match_config(Set *match_mac,
                       char * const *match_drivers,
                       char * const *match_types,
                       char * const *match_names,
-                      Condition *match_host,
-                      Condition *match_virt,
-                      Condition *match_kernel_cmdline,
-                      Condition *match_kernel_version,
-                      Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
                       const char *dev_driver,
                       const char *dev_type,
                       const char *dev_name) {
 
-        if (match_host && condition_test(match_host) <= 0)
-                return false;
-
-        if (match_virt && condition_test(match_virt) <= 0)
-                return false;
-
-        if (match_kernel_cmdline && condition_test(match_kernel_cmdline) <= 0)
-                return false;
-
-        if (match_kernel_version && condition_test(match_kernel_version) <= 0)
-                return false;
-
-        if (match_arch && condition_test(match_arch) <= 0)
-                return false;
-
         if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
                 return false;
 
@@ -159,32 +139,31 @@ int config_parse_net_condition(const char *unit,
                                void *userdata) {
 
         ConditionType cond = ltype;
-        Condition **ret = data;
+        Condition **list = data, *c;
         bool negate;
-        Condition *c;
-        _cleanup_free_ char *s = NULL;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
         assert(data);
 
+        if (isempty(rvalue)) {
+                *list = condition_free_list_type(*list, cond);
+                return 0;
+        }
+
         negate = rvalue[0] == '!';
         if (negate)
                 rvalue++;
 
-        s = strdup(rvalue);
-        if (!s)
-                return log_oom();
-
-        c = condition_new(cond, s, false, negate);
+        c = condition_new(cond, rvalue, false, negate);
         if (!c)
                 return log_oom();
 
-        if (*ret)
-                condition_free(*ret);
+        /* Drop previous assignment. */
+        *list = condition_free_list_type(*list, cond);
 
-        *ret = c;
+        LIST_PREPEND(conditions, *list, c);
         return 0;
 }
 
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index 9119d9a4..d895103b 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -6,7 +6,6 @@
 #include "sd-device.h"
 #include "sd-dhcp-lease.h"
 
-#include "condition.h"
 #include "conf-parser.h"
 #include "def.h"
 #include "set.h"
@@ -20,11 +19,6 @@ bool net_match_config(Set *match_mac,
                       char * const *match_driver,
                       char * const *match_type,
                       char * const *match_name,
-                      Condition *match_host,
-                      Condition *match_virt,
-                      Condition *match_kernel_cmdline,
-                      Condition *match_kernel_version,
-                      Condition *match_arch,
                       const struct ether_addr *dev_mac,
                       const char *dev_path,
                       const char *dev_driver,
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 27f79638..cadcc8a8 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -25,10 +25,10 @@
 #include "event-util.h"
 #include "hostname-util.h"
 #include "io-util.h"
+#include "memory-util.h"
 #include "random-util.h"
 #include "string-util.h"
 #include "strv.h"
-#include "util.h"
 
 #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN)  /* Arbitrary limit */
 #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index 39d2a6d0..6c7aecc8 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -10,6 +10,9 @@
 #include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "sd-dhcp-lease.h"
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index 6de4adb5..53ce8929 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -22,7 +22,7 @@
 #include "random-util.h"
 #include "siphash24.h"
 #include "string-util.h"
-#include "util.h"
+#include "time-util.h"
 
 /* Constants from the RFC */
 #define PROBE_WAIT_USEC (1U * USEC_PER_SEC)
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index 8b1e9665..4f186762 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -219,28 +219,21 @@ static int ipv4ll_pick_address(sd_ipv4ll *ll) {
         return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
 }
 
-int sd_ipv4ll_restart(sd_ipv4ll *ll) {
-        ll->address = 0;
-
-        return sd_ipv4ll_start(ll);
-}
-
 #define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
 
-int sd_ipv4ll_start(sd_ipv4ll *ll) {
+static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
         int r;
         bool picked_address = false;
 
         assert_return(ll, -EINVAL);
         assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
-        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
 
         /* If no random seed is set, generate some from the MAC address */
         if (!ll->seed_set)
                 ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
 
-        /* Restart the generation counter. */
-        ll->seed.generation = 0;
+        if (reset_generation)
+                ll->seed.generation = 0;
 
         if (ll->address == 0) {
                 r = ipv4ll_pick_address(ll);
@@ -264,6 +257,19 @@ int sd_ipv4ll_start(sd_ipv4ll *ll) {
         return 0;
 }
 
+int sd_ipv4ll_start(sd_ipv4ll *ll) {
+        assert_return(ll, -EINVAL);
+        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+        return ipv4ll_start_internal(ll, true);
+}
+
+int sd_ipv4ll_restart(sd_ipv4ll *ll) {
+        ll->address = 0;
+
+        return ipv4ll_start_internal(ll, false);
+}
+
 static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) {
         assert(ll);
 
@@ -299,11 +305,7 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
 
                         ll->claimed_address = 0;
                 } else {
-                        r = ipv4ll_pick_address(ll);
-                        if (r < 0)
-                                goto error;
-
-                        r = sd_ipv4acd_start(ll->acd);
+                        r = sd_ipv4ll_restart(ll);
                         if (r < 0)
                                 goto error;
                 }
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index 741128e1..bdf58dac 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -15,7 +15,9 @@
 #include "lldp-internal.h"
 #include "lldp-neighbor.h"
 #include "lldp-network.h"
+#include "memory-util.h"
 #include "socket-util.h"
+#include "sort-util.h"
 #include "string-table.h"
 
 #define LLDP_DEFAULT_NEIGHBORS_MAX 128U
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index 6f77421b..dad0801b 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -17,6 +17,7 @@
 #include "hashmap.h"
 #include "list.h"
 #include "macro.h"
+#include "memory-util.h"
 #include "missing.h"
 #include "prioq.h"
 #include "process-util.h"
@@ -25,7 +26,6 @@
 #include "string-table.h"
 #include "string-util.h"
 #include "time-util.h"
-#include "util.h"
 
 #define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC)
 
@@ -3120,7 +3120,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
                 timeout = 0;
 
         m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max,
-                       timeout == (uint64_t) -1 ? -1 : (int) ((timeout + USEC_PER_MSEC - 1) / USEC_PER_MSEC));
+                       timeout == (uint64_t) -1 ? -1 : (int) DIV_ROUND_UP(timeout, USEC_PER_MSEC));
         if (m < 0) {
                 if (errno == EINTR) {
                         e->state = SD_EVENT_PENDING;
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/systemd/src/libsystemd/sd-id128/id128-util.h
index 65f14ab2..82a69a77 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -10,9 +10,6 @@
 
 char *id128_to_uuid_string(sd_id128_t id, char s[37]);
 
-/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */
-#define ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
-
 bool id128_is_valid(const char *s) _pure_;
 
 typedef enum Id128Format {
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index 13a28291..d0cae981 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -121,7 +121,6 @@ _public_ int sd_id128_get_boot(sd_id128_t *ret) {
 
 #if 0 /* NM_IGNORED */
 static int get_invocation_from_keyring(sd_id128_t *ret) {
-
         _cleanup_free_ char *description = NULL;
         char *d, *p, *g, *u, *e;
         unsigned long perms;
@@ -140,7 +139,7 @@ static int get_invocation_from_keyring(sd_id128_t *ret) {
         if (key == -1) {
                 /* Keyring support not available? No invocation key stored? */
                 if (IN_SET(errno, ENOSYS, ENOKEY))
-                        return 0;
+                        return -ENXIO;
 
                 return -errno;
         }
@@ -216,7 +215,19 @@ static int get_invocation_from_keyring(sd_id128_t *ret) {
         if (c != sizeof(sd_id128_t))
                 return -EIO;
 
-        return 1;
+        return 0;
+}
+
+static int get_invocation_from_environment(sd_id128_t *ret) {
+        const char *e;
+
+        assert(ret);
+
+        e = secure_getenv("INVOCATION_ID");
+        if (!e)
+                return -ENXIO;
+
+        return sd_id128_from_string(e, ret);
 }
 
 _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
@@ -226,31 +237,17 @@ _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         assert_return(ret, -EINVAL);
 
         if (sd_id128_is_null(saved_invocation_id)) {
+                /* We first check the environment. The environment variable is primarily relevant for user
+                 * services, and sufficiently safe as long as no privilege boundary is involved. */
+                r = get_invocation_from_environment(&saved_invocation_id);
+                if (r < 0 && r != -ENXIO)
+                        return r;
 
-                /* We first try to read the invocation ID from the kernel keyring. This has the benefit that it is not
-                 * fakeable by unprivileged code. If the information is not available in the keyring, we use
-                 * $INVOCATION_ID but ignore the data if our process was called by less privileged code
-                 * (i.e. secure_getenv() instead of getenv()).
-                 *
-                 * The kernel keyring is only relevant for system services (as for user services we don't store the
-                 * invocation ID in the keyring, as there'd be no trust benefit in that). The environment variable is
-                 * primarily relevant for user services, and sufficiently safe as no privilege boundary is involved. */
-
+                /* The kernel keyring is relevant for system services (as for user services we don't store
+                 * the invocation ID in the keyring, as there'd be no trust benefit in that). */
                 r = get_invocation_from_keyring(&saved_invocation_id);
                 if (r < 0)
                         return r;
-
-                if (r == 0) {
-                        const char *e;
-
-                        e = secure_getenv("INVOCATION_ID");
-                        if (!e)
-                                return -ENXIO;
-
-                        r = sd_id128_from_string(e, &saved_invocation_id);
-                        if (r < 0)
-                                return r;
-                }
         }
 
         *ret = saved_invocation_id;
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index 787a12f2..7bb86093 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -71,7 +71,7 @@ typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata);
 typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata);
 typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata);
 typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata);
-#if defined _GNU_SOURCE || _POSIX_C_SOURCE >= 199309L
+#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L)
 typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata);
 #else
 typedef void* sd_event_child_handler_t;
diff --git a/src/systemd/src/systemd/sd-id128.h b/src/systemd/src/systemd/sd-id128.h
index bdf88ed5..c5fbe0a4 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/systemd/src/systemd/sd-id128.h
@@ -63,6 +63,9 @@ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
 #define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
 #define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15]
 
+/* Like SD_ID128_FORMAT_STR, but formats as UUID, not in plain format */
+#define SD_ID128_UUID_FORMAT_STR "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x"
+
 #define SD_ID128_CONST_STR(x)                                           \
         ((const char[SD_ID128_STRING_MAX]) {                            \
                 ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 153128f2..17857676 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -36,7 +36,7 @@ exe = executable(
   test_unit,
   [test_unit + '.c'],
   include_directories: src_inc,
-  dependencies: nm_core_dep,
+  dependencies: libnm_core_dep,
   c_args: cflags,
   link_with: [
     libnm_systemd_core,
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index 91edcab2..20cbd50e 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -269,12 +269,12 @@ _test_unbase64mem_mem (const char *base64, const guint8 *expected_arr, gsize exp
 	for (i = 0; expected_base64[i]; i++)
 		_test_unbase64char (expected_base64[i], FALSE);
 
-	r = nm_sd_utils_unbase64mem (expected_base64, strlen (expected_base64), &exp2_arr, &exp2_len);
+	r = nm_sd_utils_unbase64mem (expected_base64, strlen (expected_base64), TRUE, &exp2_arr, &exp2_len);
 	g_assert_cmpint (r, ==, 0);
 	g_assert_cmpmem (expected_arr, expected_len, exp2_arr, exp2_len);
 
 	if (!nm_streq (base64, expected_base64)) {
-		r = nm_sd_utils_unbase64mem (base64, strlen (base64), &exp3_arr, &exp3_len);
+		r = nm_sd_utils_unbase64mem (base64, strlen (base64), TRUE, &exp3_arr, &exp3_len);
 		g_assert_cmpint (r, ==, 0);
 		g_assert_cmpmem (expected_arr, expected_len, exp3_arr, exp3_len);
 	}
@@ -289,7 +289,7 @@ _test_unbase64mem_inval (const char *base64)
 	gsize exp_len = 0;
 	int r;
 
-	r = nm_sd_utils_unbase64mem (base64, strlen (base64), &exp_arr, &exp_len);
+	r = nm_sd_utils_unbase64mem (base64, strlen (base64), TRUE, &exp_arr, &exp_len);
 	g_assert_cmpint (r, <, 0);
 	g_assert (!exp_arr);
 	g_assert (exp_len == 0);