summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/nm-fake-platform.c178
-rw-r--r--src/platform/nm-linux-platform.c1170
-rw-r--r--src/platform/nm-platform-utils.c3
-rw-r--r--src/platform/nm-platform.c813
-rw-r--r--src/platform/nm-platform.h340
-rw-r--r--src/platform/nmp-netns.c3
-rw-r--r--src/platform/nmp-object.c28
-rw-r--r--src/platform/nmp-object.h8
-rw-r--r--src/platform/tests/meson.build2
-rw-r--r--src/platform/tests/test-cleanup.c3
-rw-r--r--src/platform/tests/test-common.c124
-rw-r--r--src/platform/tests/test-common.h23
-rw-r--r--src/platform/tests/test-link.c33
-rw-r--r--src/platform/tests/test-route.c11
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c2
15 files changed, 1315 insertions, 1426 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 6aae808a..4a81dc49 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -46,7 +46,7 @@ struct _NMFakePlatformClass {
 
 G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
 
-#define NM_FAKE_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakePlatform, NM_IS_FAKE_PLATFORM)
+#define NM_FAKE_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakePlatform, NM_IS_FAKE_PLATFORM, NMPlatform)
 
 /*****************************************************************************/
 
@@ -126,7 +126,7 @@ static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in
 static gboolean
 sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 
 	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
 
@@ -138,7 +138,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 static char *
 sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *path)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	const char *v;
 
 	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
@@ -155,7 +155,7 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 static NMFakePlatformLink *
 link_get (NMPlatform *platform, int ifindex)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMFakePlatformLink *device;
 	int idx;
 
@@ -223,7 +223,7 @@ link_add_pre (NMPlatform *platform,
               const void *address,
               size_t address_len)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMFakePlatformLink *device;
 	int ifindex;
 	NMPObject *o;
@@ -276,11 +276,12 @@ link_add_pre (NMPlatform *platform,
 
 static int
 link_add (NMPlatform *platform,
-          const char *name,
           NMLinkType type,
-          const char *veth_peer,
+          const char *name,
+          int parent,
           const void *address,
           size_t address_len,
+          gconstpointer extra_data,
           const NMPlatformLink **out_link)
 {
 	NMFakePlatformLink *device;
@@ -291,14 +292,52 @@ link_add (NMPlatform *platform,
 	nm_auto_nmpobj const NMPObject *obj_new_veth = NULL;
 	NMPCacheOpsType cache_op;
 	NMPCacheOpsType cache_op_veth = NMP_CACHE_OPS_UNCHANGED;
+	const char *veth_peer = NULL;
+	NMPObject *dev_obj;
+	NMPObject *dev_lnk = NULL;
 
 	device = link_add_pre (platform, name, type, address, address_len);
 
-	if (veth_peer) {
-		g_assert (type == NM_LINK_TYPE_VETH);
+	g_assert (device);
+
+	dev_obj = (NMPObject *) device->obj;
+
+	if (parent > 0)
+		dev_obj->link.parent = parent;
+	else
+		g_assert (parent == 0);
+
+	g_assert ((parent != 0) == NM_IN_SET (type, NM_LINK_TYPE_VLAN));
+
+	switch (type) {
+	case NM_LINK_TYPE_VETH:
+		veth_peer = extra_data;
+		g_assert (veth_peer);
 		device_veth = link_add_pre (platform, veth_peer, type, NULL, 0);
-	} else
-		g_assert (type != NM_LINK_TYPE_VETH);
+		break;
+	case NM_LINK_TYPE_VLAN: {
+		const NMPlatformLnkVlan *props = extra_data;
+
+		g_assert (props);
+
+		dev_lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, props);
+		break;
+	}
+	case NM_LINK_TYPE_VXLAN: {
+		const NMPlatformLnkVxlan *props = extra_data;
+
+		g_assert (props);
+
+		dev_lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, props);
+		break;
+	}
+	default:
+		g_assert (!extra_data);
+		break;
+	}
+
+	if (dev_lnk)
+		dev_obj->_link.netlink.lnk = dev_lnk;
 
 	link_add_prepare (platform, device, (NMPObject *) device->obj);
 	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
@@ -381,7 +420,7 @@ link_delete (NMPlatform *platform, int ifindex)
 		return FALSE;
 
 	obj_old = g_steal_pointer (&device->obj);
-	g_clear_pointer (&device->udi, g_free);
+	nm_clear_g_free (&device->udi);
 
 	cache_op = nmp_cache_remove (nm_platform_get_cache (platform),
 	                             obj_old,
@@ -710,45 +749,6 @@ link_release (NMPlatform *platform, int master_idx, int slave_idx)
 	return TRUE;
 }
 
-struct vlan_add_data {
-	guint32 vlan_flags;
-	int parent;
-	int vlan_id;
-};
-
-static void
-_vlan_add_prepare (NMPlatform *platform,
-                   NMFakePlatformLink *device,
-                   gconstpointer user_data)
-{
-	const struct vlan_add_data *d = user_data;
-	NMPObject *obj_tmp;
-	NMPObject *lnk;
-
-	obj_tmp = (NMPObject *) device->obj;
-
-	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-	lnk->lnk_vlan.id = d->vlan_id;
-	lnk->lnk_vlan.flags = d->vlan_flags;
-
-	obj_tmp->link.parent = d->parent;
-	obj_tmp->_link.netlink.lnk = lnk;
-}
-
-static gboolean
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
-{
-	const struct vlan_add_data d = {
-		.parent = parent,
-		.vlan_id = vlan_id,
-		.vlan_flags = vlan_flags,
-	};
-
-	link_add_one (platform, name, NM_LINK_TYPE_VLAN,
-	              _vlan_add_prepare, &d, out_link);
-	return TRUE;
-}
-
 static gboolean
 link_vlan_change (NMPlatform *platform,
                   int ifindex,
@@ -764,35 +764,6 @@ link_vlan_change (NMPlatform *platform,
 	return FALSE;
 }
 
-static void
-_vxlan_add_prepare (NMPlatform *platform,
-                    NMFakePlatformLink *device,
-                    gconstpointer user_data)
-{
-	const NMPlatformLnkVxlan *props = user_data;
-	NMPObject *obj_tmp;
-	NMPObject *lnk;
-
-	obj_tmp = (NMPObject *) device->obj;
-
-	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
-	lnk->lnk_vxlan = *props;
-
-	obj_tmp->link.parent = props->parent_ifindex;
-	obj_tmp->_link.netlink.lnk = lnk;
-}
-
-static gboolean
-link_vxlan_add (NMPlatform *platform,
-                const char *name,
-                const NMPlatformLnkVxlan *props,
-                const NMPlatformLink **out_link)
-{
-	link_add_one (platform, name, NM_LINK_TYPE_VXLAN,
-	              _vxlan_add_prepare, props, out_link);
-	return TRUE;
-}
-
 struct infiniband_add_data {
 	int parent;
 	int p_key;
@@ -967,6 +938,7 @@ ip4_address_add (NMPlatform *platform,
                  in_addr_t addr,
                  guint8 plen,
                  in_addr_t peer_addr,
+                 in_addr_t broadcast_address,
                  guint32 lifetime,
                  guint32 preferred,
                  guint32 flags,
@@ -974,16 +946,19 @@ ip4_address_add (NMPlatform *platform,
 {
 	NMPlatformIP4Address address;
 
-	memset (&address, 0, sizeof (address));
-	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
-	address.ifindex = ifindex;
-	address.address = addr;
-	address.peer_address = peer_addr;
-	address.plen = plen;
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
-	address.lifetime = lifetime;
-	address.preferred = preferred;
-	address.n_ifa_flags = flags;
+	address = (NMPlatformIP4Address) {
+		.addr_source               = NM_IP_CONFIG_SOURCE_KERNEL,
+		.ifindex                   = ifindex,
+		.address                   = addr,
+		.plen                      = plen,
+		.peer_address              = peer_addr,
+		.broadcast_address         = broadcast_address,
+		.use_ip4_broadcast_address = TRUE,
+		.timestamp                 = nm_utils_get_monotonic_timestamp_sec (),
+		.lifetime                  = lifetime,
+		.preferred                 = preferred,
+		.n_ifa_flags               = flags,
+	};
 	if (label)
 		g_strlcpy (address.label, label, sizeof (address.label));
 
@@ -1008,7 +983,7 @@ ip6_address_add (NMPlatform *platform,
 	address.address = addr;
 	address.peer_address = (IN6_IS_ADDR_UNSPECIFIED (&peer_addr) || IN6_ARE_ADDR_EQUAL (&addr, &peer_addr)) ? in6addr_any : peer_addr;
 	address.plen = plen;
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.timestamp = nm_utils_get_monotonic_timestamp_sec ();
 	address.lifetime = lifetime;
 	address.preferred = preferred;
 	address.n_ifa_flags = flags;
@@ -1262,10 +1237,10 @@ ip_route_add (NMPlatform *platform,
 
 			if (addr_family == AF_INET) {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet4_ntop (r4->network, sbuf), r->plen, r->metric);
+				             r->ifindex, _nm_utils_inet4_ntop (r4->network, sbuf), r->plen, r->metric);
 			} else {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet6_ntop (&r6->network, sbuf), r->plen, r->metric);
+				             r->ifindex, _nm_utils_inet6_ntop (&r6->network, sbuf), r->plen, r->metric);
 			}
 			return -NME_UNSPEC;
 		}
@@ -1354,19 +1329,16 @@ nm_fake_platform_setup (void)
 
 	nm_platform_setup (platform);
 
-	/* add loopback interface */
-	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, NULL, 0, NULL);
-
-	/* add some ethernets */
-	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
-	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
-	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
+	link_add (platform, NM_LINK_TYPE_LOOPBACK, "lo",   0, NULL, 0, NULL, NULL);
+	link_add (platform, NM_LINK_TYPE_ETHERNET, "eth0", 0, NULL, 0, NULL, NULL);
+	link_add (platform, NM_LINK_TYPE_ETHERNET, "eth1", 0, NULL, 0, NULL, NULL);
+	link_add (platform, NM_LINK_TYPE_ETHERNET, "eth2", 0, NULL, 0, NULL, NULL);
 }
 
 static void
 finalize (GObject *object)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) object);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (object);
 	int i;
 
 	g_hash_table_unref (priv->options);
@@ -1374,7 +1346,7 @@ finalize (GObject *object)
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
 		g_free (device->udi);
-		g_clear_pointer (&device->obj, nmp_object_unref);
+		nm_clear_pointer (&device->obj, nmp_object_unref);
 	}
 	g_array_unref (priv->links);
 
@@ -1418,9 +1390,7 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
 
-	platform_class->vlan_add = vlan_add;
 	platform_class->link_vlan_change = link_vlan_change;
-	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
 	platform_class->infiniband_partition_delete = infiniband_partition_delete;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 7abe4dfc..f7249bb7 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -418,13 +418,14 @@ typedef struct {
 	struct nl_sock *genl;
 
 	struct nl_sock *nlh;
+
+	GSource *event_source;
+
 	guint32 nlh_seq_next;
 #if NM_MORE_LOGGING
 	guint32 nlh_seq_last_handled;
 #endif
 	guint32 nlh_seq_last_seen;
-	GIOChannel *event_channel;
-	guint event_id;
 
 	guint32 pruning[_REFRESH_ALL_TYPE_NUM];
 
@@ -603,7 +604,7 @@ _vlan_qos_mapping_cmp_from_ptr (gconstpointer a, gconstpointer b, gpointer user_
  ******************************************************************/
 
 typedef struct {
-	const NMLinkType nm_type;
+
 	const char *type_string;
 
 	/* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type
@@ -623,68 +624,202 @@ typedef struct {
 	const char *devtype;
 } LinkDesc;
 
-static const LinkDesc linktypes[] = {
-	{ NM_LINK_TYPE_NONE,          "none",        NULL,          NULL },
-	{ NM_LINK_TYPE_UNKNOWN,       "unknown",     NULL,          NULL },
-
-	{ NM_LINK_TYPE_ETHERNET,      "ethernet",    NULL,          NULL },
-	{ NM_LINK_TYPE_INFINIBAND,    "infiniband",  NULL,          NULL },
-	{ NM_LINK_TYPE_OLPC_MESH,     "olpc-mesh",   NULL,          NULL },
-	{ NM_LINK_TYPE_WIFI,          "wifi",        NULL,          "wlan" },
-	{ NM_LINK_TYPE_WWAN_NET,      "wwan",        NULL,          "wwan" },
-	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
-	{ NM_LINK_TYPE_WPAN,          "wpan",        NULL,          NULL },
-	{ NM_LINK_TYPE_6LOWPAN,       "6lowpan",     NULL,          NULL },
-
-	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
-	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
-	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
-	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
-	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         NULL },
-	{ NM_LINK_TYPE_IP6TNL,        "ip6tnl",      "ip6tnl",      NULL },
-	{ NM_LINK_TYPE_IP6GRE,        "ip6gre",      "ip6gre",      NULL },
-	{ NM_LINK_TYPE_IP6GRETAP,     "ip6gretap",   "ip6gretap",   NULL },
-	{ NM_LINK_TYPE_IPIP,          "ipip",        "ipip",        NULL },
-	{ NM_LINK_TYPE_LOOPBACK,      "loopback",    NULL,          NULL },
-	{ NM_LINK_TYPE_MACSEC,        "macsec",      "macsec",      NULL },
-	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
-	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
-	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
-	{ NM_LINK_TYPE_PPP,           "ppp",         NULL,          "ppp" },
-	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         NULL },
-	{ NM_LINK_TYPE_TUN,           "tun",         "tun",         NULL },
-	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
-	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
-	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
-	{ NM_LINK_TYPE_WIREGUARD,     "wireguard",   "wireguard",   "wireguard" },
-
-	{ NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
-	{ NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
-	{ NM_LINK_TYPE_TEAM,          "team",        "team",        NULL },
+static const LinkDesc link_descs[] = {
+	[NM_LINK_TYPE_NONE] =        { "none",        NULL,          NULL        },
+	[NM_LINK_TYPE_UNKNOWN] =     { "unknown",     NULL,          NULL        },
+	[NM_LINK_TYPE_ANY] =         { "any",         NULL,          NULL        },
+
+	[NM_LINK_TYPE_ETHERNET] =    { "ethernet",    NULL,          NULL        },
+	[NM_LINK_TYPE_INFINIBAND] =  { "infiniband",  NULL,          NULL        },
+	[NM_LINK_TYPE_OLPC_MESH] =   { "olpc-mesh",   NULL,          NULL        },
+	[NM_LINK_TYPE_WIFI] =        { "wifi",        NULL,          "wlan"      },
+	[NM_LINK_TYPE_WWAN_NET] =    { "wwan",        NULL,          "wwan"      },
+	[NM_LINK_TYPE_WIMAX] =       { "wimax",       "wimax",       "wimax"     },
+	[NM_LINK_TYPE_WPAN] =        { "wpan",        NULL,          NULL        },
+	[NM_LINK_TYPE_6LOWPAN] =     { "6lowpan",     NULL,          NULL        },
+
+	[NM_LINK_TYPE_BNEP] =        { "bluetooth",   NULL,          "bluetooth" },
+	[NM_LINK_TYPE_DUMMY] =       { "dummy",       "dummy",       NULL        },
+	[NM_LINK_TYPE_GRE] =         { "gre",         "gre",         NULL        },
+	[NM_LINK_TYPE_GRETAP] =      { "gretap",      "gretap",      NULL        },
+	[NM_LINK_TYPE_IFB] =         { "ifb",         "ifb",         NULL        },
+	[NM_LINK_TYPE_IP6TNL] =      { "ip6tnl",      "ip6tnl",      NULL        },
+	[NM_LINK_TYPE_IP6GRE] =      { "ip6gre",      "ip6gre",      NULL        },
+	[NM_LINK_TYPE_IP6GRETAP] =   { "ip6gretap",   "ip6gretap",   NULL        },
+	[NM_LINK_TYPE_IPIP] =        { "ipip",        "ipip",        NULL        },
+	[NM_LINK_TYPE_LOOPBACK] =    { "loopback",    NULL,          NULL        },
+	[NM_LINK_TYPE_MACSEC] =      { "macsec",      "macsec",      NULL        },
+	[NM_LINK_TYPE_MACVLAN] =     { "macvlan",     "macvlan",     NULL        },
+	[NM_LINK_TYPE_MACVTAP] =     { "macvtap",     "macvtap",     NULL        },
+	[NM_LINK_TYPE_OPENVSWITCH] = { "openvswitch", "openvswitch", NULL        },
+	[NM_LINK_TYPE_PPP] =         { "ppp",         NULL,          "ppp"       },
+	[NM_LINK_TYPE_SIT] =         { "sit",         "sit",         NULL        },
+	[NM_LINK_TYPE_TUN] =         { "tun",         "tun",         NULL        },
+	[NM_LINK_TYPE_VETH] =        { "veth",        "veth",        NULL        },
+	[NM_LINK_TYPE_VLAN] =        { "vlan",        "vlan",        "vlan"      },
+	[NM_LINK_TYPE_VRF] =         { "vrf",         "vrf",         "vrf"       },
+	[NM_LINK_TYPE_VXLAN] =       { "vxlan",       "vxlan",       "vxlan"     },
+	[NM_LINK_TYPE_WIREGUARD] =   { "wireguard",   "wireguard",   "wireguard" },
+
+	[NM_LINK_TYPE_BRIDGE] =      { "bridge",      "bridge",      "bridge"    },
+	[NM_LINK_TYPE_BOND] =        { "bond",        "bond",        "bond"      },
+	[NM_LINK_TYPE_TEAM] =        { "team",        "team",        NULL        },
 };
 
-static const char *
-nm_link_type_to_rtnl_type_string (NMLinkType type)
+static const LinkDesc *
+_link_desc_from_link_type (NMLinkType link_type)
 {
-	int i;
+	nm_assert (_NM_INT_NOT_NEGATIVE (link_type));
+	nm_assert (link_type < G_N_ELEMENTS (link_descs));
+	nm_assert (link_descs[link_type].type_string);
 
-	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-		if (type == linktypes[i].nm_type)
-			return linktypes[i].rtnl_type;
+	return &link_descs[link_type];
+}
+
+static NMLinkType
+_link_type_from_rtnl_type (const char *name) \
+{
+	static const NMLinkType LIST[] = {
+		NM_LINK_TYPE_BOND,        /* "bond"        */
+		NM_LINK_TYPE_BRIDGE,      /* "bridge"      */
+		NM_LINK_TYPE_DUMMY,       /* "dummy"       */
+		NM_LINK_TYPE_GRE,         /* "gre"         */
+		NM_LINK_TYPE_GRETAP,      /* "gretap"      */
+		NM_LINK_TYPE_IFB,         /* "ifb"         */
+		NM_LINK_TYPE_IP6GRE,      /* "ip6gre"      */
+		NM_LINK_TYPE_IP6GRETAP,   /* "ip6gretap"   */
+		NM_LINK_TYPE_IP6TNL,      /* "ip6tnl"      */
+		NM_LINK_TYPE_IPIP,        /* "ipip"        */
+		NM_LINK_TYPE_MACSEC,      /* "macsec"      */
+		NM_LINK_TYPE_MACVLAN,     /* "macvlan"     */
+		NM_LINK_TYPE_MACVTAP,     /* "macvtap"     */
+		NM_LINK_TYPE_OPENVSWITCH, /* "openvswitch" */
+		NM_LINK_TYPE_SIT,         /* "sit"         */
+		NM_LINK_TYPE_TEAM,        /* "team"        */
+		NM_LINK_TYPE_TUN,         /* "tun"         */
+		NM_LINK_TYPE_VETH,        /* "veth"        */
+		NM_LINK_TYPE_VLAN,        /* "vlan"        */
+		NM_LINK_TYPE_VRF,         /* "vrf"         */
+		NM_LINK_TYPE_VXLAN,       /* "vxlan"       */
+		NM_LINK_TYPE_WIMAX,       /* "wimax"       */
+		NM_LINK_TYPE_WIREGUARD,   /* "wireguard"   */
+	};
+
+	nm_assert (name);
+
+	if (NM_MORE_ASSERT_ONCE (5)) {
+		int i, j, k;
+
+		for (i = 0; i < G_N_ELEMENTS (LIST); i++) {
+			nm_assert (_link_desc_from_link_type (LIST[i]) == &link_descs[LIST[i]]);
+			nm_assert (link_descs[LIST[i]].rtnl_type);
+			if (i > 0)
+				nm_assert (strcmp (link_descs[LIST[i - 1]].rtnl_type, link_descs[LIST[i]].rtnl_type) < 0);
+		}
+		for (i = 0; i < G_N_ELEMENTS (link_descs); i++) {
+			if (!link_descs[i].rtnl_type)
+				continue;
+			for (j = 0, k = 0; j < G_N_ELEMENTS (LIST); j++)
+				k += (LIST[j] == i);
+			nm_assert (k == 1);
+		}
+	}
+
+	{
+		unsigned imin = 0;
+		unsigned imax = (G_N_ELEMENTS (LIST) - 1);
+		unsigned imid = (G_N_ELEMENTS (LIST) - 1) / 2;
+
+		for (;;) {
+			const int cmp = strcmp (link_descs[LIST[imid]].rtnl_type, name);
+
+			if (G_UNLIKELY (cmp == 0))
+				return LIST[imid];
+
+			if (cmp < 0)
+				imin = imid + 1u;
+			else
+				imax = imid - 1u;
+
+			if (G_UNLIKELY (imin > imax))
+				return NM_LINK_TYPE_NONE;
+
+			imid = (imin + imax) / 2u;
+		}
 	}
-	g_return_val_if_reached (NULL);
 }
 
-const char *
-nm_link_type_to_string (NMLinkType type)
-{
-	int i;
+static NMLinkType
+_link_type_from_devtype (const char *name) \
+{
+	static const NMLinkType LIST[] = {
+		NM_LINK_TYPE_BNEP,      /* "bluetooth" */
+		NM_LINK_TYPE_BOND,      /* "bond"      */
+		NM_LINK_TYPE_BRIDGE,    /* "bridge"    */
+		NM_LINK_TYPE_PPP,       /* "ppp"       */
+		NM_LINK_TYPE_VLAN,      /* "vlan"      */
+		NM_LINK_TYPE_VRF,       /* "vrf"       */
+		NM_LINK_TYPE_VXLAN,     /* "vxlan"     */
+		NM_LINK_TYPE_WIMAX,     /* "wimax"     */
+		NM_LINK_TYPE_WIREGUARD, /* "wireguard" */
+		NM_LINK_TYPE_WIFI,      /* "wlan"      */
+		NM_LINK_TYPE_WWAN_NET,  /* "wwan"      */
+	};
+
+	nm_assert (name);
 
-	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-		if (type == linktypes[i].nm_type)
-			return linktypes[i].type_string;
+	if (NM_MORE_ASSERT_ONCE (5)) {
+		int i, j, k;
+
+		for (i = 0; i < G_N_ELEMENTS (LIST); i++) {
+			nm_assert (_link_desc_from_link_type (LIST[i]) == &link_descs[LIST[i]]);
+			nm_assert (link_descs[LIST[i]].devtype);
+			if (i > 0)
+				nm_assert (strcmp (link_descs[LIST[i - 1]].devtype, link_descs[LIST[i]].devtype) < 0);
+		}
+		for (i = 0; i < G_N_ELEMENTS (link_descs); i++) {
+			if (!link_descs[i].devtype)
+				continue;
+			for (j = 0, k = 0; j < G_N_ELEMENTS (LIST); j++)
+				k += (LIST[j] == i);
+			nm_assert (k == 1);
+		}
 	}
-	g_return_val_if_reached (NULL);
+
+	{
+		unsigned imin = 0;
+		unsigned imax = (G_N_ELEMENTS (LIST) - 1);
+		unsigned imid = (G_N_ELEMENTS (LIST) - 1) / 2;
+
+		for (;;) {
+			const int cmp = strcmp (link_descs[LIST[imid]].devtype, name);
+
+			if (G_UNLIKELY (cmp == 0))
+				return LIST[imid];
+
+			if (cmp < 0)
+				imin = imid + 1u;
+			else
+				imax = imid - 1u;
+
+			if (G_UNLIKELY (imin > imax))
+				return NM_LINK_TYPE_NONE;
+
+			imid = (imin + imax) / 2u;
+		}
+	}
+}
+
+static const char *
+nm_link_type_to_rtnl_type_string (NMLinkType link_type)
+{
+	return _link_desc_from_link_type (link_type)->rtnl_type;
+}
+
+const char *
+nm_link_type_to_string (NMLinkType link_type)
+{
+	return _link_desc_from_link_type (link_type)->type_string;
 }
 
 /******************************************************************
@@ -738,8 +873,8 @@ _addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 
 	/* do all the calculations in milliseconds scale */
 
-	now_nm = nm_utils_get_monotonic_timestamp_ms ();
-	now_nl = nm_utils_clock_gettime_ms (CLOCK_MONOTONIC);
+	now_nm = nm_utils_get_monotonic_timestamp_msec ();
+	now_nl = nm_utils_clock_gettime_msec (CLOCK_MONOTONIC);
 
 	nm_assert (now_nm >= 1000);
 	nm_assert (now_nl >= 0);
@@ -748,13 +883,13 @@ _addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 
 	NM_SET_OUT (out_now_nm, now_nm / 1000);
 
-	/* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() scale is
+	/* converting the timestamp into nm_utils_get_monotonic_timestamp_msec() scale is
 	 * a good guess but fails in the following situations:
 	 *
 	 * - If the address existed before start of the process, the timestamp in nm scale would
 	 *   be negative or zero. In this case we default to 1.
 	 * - during hibernation, the CLOCK_MONOTONIC/timestamp drifts from
-	 *   nm_utils_get_monotonic_timestamp_ms() scale.
+	 *   nm_utils_get_monotonic_timestamp_msec() scale.
 	 */
 	if (result <= 1000)
 		return 1;
@@ -809,7 +944,7 @@ _addrtime_get_lifetimes (guint32 timestamp,
 		if (now == 0) {
 			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
 			nm_assert (timestamp == 1);
-			now = nm_utils_get_monotonic_timestamp_s ();
+			now = nm_utils_get_monotonic_timestamp_sec ();
 		}
 		if (timestamp < now) {
 			guint32 diff = now - timestamp;
@@ -894,10 +1029,12 @@ _linktype_get_type (NMPlatform *platform,
                     const NMPObject **link_cached,
                     const char **out_kind)
 {
-	guint i;
+	NMLinkType link_type;
 
 	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (platform);
 	nm_assert (ifname);
+	nm_assert (_link_type_from_devtype ("wlan") == NM_LINK_TYPE_WIFI);
+	nm_assert (_link_type_from_rtnl_type ("bond") == NM_LINK_TYPE_BOND);
 
 	if (completed_from_cache) {
 		const NMPObject *obj;
@@ -905,7 +1042,7 @@ _linktype_get_type (NMPlatform *platform,
 		obj = _lookup_cached_link (cache, ifindex, completed_from_cache, link_cached);
 
 		/* If we detected the link type before, we stick to that
-		 * decision unless the "kind" no "name" changed. If "name" changed,
+		 * decision unless the "kind" or "name" changed. If "name" changed,
 		 * it means that their type may not have been determined correctly
 		 * due to race conditions while accessing sysfs.
 		 *
@@ -936,11 +1073,9 @@ _linktype_get_type (NMPlatform *platform,
 	*out_kind = g_intern_string (kind);
 
 	if (kind) {
-		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-			if (nm_streq0 (kind, linktypes[i].rtnl_type)) {
-				return linktypes[i].nm_type;
-			}
-		}
+		link_type = _link_type_from_rtnl_type (kind);
+		if (link_type != NM_LINK_TYPE_NONE)
+			return link_type;
 	}
 
 	if (arptype == ARPHRD_LOOPBACK)
@@ -987,16 +1122,16 @@ _linktype_get_type (NMPlatform *platform,
 				return NM_LINK_TYPE_OLPC_MESH;
 
 			devtype = _linktype_read_devtype (dirfd);
-			for (i = 0; devtype && i < G_N_ELEMENTS (linktypes); i++) {
-				if (g_strcmp0 (devtype, linktypes[i].devtype) == 0) {
-					if (linktypes[i].nm_type == NM_LINK_TYPE_BNEP) {
+			if (devtype) {
+				link_type = _link_type_from_devtype (devtype);
+				if (link_type != NM_LINK_TYPE_NONE) {
+					if (   link_type == NM_LINK_TYPE_BNEP
+					    && arptype != ARPHRD_ETHER) {
 						/* Both BNEP and 6lowpan use DEVTYPE=bluetooth, so we must
 						 * use arptype to distinguish between them.
 						 */
-						if (arptype != ARPHRD_ETHER)
-							continue;
-					}
-					return linktypes[i].nm_type;
+					} else
+						return link_type;
 				}
 			}
 
@@ -1022,13 +1157,14 @@ _linktype_get_type (NMPlatform *platform,
 			 */
 			if (!kind && !devtype)
 				return NM_LINK_TYPE_ETHERNET;
+
 			/* The USB gadget interfaces behave and look like ordinary ethernet devices
 			 * aside from the DEVTYPE. */
-			if (!g_strcmp0 (devtype, "gadget"))
+			if (nm_streq0 (devtype, "gadget"))
 				return NM_LINK_TYPE_ETHERNET;
 
 			/* Distributed Switch Architecture switch chips */
-			if (!g_strcmp0 (devtype, "dsa"))
+			if (nm_streq0 (devtype, "dsa"))
 				return NM_LINK_TYPE_ETHERNET;
 		}
 	}
@@ -1753,6 +1889,8 @@ _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 #undef IFLA_VXLAN_MAX
 #define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6
 
+#define IFLA_VRF_TABLE        1
+
 /* older kernel header might not contain 'struct ifla_vxlan_port_range'.
  * Redefine it. */
 struct nm_ifla_vxlan_port_range {
@@ -1847,6 +1985,33 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 	return obj;
 }
 
+static NMPObject *
+_parse_lnk_vrf (const char *kind, struct nlattr *info_data)
+{
+	static const struct nla_policy policy[] = {
+		[IFLA_VRF_TABLE]         = { .type = NLA_U32 },
+	};
+	NMPlatformLnkVrf *props;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	NMPObject *obj;
+
+	if (   !info_data
+	    || !nm_streq0 (kind, "vrf"))
+		return NULL;
+
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VRF, NULL);
+
+	props = &obj->lnk_vrf;
+
+	if (tb[IFLA_VRF_TABLE])
+		props->table = nla_get_u32 (tb[IFLA_VRF_TABLE]);
+
+	return obj;
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -2797,6 +2962,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_VLAN:
 		lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_VRF:
+		lnk_data = _parse_lnk_vrf (nl_info_kind, nl_info_data);
+		break;
 	case NM_LINK_TYPE_VXLAN:
 		lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
 		break;
@@ -2978,6 +3146,12 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 			memcpy (&obj->ip4_address.address, nla_data (tb[IFA_LOCAL]), addr_len);
 		if (tb[IFA_ADDRESS])
 			memcpy (&obj->ip4_address.peer_address, nla_data (tb[IFA_ADDRESS]), addr_len);
+
+		_check_addr_or_return_null (tb, IFA_BROADCAST, addr_len);
+		obj->ip4_address.broadcast_address =   tb[IFA_BROADCAST]
+		                                     ? nla_get_u32 (tb[IFA_BROADCAST])
+		                                     : 0u;
+		obj->ip4_address.use_ip4_broadcast_address = TRUE;
 	} else {
 		/* For IPv6, IFA_ADDRESS is always present.
 		 *
@@ -3689,13 +3863,13 @@ nla_put_failure:
 static gboolean
 _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
                                NMLinkType link_type,
-                               const char *veth_peer)
+                               gconstpointer extra_data)
 {
 	struct nlattr *info;
+	struct nlattr *data = NULL;
 	const char *kind;
 
 	nm_assert (msg);
-	nm_assert (!!veth_peer == (link_type == NM_LINK_TYPE_VETH));
 
 	kind = nm_link_type_to_rtnl_type_string (link_type);
 	if (!kind)
@@ -3706,9 +3880,89 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 
 	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
 
-	if (veth_peer) {
+	switch (link_type) {
+	case NM_LINK_TYPE_VLAN: {
+		const NMPlatformLnkVlan *props = extra_data;
+
+		nm_assert (extra_data);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U16 (msg, IFLA_VLAN_ID, props->id);
+
+		{
+			struct ifla_vlan_flags flags = {
+				.flags = props->flags & NM_VLAN_FLAGS_ALL,
+				.mask  = NM_VLAN_FLAGS_ALL,
+			};
+
+			NLA_PUT (msg, IFLA_VLAN_FLAGS, sizeof (flags), &flags);
+		}
+		break;
+	}
+	case NM_LINK_TYPE_VRF: {
+		const NMPlatformLnkVrf *props = extra_data;
+
+		nm_assert (extra_data);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, IFLA_VRF_TABLE, props->table);
+		break;
+	}
+	case NM_LINK_TYPE_VXLAN: {
+		const NMPlatformLnkVxlan *props = extra_data;
+
+		nm_assert (extra_data);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, IFLA_VXLAN_ID, props->id);
+
+		if (props->group)
+			NLA_PUT (msg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group);
+		else if (!IN6_IS_ADDR_UNSPECIFIED (&props->group6))
+			NLA_PUT (msg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6);
+
+		if (props->local)
+			NLA_PUT (msg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local);
+		else if (!IN6_IS_ADDR_UNSPECIFIED (&props->local6))
+			NLA_PUT (msg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6);
+
+		if (props->parent_ifindex >= 0)
+			NLA_PUT_U32 (msg, IFLA_VXLAN_LINK, props->parent_ifindex);
+
+		if (   props->src_port_min
+		    || props->src_port_max) {
+			struct nm_ifla_vxlan_port_range port_range = {
+				.low  = htons (props->src_port_min),
+				.high = htons (props->src_port_max),
+			};
+
+			NLA_PUT (msg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range);
+		}
+
+		NLA_PUT_U16 (msg, IFLA_VXLAN_PORT, htons (props->dst_port));
+		NLA_PUT_U8 (msg, IFLA_VXLAN_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_TTL, props->ttl);
+		NLA_PUT_U32 (msg, IFLA_VXLAN_AGEING, props->ageing);
+		NLA_PUT_U32 (msg, IFLA_VXLAN_LIMIT, props->limit);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_LEARNING, !!props->learning);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_PROXY, !!props->proxy);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_RSC, !!props->rsc);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_L2MISS, !!props->l2miss);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_L3MISS, !!props->l3miss);
+		break;
+	}
+	case NM_LINK_TYPE_VETH: {
+		const char *veth_peer = extra_data;
 		const struct ifinfomsg ifi = { };
-		struct nlattr *data, *info_peer;
+		struct nlattr *info_peer;
+
+		nm_assert (veth_peer);
 
 		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
 			goto nla_put_failure;
@@ -3718,8 +3972,174 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 			goto nla_put_failure;
 		NLA_PUT_STRING (msg, IFLA_IFNAME, veth_peer);
 		nla_nest_end (msg, info_peer);
-		nla_nest_end (msg, data);
+		break;
+	}
+	case NM_LINK_TYPE_GRE:
+	case NM_LINK_TYPE_GRETAP: {
+		const NMPlatformLnkGre *props = extra_data;
+
+		nm_assert (props);
+		nm_assert (props->is_tap == (link_type == NM_LINK_TYPE_GRETAP));
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_GRE_LINK, props->parent_ifindex);
+		NLA_PUT_U32 (msg, IFLA_GRE_LOCAL, props->local);
+		NLA_PUT_U32 (msg, IFLA_GRE_REMOTE, props->remote);
+		NLA_PUT_U8 (msg, IFLA_GRE_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_GRE_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
+		NLA_PUT_U32 (msg, IFLA_GRE_IKEY, htonl (props->input_key));
+		NLA_PUT_U32 (msg, IFLA_GRE_OKEY, htonl (props->output_key));
+		NLA_PUT_U16 (msg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+		NLA_PUT_U16 (msg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+		break;
+	}
+	case NM_LINK_TYPE_SIT: {
+		const NMPlatformLnkSit *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_IPTUN_LINK, props->parent_ifindex);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_LOCAL, props->local);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_REMOTE, props->remote);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
+		break;
+	}
+	case NM_LINK_TYPE_IP6TNL: {
+		const NMPlatformLnkIp6Tnl *props = extra_data;
+		guint32 flowinfo;
+
+		nm_assert (props);
+		nm_assert (!props->is_gre);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_IPTUN_LINK, props->parent_ifindex);
+
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->local))
+			NLA_PUT (msg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local);
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->remote))
+			NLA_PUT (msg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote);
+
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit);
+
+		flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+		flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+		            & IP6_FLOWINFO_TCLASS_MASK;
+		NLA_PUT_U32 (msg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
+		NLA_PUT_U8 (msg, IFLA_IPTUN_PROTO, props->proto);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_FLAGS, props->flags);
+		break;
+	}
+	case NM_LINK_TYPE_IP6GRE:
+	case NM_LINK_TYPE_IP6GRETAP: {
+		const NMPlatformLnkIp6Tnl *props = extra_data;
+		guint32 flowinfo;
+
+		nm_assert (props);
+		nm_assert (props->is_gre);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_GRE_LINK, props->parent_ifindex);
+
+		NLA_PUT_U32 (msg, IFLA_GRE_IKEY, htonl (props->input_key));
+		NLA_PUT_U32 (msg, IFLA_GRE_OKEY, htonl (props->output_key));
+		NLA_PUT_U16 (msg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+		NLA_PUT_U16 (msg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->local))
+			NLA_PUT (msg, IFLA_GRE_LOCAL, sizeof (props->local), &props->local);
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->local))
+			NLA_PUT (msg, IFLA_GRE_REMOTE, sizeof (props->remote), &props->remote);
+
+		NLA_PUT_U8 (msg, IFLA_GRE_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit);
+
+		flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+		flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+		            & IP6_FLOWINFO_TCLASS_MASK;
+		NLA_PUT_U32 (msg, IFLA_GRE_FLOWINFO, htonl (flowinfo));
+		NLA_PUT_U32 (msg, IFLA_GRE_FLAGS, props->flags);
+		break;
+	}
+	case NM_LINK_TYPE_IPIP: {
+		const NMPlatformLnkIpIp *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_IPTUN_LINK, props->parent_ifindex);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_LOCAL, props->local);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_REMOTE, props->remote);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
+		break;
+	}
+	case NM_LINK_TYPE_MACSEC: {
+		const NMPlatformLnkMacsec *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->icv_length)
+			NLA_PUT_U8 (msg, IFLA_MACSEC_ICV_LEN, 16);
+		if (props->cipher_suite)
+			NLA_PUT_U64 (msg, IFLA_MACSEC_CIPHER_SUITE, props->cipher_suite);
+		if (props->replay_protect)
+			NLA_PUT_U32 (msg, IFLA_MACSEC_WINDOW, props->window);
+
+		NLA_PUT_U64 (msg, IFLA_MACSEC_SCI, htobe64 (props->sci));
+		NLA_PUT_U8 (msg, IFLA_MACSEC_ENCODING_SA, props->encoding_sa);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_ENCRYPT, props->encrypt);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_PROTECT, props->protect);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_INC_SCI, props->include_sci);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_ES, props->es);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_SCB, props->scb);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_REPLAY_PROTECT, props->replay_protect);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_VALIDATION, props->validation);
+		break;
+	};
+	case NM_LINK_TYPE_MACVTAP:
+	case NM_LINK_TYPE_MACVLAN: {
+		const NMPlatformLnkMacvlan *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, IFLA_MACVLAN_MODE, props->mode);
+		NLA_PUT_U16 (msg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0);
+		break;
 	}
+	default:
+		nm_assert (!extra_data);
+		break;
+	}
+
+	if (data)
+		nla_nest_end (msg, data);
 
 	nla_nest_end (msg, info);
 
@@ -3743,6 +4163,11 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 	guint i;
 	gboolean has_any_vlan_properties = FALSE;
 
+	G_STATIC_ASSERT (NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
+
 #define VLAN_XGRESS_PRIO_VALID(from) (((from) & ~(guint32) 0x07) == 0)
 
 	nm_assert (msg);
@@ -3892,6 +4317,7 @@ _nl_msg_new_address (int nlmsg_type,
                      int scope,
                      guint32 lifetime,
                      guint32 preferred,
+                     in_addr_t ip4_broadcast_address,
                      const char *label)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -3935,16 +4361,8 @@ _nl_msg_new_address (int nlmsg_type,
 	if (label && label[0])
 		NLA_PUT_STRING (msg, IFA_LABEL, label);
 
-	if (   family == AF_INET
-	    && nlmsg_type != RTM_DELADDR
-	    && plen < 31 /* RFC 3021 */
-	    && address
-	    && *((in_addr_t *) address) != 0) {
-		in_addr_t broadcast;
-
-		broadcast = *((in_addr_t *) address) | ~_nm_utils_ip4_prefix_to_netmask (plen);
-		NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast);
-	}
+	if (ip4_broadcast_address != 0)
+		NLA_PUT (msg, IFA_BROADCAST, sizeof (in_addr_t), &ip4_broadcast_address);
 
 	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
 	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
@@ -4236,7 +4654,7 @@ _nl_msg_new_qdisc (int nlmsg_type,
 	if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
 		goto nla_put_failure;
 
-	if (strcmp (qdisc->kind, "fq_codel") == 0) {
+	if (nm_streq (qdisc->kind, "fq_codel")) {
 		if (qdisc->fq_codel.limit)
 			NLA_PUT_U32 (msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit);
 		if (qdisc->fq_codel.flows)
@@ -4909,7 +5327,8 @@ refresh_all_type_get_info (RefreshAllType refresh_all_type)
 	return &infos[refresh_all_type];
 }
 
-_NM_UTILS_LOOKUP_DEFINE (static, delayed_action_type_to_refresh_all_type, DelayedActionType, RefreshAllType,
+static
+NM_UTILS_LOOKUP_DEFINE (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),
@@ -4992,7 +5411,8 @@ 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,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (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"),
@@ -5031,14 +5451,14 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data
 		data = user_data;
 
 		if (data) {
-			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_ns ();
+			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_nsec ();
 			char b[255];
 
 			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT", response-type %d%s%s)",
 			                        data->seq_number,
 			                        timeout < 0 ? "-" : "",
-			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND,
-			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
+			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NSEC_PER_SEC,
+			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NSEC_PER_SEC,
 			                        (int) data->response_type,
 			                        data->seq_result ? ", " : "",
 			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, NULL, b, sizeof (b)) : "");
@@ -5140,7 +5560,7 @@ delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform,
 		if (data->seq_result)
 			delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
 		else if (   p_now_ns
-		         && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) >= data->timeout_abs_ns)) {
+		         && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_nsec ())) >= data->timeout_abs_ns)) {
 			/* the caller can optionally check for timeout by providing a p_now_ns argument. */
 			delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
 		} else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
@@ -5360,7 +5780,7 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 {
 	DelayedActionWaitForNlResponseData data = {
 		.seq_number = seq_number,
-		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
+		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_nsec () + (200 * (NM_UTILS_NSEC_PER_SEC / 1000)),
 		.out_seq_result = out_seq_result,
 		.out_errmsg = out_errmsg,
 		.response_type = response_type,
@@ -6491,11 +6911,12 @@ out:
 
 static int
 link_add (NMPlatform *platform,
-          const char *name,
           NMLinkType type,
-          const char *veth_peer,
+          const char *name,
+          int parent,
           const void *address,
           size_t address_len,
+          gconstpointer extra_data,
           const NMPlatformLink **out_link)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -6519,10 +6940,13 @@ link_add (NMPlatform *platform,
 	if (!nlmsg)
 		return -NME_UNSPEC;
 
+	if (parent > 0)
+		NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
+
 	if (address && address_len)
 		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
-	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, veth_peer))
+	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, extra_data))
 		return -NME_UNSPEC;
 
 	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
@@ -7158,385 +7582,6 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 }
 
 static gboolean
-vlan_add (NMPlatform *platform,
-          const char *name,
-          int parent,
-          int vlan_id,
-          guint32 vlan_flags,
-          const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-	G_STATIC_ASSERT (NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR);
-	G_STATIC_ASSERT (NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP);
-	G_STATIC_ASSERT (NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING);
-	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
-
-	vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL;
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
-	                                         vlan_id,
-	                                         NM_VLAN_FLAGS_ALL,
-	                                         vlan_flags,
-	                                         NULL,
-	                                         0,
-	                                         NULL,
-	                                         0))
-		return FALSE;
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_gre_add (NMPlatform *platform,
-              const char *name,
-              const NMPlatformLnkGre *props,
-              const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->is_tap ? "gretap" : "gre");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_LOCAL, props->local);
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_REMOTE, props->remote);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_ip6tnl_add (NMPlatform *platform,
-                 const char *name,
-                 const NMPlatformLnkIp6Tnl *props,
-                 const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-	guint32 flowinfo;
-
-	g_return_val_if_fail (!props->is_gre, FALSE);
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ip6tnl");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
-
-	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local);
-	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote);
-
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit);
-
-	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
-	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
-	            & IP6_FLOWINFO_TCLASS_MASK;
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLAGS, props->flags);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_ip6gre_add (NMPlatform *platform,
-                 const char *name,
-                 const NMPlatformLnkIp6Tnl *props,
-                 const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-	guint32 flowinfo;
-
-	g_return_val_if_fail (props->is_gre, FALSE);
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->is_tap ? "ip6gretap" : "ip6gre");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
-
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
-
-	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_GRE_LOCAL, sizeof (props->local), &props->local);
-	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_GRE_REMOTE, sizeof (props->remote), &props->remote);
-
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit);
-
-	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
-	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
-	            & IP6_FLOWINFO_TCLASS_MASK;
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_FLOWINFO, htonl (flowinfo));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_FLAGS, props->flags);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_ipip_add (NMPlatform *platform,
-               const char *name,
-               const NMPlatformLnkIpIp *props,
-               const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ipip");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_macsec_add (NMPlatform *platform,
-                 const char *name,
-                 int parent,
-                 const NMPlatformLnkMacsec *props,
-                 const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "macsec");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->icv_length)
-		NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ICV_LEN, 16);
-	if (props->cipher_suite)
-		NLA_PUT_U64 (nlmsg, IFLA_MACSEC_CIPHER_SUITE, props->cipher_suite);
-	if (props->replay_protect)
-		NLA_PUT_U32 (nlmsg, IFLA_MACSEC_WINDOW, props->window);
-
-	NLA_PUT_U64 (nlmsg, IFLA_MACSEC_SCI, htobe64 (props->sci));
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ENCODING_SA, props->encoding_sa);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ENCRYPT, props->encrypt);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_PROTECT, props->protect);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_INC_SCI, props->include_sci);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ES, props->es);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_SCB, props->scb);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_REPLAY_PROTECT, props->replay_protect);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_VALIDATION, props->validation);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 NM_LINK_TYPE_MACSEC,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_macvlan_add (NMPlatform *platform,
-                  const char *name,
-                  int parent,
-                  const NMPlatformLnkMacvlan *props,
-                  const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->tap ? "macvtap" : "macvlan");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	NLA_PUT_U32 (nlmsg, IFLA_MACVLAN_MODE, props->mode);
-	NLA_PUT_U16 (nlmsg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_sit_add (NMPlatform *platform,
-              const char *name,
-              const NMPlatformLnkSit *props,
-              const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "sit");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
 link_tun_add (NMPlatform *platform,
               const char *name,
               const NMPlatformLnkTun *props,
@@ -7592,107 +7637,6 @@ link_tun_add (NMPlatform *platform,
 	return TRUE;
 }
 
-static gboolean
-link_vxlan_add (NMPlatform *platform,
-                const char *name,
-                const NMPlatformLnkVxlan *props,
-                const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-	struct nm_ifla_vxlan_port_range port_range;
-
-	g_return_val_if_fail (props, FALSE);
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "vxlan");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_ID, props->id);
-
-	if (props->group)
-		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group);
-	else if (memcmp (&props->group6, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6);
-
-	if (props->local)
-		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local);
-	else if (memcmp (&props->local6, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6);
-
-	if (props->parent_ifindex >= 0)
-		NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LINK, props->parent_ifindex);
-
-	if (props->src_port_min || props->src_port_max) {
-		port_range.low = htons (props->src_port_min);
-		port_range.high = htons (props->src_port_max);
-		NLA_PUT (nlmsg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range);
-	}
-
-	NLA_PUT_U16 (nlmsg, IFLA_VXLAN_PORT, htons (props->dst_port));
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TTL, props->ttl);
-	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_AGEING, props->ageing);
-	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LIMIT, props->limit);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_LEARNING, !!props->learning);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_PROXY, !!props->proxy);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_RSC, !!props->rsc);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L2MISS, !!props->l2miss);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L3MISS, !!props->l3miss);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_6lowpan_add (NMPlatform *platform,
-                  const char *name,
-                  int parent,
-                  const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "lowpan");
-
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 NM_LINK_TYPE_6LOWPAN,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-
 static void
 _vlan_change_vlan_qos_mapping_create (gboolean is_ingress_map,
                                       gboolean reset_all,
@@ -8196,6 +8140,7 @@ ip4_address_add (NMPlatform *platform,
                  in_addr_t addr,
                  guint8 plen,
                  in_addr_t peer_addr,
+                 in_addr_t broadcast_address,
                  guint32 lifetime,
                  guint32 preferred,
                  guint32 flags,
@@ -8215,6 +8160,7 @@ ip4_address_add (NMPlatform *platform,
 	                             nm_utils_ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
 	                             lifetime,
 	                             preferred,
+	                             broadcast_address,
 	                             label);
 
 	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
@@ -8245,6 +8191,7 @@ ip6_address_add (NMPlatform *platform,
 	                             RT_SCOPE_UNIVERSE,
 	                             lifetime,
 	                             preferred,
+	                             0,
 	                             NULL);
 
 	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
@@ -8268,6 +8215,7 @@ ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 pl
 	                             RT_SCOPE_NOWHERE,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             0,
 	                             NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
@@ -8293,6 +8241,7 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, gui
 	                             RT_SCOPE_NOWHERE,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             0,
 	                             NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
@@ -8402,7 +8351,7 @@ ip_route_get (NMPlatform *platform,
 			.r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */,
 		};
 
-		g_clear_pointer (&route, nmp_object_unref);
+		nm_clear_pointer (&route, nmp_object_unref);
 
 		if (!_nl_addattr_l (&req.n, sizeof (req), RTA_DST, address, addr_len))
 			nm_assert_not_reached ();
@@ -8576,12 +8525,8 @@ tfilter_add (NMPlatform *platform,
 
 /*****************************************************************************/
 
-#define EVENT_CONDITIONS      ((GIOCondition) (G_IO_IN | G_IO_PRI))
-#define ERROR_CONDITIONS      ((GIOCondition) (G_IO_ERR | G_IO_NVAL))
-#define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP))
-
 static gboolean
-event_handler (GIOChannel *channel,
+event_handler (int fd,
                GIOCondition io_condition,
                gpointer user_data)
 {
@@ -8609,7 +8554,7 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 	nm_auto_free unsigned char *buf = NULL;
 
 continue_reading:
-	g_clear_pointer (&buf, free);
+	nm_clear_pointer (&buf, free);
 	n = nl_recv (sk, &nla, &buf, &creds, &creds_has);
 
 	if (n <= 0) {
@@ -8800,7 +8745,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 	int r;
 	struct pollfd pfd;
 	gboolean any = FALSE;
-	int timeout_ms;
+	int timeout_msec;
 	struct {
 		guint32 seq_number;
 		gint64 timeout_abs_ns;
@@ -8881,12 +8826,12 @@ after_read:
 
 		_LOGT ("netlink: read: wait for ACK for sequence number %u...", next.seq_number);
 
-		timeout_ms = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
+		timeout_msec = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NSEC_PER_SEC / 1000);
 
 		memset (&pfd, 0, sizeof (pfd));
 		pfd.fd = nl_socket_get_fd (priv->nlh);
 		pfd.events = POLLIN;
-		r = poll (&pfd, 1, MAX (1, timeout_ms));
+		r = poll (&pfd, 1, MAX (1, timeout_msec));
 
 		if (r == 0) {
 			/* timeout and there is nothing to read. */
@@ -9052,14 +8997,13 @@ constructed (GObject *_object)
 {
 	NMPlatform *platform = NM_PLATFORM (_object);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int channel_flags;
-	gboolean status;
 	int nle;
+	int fd;
 
 	nm_assert (!platform->_netns || platform->_netns == nmp_netns_get_current ());
 
 	if (nm_platform_get_use_udev (platform)) {
-		priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
+		priv->udev_client = nm_udev_client_new (NM_MAKE_STRV ("net"),
 		                                        handle_udev_event, platform);
 	}
 
@@ -9123,18 +9067,18 @@ constructed (GObject *_object)
 	                                 RTNLGRP_TC,
 	                                 0);
 	g_assert (!nle);
-	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
-	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh));
-	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
+	fd = nl_socket_get_fd (priv->nlh);
+
+	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), fd);
 
-	channel_flags = g_io_channel_get_flags (priv->event_channel);
-	status = g_io_channel_set_flags (priv->event_channel,
-	                                 channel_flags | G_IO_FLAG_NONBLOCK, NULL);
-	g_assert (status);
-	priv->event_id = g_io_add_watch (priv->event_channel,
-	                                (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
-	                                 event_handler, platform);
+	priv->event_source = nm_g_unix_fd_source_new (fd,
+	                                              G_IO_IN | G_IO_NVAL | G_IO_PRI | G_IO_ERR | G_IO_HUP,
+	                                              G_PRIORITY_DEFAULT,
+	                                              event_handler,
+	                                              platform,
+	                                              NULL);
+	g_source_attach (priv->event_source, NULL);
 
 	/* complete construction of the GObject instance before populating the cache. */
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->constructed (_object);
@@ -9227,8 +9171,8 @@ finalize (GObject *object)
 
 	nl_socket_free (priv->genl);
 
-	g_source_remove (priv->event_id);
-	g_io_channel_unref (priv->event_channel);
+	nm_clear_g_source_inst (&priv->event_source);
+
 	nl_socket_free (priv->nlh);
 
 	if (priv->sysctl_get_prev_values) {
@@ -9294,10 +9238,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->link_can_assume = link_can_assume;
 
-	platform_class->vlan_add = vlan_add;
 	platform_class->link_vlan_change = link_vlan_change;
 	platform_class->link_wireguard_change = link_wireguard_change;
-	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
 	platform_class->infiniband_partition_delete = infiniband_partition_delete;
@@ -9325,15 +9267,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wpan_set_short_addr = wpan_set_short_addr;
 	platform_class->wpan_set_channel = wpan_set_channel;
 
-	platform_class->link_gre_add = link_gre_add;
-	platform_class->link_ip6tnl_add = link_ip6tnl_add;
-	platform_class->link_ip6gre_add = link_ip6gre_add;
-	platform_class->link_macsec_add = link_macsec_add;
-	platform_class->link_macvlan_add = link_macvlan_add;
-	platform_class->link_ipip_add = link_ipip_add;
-	platform_class->link_sit_add = link_sit_add;
 	platform_class->link_tun_add = link_tun_add;
-	platform_class->link_6lowpan_add = link_6lowpan_add;
 
 	platform_class->object_delete = object_delete;
 	platform_class->ip4_address_add = ip4_address_add;
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index b55fbd66..98884cd7 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -262,7 +262,8 @@ out:
  * ethtool
  *****************************************************************************/
 
-NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
+static
+NM_UTILS_ENUM2STR_DEFINE (_ethtool_cmd_to_string, guint32,
 	NM_UTILS_ENUM2STR (ETHTOOL_GDRVINFO,   "ETHTOOL_GDRVINFO"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GFEATURES,  "ETHTOOL_GFEATURES"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GLINK,      "ETHTOOL_GLINK"),
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 6795dde7..9bbc3745 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -298,7 +298,8 @@ nm_platform_get_multi_idx (NMPlatform *self)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_ADD,     "add"),
 	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_CHANGE,  "change"),
@@ -1150,11 +1151,12 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
 /**
  * nm_platform_link_add:
  * @self: platform instance
- * @name: Interface name
  * @type: Interface type
- * @veth_peer: For veths, the peer name
+ * @name: Interface name
+ * @parent: the IFLA_LINK parameter or 0.
  * @address: (allow-none): set the mac address of the link
  * @address_len: the length of the @address
+ * @extra_data: depending on @type, additional data.
  * @out_link: on success, the link object
  *
  * Add a software interface.  If the interface already exists and is of type
@@ -1167,66 +1169,104 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
  *
  * Returns: the negative nm-error on failure.
  */
-static int
+int
 nm_platform_link_add (NMPlatform *self,
-                      const char *name,
                       NMLinkType type,
-                      const char *veth_peer,
+                      const char *name,
+                      int parent,
                       const void *address,
                       size_t address_len,
+                      gconstpointer extra_data,
                       const NMPlatformLink **out_link)
 {
 	int r;
 	char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
+	char parent_buf[64];
+	char buf[512];
 
 	_CHECK_SELF (self, klass, -NME_BUG);
 
 	g_return_val_if_fail (name, -NME_BUG);
 	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , -NME_BUG);
 	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, -NME_BUG);
-	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), -NME_BUG);
+	g_return_val_if_fail (parent >= 0, -NME_BUG);
 
 	r = _link_add_check_existing (self, name, type, out_link);
 	if (r < 0)
 		return r;
 
-	_LOG2D ("link: adding link: %s (%d)"
-	        "%s%s" /* address */
-	        "%s%s" /* veth peer */
+	_LOG2D ("link: adding link: "
+	        "%s "    /* type */
+	        "\"%s\"" /* name */
+	        "%s%s"   /* parent */
+	        "%s%s"   /* address */
+	        "%s"     /* extra_data */
 	        "",
 	        nm_link_type_to_string (type),
-	        (int) type,
+	        name,
+	        parent > 0 ? ", parent " : "",
+	        parent > 0 ? nm_sprintf_buf (parent_buf, "%d", parent) : "",
 	        address ? ", address: " : "",
 	        address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
-	        veth_peer ? ", veth-peer: " : "",
-	        veth_peer ?: "");
-
-	return klass->link_add (self, name, type, veth_peer, address, address_len, out_link);
-}
-
-int
-nm_platform_link_veth_add (NMPlatform *self,
-                            const char *name,
-                            const char *peer,
-                            const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_VETH, peer, NULL, 0, out_link);
-}
-
-/**
- * nm_platform_link_dummy_add:
- * @self: platform instance
- * @name: New interface name
- * @out_link: on success, the link object
- *
- * Create a software ethernet-like interface
- */
-int
-nm_platform_link_dummy_add (NMPlatform *self,
-                            const char *name,
-                            const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, NULL, 0, out_link);
+	        ({
+	            char *buf_p = buf;
+	            gsize buf_len = sizeof (buf);
+
+	            buf[0] = '\0';
+
+	            switch (type) {
+	            case NM_LINK_TYPE_VLAN:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_vlan_to_string ((const NMPlatformLnkVlan *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_VRF:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_vrf_to_string ((const NMPlatformLnkVrf *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_VXLAN:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_vxlan_to_string ((const NMPlatformLnkVxlan *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_VETH:
+	                nm_sprintf_buf (buf, ", veth-peer \"%s\"", (const char *) extra_data);
+	                break;
+	            case NM_LINK_TYPE_GRE:
+	            case NM_LINK_TYPE_GRETAP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_gre_to_string ((const NMPlatformLnkGre *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_SIT:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_sit_to_string ((const NMPlatformLnkSit *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_IP6TNL:
+	            case NM_LINK_TYPE_IP6GRE:
+	            case NM_LINK_TYPE_IP6GRETAP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_ip6tnl_to_string ((const NMPlatformLnkIp6Tnl *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_IPIP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_ipip_to_string ((const NMPlatformLnkIpIp *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_MACSEC:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_macsec_to_string ((const NMPlatformLnkMacsec *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_MACVLAN:
+	            case NM_LINK_TYPE_MACVTAP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_macvlan_to_string ((const NMPlatformLnkMacvlan *) extra_data, buf_p, buf_len);
+	                break;
+	            default:
+	                nm_assert (!extra_data);
+	                break;
+	            }
+
+	            buf;
+	        }));
+
+	return klass->link_add (self, type, name, parent, address, address_len, extra_data, out_link);
 }
 
 /**
@@ -1283,7 +1323,7 @@ nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
 }
 
 const char *
-nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */)
+nm_platform_if_indextoname (NMPlatform *self, int ifindex, char out_ifname[static 16 /* IFNAMSIZ */])
 {
 	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
 
@@ -1408,7 +1448,7 @@ nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanag
 gboolean
 nm_platform_link_is_software (NMPlatform *self, int ifindex)
 {
-	return (nm_platform_link_get_type (self, ifindex) & 0x10000);
+	return nm_link_type_is_software (nm_platform_link_get_type (self, ifindex));
 }
 
 /**
@@ -1422,7 +1462,7 @@ nm_platform_link_is_software (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
 {
-	return (nm_platform_link_get_type (self, ifindex) & 0x20000);
+	return nm_link_type_supports_slaves (nm_platform_link_get_type (self, ifindex));
 }
 
 /**
@@ -2193,7 +2233,7 @@ nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatfor
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVLAN, out_link);
 }
 
-const NMPlatformLnkMacvtap *
+const NMPlatformLnkMacvlan *
 nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVTAP, out_link);
@@ -2217,6 +2257,12 @@ nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLi
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VLAN, out_link);
 }
 
+const NMPlatformLnkVrf *
+nm_platform_link_get_lnk_vrf (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VRF, out_link);
+}
+
 const NMPlatformLnkVxlan *
 nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -2231,7 +2277,8 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
 
 /*****************************************************************************/
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_flags_to_string, NMPlatformWireGuardChangeFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_wireguard_change_flags_to_string, NMPlatformWireGuardChangeFlags,
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE,            "none"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS,   "replace-peers"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY, "has-private-key"),
@@ -2239,7 +2286,8 @@ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_flags_to_string, NMPlatformW
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK,      "has-fwmark"),
 );
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_peer_flags_to_string, NMPlatformWireGuardChangePeerFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_wireguard_change_peer_flags_to_string, NMPlatformWireGuardChangePeerFlags,
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE,                   "none"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME,              "remove"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY,      "psk"),
@@ -2250,14 +2298,6 @@ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_peer_flags_to_string, NMPlat
 );
 
 int
-nm_platform_link_wireguard_add (NMPlatform *self,
-                                const char *name,
-                                const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_WIREGUARD, NULL, NULL, 0, out_link);
-}
-
-int
 nm_platform_link_wireguard_change (NMPlatform *self,
                                    int ifindex,
                                    const NMPlatformLnkWireGuard *lnk_wireguard,
@@ -2316,128 +2356,6 @@ nm_platform_link_wireguard_change (NMPlatform *self,
 /*****************************************************************************/
 
 /**
- * nm_platform_link_bridge_add:
- * @self: platform instance
- * @name: New interface name
- * @address: (allow-none): set the mac address of the new bridge
- * @address_len: the length of the @address
- * @out_link: on success, the link object
- *
- * Create a software bridge.
- */
-int
-nm_platform_link_bridge_add (NMPlatform *self,
-                             const char *name,
-                             const void *address,
-                             size_t address_len,
-                             const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, NULL, address, address_len, out_link);
-}
-
-/**
- * nm_platform_link_bond_add:
- * @self: platform instance
- * @name: New interface name
- * @out_link: on success, the link object
- *
- * Create a software bonding device.
- */
-int
-nm_platform_link_bond_add (NMPlatform *self,
-                           const char *name,
-                           const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, NULL, 0, out_link);
-}
-
-/**
- * nm_platform_link_team_add:
- * @self: platform instance
- * @name: New interface name
- * @out_link: on success, the link object
- *
- * Create a software teaming device.
- */
-int
-nm_platform_link_team_add (NMPlatform *self,
-                           const char *name,
-                           const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, NULL, 0, out_link);
-}
-
-/**
- * nm_platform_link_vlan_add:
- * @self: platform instance
- * @name: New interface name
- * @vlanid: VLAN identifier
- * @vlanflags: VLAN flags from libnm
- * @out_link: on success, the link object
- *
- * Create a software VLAN device.
- */
-int
-nm_platform_link_vlan_add (NMPlatform *self,
-                           const char *name,
-                           int parent,
-                           int vlanid,
-                           guint32 vlanflags,
-                           const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (parent >= 0, -NME_BUG);
-	g_return_val_if_fail (vlanid >= 0, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("link: adding link vlan parent %d vlanid %d vlanflags %x",
-	        parent, vlanid, vlanflags);
-
-	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_link_vxlan_add:
- * @self: platform instance
- * @name: New interface name
- * @props: properties of the new link
- * @out_link: on success, the link object
- *
- * Create a VXLAN device.
- */
-int
-nm_platform_link_vxlan_add (NMPlatform *self,
-                            const char *name,
-                            const NMPlatformLnkVxlan *props,
-                            const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("link: adding link %s", nm_platform_lnk_vxlan_to_string (props, NULL, 0));
-
-	if (!klass->link_vxlan_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
  * nm_platform_link_tun_add:
  * @self: platform instance
  * @name: new interface name
@@ -2489,38 +2407,6 @@ nm_platform_link_tun_add (NMPlatform *self,
 	return 0;
 }
 
-/**
- * nm_platform_6lowpan_add:
- * @self: platform instance
- * @parent: parent link
- * @name: name of the new interface
- * @out_link: on success, the link object
- *
- * Create a 6LoWPAN interface.
- */
-int
-nm_platform_link_6lowpan_add (NMPlatform *self,
-                              const char *name,
-                              int parent,
-                              const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link 6lowpan parent %u", parent);
-
-	if (!klass->link_6lowpan_add (self, name, parent, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
 gboolean
 nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent)
 {
@@ -2761,39 +2647,6 @@ nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, i
 	return nm_platform_link_vlan_change (self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1);
 }
 
-/**
- * nm_platform_link_gre_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a software GRE device.
- */
-int
-nm_platform_link_gre_add (NMPlatform *self,
-                          const char *name,
-                          const NMPlatformLnkGre *props,
-                          const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_gre_to_string (props, NULL, 0));
-
-	if (!klass->link_gre_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
 static int
 _infiniband_add_add_or_delete (NMPlatform *self,
                                int ifindex,
@@ -2918,217 +2771,6 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
 	return TRUE;
 }
 
-/**
- * nm_platform_ip6tnl_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create an IPv6 tunnel.
- */
-int
-nm_platform_link_ip6tnl_add (NMPlatform *self,
-                             const char *name,
-                             const NMPlatformLnkIp6Tnl *props,
-                             const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-	g_return_val_if_fail (!props->is_gre, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
-
-	if (!klass->link_ip6tnl_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_ip6gre_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create an IPv6 GRE/GRETAP tunnel.
- */
-int
-nm_platform_link_ip6gre_add (NMPlatform *self,
-                             const char *name,
-                             const NMPlatformLnkIp6Tnl *props,
-                             const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-	g_return_val_if_fail (props->is_gre, -NME_BUG);
-
-	r = _link_add_check_existing (self,
-	                              name,
-	                              props->is_tap
-	                                  ? NM_LINK_TYPE_IP6GRETAP
-	                                  : NM_LINK_TYPE_IP6GRE,
-	                              out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
-
-	if (!klass->link_ip6gre_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_ipip_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create an IPIP tunnel.
- */
-int
-nm_platform_link_ipip_add (NMPlatform *self,
-                           const char *name,
-                           const NMPlatformLnkIpIp *props,
-                           const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_ipip_to_string (props, NULL, 0));
-
-	if (!klass->link_ipip_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_macsec_add:
- * @self: platform instance
- * @name: name of the new interface
- * @parent: parent link
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a MACsec interface.
- */
-int
-nm_platform_link_macsec_add (NMPlatform *self,
-                             const char *name,
-                             int parent,
-                             const NMPlatformLnkMacsec *props,
-                             const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_macsec_to_string (props, NULL, 0));
-
-	if (!klass->link_macsec_add (self, name, parent, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_macvlan_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a MACVLAN or MACVTAP device.
- */
-int
-nm_platform_link_macvlan_add (NMPlatform *self,
-                              const char *name,
-                              int parent,
-                              const NMPlatformLnkMacvlan *props,
-                              const NMPlatformLink **out_link)
-{
-	int r;
-	NMLinkType type;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	type = props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
-
-	r = _link_add_check_existing (self, name, type, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_macvlan_to_string (props, NULL, 0));
-
-	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_sit_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a software SIT device.
- */
-int
-nm_platform_link_sit_add (NMPlatform *self,
-                          const char *name,
-                          const NMPlatformLnkSit *props,
-                          const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_sit_to_string (props, NULL, 0));
-
-	if (!klass->link_sit_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
 gboolean
 nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_peer_ifindex)
 {
@@ -3675,6 +3317,7 @@ nm_platform_ip4_address_add (NMPlatform *self,
                              in_addr_t address,
                              guint8 plen,
                              in_addr_t peer_address,
+                             in_addr_t broadcast_address,
                              guint32 lifetime,
                              guint32 preferred,
                              guint32 flags,
@@ -3689,22 +3332,26 @@ nm_platform_ip4_address_add (NMPlatform *self,
 	g_return_val_if_fail (!label || strlen (label) < sizeof (((NMPlatformIP4Address *) NULL)->label), FALSE);
 
 	if (_LOGD_ENABLED ()) {
-		NMPlatformIP4Address addr = { 0 };
-
-		addr.ifindex = ifindex;
-		addr.address = address;
-		addr.peer_address = peer_address;
-		addr.plen = plen;
-		addr.timestamp = 0; /* set it at zero, which to_string will treat as *now* */
-		addr.lifetime = lifetime;
-		addr.preferred = preferred;
-		addr.n_ifa_flags = flags;
+		NMPlatformIP4Address addr;
+
+		addr = (NMPlatformIP4Address) {
+			.ifindex                   = ifindex,
+			.address                   = address,
+			.peer_address              = peer_address,
+			.plen                      = plen,
+			.timestamp                 = 0, /* set it at zero, which to_string will treat as *now* */
+			.lifetime                  = lifetime,
+			.preferred                 = preferred,
+			.n_ifa_flags               = flags,
+			.broadcast_address         = broadcast_address,
+			.use_ip4_broadcast_address = TRUE,
+		};
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
 		_LOG3D ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags, label);
+	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, broadcast_address, lifetime, preferred, flags, label);
 }
 
 gboolean
@@ -3755,12 +3402,12 @@ nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address
 	g_return_val_if_fail (plen <= 32, FALSE);
 
 	_LOG3D ("address: deleting IPv4 address %s/%d, %s%s",
-	        nm_utils_inet4_ntop (address, b1),
+	        _nm_utils_inet4_ntop (address, b1),
 	        plen,
 	        peer_address != address
 	            ? nm_sprintf_buf (str_peer,
 	                              "peer %s, ",
-	                              nm_utils_inet4_ntop (peer_address, b2))
+	                              _nm_utils_inet4_ntop (peer_address, b2))
 	            : "",
 	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
@@ -3778,7 +3425,7 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 	g_return_val_if_fail (plen <= 128, FALSE);
 
 	_LOG3D ("address: deleting IPv6 address %s/%d, %s",
-	        nm_utils_inet6_ntop (&address, sbuf), plen,
+	        _nm_utils_inet6_ntop (&address, sbuf), plen,
 	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip6_address_delete (self, ifindex, address, plen);
 }
@@ -4048,7 +3695,7 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 {
 	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
 	const NMPlatformIP4Address *known_address;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	GHashTable *plat_subnets = NULL;
 	GHashTable *known_subnets = NULL;
 	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
@@ -4163,8 +3810,14 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 		if (!lifetime)
 			goto delete_and_next2;
 
-		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
-		                                  known_address->peer_address, lifetime, preferred,
+		if (!nm_platform_ip4_address_add (self,
+		                                  ifindex,
+		                                  known_address->address,
+		                                  known_address->plen,
+		                                  known_address->peer_address,
+		                                  nm_platform_ip4_broadcast_address_from_addr (known_address),
+		                                  lifetime,
+		                                  preferred,
 		                                  ifa_flags,
 		                                  known_address->label))
 			goto delete_and_next2;
@@ -4178,23 +3831,39 @@ delete_and_next2:
 	return TRUE;
 }
 
-static guint
-ip6_address_scope_priority (const struct in6_addr *addr)
+typedef enum {
+	IP6_ADDR_SCOPE_LOOPBACK,
+	IP6_ADDR_SCOPE_LINKLOCAL,
+	IP6_ADDR_SCOPE_SITELOCAL,
+	IP6_ADDR_SCOPE_OTHER,
+} IP6AddrScope;
+
+static IP6AddrScope
+ip6_address_scope (const NMPlatformIP6Address *a)
 {
-	if (IN6_IS_ADDR_LINKLOCAL (addr))
-		return 1;
-	if (IN6_IS_ADDR_SITELOCAL (addr))
-		return 2;
-	return 3;
+	if (IN6_IS_ADDR_LOOPBACK (&a->address))
+		return IP6_ADDR_SCOPE_LOOPBACK;
+	if (IN6_IS_ADDR_LINKLOCAL (&a->address))
+		return IP6_ADDR_SCOPE_LINKLOCAL;
+	if (IN6_IS_ADDR_SITELOCAL (&a->address))
+		return IP6_ADDR_SCOPE_SITELOCAL;
+	return IP6_ADDR_SCOPE_OTHER;
 }
 
 static int
-ip6_address_scope_cmp (gconstpointer a, gconstpointer b)
+ip6_address_scope_cmp (gconstpointer p_a, gconstpointer p_b, gpointer increasing)
 {
-	const NMPlatformIP6Address *x = NMP_OBJECT_CAST_IP6_ADDRESS (*(const void **) a);
-	const NMPlatformIP6Address *y = NMP_OBJECT_CAST_IP6_ADDRESS (*(const void **) b);
+	const NMPlatformIP6Address *a;
+	const NMPlatformIP6Address *b;
 
-	return ip6_address_scope_priority (&x->address) - ip6_address_scope_priority (&y->address);
+	if (!increasing)
+		NM_SWAP (p_a, p_b);
+
+	a = NMP_OBJECT_CAST_IP6_ADDRESS (*(const NMPObject *const*) p_a);
+	b = NMP_OBJECT_CAST_IP6_ADDRESS (*(const NMPObject *const*) p_b);
+
+	NM_CMP_DIRECT (ip6_address_scope (a), ip6_address_scope (b));
+	return 0;
 }
 
 /**
@@ -4224,7 +3893,7 @@ nm_platform_ip6_address_sync (NMPlatform *self,
                               gboolean full_sync)
 {
 	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	guint i_plat, i_know;
 	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
 	NMPLookup lookup;
@@ -4235,7 +3904,7 @@ nm_platform_ip6_address_sync (NMPlatform *self,
 	 * apply the same sorting to known addresses, so that we don't try to
 	 * unnecessary change the order of addresses with different scopes. */
 	if (known_addresses)
-		g_ptr_array_sort (known_addresses, ip6_address_scope_cmp);
+		g_ptr_array_sort_with_data (known_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (TRUE));
 
 	if (!_addr_array_clean_expired (AF_INET6, ifindex, known_addresses, now, &known_addresses_idx))
 		known_addresses = NULL;
@@ -4250,6 +3919,10 @@ nm_platform_ip6_address_sync (NMPlatform *self,
 
 	if (plat_addresses) {
 		guint known_addresses_len;
+		IP6AddrScope cur_scope;
+		gboolean delete_remaining_addrs;
+
+		g_ptr_array_sort_with_data (plat_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (FALSE));
 
 		known_addresses_len = known_addresses ? known_addresses->len : 0;
 
@@ -4301,38 +3974,54 @@ clear_and_next:
 		 * selection will choose addresses in the order as they are reported by kernel.
 		 * Note that the order in @plat_addresses of the remaining matches is highest
 		 * priority first.
-		 * We need to compare this to the order in @known_addresses (which has lowest
-		 * priority first).
+		 * We need to compare this to the order of addresses with same scope in
+		 * @known_addresses (which has lowest priority first).
 		 *
 		 * If we find a first discrepancy, we need to delete all remaining addresses
-		 * from that point on, because below we must re-add all the addresses in the
-		 * right order to get their priority right. */
+		 * with same scope from that point on, because below we must re-add all the
+		 * addresses in the right order to get their priority right. */
+		cur_scope = IP6_ADDR_SCOPE_LOOPBACK;
+		delete_remaining_addrs = FALSE;
 		i_plat = plat_addresses->len;
 		i_know = 0;
 		while (i_plat > 0) {
 			const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]);
+			IP6AddrScope plat_scope;
 
 			if (!plat_addr)
 				continue;
 
-			for (; i_know < known_addresses_len; i_know++) {
-				const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
+			plat_scope = ip6_address_scope (plat_addr);
+			if (cur_scope != plat_scope) {
+				nm_assert (cur_scope < plat_scope);
+				delete_remaining_addrs = FALSE;
+				cur_scope = plat_scope;
+			}
 
-				if (!know_addr)
-					continue;
+			if (!delete_remaining_addrs) {
+				delete_remaining_addrs = TRUE;
+				for (; i_know < known_addresses_len; i_know++) {
+					const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
+					IP6AddrScope know_scope;
 
-				if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) {
-					/* we have a match. Mark address as handled. */
-					i_know++;
-					goto next_plat;
-				}
+					if (!know_addr)
+						continue;
 
-				/* all remainging addresses need to be removed as well, so that we can
-				 * re-add them in the correct order. Signal that, by setting @i_know
-				 * so that the next @i_plat iteration, we won't enter the loop and
-				 * delete the address right away */
-				i_know = known_addresses_len;
-				break;
+					know_scope = ip6_address_scope (know_addr);
+					if (know_scope < plat_scope)
+						continue;
+
+					if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) {
+						/* we have a match. Mark address as handled. */
+						i_know++;
+						delete_remaining_addrs = FALSE;
+						goto next_plat;
+					}
+
+					/* plat_address has no match. Now delete_remaining_addrs is TRUE and we will
+					 * delete all the remaining addresses with cur_scope. */
+					break;
+				}
 			}
 
 			nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen);
@@ -4965,15 +4654,15 @@ nm_platform_ip_route_get (NMPlatform *self,
 #define IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S ((int) (((IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS + 999) * 3) / 1000))
 
 static gint64
-_ip4_dev_route_blacklist_timeout_ms_get (gint64 timeout_ms)
+_ip4_dev_route_blacklist_timeout_ms_get (gint64 timeout_msec)
 {
-	return timeout_ms >> 1;
+	return timeout_msec >> 1;
 }
 
 static gint64
-_ip4_dev_route_blacklist_timeout_ms_marked (gint64 timeout_ms)
+_ip4_dev_route_blacklist_timeout_ms_marked (gint64 timeout_msec)
 {
-	return !!(timeout_ms & ((gint64) 1));
+	return !!(timeout_msec & ((gint64) 1));
 }
 
 static gboolean
@@ -4992,7 +4681,7 @@ again:
 	if (!priv->ip4_dev_route_blacklist_hash)
 		goto out;
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 
 	g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
@@ -5055,7 +4744,7 @@ _ip4_dev_route_blacklist_notify_route (NMPlatform *self,
 	                                   (gpointer *) &p_timeout_ms))
 		return;
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 	if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get (*p_timeout_ms)) {
 		/* already expired. Wait for gc. */
 		return;
@@ -5086,7 +4775,7 @@ _ip4_dev_route_blacklist_gc_timeout_handle (gpointer user_data)
 
 	nm_assert (priv->ip4_dev_route_blacklist_gc_timeout_id);
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 
 	g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
@@ -5108,7 +4797,7 @@ _ip4_dev_route_blacklist_schedule (NMPlatform *self)
 
 	if (   !priv->ip4_dev_route_blacklist_hash
 	    || g_hash_table_size (priv->ip4_dev_route_blacklist_hash) == 0) {
-		g_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
+		nm_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
 		nm_clear_g_source (&priv->ip4_dev_route_blacklist_gc_timeout_id);
 	} else {
 		if (!priv->ip4_dev_route_blacklist_gc_timeout_id) {
@@ -5155,8 +4844,8 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 	GHashTableIter iter;
 	const NMPObject *p_obj;
 	guint i;
-	gint64 timeout_ms;
-	gint64 timeout_ms_val;
+	gint64 timeout_msec;
+	gint64 timeout_msec_val;
 	gint64 *p_timeout_ms;
 	gboolean needs_check = FALSE;
 
@@ -5191,8 +4880,8 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 			                                                            nm_g_slice_free_fcn_gint64);
 		}
 
-		timeout_ms = nm_utils_get_monotonic_timestamp_ms () + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS;
-		timeout_ms_val = (timeout_ms << 1) | ((gint64) 1);
+		timeout_msec = nm_utils_get_monotonic_timestamp_msec () + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS;
+		timeout_msec_val = (timeout_msec << 1) | ((gint64) 1);
 		for (i = 0; i < ip4_dev_route_blacklist->len; i++) {
 			const NMPObject *o;
 
@@ -5206,7 +4895,7 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 					/* un-expire and reuse the entry. */
 					_LOGT ("ip4-dev-route: register %s (update)",
 					       nmp_object_to_string (p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-					*p_timeout_ms = timeout_ms_val;
+					*p_timeout_ms = timeout_msec_val;
 					continue;
 				}
 			}
@@ -5214,7 +4903,7 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 			_LOGT ("ip4-dev-route: register %s",
 			       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 			p_timeout_ms = g_slice_new (gint64);
-			*p_timeout_ms = timeout_ms_val;
+			*p_timeout_ms = timeout_msec_val;
 			g_hash_table_replace (priv->ip4_dev_route_blacklist_hash,
 			                      (gpointer) nmp_object_ref (o),
 			                      p_timeout_ms);
@@ -5605,16 +5294,16 @@ nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len
 	            "%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->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) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
 	            lnk->path_mtu_discovery ? "" : " nopmtudisc",
 	            lnk->input_flags ? nm_sprintf_buf (str_input_flags, " iflags 0x%x", lnk->input_flags) : "",
 	            lnk->output_flags ? nm_sprintf_buf (str_output_flags, " oflags 0x%x", lnk->output_flags) : "",
-	            NM_FLAGS_HAS (lnk->input_flags, GRE_KEY) || lnk->input_key ? nm_sprintf_buf (str_input_key, " ikey %s", nm_utils_inet4_ntop (lnk->input_key, str_input_key1)) : "",
-	            NM_FLAGS_HAS (lnk->output_flags, GRE_KEY) || lnk->output_key ? nm_sprintf_buf (str_output_key, " okey %s", nm_utils_inet4_ntop (lnk->output_key, str_output_key1)) : "");
+	            NM_FLAGS_HAS (lnk->input_flags, GRE_KEY) || lnk->input_key ? nm_sprintf_buf (str_input_key, " ikey %s", _nm_utils_inet4_ntop (lnk->input_key, str_input_key1)) : "",
+	            NM_FLAGS_HAS (lnk->output_flags, GRE_KEY) || lnk->output_key ? nm_sprintf_buf (str_output_key, " okey %s", _nm_utils_inet4_ntop (lnk->output_key, str_output_key1)) : "");
 	return buf;
 }
 
@@ -5673,8 +5362,8 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            " flags 0x%x"
 	            "",
 	            str_type,
-	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
-	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
+	            nm_sprintf_buf (str_remote, " remote %s", _nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
+	            nm_sprintf_buf (str_local, " local %s", _nm_utils_inet6_ntop (&lnk->local, str_local1)),
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass),
@@ -5708,8 +5397,8 @@ nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *buf, gsize l
 	            "%s" /* tos */
 	            "%s" /* path_mtu_discovery */
 	            "",
-	            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->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) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
@@ -5791,8 +5480,8 @@ nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len
 	            "%s" /* flags */
 	            "%s" /* proto */
 	            "",
-	            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->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) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
@@ -5856,6 +5545,20 @@ nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize l
 }
 
 const char *
+nm_platform_lnk_vrf_to_string (const NMPlatformLnkVrf *lnk, char *buf, gsize len)
+{
+	char *b;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	b = buf;
+
+	nm_utils_strbuf_append (&b, &len, "table %u", lnk->table);
+	return buf;
+}
+
+const char *
 nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len)
 {
 	char str_group[100];
@@ -5879,7 +5582,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_group, sizeof (str_group),
 		            " %s %s",
 		            IN_MULTICAST (ntohl (lnk->group)) ? "group" : "remote",
-		            nm_utils_inet4_ntop (lnk->group, sbuf));
+		            _nm_utils_inet4_ntop (lnk->group, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->group6))
 		str_group6[0] = '\0';
@@ -5888,7 +5591,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		            " %s%s %s",
 		            IN6_IS_ADDR_MULTICAST (&lnk->group6) ? "group" : "remote",
 		            str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->group6, sbuf));
+		            _nm_utils_inet6_ntop (&lnk->group6, sbuf));
 	}
 
 	if (lnk->local == 0)
@@ -5896,7 +5599,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	else {
 		g_snprintf (str_local, sizeof (str_local),
 		            " local %s",
-		            nm_utils_inet4_ntop (lnk->local, sbuf));
+		            _nm_utils_inet4_ntop (lnk->local, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->local6))
 		str_local6[0] = '\0';
@@ -5904,7 +5607,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_local6, sizeof (str_local6),
 		            " local%s %s",
 		            str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->local6, sbuf));
+		            _nm_utils_inet6_ntop (&lnk->local6, sbuf));
 	}
 
 	g_snprintf (buf, len,
@@ -6054,7 +5757,9 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	char str_lft[30], str_pref[30], str_time[50], s_source[50];
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
+	in_addr_t broadcast_address;
+	char str_broadcast[INET_ADDRSTRLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
 		return buf;
@@ -6083,9 +5788,27 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	                                      now, str_pref, sizeof (str_pref)) );
 	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
 
+	broadcast_address = nm_platform_ip4_broadcast_address_from_addr (address);
+
 	g_snprintf (buf, len,
-	            "%s/%d lft %s pref %s%s%s%s%s%s src %s%s",
-	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
+	            "%s/%d"
+	            "%s%s" /* broadcast */
+	            " lft %s"
+	            " pref %s"
+	            "%s" /* time */
+	            "%s" /* peer  */
+	            "%s" /* dev */
+	            "%s" /* flags */
+	            "%s" /* label */
+	            " src %s"
+	            "%s" /* external */
+	            "",
+	            s_address, address->plen,
+	            broadcast_address ? " brd " : "",
+	            broadcast_address ? nm_utils_inet4_ntop (broadcast_address, str_broadcast) : "",
+	            str_lft_p,
+	            str_pref_p,
+	            str_time_p,
 	            str_peer ?: "",
 	            str_dev,
 	            _to_string_ifa_flags (address->n_ifa_flags, s_flags, sizeof (s_flags)),
@@ -6167,7 +5890,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 
 	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
 		return buf;
@@ -6203,7 +5926,8 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	return buf;
 }
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_rtm_flags_to_string, unsigned,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_rtm_flags_to_string, unsigned,
 	NM_UTILS_FLAGS2STR (RTNH_F_DEAD,                   "dead"),
 	NM_UTILS_FLAGS2STR (RTNH_F_PERVASIVE,              "pervasive"),
 	NM_UTILS_FLAGS2STR (RTNH_F_ONLINK,                 "onlink"),
@@ -6380,7 +6104,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            route->mss,
 	            nmp_utils_ip_config_source_to_string (route->rt_source, s_source, sizeof (s_source)),
 	            route->src_plen || !IN6_IS_ADDR_UNSPECIFIED (&route->src)
-	              ? nm_sprintf_buf (s_src_all, " src %s/%u", nm_utils_inet6_ntop (&route->src, s_src), (unsigned) route->src_plen)
+	              ? nm_sprintf_buf (s_src_all, " src %s/%u", _nm_utils_inet6_ntop (&route->src, s_src), (unsigned) route->src_plen)
 	              : "",
 	            _rtm_flags_to_string_full (str_rtm_flags, sizeof (str_rtm_flags), route->r_rtm_flags),
 	            s_pref_src[0] ? " pref-src " : "",
@@ -7222,6 +6946,21 @@ nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b
 }
 
 void
+nm_platform_lnk_vrf_hash_update (const NMPlatformLnkVrf *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->table);
+}
+
+int
+nm_platform_lnk_vrf_cmp (const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, table);
+	return 0;
+}
+
+void
 nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h)
 {
 	nm_hash_update_vals (h,
@@ -7298,6 +7037,7 @@ nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashStat
 	nm_hash_update_vals (h,
 	                     obj->ifindex,
 	                     obj->addr_source,
+	                     nm_platform_ip4_broadcast_address_from_addr (obj),
 	                     obj->timestamp,
 	                     obj->lifetime,
 	                     obj->preferred,
@@ -7317,6 +7057,7 @@ nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4A
 	NM_CMP_FIELD (a, b, address);
 	NM_CMP_FIELD (a, b, plen);
 	NM_CMP_FIELD (a, b, peer_address);
+	NM_CMP_DIRECT (nm_platform_ip4_broadcast_address_from_addr (a), nm_platform_ip4_broadcast_address_from_addr (b));
 	NM_CMP_FIELD (a, b, addr_source);
 	NM_CMP_FIELD (a, b, timestamp);
 	NM_CMP_FIELD (a, b, lifetime);
@@ -8238,7 +7979,7 @@ finalize (GObject *object)
 
 	nm_clear_g_source (&priv->ip4_dev_route_blacklist_check_id);
 	nm_clear_g_source (&priv->ip4_dev_route_blacklist_gc_timeout_id);
-	g_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
+	nm_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
 	g_clear_object (&self->_netns);
 	nm_dedup_multi_index_unref (priv->multi_idx);
 	nmp_cache_free (priv->cache);
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 4bd8e34d..7edaaf58 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -287,7 +287,7 @@ typedef enum {
 	 * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly
 	 *   set to 0). Such addresses are permanent.
 	 * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
-	 *   and never returned by nm_utils_get_monotonic_timestamp_s(). In this case @valid/@preferred
+	 *   and never returned by nm_utils_get_monotonic_timestamp_sec(). In this case @valid/@preferred
 	 *   is anchored at @timestamp.
 	 * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time
 	 *   moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add().
@@ -308,6 +308,8 @@ typedef enum {
 	guint8 plen; \
 	\
 	bool external:1; \
+	\
+	bool use_ip4_broadcast_address:1; \
 	;
 
 /**
@@ -325,7 +327,7 @@ typedef struct {
 
 /**
  * NMPlatformIP4Address:
- * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_s()
+ * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
  **/
 struct _NMPlatformIP4Address {
 	__NMPlatformIPAddress_COMMON;
@@ -344,12 +346,18 @@ struct _NMPlatformIP4Address {
 	 * */
 	in_addr_t peer_address;  /* PTP peer address */
 
+	/* IFA_BROADCAST.
+	 *
+	 * This parameter is ignored unless use_ip4_broadcast_address is TRUE.
+	 * See nm_platform_ip4_broadcast_address_from_addr(). */
+	in_addr_t broadcast_address;
+
 	char label[NMP_IFNAMSIZ];
 };
 
 /**
  * NMPlatformIP6Address:
- * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_s()
+ * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
  **/
 struct _NMPlatformIP6Address {
 	__NMPlatformIPAddress_COMMON;
@@ -795,8 +803,6 @@ typedef struct {
 	bool tap:1;
 } NMPlatformLnkMacvlan;
 
-typedef NMPlatformLnkMacvlan NMPlatformLnkMacvtap;
-
 typedef struct {
 	in_addr_t local;
 	in_addr_t remote;
@@ -830,6 +836,10 @@ typedef struct {
 } NMPlatformLnkVlan;
 
 typedef struct {
+	guint32 table;
+} NMPlatformLnkVrf;
+
+typedef struct {
 	struct in6_addr group6;
 	struct in6_addr local6;
 	in_addr_t group;
@@ -965,11 +975,12 @@ typedef struct {
 	void (*process_events) (NMPlatform *self);
 
 	int (*link_add) (NMPlatform *self,
-	                 const char *name,
 	                 NMLinkType type,
-	                 const char *veth_peer,
+	                 const char *name,
+	                 int parent,
 	                 const void *address,
 	                 size_t address_len,
+	                 gconstpointer extra_data,
 	                 const NMPlatformLink **out_link);
 	gboolean (*link_delete) (NMPlatform *self, int ifindex);
 	gboolean (*link_refresh) (NMPlatform *self, int ifindex);
@@ -1028,7 +1039,6 @@ typedef struct {
 	                              guint peers_len,
 	                              NMPlatformWireGuardChangeFlags change_flags);
 
-	gboolean (*vlan_add) (NMPlatform *self, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
 	                              int ifindex,
 	                              NMVlanFlags flags_mask,
@@ -1039,49 +1049,11 @@ typedef struct {
 	                              gboolean egress_reset_all,
 	                              const NMVlanQosMapping *egress_map,
 	                              gsize n_egress_map);
-	gboolean (*link_vxlan_add) (NMPlatform *self,
-	                            const char *name,
-	                            const NMPlatformLnkVxlan *props,
-	                            const NMPlatformLink **out_link);
-	gboolean (*link_gre_add) (NMPlatform *self,
-	                          const char *name,
-	                          const NMPlatformLnkGre *props,
-	                          const NMPlatformLink **out_link);
-	gboolean (*link_ip6tnl_add) (NMPlatform *self,
-	                             const char *name,
-	                             const NMPlatformLnkIp6Tnl *props,
-	                             const NMPlatformLink **out_link);
-	gboolean (*link_ip6gre_add) (NMPlatform *self,
-	                             const char *name,
-	                             const NMPlatformLnkIp6Tnl *props,
-	                             const NMPlatformLink **out_link);
-	gboolean (*link_ipip_add) (NMPlatform *self,
-	                           const char *name,
-	                           const NMPlatformLnkIpIp *props,
-	                           const NMPlatformLink **out_link);
-	gboolean (*link_macsec_add) (NMPlatform *self,
-	                             const char *name,
-	                             int parent,
-	                             const NMPlatformLnkMacsec *props,
-	                             const NMPlatformLink **out_link);
-	gboolean (*link_macvlan_add) (NMPlatform *self,
-	                              const char *name,
-	                              int parent,
-	                              const NMPlatformLnkMacvlan *props,
-	                              const NMPlatformLink **out_link);
-	gboolean (*link_sit_add) (NMPlatform *self,
-	                          const char *name,
-	                          const NMPlatformLnkSit *props,
-	                          const NMPlatformLink **out_link);
 	gboolean (*link_tun_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkTun *props,
 	                          const NMPlatformLink **out_link,
 	                          int *out_fd);
-	gboolean (*link_6lowpan_add) (NMPlatform *self,
-	                              const char *name,
-	                              int parent,
-	                              const NMPlatformLink **out_link);
 
 	gboolean (*infiniband_partition_add) (NMPlatform *self, int parent, int p_key, const NMPlatformLink **out_link);
 	gboolean (*infiniband_partition_delete) (NMPlatform *self, int parent, int p_key);
@@ -1116,6 +1088,7 @@ typedef struct {
 	                             in_addr_t address,
 	                             guint8 plen,
 	                             in_addr_t peer_address,
+	                             in_addr_t broadcast_address,
 	                             guint32 lifetime,
 	                             guint32 preferred_lft,
 	                             guint32 flags,
@@ -1187,6 +1160,31 @@ NMPlatform *nm_platform_get (void);
 
 /*****************************************************************************/
 
+static inline in_addr_t
+nm_platform_ip4_broadcast_address_create (in_addr_t address,
+                                          guint8 plen)
+{
+	return address | ~_nm_utils_ip4_prefix_to_netmask (plen);
+}
+
+static inline in_addr_t
+nm_platform_ip4_broadcast_address_from_addr (const NMPlatformIP4Address *addr)
+{
+	nm_assert (addr);
+
+	if (addr->use_ip4_broadcast_address)
+		return addr->broadcast_address;
+
+	/* the set broadcast-address gets ignored, and we determine a default brd base
+	 * on the peer IFA_ADDRESS. */
+	if (   addr->peer_address != 0u
+	    && addr->plen < 31 /* RFC3021 */)
+		return nm_platform_ip4_broadcast_address_create (addr->peer_address, addr->plen);
+	return 0u;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_platform_route_table_coerce:
  * @table: the route table, in its original value as received
@@ -1352,7 +1350,8 @@ int nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (NMPlatform *platform,
                                                    gboolean consider_all,
                                                    gboolean *out_due_to_all);
 
-const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */);
+const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex,
+                                        char out_ifname[static 16 /* IFNAMSIZ */]);
 int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname);
 
 const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
@@ -1363,11 +1362,200 @@ const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const ch
 const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, NMLinkType link_type, gconstpointer address, size_t length);
 
 GPtrArray *nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name);
-int nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-int nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
-int nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-int nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-int nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
+
+int nm_platform_link_add (NMPlatform *self,
+                          NMLinkType type,
+                          const char *name,
+                          int parent,
+                          const void *address,
+                          size_t address_len,
+                          gconstpointer extra_data,
+                          const NMPlatformLink **out_link);
+
+static inline int
+nm_platform_link_veth_add (NMPlatform *self,
+                            const char *name,
+                            const char *peer,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_VETH, name, 0, NULL, 0, peer, out_link);
+}
+
+static inline int
+nm_platform_link_dummy_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_DUMMY, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_bridge_add (NMPlatform *self,
+                             const char *name,
+                             const void *address,
+                             size_t address_len,
+                             const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_BRIDGE, name, 0, address, address_len, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_bond_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_BOND, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_team_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_TEAM, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_wireguard_add (NMPlatform *self,
+                                const char *name,
+                                const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_WIREGUARD, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_gre_add (NMPlatform *self,
+                          const char *name,
+                          const void *address,
+                          size_t address_len,
+                          const NMPlatformLnkGre *props,
+                          const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+
+	return nm_platform_link_add (self, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, name, 0, address, address_len, props, out_link);
+}
+
+static inline int
+nm_platform_link_sit_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkSit *props,
+                          const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_SIT, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_vlan_add (NMPlatform *self,
+                           const char *name,
+                           int parent,
+                           int vlanid,
+                           guint32 vlanflags,
+                           const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (parent >= 0, -NME_BUG);
+	g_return_val_if_fail (vlanid >= 0, -NME_BUG);
+
+	return nm_platform_link_add (self,
+	                             NM_LINK_TYPE_VLAN,
+	                             name,
+	                             parent,
+	                             NULL,
+	                             0,
+	                             &((NMPlatformLnkVlan) {
+	                                .id    = vlanid,
+	                                .flags = vlanflags,
+	                             }),
+	                             out_link);
+}
+
+static inline int
+nm_platform_link_vrf_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkVrf *props,
+                          const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_VRF, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_vxlan_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLnkVxlan *props,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_6lowpan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_6LOWPAN, name, parent, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_ip6tnl_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (!props->is_gre, -NME_BUG);
+
+	return nm_platform_link_add (self, NM_LINK_TYPE_IP6TNL, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_ip6gre_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (props->is_gre, -NME_BUG);
+
+	return nm_platform_link_add (self, props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_ipip_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLnkIpIp *props,
+                           const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+
+	return nm_platform_link_add (self, NM_LINK_TYPE_IPIP, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_macsec_add (NMPlatform *self,
+                             const char *name,
+                             int parent,
+                             const NMPlatformLnkMacsec *props,
+                             const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (parent > 0, -NME_BUG);
+
+	return nm_platform_link_add (self, NM_LINK_TYPE_MACSEC, name, parent, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_macvlan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLnkMacvlan *props,
+                              const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (parent > 0, -NME_BUG);
+
+	return nm_platform_link_add (self, props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN, name, parent, NULL, 0, props, out_link);
+}
 
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
@@ -1477,19 +1665,14 @@ const NMPlatformLnkInfiniband *nm_platform_link_get_lnk_infiniband (NMPlatform *
 const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacsec *nm_platform_link_get_lnk_macsec (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkMacvtap *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkVrf *nm_platform_link_get_lnk_vrf (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkWireGuard *nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
-int nm_platform_link_vlan_add (NMPlatform *self,
-                               const char *name,
-                               int parent,
-                               int vlanid,
-                               guint32 vlanflags,
-                               const NMPlatformLink **out_link);
 gboolean nm_platform_link_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_change (NMPlatform *self,
@@ -1503,11 +1686,6 @@ gboolean nm_platform_link_vlan_change (NMPlatform *self,
                                        const NMVlanQosMapping *egress_map,
                                        gsize n_egress_map);
 
-int nm_platform_link_vxlan_add (NMPlatform *self,
-                                const char *name,
-                                const NMPlatformLnkVxlan *props,
-                                const NMPlatformLink **out_link);
-
 int nm_platform_link_infiniband_add (NMPlatform *self,
                                      int parent,
                                      int p_key,
@@ -1550,32 +1728,6 @@ const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Addr
 
 const NMPlatformIP4Address *nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 
-int nm_platform_link_gre_add (NMPlatform *self,
-                              const char *name,
-                              const NMPlatformLnkGre *props,
-                              const NMPlatformLink **out_link);
-int nm_platform_link_ip6tnl_add (NMPlatform *self,
-                                 const char *name,
-                                 const NMPlatformLnkIp6Tnl *props,
-                                 const NMPlatformLink **out_link);
-int nm_platform_link_ip6gre_add (NMPlatform *self,
-                                 const char *name,
-                                 const NMPlatformLnkIp6Tnl *props,
-                                 const NMPlatformLink **out_link);
-int nm_platform_link_ipip_add (NMPlatform *self,
-                               const char *name,
-                               const NMPlatformLnkIpIp *props,
-                               const NMPlatformLink **out_link);
-int nm_platform_link_macsec_add (NMPlatform *self,
-                                 const char *name,
-                                 int parent,
-                                 const NMPlatformLnkMacsec *props,
-                                 const NMPlatformLink **out_link);
-int nm_platform_link_macvlan_add (NMPlatform *self,
-                                  const char *name,
-                                  int parent,
-                                  const NMPlatformLnkMacvlan *props,
-                                  const NMPlatformLink **out_link);
 int nm_platform_link_sit_add (NMPlatform *self,
                               const char *name,
                               const NMPlatformLnkSit *props,
@@ -1585,10 +1737,6 @@ int nm_platform_link_tun_add (NMPlatform *self,
                               const NMPlatformLnkTun *props,
                               const NMPlatformLink **out_link,
                               int *out_fd);
-int nm_platform_link_6lowpan_add (NMPlatform *self,
-                                  const char *name,
-                                  int parent,
-                                  const NMPlatformLink **out_link);
 gboolean nm_platform_link_6lowpan_get_properties (NMPlatform *self,
                                                   int ifindex,
                                                   int *out_parent);
@@ -1614,6 +1762,7 @@ gboolean nm_platform_ip4_address_add (NMPlatform *self,
                                       in_addr_t address,
                                       guint8 plen,
                                       in_addr_t peer_address,
+                                      in_addr_t broadcast_address,
                                       guint32 lifetime,
                                       guint32 preferred_lft,
                                       guint32 flags,
@@ -1693,6 +1842,7 @@ const char *nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk,
 const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_vrf_to_string (const NMPlatformLnkVrf *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *buf, gsize len);
 const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
@@ -1725,6 +1875,7 @@ int nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatform
 int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b);
 int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
 int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
+int nm_platform_lnk_vrf_cmp (const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b);
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
 int nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
@@ -1771,6 +1922,7 @@ void nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHas
 void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *h);
 void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
+void nm_platform_lnk_vrf_hash_update (const NMPlatformLnkVrf *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
 void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, NMHashState *h);
 
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index 78f76cc7..bc91d7a8 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -31,7 +31,8 @@
 #define _CLONE_NS_ALL    ((int) (CLONE_NEWNS | CLONE_NEWNET))
 #define _CLONE_NS_ALL_V          CLONE_NEWNS , CLONE_NEWNET
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_clone_ns_to_str, int,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_clone_ns_to_str, int,
 	NM_UTILS_FLAGS2STR (CLONE_NEWNS,  "mnt"),
 	NM_UTILS_FLAGS2STR (CLONE_NEWNET, "net"),
 );
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 97aa4f28..541211cd 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -233,13 +233,13 @@ nm_sock_addr_union_to_string (const NMSockAddrUnion *sa,
 	case AF_INET:
 		g_snprintf (buf, len,
 		            "%s:%u",
-		            nm_utils_inet4_ntop (sa->in.sin_addr.s_addr, s_addr),
+		            _nm_utils_inet4_ntop (sa->in.sin_addr.s_addr, s_addr),
 		            (guint) htons (sa->in.sin_port));
 		break;
 	case AF_INET6:
 		g_snprintf (buf, len,
 		            "[%s%s]:%u",
-		            nm_utils_inet6_ntop (&sa->in6.sin6_addr, s_addr),
+		            _nm_utils_inet6_ntop (&sa->in6.sin6_addr, s_addr),
 		            (  sa->in6.sin6_scope_id != 0
 		             ? nm_sprintf_buf (s_scope_id, "%u", sa->in6.sin6_scope_id)
 		             : ""),
@@ -525,11 +525,11 @@ _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
                                const NMVlanQosMapping *src_map)
 {
 	if (src_n_map == 0) {
-		g_clear_pointer (dst_map, g_free);
+		nm_clear_g_free (dst_map);
 		*dst_n_map = 0;
 	} else if (   src_n_map != *dst_n_map
 	           || _vlan_xgress_qos_mappings_cmp (src_n_map, *dst_map, src_map) != 0) {
-		g_clear_pointer (dst_map, g_free);
+		nm_clear_g_free (dst_map);
 		*dst_n_map = src_n_map;
 		if (src_n_map > 0)
 			*dst_map = nm_memdup (src_map, sizeof (*src_map) * src_n_map);
@@ -1082,10 +1082,10 @@ _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsiz
 	return buf; \
 }
 _vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",            obj->ifindex);
-_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
+_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, _nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
                                                                obj->peer_address != obj->address ? "," : "",
-                                                               obj->peer_address != obj->address ? nm_utils_inet4_ntop (nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen), buf2) : "");
-_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1));
+                                                               obj->peer_address != obj->address ? _nm_utils_inet4_ntop (nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen), buf2) : "");
+_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, _nm_utils_inet6_ntop (&obj->address, buf1));
 _vt_cmd_plobj_to_string_id (qdisc,       NMPlatformQdisc,      "%d: %d",        obj->ifindex, obj->parent);
 _vt_cmd_plobj_to_string_id (tfilter,     NMPlatformTfilter,    "%d: %d",        obj->ifindex, obj->parent);
 
@@ -3291,7 +3291,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVTAP,
 		.sizeof_data                        = sizeof (NMPObjectLnkMacvtap),
-		.sizeof_public                      = sizeof (NMPlatformLnkMacvtap),
+		.sizeof_public                      = sizeof (NMPlatformLnkMacvlan),
 		.obj_type_name                      = "macvtap",
 		.lnk_link_type                      = NM_LINK_TYPE_MACVTAP,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
@@ -3336,6 +3336,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vlan_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_VRF - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_VRF,
+		.sizeof_data                        = sizeof (NMPObjectLnkVrf),
+		.sizeof_public                      = sizeof (NMPlatformLnkVrf),
+		.obj_type_name                      = "vrf",
+		.lnk_link_type                      = NM_LINK_TYPE_VRF,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vrf_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vrf_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vrf_cmp,
+	},
+
 	[NMP_OBJECT_TYPE_LNK_VXLAN - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_VXLAN,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 0c0796c3..44525ed0 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -287,6 +287,10 @@ typedef struct {
 } NMPObjectLnkVlan;
 
 typedef struct {
+	NMPlatformLnkVrf _public;
+} NMPObjectLnkVrf;
+
+typedef struct {
 	NMPlatformLnkVxlan _public;
 } NMPObjectLnkVxlan;
 
@@ -366,6 +370,9 @@ struct _NMPObject {
 		NMPlatformLnkVlan       lnk_vlan;
 		NMPObjectLnkVlan        _lnk_vlan;
 
+		NMPlatformLnkVrf        lnk_vrf;
+		NMPObjectLnkVrf         _lnk_vrf;
+
 		NMPlatformLnkVxlan      lnk_vxlan;
 		NMPObjectLnkVxlan       _lnk_vxlan;
 
@@ -493,6 +500,7 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMPObjectType obj_type)
 	case NMP_OBJECT_TYPE_LNK_SIT:
 	case NMP_OBJECT_TYPE_LNK_TUN:
 	case NMP_OBJECT_TYPE_LNK_VLAN:
+	case NMP_OBJECT_TYPE_LNK_VRF:
 	case NMP_OBJECT_TYPE_LNK_VXLAN:
 	case NMP_OBJECT_TYPE_LNK_WIREGUARD:
 		return TRUE;
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index 6f7173b6..4a50bca9 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_fake_c_flags = test_c_flags + ['-DSETUP=nm_fake_platform_setup']
 test_linux_c_flags = test_c_flags + ['-DSETUP=nm_linux_platform_setup']
 
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 34175c01..d19ff1f0 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -61,8 +61,7 @@ test_cleanup_internal (void)
 			break;
 	});
 
-	/* Add routes and addresses */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, lifetime, preferred, 0, NULL));
+	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, nm_platform_ip4_broadcast_address_create (addr4, plen4), lifetime, preferred, 0, NULL));
 	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, plen6, in6addr_any, lifetime, preferred, flags));
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway4, 32, INADDR_ANY, 0, metric, mss);
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network4, plen4, gateway4, 0, metric, mss);
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index f831fe94..f393ae93 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -190,7 +190,7 @@ _accept_or_wait_signal (const char *file, int line, const char *func, SignalData
 	if (data->received_count == 0) {
 		data->loop = g_main_loop_new (NULL, FALSE);
 		g_main_loop_run (data->loop);
-		g_clear_pointer (&data->loop, g_main_loop_unref);
+		nm_clear_pointer (&data->loop, g_main_loop_unref);
 	}
 
 	_accept_signal (file, line, func, data);
@@ -205,7 +205,7 @@ _wait_signal (const char *file, int line, const char *func, SignalData *data)
 
 	data->loop = g_main_loop_new (NULL, FALSE);
 	g_main_loop_run (data->loop);
-	g_clear_pointer (&data->loop, g_main_loop_unref);
+	nm_clear_pointer (&data->loop, g_main_loop_unref);
 
 	_accept_signal (file, line, func, data);
 }
@@ -356,7 +356,7 @@ _nmtstp_assert_ip4_route_exists (const char *file,
 
 		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet4_ntop (network, sbuf),
+		         _nm_utils_inet4_ntop (network, sbuf),
 		         plen,
 		         metric,
 		         tos,
@@ -467,10 +467,10 @@ _nmtstp_assert_ip6_route_exists (const char *file,
 
 		g_error ("[%s:%u] %s(): The ip6 route %s/%d metric %u src %s/%d shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet6_ntop (network, s_network),
+		         _nm_utils_inet6_ntop (network, s_network),
 		         plen,
 		         metric,
-		         nm_utils_inet6_ntop (src, s_src),
+		         _nm_utils_inet6_ntop (src, s_src),
 		         src_plen,
 		         c_exists,
 		         c);
@@ -553,7 +553,7 @@ _wait_for_signal_timeout (gpointer user_data)
 }
 
 guint
-nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
+nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec)
 {
 	WaitForSignalData data = { 0 };
 	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
@@ -568,7 +568,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 	id_ip4_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 	id_ip6_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 
-	/* if timeout_ms is negative, it means the wait-time already expired.
+	/* if timeout_msec is negative, it means the wait-time already expired.
 	 * Maybe, we should do nothing and return right away, without even
 	 * processing events from platform. However, that inconsistency (of not
 	 * processing events from mainloop) is inconvenient.
@@ -578,7 +578,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 	 * a zero timeout: we check whether there are any events pending in platform,
 	 * and quite the mainloop immediately afterwards. But we always check. */
 
-	data.id = g_timeout_add (CLAMP (timeout_ms, 0, G_MAXUINT32),
+	data.id = g_timeout_add (CLAMP (timeout_msec, 0, G_MAXUINT32),
 	                         _wait_for_signal_timeout, &data);
 
 	g_main_loop_run (data.loop);
@@ -590,7 +590,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip4_route));
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip6_route));
 
-	g_clear_pointer (&data.loop, g_main_loop_unref);
+	nm_clear_pointer (&data.loop, g_main_loop_unref);
 
 	/* return the number of signals, or 0 if timeout was reached .*/
 	return data.signal_counts;
@@ -603,7 +603,7 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 	guint signal_counts;
 
 	while (TRUE) {
-		now = nm_utils_get_monotonic_timestamp_ms ();
+		now = nm_utils_get_monotonic_timestamp_msec ();
 
 		if (until_ms < now)
 			return 0;
@@ -615,11 +615,11 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 }
 
 const NMPlatformLink *
-nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms)
+nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_msec)
 {
 	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type,
-	                                   timeout_ms
-	                                     ? nm_utils_get_monotonic_timestamp_ms () + timeout_ms
+	                                   timeout_msec
+	                                     ? nm_utils_get_monotonic_timestamp_msec () + timeout_msec
 	                                     : 0);
 }
 
@@ -633,7 +633,7 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 	_init_platform (&platform, FALSE);
 
 	while (TRUE) {
-		now = nm_utils_get_monotonic_timestamp_ms ();
+		now = nm_utils_get_monotonic_timestamp_msec ();
 
 		plink = nm_platform_link_get_by_ifname (platform, ifname);
 		if (   plink
@@ -704,7 +704,7 @@ nmtstp_ip_address_check_lifetime (const NMPlatformIPAddress *addr,
 	g_assert (addr);
 
 	if (now == -1)
-		now = nm_utils_get_monotonic_timestamp_s ();
+		now = nm_utils_get_monotonic_timestamp_sec ();
 	g_assert (now > 0);
 
 	g_assert (expected_preferred <= expected_lifetime);
@@ -750,7 +750,7 @@ nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
 	g_assert (addr);
 
 	if (now == -1)
-		now = nm_utils_get_monotonic_timestamp_s ();
+		now = nm_utils_get_monotonic_timestamp_sec ();
 	g_assert (now > 0);
 
 	g_assert (expected_preferred <= expected_lifetime);
@@ -831,12 +831,12 @@ _ip_address_add (NMPlatform *platform,
 			if (   peer_address->addr4 != address->addr4
 			    || nmtst_get_rand_uint32 () % 2) {
 				/* If the peer is the same as the local address, we can omit it. The result should be identical */
-				nm_sprintf_buf (s_peer, " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
+				nm_sprintf_buf (s_peer, " peer %s", _nm_utils_inet4_ntop (peer_address->addr4, b2));
 			} else
 				s_peer[0] = '\0';
 
 			nmtstp_run_command_check ("ip address change %s%s/%d dev %s%s%s%s",
-			                          nm_utils_inet4_ntop (address->addr4, b1),
+			                          _nm_utils_inet4_ntop (address->addr4, b1),
 			                          s_peer,
 			                          plen,
 			                          ifname,
@@ -849,9 +849,9 @@ _ip_address_add (NMPlatform *platform,
 			/* flags not implemented (yet) */
 			g_assert (flags == 0);
 			nmtstp_run_command_check ("ip address change %s%s%s/%d dev %s%s%s%s",
-			                          nm_utils_inet6_ntop (&address->addr6, b1),
+			                          _nm_utils_inet6_ntop (&address->addr6, b1),
 			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? " peer " : "",
-			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? nm_utils_inet6_ntop (&peer_address->addr6, b2) : "",
+			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? _nm_utils_inet6_ntop (&peer_address->addr6, b2) : "",
 			                          plen,
 			                          ifname,
 			                          s_valid ?: "",
@@ -867,6 +867,7 @@ _ip_address_add (NMPlatform *platform,
 			                                       address->addr4,
 			                                       plen,
 			                                       peer_address->addr4,
+			                                       0u,
 			                                       lifetime,
 			                                       preferred,
 			                                       flags,
@@ -886,7 +887,7 @@ _ip_address_add (NMPlatform *platform,
 	}
 
 	/* Let's wait until we see the address. */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 500;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 500;
 	do {
 
 		if (external_command)
@@ -1058,15 +1059,15 @@ _ip_address_del (NMPlatform *platform,
 
 		if (is_v4) {
 			success = nmtstp_run_command ("ip address delete %s%s%s/%d dev %s",
-			                              nm_utils_inet4_ntop (address->addr4, b1),
+			                              _nm_utils_inet4_ntop (address->addr4, b1),
 			                              peer_address->addr4 != address->addr4 ? " peer " : "",
-			                              peer_address->addr4 != address->addr4 ? nm_utils_inet4_ntop (peer_address->addr4, b2) : "",
+			                              peer_address->addr4 != address->addr4 ? _nm_utils_inet4_ntop (peer_address->addr4, b2) : "",
 			                              plen,
 			                              ifname);
 		} else {
 			g_assert (!peer_address);
 			success = nmtstp_run_command ("ip address delete %s/%d dev %s",
-			                              nm_utils_inet6_ntop (&address->addr6, b1),
+			                              _nm_utils_inet6_ntop (&address->addr6, b1),
 			                              plen,
 			                              ifname);
 		}
@@ -1091,7 +1092,7 @@ _ip_address_del (NMPlatform *platform,
 	}
 
 	/* Let's wait until we get the result */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 250;
 	do {
 		if (external_command)
 			nm_platform_process_events (platform);
@@ -1257,15 +1258,15 @@ nmtstp_link_gre_add (NMPlatform *platform,
 		                                name,
 		                                type,
 		                                dev ?: "",
-		                                nm_utils_inet4_ntop (lnk->local, b1),
-		                                nm_utils_inet4_ntop (lnk->remote, b2),
+		                                _nm_utils_inet4_ntop (lnk->local, b1),
+		                                _nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
-		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_gre_add (platform, name, lnk, &pllink));
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_gre_add (platform, name, NULL, 0, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, link_type);
 
@@ -1319,8 +1320,8 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 		                                name,
 		                                mode,
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, b1),
-		                                nm_utils_inet6_ntop (&lnk->remote, b2),
+		                                _nm_utils_inet6_ntop (&lnk->local, b1),
+		                                _nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit),
@@ -1367,8 +1368,8 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 		                                name,
 		                                lnk->is_tap ? "ip6gretap" : "ip6gre",
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, b1),
-		                                nm_utils_inet6_ntop (&lnk->remote, b2),
+		                                _nm_utils_inet6_ntop (&lnk->local, b1),
+		                                _nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                lnk->flow_label);
@@ -1412,8 +1413,8 @@ nmtstp_link_ipip_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, b1),
-		                                nm_utils_inet4_ntop (lnk->remote, b2),
+		                                _nm_utils_inet4_ntop (lnk->local, b1),
+		                                _nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
@@ -1506,8 +1507,8 @@ nmtstp_link_sit_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, b1),
-		                                nm_utils_inet4_ntop (lnk->remote, b2),
+		                                _nm_utils_inet4_ntop (lnk->local, b1),
+		                                _nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
@@ -1586,6 +1587,45 @@ nmtstp_link_tun_add (NMPlatform *platform,
 }
 
 const NMPlatformLink *
+nmtstp_link_vrf_add (NMPlatform *platform,
+                     gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkVrf *lnk,
+                     gboolean *out_not_supported)
+{
+	const NMPlatformLink *pllink = NULL;
+	int r = 0;
+
+	g_assert (nm_utils_ifname_valid_kernel (name, NULL));
+
+	NM_SET_OUT (out_not_supported, FALSE);
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		r = nmtstp_run_command ("ip link add %s type vrf table %u",
+		                        name,
+		                        lnk->table);
+
+		if (r == 0)
+			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_VRF, 100);
+		else
+			_LOGI ("Adding vrf device via iproute2 failed. Assume iproute2 is not up to the task.");
+	}
+
+	if (!pllink) {
+		r = nm_platform_link_vrf_add (platform, name, lnk, &pllink);
+		if (r == -EOPNOTSUPP)
+			NM_SET_OUT (out_not_supported, TRUE);
+	}
+
+	_assert_pllink (platform, r == 0, pllink, name, NM_LINK_TYPE_VRF);
+
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_vxlan_add (NMPlatform *platform,
                        gboolean external_command,
                        const char *name,
@@ -1610,16 +1650,16 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
 			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
 
 		if (lnk->local)
-			nm_utils_inet4_ntop (lnk->local, local);
+			_nm_utils_inet4_ntop (lnk->local, local);
 		else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any)))
-			nm_utils_inet6_ntop (&lnk->local6, local);
+			_nm_utils_inet6_ntop (&lnk->local6, local);
 		else
 			local[0] = '\0';
 
 		if (lnk->group)
-			nm_utils_inet4_ntop (lnk->group, group);
+			_nm_utils_inet4_ntop (lnk->group, group);
 		else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any)))
-			nm_utils_inet6_ntop (&lnk->group6, group);
+			_nm_utils_inet6_ntop (&lnk->group6, group);
 		else
 			group[0] = '\0';
 
@@ -1736,7 +1776,7 @@ nmtstp_link_delete (NMPlatform *platform,
 	}
 
 	/* Let's wait until we get the result */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 250;
 	do {
 		if (external_command)
 			nm_platform_process_events (platform);
@@ -1785,7 +1825,7 @@ nmtstp_link_set_updown (NMPlatform *platform,
 	}
 
 	/* Let's wait until we get the result */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 250;
 	do {
 		if (external_command)
 			nm_platform_process_events (platform);
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index c571df6a..522c3756 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -32,13 +32,13 @@
         const NMLogDomain __domain = (domain); \
         \
         if (nm_logging_enabled (__level, __domain)) { \
-            gint64 _ts = nm_utils_get_monotonic_timestamp_ns (); \
+            gint64 _ts = nm_utils_get_monotonic_timestamp_nsec (); \
             \
             _nm_log (__level, __domain, 0, NULL, NULL, \
                      "%s[%ld.%09ld]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                      _NMLOG_PREFIX_NAME, \
-                     (long) (_ts / NM_UTILS_NS_PER_SECOND), \
-                     (long) (_ts % NM_UTILS_NS_PER_SECOND) \
+                     (long) (_ts / NM_UTILS_NSEC_PER_SEC), \
+                     (long) (_ts % NM_UTILS_NSEC_PER_SEC) \
                      _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
@@ -101,14 +101,14 @@ int nmtstp_run_command (const char *format, ...) _nm_printf (1, 2);
 
 /*****************************************************************************/
 
-guint nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms);
+guint nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec);
 guint nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms);
-const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_msec);
 const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
-#define nmtstp_assert_wait_for_signal(platform, timeout_ms) \
+#define nmtstp_assert_wait_for_signal(platform, timeout_msec) \
 	G_STMT_START { \
-		if (nmtstp_wait_for_signal (platform, timeout_ms) == 0) \
+		if (nmtstp_wait_for_signal (platform, timeout_msec) == 0) \
 			g_assert_not_reached (); \
 	} G_STMT_END
 
@@ -118,8 +118,8 @@ const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const ch
 			g_assert_not_reached (); \
 	} G_STMT_END
 
-#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_ms) \
-	nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_ms))
+#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_msec) \
+	nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_msec))
 
 #define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \
 	nmtst_assert_nonnull (nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms))
@@ -360,6 +360,11 @@ const NMPlatformLink *nmtstp_link_tun_add (NMPlatform *platform,
                                            const char *name,
                                            const NMPlatformLnkTun *lnk,
                                            int *out_fd);
+const NMPlatformLink *nmtstp_link_vrf_add (NMPlatform *platform,
+                                           gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkVrf *lnk,
+                                           gboolean *out_not_supported);
 const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform,
                                              gboolean external_command,
                                              const char *name,
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 37e1cde5..2661914b 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1144,7 +1144,7 @@ test_software_detect (gconstpointer user_data)
 		break;
 	}
 	case NM_LINK_TYPE_MACVTAP: {
-		NMPlatformLnkMacvtap lnk_macvtap = { };
+		NMPlatformLnkMacvlan lnk_macvtap = { };
 
 		lnk_macvtap.mode = MACVLAN_MODE_PRIVATE;
 		lnk_macvtap.no_promisc = FALSE;
@@ -1182,6 +1182,21 @@ test_software_detect (gconstpointer user_data)
 	case NM_LINK_TYPE_VLAN:
 		nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1242", DEVICE_NAME, PARENT_NAME);
 		break;
+	case NM_LINK_TYPE_VRF: {
+		NMPlatformLnkVrf lnk_vrf = { };
+		gboolean not_supported;
+
+		lnk_vrf.table = 9876;
+
+		if (!nmtstp_link_vrf_add (NULL, ext, DEVICE_NAME, &lnk_vrf, &not_supported)) {
+			if (not_supported) {
+				g_test_skip ("Cannot create VRF interface because of missing kernel support");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding VRF interface");
+		}
+		break;
+	}
 	case NM_LINK_TYPE_VXLAN: {
 		NMPlatformLnkVxlan lnk_vxlan = { };
 
@@ -1389,7 +1404,7 @@ test_software_detect (gconstpointer user_data)
 			break;
 		}
 		case NM_LINK_TYPE_MACVTAP: {
-			const NMPlatformLnkMacvtap *plnk = &lnk->lnk_macvlan;
+			const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan;
 
 			g_assert (plnk == nm_platform_link_get_lnk_macvtap (NM_PLATFORM_GET, ifindex, NULL));
 			g_assert_cmpint (plnk->no_promisc, ==, FALSE);
@@ -1444,6 +1459,13 @@ test_software_detect (gconstpointer user_data)
 			g_assert_cmpint (plnk->id, ==, 1242);
 			break;
 		}
+		case NM_LINK_TYPE_VRF: {
+			const NMPlatformLnkVrf *plnk = &lnk->lnk_vrf;
+
+			g_assert (plnk == nm_platform_link_get_lnk_vrf (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->table, ==, 9876);
+			break;
+		}
 		case NM_LINK_TYPE_VXLAN: {
 			const NMPlatformLnkVxlan *plnk = &lnk->lnk_vxlan;
 
@@ -2095,7 +2117,7 @@ test_vlan_set_xgress (void)
 static void
 test_create_many_links_do (guint n_devices)
 {
-	gint64 time, start_time = nm_utils_get_monotonic_timestamp_ns ();
+	gint64 time, start_time = nm_utils_get_monotonic_timestamp_nsec ();
 	guint i;
 	char name[64];
 	const NMPlatformLink *pllink;
@@ -2149,8 +2171,8 @@ test_create_many_links_do (guint n_devices)
 	_LOGI (">>> process events after deleting devices...");
 	nm_platform_process_events (NM_PLATFORM_GET);
 
-	time = nm_utils_get_monotonic_timestamp_ns () - start_time;
-	_LOGI (">>> finished in %ld.%09ld seconds", (long) (time / NM_UTILS_NS_PER_SECOND), (long) (time % NM_UTILS_NS_PER_SECOND));
+	time = nm_utils_get_monotonic_timestamp_nsec () - start_time;
+	_LOGI (">>> finished in %ld.%09ld seconds", (long) (time / NM_UTILS_NSEC_PER_SEC), (long) (time % NM_UTILS_NSEC_PER_SEC));
 }
 
 static void
@@ -3313,6 +3335,7 @@ _nmtstp_setup_tests (void)
 		test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0);
 		test_software_detect_add ("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0);
 		test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0);
+		test_software_detect_add ("/link/software/detect/vrf", NM_LINK_TYPE_VRF, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1);
 		test_software_detect_add ("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0);
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index f8b1bb9a..f074ca69 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -19,7 +19,7 @@
 
 static void
 _wait_for_ipv4_addr_device_route (NMPlatform *platform,
-                                  gint64 timeout_ms,
+                                  gint64 timeout_msec,
                                   int ifindex,
                                   in_addr_t addr,
                                   guint8 plen)
@@ -55,7 +55,7 @@ _wait_for_ipv4_addr_device_route (NMPlatform *platform,
 
 static void
 _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
-                                   gint64 timeout_ms,
+                                   gint64 timeout_msec,
                                    int ifindex,
                                    guint addr_n,
                                    const struct in6_addr *addrs)
@@ -67,7 +67,7 @@ _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
 	 * small amount of time, which prevents the immediate addition of the route
 	 * with RTA_PREFSRC */
 
-	NMTST_WAIT_ASSERT (timeout_ms, {
+	NMTST_WAIT_ASSERT (timeout_msec, {
 		gboolean should_wait = FALSE;
 		const NMPlatformIP6Address *plt_addr;
 
@@ -544,6 +544,7 @@ test_ip4_route_options (gconstpointer test_data)
 		                                       a->address,
 		                                       a->plen,
 		                                       a->peer_address,
+		                                       nm_platform_ip4_broadcast_address_create (a->address, a->plen),
 		                                       a->lifetime,
 		                                       a->preferred,
 		                                       a->n_ifa_flags,
@@ -1238,7 +1239,7 @@ again_uid_range:
 	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);
+			NM_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;
@@ -1258,7 +1259,7 @@ again_uid_range:
 				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);
+					NM_SWAP (range->start, range->end);
 			}
 		}
 	}
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index afeb3b02..a090ef9e 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -152,7 +152,7 @@ dispose (GObject *object)
 {
 	NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211 (object);
 
-	g_clear_pointer (&self->freqs, g_free);
+	nm_clear_g_free (&self->freqs);
 }
 
 struct nl80211_iface_info {