summary refs log tree commit diff
path: root/src/core/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-10-01 23:05:04 +0200
committerMichael Biebl <biebl@debian.org>2021-10-01 23:05:04 +0200
commite74c568b07b50b97873fb4ee1d776dedefbd54d6 (patch)
tree3469f17ea9af91f7ff169b890633bda68b0cf76e /src/core/platform
parentbfe522304da217296e2a61040f58e35ec5d6f3f2 (diff)
New upstream version 1.32.12 upstream/1.32.12
Diffstat (limited to 'src/core/platform')
-rw-r--r--src/core/platform/linux/nl802154.h450
-rw-r--r--src/core/platform/nm-fake-platform.c119
-rw-r--r--src/core/platform/nm-fake-platform.h2
-rw-r--r--src/core/platform/nm-linux-platform.c9696
-rw-r--r--src/core/platform/nm-linux-platform.h30
-rw-r--r--src/core/platform/nm-platform-private.h29
-rw-r--r--src/core/platform/nm-platform.c9066
-rw-r--r--src/core/platform/nm-platform.h2372
-rw-r--r--src/core/platform/nmp-object.c3457
-rw-r--r--src/core/platform/nmp-object.h1144
-rw-r--r--src/core/platform/nmp-rules-manager.c808
-rw-r--r--src/core/platform/nmp-rules-manager.h53
-rw-r--r--src/core/platform/tests/monitor.c2
-rw-r--r--src/core/platform/tests/test-address.c6
-rw-r--r--src/core/platform/tests/test-cleanup.c9
-rw-r--r--src/core/platform/tests/test-common.c54
-rw-r--r--src/core/platform/tests/test-common.h31
-rw-r--r--src/core/platform/tests/test-link.c114
-rw-r--r--src/core/platform/tests/test-nmp-object.c4
-rw-r--r--src/core/platform/tests/test-platform-general.c10
-rw-r--r--src/core/platform/tests/test-route.c108
-rw-r--r--src/core/platform/tests/test-tc.c6
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-nl80211.c909
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-nl80211.h29
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-private.h66
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-wext.c836
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-wext.h28
-rw-r--r--src/core/platform/wifi/nm-wifi-utils.c211
-rw-r--r--src/core/platform/wifi/nm-wifi-utils.h74
-rw-r--r--src/core/platform/wpan/nm-wpan-utils.c286
-rw-r--r--src/core/platform/wpan/nm-wpan-utils.h37
31 files changed, 257 insertions, 29789 deletions
diff --git a/src/core/platform/linux/nl802154.h b/src/core/platform/linux/nl802154.h
deleted file mode 100644
index c21bd079..00000000
--- a/src/core/platform/linux/nl802154.h
+++ /dev/null
@@ -1,450 +0,0 @@
-#ifndef __NL802154_H
-#define __NL802154_H
-/*
- * 802.15.4 netlink interface public header
- *
- * Copyright (C) 2014 Alexander Aring <aar@pengutronix.de>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- */
-
-#define NL802154_GENL_NAME "nl802154"
-
-enum nl802154_commands {
-    /* don't change the order or add anything between, this is ABI! */
-    /* currently we don't shipping this file via uapi, ignore the above one */
-    NL802154_CMD_UNSPEC,
-
-    NL802154_CMD_GET_WPAN_PHY, /* can dump */
-    NL802154_CMD_SET_WPAN_PHY,
-    NL802154_CMD_NEW_WPAN_PHY,
-    NL802154_CMD_DEL_WPAN_PHY,
-
-    NL802154_CMD_GET_INTERFACE, /* can dump */
-    NL802154_CMD_SET_INTERFACE,
-    NL802154_CMD_NEW_INTERFACE,
-    NL802154_CMD_DEL_INTERFACE,
-
-    NL802154_CMD_SET_CHANNEL,
-
-    NL802154_CMD_SET_PAN_ID,
-    NL802154_CMD_SET_SHORT_ADDR,
-
-    NL802154_CMD_SET_TX_POWER,
-    NL802154_CMD_SET_CCA_MODE,
-    NL802154_CMD_SET_CCA_ED_LEVEL,
-
-    NL802154_CMD_SET_MAX_FRAME_RETRIES,
-
-    NL802154_CMD_SET_BACKOFF_EXPONENT,
-    NL802154_CMD_SET_MAX_CSMA_BACKOFFS,
-
-    NL802154_CMD_SET_LBT_MODE,
-
-    NL802154_CMD_SET_ACKREQ_DEFAULT,
-
-    NL802154_CMD_SET_WPAN_PHY_NETNS,
-
-/* add new commands above here */
-
-#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
-    NL802154_CMD_SET_SEC_PARAMS,
-    NL802154_CMD_GET_SEC_KEY, /* can dump */
-    NL802154_CMD_NEW_SEC_KEY,
-    NL802154_CMD_DEL_SEC_KEY,
-    NL802154_CMD_GET_SEC_DEV, /* can dump */
-    NL802154_CMD_NEW_SEC_DEV,
-    NL802154_CMD_DEL_SEC_DEV,
-    NL802154_CMD_GET_SEC_DEVKEY, /* can dump */
-    NL802154_CMD_NEW_SEC_DEVKEY,
-    NL802154_CMD_DEL_SEC_DEVKEY,
-    NL802154_CMD_GET_SEC_LEVEL, /* can dump */
-    NL802154_CMD_NEW_SEC_LEVEL,
-    NL802154_CMD_DEL_SEC_LEVEL,
-#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
-
-    /* used to define NL802154_CMD_MAX below */
-    __NL802154_CMD_AFTER_LAST,
-    NL802154_CMD_MAX = __NL802154_CMD_AFTER_LAST - 1
-};
-
-enum nl802154_attrs {
-    /* don't change the order or add anything between, this is ABI! */
-    /* currently we don't shipping this file via uapi, ignore the above one */
-    NL802154_ATTR_UNSPEC,
-
-    NL802154_ATTR_WPAN_PHY,
-    NL802154_ATTR_WPAN_PHY_NAME,
-
-    NL802154_ATTR_IFINDEX,
-    NL802154_ATTR_IFNAME,
-    NL802154_ATTR_IFTYPE,
-
-    NL802154_ATTR_WPAN_DEV,
-
-    NL802154_ATTR_PAGE,
-    NL802154_ATTR_CHANNEL,
-
-    NL802154_ATTR_PAN_ID,
-    NL802154_ATTR_SHORT_ADDR,
-
-    NL802154_ATTR_TX_POWER,
-
-    NL802154_ATTR_CCA_MODE,
-    NL802154_ATTR_CCA_OPT,
-    NL802154_ATTR_CCA_ED_LEVEL,
-
-    NL802154_ATTR_MAX_FRAME_RETRIES,
-
-    NL802154_ATTR_MAX_BE,
-    NL802154_ATTR_MIN_BE,
-    NL802154_ATTR_MAX_CSMA_BACKOFFS,
-
-    NL802154_ATTR_LBT_MODE,
-
-    NL802154_ATTR_GENERATION,
-
-    NL802154_ATTR_CHANNELS_SUPPORTED,
-    NL802154_ATTR_SUPPORTED_CHANNEL,
-
-    NL802154_ATTR_EXTENDED_ADDR,
-
-    NL802154_ATTR_WPAN_PHY_CAPS,
-
-    NL802154_ATTR_SUPPORTED_COMMANDS,
-
-    NL802154_ATTR_ACKREQ_DEFAULT,
-
-    NL802154_ATTR_PAD,
-
-    NL802154_ATTR_PID,
-    NL802154_ATTR_NETNS_FD,
-
-/* add attributes here, update the policy in nl802154.c */
-
-#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
-    NL802154_ATTR_SEC_ENABLED,
-    NL802154_ATTR_SEC_OUT_LEVEL,
-    NL802154_ATTR_SEC_OUT_KEY_ID,
-    NL802154_ATTR_SEC_FRAME_COUNTER,
-
-    NL802154_ATTR_SEC_LEVEL,
-    NL802154_ATTR_SEC_DEVICE,
-    NL802154_ATTR_SEC_DEVKEY,
-    NL802154_ATTR_SEC_KEY,
-#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
-
-    __NL802154_ATTR_AFTER_LAST,
-    NL802154_ATTR_MAX = __NL802154_ATTR_AFTER_LAST - 1
-};
-
-enum nl802154_iftype {
-    /* for backwards compatibility TODO */
-    NL802154_IFTYPE_UNSPEC = -1,
-
-    NL802154_IFTYPE_NODE,
-    NL802154_IFTYPE_MONITOR,
-    NL802154_IFTYPE_COORD,
-
-    /* keep last */
-    NUM_NL802154_IFTYPES,
-    NL802154_IFTYPE_MAX = NUM_NL802154_IFTYPES - 1
-};
-
-/**
- * enum nl802154_wpan_phy_capability_attr - wpan phy capability attributes
- *
- * @__NL802154_CAP_ATTR_INVALID: attribute number 0 is reserved
- * @NL802154_CAP_ATTR_CHANNELS: a nested attribute for nl802154_channel_attr
- * @NL802154_CAP_ATTR_TX_POWERS: a nested attribute for nl802154_wpan_phy_tx_power
- * @NL802154_CAP_ATTR_MIN_CCA_ED_LEVEL: minimum value for cca_ed_level
- * @NL802154_CAP_ATTR_MAX_CCA_ED_LEVEL: maxmimum value for cca_ed_level
- * @NL802154_CAP_ATTR_CCA_MODES: nl802154_cca_modes flags
- * @NL802154_CAP_ATTR_CCA_OPTS: nl802154_cca_opts flags
- * @NL802154_CAP_ATTR_MIN_MINBE: minimum of minbe value
- * @NL802154_CAP_ATTR_MAX_MINBE: maximum of minbe value
- * @NL802154_CAP_ATTR_MIN_MAXBE: minimum of maxbe value
- * @NL802154_CAP_ATTR_MAX_MINBE: maximum of maxbe value
- * @NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS: minimum of csma backoff value
- * @NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS: maximum of csma backoffs value
- * @NL802154_CAP_ATTR_MIN_FRAME_RETRIES: minimum of frame retries value
- * @NL802154_CAP_ATTR_MAX_FRAME_RETRIES: maximum of frame retries value
- * @NL802154_CAP_ATTR_IFTYPES: nl802154_iftype flags
- * @NL802154_CAP_ATTR_LBT: nl802154_supported_bool_states flags
- * @NL802154_CAP_ATTR_MAX: highest cap attribute currently defined
- * @__NL802154_CAP_ATTR_AFTER_LAST: internal use
- */
-enum nl802154_wpan_phy_capability_attr {
-    __NL802154_CAP_ATTR_INVALID,
-
-    NL802154_CAP_ATTR_IFTYPES,
-
-    NL802154_CAP_ATTR_CHANNELS,
-    NL802154_CAP_ATTR_TX_POWERS,
-
-    NL802154_CAP_ATTR_CCA_ED_LEVELS,
-    NL802154_CAP_ATTR_CCA_MODES,
-    NL802154_CAP_ATTR_CCA_OPTS,
-
-    NL802154_CAP_ATTR_MIN_MINBE,
-    NL802154_CAP_ATTR_MAX_MINBE,
-
-    NL802154_CAP_ATTR_MIN_MAXBE,
-    NL802154_CAP_ATTR_MAX_MAXBE,
-
-    NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS,
-    NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS,
-
-    NL802154_CAP_ATTR_MIN_FRAME_RETRIES,
-    NL802154_CAP_ATTR_MAX_FRAME_RETRIES,
-
-    NL802154_CAP_ATTR_LBT,
-
-    /* keep last */
-    __NL802154_CAP_ATTR_AFTER_LAST,
-    NL802154_CAP_ATTR_MAX = __NL802154_CAP_ATTR_AFTER_LAST - 1
-};
-
-/**
- * enum nl802154_cca_modes - cca modes
- *
- * @__NL802154_CCA_INVALID: cca mode number 0 is reserved
- * @NL802154_CCA_ENERGY: Energy above threshold
- * @NL802154_CCA_CARRIER: Carrier sense only
- * @NL802154_CCA_ENERGY_CARRIER: Carrier sense with energy above threshold
- * @NL802154_CCA_ALOHA: CCA shall always report an idle medium
- * @NL802154_CCA_UWB_SHR: UWB preamble sense based on the SHR of a frame
- * @NL802154_CCA_UWB_MULTIPLEXED: UWB preamble sense based on the packet with the multiplexed preamble
- * @__NL802154_CCA_ATTR_AFTER_LAST: Internal
- * @NL802154_CCA_ATTR_MAX: Maximum CCA attribute number
- */
-enum nl802154_cca_modes {
-    __NL802154_CCA_INVALID,
-    NL802154_CCA_ENERGY,
-    NL802154_CCA_CARRIER,
-    NL802154_CCA_ENERGY_CARRIER,
-    NL802154_CCA_ALOHA,
-    NL802154_CCA_UWB_SHR,
-    NL802154_CCA_UWB_MULTIPLEXED,
-
-    /* keep last */
-    __NL802154_CCA_ATTR_AFTER_LAST,
-    NL802154_CCA_ATTR_MAX = __NL802154_CCA_ATTR_AFTER_LAST - 1
-};
-
-/**
- * enum nl802154_cca_opts - additional options for cca modes
- *
- * @NL802154_CCA_OPT_ENERGY_CARRIER_OR: NL802154_CCA_ENERGY_CARRIER with OR
- * @NL802154_CCA_OPT_ENERGY_CARRIER_AND: NL802154_CCA_ENERGY_CARRIER with AND
- */
-enum nl802154_cca_opts {
-    NL802154_CCA_OPT_ENERGY_CARRIER_AND,
-    NL802154_CCA_OPT_ENERGY_CARRIER_OR,
-
-    /* keep last */
-    __NL802154_CCA_OPT_ATTR_AFTER_LAST,
-    NL802154_CCA_OPT_ATTR_MAX = __NL802154_CCA_OPT_ATTR_AFTER_LAST - 1
-};
-
-/**
- * enum nl802154_supported_bool_states - bool states for bool capability entry
- *
- * @NL802154_SUPPORTED_BOOL_FALSE: indicates to set false
- * @NL802154_SUPPORTED_BOOL_TRUE: indicates to set true
- * @__NL802154_SUPPORTED_BOOL_INVALD: reserved
- * @NL802154_SUPPORTED_BOOL_BOTH: indicates to set true and false
- * @__NL802154_SUPPORTED_BOOL_AFTER_LAST: Internal
- * @NL802154_SUPPORTED_BOOL_MAX: highest value for bool states
- */
-enum nl802154_supported_bool_states {
-    NL802154_SUPPORTED_BOOL_FALSE,
-    NL802154_SUPPORTED_BOOL_TRUE,
-    /* to handle them in a mask */
-    __NL802154_SUPPORTED_BOOL_INVALD,
-    NL802154_SUPPORTED_BOOL_BOTH,
-
-    /* keep last */
-    __NL802154_SUPPORTED_BOOL_AFTER_LAST,
-    NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1
-};
-
-#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
-
-enum nl802154_dev_addr_modes {
-    NL802154_DEV_ADDR_NONE,
-    __NL802154_DEV_ADDR_INVALID,
-    NL802154_DEV_ADDR_SHORT,
-    NL802154_DEV_ADDR_EXTENDED,
-
-    /* keep last */
-    __NL802154_DEV_ADDR_AFTER_LAST,
-    NL802154_DEV_ADDR_MAX = __NL802154_DEV_ADDR_AFTER_LAST - 1
-};
-
-enum nl802154_dev_addr_attrs {
-    NL802154_DEV_ADDR_ATTR_UNSPEC,
-
-    NL802154_DEV_ADDR_ATTR_PAN_ID,
-    NL802154_DEV_ADDR_ATTR_MODE,
-    NL802154_DEV_ADDR_ATTR_SHORT,
-    NL802154_DEV_ADDR_ATTR_EXTENDED,
-    NL802154_DEV_ADDR_ATTR_PAD,
-
-    /* keep last */
-    __NL802154_DEV_ADDR_ATTR_AFTER_LAST,
-    NL802154_DEV_ADDR_ATTR_MAX = __NL802154_DEV_ADDR_ATTR_AFTER_LAST - 1
-};
-
-enum nl802154_key_id_modes {
-    NL802154_KEY_ID_MODE_IMPLICIT,
-    NL802154_KEY_ID_MODE_INDEX,
-    NL802154_KEY_ID_MODE_INDEX_SHORT,
-    NL802154_KEY_ID_MODE_INDEX_EXTENDED,
-
-    /* keep last */
-    __NL802154_KEY_ID_MODE_AFTER_LAST,
-    NL802154_KEY_ID_MODE_MAX = __NL802154_KEY_ID_MODE_AFTER_LAST - 1
-};
-
-enum nl802154_key_id_attrs {
-    NL802154_KEY_ID_ATTR_UNSPEC,
-
-    NL802154_KEY_ID_ATTR_MODE,
-    NL802154_KEY_ID_ATTR_INDEX,
-    NL802154_KEY_ID_ATTR_IMPLICIT,
-    NL802154_KEY_ID_ATTR_SOURCE_SHORT,
-    NL802154_KEY_ID_ATTR_SOURCE_EXTENDED,
-    NL802154_KEY_ID_ATTR_PAD,
-
-    /* keep last */
-    __NL802154_KEY_ID_ATTR_AFTER_LAST,
-    NL802154_KEY_ID_ATTR_MAX = __NL802154_KEY_ID_ATTR_AFTER_LAST - 1
-};
-
-enum nl802154_seclevels {
-    NL802154_SECLEVEL_NONE,
-    NL802154_SECLEVEL_MIC32,
-    NL802154_SECLEVEL_MIC64,
-    NL802154_SECLEVEL_MIC128,
-    NL802154_SECLEVEL_ENC,
-    NL802154_SECLEVEL_ENC_MIC32,
-    NL802154_SECLEVEL_ENC_MIC64,
-    NL802154_SECLEVEL_ENC_MIC128,
-
-    /* keep last */
-    __NL802154_SECLEVEL_AFTER_LAST,
-    NL802154_SECLEVEL_MAX = __NL802154_SECLEVEL_AFTER_LAST - 1
-};
-
-enum nl802154_frames {
-    NL802154_FRAME_BEACON,
-    NL802154_FRAME_DATA,
-    NL802154_FRAME_ACK,
-    NL802154_FRAME_CMD,
-
-    /* keep last */
-    __NL802154_FRAME_AFTER_LAST,
-    NL802154_FRAME_MAX = __NL802154_FRAME_AFTER_LAST - 1
-};
-
-enum nl802154_cmd_frames {
-    __NL802154_CMD_FRAME_INVALID,
-    NL802154_CMD_FRAME_ASSOC_REQUEST,
-    NL802154_CMD_FRAME_ASSOC_RESPONSE,
-    NL802154_CMD_FRAME_DISASSOC_NOTIFY,
-    NL802154_CMD_FRAME_DATA_REQUEST,
-    NL802154_CMD_FRAME_PAN_ID_CONFLICT_NOTIFY,
-    NL802154_CMD_FRAME_ORPHAN_NOTIFY,
-    NL802154_CMD_FRAME_BEACON_REQUEST,
-    NL802154_CMD_FRAME_COORD_REALIGNMENT,
-    NL802154_CMD_FRAME_GTS_REQUEST,
-
-    /* keep last */
-    __NL802154_CMD_FRAME_AFTER_LAST,
-    NL802154_CMD_FRAME_MAX = __NL802154_CMD_FRAME_AFTER_LAST - 1
-};
-
-enum nl802154_seclevel_attrs {
-    NL802154_SECLEVEL_ATTR_UNSPEC,
-
-    NL802154_SECLEVEL_ATTR_LEVELS,
-    NL802154_SECLEVEL_ATTR_FRAME,
-    NL802154_SECLEVEL_ATTR_CMD_FRAME,
-    NL802154_SECLEVEL_ATTR_DEV_OVERRIDE,
-
-    /* keep last */
-    __NL802154_SECLEVEL_ATTR_AFTER_LAST,
-    NL802154_SECLEVEL_ATTR_MAX = __NL802154_SECLEVEL_ATTR_AFTER_LAST - 1
-};
-
-/* TODO what is this? couldn't find in mib */
-enum {
-    NL802154_DEVKEY_IGNORE,
-    NL802154_DEVKEY_RESTRICT,
-    NL802154_DEVKEY_RECORD,
-
-    /* keep last */
-    __NL802154_DEVKEY_AFTER_LAST,
-    NL802154_DEVKEY_MAX = __NL802154_DEVKEY_AFTER_LAST - 1
-};
-
-enum nl802154_dev {
-    NL802154_DEV_ATTR_UNSPEC,
-
-    NL802154_DEV_ATTR_FRAME_COUNTER,
-    NL802154_DEV_ATTR_PAN_ID,
-    NL802154_DEV_ATTR_SHORT_ADDR,
-    NL802154_DEV_ATTR_EXTENDED_ADDR,
-    NL802154_DEV_ATTR_SECLEVEL_EXEMPT,
-    NL802154_DEV_ATTR_KEY_MODE,
-    NL802154_DEV_ATTR_PAD,
-
-    /* keep last */
-    __NL802154_DEV_ATTR_AFTER_LAST,
-    NL802154_DEV_ATTR_MAX = __NL802154_DEV_ATTR_AFTER_LAST - 1
-};
-
-enum nl802154_devkey {
-    NL802154_DEVKEY_ATTR_UNSPEC,
-
-    NL802154_DEVKEY_ATTR_FRAME_COUNTER,
-    NL802154_DEVKEY_ATTR_EXTENDED_ADDR,
-    NL802154_DEVKEY_ATTR_ID,
-    NL802154_DEVKEY_ATTR_PAD,
-
-    /* keep last */
-    __NL802154_DEVKEY_ATTR_AFTER_LAST,
-    NL802154_DEVKEY_ATTR_MAX = __NL802154_DEVKEY_ATTR_AFTER_LAST - 1
-};
-
-enum nl802154_key {
-    NL802154_KEY_ATTR_UNSPEC,
-
-    NL802154_KEY_ATTR_ID,
-    NL802154_KEY_ATTR_USAGE_FRAMES,
-    NL802154_KEY_ATTR_USAGE_CMDS,
-    NL802154_KEY_ATTR_BYTES,
-
-    /* keep last */
-    __NL802154_KEY_ATTR_AFTER_LAST,
-    NL802154_KEY_ATTR_MAX = __NL802154_KEY_ATTR_AFTER_LAST - 1
-};
-
-    #define NL802154_KEY_SIZE         16
-    #define NL802154_CMD_FRAME_NR_IDS 256
-
-#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
-
-#endif /* __NL802154_H */
diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c
index 81c9f06c..b773df9a 100644
--- a/src/core/platform/nm-fake-platform.c
+++ b/src/core/platform/nm-fake-platform.c
@@ -13,12 +13,9 @@
 #include <linux/if.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-utils.h"
-
-#include "nm-core-utils.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "nm-platform-private.h"
-#include "nmp-object.h"
+#include "libnm-platform/nm-platform-utils.h"
+#include "libnm-platform/nm-platform-private.h"
+#include "libnm-platform/nmp-object.h"
 
 #include "nm-test-utils-core.h"
 
@@ -506,6 +503,21 @@ link_set_flags(NMPlatform *platform, NMFakePlatformLink *device, guint n_ifi_fla
     link_set_obj(platform, device, obj_tmp);
 }
 
+static int
+link_change_flags(NMPlatform *platform, int ifindex, unsigned flags_mask, unsigned flags_set)
+{
+    NMFakePlatformLink *device = link_get(platform, ifindex);
+
+    if (!device)
+        return -ENOENT;
+
+    link_set_flags(platform,
+                   device,
+                   NM_FLAGS_ASSIGN_MASK(device->obj->link.n_ifi_flags, flags_mask, flags_set));
+
+    return 0;
+}
+
 static void
 link_changed(NMPlatform *        platform,
              NMFakePlatformLink *device,
@@ -543,72 +555,13 @@ link_changed(NMPlatform *        platform,
     }
 }
 
-static gboolean
-link_set_up(NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
-{
-    NMFakePlatformLink *device = link_get(platform, ifindex);
-
-    if (out_no_firmware)
-        *out_no_firmware = FALSE;
-
-    if (!device) {
-        _LOGE("failure changing link: netlink error (No such device)");
-        return FALSE;
-    }
-
-    link_set_flags(platform, device, NM_FLAGS_ASSIGN(device->obj->link.n_ifi_flags, IFF_UP, TRUE));
-    return TRUE;
-}
-
-static gboolean
-link_set_down(NMPlatform *platform, int ifindex)
-{
-    NMFakePlatformLink *device = link_get(platform, ifindex);
-
-    if (!device) {
-        _LOGE("failure changing link: netlink error (No such device)");
-        return FALSE;
-    }
-
-    link_set_flags(platform, device, NM_FLAGS_UNSET(device->obj->link.n_ifi_flags, IFF_UP));
-    return TRUE;
-}
-
-static gboolean
-link_set_arp(NMPlatform *platform, int ifindex)
-{
-    NMFakePlatformLink *device = link_get(platform, ifindex);
-
-    if (!device) {
-        _LOGE("failure changing link: netlink error (No such device)");
-        return FALSE;
-    }
-
-    link_set_flags(platform, device, NM_FLAGS_UNSET(device->obj->link.n_ifi_flags, IFF_NOARP));
-    return TRUE;
-}
-
-static gboolean
-link_set_noarp(NMPlatform *platform, int ifindex)
-{
-    NMFakePlatformLink *device = link_get(platform, ifindex);
-
-    if (!device) {
-        _LOGE("failure changing link: netlink error (No such device)");
-        return FALSE;
-    }
-
-    link_set_flags(platform, device, NM_FLAGS_SET(device->obj->link.n_ifi_flags, IFF_NOARP));
-    return TRUE;
-}
-
 static int
 link_set_address(NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
     NMFakePlatformLink *device        = link_get(platform, ifindex);
     nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-    if (len == 0 || len > NM_UTILS_HWADDR_LEN_MAX || !addr)
+    if (len == 0 || len > _NM_UTILS_HWADDR_LEN_MAX || !addr)
         g_return_val_if_reached(-NME_BUG);
 
     if (!device)
@@ -749,8 +702,8 @@ link_release(NMPlatform *platform, int master_idx, int slave_idx)
 static gboolean
 link_vlan_change(NMPlatform *            platform,
                  int                     ifindex,
-                 NMVlanFlags             flags_mask,
-                 NMVlanFlags             flags_set,
+                 _NMVlanFlags            flags_mask,
+                 _NMVlanFlags            flags_set,
                  gboolean                ingress_reset_all,
                  const NMVlanQosMapping *ingress_map,
                  gsize                   n_ingress_map,
@@ -799,7 +752,7 @@ infiniband_partition_add(NMPlatform *           platform,
     parent_device = link_get(platform, parent);
     g_return_val_if_fail(parent_device != NULL, FALSE);
 
-    nm_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key);
+    nmp_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key);
 
     link_add_one(platform, name, NM_LINK_TYPE_INFINIBAND, _infiniband_add_prepare, &d, out_link);
     return TRUE;
@@ -814,12 +767,12 @@ infiniband_partition_delete(NMPlatform *platform, int parent, int p_key)
     parent_device = link_get(platform, parent);
     g_return_val_if_fail(parent_device != NULL, FALSE);
 
-    nm_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key);
+    nmp_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key);
     return link_delete(platform, nm_platform_link_get_ifindex(platform, name));
 }
 
 static gboolean
-wifi_get_capabilities(NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps)
+wifi_get_capabilities(NMPlatform *platform, int ifindex, _NMDeviceWifiCapabilities *caps)
 {
     NMFakePlatformLink *device = link_get(platform, ifindex);
 
@@ -829,10 +782,10 @@ wifi_get_capabilities(NMPlatform *platform, int ifindex, NMDeviceWifiCapabilitie
         return FALSE;
 
     if (caps) {
-        *caps = (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104
-                 | NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP
-                 | NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN | NM_WIFI_DEVICE_CAP_AP
-                 | NM_WIFI_DEVICE_CAP_ADHOC);
+        *caps = (_NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | _NM_WIFI_DEVICE_CAP_CIPHER_WEP104
+                 | _NM_WIFI_DEVICE_CAP_CIPHER_TKIP | _NM_WIFI_DEVICE_CAP_CIPHER_CCMP
+                 | _NM_WIFI_DEVICE_CAP_WPA | _NM_WIFI_DEVICE_CAP_RSN | _NM_WIFI_DEVICE_CAP_AP
+                 | _NM_WIFI_DEVICE_CAP_ADHOC);
     }
     return TRUE;
 }
@@ -861,14 +814,14 @@ wifi_get_rate(NMPlatform *platform, int ifindex)
     return 0;
 }
 
-static NM80211Mode
+static _NM80211Mode
 wifi_get_mode(NMPlatform *platform, int ifindex)
 {
-    return NM_802_11_MODE_UNKNOWN;
+    return _NM_802_11_MODE_UNKNOWN;
 }
 
 static void
-wifi_set_mode(NMPlatform *platform, int ifindex, NM80211Mode mode)
+wifi_set_mode(NMPlatform *platform, int ifindex, _NM80211Mode mode)
 {
     ;
 }
@@ -1164,6 +1117,9 @@ ip_route_add(NMPlatform *             platform,
                                                 : NMP_OBJECT_TYPE_IP6_ROUTE,
                          (const NMPlatformObject *) route);
     r = NMP_OBJECT_CAST_IP_ROUTE(obj);
+
+    r->is_external = TRUE;
+
     nm_platform_ip_route_normalize(addr_family, r);
 
     switch (addr_family) {
@@ -1357,14 +1313,11 @@ nm_fake_platform_class_init(NMFakePlatformClass *klass)
     platform_class->link_add    = link_add;
     platform_class->link_delete = link_delete;
 
-    platform_class->link_set_up    = link_set_up;
-    platform_class->link_set_down  = link_set_down;
-    platform_class->link_set_arp   = link_set_arp;
-    platform_class->link_set_noarp = link_set_noarp;
-
     platform_class->link_set_address = link_set_address;
     platform_class->link_set_mtu     = link_set_mtu;
 
+    platform_class->link_change_flags = link_change_flags;
+
     platform_class->link_get_driver_info = link_get_driver_info;
 
     platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
diff --git a/src/core/platform/nm-fake-platform.h b/src/core/platform/nm-fake-platform.h
index a1d44f8f..8f8571fa 100644
--- a/src/core/platform/nm-fake-platform.h
+++ b/src/core/platform/nm-fake-platform.h
@@ -6,7 +6,7 @@
 #ifndef __NETWORKMANAGER_FAKE_PLATFORM_H__
 #define __NETWORKMANAGER_FAKE_PLATFORM_H__
 
-#include "nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 
 #define NM_TYPE_FAKE_PLATFORM (nm_fake_platform_get_type())
 #define NM_FAKE_PLATFORM(obj) \
diff --git a/src/core/platform/nm-linux-platform.c b/src/core/platform/nm-linux-platform.c
deleted file mode 100644
index 7bd42bba..00000000
--- a/src/core/platform/nm-linux-platform.c
+++ /dev/null
@@ -1,9696 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2012 - 2018 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-linux-platform.h"
-
-#include <arpa/inet.h>
-#include <dlfcn.h>
-#include <endian.h>
-#include <fcntl.h>
-#include <libudev.h>
-#include <net/ethernet.h>
-#include <linux/fib_rules.h>
-#include <linux/ip.h>
-#include <linux/if.h>
-#include <linux/if_bridge.h>
-#include <linux/if_link.h>
-#include <linux/if_tun.h>
-#include <linux/if_tunnel.h>
-#include <linux/if_vlan.h>
-#include <linux/ip6_tunnel.h>
-#include <linux/tc_act/tc_mirred.h>
-#include <netinet/icmp6.h>
-#include <netinet/in.h>
-#include <net/if_arp.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/statvfs.h>
-#include <unistd.h>
-
-#include "nm-std-aux/unaligned.h"
-
-#include "nm-utils.h"
-#include "nm-core-internal.h"
-#include "nm-setting-vlan.h"
-
-#include "nm-glib-aux/nm-secret-utils.h"
-#include "nm-glib-aux/nm-c-list.h"
-#include "nm-platform/nm-netlink.h"
-#include "nm-core-utils.h"
-#include "nmp-object.h"
-#include "nm-platform/nmp-netns.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "nm-platform-private.h"
-#include "wifi/nm-wifi-utils.h"
-#include "wifi/nm-wifi-utils-wext.h"
-#include "wpan/nm-wpan-utils.h"
-#include "nm-glib-aux/nm-io-utils.h"
-#include "nm-udev-aux/nm-udev-utils.h"
-
-/*****************************************************************************/
-
-/* re-implement <linux/tc_act/tc_defact.h> to build against kernel
- * headers that lack this. */
-
-#include <linux/pkt_cls.h>
-
-struct tc_defact {
-    tc_gen;
-};
-
-enum { TCA_DEF_UNSPEC, TCA_DEF_TM, TCA_DEF_PARMS, TCA_DEF_DATA, TCA_DEF_PAD, __TCA_DEF_MAX };
-#define TCA_DEF_MAX (__TCA_DEF_MAX - 1)
-
-/*****************************************************************************/
-
-/* Compat with older kernels. */
-
-#define TCA_FQ_CODEL_CE_THRESHOLD 7
-#define TCA_FQ_CODEL_MEMORY_LIMIT 9
-
-/*****************************************************************************/
-
-#define VLAN_FLAG_MVRP 0x8
-
-/*****************************************************************************/
-
-#define IFQDISCSIZ 32
-
-/*****************************************************************************/
-
-#ifndef IFLA_PROMISCUITY
-    #define IFLA_PROMISCUITY 30
-#endif
-#define IFLA_NUM_TX_QUEUES 31
-#define IFLA_NUM_RX_QUEUES 32
-#define IFLA_CARRIER       33
-#define IFLA_PHYS_PORT_ID  34
-#define IFLA_LINK_NETNSID  37
-#define __IFLA_MAX         39
-
-#define IFLA_INET6_TOKEN         7
-#define IFLA_INET6_ADDR_GEN_MODE 8
-#define __IFLA_INET6_MAX         9
-
-#define IFLA_VLAN_PROTOCOL 5
-#define __IFLA_VLAN_MAX    6
-
-#define IFA_FLAGS 8
-#define __IFA_MAX 9
-
-#define IFLA_MACVLAN_FLAGS 2
-#define __IFLA_MACVLAN_MAX 3
-
-#define IFLA_IPTUN_LINK        1
-#define IFLA_IPTUN_LOCAL       2
-#define IFLA_IPTUN_REMOTE      3
-#define IFLA_IPTUN_TTL         4
-#define IFLA_IPTUN_TOS         5
-#define IFLA_IPTUN_ENCAP_LIMIT 6
-#define IFLA_IPTUN_FLOWINFO    7
-#define IFLA_IPTUN_FLAGS       8
-#define IFLA_IPTUN_PROTO       9
-#define IFLA_IPTUN_PMTUDISC    10
-#define __IFLA_IPTUN_MAX       19
-#ifndef IFLA_IPTUN_MAX
-    #define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1)
-#endif
-
-#define IFLA_TUN_UNSPEC              0
-#define IFLA_TUN_OWNER               1
-#define IFLA_TUN_GROUP               2
-#define IFLA_TUN_TYPE                3
-#define IFLA_TUN_PI                  4
-#define IFLA_TUN_VNET_HDR            5
-#define IFLA_TUN_PERSIST             6
-#define IFLA_TUN_MULTI_QUEUE         7
-#define IFLA_TUN_NUM_QUEUES          8
-#define IFLA_TUN_NUM_DISABLED_QUEUES 9
-#define __IFLA_TUN_MAX               10
-#define IFLA_TUN_MAX                 (__IFLA_TUN_MAX - 1)
-
-G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1));
-#define RTA_PREF 20
-#undef RTA_MAX
-#define RTA_MAX (MAX((__RTA_MAX - 1), RTA_PREF))
-
-#ifndef MACVLAN_FLAG_NOPROMISC
-    #define MACVLAN_FLAG_NOPROMISC 1
-#endif
-
-#define IP6_FLOWINFO_TCLASS_MASK    0x0FF00000
-#define IP6_FLOWINFO_TCLASS_SHIFT   20
-#define IP6_FLOWINFO_FLOWLABEL_MASK 0x000FFFFF
-
-#define IFLA_BR_VLAN_STATS_ENABLED 41
-
-/*****************************************************************************/
-
-/* Appeared in the kernel prior to 3.13 dated 19 January, 2014 */
-#ifndef ARPHRD_6LOWPAN
-    #define ARPHRD_6LOWPAN 825
-#endif
-
-/*****************************************************************************/
-
-#define FRA_TUN_ID             12
-#define FRA_SUPPRESS_IFGROUP   13
-#define FRA_SUPPRESS_PREFIXLEN 14
-#define FRA_PAD                18
-#define FRA_L3MDEV             19
-#define FRA_UID_RANGE          20
-#define FRA_PROTOCOL           21
-#define FRA_IP_PROTO           22
-#define FRA_SPORT_RANGE        23
-#define FRA_DPORT_RANGE        24
-
-/*****************************************************************************/
-
-#define IFLA_MACSEC_UNSPEC         0
-#define IFLA_MACSEC_SCI            1
-#define IFLA_MACSEC_PORT           2
-#define IFLA_MACSEC_ICV_LEN        3
-#define IFLA_MACSEC_CIPHER_SUITE   4
-#define IFLA_MACSEC_WINDOW         5
-#define IFLA_MACSEC_ENCODING_SA    6
-#define IFLA_MACSEC_ENCRYPT        7
-#define IFLA_MACSEC_PROTECT        8
-#define IFLA_MACSEC_INC_SCI        9
-#define IFLA_MACSEC_ES             10
-#define IFLA_MACSEC_SCB            11
-#define IFLA_MACSEC_REPLAY_PROTECT 12
-#define IFLA_MACSEC_VALIDATION     13
-#define IFLA_MACSEC_PAD            14
-#define __IFLA_MACSEC_MAX          15
-
-/*****************************************************************************/
-
-#define WG_CMD_GET_DEVICE 0
-#define WG_CMD_SET_DEVICE 1
-
-#define WGDEVICE_F_REPLACE_PEERS ((guint32)(1U << 0))
-
-#define WGPEER_F_REMOVE_ME          ((guint32)(1U << 0))
-#define WGPEER_F_REPLACE_ALLOWEDIPS ((guint32)(1U << 1))
-
-#define WGDEVICE_A_UNSPEC      0
-#define WGDEVICE_A_IFINDEX     1
-#define WGDEVICE_A_IFNAME      2
-#define WGDEVICE_A_PRIVATE_KEY 3
-#define WGDEVICE_A_PUBLIC_KEY  4
-#define WGDEVICE_A_FLAGS       5
-#define WGDEVICE_A_LISTEN_PORT 6
-#define WGDEVICE_A_FWMARK      7
-#define WGDEVICE_A_PEERS       8
-#define WGDEVICE_A_MAX         8
-
-#define WGPEER_A_UNSPEC                        0
-#define WGPEER_A_PUBLIC_KEY                    1
-#define WGPEER_A_PRESHARED_KEY                 2
-#define WGPEER_A_FLAGS                         3
-#define WGPEER_A_ENDPOINT                      4
-#define WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL 5
-#define WGPEER_A_LAST_HANDSHAKE_TIME           6
-#define WGPEER_A_RX_BYTES                      7
-#define WGPEER_A_TX_BYTES                      8
-#define WGPEER_A_ALLOWEDIPS                    9
-#define WGPEER_A_MAX                           9
-
-#define WGALLOWEDIP_A_UNSPEC    0
-#define WGALLOWEDIP_A_FAMILY    1
-#define WGALLOWEDIP_A_IPADDR    2
-#define WGALLOWEDIP_A_CIDR_MASK 3
-#define WGALLOWEDIP_A_MAX       3
-
-/*****************************************************************************/
-
-/* Redefine VF enums and structures that are not available on older kernels. */
-
-#define IFLA_VF_UNSPEC       0
-#define IFLA_VF_MAC          1
-#define IFLA_VF_VLAN         2
-#define IFLA_VF_TX_RATE      3
-#define IFLA_VF_SPOOFCHK     4
-#define IFLA_VF_LINK_STATE   5
-#define IFLA_VF_RATE         6
-#define IFLA_VF_RSS_QUERY_EN 7
-#define IFLA_VF_STATS        8
-#define IFLA_VF_TRUST        9
-#define IFLA_VF_IB_NODE_GUID 10
-#define IFLA_VF_IB_PORT_GUID 11
-#define IFLA_VF_VLAN_LIST    12
-
-#define IFLA_VF_VLAN_INFO_UNSPEC 0
-#define IFLA_VF_VLAN_INFO        1
-
-/* valid for TRUST, SPOOFCHK, LINK_STATE, RSS_QUERY_EN */
-struct _ifla_vf_setting {
-    guint32 vf;
-    guint32 setting;
-};
-
-struct _ifla_vf_rate {
-    guint32 vf;
-    guint32 min_tx_rate;
-    guint32 max_tx_rate;
-};
-
-struct _ifla_vf_vlan_info {
-    guint32 vf;
-    guint32 vlan; /* 0 - 4095, 0 disables VLAN filter */
-    guint32 qos;
-    guint16 vlan_proto; /* VLAN protocol, either 802.1Q or 802.1ad */
-};
-
-/*****************************************************************************/
-
-/* Appeared in the kernel 4.0 dated April 12, 2015 */
-#ifndef BRIDGE_VLAN_INFO_RANGE_BEGIN
-    #define BRIDGE_VLAN_INFO_RANGE_BEGIN (1 << 3) /* VLAN is start of vlan range */
-    #define BRIDGE_VLAN_INFO_RANGE_END   (1 << 4) /* VLAN is end of vlan range */
-#endif
-
-/*****************************************************************************/
-
-#define PSCHED_TIME_UNITS_PER_SEC 1000000
-
-/*****************************************************************************/
-
-typedef enum {
-    INFINIBAND_ACTION_CREATE_CHILD,
-    INFINIBAND_ACTION_DELETE_CHILD,
-} InfinibandAction;
-
-typedef enum {
-    CHANGE_LINK_TYPE_UNSPEC,
-    CHANGE_LINK_TYPE_SET_MTU,
-    CHANGE_LINK_TYPE_SET_ADDRESS,
-} ChangeLinkType;
-
-typedef struct {
-    union {
-        struct {
-            gconstpointer address;
-            gsize         length;
-        } set_address;
-    };
-} ChangeLinkData;
-
-typedef enum {
-    _REFRESH_ALL_TYPE_FIRST = 0,
-
-    REFRESH_ALL_TYPE_LINKS             = 0,
-    REFRESH_ALL_TYPE_IP4_ADDRESSES     = 1,
-    REFRESH_ALL_TYPE_IP6_ADDRESSES     = 2,
-    REFRESH_ALL_TYPE_IP4_ROUTES        = 3,
-    REFRESH_ALL_TYPE_IP6_ROUTES        = 4,
-    REFRESH_ALL_TYPE_ROUTING_RULES_IP4 = 5,
-    REFRESH_ALL_TYPE_ROUTING_RULES_IP6 = 6,
-    REFRESH_ALL_TYPE_QDISCS            = 7,
-    REFRESH_ALL_TYPE_TFILTERS          = 8,
-
-    _REFRESH_ALL_TYPE_NUM,
-} RefreshAllType;
-
-typedef struct {
-    NMPObjectType obj_type;
-
-    /* for NLM_F_DUMP, which address family to request. */
-    int addr_family;
-} RefreshAllInfo;
-
-typedef enum {
-    DELAYED_ACTION_TYPE_NONE = 0,
-
-#define F(val, name) ((sizeof(char[(((val)) == (name)) ? 1 : -1]) * 0) + (val))
-    DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS             = 1 << F(0, REFRESH_ALL_TYPE_LINKS),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES     = 1 << F(1, REFRESH_ALL_TYPE_IP4_ADDRESSES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES     = 1 << F(2, REFRESH_ALL_TYPE_IP6_ADDRESSES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES        = 1 << F(3, REFRESH_ALL_TYPE_IP4_ROUTES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES        = 1 << F(4, REFRESH_ALL_TYPE_IP6_ROUTES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 = 1
-                                                        << F(5, REFRESH_ALL_TYPE_ROUTING_RULES_IP4),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6 = 1
-                                                        << F(6, REFRESH_ALL_TYPE_ROUTING_RULES_IP6),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS   = 1 << F(7, REFRESH_ALL_TYPE_QDISCS),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS = 1 << F(8, REFRESH_ALL_TYPE_TFILTERS),
-#undef F
-
-    DELAYED_ACTION_TYPE_REFRESH_LINK         = 1 << 9,
-    DELAYED_ACTION_TYPE_MASTER_CONNECTED     = 1 << 10,
-    DELAYED_ACTION_TYPE_READ_NETLINK         = 1 << 11,
-    DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE = 1 << 12,
-
-    __DELAYED_ACTION_TYPE_MAX,
-
-    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL =
-        DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
-
-    DELAYED_ACTION_TYPE_REFRESH_ALL =
-        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-
-    DELAYED_ACTION_TYPE_MAX = __DELAYED_ACTION_TYPE_MAX - 1,
-} DelayedActionType;
-
-#define FOR_EACH_DELAYED_ACTION(iflags, flags_all)          \
-    for ((iflags) = (DelayedActionType) 0x1LL; ({           \
-             gboolean _good = FALSE;                        \
-                                                            \
-             nm_assert(nm_utils_is_power_of_two(iflags));   \
-                                                            \
-             while ((iflags) <= DELAYED_ACTION_TYPE_MAX) {  \
-                 if (NM_FLAGS_ANY((flags_all), (iflags))) { \
-                     _good = TRUE;                          \
-                     break;                                 \
-                 }                                          \
-                 (iflags) <<= 1;                            \
-             }                                              \
-             _good;                                         \
-         });                                                \
-         (iflags) <<= 1)
-
-typedef enum {
-    /* Negative values are errors from kernel. Add dummy member to
-     * make enum signed. */
-    _WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = G_MININT,
-
-    WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0,
-    WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK,
-    WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN,
-    WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC,
-    WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL,
-    WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT,
-    WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING,
-    WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS,
-} WaitForNlResponseResult;
-
-typedef enum {
-    DELAYED_ACTION_RESPONSE_TYPE_VOID                    = 0,
-    DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS = 1,
-    DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET               = 2,
-} DelayedActionWaitForNlResponseType;
-
-typedef struct {
-    guint32                            seq_number;
-    WaitForNlResponseResult            seq_result;
-    DelayedActionWaitForNlResponseType response_type;
-    gint64                             timeout_abs_ns;
-    WaitForNlResponseResult *          out_seq_result;
-    char **                            out_errmsg;
-    union {
-        int *       out_refresh_all_in_progress;
-        NMPObject **out_route_get;
-        gpointer    out_data;
-    } response;
-} DelayedActionWaitForNlResponseData;
-
-/*****************************************************************************/
-
-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;
-
-    guint32 pruning[_REFRESH_ALL_TYPE_NUM];
-
-    GHashTable *sysctl_get_prev_values;
-    CList       sysctl_list;
-
-    NMUdevClient *udev_client;
-
-    struct {
-        /* which delayed actions are scheduled, as marked in @flags.
-         * Some types have additional arguments in the fields below. */
-        DelayedActionType flags;
-
-        /* counter that a refresh all action is in progress, separated
-         * by type. */
-        int refresh_all_in_progress[_REFRESH_ALL_TYPE_NUM];
-
-        GPtrArray *list_master_connected;
-        GPtrArray *list_refresh_link;
-        GArray *   list_wait_for_nl_response;
-
-        int is_handling;
-    } delayed_action;
-} NMLinuxPlatformPrivate;
-
-struct _NMLinuxPlatform {
-    NMPlatform             parent;
-    NMLinuxPlatformPrivate _priv;
-};
-
-struct _NMLinuxPlatformClass {
-    NMPlatformClass parent;
-};
-
-G_DEFINE_TYPE(NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-#define NM_LINUX_PLATFORM_GET_PRIVATE(self) \
-    _NM_GET_PRIVATE(self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform)
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME             "platform-linux"
-#define _NMLOG_DOMAIN                  LOGD_PLATFORM
-#define _NMLOG2_DOMAIN                 LOGD_PLATFORM
-#define _NMLOG(level, ...)             _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__)
-#define _NMLOG_err(errsv, level, ...)  _LOG_err(errsv, level, _NMLOG_DOMAIN, platform, __VA_ARGS__)
-#define _NMLOG2(level, ...)            _LOG(level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__)
-#define _NMLOG2_err(errsv, level, ...) _LOG_err(errsv, level, _NMLOG2_DOMAIN, NULL, __VA_ARGS__)
-
-#define _LOG_print(__level, __domain, __errsv, self, ...)                                 \
-    G_STMT_START                                                                          \
-    {                                                                                     \
-        char              __prefix[32];                                                   \
-        const char *      __p_prefix = _NMLOG_PREFIX_NAME;                                \
-        NMPlatform *const __self     = (self);                                            \
-                                                                                          \
-        if (__self && nm_platform_get_log_with_ptr(__self)) {                             \
-            g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-            __p_prefix = __prefix;                                                        \
-        }                                                                                 \
-        _nm_log(__level,                                                                  \
-                __domain,                                                                 \
-                __errsv,                                                                  \
-                NULL,                                                                     \
-                NULL,                                                                     \
-                "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                                \
-                __p_prefix _NM_UTILS_MACRO_REST(__VA_ARGS__));                            \
-    }                                                                                     \
-    G_STMT_END
-
-#define _LOG(level, domain, self, ...)                           \
-    G_STMT_START                                                 \
-    {                                                            \
-        const NMLogLevel  __level  = (level);                    \
-        const NMLogDomain __domain = (domain);                   \
-                                                                 \
-        if (nm_logging_enabled(__level, __domain)) {             \
-            _LOG_print(__level, __domain, 0, self, __VA_ARGS__); \
-        }                                                        \
-    }                                                            \
-    G_STMT_END
-
-#define _LOG_err(errsv, level, domain, self, ...)                                                   \
-    G_STMT_START                                                                                    \
-    {                                                                                               \
-        const NMLogLevel  __level  = (level);                                                       \
-        const NMLogDomain __domain = (domain);                                                      \
-                                                                                                    \
-        if (nm_logging_enabled(__level, __domain)) {                                                \
-            int __errsv = (errsv);                                                                  \
-                                                                                                    \
-            /* The %m format specifier (GNU extension) would already allow you to specify the error
-             * message conveniently (and nm_log would get that right too). But we don't want to depend
-             * on that, so instead append the message at the end.
-             * Currently, users are expected not to use %m in the format string. */ \
-            _LOG_print(                                                                             \
-                __level,                                                                            \
-                __domain,                                                                           \
-                __errsv,                                                                            \
-                self,                                                                               \
-                _NM_UTILS_MACRO_FIRST(__VA_ARGS__) ": %s (%d)" _NM_UTILS_MACRO_REST(__VA_ARGS__),   \
-                nm_strerror_native(__errsv),                                                        \
-                __errsv);                                                                           \
-        }                                                                                           \
-    }                                                                                               \
-    G_STMT_END
-
-/*****************************************************************************/
-
-static void
-delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
-static gboolean delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink);
-static void do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name);
-static void do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type);
-static void cache_on_change(NMPlatform *     platform,
-                            NMPCacheOpsType  cache_op,
-                            const NMPObject *obj_old,
-                            const NMPObject *obj_new);
-static void cache_prune_all(NMPlatform *platform);
-static gboolean        event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks);
-static struct nl_sock *_genl_sock(NMLinuxPlatform *platform);
-
-/*****************************************************************************/
-
-static int
-wait_for_nl_response_to_nmerr(WaitForNlResponseResult seq_result)
-{
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-        return 0;
-    if (seq_result < 0)
-        return (int) seq_result;
-    return -NME_PL_NETLINK;
-}
-
-static const char *
-wait_for_nl_response_to_string(WaitForNlResponseResult seq_result,
-                               const char *            errmsg,
-                               char *                  buf,
-                               gsize                   buf_size)
-{
-    char *buf0 = buf;
-
-    switch (seq_result) {
-    case WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN:
-        nm_utils_strbuf_append_str(&buf, &buf_size, "unknown");
-        break;
-    case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK:
-        nm_utils_strbuf_append_str(&buf, &buf_size, "success");
-        break;
-    case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN:
-        nm_utils_strbuf_append_str(&buf, &buf_size, "failure");
-        break;
-    default:
-        if (seq_result < 0) {
-            nm_utils_strbuf_append(&buf,
-                                   &buf_size,
-                                   "failure %d (%s%s%s)",
-                                   -((int) seq_result),
-                                   nm_strerror_native(-((int) seq_result)),
-                                   errmsg ? " - " : "",
-                                   errmsg ?: "");
-        } else
-            nm_utils_strbuf_append(&buf, &buf_size, "internal failure %d", (int) seq_result);
-        break;
-    }
-    return buf0;
-}
-
-/******************************************************************
- * Various utilities
- ******************************************************************/
-
-static int
-_vlan_qos_mapping_cmp_from(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    const NMVlanQosMapping *map_a = a;
-    const NMVlanQosMapping *map_b = b;
-
-    if (map_a->from != map_b->from)
-        return map_a->from < map_b->from ? -1 : 1;
-    return 0;
-}
-
-static int
-_vlan_qos_mapping_cmp_from_ptr(gconstpointer a, gconstpointer b, gpointer user_data)
-{
-    return _vlan_qos_mapping_cmp_from(*((const NMVlanQosMapping **) a),
-                                      *((const NMVlanQosMapping **) b),
-                                      NULL);
-}
-
-/******************************************************************
- * NMLinkType functions
- ******************************************************************/
-
-typedef struct {
-    const char *type_string;
-
-    /* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type
-     * should only be specified if the device type can be created without
-     * additional parameters, and if the device type can be determined from
-     * the rtnl_type.  eg, tun/tap should not be specified since both
-     * tun and tap devices use "tun", and InfiniBand should not be
-     * specified because a PKey is required at creation. Drivers set this
-     * value from their 'struct rtnl_link_ops' structure.
-     */
-    const char *rtnl_type;
-
-    /* uevent DEVTYPE where applicable, from /sys/class/net/<ifname>/uevent;
-     * drivers set this value from their SET_NETDEV_DEV() call and the
-     * 'struct device_type' name member.
-     */
-    const char *devtype;
-} LinkDesc;
-
-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 LinkDesc *
-_link_desc_from_link_type(NMLinkType link_type)
-{
-    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);
-
-    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);
-        }
-    }
-
-    {
-        int imin = 0;
-        int imax = (G_N_ELEMENTS(LIST) - 1);
-        int 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 + 1;
-            else
-                imax = imid - 1;
-
-            if (G_UNLIKELY(imin > imax))
-                return NM_LINK_TYPE_NONE;
-
-            imid = (imin + imax) / 2;
-        }
-    }
-}
-
-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);
-
-    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);
-        }
-    }
-
-    {
-        int imin = 0;
-        int imax = (G_N_ELEMENTS(LIST) - 1);
-        int 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 + 1;
-            else
-                imax = imid - 1;
-
-            if (G_UNLIKELY(imin > imax))
-                return NM_LINK_TYPE_NONE;
-
-            imid = (imin + imax) / 2;
-        }
-    }
-}
-
-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;
-}
-
-/******************************************************************
- * Utilities
- ******************************************************************/
-
-/* _timestamp_nl_to_ms:
- * @timestamp_nl: a timestamp from ifa_cacheinfo.
- * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current
- * uptime and how often timestamp_nl wrapped.
- *
- * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds.
- * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts
- * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However,
- * the uint32 counter wraps every 497 days of uptime, so we have to compensate
- * for that. */
-static gint64
-_timestamp_nl_to_ms(guint32 timestamp_nl, gint64 monotonic_ms)
-{
-    const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100);
-    gint64       timestamp_nl_ms;
-
-    /* convert timestamp from 1/100th of a second to msec. */
-    timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100);
-
-    /* timestamp wraps every 497 days. Try to compensate for that.*/
-    if (timestamp_nl_ms > monotonic_ms) {
-        /* timestamp_nl_ms is in the future. Truncate it to *now* */
-        timestamp_nl_ms = monotonic_ms;
-    } else if (monotonic_ms >= WRAP_INTERVAL) {
-        timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL;
-        if (timestamp_nl_ms > monotonic_ms)
-            timestamp_nl_ms -= WRAP_INTERVAL;
-    }
-
-    return timestamp_nl_ms;
-}
-
-static guint32
-_addrtime_timestamp_to_nm(guint32 timestamp, gint32 *out_now_nm)
-{
-    gint64 now_nl;
-    gint64 now_nm;
-    gint64 result;
-
-    /* timestamp is unset. Default to 1. */
-    if (!timestamp) {
-        NM_SET_OUT(out_now_nm, 0);
-        return 1;
-    }
-
-    /* do all the calculations in milliseconds scale */
-
-    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);
-
-    result = now_nm - (now_nl - _timestamp_nl_to_ms(timestamp, now_nl));
-
-    NM_SET_OUT(out_now_nm, now_nm / 1000);
-
-    /* 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_msec() scale.
-     */
-    if (result <= 1000)
-        return 1;
-
-    if (result > now_nm)
-        return now_nm / 1000;
-
-    return result / 1000;
-}
-
-static guint32
-_addrtime_extend_lifetime(guint32 lifetime, guint32 seconds)
-{
-    guint64 v;
-
-    if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT || seconds == 0)
-        return lifetime;
-
-    v = (guint64) lifetime + (guint64) seconds;
-    return MIN(v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
-}
-
-/* The rtnl_addr object contains relative lifetimes @valid and @preferred
- * that count in seconds, starting from the moment when the kernel constructed
- * the netlink message.
- *
- * There is also a field rtnl_addr_last_update_time(), which is the absolute
- * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
- * was modified (wrapping every 497 days).
- * Immediately at the time when the address was last modified, #NOW and @last_update_time
- * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
- * However, this is not true in general. As time goes by, whenever kernel sends a new address
- * via netlink, the lifetimes keep counting down.
- **/
-static void
-_addrtime_get_lifetimes(guint32  timestamp,
-                        guint32  lifetime,
-                        guint32  preferred,
-                        guint32 *out_timestamp,
-                        guint32 *out_lifetime,
-                        guint32 *out_preferred)
-{
-    gint32 now;
-
-    if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
-        if (preferred > lifetime)
-            preferred = lifetime;
-        timestamp = _addrtime_timestamp_to_nm(timestamp, &now);
-
-        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_sec();
-        }
-        if (timestamp < now) {
-            guint32 diff = now - timestamp;
-
-            lifetime  = _addrtime_extend_lifetime(lifetime, diff);
-            preferred = _addrtime_extend_lifetime(preferred, diff);
-        } else
-            nm_assert(timestamp == now);
-    } else
-        timestamp = 0;
-    *out_timestamp = timestamp;
-    *out_lifetime  = lifetime;
-    *out_preferred = preferred;
-}
-
-/*****************************************************************************/
-
-static const NMPObject *
-_lookup_cached_link(const NMPCache *  cache,
-                    int               ifindex,
-                    gboolean *        completed_from_cache,
-                    const NMPObject **link_cached)
-{
-    const NMPObject *obj;
-
-    nm_assert(completed_from_cache && link_cached);
-
-    if (!*completed_from_cache) {
-        obj = ifindex > 0 && cache ? nmp_cache_lookup_link(cache, ifindex) : NULL;
-
-        *link_cached          = obj;
-        *completed_from_cache = TRUE;
-    }
-    return *link_cached;
-}
-
-/*****************************************************************************/
-
-#define DEVTYPE_PREFIX "DEVTYPE="
-
-static char *
-_linktype_read_devtype(int dirfd)
-{
-    gs_free char *contents = NULL;
-    char *        cont, *end;
-
-    nm_assert(dirfd >= 0);
-
-    if (!nm_utils_file_get_contents(dirfd,
-                                    "uevent",
-                                    1 * 1024 * 1024,
-                                    NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
-                                    &contents,
-                                    NULL,
-                                    NULL,
-                                    NULL))
-        return NULL;
-    for (cont = contents; cont; cont = end) {
-        end = strpbrk(cont, "\r\n");
-        if (end)
-            *end++ = '\0';
-        if (strncmp(cont, DEVTYPE_PREFIX, NM_STRLEN(DEVTYPE_PREFIX)) == 0) {
-            cont += NM_STRLEN(DEVTYPE_PREFIX);
-            memmove(contents, cont, strlen(cont) + 1);
-            return g_steal_pointer(&contents);
-        }
-    }
-    return NULL;
-}
-
-static NMLinkType
-_linktype_get_type(NMPlatform *      platform,
-                   const NMPCache *  cache,
-                   const char *      kind,
-                   int               ifindex,
-                   const char *      ifname,
-                   unsigned          flags,
-                   unsigned          arptype,
-                   gboolean *        completed_from_cache,
-                   const NMPObject **link_cached,
-                   const char **     out_kind)
-{
-    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;
-
-        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" or "name" changed. If "name" changed,
-         * it means that their type may not have been determined correctly
-         * due to race conditions while accessing sysfs.
-         *
-         * This way, we save additional ethtool/sysctl lookups, but moreover,
-         * we keep the linktype stable and don't change it as long as the link
-         * exists.
-         *
-         * Note that kernel *can* reuse the ifindex (on integer overflow, and
-         * when moving interface to other netns). Thus here there is a tiny potential
-         * of messing stuff up. */
-        if (obj && obj->_link.netlink.is_in_netlink
-            && !NM_IN_SET(obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
-            && nm_streq(ifname, obj->link.name) && (!kind || nm_streq0(kind, obj->link.kind))) {
-            nm_assert(obj->link.kind == g_intern_string(obj->link.kind));
-            *out_kind = obj->link.kind;
-            return obj->link.type;
-        }
-    }
-
-    /* we intern kind to not require us to keep the pointer alive. Essentially
-     * leaking it in a global cache. That should be safe enough, because the
-     * kind comes only from kernel messages, which depend on the number of
-     * available drivers. So, there is not the danger that we leak uncontrolled
-     * many kinds. */
-    *out_kind = g_intern_string(kind);
-
-    if (kind) {
-        link_type = _link_type_from_rtnl_type(kind);
-        if (link_type != NM_LINK_TYPE_NONE)
-            return link_type;
-    }
-
-    if (arptype == ARPHRD_LOOPBACK)
-        return NM_LINK_TYPE_LOOPBACK;
-    else if (arptype == ARPHRD_INFINIBAND)
-        return NM_LINK_TYPE_INFINIBAND;
-    else if (arptype == ARPHRD_SIT)
-        return NM_LINK_TYPE_SIT;
-    else if (arptype == ARPHRD_TUNNEL6)
-        return NM_LINK_TYPE_IP6TNL;
-    else if (arptype == ARPHRD_PPP)
-        return NM_LINK_TYPE_PPP;
-    else if (arptype == ARPHRD_IEEE802154)
-        return NM_LINK_TYPE_WPAN;
-    else if (arptype == ARPHRD_6LOWPAN)
-        return NM_LINK_TYPE_6LOWPAN;
-
-    {
-        NMPUtilsEthtoolDriverInfo driver_info;
-
-        /* Fallback OVS detection for kernel <= 3.16 */
-        if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) {
-            if (nm_streq(driver_info.driver, "openvswitch"))
-                return NM_LINK_TYPE_OPENVSWITCH;
-
-            if (arptype == 256) {
-                /* Some s390 CTC-type devices report 256 for the encapsulation type
-                 * for some reason, but we need to call them Ethernet.
-                 */
-                if (nm_streq(driver_info.driver, "ctcm"))
-                    return NM_LINK_TYPE_ETHERNET;
-            }
-        }
-    }
-
-    {
-        nm_auto_close int dirfd   = -1;
-        gs_free char *    devtype = NULL;
-        char              ifname_verified[IFNAMSIZ];
-
-        dirfd = nmp_utils_sysctl_open_netdir(ifindex, ifname, ifname_verified);
-        if (dirfd >= 0) {
-            if (faccessat(dirfd, "anycast_mask", F_OK, 0) == 0)
-                return NM_LINK_TYPE_OLPC_MESH;
-
-            devtype = _linktype_read_devtype(dirfd);
-            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.
-                         */
-                    } else
-                        return link_type;
-                }
-            }
-
-            /* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */
-            if (nm_wifi_utils_is_wifi(dirfd, ifname_verified))
-                return NM_LINK_TYPE_WIFI;
-        }
-
-        if (arptype == ARPHRD_ETHER) {
-            /* Misc non-upstream WWAN drivers.  rmnet is Qualcomm's proprietary
-             * modem interface, ccmni is MediaTek's.  FIXME: these drivers should
-             * really set devtype=WWAN.
-             */
-            if (g_str_has_prefix(ifname, "rmnet") || g_str_has_prefix(ifname, "rev_rmnet")
-                || g_str_has_prefix(ifname, "ccmni"))
-                return NM_LINK_TYPE_WWAN_NET;
-
-            /* Standard wired ethernet interfaces don't report an rtnl_link_type, so
-             * only allow fallback to Ethernet if no type is given.  This should
-             * prevent future virtual network drivers from being treated as Ethernet
-             * when they should be Generic instead.
-             */
-            if (!kind && !devtype)
-                return NM_LINK_TYPE_ETHERNET;
-
-            /* The USB gadget interfaces behave and look like ordinary ethernet devices
-             * aside from the DEVTYPE. */
-            if (nm_streq0(devtype, "gadget"))
-                return NM_LINK_TYPE_ETHERNET;
-
-            /* Distributed Switch Architecture switch chips */
-            if (nm_streq0(devtype, "dsa"))
-                return NM_LINK_TYPE_ETHERNET;
-        }
-    }
-
-    return NM_LINK_TYPE_UNKNOWN;
-}
-
-/******************************************************************
- * libnl unility functions and wrappers
- ******************************************************************/
-
-#define NLMSG_TAIL(nmsg) ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
-
-/* copied from iproute2's addattr_l(). */
-static gboolean
-_nl_addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen)
-{
-    int            len = RTA_LENGTH(alen);
-    struct rtattr *rta;
-
-    if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen)
-        return FALSE;
-
-    rta           = NLMSG_TAIL(n);
-    rta->rta_type = type;
-    rta->rta_len  = len;
-    memcpy(RTA_DATA(rta), data, alen);
-    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
-    return TRUE;
-}
-
-/******************************************************************
- * NMPObject/netlink functions
- ******************************************************************/
-
-#define _check_addr_or_return_val(tb, attr, addr_len, ret_val) \
-    ({                                                         \
-        const struct nlattr *__t = (tb)[(attr)];               \
-                                                               \
-        if (__t) {                                             \
-            if (nla_len(__t) != (addr_len)) {                  \
-                return ret_val;                                \
-            }                                                  \
-        }                                                      \
-        !!__t;                                                 \
-    })
-
-#define _check_addr_or_return_null(tb, attr, addr_len) \
-    _check_addr_or_return_val(tb, attr, addr_len, NULL)
-
-/*****************************************************************************/
-
-/* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */
-static gboolean
-_parse_af_inet6(NMPlatform *        platform,
-                struct nlattr *     attr,
-                NMUtilsIPv6IfaceId *out_token,
-                gboolean *          out_token_valid,
-                guint8 *            out_addr_gen_mode_inv,
-                gboolean *          out_addr_gen_mode_valid)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_INET6_FLAGS]      = {.type = NLA_U32},
-        [IFLA_INET6_CACHEINFO]  = {.minlen = nm_offsetofend(struct ifla_cacheinfo, retrans_time)},
-        [IFLA_INET6_CONF]       = {.minlen = 4},
-        [IFLA_INET6_STATS]      = {.minlen = 8},
-        [IFLA_INET6_ICMP6STATS] = {.minlen = 8},
-        [IFLA_INET6_TOKEN]      = {.minlen = sizeof(struct in6_addr)},
-        [IFLA_INET6_ADDR_GEN_MODE] = {.type = NLA_U8},
-    };
-    struct nlattr * tb[G_N_ELEMENTS(policy)];
-    struct in6_addr i6_token;
-    gboolean        token_valid          = FALSE;
-    gboolean        addr_gen_mode_valid  = FALSE;
-    guint8          i6_addr_gen_mode_inv = 0;
-
-    if (nla_parse_nested_arr(tb, attr, policy) < 0)
-        return FALSE;
-
-    if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
-        return FALSE;
-    if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
-        return FALSE;
-    if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
-        return FALSE;
-
-    if (_check_addr_or_return_val(tb, IFLA_INET6_TOKEN, sizeof(struct in6_addr), FALSE)) {
-        nla_memcpy(&i6_token, tb[IFLA_INET6_TOKEN], sizeof(struct in6_addr));
-        token_valid = TRUE;
-    }
-
-    /* Hack to detect support addrgenmode of the kernel. We only parse
-     * netlink messages that we receive from kernel, hence this check
-     * is valid. */
-    if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
-        /* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */
-        _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL,
-                                         tb[IFLA_INET6_ADDR_GEN_MODE] ? 1 : -1);
-    }
-
-    if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
-        i6_addr_gen_mode_inv = _nm_platform_uint8_inv(nla_get_u8(tb[IFLA_INET6_ADDR_GEN_MODE]));
-        if (i6_addr_gen_mode_inv == 0) {
-            /* an inverse addrgenmode of zero is unexpected. We need to reserve zero
-             * to signal "unset". */
-            return FALSE;
-        }
-        addr_gen_mode_valid = TRUE;
-    }
-
-    if (token_valid) {
-        *out_token_valid = token_valid;
-        nm_utils_ipv6_interface_identifier_get_from_addr(out_token, &i6_token);
-    }
-    if (addr_gen_mode_valid) {
-        *out_addr_gen_mode_valid = addr_gen_mode_valid;
-        *out_addr_gen_mode_inv   = i6_addr_gen_mode_inv;
-    }
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_bridge(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_BR_FORWARD_DELAY]              = {.type = NLA_U32},
-        [IFLA_BR_HELLO_TIME]                 = {.type = NLA_U32},
-        [IFLA_BR_MAX_AGE]                    = {.type = NLA_U32},
-        [IFLA_BR_AGEING_TIME]                = {.type = NLA_U32},
-        [IFLA_BR_STP_STATE]                  = {.type = NLA_U32},
-        [IFLA_BR_PRIORITY]                   = {.type = NLA_U16},
-        [IFLA_BR_VLAN_PROTOCOL]              = {.type = NLA_U16},
-        [IFLA_BR_VLAN_STATS_ENABLED]         = {.type = NLA_U8},
-        [IFLA_BR_GROUP_FWD_MASK]             = {.type = NLA_U16},
-        [IFLA_BR_GROUP_ADDR]                 = {.minlen = sizeof(NMEtherAddr)},
-        [IFLA_BR_MCAST_SNOOPING]             = {.type = NLA_U8},
-        [IFLA_BR_MCAST_ROUTER]               = {.type = NLA_U8},
-        [IFLA_BR_MCAST_QUERY_USE_IFADDR]     = {.type = NLA_U8},
-        [IFLA_BR_MCAST_QUERIER]              = {.type = NLA_U8},
-        [IFLA_BR_MCAST_HASH_MAX]             = {.type = NLA_U32},
-        [IFLA_BR_MCAST_LAST_MEMBER_CNT]      = {.type = NLA_U32},
-        [IFLA_BR_MCAST_STARTUP_QUERY_CNT]    = {.type = NLA_U32},
-        [IFLA_BR_MCAST_LAST_MEMBER_INTVL]    = {.type = NLA_U64},
-        [IFLA_BR_MCAST_MEMBERSHIP_INTVL]     = {.type = NLA_U64},
-        [IFLA_BR_MCAST_QUERIER_INTVL]        = {.type = NLA_U64},
-        [IFLA_BR_MCAST_QUERY_INTVL]          = {.type = NLA_U64},
-        [IFLA_BR_MCAST_QUERY_RESPONSE_INTVL] = {.type = NLA_U64},
-        [IFLA_BR_MCAST_STARTUP_QUERY_INTVL]  = {.type = NLA_U64},
-    };
-    NMPlatformLnkBridge *props;
-    struct nlattr *      tb[G_N_ELEMENTS(policy)];
-    NMPObject *          obj;
-
-    if (!info_data || !nm_streq0(kind, "bridge"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_BRIDGE, NULL);
-
-    props  = &obj->lnk_bridge;
-    *props = nm_platform_lnk_bridge_default;
-
-    if (!_nm_platform_kernel_support_detected(
-            NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED)) {
-        /* IFLA_BR_VLAN_STATS_ENABLED was added in kernel 4.10 on April 30, 2016.
-         * See commit 6dada9b10a0818ba72c249526a742c8c41274a73. */
-        _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED,
-                                         tb[IFLA_BR_VLAN_STATS_ENABLED] ? 1 : -1);
-    }
-
-    if (tb[IFLA_BR_FORWARD_DELAY])
-        props->forward_delay = nla_get_u32(tb[IFLA_BR_FORWARD_DELAY]);
-    if (tb[IFLA_BR_HELLO_TIME])
-        props->hello_time = nla_get_u32(tb[IFLA_BR_HELLO_TIME]);
-    if (tb[IFLA_BR_MAX_AGE])
-        props->max_age = nla_get_u32(tb[IFLA_BR_MAX_AGE]);
-    if (tb[IFLA_BR_AGEING_TIME])
-        props->ageing_time = nla_get_u32(tb[IFLA_BR_AGEING_TIME]);
-    if (tb[IFLA_BR_STP_STATE])
-        props->stp_state = !!nla_get_u32(tb[IFLA_BR_STP_STATE]);
-    if (tb[IFLA_BR_PRIORITY])
-        props->priority = nla_get_u16(tb[IFLA_BR_PRIORITY]);
-    if (tb[IFLA_BR_VLAN_PROTOCOL])
-        props->vlan_protocol = ntohs(nla_get_u16(tb[IFLA_BR_VLAN_PROTOCOL]));
-    if (tb[IFLA_BR_VLAN_STATS_ENABLED])
-        props->vlan_stats_enabled = nla_get_u8(tb[IFLA_BR_VLAN_STATS_ENABLED]);
-    if (tb[IFLA_BR_GROUP_FWD_MASK])
-        props->group_fwd_mask = nla_get_u16(tb[IFLA_BR_GROUP_FWD_MASK]);
-    if (tb[IFLA_BR_GROUP_ADDR])
-        props->group_addr = *nla_data_as(NMEtherAddr, tb[IFLA_BR_GROUP_ADDR]);
-    if (tb[IFLA_BR_MCAST_SNOOPING])
-        props->mcast_snooping = !!nla_get_u8(tb[IFLA_BR_MCAST_SNOOPING]);
-    if (tb[IFLA_BR_MCAST_ROUTER])
-        props->mcast_router = nla_get_u8(tb[IFLA_BR_MCAST_ROUTER]);
-    if (tb[IFLA_BR_MCAST_QUERY_USE_IFADDR])
-        props->mcast_query_use_ifaddr = !!nla_get_u8(tb[IFLA_BR_MCAST_QUERY_USE_IFADDR]);
-    if (tb[IFLA_BR_MCAST_QUERIER])
-        props->mcast_querier = nla_get_u8(tb[IFLA_BR_MCAST_QUERIER]);
-    if (tb[IFLA_BR_MCAST_HASH_MAX])
-        props->mcast_hash_max = nla_get_u32(tb[IFLA_BR_MCAST_HASH_MAX]);
-    if (tb[IFLA_BR_MCAST_LAST_MEMBER_CNT])
-        props->mcast_last_member_count = nla_get_u32(tb[IFLA_BR_MCAST_LAST_MEMBER_CNT]);
-    if (tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT])
-        props->mcast_startup_query_count = nla_get_u32(tb[IFLA_BR_MCAST_STARTUP_QUERY_CNT]);
-    if (tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL])
-        props->mcast_last_member_interval = nla_get_u64(tb[IFLA_BR_MCAST_LAST_MEMBER_INTVL]);
-    if (tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL])
-        props->mcast_membership_interval = nla_get_u64(tb[IFLA_BR_MCAST_MEMBERSHIP_INTVL]);
-    if (tb[IFLA_BR_MCAST_QUERIER_INTVL])
-        props->mcast_querier_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERIER_INTVL]);
-    if (tb[IFLA_BR_MCAST_QUERY_INTVL])
-        props->mcast_query_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERY_INTVL]);
-    if (tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL])
-        props->mcast_query_response_interval = nla_get_u64(tb[IFLA_BR_MCAST_QUERY_RESPONSE_INTVL]);
-    if (tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL])
-        props->mcast_startup_query_interval = nla_get_u64(tb[IFLA_BR_MCAST_STARTUP_QUERY_INTVL]);
-
-    return obj;
-}
-
-/***********************************************************************************/
-
-static NMPObject *
-_parse_lnk_gre(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_GRE_LINK]     = {.type = NLA_U32},
-        [IFLA_GRE_IFLAGS]   = {.type = NLA_U16},
-        [IFLA_GRE_OFLAGS]   = {.type = NLA_U16},
-        [IFLA_GRE_IKEY]     = {.type = NLA_U32},
-        [IFLA_GRE_OKEY]     = {.type = NLA_U32},
-        [IFLA_GRE_LOCAL]    = {.type = NLA_U32},
-        [IFLA_GRE_REMOTE]   = {.type = NLA_U32},
-        [IFLA_GRE_TTL]      = {.type = NLA_U8},
-        [IFLA_GRE_TOS]      = {.type = NLA_U8},
-        [IFLA_GRE_PMTUDISC] = {.type = NLA_U8},
-    };
-    struct nlattr *   tb[G_N_ELEMENTS(policy)];
-    NMPObject *       obj;
-    NMPlatformLnkGre *props;
-    gboolean          is_tap;
-
-    if (!info_data || !kind)
-        return NULL;
-
-    if (nm_streq(kind, "gretap"))
-        is_tap = TRUE;
-    else if (nm_streq(kind, "gre"))
-        is_tap = FALSE;
-    else
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj   = nmp_object_new(is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL);
-    props = &obj->lnk_gre;
-
-    props->parent_ifindex     = tb[IFLA_GRE_LINK] ? nla_get_u32(tb[IFLA_GRE_LINK]) : 0;
-    props->input_flags        = tb[IFLA_GRE_IFLAGS] ? ntohs(nla_get_u16(tb[IFLA_GRE_IFLAGS])) : 0;
-    props->output_flags       = tb[IFLA_GRE_OFLAGS] ? ntohs(nla_get_u16(tb[IFLA_GRE_OFLAGS])) : 0;
-    props->input_key          = tb[IFLA_GRE_IKEY] ? ntohl(nla_get_u32(tb[IFLA_GRE_IKEY])) : 0;
-    props->output_key         = tb[IFLA_GRE_OKEY] ? ntohl(nla_get_u32(tb[IFLA_GRE_OKEY])) : 0;
-    props->local              = tb[IFLA_GRE_LOCAL] ? nla_get_u32(tb[IFLA_GRE_LOCAL]) : 0;
-    props->remote             = tb[IFLA_GRE_REMOTE] ? nla_get_u32(tb[IFLA_GRE_REMOTE]) : 0;
-    props->tos                = tb[IFLA_GRE_TOS] ? nla_get_u8(tb[IFLA_GRE_TOS]) : 0;
-    props->ttl                = tb[IFLA_GRE_TTL] ? nla_get_u8(tb[IFLA_GRE_TTL]) : 0;
-    props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8(tb[IFLA_GRE_PMTUDISC]);
-    props->is_tap             = is_tap;
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers
- * we're building against might not have those properties even though the
- * running kernel might.
- */
-#define IFLA_IPOIB_UNSPEC 0
-#define IFLA_IPOIB_PKEY   1
-#define IFLA_IPOIB_MODE   2
-#define IFLA_IPOIB_UMCAST 3
-#undef IFLA_IPOIB_MAX
-#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST
-
-#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
-#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
-
-static NMPObject *
-_parse_lnk_infiniband(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_IPOIB_PKEY]   = {.type = NLA_U16},
-        [IFLA_IPOIB_MODE]   = {.type = NLA_U16},
-        [IFLA_IPOIB_UMCAST] = {.type = NLA_U16},
-    };
-    struct nlattr *          tb[G_N_ELEMENTS(policy)];
-    NMPlatformLnkInfiniband *info;
-    NMPObject *              obj;
-    const char *             mode;
-
-    if (!info_data || !nm_streq0(kind, "ipoib"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
-        return NULL;
-
-    switch (nla_get_u16(tb[IFLA_IPOIB_MODE])) {
-    case IPOIB_MODE_DATAGRAM:
-        mode = "datagram";
-        break;
-    case IPOIB_MODE_CONNECTED:
-        mode = "connected";
-        break;
-    default:
-        return NULL;
-    }
-
-    obj  = nmp_object_new(NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL);
-    info = &obj->lnk_infiniband;
-
-    info->p_key = nla_get_u16(tb[IFLA_IPOIB_PKEY]);
-    info->mode  = mode;
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_ip6tnl(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_IPTUN_LINK]        = {.type = NLA_U32},
-        [IFLA_IPTUN_LOCAL]       = {.minlen = sizeof(struct in6_addr)},
-        [IFLA_IPTUN_REMOTE]      = {.minlen = sizeof(struct in6_addr)},
-        [IFLA_IPTUN_TTL]         = {.type = NLA_U8},
-        [IFLA_IPTUN_ENCAP_LIMIT] = {.type = NLA_U8},
-        [IFLA_IPTUN_FLOWINFO]    = {.type = NLA_U32},
-        [IFLA_IPTUN_PROTO]       = {.type = NLA_U8},
-        [IFLA_IPTUN_FLAGS]       = {.type = NLA_U32},
-    };
-    struct nlattr *      tb[G_N_ELEMENTS(policy)];
-    NMPObject *          obj;
-    NMPlatformLnkIp6Tnl *props;
-    guint32              flowinfo;
-
-    if (!info_data || !nm_streq0(kind, "ip6tnl"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj   = nmp_object_new(NMP_OBJECT_TYPE_LNK_IP6TNL, NULL);
-    props = &obj->lnk_ip6tnl;
-
-    if (tb[IFLA_IPTUN_LINK])
-        props->parent_ifindex = nla_get_u32(tb[IFLA_IPTUN_LINK]);
-    if (tb[IFLA_IPTUN_LOCAL])
-        props->local = *nla_data_as(struct in6_addr, tb[IFLA_IPTUN_LOCAL]);
-    if (tb[IFLA_IPTUN_REMOTE])
-        props->remote = *nla_data_as(struct in6_addr, tb[IFLA_IPTUN_REMOTE]);
-    if (tb[IFLA_IPTUN_TTL])
-        props->ttl = nla_get_u8(tb[IFLA_IPTUN_TTL]);
-    if (tb[IFLA_IPTUN_ENCAP_LIMIT])
-        props->encap_limit = nla_get_u8(tb[IFLA_IPTUN_ENCAP_LIMIT]);
-    if (tb[IFLA_IPTUN_FLOWINFO]) {
-        flowinfo          = ntohl(nla_get_u32(tb[IFLA_IPTUN_FLOWINFO]));
-        props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK;
-        props->tclass     = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT;
-    }
-    if (tb[IFLA_IPTUN_PROTO])
-        props->proto = nla_get_u8(tb[IFLA_IPTUN_PROTO]);
-    if (tb[IFLA_IPTUN_FLAGS])
-        props->flags = nla_get_u32(tb[IFLA_IPTUN_FLAGS]);
-
-    return obj;
-}
-
-static NMPObject *
-_parse_lnk_ip6gre(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_GRE_LINK]        = {.type = NLA_U32},
-        [IFLA_GRE_IFLAGS]      = {.type = NLA_U16},
-        [IFLA_GRE_OFLAGS]      = {.type = NLA_U16},
-        [IFLA_GRE_IKEY]        = {.type = NLA_U32},
-        [IFLA_GRE_OKEY]        = {.type = NLA_U32},
-        [IFLA_GRE_LOCAL]       = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)},
-        [IFLA_GRE_REMOTE]      = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)},
-        [IFLA_GRE_TTL]         = {.type = NLA_U8},
-        [IFLA_GRE_ENCAP_LIMIT] = {.type = NLA_U8},
-        [IFLA_GRE_FLOWINFO]    = {.type = NLA_U32},
-        [IFLA_GRE_FLAGS]       = {.type = NLA_U32},
-    };
-    struct nlattr *      tb[G_N_ELEMENTS(policy)];
-    NMPObject *          obj;
-    NMPlatformLnkIp6Tnl *props;
-    guint32              flowinfo;
-    gboolean             is_tap;
-
-    if (!info_data || !kind)
-        return NULL;
-
-    if (nm_streq(kind, "ip6gre"))
-        is_tap = FALSE;
-    else if (nm_streq(kind, "ip6gretap"))
-        is_tap = TRUE;
-    else
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj = nmp_object_new(is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL);
-    props         = &obj->lnk_ip6tnl;
-    props->is_gre = TRUE;
-    props->is_tap = is_tap;
-
-    if (tb[IFLA_GRE_LINK])
-        props->parent_ifindex = nla_get_u32(tb[IFLA_GRE_LINK]);
-    if (tb[IFLA_GRE_IFLAGS])
-        props->input_flags = ntohs(nla_get_u16(tb[IFLA_GRE_IFLAGS]));
-    if (tb[IFLA_GRE_OFLAGS])
-        props->output_flags = ntohs(nla_get_u16(tb[IFLA_GRE_OFLAGS]));
-    if (tb[IFLA_GRE_IKEY])
-        props->input_key = ntohl(nla_get_u32(tb[IFLA_GRE_IKEY]));
-    if (tb[IFLA_GRE_OKEY])
-        props->output_key = ntohl(nla_get_u32(tb[IFLA_GRE_OKEY]));
-    if (tb[IFLA_GRE_LOCAL])
-        props->local = *nla_data_as(struct in6_addr, tb[IFLA_GRE_LOCAL]);
-    if (tb[IFLA_GRE_REMOTE])
-        props->remote = *nla_data_as(struct in6_addr, tb[IFLA_GRE_REMOTE]);
-    if (tb[IFLA_GRE_TTL])
-        props->ttl = nla_get_u8(tb[IFLA_GRE_TTL]);
-    if (tb[IFLA_GRE_ENCAP_LIMIT])
-        props->encap_limit = nla_get_u8(tb[IFLA_GRE_ENCAP_LIMIT]);
-    if (tb[IFLA_GRE_FLOWINFO]) {
-        flowinfo          = ntohl(nla_get_u32(tb[IFLA_GRE_FLOWINFO]));
-        props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK;
-        props->tclass     = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT;
-    }
-    if (tb[IFLA_GRE_FLAGS])
-        props->flags = nla_get_u32(tb[IFLA_GRE_FLAGS]);
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_ipip(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_IPTUN_LINK]     = {.type = NLA_U32},
-        [IFLA_IPTUN_LOCAL]    = {.type = NLA_U32},
-        [IFLA_IPTUN_REMOTE]   = {.type = NLA_U32},
-        [IFLA_IPTUN_TTL]      = {.type = NLA_U8},
-        [IFLA_IPTUN_TOS]      = {.type = NLA_U8},
-        [IFLA_IPTUN_PMTUDISC] = {.type = NLA_U8},
-    };
-    struct nlattr *    tb[G_N_ELEMENTS(policy)];
-    NMPObject *        obj;
-    NMPlatformLnkIpIp *props;
-
-    if (!info_data || !nm_streq0(kind, "ipip"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj   = nmp_object_new(NMP_OBJECT_TYPE_LNK_IPIP, NULL);
-    props = &obj->lnk_ipip;
-
-    props->parent_ifindex     = tb[IFLA_IPTUN_LINK] ? nla_get_u32(tb[IFLA_IPTUN_LINK]) : 0;
-    props->local              = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32(tb[IFLA_IPTUN_LOCAL]) : 0;
-    props->remote             = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32(tb[IFLA_IPTUN_REMOTE]) : 0;
-    props->tos                = tb[IFLA_IPTUN_TOS] ? nla_get_u8(tb[IFLA_IPTUN_TOS]) : 0;
-    props->ttl                = tb[IFLA_IPTUN_TTL] ? nla_get_u8(tb[IFLA_IPTUN_TTL]) : 0;
-    props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]);
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_macvlan(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_MACVLAN_MODE]  = {.type = NLA_U32},
-        [IFLA_MACVLAN_FLAGS] = {.type = NLA_U16},
-    };
-    NMPlatformLnkMacvlan *props;
-    struct nlattr *       tb[G_N_ELEMENTS(policy)];
-    NMPObject *           obj;
-    gboolean              tap;
-
-    if (!info_data || !kind)
-        return NULL;
-
-    if (nm_streq(kind, "macvlan"))
-        tap = FALSE;
-    else if (nm_streq(kind, "macvtap"))
-        tap = TRUE;
-    else
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    if (!tb[IFLA_MACVLAN_MODE])
-        return NULL;
-
-    obj   = nmp_object_new(tap ? NMP_OBJECT_TYPE_LNK_MACVTAP : NMP_OBJECT_TYPE_LNK_MACVLAN, NULL);
-    props = &obj->lnk_macvlan;
-    props->mode = nla_get_u32(tb[IFLA_MACVLAN_MODE]);
-    props->tap  = tap;
-
-    if (tb[IFLA_MACVLAN_FLAGS])
-        props->no_promisc =
-            NM_FLAGS_HAS(nla_get_u16(tb[IFLA_MACVLAN_FLAGS]), MACVLAN_FLAG_NOPROMISC);
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_macsec(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_MACSEC_SCI]            = {.type = NLA_U64},
-        [IFLA_MACSEC_ICV_LEN]        = {.type = NLA_U8},
-        [IFLA_MACSEC_CIPHER_SUITE]   = {.type = NLA_U64},
-        [IFLA_MACSEC_WINDOW]         = {.type = NLA_U32},
-        [IFLA_MACSEC_ENCODING_SA]    = {.type = NLA_U8},
-        [IFLA_MACSEC_ENCRYPT]        = {.type = NLA_U8},
-        [IFLA_MACSEC_PROTECT]        = {.type = NLA_U8},
-        [IFLA_MACSEC_INC_SCI]        = {.type = NLA_U8},
-        [IFLA_MACSEC_ES]             = {.type = NLA_U8},
-        [IFLA_MACSEC_SCB]            = {.type = NLA_U8},
-        [IFLA_MACSEC_REPLAY_PROTECT] = {.type = NLA_U8},
-        [IFLA_MACSEC_VALIDATION]     = {.type = NLA_U8},
-    };
-    struct nlattr *      tb[G_N_ELEMENTS(policy)];
-    NMPObject *          obj;
-    NMPlatformLnkMacsec *props;
-
-    if (!info_data || !nm_streq0(kind, "macsec"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj   = nmp_object_new(NMP_OBJECT_TYPE_LNK_MACSEC, NULL);
-    props = &obj->lnk_macsec;
-
-    if (tb[IFLA_MACSEC_SCI]) {
-        props->sci = nla_get_be64(tb[IFLA_MACSEC_SCI]);
-    }
-    if (tb[IFLA_MACSEC_ICV_LEN]) {
-        props->icv_length = nla_get_u8(tb[IFLA_MACSEC_ICV_LEN]);
-    }
-    if (tb[IFLA_MACSEC_CIPHER_SUITE]) {
-        props->cipher_suite = nla_get_u64(tb[IFLA_MACSEC_CIPHER_SUITE]);
-    }
-    if (tb[IFLA_MACSEC_WINDOW]) {
-        props->window = nla_get_u32(tb[IFLA_MACSEC_WINDOW]);
-    }
-    if (tb[IFLA_MACSEC_ENCODING_SA]) {
-        props->encoding_sa = !!nla_get_u8(tb[IFLA_MACSEC_ENCODING_SA]);
-    }
-    if (tb[IFLA_MACSEC_ENCRYPT]) {
-        props->encrypt = !!nla_get_u8(tb[IFLA_MACSEC_ENCRYPT]);
-    }
-    if (tb[IFLA_MACSEC_PROTECT]) {
-        props->protect = !!nla_get_u8(tb[IFLA_MACSEC_PROTECT]);
-    }
-    if (tb[IFLA_MACSEC_INC_SCI]) {
-        props->include_sci = !!nla_get_u8(tb[IFLA_MACSEC_INC_SCI]);
-    }
-    if (tb[IFLA_MACSEC_ES]) {
-        props->es = !!nla_get_u8(tb[IFLA_MACSEC_ES]);
-    }
-    if (tb[IFLA_MACSEC_SCB]) {
-        props->scb = !!nla_get_u8(tb[IFLA_MACSEC_SCB]);
-    }
-    if (tb[IFLA_MACSEC_REPLAY_PROTECT]) {
-        props->replay_protect = !!nla_get_u8(tb[IFLA_MACSEC_REPLAY_PROTECT]);
-    }
-    if (tb[IFLA_MACSEC_VALIDATION]) {
-        props->validation = nla_get_u8(tb[IFLA_MACSEC_VALIDATION]);
-    }
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_sit(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_IPTUN_LINK]     = {.type = NLA_U32},
-        [IFLA_IPTUN_LOCAL]    = {.type = NLA_U32},
-        [IFLA_IPTUN_REMOTE]   = {.type = NLA_U32},
-        [IFLA_IPTUN_TTL]      = {.type = NLA_U8},
-        [IFLA_IPTUN_TOS]      = {.type = NLA_U8},
-        [IFLA_IPTUN_PMTUDISC] = {.type = NLA_U8},
-        [IFLA_IPTUN_FLAGS]    = {.type = NLA_U16},
-        [IFLA_IPTUN_PROTO]    = {.type = NLA_U8},
-    };
-    struct nlattr *   tb[G_N_ELEMENTS(policy)];
-    NMPObject *       obj;
-    NMPlatformLnkSit *props;
-
-    if (!info_data || !nm_streq0(kind, "sit"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj   = nmp_object_new(NMP_OBJECT_TYPE_LNK_SIT, NULL);
-    props = &obj->lnk_sit;
-
-    props->parent_ifindex     = tb[IFLA_IPTUN_LINK] ? nla_get_u32(tb[IFLA_IPTUN_LINK]) : 0;
-    props->local              = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32(tb[IFLA_IPTUN_LOCAL]) : 0;
-    props->remote             = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32(tb[IFLA_IPTUN_REMOTE]) : 0;
-    props->tos                = tb[IFLA_IPTUN_TOS] ? nla_get_u8(tb[IFLA_IPTUN_TOS]) : 0;
-    props->ttl                = tb[IFLA_IPTUN_TTL] ? nla_get_u8(tb[IFLA_IPTUN_TTL]) : 0;
-    props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8(tb[IFLA_IPTUN_PMTUDISC]);
-    props->flags              = tb[IFLA_IPTUN_FLAGS] ? nla_get_u16(tb[IFLA_IPTUN_FLAGS]) : 0;
-    props->proto              = tb[IFLA_IPTUN_PROTO] ? nla_get_u8(tb[IFLA_IPTUN_PROTO]) : 0;
-
-    return obj;
-}
-
-/*****************************************************************************/
-
-static NMPObject *
-_parse_lnk_tun(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_TUN_OWNER]               = {.type = NLA_U32},
-        [IFLA_TUN_GROUP]               = {.type = NLA_U32},
-        [IFLA_TUN_TYPE]                = {.type = NLA_U8},
-        [IFLA_TUN_PI]                  = {.type = NLA_U8},
-        [IFLA_TUN_VNET_HDR]            = {.type = NLA_U8},
-        [IFLA_TUN_PERSIST]             = {.type = NLA_U8},
-        [IFLA_TUN_MULTI_QUEUE]         = {.type = NLA_U8},
-        [IFLA_TUN_NUM_QUEUES]          = {.type = NLA_U32},
-        [IFLA_TUN_NUM_DISABLED_QUEUES] = {.type = NLA_U32},
-    };
-    struct nlattr *   tb[G_N_ELEMENTS(policy)];
-    NMPObject *       obj;
-    NMPlatformLnkTun *props;
-
-    if (!info_data || !nm_streq0(kind, "tun"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    if (!tb[IFLA_TUN_TYPE])
-        return NULL;
-
-    obj   = nmp_object_new(NMP_OBJECT_TYPE_LNK_TUN, NULL);
-    props = &obj->lnk_tun;
-
-    props->type = nla_get_u8(tb[IFLA_TUN_TYPE]);
-
-    props->pi          = !!nla_get_u8_cond(tb, IFLA_TUN_PI, FALSE);
-    props->vnet_hdr    = !!nla_get_u8_cond(tb, IFLA_TUN_VNET_HDR, FALSE);
-    props->multi_queue = !!nla_get_u8_cond(tb, IFLA_TUN_MULTI_QUEUE, FALSE);
-    props->persist     = !!nla_get_u8_cond(tb, IFLA_TUN_PERSIST, FALSE);
-
-    if (tb[IFLA_TUN_OWNER]) {
-        props->owner_valid = TRUE;
-        props->owner       = nla_get_u32(tb[IFLA_TUN_OWNER]);
-    }
-    if (tb[IFLA_TUN_GROUP]) {
-        props->group_valid = TRUE;
-        props->group       = nla_get_u32(tb[IFLA_TUN_GROUP]);
-    }
-    return obj;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_vlan_qos_mapping_from_nla(struct nlattr *          nlattr,
-                           const NMVlanQosMapping **out_map,
-                           guint *                  out_n_map)
-{
-    struct nlattr *   nla;
-    int               remaining;
-    gs_unref_ptrarray GPtrArray *array = NULL;
-
-    G_STATIC_ASSERT(sizeof(NMVlanQosMapping) == sizeof(struct ifla_vlan_qos_mapping));
-    G_STATIC_ASSERT(sizeof(((NMVlanQosMapping *) 0)->to)
-                    == sizeof(((struct ifla_vlan_qos_mapping *) 0)->to));
-    G_STATIC_ASSERT(sizeof(((NMVlanQosMapping *) 0)->from)
-                    == sizeof(((struct ifla_vlan_qos_mapping *) 0)->from));
-    G_STATIC_ASSERT(sizeof(NMVlanQosMapping)
-                    == sizeof(((NMVlanQosMapping *) 0)->from)
-                           + sizeof(((NMVlanQosMapping *) 0)->to));
-
-    nm_assert(out_map && !*out_map);
-    nm_assert(out_n_map && !*out_n_map);
-
-    if (!nlattr)
-        return TRUE;
-
-    array = g_ptr_array_new();
-    nla_for_each_nested (nla, nlattr, remaining) {
-        if (nla_len(nla) < sizeof(NMVlanQosMapping))
-            return FALSE;
-        g_ptr_array_add(array, nla_data(nla));
-    }
-
-    if (array->len > 0) {
-        NMVlanQosMapping *list;
-        guint             i, j;
-
-        /* The sorting is necessary, because for egress mapping, kernel
-         * doesn't sent the items strictly sorted by the from field. */
-        g_ptr_array_sort_with_data(array, _vlan_qos_mapping_cmp_from_ptr, NULL);
-
-        list = g_new(NMVlanQosMapping, array->len);
-
-        for (i = 0, j = 0; i < array->len; i++) {
-            NMVlanQosMapping *map;
-
-            map = array->pdata[i];
-
-            /* kernel doesn't really send us duplicates. Just be extra cautious
-             * because we want strong guarantees about the sort order and uniqueness
-             * of our mapping list (for simpler equality comparison). */
-            if (j > 0 && list[j - 1].from == map->from)
-                list[j - 1] = *map;
-            else
-                list[j++] = *map;
-        }
-
-        *out_n_map = j;
-        *out_map   = list;
-    }
-
-    return TRUE;
-}
-
-/* Copied and heavily modified from libnl3's vlan_parse() */
-static NMPObject *
-_parse_lnk_vlan(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_VLAN_ID]          = {.type = NLA_U16},
-        [IFLA_VLAN_FLAGS]       = {.minlen = nm_offsetofend(struct ifla_vlan_flags, flags)},
-        [IFLA_VLAN_INGRESS_QOS] = {.type = NLA_NESTED},
-        [IFLA_VLAN_EGRESS_QOS]  = {.type = NLA_NESTED},
-        [IFLA_VLAN_PROTOCOL]    = {.type = NLA_U16},
-    };
-    struct nlattr *tb[G_N_ELEMENTS(policy)];
-    nm_auto_nmpobj NMPObject *obj = NULL;
-    NMPObject *               obj_result;
-
-    if (!info_data || !nm_streq0(kind, "vlan"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    if (!tb[IFLA_VLAN_ID])
-        return NULL;
-
-    obj              = nmp_object_new(NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-    obj->lnk_vlan.id = nla_get_u16(tb[IFLA_VLAN_ID]);
-
-    if (tb[IFLA_VLAN_FLAGS]) {
-        struct ifla_vlan_flags flags;
-
-        nla_memcpy(&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
-
-        obj->lnk_vlan.flags = flags.flags;
-    }
-
-    if (!_vlan_qos_mapping_from_nla(tb[IFLA_VLAN_INGRESS_QOS],
-                                    &obj->_lnk_vlan.ingress_qos_map,
-                                    &obj->_lnk_vlan.n_ingress_qos_map))
-        return NULL;
-
-    if (!_vlan_qos_mapping_from_nla(tb[IFLA_VLAN_EGRESS_QOS],
-                                    &obj->_lnk_vlan.egress_qos_map,
-                                    &obj->_lnk_vlan.n_egress_qos_map))
-        return NULL;
-
-    obj_result = obj;
-    obj        = NULL;
-    return obj_result;
-}
-
-/*****************************************************************************/
-
-/* The installed kernel headers might not have VXLAN stuff at all, or
- * they might have the original properties, but not PORT, GROUP6, or LOCAL6.
- * So until we depend on kernel >= 3.11, we just ignore the actual enum
- * in if_link.h and define the values ourselves.
- */
-#define IFLA_VXLAN_UNSPEC     0
-#define IFLA_VXLAN_ID         1
-#define IFLA_VXLAN_GROUP      2
-#define IFLA_VXLAN_LINK       3
-#define IFLA_VXLAN_LOCAL      4
-#define IFLA_VXLAN_TTL        5
-#define IFLA_VXLAN_TOS        6
-#define IFLA_VXLAN_LEARNING   7
-#define IFLA_VXLAN_AGEING     8
-#define IFLA_VXLAN_LIMIT      9
-#define IFLA_VXLAN_PORT_RANGE 10
-#define IFLA_VXLAN_PROXY      11
-#define IFLA_VXLAN_RSC        12
-#define IFLA_VXLAN_L2MISS     13
-#define IFLA_VXLAN_L3MISS     14
-#define IFLA_VXLAN_PORT       15
-#define IFLA_VXLAN_GROUP6     16
-#define IFLA_VXLAN_LOCAL6     17
-#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 {
-    guint16 low;
-    guint16 high;
-};
-
-static NMPObject *
-_parse_lnk_vxlan(const char *kind, struct nlattr *info_data)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_VXLAN_ID]         = {.type = NLA_U32},
-        [IFLA_VXLAN_GROUP]      = {.type = NLA_U32},
-        [IFLA_VXLAN_GROUP6]     = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)},
-        [IFLA_VXLAN_LINK]       = {.type = NLA_U32},
-        [IFLA_VXLAN_LOCAL]      = {.type = NLA_U32},
-        [IFLA_VXLAN_LOCAL6]     = {.type = NLA_UNSPEC, .minlen = sizeof(struct in6_addr)},
-        [IFLA_VXLAN_TOS]        = {.type = NLA_U8},
-        [IFLA_VXLAN_TTL]        = {.type = NLA_U8},
-        [IFLA_VXLAN_LEARNING]   = {.type = NLA_U8},
-        [IFLA_VXLAN_AGEING]     = {.type = NLA_U32},
-        [IFLA_VXLAN_LIMIT]      = {.type = NLA_U32},
-        [IFLA_VXLAN_PORT_RANGE] = {.type   = NLA_UNSPEC,
-                                   .minlen = sizeof(struct nm_ifla_vxlan_port_range)},
-        [IFLA_VXLAN_PROXY]      = {.type = NLA_U8},
-        [IFLA_VXLAN_RSC]        = {.type = NLA_U8},
-        [IFLA_VXLAN_L2MISS]     = {.type = NLA_U8},
-        [IFLA_VXLAN_L3MISS]     = {.type = NLA_U8},
-        [IFLA_VXLAN_PORT]       = {.type = NLA_U16},
-    };
-    NMPlatformLnkVxlan *props;
-    struct nlattr *     tb[G_N_ELEMENTS(policy)];
-    NMPObject *         obj;
-
-    if (!info_data || !nm_streq0(kind, "vxlan"))
-        return NULL;
-
-    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
-        return NULL;
-
-    obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
-
-    props = &obj->lnk_vxlan;
-
-    if (tb[IFLA_VXLAN_LINK])
-        props->parent_ifindex = nla_get_u32(tb[IFLA_VXLAN_LINK]);
-    if (tb[IFLA_VXLAN_ID])
-        props->id = nla_get_u32(tb[IFLA_VXLAN_ID]);
-    if (tb[IFLA_VXLAN_GROUP])
-        props->group = nla_get_u32(tb[IFLA_VXLAN_GROUP]);
-    if (tb[IFLA_VXLAN_LOCAL])
-        props->local = nla_get_u32(tb[IFLA_VXLAN_LOCAL]);
-    if (tb[IFLA_VXLAN_LOCAL6])
-        props->local6 = *nla_data_as(struct in6_addr, tb[IFLA_VXLAN_LOCAL6]);
-    if (tb[IFLA_VXLAN_GROUP6])
-        props->group6 = *nla_data_as(struct in6_addr, tb[IFLA_VXLAN_GROUP6]);
-
-    if (tb[IFLA_VXLAN_AGEING])
-        props->ageing = nla_get_u32(tb[IFLA_VXLAN_AGEING]);
-    if (tb[IFLA_VXLAN_LIMIT])
-        props->limit = nla_get_u32(tb[IFLA_VXLAN_LIMIT]);
-    if (tb[IFLA_VXLAN_TOS])
-        props->tos = nla_get_u8(tb[IFLA_VXLAN_TOS]);
-    if (tb[IFLA_VXLAN_TTL])
-        props->ttl = nla_get_u8(tb[IFLA_VXLAN_TTL]);
-
-    if (tb[IFLA_VXLAN_PORT])
-        props->dst_port = ntohs(nla_get_u16(tb[IFLA_VXLAN_PORT]));
-
-    if (tb[IFLA_VXLAN_PORT_RANGE]) {
-        struct nm_ifla_vxlan_port_range *range;
-
-        range = nla_data_as(struct nm_ifla_vxlan_port_range, tb[IFLA_VXLAN_PORT_RANGE]);
-        props->src_port_min = ntohs(range->low);
-        props->src_port_max = ntohs(range->high);
-    }
-
-    if (tb[IFLA_VXLAN_LEARNING])
-        props->learning = !!nla_get_u8(tb[IFLA_VXLAN_LEARNING]);
-    if (tb[IFLA_VXLAN_PROXY])
-        props->proxy = !!nla_get_u8(tb[IFLA_VXLAN_PROXY]);
-    if (tb[IFLA_VXLAN_RSC])
-        props->rsc = !!nla_get_u8(tb[IFLA_VXLAN_RSC]);
-    if (tb[IFLA_VXLAN_L2MISS])
-        props->l2miss = !!nla_get_u8(tb[IFLA_VXLAN_L2MISS]);
-    if (tb[IFLA_VXLAN_L3MISS])
-        props->l3miss = !!nla_get_u8(tb[IFLA_VXLAN_L3MISS]);
-
-    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
-_wireguard_update_from_allowed_ips_nla(NMPWireGuardAllowedIP *allowed_ip, struct nlattr *nlattr)
-{
-    static const struct nla_policy policy[] = {
-        [WGALLOWEDIP_A_FAMILY]    = {.type = NLA_U16},
-        [WGALLOWEDIP_A_IPADDR]    = {.minlen = sizeof(struct in_addr)},
-        [WGALLOWEDIP_A_CIDR_MASK] = {.type = NLA_U8},
-    };
-    struct nlattr *tb[G_N_ELEMENTS(policy)];
-    int            family;
-    int            addr_len;
-
-    if (nla_parse_nested_arr(tb, nlattr, policy) < 0)
-        return FALSE;
-
-    if (!tb[WGALLOWEDIP_A_FAMILY])
-        return FALSE;
-
-    family = nla_get_u16(tb[WGALLOWEDIP_A_FAMILY]);
-    if (family == AF_INET)
-        addr_len = sizeof(in_addr_t);
-    else if (family == AF_INET6)
-        addr_len = sizeof(struct in6_addr);
-    else
-        return FALSE;
-
-    _check_addr_or_return_val(tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE);
-
-    *allowed_ip = (NMPWireGuardAllowedIP){
-        .family = family,
-    };
-
-    nm_assert((int) allowed_ip->family == family);
-
-    if (tb[WGALLOWEDIP_A_IPADDR])
-        nla_memcpy(&allowed_ip->addr, tb[WGALLOWEDIP_A_IPADDR], addr_len);
-    if (tb[WGALLOWEDIP_A_CIDR_MASK])
-        allowed_ip->mask = nla_get_u8(tb[WGALLOWEDIP_A_CIDR_MASK]);
-
-    return TRUE;
-}
-
-typedef struct {
-    CList            lst;
-    NMPWireGuardPeer data;
-} WireGuardPeerConstruct;
-
-static gboolean
-_wireguard_update_from_peers_nla(CList *peers, GArray **p_allowed_ips, struct nlattr *peer_attr)
-{
-    static const struct nla_policy policy[] = {
-        [WGPEER_A_PUBLIC_KEY]                    = {.minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN},
-        [WGPEER_A_PRESHARED_KEY]                 = {},
-        [WGPEER_A_FLAGS]                         = {.type = NLA_U32},
-        [WGPEER_A_ENDPOINT]                      = {},
-        [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = {.type = NLA_U16},
-        [WGPEER_A_LAST_HANDSHAKE_TIME]           = {},
-        [WGPEER_A_RX_BYTES]                      = {.type = NLA_U64},
-        [WGPEER_A_TX_BYTES]                      = {.type = NLA_U64},
-        [WGPEER_A_ALLOWEDIPS]                    = {.type = NLA_NESTED},
-    };
-    struct nlattr *         tb[G_N_ELEMENTS(policy)];
-    WireGuardPeerConstruct *peer_c;
-
-    if (nla_parse_nested_arr(tb, peer_attr, policy) < 0)
-        return FALSE;
-
-    if (!tb[WGPEER_A_PUBLIC_KEY])
-        return FALSE;
-
-    /* a peer with the same public key as last peer is just a continuation for extra AllowedIPs */
-    peer_c = c_list_last_entry(peers, WireGuardPeerConstruct, lst);
-    if (peer_c
-        && !memcmp(nla_data(tb[WGPEER_A_PUBLIC_KEY]),
-                   peer_c->data.public_key,
-                   NMP_WIREGUARD_PUBLIC_KEY_LEN)) {
-        G_STATIC_ASSERT_EXPR(NMP_WIREGUARD_PUBLIC_KEY_LEN == sizeof(peer_c->data.public_key));
-        /* this message is a continuation of the previous peer.
-         * Only parse WGPEER_A_ALLOWEDIPS below. */
-    } else {
-        /* otherwise, start a new peer */
-        peer_c = g_slice_new0(WireGuardPeerConstruct);
-        c_list_link_tail(peers, &peer_c->lst);
-
-        nla_memcpy(&peer_c->data.public_key,
-                   tb[WGPEER_A_PUBLIC_KEY],
-                   sizeof(peer_c->data.public_key));
-
-        if (tb[WGPEER_A_PRESHARED_KEY]) {
-            nla_memcpy(&peer_c->data.preshared_key,
-                       tb[WGPEER_A_PRESHARED_KEY],
-                       sizeof(peer_c->data.preshared_key));
-            /* FIXME(netlink-bzero-secret) */
-            nm_explicit_bzero(nla_data(tb[WGPEER_A_PRESHARED_KEY]),
-                              nla_len(tb[WGPEER_A_PRESHARED_KEY]));
-        }
-
-        nm_sock_addr_union_cpy_untrusted(
-            &peer_c->data.endpoint,
-            tb[WGPEER_A_ENDPOINT] ? nla_data(tb[WGPEER_A_ENDPOINT]) : NULL,
-            tb[WGPEER_A_ENDPOINT] ? nla_len(tb[WGPEER_A_ENDPOINT]) : 0);
-
-        if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
-            peer_c->data.persistent_keepalive_interval =
-                nla_get_u16(tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
-        if (tb[WGPEER_A_LAST_HANDSHAKE_TIME]) {
-            if (nla_len(tb[WGPEER_A_LAST_HANDSHAKE_TIME])
-                >= sizeof(peer_c->data.last_handshake_time))
-                nla_memcpy(&peer_c->data.last_handshake_time,
-                           tb[WGPEER_A_LAST_HANDSHAKE_TIME],
-                           sizeof(peer_c->data.last_handshake_time));
-        }
-        if (tb[WGPEER_A_RX_BYTES])
-            peer_c->data.rx_bytes = nla_get_u64(tb[WGPEER_A_RX_BYTES]);
-        if (tb[WGPEER_A_TX_BYTES])
-            peer_c->data.tx_bytes = nla_get_u64(tb[WGPEER_A_TX_BYTES]);
-    }
-
-    if (tb[WGPEER_A_ALLOWEDIPS]) {
-        struct nlattr *attr;
-        int            rem;
-        GArray *       allowed_ips = *p_allowed_ips;
-
-        nla_for_each_nested (attr, tb[WGPEER_A_ALLOWEDIPS], rem) {
-            if (!allowed_ips) {
-                allowed_ips    = g_array_new(FALSE, FALSE, sizeof(NMPWireGuardAllowedIP));
-                *p_allowed_ips = allowed_ips;
-                g_array_set_size(allowed_ips, 1);
-            } else
-                g_array_set_size(allowed_ips, allowed_ips->len + 1);
-
-            if (!_wireguard_update_from_allowed_ips_nla(
-                    &g_array_index(allowed_ips, NMPWireGuardAllowedIP, allowed_ips->len - 1),
-                    attr)) {
-                /* we ignore the error of parsing one allowed-ip. */
-                g_array_set_size(allowed_ips, allowed_ips->len - 1);
-                continue;
-            }
-
-            if (!peer_c->data._construct_idx_end)
-                peer_c->data._construct_idx_start = allowed_ips->len - 1;
-            peer_c->data._construct_idx_end = allowed_ips->len;
-        }
-    }
-
-    return TRUE;
-}
-
-typedef struct {
-    const int  ifindex;
-    NMPObject *obj;
-    CList      peers;
-    GArray *   allowed_ips;
-} WireGuardParseData;
-
-static int
-_wireguard_get_device_cb(struct nl_msg *msg, void *arg)
-{
-    static const struct nla_policy policy[] = {
-        [WGDEVICE_A_IFINDEX]     = {.type = NLA_U32},
-        [WGDEVICE_A_IFNAME]      = {.type = NLA_NUL_STRING, .maxlen = IFNAMSIZ},
-        [WGDEVICE_A_PRIVATE_KEY] = {},
-        [WGDEVICE_A_PUBLIC_KEY]  = {},
-        [WGDEVICE_A_FLAGS]       = {.type = NLA_U32},
-        [WGDEVICE_A_LISTEN_PORT] = {.type = NLA_U16},
-        [WGDEVICE_A_FWMARK]      = {.type = NLA_U32},
-        [WGDEVICE_A_PEERS]       = {.type = NLA_NESTED},
-    };
-    struct nlattr *     tb[G_N_ELEMENTS(policy)];
-    WireGuardParseData *parse_data = arg;
-
-    if (genlmsg_parse_arr(nlmsg_hdr(msg), 0, tb, policy) < 0)
-        return NL_SKIP;
-
-    if (tb[WGDEVICE_A_IFINDEX]) {
-        int ifindex;
-
-        ifindex = (int) nla_get_u32(tb[WGDEVICE_A_IFINDEX]);
-        if (ifindex <= 0 || parse_data->ifindex != ifindex)
-            return NL_SKIP;
-    } else {
-        if (!parse_data->obj)
-            return NL_SKIP;
-    }
-
-    if (parse_data->obj) {
-        /* we already have an object instance. This means the netlink message
-         * is a continuation, only providing more WGDEVICE_A_PEERS data below. */
-    } else {
-        NMPObject *             obj;
-        NMPlatformLnkWireGuard *props;
-
-        obj   = nmp_object_new(NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL);
-        props = &obj->lnk_wireguard;
-
-        if (tb[WGDEVICE_A_PRIVATE_KEY]) {
-            nla_memcpy(props->private_key, tb[WGDEVICE_A_PRIVATE_KEY], sizeof(props->private_key));
-            /* FIXME(netlink-bzero-secret): extend netlink library to wipe memory. For now,
-             * just hack it here (yes, this does not cover all places where the
-             * private key was copied). */
-            nm_explicit_bzero(nla_data(tb[WGDEVICE_A_PRIVATE_KEY]),
-                              nla_len(tb[WGDEVICE_A_PRIVATE_KEY]));
-        }
-        if (tb[WGDEVICE_A_PUBLIC_KEY])
-            nla_memcpy(props->public_key, tb[WGDEVICE_A_PUBLIC_KEY], sizeof(props->public_key));
-        if (tb[WGDEVICE_A_LISTEN_PORT])
-            props->listen_port = nla_get_u16(tb[WGDEVICE_A_LISTEN_PORT]);
-        if (tb[WGDEVICE_A_FWMARK])
-            props->fwmark = nla_get_u32(tb[WGDEVICE_A_FWMARK]);
-
-        parse_data->obj = obj;
-    }
-
-    if (tb[WGDEVICE_A_PEERS]) {
-        struct nlattr *attr;
-        int            rem;
-
-        nla_for_each_nested (attr, tb[WGDEVICE_A_PEERS], rem) {
-            if (!_wireguard_update_from_peers_nla(&parse_data->peers,
-                                                  &parse_data->allowed_ips,
-                                                  attr)) {
-                /* we ignore the error of parsing one peer.
-                 * _wireguard_update_from_peers_nla() leaves the @peers array in the
-                 * desired state. */
-            }
-        }
-    }
-
-    return NL_OK;
-}
-
-static const NMPObject *
-_wireguard_read_info(NMPlatform *    platform /* used only as logging context */,
-                     struct nl_sock *genl,
-                     int             wireguard_family_id,
-                     int             ifindex)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    NMPObject *                  obj = NULL;
-    WireGuardPeerConstruct *     peer_c;
-    WireGuardPeerConstruct *     peer_c_safe;
-    gs_unref_array GArray *allowed_ips = NULL;
-    WireGuardParseData     parse_data  = {
-        .ifindex = ifindex,
-    };
-    guint i;
-
-    nm_assert(genl);
-    nm_assert(wireguard_family_id >= 0);
-    nm_assert(ifindex > 0);
-
-    _LOGT("wireguard: fetching information for ifindex %d (genl-id %d)...",
-          ifindex,
-          wireguard_family_id);
-
-    msg = nlmsg_alloc();
-
-    if (!genlmsg_put(msg,
-                     NL_AUTO_PORT,
-                     NL_AUTO_SEQ,
-                     wireguard_family_id,
-                     0,
-                     NLM_F_DUMP,
-                     WG_CMD_GET_DEVICE,
-                     1))
-        return NULL;
-
-    NLA_PUT_U32(msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
-
-    if (nl_send_auto(genl, msg) < 0)
-        return NULL;
-
-    c_list_init(&parse_data.peers);
-
-    /* we ignore errors, and return whatever we could successfully
-     * parse. */
-    nl_recvmsgs(genl,
-                &((const struct nl_cb){
-                    .valid_cb  = _wireguard_get_device_cb,
-                    .valid_arg = (gpointer) &parse_data,
-                }));
-
-    /* unpack: transfer ownership */
-    obj         = parse_data.obj;
-    allowed_ips = parse_data.allowed_ips;
-
-    if (!obj) {
-        while ((peer_c = c_list_first_entry(&parse_data.peers, WireGuardPeerConstruct, lst))) {
-            c_list_unlink_stale(&peer_c->lst);
-            nm_explicit_bzero(&peer_c->data.preshared_key, sizeof(peer_c->data.preshared_key));
-            g_slice_free(WireGuardPeerConstruct, peer_c);
-        }
-        return NULL;
-    }
-
-    /* we receive peers/allowed-ips possibly in separate netlink messages. Hence, while
-     * parsing the dump, we don't know upfront how many peers/allowed-ips we will receive.
-     *
-     * We solve that, by collecting all peers with a CList. It's done this way,
-     * because a GArray would require growing the array, but we want to bzero()
-     * the preshared-key of each peer while reallocating. The CList apprach avoids
-     * that.
-     *
-     * For allowed-ips, we instead track one GArray, which are all appended
-     * there. The realloc/resize of the GArray is fine there. However,
-     * while we build the GArray, we don't yet have the final pointers.
-     * Hence, while constructing, we track the indexes with peer->_construct_idx_*
-     * fields. These indexes must be converted to actual pointers blow.
-     *
-     * This is all done during parsing. In the final NMPObjectLnkWireGuard we
-     * don't want the CList anymore and repackage the NMPObject tightly. The
-     * reason is, that NMPObject instances are immutable and long-living. Spend
-     * a bit effort below during construction to obtain a most suitable representation
-     * in this regard. */
-    obj->_lnk_wireguard.peers_len = c_list_length(&parse_data.peers);
-    obj->_lnk_wireguard.peers     = obj->_lnk_wireguard.peers_len > 0
-                                        ? g_new(NMPWireGuardPeer, obj->_lnk_wireguard.peers_len)
-                                        : NULL;
-
-    /* duplicate allowed_ips instead of using the pointer. The GArray possibly has more
-     * space allocated then we need, and we want to get rid of this excess buffer.
-     * Note that NMPObject instance is possibly put into the cache and long-living. */
-    obj->_lnk_wireguard._allowed_ips_buf_len = allowed_ips ? allowed_ips->len : 0u;
-    obj->_lnk_wireguard._allowed_ips_buf =
-        obj->_lnk_wireguard._allowed_ips_buf_len > 0
-            ? (NMPWireGuardAllowedIP *) nm_memdup(allowed_ips->data,
-                                                  sizeof(NMPWireGuardAllowedIP) * allowed_ips->len)
-            : NULL;
-
-    i = 0;
-    c_list_for_each_entry_safe (peer_c, peer_c_safe, &parse_data.peers, lst) {
-        NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &obj->_lnk_wireguard.peers[i++];
-
-        *peer = peer_c->data;
-
-        c_list_unlink_stale(&peer_c->lst);
-        nm_explicit_bzero(&peer_c->data.preshared_key, sizeof(peer_c->data.preshared_key));
-        g_slice_free(WireGuardPeerConstruct, peer_c);
-
-        if (peer->_construct_idx_end != 0) {
-            guint len;
-
-            nm_assert(obj->_lnk_wireguard._allowed_ips_buf);
-            nm_assert(peer->_construct_idx_end > peer->_construct_idx_start);
-            nm_assert(peer->_construct_idx_start < obj->_lnk_wireguard._allowed_ips_buf_len);
-            nm_assert(peer->_construct_idx_end <= obj->_lnk_wireguard._allowed_ips_buf_len);
-
-            len               = peer->_construct_idx_end - peer->_construct_idx_start;
-            peer->allowed_ips = &obj->_lnk_wireguard._allowed_ips_buf[peer->_construct_idx_start];
-            peer->allowed_ips_len = len;
-        } else {
-            nm_assert(!peer->_construct_idx_start);
-            nm_assert(!peer->_construct_idx_end);
-            peer->allowed_ips     = NULL;
-            peer->allowed_ips_len = 0;
-        }
-    }
-
-    return obj;
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static int
-_wireguard_get_family_id(NMPlatform *platform, int ifindex_try)
-{
-    NMLinuxPlatformPrivate *priv                = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    int                     wireguard_family_id = -1;
-
-    if (ifindex_try > 0) {
-        const NMPlatformLink *plink;
-
-        if (nm_platform_link_get_lnk_wireguard(platform, ifindex_try, &plink))
-            wireguard_family_id = NMP_OBJECT_UP_CAST(plink)->_link.wireguard_family_id;
-    }
-    if (wireguard_family_id < 0)
-        wireguard_family_id = genl_ctrl_resolve(priv->genl, "wireguard");
-    return wireguard_family_id;
-}
-
-static const NMPObject *
-_wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifindex)
-{
-    NMLinuxPlatformPrivate *priv            = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    nm_auto_nmpobj const NMPObject *obj_old = NULL;
-    nm_auto_nmpobj const NMPObject *obj_new = NULL;
-    nm_auto_nmpobj const NMPObject *lnk_new = NULL;
-    NMPCacheOpsType                 cache_op;
-    const NMPObject *               plink = NULL;
-    nm_auto_nmpobj NMPObject *obj         = NULL;
-
-    nm_assert(wireguard_family_id >= 0);
-    nm_assert(ifindex > 0);
-
-    nm_platform_process_events(platform);
-
-    plink = nm_platform_link_get_obj(platform, ifindex, TRUE);
-
-    if (!plink || plink->link.type != NM_LINK_TYPE_WIREGUARD) {
-        nm_platform_link_refresh(platform, ifindex);
-        plink = nm_platform_link_get_obj(platform, ifindex, TRUE);
-        if (!plink || plink->link.type != NM_LINK_TYPE_WIREGUARD)
-            return NULL;
-        if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
-            lnk_new = nmp_object_ref(plink->_link.netlink.lnk);
-    } else {
-        lnk_new = _wireguard_read_info(platform, priv->genl, wireguard_family_id, ifindex);
-        if (!lnk_new) {
-            if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
-                lnk_new = nmp_object_ref(plink->_link.netlink.lnk);
-        } else if (nmp_object_equal(plink->_link.netlink.lnk, lnk_new)) {
-            nmp_object_unref(lnk_new);
-            lnk_new = nmp_object_ref(plink->_link.netlink.lnk);
-        }
-    }
-
-    if (plink->_link.wireguard_family_id == wireguard_family_id
-        && plink->_link.netlink.lnk == lnk_new)
-        return plink;
-
-    /* we use nmp_cache_update_netlink() to re-inject the new object into the cache.
-     * For that, we need to clone it, and tweak it so that it's suitable. It's a bit
-     * of a hack, in particular that we need to clear driver and udev-device. */
-    obj                            = nmp_object_clone(plink, FALSE);
-    obj->_link.wireguard_family_id = wireguard_family_id;
-    nmp_object_unref(obj->_link.netlink.lnk);
-    obj->_link.netlink.lnk = g_steal_pointer(&lnk_new);
-    obj->link.driver       = NULL;
-    nm_clear_pointer(&obj->_link.udev.device, udev_device_unref);
-
-    cache_op =
-        nmp_cache_update_netlink(nm_platform_get_cache(platform), obj, FALSE, &obj_old, &obj_new);
-    nm_assert(NM_IN_SET(cache_op, NMP_CACHE_OPS_UPDATED));
-    if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
-        cache_on_change(platform, cache_op, obj_old, obj_new);
-        nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new);
-    }
-
-    nm_assert(!obj_new
-              || (NMP_OBJECT_GET_TYPE(obj_new) == NMP_OBJECT_TYPE_LINK
-                  && obj_new->link.type == NM_LINK_TYPE_WIREGUARD
-                  && (!obj_new->_link.netlink.lnk
-                      || NMP_OBJECT_GET_TYPE(obj_new->_link.netlink.lnk)
-                             == NMP_OBJECT_TYPE_LNK_WIREGUARD)));
-    return obj_new;
-}
-
-static int
-_wireguard_create_change_nlmsgs(NMPlatform *                              platform,
-                                int                                       ifindex,
-                                int                                       wireguard_family_id,
-                                const NMPlatformLnkWireGuard *            lnk_wireguard,
-                                const NMPWireGuardPeer *                  peers,
-                                const NMPlatformWireGuardChangePeerFlags *peer_flags,
-                                guint                                     peers_len,
-                                NMPlatformWireGuardChangeFlags            change_flags,
-                                GPtrArray **                              out_msgs)
-{
-    gs_unref_ptrarray GPtrArray *      msgs    = NULL;
-    nm_auto_nlmsg struct nl_msg *      msg     = NULL;
-    const guint                        IDX_NIL = G_MAXUINT;
-    guint                              idx_peer_curr;
-    guint                              idx_allowed_ips_curr;
-    struct nlattr *                    nest_peers;
-    struct nlattr *                    nest_curr_peer;
-    struct nlattr *                    nest_allowed_ips;
-    struct nlattr *                    nest_curr_allowed_ip;
-    NMPlatformWireGuardChangePeerFlags p_flags = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT;
-
-#define _nla_nest_end(msg, nest_start)             \
-    G_STMT_START                                   \
-    {                                              \
-        if (nla_nest_end((msg), (nest_start)) < 0) \
-            g_return_val_if_reached(-NME_BUG);     \
-    }                                              \
-    G_STMT_END
-
-    /* Adapted from LGPL-2.1+ code [1].
-     *
-     * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */
-
-    idx_peer_curr        = IDX_NIL;
-    idx_allowed_ips_curr = IDX_NIL;
-
-    /* TODO: for the moment, we always reset all peers and allowed-ips (WGDEVICE_F_REPLACE_PEERS, WGPEER_F_REPLACE_ALLOWEDIPS).
-     * The platform API should be extended to also support partial updates. In particular, configuring the same configuration
-     * multiple times, should not clear and re-add all settings, but rather sync the existing settings with the desired configuration. */
-
-again:
-
-    msg = nlmsg_alloc();
-    if (!genlmsg_put(msg,
-                     NL_AUTO_PORT,
-                     NL_AUTO_SEQ,
-                     wireguard_family_id,
-                     0,
-                     NLM_F_REQUEST,
-                     WG_CMD_SET_DEVICE,
-                     1))
-        g_return_val_if_reached(-NME_BUG);
-
-    NLA_PUT_U32(msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
-
-    if (idx_peer_curr == IDX_NIL) {
-        guint32 flags;
-
-        if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY))
-            NLA_PUT(msg,
-                    WGDEVICE_A_PRIVATE_KEY,
-                    sizeof(lnk_wireguard->private_key),
-                    lnk_wireguard->private_key);
-        if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT))
-            NLA_PUT_U16(msg, WGDEVICE_A_LISTEN_PORT, lnk_wireguard->listen_port);
-        if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK))
-            NLA_PUT_U32(msg, WGDEVICE_A_FWMARK, lnk_wireguard->fwmark);
-
-        flags = 0;
-        if (NM_FLAGS_HAS(change_flags, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS))
-            flags |= WGDEVICE_F_REPLACE_PEERS;
-        NLA_PUT_U32(msg, WGDEVICE_A_FLAGS, flags);
-    }
-
-    if (peers_len == 0)
-        goto send;
-
-    nest_curr_peer       = NULL;
-    nest_allowed_ips     = NULL;
-    nest_curr_allowed_ip = NULL;
-
-    nest_peers = nla_nest_start(msg, WGDEVICE_A_PEERS);
-    if (!nest_peers)
-        g_return_val_if_reached(-NME_BUG);
-
-    if (idx_peer_curr == IDX_NIL)
-        idx_peer_curr = 0;
-    for (; idx_peer_curr < peers_len; idx_peer_curr++) {
-        const NMPWireGuardPeer *p = &peers[idx_peer_curr];
-
-        if (peer_flags) {
-            p_flags = peer_flags[idx_peer_curr];
-            if (!NM_FLAGS_ANY(p_flags,
-                              NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME
-                                  | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY
-                                  | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL
-                                  | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT
-                                  | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
-                                  | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)) {
-                /* no flags set. We take that as indication to skip configuring the peer
-                 * entirely. */
-                nm_assert(p_flags == NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE);
-                continue;
-            }
-        }
-
-        nest_curr_peer = nla_nest_start(msg, 0);
-        if (!nest_curr_peer)
-            goto toobig_peers;
-
-        if (nla_put(msg, WGPEER_A_PUBLIC_KEY, NMP_WIREGUARD_PUBLIC_KEY_LEN, p->public_key) < 0)
-            goto toobig_peers;
-
-        if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME)) {
-            /* all other p_flags are silently ignored. */
-            if (nla_put_uint32(msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME) < 0)
-                goto toobig_peers;
-        } else {
-            if (idx_allowed_ips_curr == IDX_NIL) {
-                if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY)
-                    && nla_put(msg,
-                               WGPEER_A_PRESHARED_KEY,
-                               sizeof(p->preshared_key),
-                               p->preshared_key)
-                           < 0)
-                    goto toobig_peers;
-
-                if (NM_FLAGS_HAS(p_flags,
-                                 NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL)
-                    && nla_put_uint16(msg,
-                                      WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
-                                      p->persistent_keepalive_interval)
-                           < 0)
-                    goto toobig_peers;
-
-                if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS)
-                    && nla_put_uint32(msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS) < 0)
-                    goto toobig_peers;
-
-                if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT)) {
-                    if (NM_IN_SET(p->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
-                        if (nla_put(msg,
-                                    WGPEER_A_ENDPOINT,
-                                    p->endpoint.sa.sa_family == AF_INET ? sizeof(p->endpoint.in)
-                                                                        : sizeof(p->endpoint.in6),
-                                    &p->endpoint)
-                            < 0)
-                            goto toobig_peers;
-                    } else {
-                        /* I think there is no way to clear an endpoint, though there should be. */
-                        nm_assert(p->endpoint.sa.sa_family == AF_UNSPEC);
-                    }
-                }
-            }
-
-            if (NM_FLAGS_HAS(p_flags, NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS)
-                && p->allowed_ips_len > 0) {
-                if (idx_allowed_ips_curr == IDX_NIL)
-                    idx_allowed_ips_curr = 0;
-
-                nest_allowed_ips = nla_nest_start(msg, WGPEER_A_ALLOWEDIPS);
-                if (!nest_allowed_ips)
-                    goto toobig_allowedips;
-
-                for (; idx_allowed_ips_curr < p->allowed_ips_len; idx_allowed_ips_curr++) {
-                    const NMPWireGuardAllowedIP *aip = &p->allowed_ips[idx_allowed_ips_curr];
-
-                    nest_curr_allowed_ip = nla_nest_start(msg, 0);
-                    if (!nest_curr_allowed_ip)
-                        goto toobig_allowedips;
-
-                    g_return_val_if_fail(NM_IN_SET(aip->family, AF_INET, AF_INET6), -NME_BUG);
-
-                    if (nla_put_uint16(msg, WGALLOWEDIP_A_FAMILY, aip->family) < 0)
-                        goto toobig_allowedips;
-                    if (nla_put(msg,
-                                WGALLOWEDIP_A_IPADDR,
-                                nm_utils_addr_family_to_size(aip->family),
-                                &aip->addr)
-                        < 0)
-                        goto toobig_allowedips;
-                    if (nla_put_uint8(msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0)
-                        goto toobig_allowedips;
-
-                    _nla_nest_end(msg, nest_curr_allowed_ip);
-                    nest_curr_allowed_ip = NULL;
-                }
-                idx_allowed_ips_curr = IDX_NIL;
-
-                _nla_nest_end(msg, nest_allowed_ips);
-                nest_allowed_ips = NULL;
-            }
-        }
-
-        _nla_nest_end(msg, nest_curr_peer);
-        nest_curr_peer = NULL;
-    }
-
-    _nla_nest_end(msg, nest_peers);
-    goto send;
-
-toobig_allowedips:
-    if (nest_curr_allowed_ip)
-        nla_nest_cancel(msg, nest_curr_allowed_ip);
-    if (nest_allowed_ips)
-        _nla_nest_end(msg, nest_allowed_ips);
-    _nla_nest_end(msg, nest_curr_peer);
-    _nla_nest_end(msg, nest_peers);
-    goto send;
-
-toobig_peers:
-    if (nest_curr_peer)
-        nla_nest_cancel(msg, nest_curr_peer);
-    _nla_nest_end(msg, nest_peers);
-    goto send;
-
-send:
-    if (!msgs)
-        msgs = g_ptr_array_new_with_free_func((GDestroyNotify) nlmsg_free);
-    g_ptr_array_add(msgs, g_steal_pointer(&msg));
-
-    if (idx_peer_curr != IDX_NIL && idx_peer_curr < peers_len)
-        goto again;
-
-    NM_SET_OUT(out_msgs, g_steal_pointer(&msgs));
-    return 0;
-
-nla_put_failure:
-    g_return_val_if_reached(-NME_BUG);
-
-#undef _nla_nest_end
-}
-
-static int
-link_wireguard_change(NMPlatform *                              platform,
-                      int                                       ifindex,
-                      const NMPlatformLnkWireGuard *            lnk_wireguard,
-                      const NMPWireGuardPeer *                  peers,
-                      const NMPlatformWireGuardChangePeerFlags *peer_flags,
-                      guint                                     peers_len,
-                      NMPlatformWireGuardChangeFlags            change_flags)
-{
-    NMLinuxPlatformPrivate *priv      = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    gs_unref_ptrarray GPtrArray *msgs = NULL;
-    int                          wireguard_family_id;
-    guint                        i;
-    int                          r;
-
-    wireguard_family_id = _wireguard_get_family_id(platform, ifindex);
-    if (wireguard_family_id < 0)
-        return -NME_PL_NO_FIRMWARE;
-
-    r = _wireguard_create_change_nlmsgs(platform,
-                                        ifindex,
-                                        wireguard_family_id,
-                                        lnk_wireguard,
-                                        peers,
-                                        peer_flags,
-                                        peers_len,
-                                        change_flags,
-                                        &msgs);
-    if (r < 0) {
-        _LOGW("wireguard: set-device, cannot construct netlink message: %s", nm_strerror(r));
-        return r;
-    }
-
-    for (i = 0; i < msgs->len; i++) {
-        r = nl_send_auto(priv->genl, msgs->pdata[i]);
-        if (r < 0) {
-            _LOGW("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror(r));
-            return r;
-        }
-
-        do {
-            r = nl_recvmsgs(priv->genl, NULL);
-        } while (r == -EAGAIN);
-        if (r < 0) {
-            _LOGW("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror(r));
-            return r;
-        }
-
-        _LOGT("wireguard: set-device, message #%u sent and confirmed", i);
-    }
-
-    _wireguard_refresh_link(platform, wireguard_family_id, ifindex);
-
-    return 0;
-}
-
-/*****************************************************************************/
-
-static void
-_nmp_link_address_set(NMPLinkAddress *dst, const struct nlattr *nla)
-{
-    *dst = (NMPLinkAddress){
-        .len = 0,
-    };
-    if (nla) {
-        int l = nla_len(nla);
-
-        if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
-            G_STATIC_ASSERT_EXPR(sizeof(dst->data) == NM_UTILS_HWADDR_LEN_MAX);
-            memcpy(dst->data, nla_data(nla), l);
-            dst->len = l;
-        }
-    }
-}
-
-/* Copied and heavily modified from libnl3's link_msg_parser(). */
-static NMPObject *
-_new_from_nl_link(NMPlatform *     platform,
-                  const NMPCache * cache,
-                  struct nlmsghdr *nlh,
-                  gboolean         id_only)
-{
-    static const struct nla_policy policy[] = {
-        [IFLA_IFNAME]        = {.type = NLA_STRING, .maxlen = IFNAMSIZ},
-        [IFLA_MTU]           = {.type = NLA_U32},
-        [IFLA_TXQLEN]        = {.type = NLA_U32},
-        [IFLA_LINK]          = {.type = NLA_U32},
-        [IFLA_WEIGHT]        = {.type = NLA_U32},
-        [IFLA_MASTER]        = {.type = NLA_U32},
-        [IFLA_OPERSTATE]     = {.type = NLA_U8},
-        [IFLA_LINKMODE]      = {.type = NLA_U8},
-        [IFLA_LINKINFO]      = {.type = NLA_NESTED},
-        [IFLA_QDISC]         = {.type = NLA_STRING, .maxlen = IFQDISCSIZ},
-        [IFLA_STATS]         = {.minlen = nm_offsetofend(struct rtnl_link_stats, tx_compressed)},
-        [IFLA_STATS64]       = {.minlen = nm_offsetofend(struct rtnl_link_stats64, tx_compressed)},
-        [IFLA_MAP]           = {.minlen = nm_offsetofend(struct rtnl_link_ifmap, port)},
-        [IFLA_IFALIAS]       = {.type = NLA_STRING, .maxlen = IFALIASZ},
-        [IFLA_NUM_VF]        = {.type = NLA_U32},
-        [IFLA_AF_SPEC]       = {.type = NLA_NESTED},
-        [IFLA_PROMISCUITY]   = {.type = NLA_U32},
-        [IFLA_NUM_TX_QUEUES] = {.type = NLA_U32},
-        [IFLA_NUM_RX_QUEUES] = {.type = NLA_U32},
-        [IFLA_GROUP]         = {.type = NLA_U32},
-        [IFLA_CARRIER]       = {.type = NLA_U8},
-        [IFLA_PHYS_PORT_ID]  = {.type = NLA_UNSPEC},
-        [IFLA_NET_NS_PID]    = {.type = NLA_U32},
-        [IFLA_NET_NS_FD]     = {.type = NLA_U32},
-        [IFLA_LINK_NETNSID]  = {},
-    };
-    const struct ifinfomsg *ifi;
-    struct nlattr *         tb[G_N_ELEMENTS(policy)];
-    struct nlattr *         nl_info_data               = NULL;
-    const char *            nl_info_kind               = NULL;
-    nm_auto_nmpobj NMPObject *obj                      = NULL;
-    gboolean                  completed_from_cache_val = FALSE;
-    gboolean *                completed_from_cache     = cache ? &completed_from_cache_val : NULL;
-    const NMPObject *         link_cached              = NULL;
-    const NMPObject *         lnk_data                 = NULL;
-    gboolean                  address_complete_from_cache   = TRUE;
-    gboolean                  broadcast_complete_from_cache = TRUE;
-    gboolean                  lnk_data_complete_from_cache  = TRUE;
-    gboolean                  need_ext_data                 = FALSE;
-    gboolean                  af_inet6_token_valid          = FALSE;
-    gboolean                  af_inet6_addr_gen_mode_valid  = FALSE;
-
-    if (!nlmsg_valid_hdr(nlh, sizeof(*ifi)))
-        return NULL;
-
-    ifi = nlmsg_data(nlh);
-
-    if (ifi->ifi_family != AF_UNSPEC)
-        return NULL;
-    if (ifi->ifi_index <= 0)
-        return NULL;
-
-    obj = nmp_object_new_link(ifi->ifi_index);
-
-    if (id_only)
-        return g_steal_pointer(&obj);
-
-    if (nlmsg_parse_arr(nlh, sizeof(*ifi), tb, policy) < 0)
-        return NULL;
-
-    if (!tb[IFLA_IFNAME])
-        return NULL;
-    nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
-    if (!obj->link.name[0])
-        return NULL;
-
-    if (!tb[IFLA_MTU]) {
-        /* Kernel has two places that send RTM_GETLINK messages:
-         * net/core/rtnetlink.c and net/wireless/ext-core.c.
-         * Unfortunately ext-core.c sets only IFLA_WIRELESS and
-         * IFLA_IFNAME. This confuses code in this function, because
-         * it cannot get complete set of data for the interface and
-         * later incomplete object this function creates is used to
-         * overwrite existing data in NM's cache.
-         * Since ext-core.c doesn't set IFLA_MTU we can use it as a
-         * signal to ignore incoming message.
-         * To some extent this is a hack and correct approach is to
-         * merge objects per-field.
-         */
-        return NULL;
-    }
-    obj->link.mtu = nla_get_u32(tb[IFLA_MTU]);
-
-    if (tb[IFLA_LINKINFO]) {
-        static const struct nla_policy policy_link_info[] = {
-            [IFLA_INFO_KIND]   = {.type = NLA_STRING},
-            [IFLA_INFO_DATA]   = {.type = NLA_NESTED},
-            [IFLA_INFO_XSTATS] = {.type = NLA_NESTED},
-        };
-        struct nlattr *li[G_N_ELEMENTS(policy_link_info)];
-
-        if (nla_parse_nested_arr(li, tb[IFLA_LINKINFO], policy_link_info) < 0)
-            return NULL;
-
-        if (li[IFLA_INFO_KIND])
-            nl_info_kind = nla_get_string(li[IFLA_INFO_KIND]);
-
-        nl_info_data = li[IFLA_INFO_DATA];
-    }
-
-    if (tb[IFLA_STATS64]) {
-        const char *stats = nla_data(tb[IFLA_STATS64]);
-
-        obj->link.rx_packets =
-            unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, rx_packets)]);
-        obj->link.rx_bytes =
-            unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, rx_bytes)]);
-        obj->link.tx_packets =
-            unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, tx_packets)]);
-        obj->link.tx_bytes =
-            unaligned_read_ne64(&stats[G_STRUCT_OFFSET(struct rtnl_link_stats64, tx_bytes)]);
-    }
-
-    obj->link.n_ifi_flags = ifi->ifi_flags;
-    obj->link.connected   = NM_FLAGS_HAS(obj->link.n_ifi_flags, IFF_LOWER_UP);
-    obj->link.arptype     = ifi->ifi_type;
-
-    obj->link.type = _linktype_get_type(platform,
-                                        cache,
-                                        nl_info_kind,
-                                        obj->link.ifindex,
-                                        obj->link.name,
-                                        obj->link.n_ifi_flags,
-                                        obj->link.arptype,
-                                        completed_from_cache,
-                                        &link_cached,
-                                        &obj->link.kind);
-
-    if (tb[IFLA_MASTER])
-        obj->link.master = nla_get_u32(tb[IFLA_MASTER]);
-
-    if (tb[IFLA_LINK]) {
-        if (!tb[IFLA_LINK_NETNSID])
-            obj->link.parent = nla_get_u32(tb[IFLA_LINK]);
-        else
-            obj->link.parent = NM_PLATFORM_LINK_OTHER_NETNS;
-    }
-
-    if (tb[IFLA_ADDRESS]) {
-        _nmp_link_address_set(&obj->link.l_address, tb[IFLA_ADDRESS]);
-        address_complete_from_cache = FALSE;
-    }
-
-    if (tb[IFLA_BROADCAST]) {
-        _nmp_link_address_set(&obj->link.l_broadcast, tb[IFLA_BROADCAST]);
-        broadcast_complete_from_cache = FALSE;
-    }
-
-    if (tb[IFLA_AF_SPEC]) {
-        struct nlattr *af_attr;
-        int            remaining;
-
-        nla_for_each_nested (af_attr, tb[IFLA_AF_SPEC], remaining) {
-            switch (nla_type(af_attr)) {
-            case AF_INET6:
-                _parse_af_inet6(platform,
-                                af_attr,
-                                &obj->link.inet6_token,
-                                &af_inet6_token_valid,
-                                &obj->link.inet6_addr_gen_mode_inv,
-                                &af_inet6_addr_gen_mode_valid);
-                break;
-            }
-        }
-    }
-
-    switch (obj->link.type) {
-    case NM_LINK_TYPE_BRIDGE:
-        lnk_data = _parse_lnk_bridge(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_GRE:
-    case NM_LINK_TYPE_GRETAP:
-        lnk_data = _parse_lnk_gre(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_INFINIBAND:
-        lnk_data = _parse_lnk_infiniband(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_IP6TNL:
-        lnk_data = _parse_lnk_ip6tnl(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_IP6GRE:
-    case NM_LINK_TYPE_IP6GRETAP:
-        lnk_data = _parse_lnk_ip6gre(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_IPIP:
-        lnk_data = _parse_lnk_ipip(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_MACSEC:
-        lnk_data = _parse_lnk_macsec(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_MACVLAN:
-    case NM_LINK_TYPE_MACVTAP:
-        lnk_data = _parse_lnk_macvlan(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_SIT:
-        lnk_data = _parse_lnk_sit(nl_info_kind, nl_info_data);
-        break;
-    case NM_LINK_TYPE_TUN:
-        lnk_data = _parse_lnk_tun(nl_info_kind, nl_info_data);
-        break;
-    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;
-    case NM_LINK_TYPE_WIFI:
-    case NM_LINK_TYPE_OLPC_MESH:
-    case NM_LINK_TYPE_WPAN:
-        need_ext_data                = TRUE;
-        lnk_data_complete_from_cache = FALSE;
-        break;
-    case NM_LINK_TYPE_WIREGUARD:
-        lnk_data_complete_from_cache = TRUE;
-        break;
-    default:
-        lnk_data_complete_from_cache = FALSE;
-        break;
-    }
-
-    if (completed_from_cache
-        && (lnk_data_complete_from_cache || need_ext_data || address_complete_from_cache
-            || broadcast_complete_from_cache || !af_inet6_token_valid
-            || !af_inet6_addr_gen_mode_valid || !tb[IFLA_STATS64])) {
-        _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached);
-        if (link_cached && link_cached->_link.netlink.is_in_netlink) {
-            if (lnk_data_complete_from_cache && link_cached->link.type == obj->link.type
-                && link_cached->_link.netlink.lnk
-                && (!lnk_data || nmp_object_equal(lnk_data, link_cached->_link.netlink.lnk))) {
-                /* We always try to look into the cache and reuse the object there.
-                 * We do that, because we consider the lnk object as immutable and don't
-                 * modify it after creating. Hence we can share it and reuse.
-                 *
-                 * Also, sometimes the info-data is missing for updates. In this case
-                 * we want to keep the previously received lnk_data. */
-                nmp_object_unref(lnk_data);
-                lnk_data = nmp_object_ref(link_cached->_link.netlink.lnk);
-            }
-
-            if (need_ext_data && link_cached->link.type == obj->link.type
-                && link_cached->_link.ext_data) {
-                /* Prefer reuse of existing ext_data object */
-                obj->_link.ext_data = g_object_ref(link_cached->_link.ext_data);
-            }
-
-            if (address_complete_from_cache)
-                obj->link.l_address = link_cached->link.l_address;
-            if (broadcast_complete_from_cache)
-                obj->link.l_broadcast = link_cached->link.l_broadcast;
-            if (!af_inet6_token_valid)
-                obj->link.inet6_token = link_cached->link.inet6_token;
-            if (!af_inet6_addr_gen_mode_valid)
-                obj->link.inet6_addr_gen_mode_inv = link_cached->link.inet6_addr_gen_mode_inv;
-            if (!tb[IFLA_STATS64]) {
-                obj->link.rx_packets = link_cached->link.rx_packets;
-                obj->link.rx_bytes   = link_cached->link.rx_bytes;
-                obj->link.tx_packets = link_cached->link.tx_packets;
-                obj->link.tx_bytes   = link_cached->link.tx_bytes;
-            }
-        }
-    }
-
-    obj->_link.netlink.lnk = lnk_data;
-
-    if (need_ext_data && obj->_link.ext_data == NULL) {
-        switch (obj->link.type) {
-        case NM_LINK_TYPE_WIFI:
-        case NM_LINK_TYPE_OLPC_MESH:
-            obj->_link.ext_data =
-                (GObject *) nm_wifi_utils_new(ifi->ifi_index,
-                                              _genl_sock(NM_LINUX_PLATFORM(platform)),
-                                              TRUE);
-            break;
-        case NM_LINK_TYPE_WPAN:
-            obj->_link.ext_data =
-                (GObject *) nm_wpan_utils_new(ifi->ifi_index,
-                                              _genl_sock(NM_LINUX_PLATFORM(platform)),
-                                              TRUE);
-            break;
-        default:
-            g_assert_not_reached();
-        }
-    }
-
-    if (obj->link.type == NM_LINK_TYPE_WIREGUARD) {
-        const NMPObject *lnk_data_new = NULL;
-        struct nl_sock * genl         = NM_LINUX_PLATFORM_GET_PRIVATE(platform)->genl;
-
-        /* The WireGuard kernel module does not yet send link update
-         * notifications, so we don't actually update the cache. For
-         * now, always refetch link data here. */
-
-        _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached);
-        if (link_cached && link_cached->_link.netlink.is_in_netlink
-            && link_cached->link.type == NM_LINK_TYPE_WIREGUARD)
-            obj->_link.wireguard_family_id = link_cached->_link.wireguard_family_id;
-        else
-            obj->_link.wireguard_family_id = -1;
-
-        if (obj->_link.wireguard_family_id < 0)
-            obj->_link.wireguard_family_id = genl_ctrl_resolve(genl, "wireguard");
-
-        if (obj->_link.wireguard_family_id >= 0) {
-            lnk_data_new = _wireguard_read_info(platform,
-                                                genl,
-                                                obj->_link.wireguard_family_id,
-                                                obj->link.ifindex);
-        }
-
-        if (lnk_data_new && obj->_link.netlink.lnk
-            && nmp_object_equal(obj->_link.netlink.lnk, lnk_data_new))
-            nmp_object_unref(lnk_data_new);
-        else {
-            nmp_object_unref(obj->_link.netlink.lnk);
-            obj->_link.netlink.lnk = lnk_data_new;
-        }
-    }
-
-    obj->_link.netlink.is_in_netlink = TRUE;
-    return g_steal_pointer(&obj);
-}
-
-/* Copied and heavily modified from libnl3's addr_msg_parser(). */
-static NMPObject *
-_new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only)
-{
-    static const struct nla_policy policy[] = {
-        [IFA_LABEL]     = {.type = NLA_STRING, .maxlen = IFNAMSIZ},
-        [IFA_CACHEINFO] = {.minlen = nm_offsetofend(struct ifa_cacheinfo, tstamp)},
-        [IFA_FLAGS]     = {},
-    };
-    struct nlattr *         tb[G_N_ELEMENTS(policy)];
-    const struct ifaddrmsg *ifa;
-    gboolean                is_v4;
-    nm_auto_nmpobj NMPObject *obj = NULL;
-    int                       addr_len;
-    guint32                   lifetime, preferred, timestamp;
-
-    if (!nlmsg_valid_hdr(nlh, sizeof(*ifa)))
-        return NULL;
-
-    ifa = nlmsg_data(nlh);
-
-    if (!NM_IN_SET(ifa->ifa_family, AF_INET, AF_INET6))
-        return NULL;
-
-    is_v4 = ifa->ifa_family == AF_INET;
-
-    if (nlmsg_parse_arr(nlh, sizeof(*ifa), tb, policy) < 0)
-        return NULL;
-
-    addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr);
-
-    if (ifa->ifa_prefixlen > (is_v4 ? 32 : 128))
-        return NULL;
-
-    /*****************************************************************/
-
-    obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL);
-
-    obj->ip_address.ifindex = ifa->ifa_index;
-    obj->ip_address.plen    = ifa->ifa_prefixlen;
-
-    _check_addr_or_return_null(tb, IFA_ADDRESS, addr_len);
-    _check_addr_or_return_null(tb, IFA_LOCAL, addr_len);
-    if (is_v4) {
-        /* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they
-         * are effectively 0.0.0.0 (all-zero). */
-        if (tb[IFA_LOCAL])
-            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.
-         *
-         * If IFA_LOCAL is missing, IFA_ADDRESS is @address and @peer_address
-         * is :: (all-zero).
-         *
-         * If unexpectedly IFA_ADDRESS is missing, make the best of it -- but it _should_
-         * actually be there. */
-        if (tb[IFA_ADDRESS] || tb[IFA_LOCAL]) {
-            if (tb[IFA_LOCAL]) {
-                memcpy(&obj->ip6_address.address, nla_data(tb[IFA_LOCAL]), addr_len);
-                if (tb[IFA_ADDRESS])
-                    memcpy(&obj->ip6_address.peer_address, nla_data(tb[IFA_ADDRESS]), addr_len);
-                else
-                    obj->ip6_address.peer_address = obj->ip6_address.address;
-            } else
-                memcpy(&obj->ip6_address.address, nla_data(tb[IFA_ADDRESS]), addr_len);
-        }
-    }
-
-    obj->ip_address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
-
-    obj->ip_address.n_ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : ifa->ifa_flags;
-
-    if (is_v4) {
-        if (tb[IFA_LABEL]) {
-            char label[IFNAMSIZ];
-
-            nla_strlcpy(label, tb[IFA_LABEL], IFNAMSIZ);
-
-            /* Check for ':'; we're only interested in labels used as interface aliases */
-            if (strchr(label, ':'))
-                g_strlcpy(obj->ip4_address.label, label, sizeof(obj->ip4_address.label));
-        }
-    }
-
-    lifetime  = NM_PLATFORM_LIFETIME_PERMANENT;
-    preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-    timestamp = 0;
-    /* IPv6 only */
-    if (tb[IFA_CACHEINFO]) {
-        const struct ifa_cacheinfo *ca;
-
-        ca        = nla_data_as(struct ifa_cacheinfo, tb[IFA_CACHEINFO]);
-        lifetime  = ca->ifa_valid;
-        preferred = ca->ifa_prefered;
-        timestamp = ca->tstamp;
-    }
-    _addrtime_get_lifetimes(timestamp,
-                            lifetime,
-                            preferred,
-                            &obj->ip_address.timestamp,
-                            &obj->ip_address.lifetime,
-                            &obj->ip_address.preferred);
-
-    return g_steal_pointer(&obj);
-}
-
-/* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */
-static NMPObject *
-_new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only)
-{
-    static const struct nla_policy policy[] = {
-        [RTA_TABLE]     = {.type = NLA_U32},
-        [RTA_IIF]       = {.type = NLA_U32},
-        [RTA_OIF]       = {.type = NLA_U32},
-        [RTA_PRIORITY]  = {.type = NLA_U32},
-        [RTA_PREF]      = {.type = NLA_U8},
-        [RTA_FLOW]      = {.type = NLA_U32},
-        [RTA_CACHEINFO] = {.minlen = nm_offsetofend(struct rta_cacheinfo, rta_tsage)},
-        [RTA_METRICS]   = {.type = NLA_NESTED},
-        [RTA_MULTIPATH] = {.type = NLA_NESTED},
-    };
-    const struct rtmsg *rtm;
-    struct nlattr *     tb[G_N_ELEMENTS(policy)];
-    gboolean            is_v4;
-    nm_auto_nmpobj NMPObject *obj = NULL;
-    int                       addr_len;
-    struct {
-        gboolean is_present;
-        int      ifindex;
-        NMIPAddr gateway;
-    } nh = {
-        .is_present = FALSE,
-    };
-    guint32 mss;
-    guint32 window   = 0;
-    guint32 cwnd     = 0;
-    guint32 initcwnd = 0;
-    guint32 initrwnd = 0;
-    guint32 mtu      = 0;
-    guint32 lock     = 0;
-
-    if (!nlmsg_valid_hdr(nlh, sizeof(*rtm)))
-        return NULL;
-
-    rtm = nlmsg_data(nlh);
-
-    /*****************************************************************
-     * only handle ~supported~ routes.
-     *****************************************************************/
-
-    if (!NM_IN_SET(rtm->rtm_family, AF_INET, AF_INET6))
-        return NULL;
-
-    if (!NM_IN_SET(rtm->rtm_type, RTN_UNICAST, RTN_LOCAL))
-        return NULL;
-
-    if (nlmsg_parse_arr(nlh, sizeof(struct rtmsg), tb, policy) < 0)
-        return NULL;
-
-    /*****************************************************************/
-
-    is_v4    = rtm->rtm_family == AF_INET;
-    addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr);
-
-    if (rtm->rtm_dst_len > (is_v4 ? 32 : 128))
-        return NULL;
-
-    /*****************************************************************
-     * parse nexthops. Only handle routes with one nh.
-     *****************************************************************/
-
-    if (tb[RTA_MULTIPATH]) {
-        size_t            tlen = nla_len(tb[RTA_MULTIPATH]);
-        struct rtnexthop *rtnh;
-
-        if (tlen < sizeof(*rtnh))
-            goto rta_multipath_done;
-
-        rtnh = nla_data_as(struct rtnexthop, tb[RTA_MULTIPATH]);
-
-        if (tlen < rtnh->rtnh_len)
-            goto rta_multipath_done;
-
-        while (TRUE) {
-            if (nh.is_present) {
-                /* we don't support multipath routes. */
-                return NULL;
-            }
-
-            nh.is_present = TRUE;
-            nh.ifindex    = rtnh->rtnh_ifindex;
-
-            if (rtnh->rtnh_len > sizeof(*rtnh)) {
-                struct nlattr *ntb[G_N_ELEMENTS(policy)];
-
-                if (nla_parse_arr(ntb,
-                                  (struct nlattr *) RTNH_DATA(rtnh),
-                                  rtnh->rtnh_len - sizeof(*rtnh),
-                                  policy)
-                    < 0)
-                    return NULL;
-
-                if (_check_addr_or_return_null(ntb, RTA_GATEWAY, addr_len))
-                    memcpy(&nh.gateway, nla_data(ntb[RTA_GATEWAY]), addr_len);
-            }
-
-            if (tlen < RTNH_ALIGN(rtnh->rtnh_len) + sizeof(*rtnh))
-                goto rta_multipath_done;
-
-            tlen -= RTNH_ALIGN(rtnh->rtnh_len);
-            rtnh = RTNH_NEXT(rtnh);
-        }
-rta_multipath_done:;
-    }
-
-    if (tb[RTA_OIF] || tb[RTA_GATEWAY] || tb[RTA_FLOW]) {
-        int      ifindex = 0;
-        NMIPAddr gateway = {};
-
-        if (tb[RTA_OIF])
-            ifindex = nla_get_u32(tb[RTA_OIF]);
-        if (_check_addr_or_return_null(tb, RTA_GATEWAY, addr_len))
-            memcpy(&gateway, nla_data(tb[RTA_GATEWAY]), addr_len);
-
-        if (!nh.is_present) {
-            /* If no nexthops have been provided via RTA_MULTIPATH
-             * we add it as regular nexthop to maintain backwards
-             * compatibility */
-            nh.ifindex = ifindex;
-            nh.gateway = gateway;
-        } else {
-            /* Kernel supports new style nexthop configuration,
-             * verify that it is a duplicate and ignore old-style nexthop. */
-            if (nh.ifindex != ifindex || memcmp(&nh.gateway, &gateway, addr_len) != 0)
-                return NULL;
-        }
-    } else if (!nh.is_present)
-        return NULL;
-
-    /*****************************************************************/
-
-    mss = 0;
-    if (tb[RTA_METRICS]) {
-        static const struct nla_policy rtax_policy[] = {
-            [RTAX_LOCK]     = {.type = NLA_U32},
-            [RTAX_ADVMSS]   = {.type = NLA_U32},
-            [RTAX_WINDOW]   = {.type = NLA_U32},
-            [RTAX_CWND]     = {.type = NLA_U32},
-            [RTAX_INITCWND] = {.type = NLA_U32},
-            [RTAX_INITRWND] = {.type = NLA_U32},
-            [RTAX_MTU]      = {.type = NLA_U32},
-        };
-        struct nlattr *mtb[G_N_ELEMENTS(rtax_policy)];
-
-        if (nla_parse_nested_arr(mtb, tb[RTA_METRICS], rtax_policy) < 0)
-            return NULL;
-
-        if (mtb[RTAX_LOCK])
-            lock = nla_get_u32(mtb[RTAX_LOCK]);
-        if (mtb[RTAX_ADVMSS])
-            mss = nla_get_u32(mtb[RTAX_ADVMSS]);
-        if (mtb[RTAX_WINDOW])
-            window = nla_get_u32(mtb[RTAX_WINDOW]);
-        if (mtb[RTAX_CWND])
-            cwnd = nla_get_u32(mtb[RTAX_CWND]);
-        if (mtb[RTAX_INITCWND])
-            initcwnd = nla_get_u32(mtb[RTAX_INITCWND]);
-        if (mtb[RTAX_INITRWND])
-            initrwnd = nla_get_u32(mtb[RTAX_INITRWND]);
-        if (mtb[RTAX_MTU])
-            mtu = nla_get_u32(mtb[RTAX_MTU]);
-    }
-
-    /*****************************************************************/
-
-    obj = nmp_object_new(is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
-
-    obj->ip_route.type_coerced  = nm_platform_route_type_coerce(rtm->rtm_type);
-    obj->ip_route.table_coerced = nm_platform_route_table_coerce(
-        tb[RTA_TABLE] ? nla_get_u32(tb[RTA_TABLE]) : (guint32) rtm->rtm_table);
-
-    obj->ip_route.ifindex = nh.ifindex;
-
-    if (_check_addr_or_return_null(tb, RTA_DST, addr_len))
-        memcpy(obj->ip_route.network_ptr, nla_data(tb[RTA_DST]), addr_len);
-
-    obj->ip_route.plen = rtm->rtm_dst_len;
-
-    if (tb[RTA_PRIORITY])
-        obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]);
-
-    if (is_v4)
-        obj->ip4_route.gateway = nh.gateway.addr4;
-    else
-        obj->ip6_route.gateway = nh.gateway.addr6;
-
-    if (is_v4)
-        obj->ip4_route.scope_inv = nm_platform_route_scope_inv(rtm->rtm_scope);
-
-    if (_check_addr_or_return_null(tb, RTA_PREFSRC, addr_len)) {
-        if (is_v4)
-            memcpy(&obj->ip4_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len);
-        else
-            memcpy(&obj->ip6_route.pref_src, nla_data(tb[RTA_PREFSRC]), addr_len);
-    }
-
-    if (is_v4)
-        obj->ip4_route.tos = rtm->rtm_tos;
-    else {
-        if (tb[RTA_SRC]) {
-            _check_addr_or_return_null(tb, RTA_SRC, addr_len);
-            memcpy(&obj->ip6_route.src, nla_data(tb[RTA_SRC]), addr_len);
-        }
-        obj->ip6_route.src_plen = rtm->rtm_src_len;
-    }
-
-    obj->ip_route.mss           = mss;
-    obj->ip_route.window        = window;
-    obj->ip_route.cwnd          = cwnd;
-    obj->ip_route.initcwnd      = initcwnd;
-    obj->ip_route.initrwnd      = initrwnd;
-    obj->ip_route.mtu           = mtu;
-    obj->ip_route.lock_window   = NM_FLAGS_HAS(lock, 1 << RTAX_WINDOW);
-    obj->ip_route.lock_cwnd     = NM_FLAGS_HAS(lock, 1 << RTAX_CWND);
-    obj->ip_route.lock_initcwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITCWND);
-    obj->ip_route.lock_initrwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITRWND);
-    obj->ip_route.lock_mtu      = NM_FLAGS_HAS(lock, 1 << RTAX_MTU);
-
-    if (!is_v4) {
-        if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF)) {
-            /* Detect support for RTA_PREF by inspecting the netlink message.
-             * RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */
-            _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF,
-                                             tb[RTA_PREF] ? 1 : -1);
-        }
-
-        if (tb[RTA_PREF])
-            obj->ip6_route.rt_pref = nla_get_u8(tb[RTA_PREF]);
-    }
-
-    obj->ip_route.r_rtm_flags = rtm->rtm_flags;
-    obj->ip_route.rt_source   = nmp_utils_ip_config_source_from_rtprot(rtm->rtm_protocol);
-
-    return g_steal_pointer(&obj);
-}
-
-static NMPObject *
-_new_from_nl_routing_rule(struct nlmsghdr *nlh, gboolean id_only)
-{
-    static const struct nla_policy policy[] = {
-        [FRA_UNSPEC] = {},
-        [FRA_DST]    = {/* struct in_addr, struct in6_addr */},
-        [FRA_SRC]    = {/* struct in_addr, struct in6_addr */},
-        [FRA_IIFNAME] =
-            {
-                .type   = NLA_STRING,
-                .maxlen = IFNAMSIZ,
-            },
-        [FRA_GOTO] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_UNUSED2] = {},
-        [FRA_PRIORITY] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_UNUSED3] = {},
-        [FRA_UNUSED4] = {},
-        [FRA_UNUSED5] = {},
-        [FRA_FWMARK] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_FLOW] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_TUN_ID] =
-            {
-                .type = NLA_U64,
-            },
-        [FRA_SUPPRESS_IFGROUP] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_SUPPRESS_PREFIXLEN] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_TABLE] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_FWMASK] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_OIFNAME] =
-            {
-                .type   = NLA_STRING,
-                .maxlen = IFNAMSIZ,
-            },
-        [FRA_PAD] =
-            {
-                .type = NLA_U32,
-            },
-        [FRA_L3MDEV] =
-            {
-                .type = NLA_U8,
-            },
-        [FRA_UID_RANGE] =
-            {
-                .minlen = sizeof(NMFibRuleUidRange),
-                .maxlen = sizeof(NMFibRuleUidRange),
-            },
-        [FRA_PROTOCOL] =
-            {
-                .type = NLA_U8,
-            },
-        [FRA_IP_PROTO] =
-            {
-                .type = NLA_U8,
-            },
-        [FRA_SPORT_RANGE] =
-            {
-                .minlen = sizeof(NMFibRulePortRange),
-                .maxlen = sizeof(NMFibRulePortRange),
-            },
-        [FRA_DPORT_RANGE] =
-            {
-                .minlen = sizeof(NMFibRulePortRange),
-                .maxlen = sizeof(NMFibRulePortRange),
-            },
-    };
-    struct nlattr *            tb[G_N_ELEMENTS(policy)];
-    const struct fib_rule_hdr *frh;
-    NMPlatformRoutingRule *    props;
-    nm_auto_nmpobj NMPObject *obj = NULL;
-    int                       addr_family;
-    guint8                    addr_size;
-
-    if (nlmsg_parse_arr(nlh, sizeof(*frh), tb, policy) < 0)
-        return NULL;
-
-    frh = nlmsg_data(nlh);
-
-    addr_family = frh->family;
-
-    if (!NM_IN_SET(addr_family, AF_INET, AF_INET6)) {
-        /* we don't care about other address families. */
-        return NULL;
-    }
-
-    addr_size = nm_utils_addr_family_to_size(addr_family);
-
-    obj   = nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, NULL);
-    props = &obj->routing_rule;
-
-    props->addr_family = addr_family;
-    props->action      = frh->action;
-    props->flags       = frh->flags;
-    props->tos         = frh->tos;
-
-    props->table = tb[FRA_TABLE] ? nla_get_u32(tb[FRA_TABLE]) : frh->table;
-
-    if (tb[FRA_SUPPRESS_PREFIXLEN])
-        props->suppress_prefixlen_inverse = ~nla_get_u32(tb[FRA_SUPPRESS_PREFIXLEN]);
-
-    if (tb[FRA_SUPPRESS_IFGROUP])
-        props->suppress_ifgroup_inverse = ~nla_get_u32(tb[FRA_SUPPRESS_IFGROUP]);
-
-    if (tb[FRA_IIFNAME])
-        nla_strlcpy(props->iifname, tb[FRA_IIFNAME], sizeof(props->iifname));
-
-    if (tb[FRA_OIFNAME])
-        nla_strlcpy(props->oifname, tb[FRA_OIFNAME], sizeof(props->oifname));
-
-    if (tb[FRA_PRIORITY])
-        props->priority = nla_get_u32(tb[FRA_PRIORITY]);
-
-    if (tb[FRA_FWMARK])
-        props->fwmark = nla_get_u32(tb[FRA_FWMARK]);
-
-    if (tb[FRA_FWMASK])
-        props->fwmask = nla_get_u32(tb[FRA_FWMASK]);
-
-    if (tb[FRA_GOTO])
-        props->goto_target = nla_get_u32(tb[FRA_GOTO]);
-
-    props->src_len = frh->src_len;
-    if (props->src_len > addr_size * 8)
-        return NULL;
-    if (!tb[FRA_SRC]) {
-        if (props->src_len > 0)
-            return NULL;
-    } else if (!nm_ip_addr_set_from_untrusted(addr_family,
-                                              &props->src,
-                                              nla_data(tb[FRA_SRC]),
-                                              nla_len(tb[FRA_SRC]),
-                                              NULL))
-        return NULL;
-
-    props->dst_len = frh->dst_len;
-    if (props->dst_len > addr_size * 8)
-        return NULL;
-    if (!tb[FRA_DST]) {
-        if (props->dst_len > 0)
-            return NULL;
-    } else if (!nm_ip_addr_set_from_untrusted(addr_family,
-                                              &props->dst,
-                                              nla_data(tb[FRA_DST]),
-                                              nla_len(tb[FRA_DST]),
-                                              NULL))
-        return NULL;
-
-    if (tb[FRA_FLOW])
-        props->flow = nla_get_u32(tb[FRA_FLOW]);
-
-    if (tb[FRA_TUN_ID])
-        props->tun_id = nla_get_be64(tb[FRA_TUN_ID]);
-
-    if (tb[FRA_L3MDEV]) {
-        if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) {
-            /* support for FRA_L3MDEV was added in 96c63fa7393d0a346acfe5a91e0c7d4c7782641b,
-             * kernel 4.8, 3 October 2017.
-             *
-             * We can only detect support if the attribute is present. A missing attribute
-             * is not conclusive. */
-            _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV, 1);
-        }
-
-        /* actually, kernel only allows this attribute to be missing or
-         * "1". Still, encode it as full uint8.
-         *
-         * Note that FRA_L3MDEV and FRA_TABLE are mutally exclusive. */
-        props->l3mdev = nla_get_u8(tb[FRA_L3MDEV]);
-    }
-
-    if (tb[FRA_PROTOCOL])
-        props->protocol = nla_get_u8(tb[FRA_PROTOCOL]);
-    else
-        nm_assert(props->protocol == RTPROT_UNSPEC);
-
-    if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)) {
-        /* FRA_PROTOCOL was added in kernel 4.17, dated 3 June, 2018.
-         * See commit 1b71af6053af1bd2f849e9fda4f71c1e3f145dcf. */
-        _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL,
-                                         tb[FRA_PROTOCOL] ? 1 : -1);
-    }
-
-    if (tb[FRA_IP_PROTO])
-        props->ip_proto = nla_get_u8(tb[FRA_IP_PROTO]);
-
-    G_STATIC_ASSERT_EXPR(sizeof(NMFibRulePortRange) == 4);
-    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRulePortRange, start) == 0);
-    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRulePortRange, end) == 2);
-
-    nla_memcpy_checked_size(&props->sport_range, tb[FRA_SPORT_RANGE], sizeof(props->sport_range));
-    nla_memcpy_checked_size(&props->dport_range, tb[FRA_DPORT_RANGE], sizeof(props->dport_range));
-
-    if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) {
-        /* support for FRA_IP_PROTO, FRA_SPORT_RANGE, and FRA_DPORT_RANGE was added together
-         * by bfff4862653bb96001ab57c1edd6d03f48e5f035, kernel 4.17, 4 June 2018.
-         *
-         * Unfortunately, a missing attribute does not tell us anything about support.
-         * We can only tell for sure when we have support, but not when we don't have.  */
-        if (tb[FRA_IP_PROTO] || tb[FRA_SPORT_RANGE] || tb[FRA_DPORT_RANGE])
-            _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO, 1);
-    }
-
-    G_STATIC_ASSERT_EXPR(sizeof(NMFibRuleUidRange) == 8);
-    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRuleUidRange, start) == 0);
-    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMFibRuleUidRange, end) == 4);
-
-    if (tb[FRA_UID_RANGE]) {
-        if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) {
-            /* support for FRA_UID_RANGE was added in 622ec2c9d52405973c9f1ca5116eb1c393adfc7d,
-             * kernel 4.10, 19 February 2017.
-             *
-             * We can only detect support if the attribute is present. A missing attribute
-             * is not conclusive. */
-            _nm_platform_kernel_support_init(NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE, 1);
-        }
-
-        nla_memcpy_checked_size(&props->uid_range, tb[FRA_UID_RANGE], sizeof(props->uid_range));
-        props->uid_range_has = TRUE;
-    }
-
-    return g_steal_pointer(&obj);
-}
-
-static guint32
-psched_tick_to_time(NMPlatform *platform, guint32 tick)
-{
-    static gboolean initialized;
-    static double   tick_in_usec = 1;
-
-    if (!initialized) {
-        gs_free char *params       = NULL;
-        double        clock_factor = 1;
-        guint32       clock_res;
-        guint32       t2us;
-        guint32       us2t;
-
-        initialized = TRUE;
-        params = nm_platform_sysctl_get(platform, NMP_SYSCTL_PATHID_ABSOLUTE("/proc/net/psched"));
-        if (!params || sscanf(params, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) {
-            _LOGW("packet scheduler parameters not available");
-        } else {
-            /* See tc_core_init() in iproute2 */
-            if (clock_res == 1000000000)
-                t2us = us2t;
-
-            clock_factor = (double) clock_res / PSCHED_TIME_UNITS_PER_SEC;
-            tick_in_usec = (double) t2us / us2t * clock_factor;
-        }
-    }
-
-    return tick / tick_in_usec;
-}
-
-static NMPObject *
-_new_from_nl_qdisc(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only)
-{
-    static const struct nla_policy policy[] = {
-        [TCA_KIND]    = {.type = NLA_STRING},
-        [TCA_OPTIONS] = {.type = NLA_NESTED},
-    };
-    struct nlattr *     tb[G_N_ELEMENTS(policy)];
-    const struct tcmsg *tcm;
-    nm_auto_nmpobj NMPObject *obj = NULL;
-
-    if (nlmsg_parse_arr(nlh, sizeof(*tcm), tb, policy) < 0)
-        return NULL;
-
-    if (!tb[TCA_KIND])
-        return NULL;
-
-    tcm = nlmsg_data(nlh);
-
-    obj = nmp_object_new(NMP_OBJECT_TYPE_QDISC, NULL);
-
-    obj->qdisc.kind        = g_intern_string(nla_get_string(tb[TCA_KIND]));
-    obj->qdisc.ifindex     = tcm->tcm_ifindex;
-    obj->qdisc.addr_family = tcm->tcm_family;
-    obj->qdisc.handle      = tcm->tcm_handle;
-    obj->qdisc.parent      = tcm->tcm_parent;
-    obj->qdisc.info        = tcm->tcm_info;
-
-    if (nm_streq0(obj->qdisc.kind, "fq_codel")) {
-        obj->qdisc.fq_codel.memory_limit = NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET;
-        obj->qdisc.fq_codel.ce_threshold = NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED;
-    }
-
-    if (tb[TCA_OPTIONS]) {
-        struct nlattr *options_attr;
-        int            remaining;
-
-        if (nm_streq0(obj->qdisc.kind, "sfq")) {
-            struct tc_sfq_qopt_v1 opt;
-
-            if (tb[TCA_OPTIONS]->nla_len >= nla_attr_size(sizeof(opt))) {
-                memcpy(&opt, nla_data(tb[TCA_OPTIONS]), sizeof(opt));
-                obj->qdisc.sfq.quantum        = opt.v0.quantum;
-                obj->qdisc.sfq.perturb_period = opt.v0.perturb_period;
-                obj->qdisc.sfq.limit          = opt.v0.limit;
-                obj->qdisc.sfq.divisor        = opt.v0.divisor;
-                obj->qdisc.sfq.flows          = opt.v0.flows;
-                obj->qdisc.sfq.depth          = opt.depth;
-            }
-        } else if (nm_streq0(obj->qdisc.kind, "tbf")) {
-            static const struct nla_policy tbf_policy[] = {
-                [TCA_TBF_PARMS]  = {.minlen = sizeof(struct tc_tbf_qopt)},
-                [TCA_TBF_RATE64] = {.type = NLA_U64},
-            };
-            struct nlattr *    tbf_tb[G_N_ELEMENTS(tbf_policy)];
-            struct tc_tbf_qopt opt;
-
-            if (nla_parse_nested_arr(tbf_tb, tb[TCA_OPTIONS], tbf_policy) < 0)
-                return NULL;
-            if (!tbf_tb[TCA_TBF_PARMS])
-                return NULL;
-
-            nla_memcpy_checked_size(&opt, tbf_tb[TCA_TBF_PARMS], sizeof(opt));
-            obj->qdisc.tbf.rate = opt.rate.rate;
-            if (tbf_tb[TCA_TBF_RATE64])
-                obj->qdisc.tbf.rate = nla_get_u64(tbf_tb[TCA_TBF_RATE64]);
-            obj->qdisc.tbf.burst =
-                ((double) obj->qdisc.tbf.rate * psched_tick_to_time(platform, opt.buffer))
-                / PSCHED_TIME_UNITS_PER_SEC;
-            obj->qdisc.tbf.limit = opt.limit;
-        } else {
-            nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) {
-                if (nla_len(options_attr) < sizeof(uint32_t))
-                    continue;
-
-                if (nm_streq0(obj->qdisc.kind, "fq_codel")) {
-                    switch (nla_type(options_attr)) {
-                    case TCA_FQ_CODEL_LIMIT:
-                        obj->qdisc.fq_codel.limit = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_FLOWS:
-                        obj->qdisc.fq_codel.flows = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_TARGET:
-                        obj->qdisc.fq_codel.target = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_INTERVAL:
-                        obj->qdisc.fq_codel.interval = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_QUANTUM:
-                        obj->qdisc.fq_codel.quantum = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_CE_THRESHOLD:
-                        obj->qdisc.fq_codel.ce_threshold = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_MEMORY_LIMIT:
-                        obj->qdisc.fq_codel.memory_limit = nla_get_u32(options_attr);
-                        break;
-                    case TCA_FQ_CODEL_ECN:
-                        obj->qdisc.fq_codel.ecn = !!nla_get_u32(options_attr);
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    return g_steal_pointer(&obj);
-}
-
-static NMPObject *
-_new_from_nl_tfilter(struct nlmsghdr *nlh, gboolean id_only)
-{
-    static const struct nla_policy policy[] = {
-        [TCA_KIND] = {.type = NLA_STRING},
-    };
-    struct nlattr *     tb[G_N_ELEMENTS(policy)];
-    NMPObject *         obj = NULL;
-    const struct tcmsg *tcm;
-
-    if (nlmsg_parse_arr(nlh, sizeof(*tcm), tb, policy) < 0)
-        return NULL;
-
-    if (!tb[TCA_KIND])
-        return NULL;
-
-    tcm = nlmsg_data(nlh);
-
-    obj = nmp_object_new(NMP_OBJECT_TYPE_TFILTER, NULL);
-
-    obj->tfilter.kind        = g_intern_string(nla_get_string(tb[TCA_KIND]));
-    obj->tfilter.ifindex     = tcm->tcm_ifindex;
-    obj->tfilter.addr_family = tcm->tcm_family;
-    obj->tfilter.handle      = tcm->tcm_handle;
-    obj->tfilter.parent      = tcm->tcm_parent;
-    obj->tfilter.info        = tcm->tcm_info;
-
-    return obj;
-}
-
-/**
- * nmp_object_new_from_nl:
- * @platform: (allow-none): for creating certain objects, the constructor wants to check
- *   sysfs. For this the platform instance is needed. If missing, the object might not
- *   be correctly detected.
- * @cache: (allow-none): for certain objects, the netlink message doesn't contain all the information.
- *   If a cache is given, the object is completed with information from the cache.
- * @nlh: the netlink message header
- * @id_only: whether only to create an empty object with only the ID fields set.
- *
- * Returns: %NULL or a newly created NMPObject instance.
- **/
-static NMPObject *
-nmp_object_new_from_nl(NMPlatform *    platform,
-                       const NMPCache *cache,
-                       struct nl_msg * msg,
-                       gboolean        id_only)
-{
-    struct nlmsghdr *msghdr;
-
-    if (nlmsg_get_proto(msg) != NETLINK_ROUTE)
-        return NULL;
-
-    msghdr = nlmsg_hdr(msg);
-
-    switch (msghdr->nlmsg_type) {
-    case RTM_NEWLINK:
-    case RTM_DELLINK:
-    case RTM_GETLINK:
-    case RTM_SETLINK:
-        return _new_from_nl_link(platform, cache, msghdr, id_only);
-    case RTM_NEWADDR:
-    case RTM_DELADDR:
-    case RTM_GETADDR:
-        return _new_from_nl_addr(msghdr, id_only);
-    case RTM_NEWROUTE:
-    case RTM_DELROUTE:
-    case RTM_GETROUTE:
-        return _new_from_nl_route(msghdr, id_only);
-    case RTM_NEWRULE:
-    case RTM_DELRULE:
-    case RTM_GETRULE:
-        return _new_from_nl_routing_rule(msghdr, id_only);
-    case RTM_NEWQDISC:
-    case RTM_DELQDISC:
-    case RTM_GETQDISC:
-        return _new_from_nl_qdisc(platform, msghdr, id_only);
-    case RTM_NEWTFILTER:
-    case RTM_DELTFILTER:
-    case RTM_GETTFILTER:
-        return _new_from_nl_tfilter(msghdr, id_only);
-    default:
-        return NULL;
-    }
-}
-
-/*****************************************************************************/
-
-static gboolean
-_nl_msg_new_link_set_afspec(struct nl_msg *msg, int addr_gen_mode, NMUtilsIPv6IfaceId *iid)
-{
-    struct nlattr *af_spec;
-    struct nlattr *af_attr;
-
-    nm_assert(msg);
-
-    if (!(af_spec = nla_nest_start(msg, IFLA_AF_SPEC)))
-        goto nla_put_failure;
-
-    if (addr_gen_mode >= 0 || iid) {
-        if (!(af_attr = nla_nest_start(msg, AF_INET6)))
-            goto nla_put_failure;
-
-        if (addr_gen_mode >= 0)
-            NLA_PUT_U8(msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode);
-
-        if (iid) {
-            struct in6_addr i6_token = {.s6_addr = {
-                                            0,
-                                        }};
-
-            nm_utils_ipv6_addr_set_interface_identifier(&i6_token, *iid);
-            NLA_PUT(msg, IFLA_INET6_TOKEN, sizeof(struct in6_addr), &i6_token);
-        }
-
-        nla_nest_end(msg, af_attr);
-    }
-
-    nla_nest_end(msg, af_spec);
-
-    return TRUE;
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-_nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpointer extra_data)
-{
-    struct nlattr *info;
-    struct nlattr *data = NULL;
-    const char *   kind;
-
-    nm_assert(msg);
-
-    kind = nm_link_type_to_rtnl_type_string(link_type);
-    if (!kind)
-        goto nla_put_failure;
-
-    if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
-        goto nla_put_failure;
-
-    NLA_PUT_STRING(msg, IFLA_INFO_KIND, kind);
-
-    switch (link_type) {
-    case NM_LINK_TYPE_BRIDGE:
-    {
-        const NMPlatformLnkBridge *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_BR_FORWARD_DELAY, props->forward_delay);
-        NLA_PUT_U32(msg, IFLA_BR_HELLO_TIME, props->hello_time);
-        NLA_PUT_U32(msg, IFLA_BR_MAX_AGE, props->max_age);
-        NLA_PUT_U32(msg, IFLA_BR_AGEING_TIME, props->ageing_time);
-        NLA_PUT_U32(msg, IFLA_BR_STP_STATE, !!props->stp_state);
-        NLA_PUT_U16(msg, IFLA_BR_PRIORITY, props->priority);
-        NLA_PUT_U16(msg, IFLA_BR_VLAN_PROTOCOL, htons(props->vlan_protocol));
-        if (props->vlan_stats_enabled)
-            NLA_PUT_U8(msg, IFLA_BR_VLAN_STATS_ENABLED, !!props->vlan_stats_enabled);
-        NLA_PUT_U16(msg, IFLA_BR_GROUP_FWD_MASK, props->group_fwd_mask);
-        NLA_PUT(msg, IFLA_BR_GROUP_ADDR, sizeof(props->group_addr), &props->group_addr);
-        NLA_PUT_U8(msg, IFLA_BR_MCAST_SNOOPING, !!props->mcast_snooping);
-        NLA_PUT_U8(msg, IFLA_BR_MCAST_ROUTER, props->mcast_router);
-        NLA_PUT_U8(msg, IFLA_BR_MCAST_QUERY_USE_IFADDR, !!props->mcast_query_use_ifaddr);
-        NLA_PUT_U8(msg, IFLA_BR_MCAST_QUERIER, !!props->mcast_querier);
-        NLA_PUT_U32(msg, IFLA_BR_MCAST_HASH_MAX, props->mcast_hash_max);
-        NLA_PUT_U32(msg, IFLA_BR_MCAST_LAST_MEMBER_CNT, props->mcast_last_member_count);
-        NLA_PUT_U32(msg, IFLA_BR_MCAST_STARTUP_QUERY_CNT, props->mcast_startup_query_count);
-        NLA_PUT_U64(msg, IFLA_BR_MCAST_LAST_MEMBER_INTVL, props->mcast_last_member_interval);
-        NLA_PUT_U64(msg, IFLA_BR_MCAST_MEMBERSHIP_INTVL, props->mcast_membership_interval);
-        NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERIER_INTVL, props->mcast_querier_interval);
-        NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERY_INTVL, props->mcast_query_interval);
-        NLA_PUT_U64(msg, IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, props->mcast_query_response_interval);
-        NLA_PUT_U64(msg, IFLA_BR_MCAST_STARTUP_QUERY_INTVL, props->mcast_startup_query_interval);
-        break;
-    }
-    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 *        info_peer;
-
-        nm_assert(veth_peer);
-
-        if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
-            goto nla_put_failure;
-        if (!(info_peer = nla_nest_start(msg, 1 /*VETH_INFO_PEER*/)))
-            goto nla_put_failure;
-        if (nlmsg_append_struct(msg, &ifi) < 0)
-            goto nla_put_failure;
-        NLA_PUT_STRING(msg, IFLA_IFNAME, veth_peer);
-        nla_nest_end(msg, info_peer);
-        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);
-
-    return TRUE;
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-_nl_msg_new_link_set_linkinfo_vlan(struct nl_msg *         msg,
-                                   int                     vlan_id,
-                                   guint32                 flags_mask,
-                                   guint32                 flags_set,
-                                   const NMVlanQosMapping *ingress_qos,
-                                   int                     ingress_qos_len,
-                                   const NMVlanQosMapping *egress_qos,
-                                   int                     egress_qos_len)
-{
-    struct nlattr *info;
-    struct nlattr *data;
-    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);
-
-    /* We must not create an empty IFLA_LINKINFO section. Otherwise, kernel
-     * rejects the request as invalid. */
-    if (flags_mask != 0 || vlan_id >= 0)
-        has_any_vlan_properties = TRUE;
-    if (!has_any_vlan_properties && ingress_qos && ingress_qos_len > 0) {
-        for (i = 0; i < ingress_qos_len; i++) {
-            if (VLAN_XGRESS_PRIO_VALID(ingress_qos[i].from)) {
-                has_any_vlan_properties = TRUE;
-                break;
-            }
-        }
-    }
-    if (!has_any_vlan_properties && egress_qos && egress_qos_len > 0) {
-        for (i = 0; i < egress_qos_len; i++) {
-            if (VLAN_XGRESS_PRIO_VALID(egress_qos[i].to)) {
-                has_any_vlan_properties = TRUE;
-                break;
-            }
-        }
-    }
-    if (!has_any_vlan_properties)
-        return TRUE;
-
-    if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
-        goto nla_put_failure;
-
-    NLA_PUT_STRING(msg, IFLA_INFO_KIND, "vlan");
-
-    if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
-        goto nla_put_failure;
-
-    if (vlan_id >= 0)
-        NLA_PUT_U16(msg, IFLA_VLAN_ID, vlan_id);
-
-    if (flags_mask != 0) {
-        struct ifla_vlan_flags flags = {
-            .flags = flags_mask & flags_set,
-            .mask  = flags_mask,
-        };
-
-        NLA_PUT(msg, IFLA_VLAN_FLAGS, sizeof(flags), &flags);
-    }
-
-    if (ingress_qos && ingress_qos_len > 0) {
-        struct nlattr *qos = NULL;
-
-        for (i = 0; i < ingress_qos_len; i++) {
-            /* Silently ignore invalid mappings. Kernel would truncate
-             * them and modify the wrong mapping. */
-            if (VLAN_XGRESS_PRIO_VALID(ingress_qos[i].from)) {
-                if (!qos) {
-                    if (!(qos = nla_nest_start(msg, IFLA_VLAN_INGRESS_QOS)))
-                        goto nla_put_failure;
-                }
-                NLA_PUT(msg, i, sizeof(ingress_qos[i]), &ingress_qos[i]);
-            }
-        }
-
-        if (qos)
-            nla_nest_end(msg, qos);
-    }
-
-    if (egress_qos && egress_qos_len > 0) {
-        struct nlattr *qos = NULL;
-
-        for (i = 0; i < egress_qos_len; i++) {
-            if (VLAN_XGRESS_PRIO_VALID(egress_qos[i].to)) {
-                if (!qos) {
-                    if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
-                        goto nla_put_failure;
-                }
-                NLA_PUT(msg, i, sizeof(egress_qos[i]), &egress_qos[i]);
-            }
-        }
-
-        if (qos)
-            nla_nest_end(msg, qos);
-    }
-
-    nla_nest_end(msg, data);
-    nla_nest_end(msg, info);
-
-    return TRUE;
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static struct nl_msg *
-_nl_msg_new_link_full(int         nlmsg_type,
-                      int         nlmsg_flags,
-                      int         ifindex,
-                      const char *ifname,
-                      guint8      family,
-                      unsigned    flags_mask,
-                      unsigned    flags_set)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    const struct ifinfomsg       ifi = {
-        .ifi_family = family,
-        .ifi_change = flags_mask,
-        .ifi_flags  = flags_set,
-        .ifi_index  = ifindex,
-    };
-
-    nm_assert(NM_IN_SET(nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK, RTM_SETLINK));
-
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
-
-    if (nlmsg_append_struct(msg, &ifi) < 0)
-        goto nla_put_failure;
-
-    if (ifname)
-        NLA_PUT_STRING(msg, IFLA_IFNAME, ifname);
-
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static struct nl_msg *
-_nl_msg_new_link(int nlmsg_type, int nlmsg_flags, int ifindex, const char *ifname)
-{
-    return _nl_msg_new_link_full(nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0);
-}
-
-/* Copied and modified from libnl3's build_addr_msg(). */
-static struct nl_msg *
-_nl_msg_new_address(int           nlmsg_type,
-                    int           nlmsg_flags,
-                    int           family,
-                    int           ifindex,
-                    gconstpointer address,
-                    guint8        plen,
-                    gconstpointer peer_address,
-                    guint32       flags,
-                    int           scope,
-                    guint32       lifetime,
-                    guint32       preferred,
-                    in_addr_t     ip4_broadcast_address,
-                    const char *  label)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    struct ifaddrmsg             am  = {
-        .ifa_family    = family,
-        .ifa_index     = ifindex,
-        .ifa_prefixlen = plen,
-        .ifa_flags     = flags,
-    };
-    gsize addr_len;
-
-    nm_assert(NM_IN_SET(family, AF_INET, AF_INET6));
-    nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWADDR, RTM_DELADDR));
-
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
-
-    if (scope == -1) {
-        /* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */
-        if (family == AF_INET && address && *((char *) address) == 127)
-            scope = RT_SCOPE_HOST;
-        else
-            scope = RT_SCOPE_UNIVERSE;
-    }
-    am.ifa_scope = scope,
-
-    addr_len = family == AF_INET ? sizeof(in_addr_t) : sizeof(struct in6_addr);
-
-    if (nlmsg_append_struct(msg, &am) < 0)
-        goto nla_put_failure;
-
-    if (address)
-        NLA_PUT(msg, IFA_LOCAL, addr_len, address);
-
-    if (peer_address)
-        NLA_PUT(msg, IFA_ADDRESS, addr_len, peer_address);
-    else if (address)
-        NLA_PUT(msg, IFA_ADDRESS, addr_len, address);
-
-    if (label && label[0])
-        NLA_PUT_STRING(msg, IFA_LABEL, label);
-
-    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) {
-        struct ifa_cacheinfo ca = {
-            .ifa_valid    = lifetime,
-            .ifa_prefered = preferred,
-        };
-
-        NLA_PUT(msg, IFA_CACHEINFO, sizeof(ca), &ca);
-    }
-
-    if (flags & ~((guint32) 0xFF)) {
-        /* only set the IFA_FLAGS attribute, if they actually contain additional
-         * flags that are not already set to am.ifa_flags.
-         *
-         * Older kernels refuse RTM_NEWADDR and RTM_NEWROUTE messages with EINVAL
-         * if they contain unknown netlink attributes. See net/core/rtnetlink.c, which
-         * was fixed by kernel commit 661d2967b3f1b34eeaa7e212e7b9bbe8ee072b59. */
-        NLA_PUT_U32(msg, IFA_FLAGS, flags);
-    }
-
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static guint32
-ip_route_get_lock_flag(const NMPlatformIPRoute *route)
-{
-    return (((guint32) route->lock_window) << RTAX_WINDOW)
-           | (((guint32) route->lock_cwnd) << RTAX_CWND)
-           | (((guint32) route->lock_initcwnd) << RTAX_INITCWND)
-           | (((guint32) route->lock_initrwnd) << RTAX_INITRWND)
-           | (((guint32) route->lock_mtu) << RTAX_MTU);
-}
-
-/* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */
-static struct nl_msg *
-_nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj)
-{
-    nm_auto_nlmsg struct nl_msg *msg   = NULL;
-    const NMPClass *             klass = NMP_OBJECT_GET_CLASS(obj);
-    gboolean                     is_v4 = klass->addr_family == AF_INET;
-    const guint32                lock  = ip_route_get_lock_flag(NMP_OBJECT_CAST_IP_ROUTE(obj));
-    const guint32                table =
-        nm_platform_route_table_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->table_coerced, TRUE);
-    const struct rtmsg rtmsg = {
-        .rtm_family   = klass->addr_family,
-        .rtm_tos      = is_v4 ? obj->ip4_route.tos : 0,
-        .rtm_table    = table <= 0xFF ? table : RT_TABLE_UNSPEC,
-        .rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot(obj->ip_route.rt_source),
-        .rtm_scope =
-            is_v4 ? nm_platform_route_scope_inv(obj->ip4_route.scope_inv) : RT_SCOPE_NOWHERE,
-        .rtm_type    = nm_platform_route_type_uncoerce(NMP_OBJECT_CAST_IP_ROUTE(obj)->type_coerced),
-        .rtm_flags   = obj->ip_route.r_rtm_flags & ((unsigned) (RTNH_F_ONLINK)),
-        .rtm_dst_len = obj->ip_route.plen,
-        .rtm_src_len = is_v4 ? 0 : NMP_OBJECT_CAST_IP6_ROUTE(obj)->src_plen,
-    };
-
-    gsize addr_len;
-
-    nm_assert(
-        NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
-    nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
-
-    msg = nlmsg_alloc_simple(nlmsg_type, (int) nlmsgflags);
-
-    if (nlmsg_append_struct(msg, &rtmsg) < 0)
-        goto nla_put_failure;
-
-    addr_len = is_v4 ? sizeof(in_addr_t) : sizeof(struct in6_addr);
-
-    NLA_PUT(msg,
-            RTA_DST,
-            addr_len,
-            is_v4 ? (gconstpointer) &obj->ip4_route.network
-                  : (gconstpointer) &obj->ip6_route.network);
-
-    if (!is_v4) {
-        if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->src))
-            NLA_PUT(msg, RTA_SRC, addr_len, &obj->ip6_route.src);
-    }
-
-    NLA_PUT_U32(msg,
-                RTA_PRIORITY,
-                is_v4 ? nm_platform_ip4_route_get_effective_metric(&obj->ip4_route)
-                      : nm_platform_ip6_route_get_effective_metric(&obj->ip6_route));
-
-    if (table > 0xFF)
-        NLA_PUT_U32(msg, RTA_TABLE, table);
-
-    if (is_v4) {
-        if (NMP_OBJECT_CAST_IP4_ROUTE(obj)->pref_src)
-            NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip4_route.pref_src);
-    } else {
-        if (!IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(obj)->pref_src))
-            NLA_PUT(msg, RTA_PREFSRC, addr_len, &obj->ip6_route.pref_src);
-    }
-
-    if (obj->ip_route.mss || obj->ip_route.window || obj->ip_route.cwnd || obj->ip_route.initcwnd
-        || obj->ip_route.initrwnd || obj->ip_route.mtu || lock) {
-        struct nlattr *metrics;
-
-        metrics = nla_nest_start(msg, RTA_METRICS);
-        if (!metrics)
-            goto nla_put_failure;
-
-        if (obj->ip_route.mss)
-            NLA_PUT_U32(msg, RTAX_ADVMSS, obj->ip_route.mss);
-        if (obj->ip_route.window)
-            NLA_PUT_U32(msg, RTAX_WINDOW, obj->ip_route.window);
-        if (obj->ip_route.cwnd)
-            NLA_PUT_U32(msg, RTAX_CWND, obj->ip_route.cwnd);
-        if (obj->ip_route.initcwnd)
-            NLA_PUT_U32(msg, RTAX_INITCWND, obj->ip_route.initcwnd);
-        if (obj->ip_route.initrwnd)
-            NLA_PUT_U32(msg, RTAX_INITRWND, obj->ip_route.initrwnd);
-        if (obj->ip_route.mtu)
-            NLA_PUT_U32(msg, RTAX_MTU, obj->ip_route.mtu);
-        if (lock)
-            NLA_PUT_U32(msg, RTAX_LOCK, lock);
-
-        nla_nest_end(msg, metrics);
-    }
-
-    /* We currently don't have need for multi-hop routes... */
-    if (is_v4) {
-        NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway);
-    } else {
-        if (!IN6_IS_ADDR_UNSPECIFIED(&obj->ip6_route.gateway))
-            NLA_PUT(msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway);
-    }
-    NLA_PUT_U32(msg, RTA_OIF, obj->ip_route.ifindex);
-
-    if (!is_v4 && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM)
-        NLA_PUT_U8(msg, RTA_PREF, obj->ip6_route.rt_pref);
-
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static struct nl_msg *
-_nl_msg_new_routing_rule(int nlmsg_type, int nlmsg_flags, const NMPlatformRoutingRule *routing_rule)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    const guint8 addr_size           = nm_utils_addr_family_to_size(routing_rule->addr_family);
-    guint32      table;
-
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
-
-    table = routing_rule->table;
-
-    if (NM_IN_SET(routing_rule->addr_family, AF_INET, AF_INET6)
-        && routing_rule->action == FR_ACT_TO_TBL && routing_rule->l3mdev == 0
-        && table == RT_TABLE_UNSPEC) {
-        /* for IPv6, this setting is invalid and rejected by kernel. That's fine.
-         *
-         * for IPv4, kernel will automatically assign an unused table. That's not
-         * fine, because we don't know what we will get.
-         *
-         * The caller must not allow that to happen. */
-        nm_assert_not_reached();
-    }
-
-    {
-        const struct fib_rule_hdr frh = {
-            .family  = routing_rule->addr_family,
-            .src_len = routing_rule->src_len,
-            .dst_len = routing_rule->dst_len,
-            .tos     = routing_rule->tos,
-            .table   = table < 0x100u ? (guint8) table : (guint8) RT_TABLE_UNSPEC,
-            .action  = routing_rule->action,
-
-            /* we only allow setting the "not" flag. */
-            .flags = routing_rule->flags & ((guint32) FIB_RULE_INVERT),
-        };
-
-        if (nlmsg_append_struct(msg, &frh) < 0)
-            goto nla_put_failure;
-    }
-
-    if (table > G_MAXINT8)
-        NLA_PUT_U32(msg, FRA_TABLE, table);
-
-    if (routing_rule->suppress_prefixlen_inverse != 0)
-        NLA_PUT_U32(msg, FRA_SUPPRESS_PREFIXLEN, ~routing_rule->suppress_prefixlen_inverse);
-
-    if (routing_rule->suppress_ifgroup_inverse != 0)
-        NLA_PUT_U32(msg, FRA_SUPPRESS_IFGROUP, ~routing_rule->suppress_ifgroup_inverse);
-
-    if (routing_rule->iifname[0] != '\0')
-        NLA_PUT_STRING(msg, FRA_IIFNAME, routing_rule->iifname);
-
-    if (routing_rule->oifname[0] != '\0')
-        NLA_PUT_STRING(msg, FRA_OIFNAME, routing_rule->oifname);
-
-    /* we always set the priority and don't support letting kernel pick one. */
-    NLA_PUT_U32(msg, FRA_PRIORITY, routing_rule->priority);
-
-    if (routing_rule->fwmark != 0 || routing_rule->fwmask != 0) {
-        NLA_PUT_U32(msg, FRA_FWMARK, routing_rule->fwmark);
-        NLA_PUT_U32(msg, FRA_FWMASK, routing_rule->fwmask);
-    }
-
-    if (routing_rule->src_len > 0)
-        NLA_PUT(msg, FRA_SRC, addr_size, &routing_rule->src);
-
-    if (routing_rule->dst_len > 0)
-        NLA_PUT(msg, FRA_DST, addr_size, &routing_rule->dst);
-
-    if (routing_rule->flow != 0) {
-        /* only relevant for IPv4. */
-        NLA_PUT_U32(msg, FRA_FLOW, routing_rule->flow);
-    }
-
-    if (routing_rule->tun_id != 0)
-        NLA_PUT_U64(msg, FRA_TUN_ID, htobe64(routing_rule->tun_id));
-
-    if (routing_rule->l3mdev)
-        NLA_PUT_U8(msg, FRA_L3MDEV, routing_rule->l3mdev);
-
-    if (routing_rule->protocol != RTPROT_UNSPEC)
-        NLA_PUT_U8(msg, FRA_PROTOCOL, routing_rule->protocol);
-
-    if (routing_rule->ip_proto != 0)
-        NLA_PUT_U8(msg, FRA_IP_PROTO, routing_rule->ip_proto);
-
-    if (routing_rule->sport_range.start || routing_rule->sport_range.end)
-        NLA_PUT(msg,
-                FRA_SPORT_RANGE,
-                sizeof(routing_rule->sport_range),
-                &routing_rule->sport_range);
-
-    if (routing_rule->dport_range.start || routing_rule->dport_range.end)
-        NLA_PUT(msg,
-                FRA_DPORT_RANGE,
-                sizeof(routing_rule->dport_range),
-                &routing_rule->dport_range);
-
-    if (routing_rule->uid_range_has)
-        NLA_PUT(msg, FRA_UID_RANGE, sizeof(routing_rule->uid_range), &routing_rule->uid_range);
-
-    switch (routing_rule->action) {
-    case FR_ACT_GOTO:
-        NLA_PUT_U32(msg, FRA_GOTO, routing_rule->goto_target);
-        break;
-    }
-
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static struct nl_msg *
-_nl_msg_new_qdisc(int nlmsg_type, int nlmsg_flags, const NMPlatformQdisc *qdisc)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    struct nlattr *              tc_options;
-    const struct tcmsg           tcm = {
-        .tcm_family  = qdisc->addr_family,
-        .tcm_ifindex = qdisc->ifindex,
-        .tcm_handle  = qdisc->handle,
-        .tcm_parent  = qdisc->parent,
-        .tcm_info    = qdisc->info,
-    };
-
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
-
-    if (nlmsg_append_struct(msg, &tcm) < 0)
-        goto nla_put_failure;
-
-    NLA_PUT_STRING(msg, TCA_KIND, qdisc->kind);
-
-    if (nm_streq(qdisc->kind, "sfq")) {
-        struct tc_sfq_qopt_v1 opt = {};
-
-        opt.v0.quantum        = qdisc->sfq.quantum;
-        opt.v0.limit          = qdisc->sfq.limit;
-        opt.v0.perturb_period = qdisc->sfq.perturb_period;
-        opt.v0.flows          = qdisc->sfq.flows;
-        opt.v0.divisor        = qdisc->sfq.divisor;
-        opt.depth             = qdisc->sfq.depth;
-
-        NLA_PUT(msg, TCA_OPTIONS, sizeof(opt), &opt);
-    } else if (nm_streq(qdisc->kind, "tbf")) {
-        struct tc_tbf_qopt opt = {};
-
-        if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS)))
-            goto nla_put_failure;
-
-        opt.rate.rate = (qdisc->tbf.rate >= (1ULL << 32)) ? ~0U : (guint32) qdisc->tbf.rate;
-        if (qdisc->tbf.limit)
-            opt.limit = qdisc->tbf.limit;
-        else if (qdisc->tbf.latency) {
-            opt.limit = qdisc->tbf.rate * (double) qdisc->tbf.latency / PSCHED_TIME_UNITS_PER_SEC
-                        + qdisc->tbf.burst;
-        }
-
-        NLA_PUT(msg, TCA_TBF_PARMS, sizeof(opt), &opt);
-        if (qdisc->tbf.rate >= (1ULL << 32))
-            NLA_PUT_U64(msg, TCA_TBF_RATE64, qdisc->tbf.rate);
-        NLA_PUT_U32(msg, TCA_TBF_BURST, qdisc->tbf.burst);
-
-        nla_nest_end(msg, tc_options);
-    } else if (nm_streq(qdisc->kind, "prio")) {
-        struct tc_prio_qopt opt = {3, {1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}};
-
-        NLA_PUT(msg, TCA_OPTIONS, sizeof(opt), &opt);
-    } else {
-        if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS)))
-            goto nla_put_failure;
-
-        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)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows);
-            if (qdisc->fq_codel.target)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target);
-            if (qdisc->fq_codel.interval)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval);
-            if (qdisc->fq_codel.quantum)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum);
-            if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold);
-            if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit);
-            if (qdisc->fq_codel.ecn)
-                NLA_PUT_U32(msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn);
-        }
-
-        nla_nest_end(msg, tc_options);
-    }
-
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static struct nl_msg *
-_nl_msg_new_tfilter(int nlmsg_type, int nlmsg_flags, const NMPlatformTfilter *tfilter)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    struct nlattr *              tc_options;
-    struct nlattr *              act_tab;
-    const struct tcmsg           tcm = {
-        .tcm_family  = tfilter->addr_family,
-        .tcm_ifindex = tfilter->ifindex,
-        .tcm_handle  = tfilter->handle,
-        .tcm_parent  = tfilter->parent,
-        .tcm_info    = tfilter->info,
-    };
-
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
-
-    if (nlmsg_append_struct(msg, &tcm) < 0)
-        goto nla_put_failure;
-
-    NLA_PUT_STRING(msg, TCA_KIND, tfilter->kind);
-
-    if (!(tc_options = nla_nest_start(msg, TCA_OPTIONS)))
-        goto nla_put_failure;
-
-    if (!(act_tab = nla_nest_start(msg, TCA_OPTIONS)))  // 3 TCA_ACT_KIND TCA_ACT_KIND
-        goto nla_put_failure;
-
-    if (tfilter->action.kind) {
-        const NMPlatformAction *action = &tfilter->action;
-        struct nlattr *         prio;
-        struct nlattr *         act_options;
-
-        if (!(prio = nla_nest_start(msg, 1 /* priority */)))
-            goto nla_put_failure;
-
-        NLA_PUT_STRING(msg, TCA_ACT_KIND, action->kind);
-
-        if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
-            const NMPlatformActionSimple *simple = &action->simple;
-            struct tc_defact              sel    = {
-                0,
-            };
-
-            if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS)))
-                goto nla_put_failure;
-
-            NLA_PUT(msg, TCA_DEF_PARMS, sizeof(sel), &sel);
-            NLA_PUT(msg, TCA_DEF_DATA, sizeof(simple->sdata), simple->sdata);
-
-            nla_nest_end(msg, act_options);
-
-        } else if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
-            const NMPlatformActionMirred *mirred = &action->mirred;
-            struct tc_mirred              sel    = {
-                0,
-            };
-
-            if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS)))
-                goto nla_put_failure;
-
-            if (mirred->egress && mirred->redirect)
-                sel.eaction = TCA_EGRESS_REDIR;
-            else if (mirred->egress && mirred->mirror)
-                sel.eaction = TCA_EGRESS_MIRROR;
-            else if (mirred->ingress && mirred->redirect)
-                sel.eaction = TCA_INGRESS_REDIR;
-            else if (mirred->ingress && mirred->mirror)
-                sel.eaction = TCA_INGRESS_MIRROR;
-            sel.ifindex = mirred->ifindex;
-
-            NLA_PUT(msg, TCA_MIRRED_PARMS, sizeof(sel), &sel);
-
-            nla_nest_end(msg, act_options);
-        }
-
-        nla_nest_end(msg, prio);
-    }
-
-    nla_nest_end(msg, tc_options);
-
-    nla_nest_end(msg, act_tab);
-
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-/*****************************************************************************/
-
-static struct nl_sock *
-_genl_sock(NMLinuxPlatform *platform)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-
-    return priv->genl;
-}
-
-#define ASSERT_SYSCTL_ARGS(pathid, dirfd, path)                                                 \
-    G_STMT_START                                                                                \
-    {                                                                                           \
-        const char *const _pathid = (pathid);                                                   \
-        const int         _dirfd  = (dirfd);                                                    \
-        const char *const _path   = (path);                                                     \
-                                                                                                \
-        nm_assert(_path &&_path[0]);                                                            \
-        g_assert(!strstr(_path, "/../"));                                                       \
-        if (_dirfd < 0) {                                                                       \
-            nm_assert(!_pathid);                                                                \
-            nm_assert(_path[0] == '/');                                                         \
-            nm_assert(g_str_has_prefix(_path, "/proc/sys/") || g_str_has_prefix(_path, "/sys/") \
-                      || g_str_has_prefix(_path, "/proc/net"));                                 \
-        } else {                                                                                \
-            nm_assert(_pathid &&_pathid[0] && _pathid[0] != '/');                               \
-            nm_assert(_path[0] != '/');                                                         \
-        }                                                                                       \
-    }                                                                                           \
-    G_STMT_END
-
-/*****************************************************************************/
-
-/* core sysctl-set functions can be called from a non-main thread.
- * Hence, we require locking from nm-logging. Indicate that by
- * setting NM_THREAD_SAFE_ON_MAIN_THREAD to zero. */
-#undef NM_THREAD_SAFE_ON_MAIN_THREAD
-#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
-
-static void
-_log_dbg_sysctl_set_impl(NMPlatform *platform,
-                         const char *pathid,
-                         int         dirfd,
-                         const char *path,
-                         const char *value)
-{
-    GError *      error         = NULL;
-    gs_free char *contents      = NULL;
-    gs_free char *value_escaped = g_strescape(value, NULL);
-
-    if (!nm_utils_file_get_contents(dirfd,
-                                    path,
-                                    1 * 1024 * 1024,
-                                    NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
-                                    &contents,
-                                    NULL,
-                                    NULL,
-                                    &error)) {
-        _LOGD("sysctl: setting '%s' to '%s' (current value cannot be read: %s)",
-              pathid ?: path,
-              value_escaped,
-              error->message);
-        g_clear_error(&error);
-        return;
-    }
-
-    g_strstrip(contents);
-    if (nm_streq(contents, value))
-        _LOGD("sysctl: setting '%s' to '%s' (current value is identical)",
-              pathid ?: path,
-              value_escaped);
-    else {
-        gs_free char *contents_escaped = g_strescape(contents, NULL);
-
-        _LOGD("sysctl: setting '%s' to '%s' (current value is '%s')",
-              pathid ?: path,
-              value_escaped,
-              contents_escaped);
-    }
-}
-
-#define _log_dbg_sysctl_set(platform, pathid, dirfd, path, value)           \
-    G_STMT_START                                                            \
-    {                                                                       \
-        if (_LOGD_ENABLED()) {                                              \
-            _log_dbg_sysctl_set_impl(platform, pathid, dirfd, path, value); \
-        }                                                                   \
-    }                                                                       \
-    G_STMT_END
-
-static gboolean
-sysctl_set_internal(NMPlatform *platform,
-                    const char *pathid,
-                    int         dirfd,
-                    const char *path,
-                    const char *value)
-{
-    int           fd, tries;
-    gssize        nwrote;
-    gssize        len;
-    char *        actual;
-    gs_free char *actual_free = NULL;
-    int           errsv;
-
-    if (dirfd < 0) {
-        pathid = path;
-
-        fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
-        if (fd == -1) {
-            errsv = errno;
-            if (errsv == ENOENT) {
-                _LOGD("sysctl: failed to open '%s': (%d) %s",
-                      pathid,
-                      errsv,
-                      nm_strerror_native(errsv));
-            } else {
-                _LOGE("sysctl: failed to open '%s': (%d) %s",
-                      pathid,
-                      errsv,
-                      nm_strerror_native(errsv));
-            }
-            errno = errsv;
-            return FALSE;
-        }
-    } else {
-        fd = openat(dirfd, path, O_WRONLY | O_TRUNC | O_CLOEXEC);
-        if (fd == -1) {
-            errsv = errno;
-            if (errsv == ENOENT) {
-                _LOGD("sysctl: failed to openat '%s': (%d) %s",
-                      pathid,
-                      errsv,
-                      nm_strerror_native(errsv));
-            } else {
-                _LOGE("sysctl: failed to openat '%s': (%d) %s",
-                      pathid,
-                      errsv,
-                      nm_strerror_native(errsv));
-            }
-            errno = errsv;
-            return FALSE;
-        }
-    }
-
-    _log_dbg_sysctl_set(platform, pathid, dirfd, path, value);
-
-    /* Most sysfs and sysctl options don't care about a trailing LF, while some
-     * (like infiniband) do.  So always add the LF.  Also, neither sysfs nor
-     * sysctl support partial writes so the LF must be added to the string we're
-     * about to write.
-     */
-    len = strlen(value) + 1;
-    nm_assert(len > 0);
-    if (len > 512)
-        actual = actual_free = g_malloc(len + 1);
-    else
-        actual = g_alloca(len + 1);
-    memcpy(actual, value, len - 1);
-    actual[len - 1] = '\n';
-    actual[len]     = '\0';
-
-    /* Try to write the entire value three times if a partial write occurs */
-    errsv = 0;
-    for (tries = 0, nwrote = 0; tries < 3 && nwrote < len - 1; tries++) {
-        nwrote = write(fd, actual, len);
-        if (nwrote == -1) {
-            errsv = errno;
-            if (errsv == EINTR) {
-                _LOGD("sysctl: interrupted, will try again");
-                continue;
-            }
-            break;
-        }
-    }
-    if (nwrote == -1) {
-        NMLogLevel level = LOGL_ERR;
-
-        if (errsv == EEXIST) {
-            level = LOGL_DEBUG;
-        } else if (errsv == EINVAL
-                   && nm_utils_sysctl_ip_conf_is_path(AF_INET6, path, NULL, "mtu")) {
-            /* setting the MTU can fail under regular conditions. Suppress
-             * logging a warning. */
-            level = LOGL_DEBUG;
-        }
-
-        _NMLOG(level,
-               "sysctl: failed to set '%s' to '%s': (%d) %s",
-               path,
-               value,
-               errsv,
-               nm_strerror_native(errsv));
-    } else if (nwrote < len - 1) {
-        _LOGE("sysctl: failed to set '%s' to '%s' after three attempts", path, value);
-    }
-
-    if (nwrote < len - 1) {
-        if (nm_close(fd) != 0) {
-            if (errsv != 0)
-                errno = errsv;
-        } else if (errsv != 0)
-            errno = errsv;
-        else
-            errno = EIO;
-        return FALSE;
-    }
-    if (nm_close(fd) != 0) {
-        /* errno is already properly set. */
-        return FALSE;
-    }
-
-    /* success. errno is undefined (no need to set). */
-    return TRUE;
-}
-
-#undef NM_THREAD_SAFE_ON_MAIN_THREAD
-#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
-
-/*****************************************************************************/
-
-static gboolean
-sysctl_set(NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-
-    g_return_val_if_fail(path, FALSE);
-    g_return_val_if_fail(value, FALSE);
-
-    ASSERT_SYSCTL_ARGS(pathid, dirfd, path);
-
-    if (dirfd < 0 && !nm_platform_netns_push(platform, &netns)) {
-        errno = ENETDOWN;
-        return FALSE;
-    }
-
-    return sysctl_set_internal(platform, pathid, dirfd, path, value);
-}
-
-typedef struct {
-    NMPlatform *            platform;
-    char *                  pathid;
-    int                     dirfd;
-    char *                  path;
-    char **                 values;
-    GCancellable *          cancellable;
-    NMPlatformAsyncCallback callback;
-    gpointer                callback_data;
-} SysctlAsyncInfo;
-
-static void
-sysctl_async_info_free(SysctlAsyncInfo *info)
-{
-    g_object_unref(info->platform);
-    g_free(info->pathid);
-    if (info->dirfd >= 0)
-        nm_close(info->dirfd);
-    g_free(info->path);
-    g_strfreev(info->values);
-    g_object_unref(info->cancellable);
-    g_slice_free(SysctlAsyncInfo, info);
-}
-
-static void
-sysctl_async_cb(GObject *object, GAsyncResult *res, gpointer user_data)
-{
-    NMPlatform *     platform;
-    GTask *          task = G_TASK(res);
-    SysctlAsyncInfo *info;
-    gs_free_error GError *error      = NULL;
-    gs_free char *        values_str = NULL;
-
-    info = g_task_get_task_data(task);
-
-    if (g_task_propagate_boolean(task, &error)) {
-        platform = info->platform;
-        _LOGD("sysctl: successfully set-async '%s' to values '%s'",
-              info->pathid ?: info->path,
-              (values_str = g_strjoinv(", ", info->values)));
-    }
-
-    if (info->callback)
-        info->callback(error, info->callback_data);
-}
-
-static void
-sysctl_async_thread_fn(GTask *       task,
-                       gpointer      source_object,
-                       gpointer      task_data,
-                       GCancellable *cancellable)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    SysctlAsyncInfo *           info  = task_data;
-    GError *                    error = NULL;
-    char **                     value;
-
-    if (g_task_return_error_if_cancelled(task))
-        return;
-
-    if (info->dirfd < 0 && !nm_platform_netns_push(info->platform, &netns)) {
-        g_set_error_literal(&error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            "sysctl: failed changing namespace");
-        g_task_return_error(task, error);
-        return;
-    }
-
-    for (value = info->values; *value; value++) {
-        if (!sysctl_set_internal(info->platform, info->pathid, info->dirfd, info->path, *value)) {
-            g_set_error(&error,
-                        NM_UTILS_ERROR,
-                        NM_UTILS_ERROR_UNKNOWN,
-                        "sysctl: failed setting '%s' to value '%s': %s",
-                        info->pathid ?: info->path,
-                        *value,
-                        nm_strerror_native(errno));
-            g_task_return_error(task, error);
-            return;
-        }
-        if (g_task_return_error_if_cancelled(task))
-            return;
-    }
-    g_task_return_boolean(task, TRUE);
-}
-
-static void
-sysctl_set_async_return_idle(gpointer user_data, GCancellable *cancellable)
-{
-    gs_unref_object NMPlatform *platform  = NULL;
-    gs_free_error GError *cancelled_error = NULL;
-    gs_free_error GError *  error         = NULL;
-    NMPlatformAsyncCallback callback;
-    gpointer                callback_data;
-
-    nm_utils_user_data_unpack(user_data, &platform, &callback, &callback_data, &error);
-    g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error);
-    callback(cancelled_error ?: error, callback_data);
-}
-
-static void
-sysctl_set_async(NMPlatform *            platform,
-                 const char *            pathid,
-                 int                     dirfd,
-                 const char *            path,
-                 const char *const *     values,
-                 NMPlatformAsyncCallback callback,
-                 gpointer                data,
-                 GCancellable *          cancellable)
-{
-    SysctlAsyncInfo *info;
-    GTask *          task;
-    int              dirfd_dup, errsv;
-    gpointer         packed;
-    GError *         error = NULL;
-
-    g_return_if_fail(platform);
-    g_return_if_fail(path);
-    g_return_if_fail(values && values[0]);
-    g_return_if_fail(cancellable);
-    g_return_if_fail(!data || callback);
-
-    ASSERT_SYSCTL_ARGS(pathid, dirfd, path);
-
-    if (dirfd >= 0) {
-        dirfd_dup = fcntl(dirfd, F_DUPFD_CLOEXEC, 0);
-        if (dirfd_dup < 0) {
-            if (!callback)
-                return;
-            errsv = errno;
-            g_set_error(&error,
-                        NM_UTILS_ERROR,
-                        NM_UTILS_ERROR_UNKNOWN,
-                        "sysctl: failure duplicating directory fd: %s",
-                        nm_strerror_native(errsv));
-            packed = nm_utils_user_data_pack(g_object_ref(platform), callback, data, error);
-            nm_utils_invoke_on_idle(cancellable, sysctl_set_async_return_idle, packed);
-            return;
-        }
-    } else
-        dirfd_dup = -1;
-
-    info                = g_slice_new0(SysctlAsyncInfo);
-    info->platform      = g_object_ref(platform);
-    info->pathid        = g_strdup(pathid);
-    info->dirfd         = dirfd_dup;
-    info->path          = g_strdup(path);
-    info->values        = g_strdupv((char **) values);
-    info->callback      = callback;
-    info->callback_data = data;
-    info->cancellable   = g_object_ref(cancellable);
-
-    task = g_task_new(platform, cancellable, sysctl_async_cb, NULL);
-    g_task_set_task_data(task, info, (GDestroyNotify) sysctl_async_info_free);
-    g_task_set_return_on_cancel(task, FALSE);
-    g_task_run_in_thread(task, sysctl_async_thread_fn);
-    g_object_unref(task);
-}
-
-static GSList *sysctl_clear_cache_list;
-
-void
-_nm_logging_clear_platform_logging_cache(void)
-{
-    while (sysctl_clear_cache_list) {
-        NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(sysctl_clear_cache_list->data);
-
-        sysctl_clear_cache_list =
-            g_slist_delete_link(sysctl_clear_cache_list, sysctl_clear_cache_list);
-
-        g_hash_table_destroy(priv->sysctl_get_prev_values);
-        priv->sysctl_get_prev_values = NULL;
-    }
-}
-
-typedef struct {
-    const char *path;
-    CList       lst;
-    char *      value;
-    char        path_data[];
-} SysctlCacheEntry;
-
-static void
-sysctl_cache_entry_free(SysctlCacheEntry *entry)
-{
-    c_list_unlink_stale(&entry->lst);
-    g_free(entry->value);
-    g_free(entry);
-}
-
-static void
-_log_dbg_sysctl_get_impl(NMPlatform *platform, const char *pathid, const char *contents)
-{
-    NMLinuxPlatformPrivate *priv  = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    SysctlCacheEntry *      entry = NULL;
-
-    if (!priv->sysctl_get_prev_values) {
-        sysctl_clear_cache_list = g_slist_prepend(sysctl_clear_cache_list, platform);
-        c_list_init(&priv->sysctl_list);
-        priv->sysctl_get_prev_values =
-            g_hash_table_new_full(nm_pstr_hash,
-                                  nm_pstr_equal,
-                                  (GDestroyNotify) sysctl_cache_entry_free,
-                                  NULL);
-    } else
-        entry = g_hash_table_lookup(priv->sysctl_get_prev_values, &pathid);
-
-    if (entry) {
-        if (!nm_streq(entry->value, contents)) {
-            gs_free char *contents_escaped   = g_strescape(contents, NULL);
-            gs_free char *prev_value_escaped = g_strescape(entry->value, NULL);
-
-            _LOGD("sysctl: reading '%s': '%s' (changed from '%s' on last read)",
-                  pathid,
-                  contents_escaped,
-                  prev_value_escaped);
-            g_free(entry->value);
-            entry->value = g_strdup(contents);
-        }
-        nm_c_list_move_front(&priv->sysctl_list, &entry->lst);
-    } else {
-        gs_free char *    contents_escaped = g_strescape(contents, NULL);
-        SysctlCacheEntry *old;
-        size_t            len;
-
-        len          = strlen(pathid);
-        entry        = g_malloc(sizeof(SysctlCacheEntry) + len + 1);
-        entry->value = g_strdup(contents);
-        entry->path  = entry->path_data;
-        memcpy(entry->path_data, pathid, len + 1);
-
-        /* Remove oldest entry when the cache becomes too big */
-        if (g_hash_table_size(priv->sysctl_get_prev_values) > 1000) {
-            old = c_list_last_entry(&priv->sysctl_list, SysctlCacheEntry, lst);
-            g_hash_table_remove(priv->sysctl_get_prev_values, old);
-        }
-
-        _LOGD("sysctl: reading '%s': '%s'", pathid, contents_escaped);
-
-        g_hash_table_add(priv->sysctl_get_prev_values, entry);
-        c_list_link_front(&priv->sysctl_list, &entry->lst);
-    }
-}
-
-#define _log_dbg_sysctl_get(platform, pathid, contents)           \
-    G_STMT_START                                                  \
-    {                                                             \
-        if (_LOGD_ENABLED())                                      \
-            _log_dbg_sysctl_get_impl(platform, pathid, contents); \
-    }                                                             \
-    G_STMT_END
-
-static char *
-sysctl_get(NMPlatform *platform, const char *pathid, int dirfd, const char *path)
-{
-    nm_auto_pop_netns NMPNetns *netns    = NULL;
-    GError *                    error    = NULL;
-    gs_free char *              contents = NULL;
-
-    ASSERT_SYSCTL_ARGS(pathid, dirfd, path);
-
-    if (dirfd < 0) {
-        if (!nm_platform_netns_push(platform, &netns)) {
-            errno = EBUSY;
-            return NULL;
-        }
-        pathid = path;
-    }
-
-    if (!nm_utils_file_get_contents(dirfd,
-                                    path,
-                                    1 * 1024 * 1024,
-                                    NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
-                                    &contents,
-                                    NULL,
-                                    NULL,
-                                    &error)) {
-        NMLogLevel log_level = LOGL_ERR;
-        int        errsv     = EBUSY;
-
-        if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
-            errsv     = ENOENT;
-            log_level = LOGL_DEBUG;
-        } else if (g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NODEV)
-                   || g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_FAILED)) {
-            /* We assume FAILED means EOPNOTSUP and don't log a error message. */
-            log_level = LOGL_DEBUG;
-        }
-
-        _NMLOG(log_level, "error reading %s: %s", pathid, error->message);
-        g_clear_error(&error);
-        errno = errsv;
-        return NULL;
-    }
-
-    g_strstrip(contents);
-
-    _log_dbg_sysctl_get(platform, pathid, contents);
-
-    /* errno is left undefined (as we don't return NULL). */
-    return g_steal_pointer(&contents);
-}
-
-/*****************************************************************************/
-
-static void
-process_events(NMPlatform *platform)
-{
-    delayed_action_handle_all(platform, TRUE);
-}
-
-/*****************************************************************************/
-
-static const RefreshAllInfo *
-refresh_all_type_get_info(RefreshAllType refresh_all_type)
-{
-    static const RefreshAllInfo infos[] = {
-#define R(_refresh_all_type, _obj_type, _addr_family) \
-    [_refresh_all_type] = {                           \
-        .obj_type    = _obj_type,                     \
-        .addr_family = _addr_family,                  \
-    }
-        R(REFRESH_ALL_TYPE_LINKS, NMP_OBJECT_TYPE_LINK, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP4_ADDRESSES, NMP_OBJECT_TYPE_IP4_ADDRESS, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP4_ROUTES, NMP_OBJECT_TYPE_IP4_ROUTE, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP6_ROUTES, NMP_OBJECT_TYPE_IP6_ROUTE, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_ROUTING_RULES_IP4, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET),
-        R(REFRESH_ALL_TYPE_ROUTING_RULES_IP6, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET6),
-        R(REFRESH_ALL_TYPE_QDISCS, NMP_OBJECT_TYPE_QDISC, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_TFILTERS, NMP_OBJECT_TYPE_TFILTER, AF_UNSPEC),
-#undef R
-    };
-
-    nm_assert(_NM_INT_NOT_NEGATIVE(refresh_all_type));
-    nm_assert(refresh_all_type < G_N_ELEMENTS(infos));
-    nm_assert(nmp_class_from_type(infos[refresh_all_type].obj_type));
-
-    return &infos[refresh_all_type];
-}
-
-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),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES,
-                         REFRESH_ALL_TYPE_IP6_ADDRESSES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, REFRESH_ALL_TYPE_IP4_ROUTES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, REFRESH_ALL_TYPE_IP6_ROUTES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4,
-                         REFRESH_ALL_TYPE_ROUTING_RULES_IP4),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
-                         REFRESH_ALL_TYPE_ROUTING_RULES_IP6),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, REFRESH_ALL_TYPE_QDISCS),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, REFRESH_ALL_TYPE_TFILTERS),
-    NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
-
-static DelayedActionType
-delayed_action_type_from_refresh_all_type(RefreshAllType refresh_all_type)
-{
-    DelayedActionType t;
-
-    nm_assert(refresh_all_type_get_info(refresh_all_type));
-
-    t = (((DelayedActionType) 1) << refresh_all_type);
-
-    nm_assert(refresh_all_type == delayed_action_type_to_refresh_all_type(t));
-
-    return t;
-}
-
-static RefreshAllType
-refresh_all_type_from_needle_object(const NMPObject *obj_needle)
-{
-    switch (NMP_OBJECT_GET_TYPE(obj_needle)) {
-    case NMP_OBJECT_TYPE_LINK:
-        return REFRESH_ALL_TYPE_LINKS;
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-        return REFRESH_ALL_TYPE_IP4_ADDRESSES;
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-        return REFRESH_ALL_TYPE_IP6_ADDRESSES;
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-        return REFRESH_ALL_TYPE_IP4_ROUTES;
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        return REFRESH_ALL_TYPE_IP6_ROUTES;
-    case NMP_OBJECT_TYPE_QDISC:
-        return REFRESH_ALL_TYPE_QDISCS;
-    case NMP_OBJECT_TYPE_TFILTER:
-        return REFRESH_ALL_TYPE_TFILTERS;
-    case NMP_OBJECT_TYPE_ROUTING_RULE:
-        switch (NMP_OBJECT_CAST_ROUTING_RULE(obj_needle)->addr_family) {
-        case AF_INET:
-            return REFRESH_ALL_TYPE_ROUTING_RULES_IP4;
-        case AF_INET6:
-            return REFRESH_ALL_TYPE_ROUTING_RULES_IP6;
-        }
-        nm_assert_not_reached();
-        return 0;
-    default:
-        nm_assert_not_reached();
-        return 0;
-    }
-}
-
-static const NMPLookup *
-refresh_all_type_init_lookup(RefreshAllType refresh_all_type, NMPLookup *lookup)
-{
-    const RefreshAllInfo *refresh_all_info;
-
-    nm_assert(lookup);
-
-    refresh_all_info = refresh_all_type_get_info(refresh_all_type);
-
-    nm_assert(refresh_all_info);
-
-    if (NM_IN_SET(refresh_all_info->obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)) {
-        return nmp_lookup_init_object_by_addr_family(lookup,
-                                                     refresh_all_info->obj_type,
-                                                     refresh_all_info->addr_family);
-    }
-
-    /* not yet implemented. */
-    nm_assert(refresh_all_info->addr_family == AF_UNSPEC);
-
-    return nmp_lookup_init_obj_type(lookup, refresh_all_info->obj_type);
-}
-
-static DelayedActionType
-delayed_action_refresh_from_needle_object(const NMPObject *obj_needle)
-{
-    return delayed_action_type_from_refresh_all_type(
-        refresh_all_type_from_needle_object(obj_needle));
-}
-
-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"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES,
-                             "refresh-all-ip6-addresses"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, "refresh-all-ip4-routes"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, "refresh-all-ip6-routes"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4,
-                             "refresh-all-routing-rules-ip4"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
-                             "refresh-all-routing-rules-ip6"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, "refresh-all-qdiscs"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, "refresh-all-tfilters"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_LINK, "refresh-link"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_MASTER_CONNECTED, "master-connected"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_READ_NETLINK, "read-netlink"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, "wait-for-nl-response"),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_NONE),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(__DELAYED_ACTION_TYPE_MAX), );
-
-static const char *
-delayed_action_to_string_full(DelayedActionType action_type,
-                              gpointer          user_data,
-                              char *            buf,
-                              gsize             buf_size)
-{
-    char *                                    buf0 = buf;
-    const DelayedActionWaitForNlResponseData *data;
-
-    nm_utils_strbuf_append_str(&buf, &buf_size, delayed_action_to_string(action_type));
-    switch (action_type) {
-    case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
-        nm_utils_strbuf_append(&buf, &buf_size, " (master-ifindex %d)", GPOINTER_TO_INT(user_data));
-        break;
-    case DELAYED_ACTION_TYPE_REFRESH_LINK:
-        nm_utils_strbuf_append(&buf, &buf_size, " (ifindex %d)", GPOINTER_TO_INT(user_data));
-        break;
-    case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
-        data = user_data;
-
-        if (data) {
-            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_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))
-                    : "");
-        } else
-            nm_utils_strbuf_append_str(&buf, &buf_size, " (any)");
-        break;
-    default:
-        nm_assert(!user_data);
-        break;
-    }
-    return buf0;
-}
-
-#define _LOGt_delayed_action(action_type, user_data, operation)                           \
-    G_STMT_START                                                                          \
-    {                                                                                     \
-        char _buf[255];                                                                   \
-                                                                                          \
-        _LOGt("delayed-action: %s %s",                                                    \
-              "" operation,                                                               \
-              delayed_action_to_string_full(action_type, user_data, _buf, sizeof(_buf))); \
-    }                                                                                     \
-    G_STMT_END
-
-/*****************************************************************************/
-
-static gboolean
-delayed_action_refresh_all_in_progress(NMPlatform *platform, DelayedActionType action_type)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    RefreshAllType          refresh_all_type;
-
-    nm_assert(nm_utils_is_power_of_two(action_type));
-    nm_assert(NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_ALL));
-    nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
-
-    if (NM_FLAGS_ANY(priv->delayed_action.flags, action_type))
-        return TRUE;
-
-    refresh_all_type = delayed_action_type_to_refresh_all_type(action_type);
-    return (priv->delayed_action.refresh_all_in_progress[refresh_all_type] > 0);
-}
-
-static void
-delayed_action_wait_for_nl_response_complete(NMPlatform *            platform,
-                                             guint                   idx,
-                                             WaitForNlResponseResult seq_result)
-{
-    NMLinuxPlatformPrivate *            priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionWaitForNlResponseData *data;
-
-    nm_assert(NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-    nm_assert(idx < priv->delayed_action.list_wait_for_nl_response->len);
-    nm_assert(seq_result);
-
-    data = &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                          DelayedActionWaitForNlResponseData,
-                          idx);
-
-    _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, data, "complete");
-
-    if (priv->delayed_action.list_wait_for_nl_response->len <= 1)
-        priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE;
-    if (data->out_seq_result)
-        *data->out_seq_result = seq_result;
-    switch (data->response_type) {
-    case DELAYED_ACTION_RESPONSE_TYPE_VOID:
-        break;
-    case DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS:
-        if (data->response.out_refresh_all_in_progress) {
-            nm_assert(*data->response.out_refresh_all_in_progress > 0);
-            *data->response.out_refresh_all_in_progress -= 1;
-            data->response.out_refresh_all_in_progress = NULL;
-        }
-        break;
-    case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET:
-        if (data->response.out_route_get) {
-            nm_assert(!*data->response.out_route_get);
-            data->response.out_route_get = NULL;
-        }
-        break;
-    }
-
-    g_array_remove_index_fast(priv->delayed_action.list_wait_for_nl_response, idx);
-}
-
-static void
-delayed_action_wait_for_nl_response_complete_check(NMPlatform *            platform,
-                                                   WaitForNlResponseResult force_result,
-                                                   guint32 *               out_next_seq_number,
-                                                   gint64 *                out_next_timeout_abs_ns,
-                                                   gint64 *                p_now_ns)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    guint                   i;
-    guint32                 next_seq_number     = 0;
-    gint64                  next_timeout_abs_ns = 0;
-    int                     now_ns              = 0;
-
-    for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len;) {
-        const DelayedActionWaitForNlResponseData *data =
-            &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                           DelayedActionWaitForNlResponseData,
-                           i);
-
-        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_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)
-            delayed_action_wait_for_nl_response_complete(platform, i, force_result);
-        else {
-            if (next_seq_number == 0 || next_timeout_abs_ns > data->timeout_abs_ns) {
-                next_seq_number     = data->seq_number;
-                next_timeout_abs_ns = data->timeout_abs_ns;
-            }
-            i++;
-        }
-    }
-
-    if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) {
-        nm_assert(
-            !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len == 0);
-    }
-
-    NM_SET_OUT(out_next_seq_number, next_seq_number);
-    NM_SET_OUT(out_next_timeout_abs_ns, next_timeout_abs_ns);
-    NM_SET_OUT(p_now_ns, now_ns);
-}
-
-static void
-delayed_action_wait_for_nl_response_complete_all(NMPlatform *            platform,
-                                                 WaitForNlResponseResult fallback_result)
-{
-    delayed_action_wait_for_nl_response_complete_check(platform, fallback_result, NULL, NULL, NULL);
-}
-
-/*****************************************************************************/
-
-static void
-delayed_action_handle_MASTER_CONNECTED(NMPlatform *platform, int master_ifindex)
-{
-    nm_auto_nmpobj const NMPObject *obj_old = NULL;
-    nm_auto_nmpobj const NMPObject *obj_new = NULL;
-    NMPCacheOpsType                 cache_op;
-
-    cache_op = nmp_cache_update_link_master_connected(nm_platform_get_cache(platform),
-                                                      master_ifindex,
-                                                      &obj_old,
-                                                      &obj_new);
-    if (cache_op == NMP_CACHE_OPS_UNCHANGED)
-        return;
-    cache_on_change(platform, cache_op, obj_old, obj_new);
-    nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new);
-}
-
-static void
-delayed_action_handle_REFRESH_LINK(NMPlatform *platform, int ifindex)
-{
-    do_request_link_no_delayed_actions(platform, ifindex, NULL);
-}
-
-static void
-delayed_action_handle_REFRESH_ALL(NMPlatform *platform, DelayedActionType flags)
-{
-    do_request_all_no_delayed_actions(platform, flags);
-}
-
-static void
-delayed_action_handle_READ_NETLINK(NMPlatform *platform)
-{
-    event_handler_read_netlink(platform, FALSE);
-}
-
-static void
-delayed_action_handle_WAIT_FOR_NL_RESPONSE(NMPlatform *platform)
-{
-    event_handler_read_netlink(platform, TRUE);
-}
-
-static gboolean
-delayed_action_handle_one(NMPlatform *platform)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    gpointer                user_data;
-
-    if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE)
-        return FALSE;
-
-    /* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions.
-     * This type of action is entirely cache-internal and is here to resolve a
-     * cache inconsistency. It should be fixed right away. */
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) {
-        nm_assert(priv->delayed_action.list_master_connected->len > 0);
-
-        user_data = priv->delayed_action.list_master_connected->pdata[0];
-        g_ptr_array_remove_index_fast(priv->delayed_action.list_master_connected, 0);
-        if (priv->delayed_action.list_master_connected->len == 0)
-            priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_MASTER_CONNECTED;
-        nm_assert(_nm_utils_ptrarray_find_first(
-                      (gconstpointer *) priv->delayed_action.list_master_connected->pdata,
-                      priv->delayed_action.list_master_connected->len,
-                      user_data)
-                  < 0);
-
-        _LOGt_delayed_action(DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle");
-        delayed_action_handle_MASTER_CONNECTED(platform, GPOINTER_TO_INT(user_data));
-        return TRUE;
-    }
-    nm_assert(priv->delayed_action.list_master_connected->len == 0);
-
-    /* Next we prefer read-netlink, because the buffer size is limited and we want to process events
-     * from netlink early. */
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) {
-        _LOGt_delayed_action(DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle");
-        priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK;
-        delayed_action_handle_READ_NETLINK(platform);
-        return TRUE;
-    }
-
-    if (NM_FLAGS_ANY(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_ALL)) {
-        DelayedActionType flags, iflags;
-
-        flags = priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_ALL;
-
-        priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
-
-        if (_LOGt_ENABLED()) {
-            FOR_EACH_DELAYED_ACTION(iflags, flags)
-            _LOGt_delayed_action(iflags, NULL, "handle");
-        }
-
-        delayed_action_handle_REFRESH_ALL(platform, flags);
-        return TRUE;
-    }
-
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
-        nm_assert(priv->delayed_action.list_refresh_link->len > 0);
-
-        user_data = priv->delayed_action.list_refresh_link->pdata[0];
-        g_ptr_array_remove_index_fast(priv->delayed_action.list_refresh_link, 0);
-        if (priv->delayed_action.list_refresh_link->len == 0)
-            priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
-        nm_assert(_nm_utils_ptrarray_find_first(
-                      (gconstpointer *) priv->delayed_action.list_refresh_link->pdata,
-                      priv->delayed_action.list_refresh_link->len,
-                      user_data)
-                  < 0);
-
-        _LOGt_delayed_action(DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
-
-        delayed_action_handle_REFRESH_LINK(platform, GPOINTER_TO_INT(user_data));
-
-        return TRUE;
-    }
-
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
-        _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, NULL, "handle");
-        delayed_action_handle_WAIT_FOR_NL_RESPONSE(platform);
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-static gboolean
-delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    gboolean                any  = FALSE;
-
-    g_return_val_if_fail(priv->delayed_action.is_handling == 0, FALSE);
-
-    priv->delayed_action.is_handling++;
-    if (read_netlink)
-        delayed_action_schedule(platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL);
-    while (delayed_action_handle_one(platform))
-        any = TRUE;
-    priv->delayed_action.is_handling--;
-
-    cache_prune_all(platform);
-
-    return any;
-}
-
-static void
-delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionType       iflags;
-
-    nm_assert(action_type != DELAYED_ACTION_TYPE_NONE);
-
-    switch (action_type) {
-    case DELAYED_ACTION_TYPE_REFRESH_LINK:
-        if (_nm_utils_ptrarray_find_first(
-                (gconstpointer *) priv->delayed_action.list_refresh_link->pdata,
-                priv->delayed_action.list_refresh_link->len,
-                user_data)
-            < 0)
-            g_ptr_array_add(priv->delayed_action.list_refresh_link, user_data);
-        break;
-    case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
-        if (_nm_utils_ptrarray_find_first(
-                (gconstpointer *) priv->delayed_action.list_master_connected->pdata,
-                priv->delayed_action.list_master_connected->len,
-                user_data)
-            < 0)
-            g_ptr_array_add(priv->delayed_action.list_master_connected, user_data);
-        break;
-    case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
-        g_array_append_vals(priv->delayed_action.list_wait_for_nl_response, user_data, 1);
-        break;
-    default:
-        nm_assert(!user_data);
-        nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_REFRESH_LINK));
-        nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED));
-        nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-        break;
-    }
-
-    priv->delayed_action.flags |= action_type;
-
-    if (_LOGt_ENABLED()) {
-        FOR_EACH_DELAYED_ACTION(iflags, action_type)
-        _LOGt_delayed_action(iflags, user_data, "schedule");
-    }
-}
-
-static void
-delayed_action_schedule_WAIT_FOR_NL_RESPONSE(NMPlatform *                       platform,
-                                             guint32                            seq_number,
-                                             WaitForNlResponseResult *          out_seq_result,
-                                             char **                            out_errmsg,
-                                             DelayedActionWaitForNlResponseType response_type,
-                                             gpointer                           response_out_data)
-{
-    DelayedActionWaitForNlResponseData data = {
-        .seq_number = seq_number,
-        .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,
-        .response.out_data = response_out_data,
-    };
-
-    delayed_action_schedule(platform, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, &data);
-}
-
-/*****************************************************************************/
-
-static void
-cache_prune_one_type(NMPlatform *platform, const NMPLookup *lookup)
-{
-    NMDedupMultiIter iter;
-    const NMPObject *obj;
-    NMPCacheOpsType  cache_op;
-    NMPCache *       cache = nm_platform_get_cache(platform);
-
-    nm_dedup_multi_iter_init(&iter, nmp_cache_lookup(cache, lookup));
-    while (nm_dedup_multi_iter_next(&iter)) {
-        const NMDedupMultiEntry *main_entry;
-
-        /* we only track the dirty flag for the OBJECT-TYPE index. That means,
-         * for other lookup types we need to check the dirty flag of the main-entry. */
-        main_entry = nmp_cache_reresolve_main_entry(cache, iter.current, lookup);
-        if (!main_entry->dirty)
-            continue;
-
-        obj = main_entry->obj;
-
-        _LOGt("cache-prune: prune %s",
-              nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
-
-        {
-            nm_auto_nmpobj const NMPObject *obj_old = NULL;
-
-            cache_op = nmp_cache_remove(cache, obj, TRUE, TRUE, &obj_old);
-            nm_assert(cache_op == NMP_CACHE_OPS_REMOVED);
-            cache_on_change(platform, cache_op, obj_old, NULL);
-            nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, NULL);
-        }
-    }
-}
-
-static void
-cache_prune_all(NMPlatform *platform)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    RefreshAllType          refresh_all_type;
-
-    for (refresh_all_type = _REFRESH_ALL_TYPE_FIRST; refresh_all_type < _REFRESH_ALL_TYPE_NUM;
-         refresh_all_type++) {
-        NMPLookup lookup;
-
-        if (priv->pruning[refresh_all_type] == 0)
-            continue;
-        priv->pruning[refresh_all_type] -= 1;
-        if (priv->pruning[refresh_all_type] > 0)
-            continue;
-        refresh_all_type_init_lookup(refresh_all_type, &lookup);
-        cache_prune_one_type(platform, &lookup);
-    }
-}
-
-static void
-cache_on_change(NMPlatform *     platform,
-                NMPCacheOpsType  cache_op,
-                const NMPObject *obj_old,
-                const NMPObject *obj_new)
-{
-    const NMPClass *klass;
-    char            str_buf[sizeof(_nm_utils_to_string_buffer)];
-    char            str_buf2[sizeof(_nm_utils_to_string_buffer)];
-    NMPCache *      cache = nm_platform_get_cache(platform);
-
-    ASSERT_nmp_cache_ops(cache, cache_op, obj_old, obj_new);
-    nm_assert(cache_op != NMP_CACHE_OPS_UNCHANGED);
-
-    klass = obj_old ? NMP_OBJECT_GET_CLASS(obj_old) : NMP_OBJECT_GET_CLASS(obj_new);
-
-    _LOGt(
-        "update-cache-%s: %s: %s%s%s",
-        klass->obj_type_name,
-        (cache_op == NMP_CACHE_OPS_UPDATED ? "UPDATE"
-                                           : (cache_op == NMP_CACHE_OPS_REMOVED   ? "REMOVE"
-                                              : (cache_op == NMP_CACHE_OPS_ADDED) ? "ADD"
-                                                                                  : "???")),
-        (cache_op != NMP_CACHE_OPS_ADDED
-             ? nmp_object_to_string(obj_old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof(str_buf2))
-             : nmp_object_to_string(obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof(str_buf2))),
-        (cache_op == NMP_CACHE_OPS_UPDATED) ? " -> " : "",
-        (cache_op == NMP_CACHE_OPS_UPDATED
-             ? nmp_object_to_string(obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof(str_buf))
-             : ""));
-
-    switch (klass->obj_type) {
-    case NMP_OBJECT_TYPE_LINK:
-    {
-        /* check whether changing a slave link can cause a master link (bridge or bond) to go up/down */
-        if (obj_old
-            && nmp_cache_link_connected_needs_toggle_by_ifindex(cache,
-                                                                obj_old->link.master,
-                                                                obj_new,
-                                                                obj_old))
-            delayed_action_schedule(platform,
-                                    DELAYED_ACTION_TYPE_MASTER_CONNECTED,
-                                    GINT_TO_POINTER(obj_old->link.master));
-        if (obj_new && (!obj_old || obj_old->link.master != obj_new->link.master)
-            && nmp_cache_link_connected_needs_toggle_by_ifindex(cache,
-                                                                obj_new->link.master,
-                                                                obj_new,
-                                                                obj_old))
-            delayed_action_schedule(platform,
-                                    DELAYED_ACTION_TYPE_MASTER_CONNECTED,
-                                    GINT_TO_POINTER(obj_new->link.master));
-    }
-        {
-            /* check whether we are about to change a master link that needs toggling connected state. */
-            if (obj_new /* <-- nonsensical, make coverity happy */
-                && nmp_cache_link_connected_needs_toggle(cache, obj_new, obj_new, obj_old))
-                delayed_action_schedule(platform,
-                                        DELAYED_ACTION_TYPE_MASTER_CONNECTED,
-                                        GINT_TO_POINTER(obj_new->link.ifindex));
-        }
-        {
-            int ifindex = 0;
-
-            /* if we remove a link (from netlink), we must refresh the addresses, routes, qdiscs and tfilters */
-            if (cache_op == NMP_CACHE_OPS_REMOVED
-                && obj_old /* <-- nonsensical, make coverity happy */)
-                ifindex = obj_old->link.ifindex;
-            else if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old
-                     && obj_new /* <-- nonsensical, make coverity happy */
-                     && !obj_new->_link.netlink.is_in_netlink
-                     && obj_new->_link.netlink.is_in_netlink
-                            != obj_old->_link.netlink.is_in_netlink)
-                ifindex = obj_new->link.ifindex;
-
-            if (ifindex > 0) {
-                delayed_action_schedule(platform,
-                                        DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-                                        NULL);
-            }
-        }
-        {
-            int ifindex = -1;
-
-            /* removal of a link could be caused by moving the link to another netns.
-             * In this case, we potentially have to update other links that have this link as parent.
-             * Currently, kernel misses to sent us a notification in this case
-             * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */
-
-            if (cache_op == NMP_CACHE_OPS_REMOVED
-                && obj_old /* <-- nonsensical, make coverity happy */
-                && obj_old->_link.netlink.is_in_netlink)
-                ifindex = obj_old->link.ifindex;
-            else if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old
-                     && obj_new /* <-- nonsensical, make coverity happy */
-                     && obj_old->_link.netlink.is_in_netlink
-                     && !obj_new->_link.netlink.is_in_netlink)
-                ifindex = obj_new->link.ifindex;
-
-            if (ifindex > 0) {
-                NMPLookup             lookup;
-                NMDedupMultiIter      iter;
-                const NMPlatformLink *l;
-
-                nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK);
-                nmp_cache_iter_for_each_link (&iter, nmp_cache_lookup(cache, &lookup), &l) {
-                    if (l->parent == ifindex)
-                        delayed_action_schedule(platform,
-                                                DELAYED_ACTION_TYPE_REFRESH_LINK,
-                                                GINT_TO_POINTER(l->ifindex));
-                }
-            }
-        }
-        {
-            /* if a link goes down, we must refresh routes */
-            if (cache_op == NMP_CACHE_OPS_UPDATED && obj_old
-                && obj_new /* <-- nonsensical, make coverity happy */
-                && obj_old->_link.netlink.is_in_netlink && obj_new->_link.netlink.is_in_netlink
-                && ((NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_UP)
-                     && !NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_UP))
-                    || (NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_LOWER_UP)
-                        && !NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_LOWER_UP)))) {
-                /* FIXME: I suspect that IFF_LOWER_UP must not be considered, and I
-                 * think kernel does send RTM_DELROUTE events for IPv6 routes, so
-                 * we might not need to refresh IPv6 routes. */
-                delayed_action_schedule(platform,
-                                        DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
-                                        NULL);
-            }
-        }
-        if (NM_IN_SET(cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)
-            && (obj_new && obj_new->_link.netlink.is_in_netlink)
-            && (!obj_old || !obj_old->_link.netlink.is_in_netlink)) {
-            gboolean                re_request_link = FALSE;
-            const NMPlatformLnkTun *lnk_tun;
-
-            if (!obj_new->_link.netlink.lnk
-                && NM_IN_SET(obj_new->link.type,
-                             NM_LINK_TYPE_GRE,
-                             NM_LINK_TYPE_GRETAP,
-                             NM_LINK_TYPE_IP6TNL,
-                             NM_LINK_TYPE_IP6GRE,
-                             NM_LINK_TYPE_IP6GRETAP,
-                             NM_LINK_TYPE_INFINIBAND,
-                             NM_LINK_TYPE_MACVLAN,
-                             NM_LINK_TYPE_MACVLAN,
-                             NM_LINK_TYPE_SIT,
-                             NM_LINK_TYPE_TUN,
-                             NM_LINK_TYPE_VLAN,
-                             NM_LINK_TYPE_VXLAN)) {
-                /* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that
-                 * kernel didn't send this notification, thus when we first learn about a link
-                 * that lacks an lnk_data we re-request it again.
-                 *
-                 * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */
-                re_request_link = TRUE;
-            } else if (obj_new->link.type == NM_LINK_TYPE_TUN && obj_new->_link.netlink.lnk
-                       && (lnk_tun = &(obj_new->_link.netlink.lnk)->lnk_tun) && !lnk_tun->persist
-                       && lnk_tun->pi && !lnk_tun->vnet_hdr && !lnk_tun->multi_queue
-                       && !lnk_tun->owner_valid && !lnk_tun->group_valid) {
-                /* kernel has/had a know issue that the first notification for TUN device would
-                 * be sent with invalid parameters. The message looks like that kind, so refetch
-                 * it. */
-                re_request_link = TRUE;
-            } else if (obj_new->link.type == NM_LINK_TYPE_VETH && obj_new->link.parent == 0) {
-                /* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
-                 * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827).
-                 * Request it again. */
-                re_request_link = TRUE;
-            } else if (obj_new->link.type == NM_LINK_TYPE_ETHERNET
-                       && obj_new->link.l_address.len == 0) {
-                /* Due to a kernel bug, we sometimes receive spurious NEWLINK
-                 * messages after a wifi interface has disappeared. Since the
-                 * link is not present anymore we can't determine its type and
-                 * thus it will show up as a Ethernet one, with no address
-                 * specified.  Request the link again to check if it really
-                 * exists.  https://bugzilla.redhat.com/show_bug.cgi?id=1302037
-                 */
-                re_request_link = TRUE;
-            }
-            if (re_request_link) {
-                delayed_action_schedule(platform,
-                                        DELAYED_ACTION_TYPE_REFRESH_LINK,
-                                        GINT_TO_POINTER(obj_new->link.ifindex));
-            }
-        }
-        {
-            /* on enslave/release, we also refresh the master. */
-            int      ifindex1 = 0, ifindex2 = 0;
-            gboolean changed_master, changed_connected;
-
-            changed_master =
-                (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0
-                     ? obj_new->link.master
-                     : 0)
-                != (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0
-                        ? obj_old->link.master
-                        : 0);
-            changed_connected = (obj_new && obj_new->_link.netlink.is_in_netlink
-                                     ? NM_FLAGS_HAS(obj_new->link.n_ifi_flags, IFF_LOWER_UP)
-                                     : 2)
-                                != (obj_old && obj_old->_link.netlink.is_in_netlink
-                                        ? NM_FLAGS_HAS(obj_old->link.n_ifi_flags, IFF_LOWER_UP)
-                                        : 2);
-
-            if (changed_master || changed_connected) {
-                ifindex1 =
-                    (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0)
-                        ? obj_old->link.master
-                        : 0;
-                ifindex2 =
-                    (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0)
-                        ? obj_new->link.master
-                        : 0;
-
-                if (ifindex1 > 0)
-                    delayed_action_schedule(platform,
-                                            DELAYED_ACTION_TYPE_REFRESH_LINK,
-                                            GINT_TO_POINTER(ifindex1));
-                if (ifindex2 > 0 && ifindex1 != ifindex2)
-                    delayed_action_schedule(platform,
-                                            DELAYED_ACTION_TYPE_REFRESH_LINK,
-                                            GINT_TO_POINTER(ifindex2));
-            }
-        }
-        break;
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-    {
-        /* Address deletion is sometimes accompanied by route deletion. We need to
-             * check all routes belonging to the same interface. */
-        if (cache_op == NMP_CACHE_OPS_REMOVED) {
-            delayed_action_schedule(platform,
-                                    (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS)
-                                        ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                        : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
-                                    NULL);
-        }
-    } break;
-    default:
-        break;
-    }
-}
-
-/*****************************************************************************/
-
-static guint32
-_nlh_seq_next_get(NMLinuxPlatformPrivate *priv)
-{
-    /* generate a new sequence number, but never return zero.
-     * Wrapping numbers are not a problem, because we don't rely
-     * on strictly increasing sequence numbers. */
-    return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next);
-}
-
-/**
- * _nl_send_nlmsghdr:
- * @platform:
- * @nlhdr:
- * @out_seq_result:
- * @response_type:
- * @response_out_data:
- *
- * Returns: 0 on success or a negative errno.
- */
-static int
-_nl_send_nlmsghdr(NMPlatform *                       platform,
-                  struct nlmsghdr *                  nlhdr,
-                  WaitForNlResponseResult *          out_seq_result,
-                  char **                            out_errmsg,
-                  DelayedActionWaitForNlResponseType response_type,
-                  gpointer                           response_out_data)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    guint32                 seq;
-    int                     errsv;
-
-    nm_assert(nlhdr);
-
-    seq              = _nlh_seq_next_get(priv);
-    nlhdr->nlmsg_seq = seq;
-
-    {
-        struct sockaddr_nl nladdr = {
-            .nl_family = AF_NETLINK,
-        };
-        struct iovec  iov = {.iov_base = nlhdr, .iov_len = nlhdr->nlmsg_len};
-        struct msghdr msg = {
-            .msg_name    = &nladdr,
-            .msg_namelen = sizeof(nladdr),
-            .msg_iov     = &iov,
-            .msg_iovlen  = 1,
-        };
-        int try_count;
-
-        if (!nlhdr->nlmsg_pid)
-            nlhdr->nlmsg_pid = nl_socket_get_local_port(priv->nlh);
-        nlhdr->nlmsg_flags |= (NLM_F_REQUEST | NLM_F_ACK);
-
-        try_count = 0;
-again:
-        errsv = sendmsg(nl_socket_get_fd(priv->nlh), &msg, 0);
-        if (errsv < 0) {
-            errsv = errno;
-            if (errsv == EINTR && try_count++ < 100)
-                goto again;
-            _LOGD("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)",
-                  nm_strerror_native(errsv),
-                  errsv);
-            return -nm_errno_from_native(errsv);
-        }
-    }
-
-    delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform,
-                                                 seq,
-                                                 out_seq_result,
-                                                 out_errmsg,
-                                                 response_type,
-                                                 response_out_data);
-    return 0;
-}
-
-/**
- * _nl_send_nlmsg:
- * @platform:
- * @nlmsg:
- * @out_seq_result:
- * @response_type:
- * @response_out_data:
- *
- * Returns: 0 on success, or a negative libnl3 error code (beware, it's not an errno).
- */
-static int
-_nl_send_nlmsg(NMPlatform *                       platform,
-               struct nl_msg *                    nlmsg,
-               WaitForNlResponseResult *          out_seq_result,
-               char **                            out_errmsg,
-               DelayedActionWaitForNlResponseType response_type,
-               gpointer                           response_out_data)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    struct nlmsghdr *       nlhdr;
-    guint32                 seq;
-    int                     nle;
-
-    nlhdr            = nlmsg_hdr(nlmsg);
-    seq              = _nlh_seq_next_get(priv);
-    nlhdr->nlmsg_seq = seq;
-
-    nle = nl_send_auto(priv->nlh, nlmsg);
-    if (nle < 0) {
-        _LOGD("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror(nle), nle);
-        return nle;
-    }
-
-    delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform,
-                                                 seq,
-                                                 out_seq_result,
-                                                 out_errmsg,
-                                                 response_type,
-                                                 response_out_data);
-    return 0;
-}
-
-static void
-do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name)
-{
-    NMLinuxPlatformPrivate *     priv  = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    int                          nle;
-
-    if (name && !name[0])
-        name = NULL;
-
-    g_return_if_fail(ifindex > 0 || name);
-
-    _LOGD("do-request-link: %d %s", ifindex, name ?: "");
-
-    if (ifindex > 0) {
-        const NMDedupMultiEntry *entry;
-
-        entry = nmp_cache_lookup_entry_link(nm_platform_get_cache(platform), ifindex);
-        if (entry) {
-            priv->pruning[REFRESH_ALL_TYPE_LINKS] += 1;
-            nm_dedup_multi_entry_set_dirty(entry, TRUE);
-        }
-    }
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nlmsg = _nl_msg_new_link(RTM_GETLINK, 0, ifindex, name);
-    if (nlmsg) {
-        nle = _nl_send_nlmsg(platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
-        if (nle < 0) {
-            _LOGE("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
-                  ifindex,
-                  name ?: "",
-                  nm_strerror(nle),
-                  -nle);
-            return;
-        }
-    }
-}
-
-static void
-do_request_link(NMPlatform *platform, int ifindex, const char *name)
-{
-    do_request_link_no_delayed_actions(platform, ifindex, name);
-    delayed_action_handle_all(platform, FALSE);
-}
-
-static struct nl_msg *
-_nl_msg_new_dump(NMPObjectType obj_type, int preferred_addr_family)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    const NMPClass *             klass;
-
-    klass = nmp_class_from_type(obj_type);
-
-    nm_assert(klass);
-    nm_assert(klass->rtm_gettype > 0);
-
-    nlmsg = nlmsg_alloc_simple(klass->rtm_gettype, NLM_F_DUMP);
-
-    if (klass->addr_family != AF_UNSPEC) {
-        /* if the class specifies a particular address family, then it is preferred. */
-        nm_assert(NM_IN_SET(preferred_addr_family, AF_UNSPEC, klass->addr_family));
-        preferred_addr_family = klass->addr_family;
-    }
-
-    switch (klass->obj_type) {
-    case NMP_OBJECT_TYPE_QDISC:
-    case NMP_OBJECT_TYPE_TFILTER:
-    {
-        const struct tcmsg tcmsg = {
-            .tcm_family = preferred_addr_family,
-        };
-
-        if (nlmsg_append_struct(nlmsg, &tcmsg) < 0)
-            g_return_val_if_reached(NULL);
-    } break;
-    case NMP_OBJECT_TYPE_LINK:
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-    case NMP_OBJECT_TYPE_ROUTING_RULE:
-    {
-        const struct rtgenmsg gmsg = {
-            .rtgen_family = preferred_addr_family,
-        };
-
-        if (nlmsg_append_struct(nlmsg, &gmsg) < 0)
-            g_return_val_if_reached(NULL);
-    } break;
-    default:
-        g_return_val_if_reached(NULL);
-    }
-
-    return g_steal_pointer(&nlmsg);
-}
-
-static void
-do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionType       action_type_prune;
-    DelayedActionType       iflags;
-
-    nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
-    action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
-
-    action_type_prune = action_type;
-
-    /* calling nmp_cache_dirty_set_all_main() with a non-main lookup-index requires an extra
-     * cache lookup for every entry.
-     *
-     * Avoid that, by special casing routing-rules here. */
-    if (NM_FLAGS_ALL(action_type_prune, DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL)) {
-        NMPLookup lookup;
-
-        priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP4] += 1;
-        priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP6] += 1;
-        nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
-        nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup);
-        action_type_prune &= ~DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL;
-    }
-
-    FOR_EACH_DELAYED_ACTION(iflags, action_type_prune)
-    {
-        RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type(iflags);
-        NMPLookup      lookup;
-
-        priv->pruning[refresh_all_type] += 1;
-        refresh_all_type_init_lookup(refresh_all_type, &lookup);
-        nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup);
-    }
-
-    FOR_EACH_DELAYED_ACTION(iflags, action_type)
-    {
-        RefreshAllType        refresh_all_type = delayed_action_type_to_refresh_all_type(iflags);
-        const RefreshAllInfo *refresh_all_info = refresh_all_type_get_info(refresh_all_type);
-        nm_auto_nlmsg struct nl_msg *nlmsg     = NULL;
-        int *                        out_refresh_all_in_progress;
-
-        out_refresh_all_in_progress =
-            &priv->delayed_action.refresh_all_in_progress[refresh_all_type];
-        nm_assert(*out_refresh_all_in_progress >= 0);
-        *out_refresh_all_in_progress += 1;
-
-        /* clear any delayed action that request a refresh of this object type. */
-        priv->delayed_action.flags &= ~iflags;
-        _LOGt_delayed_action(iflags, NULL, "handle (do-request-all)");
-
-        if (refresh_all_type == REFRESH_ALL_TYPE_LINKS) {
-            nm_assert(
-                (priv->delayed_action.list_refresh_link->len > 0)
-                == NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK));
-            if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
-                _LOGt_delayed_action(DELAYED_ACTION_TYPE_REFRESH_LINK,
-                                     NULL,
-                                     "clear (do-request-all)");
-                priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
-                g_ptr_array_set_size(priv->delayed_action.list_refresh_link, 0);
-            }
-        }
-
-        event_handler_read_netlink(platform, FALSE);
-
-        nlmsg = _nl_msg_new_dump(refresh_all_info->obj_type, refresh_all_info->addr_family);
-        if (!nlmsg)
-            goto next_after_fail;
-
-        if (_nl_send_nlmsg(platform,
-                           nlmsg,
-                           NULL,
-                           NULL,
-                           DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS,
-                           out_refresh_all_in_progress)
-            < 0)
-            goto next_after_fail;
-
-        continue;
-
-next_after_fail:
-        nm_assert(*out_refresh_all_in_progress > 0);
-        *out_refresh_all_in_progress -= 1;
-    }
-}
-
-static void
-do_request_one_type_by_needle_object(NMPlatform *platform, const NMPObject *obj_needle)
-{
-    do_request_all_no_delayed_actions(platform,
-                                      delayed_action_refresh_from_needle_object(obj_needle));
-    delayed_action_handle_all(platform, FALSE);
-}
-
-static void
-event_seq_check_refresh_all(NMPlatform *platform, guint32 seq_number)
-{
-    NMLinuxPlatformPrivate *            priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionWaitForNlResponseData *data;
-    guint                               i;
-
-    if (NM_IN_SET(seq_number, 0, priv->nlh_seq_last_seen))
-        return;
-
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
-
-        for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
-            data = &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                                  DelayedActionWaitForNlResponseData,
-                                  i);
-
-            if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
-                && data->response.out_refresh_all_in_progress
-                && data->seq_number == priv->nlh_seq_last_seen) {
-                *data->response.out_refresh_all_in_progress -= 1;
-                data->response.out_refresh_all_in_progress = NULL;
-                break;
-            }
-        }
-    }
-
-    priv->nlh_seq_last_seen = seq_number;
-}
-
-static void
-event_seq_check(NMPlatform *            platform,
-                guint32                 seq_number,
-                WaitForNlResponseResult seq_result,
-                const char *            msg)
-{
-    NMLinuxPlatformPrivate *            priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionWaitForNlResponseData *data;
-    guint                               i;
-
-    if (seq_number == 0)
-        return;
-
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
-
-        for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
-            data = &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                                  DelayedActionWaitForNlResponseData,
-                                  i);
-
-            if (data->seq_number == seq_number) {
-                /* We potentially receive many parts partial responses for the same sequence number.
-                 * Thus, we only remember the result, and collect it later. */
-                if (data->seq_result < 0) {
-                    /* we already saw an error for this sequence number.
-                     * Preserve it. */
-                } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
-                           || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
-                    data->seq_result = seq_result;
-                if (data->out_errmsg && !*data->out_errmsg)
-                    *data->out_errmsg = g_strdup(msg);
-                return;
-            }
-        }
-    }
-
-#if NM_MORE_LOGGING
-    if (seq_number != priv->nlh_seq_last_handled)
-        _LOGt("netlink: recvmsg: unwaited sequence number %u", seq_number);
-    priv->nlh_seq_last_handled = seq_number;
-#endif
-}
-
-static void
-event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events)
-{
-    NMLinuxPlatformPrivate *priv;
-    nm_auto_nmpobj NMPObject *obj = NULL;
-    NMPCacheOpsType           cache_op;
-    struct nlmsghdr *         msghdr;
-    char                      buf_nlmsghdr[400];
-    gboolean                  is_del  = FALSE;
-    gboolean                  is_dump = FALSE;
-    NMPCache *                cache   = nm_platform_get_cache(platform);
-
-    msghdr = nlmsg_hdr(msg);
-
-    if (!_nm_platform_kernel_support_detected(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)
-        && msghdr->nlmsg_type == RTM_NEWADDR) {
-        /* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6,
-         * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */
-        if (nlmsg_valid_hdr(msghdr, sizeof(struct ifaddrmsg))
-            && NM_IN_SET(((struct ifaddrmsg *) nlmsg_data(msghdr))->ifa_family,
-                         AF_INET,
-                         AF_INET6)) {
-            /* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
-             * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
-             * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14,
-             * dated 30 March, 2014.
-             *
-             * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy
-             * way to detect kernel support. */
-            _nm_platform_kernel_support_init(
-                NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS,
-                !!nlmsg_find_attr(msghdr, sizeof(struct ifaddrmsg), IFA_FLAGS) ? 1 : -1);
-        }
-    }
-
-    if (!handle_events)
-        return;
-
-    if (NM_IN_SET(msghdr->nlmsg_type,
-                  RTM_DELLINK,
-                  RTM_DELADDR,
-                  RTM_DELROUTE,
-                  RTM_DELRULE,
-                  RTM_DELQDISC,
-                  RTM_DELTFILTER)) {
-        /* The event notifies about a deleted object. We don't need to initialize all
-         * fields of the object. */
-        is_del = TRUE;
-    }
-
-    obj = nmp_object_new_from_nl(platform, cache, msg, is_del);
-    if (!obj) {
-        _LOGT("event-notification: %s: ignore",
-              nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)));
-        return;
-    }
-
-    if (!is_del
-        && NM_IN_SET(msghdr->nlmsg_type,
-                     RTM_NEWADDR,
-                     RTM_NEWLINK,
-                     RTM_NEWROUTE,
-                     RTM_NEWRULE,
-                     RTM_NEWQDISC,
-                     RTM_NEWTFILTER)) {
-        is_dump =
-            delayed_action_refresh_all_in_progress(platform,
-                                                   delayed_action_refresh_from_needle_object(obj));
-    }
-
-    _LOGT("event-notification: %s%s: %s",
-          nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)),
-          is_dump ? ", in-dump" : "",
-          nmp_object_to_string(obj,
-                               is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
-                               NULL,
-                               0));
-
-    {
-        nm_auto_nmpobj const NMPObject *obj_old = NULL;
-        nm_auto_nmpobj const NMPObject *obj_new = NULL;
-
-        switch (msghdr->nlmsg_type) {
-        case RTM_GETLINK:
-        case RTM_NEWADDR:
-        case RTM_NEWLINK:
-        case RTM_NEWQDISC:
-        case RTM_NEWRULE:
-        case RTM_NEWTFILTER:
-            cache_op = nmp_cache_update_netlink(cache, obj, is_dump, &obj_old, &obj_new);
-            if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
-                cache_on_change(platform, cache_op, obj_old, obj_new);
-                nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new);
-            }
-            break;
-
-        case RTM_NEWROUTE:
-        {
-            nm_auto_nmpobj const NMPObject *obj_replace     = NULL;
-            gboolean                        resync_required = FALSE;
-            gboolean                        only_dirty      = FALSE;
-            gboolean                        is_ipv6;
-
-            /* IPv4 routes that are a response to RTM_GETROUTE must have
-             * the cloned flag while IPv6 routes don't have to. */
-            is_ipv6 = NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP6_ROUTE;
-            if (is_ipv6 || NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED)) {
-                nm_assert(is_ipv6 || !nmp_object_is_alive(obj));
-                priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-                if (NM_FLAGS_HAS(priv->delayed_action.flags,
-                                 DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-                    guint i;
-
-                    nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
-                    for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
-                        DelayedActionWaitForNlResponseData *data =
-                            &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                                           DelayedActionWaitForNlResponseData,
-                                           i);
-
-                        if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET
-                            && data->response.out_route_get) {
-                            nm_assert(!*data->response.out_route_get);
-                            if (data->seq_number == nlmsg_hdr(msg)->nlmsg_seq) {
-                                *data->response.out_route_get = nmp_object_clone(obj, FALSE);
-                                data->response.out_route_get  = NULL;
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-
-            cache_op = nmp_cache_update_netlink_route(cache,
-                                                      obj,
-                                                      is_dump,
-                                                      msghdr->nlmsg_flags,
-                                                      &obj_old,
-                                                      &obj_new,
-                                                      &obj_replace,
-                                                      &resync_required);
-            if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
-                if (obj_replace) {
-                    const NMDedupMultiEntry *entry_replace;
-
-                    /* we found an object that is to be replaced by the RTM_NEWROUTE message.
-                     * While we invoke the signal, the platform cache might change and invalidate
-                     * the findings. Mitigate that (for the most part), by marking the entry as
-                     * dirty and only delete @obj_replace if it is still dirty afterwards.
-                     *
-                     * Yes, there is a tiny tiny chance for still getting it wrong. But in practice,
-                     * the signal handlers do not cause to call the platform again, so the cache
-                     * is not really changing. -- if they would, it would anyway be dangerous to overflow
-                     * the stack and it's not ensured that the processing of netlink messages is
-                     * reentrant (maybe it is).
-                     */
-                    entry_replace = nmp_cache_lookup_entry(cache, obj_replace);
-                    nm_assert(entry_replace && entry_replace->obj == obj_replace);
-                    nm_dedup_multi_entry_set_dirty(entry_replace, TRUE);
-                    only_dirty = TRUE;
-                }
-                cache_on_change(platform, cache_op, obj_old, obj_new);
-                nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new);
-            }
-
-            if (obj_replace) {
-                /* the RTM_NEWROUTE message indicates that another route was replaced.
-                 * Remove it now. */
-                cache_op = nmp_cache_remove(cache, obj_replace, TRUE, only_dirty, NULL);
-                if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
-                    nm_assert(cache_op == NMP_CACHE_OPS_REMOVED);
-                    cache_on_change(platform, cache_op, obj_replace, NULL);
-                    nm_platform_cache_update_emit_signal(platform, cache_op, obj_replace, NULL);
-                }
-            }
-
-            if (resync_required) {
-                /* we'd like to avoid such resyncs as they are expensive and we should only rely on the
-                 * netlink events. This needs investigation. */
-                _LOGT("schedule resync of routes after RTM_NEWROUTE");
-                delayed_action_schedule(platform,
-                                        delayed_action_refresh_from_needle_object(obj),
-                                        NULL);
-            }
-            break;
-        }
-
-        case RTM_DELADDR:
-        case RTM_DELLINK:
-        case RTM_DELQDISC:
-        case RTM_DELROUTE:
-        case RTM_DELRULE:
-        case RTM_DELTFILTER:
-            cache_op = nmp_cache_remove_netlink(cache, obj, &obj_old, &obj_new);
-            if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
-                cache_on_change(platform, cache_op, obj_old, obj_new);
-                nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new);
-            }
-            break;
-        default:
-            break;
-        }
-    }
-}
-
-/*****************************************************************************/
-
-static int
-do_add_link_with_lookup(NMPlatform *           platform,
-                        NMLinkType             link_type,
-                        const char *           name,
-                        struct nl_msg *        nlmsg,
-                        const NMPlatformLink **out_link)
-{
-    const NMPObject *       obj        = NULL;
-    WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char *          errmsg     = NULL;
-    int                     nle;
-    char                    s_buf[256];
-    NMPCache *              cache = nm_platform_get_cache(platform);
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        _LOGE("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
-              name,
-              nm_link_type_to_string(link_type),
-              nm_strerror(nle),
-              -nle);
-        NM_SET_OUT(out_link, NULL);
-        return nle;
-    }
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
-           "do-add-link[%s/%s]: %s",
-           name,
-           nm_link_type_to_string(link_type),
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
-
-    if (out_link) {
-        obj       = nmp_cache_lookup_link_full(cache, 0, name, FALSE, link_type, NULL, NULL);
-        *out_link = NMP_OBJECT_CAST_LINK(obj);
-    }
-
-    return wait_for_nl_response_to_nmerr(seq_result);
-}
-
-static int
-do_add_addrroute(NMPlatform *     platform,
-                 const NMPObject *obj_id,
-                 struct nl_msg *  nlmsg,
-                 gboolean         suppress_netlink_failure)
-{
-    WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char *          errmsg     = NULL;
-    int                     nle;
-    char                    s_buf[256];
-
-    nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
-                        NMP_OBJECT_TYPE_IP4_ADDRESS,
-                        NMP_OBJECT_TYPE_IP6_ADDRESS,
-                        NMP_OBJECT_TYPE_IP4_ROUTE,
-                        NMP_OBJECT_TYPE_IP6_ROUTE));
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        _LOGE("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
-              NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-              nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-              nm_strerror(nle),
-              -nle);
-        return -NME_PL_NETLINK;
-    }
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    _NMLOG((seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
-            || (suppress_netlink_failure && seq_result < 0))
-               ? LOGL_DEBUG
-               : LOGL_WARN,
-           "do-add-%s[%s]: %s",
-           NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-           nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
-
-    if (NMP_OBJECT_GET_TYPE(obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
-        /* In rare cases, the object is not yet ready as we received the ACK from
-         * kernel. Need to refetch.
-         *
-         * We want to safe the expensive refetch, thus we look first into the cache
-         * whether the object exists.
-         *
-         * rh#1484434 */
-        if (!nmp_cache_lookup_obj(nm_platform_get_cache(platform), obj_id))
-            do_request_one_type_by_needle_object(platform, obj_id);
-    }
-
-    return wait_for_nl_response_to_nmerr(seq_result);
-}
-
-static gboolean
-do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
-{
-    WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char *          errmsg     = NULL;
-    int                     nle;
-    char                    s_buf[256];
-    gboolean                success;
-    const char *            log_detail = "";
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        _LOGE("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
-              NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-              nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-              nm_strerror(nle),
-              -nle);
-        return FALSE;
-    }
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    success = TRUE;
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
-        /* ok */
-    } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT))
-        log_detail = ", meaning the object was already removed";
-    else if (NM_IN_SET(-((int) seq_result), ENXIO)
-             && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) {
-        /* On RHEL7 kernel, deleting a non existing address fails with ENXIO */
-        log_detail = ", meaning the address was already removed";
-    } else if (NM_IN_SET(-((int) seq_result), EADDRNOTAVAIL)
-               && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
-                            NMP_OBJECT_TYPE_IP4_ADDRESS,
-                            NMP_OBJECT_TYPE_IP6_ADDRESS))
-        log_detail = ", meaning the address was already removed";
-    else
-        success = FALSE;
-
-    _NMLOG(success ? LOGL_DEBUG : LOGL_WARN,
-           "do-delete-%s[%s]: %s%s",
-           NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-           nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)),
-           log_detail);
-
-    if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
-                  NMP_OBJECT_TYPE_IP6_ADDRESS,
-                  NMP_OBJECT_TYPE_QDISC,
-                  NMP_OBJECT_TYPE_TFILTER)) {
-        /* In rare cases, the object is still there after we receive the ACK from
-         * kernel. Need to refetch.
-         *
-         * We want to safe the expensive refetch, thus we look first into the cache
-         * whether the object exists.
-         *
-         * rh#1484434 */
-        if (nmp_cache_lookup_obj(nm_platform_get_cache(platform), obj_id))
-            do_request_one_type_by_needle_object(platform, obj_id);
-    }
-
-    return success;
-}
-
-static int
-do_change_link(NMPlatform *          platform,
-               ChangeLinkType        change_link_type,
-               int                   ifindex,
-               struct nl_msg *       nlmsg,
-               const ChangeLinkData *data)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    int                         nle;
-    WaitForNlResponseResult     seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char *              errmsg     = NULL;
-    char                        s_buf[256];
-    int                         result          = 0;
-    NMLogLevel                  log_level       = LOGL_DEBUG;
-    const char *                log_result      = "failure";
-    const char *                log_detail      = "";
-    gs_free char *              log_detail_free = NULL;
-    const NMPObject *           obj_cache;
-
-    if (!nm_platform_netns_push(platform, &netns)) {
-        log_level  = LOGL_ERR;
-        log_detail = ", failure to change network namespace";
-        goto out;
-    }
-
-retry:
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        log_level = LOGL_ERR;
-        log_detail_free =
-            g_strdup_printf(", failure sending netlink request: %s (%d)", nm_strerror(nle), -nle);
-        log_detail = log_detail_free;
-        goto out;
-    }
-
-    /* always refetch the link after changing it. There seems to be issues
-     * and we sometimes lack events. Nuke it from the orbit... */
-    delayed_action_schedule(platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER(ifindex));
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    if (NM_IN_SET(-((int) seq_result), EOPNOTSUPP) && nlmsg_hdr(nlmsg)->nlmsg_type == RTM_NEWLINK) {
-        nlmsg_hdr(nlmsg)->nlmsg_type = RTM_SETLINK;
-        goto retry;
-    }
-
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
-        log_result = "success";
-    } else if (NM_IN_SET(-((int) seq_result), EEXIST, EADDRINUSE)) {
-        /* */
-    } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT)) {
-        log_detail = ", firmware not found";
-        result     = -NME_PL_NO_FIRMWARE;
-    } else if (NM_IN_SET(-((int) seq_result), ERANGE)
-               && change_link_type == CHANGE_LINK_TYPE_SET_MTU) {
-        log_detail = ", setting MTU to requested size is not possible";
-        result     = -NME_PL_CANT_SET_MTU;
-    } else if (NM_IN_SET(-((int) seq_result), ENFILE)
-               && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS
-               && (obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex))
-               && obj_cache->link.l_address.len == data->set_address.length
-               && memcmp(obj_cache->link.l_address.data,
-                         data->set_address.address,
-                         data->set_address.length)
-                      == 0) {
-        /* workaround ENFILE which may be wrongly returned (bgo #770456).
-         * If the MAC address is as expected, assume success? */
-        log_result = "success";
-        log_detail = " (assume success changing address)";
-        result     = 0;
-    } else if (NM_IN_SET(-((int) seq_result), ENODEV)) {
-        log_level = LOGL_DEBUG;
-        result    = -NME_PL_NOT_FOUND;
-    } else if (-((int) seq_result) == EAFNOSUPPORT) {
-        log_level = LOGL_DEBUG;
-        result    = -NME_PL_OPNOTSUPP;
-    } else {
-        log_level = LOGL_WARN;
-        result    = -NME_UNSPEC;
-    }
-
-out:
-    _NMLOG(log_level,
-           "do-change-link[%d]: %s changing link: %s%s",
-           ifindex,
-           log_result,
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)),
-           log_detail);
-    return result;
-}
-
-static int
-link_add(NMPlatform *           platform,
-         NMLinkType             type,
-         const char *           name,
-         int                    parent,
-         const void *           address,
-         size_t                 address_len,
-         guint32                mtu,
-         gconstpointer          extra_data,
-         const NMPlatformLink **out_link)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-    if (type == NM_LINK_TYPE_BOND) {
-        /* When the kernel loads the bond module, either via explicit modprobe
-         * or automatically in response to creating a bond master, it will also
-         * create a 'bond0' interface.  Since the bond we're about to create may
-         * or may not be named 'bond0' prevent potential confusion about a bond
-         * that the user didn't want by telling the bonding module not to create
-         * bond0 automatically.
-         */
-        if (!g_file_test("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS))
-            (void) nm_utils_modprobe(NULL, TRUE, "bonding", "max_bonds=0", NULL);
-    }
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL, 0, name);
-    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 (mtu)
-        NLA_PUT_U32(nlmsg, IFLA_MTU, mtu);
-
-    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);
-nla_put_failure:
-    g_return_val_if_reached(-NME_BUG);
-}
-
-static gboolean
-link_delete(NMPlatform *platform, int ifindex)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    NMPObject                    obj_id;
-    const NMPObject *            obj;
-
-    obj = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex);
-    if (!obj || !obj->_link.netlink.is_in_netlink)
-        return FALSE;
-
-    nlmsg = _nl_msg_new_link(RTM_DELLINK, 0, ifindex, NULL);
-
-    nmp_object_stackinit_id_link(&obj_id, ifindex);
-    return do_delete_object(platform, &obj_id, nlmsg);
-}
-
-static gboolean
-link_refresh(NMPlatform *platform, int ifindex)
-{
-    do_request_link(platform, ifindex, NULL);
-    return !!nm_platform_link_get_obj(platform, ifindex, TRUE);
-}
-
-static gboolean
-link_set_netns(NMPlatform *platform, int ifindex, int netns_fd)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg)
-        return FALSE;
-
-    NLA_PUT(nlmsg, IFLA_NET_NS_FD, 4, &netns_fd);
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static int
-link_change_flags(NMPlatform *platform, int ifindex, unsigned flags_mask, unsigned flags_set)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    char                         s_flags[100];
-
-    _LOGD("link: change %d: flags: set 0x%x/0x%x ([%s] / [%s])",
-          ifindex,
-          flags_set,
-          flags_mask,
-          nm_platform_link_flags2str(flags_set, s_flags, sizeof(s_flags)),
-          nm_platform_link_flags2str(flags_mask, NULL, 0));
-
-    nlmsg = _nl_msg_new_link_full(RTM_NEWLINK, 0, ifindex, NULL, AF_UNSPEC, flags_mask, flags_set);
-    if (!nlmsg)
-        return -NME_UNSPEC;
-    return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
-}
-
-static gboolean
-link_set_up(NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
-{
-    int r;
-
-    r = link_change_flags(platform, ifindex, IFF_UP, IFF_UP);
-    NM_SET_OUT(out_no_firmware, (r == -NME_PL_NO_FIRMWARE));
-    return r >= 0;
-}
-
-static gboolean
-link_set_down(NMPlatform *platform, int ifindex)
-{
-    return (link_change_flags(platform, ifindex, IFF_UP, 0) >= 0);
-}
-
-static gboolean
-link_set_arp(NMPlatform *platform, int ifindex)
-{
-    return (link_change_flags(platform, ifindex, IFF_NOARP, 0) >= 0);
-}
-
-static gboolean
-link_set_noarp(NMPlatform *platform, int ifindex)
-{
-    return (link_change_flags(platform, ifindex, IFF_NOARP, IFF_NOARP) >= 0);
-}
-
-static int
-link_set_user_ipv6ll_enabled(NMPlatform *platform, int ifindex, gboolean enabled)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64;
-
-    _LOGD("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
-          ifindex,
-          nm_platform_link_inet6_addrgenmode2str(mode, NULL, 0));
-
-    if (!nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
-        _LOGD("link: change %d: user-ipv6ll: not supported", ifindex);
-        return -NME_PL_OPNOTSUPP;
-    }
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg || !_nl_msg_new_link_set_afspec(nlmsg, mode, NULL))
-        g_return_val_if_reached(-NME_BUG);
-
-    return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
-}
-
-static gboolean
-link_set_token(NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    char                         sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-    _LOGD("link: change %d: token: set IPv6 address generation token to %s",
-          ifindex,
-          nm_utils_inet6_interface_identifier_to_token(iid, sbuf));
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg || !_nl_msg_new_link_set_afspec(nlmsg, -1, &iid))
-        g_return_val_if_reached(FALSE);
-
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-}
-
-static gboolean
-link_supports_carrier_detect(NMPlatform *platform, int ifindex)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return FALSE;
-
-    /* We use netlink for the actual carrier detection, but netlink can't tell
-     * us whether the device actually supports carrier detection in the first
-     * place. We assume any device that does implements one of these two APIs.
-     */
-    return nmp_utils_ethtool_supports_carrier_detect(ifindex)
-           || nmp_utils_mii_supports_carrier_detect(ifindex);
-}
-
-static gboolean
-link_supports_vlans(NMPlatform *platform, int ifindex)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    const NMPObject *           obj;
-
-    obj = nm_platform_link_get_obj(platform, ifindex, TRUE);
-
-    /* Only ARPHRD_ETHER links can possibly support VLANs. */
-    if (!obj || obj->link.arptype != ARPHRD_ETHER)
-        return FALSE;
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return FALSE;
-
-    return nmp_utils_ethtool_supports_vlans(ifindex);
-}
-
-static gboolean
-link_supports_sriov(NMPlatform *platform, int ifindex)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    nm_auto_close int           dirfd = -1;
-    char                        ifname[IFNAMSIZ];
-    int                         num = -1;
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return FALSE;
-
-    dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname);
-    if (dirfd < 0)
-        return FALSE;
-
-    num =
-        nm_platform_sysctl_get_int32(platform,
-                                     NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"),
-                                     -1);
-
-    return num != -1;
-}
-
-static int
-link_set_address(NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    const ChangeLinkData         d     = {
-        .set_address =
-            {
-                .address = address,
-                .length  = length,
-            },
-    };
-
-    if (!address || !length)
-        g_return_val_if_reached(-NME_BUG);
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg)
-        g_return_val_if_reached(-NME_BUG);
-
-    NLA_PUT(nlmsg, IFLA_ADDRESS, length, address);
-
-    return do_change_link(platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d);
-nla_put_failure:
-    g_return_val_if_reached(-NME_BUG);
-}
-
-static int
-link_set_name(NMPlatform *platform, int ifindex, const char *name)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg)
-        g_return_val_if_reached(-NME_BUG);
-
-    NLA_PUT(nlmsg, IFLA_IFNAME, strlen(name) + 1, name);
-
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-link_get_permanent_address(NMPlatform *platform, int ifindex, guint8 *buf, size_t *length)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return FALSE;
-
-    return nmp_utils_ethtool_get_permanent_address(ifindex, buf, length);
-}
-
-static int
-link_set_mtu(NMPlatform *platform, int ifindex, guint32 mtu)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg)
-        return FALSE;
-
-    NLA_PUT_U32(nlmsg, IFLA_MTU, mtu);
-
-    return do_change_link(platform, CHANGE_LINK_TYPE_SET_MTU, ifindex, nlmsg, NULL);
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static void
-sriov_idle_cb(gpointer user_data, GCancellable *cancellable)
-{
-    gs_unref_object NMPlatform *platform  = NULL;
-    gs_free_error GError *cancelled_error = NULL;
-    gs_free_error GError *  error         = NULL;
-    NMPlatformAsyncCallback callback;
-    gpointer                callback_data;
-
-    g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error);
-    nm_utils_user_data_unpack(user_data, &platform, &error, &callback, &callback_data);
-    callback(cancelled_error ?: error, callback_data);
-}
-
-static void
-link_set_sriov_params_async(NMPlatform *            platform,
-                            int                     ifindex,
-                            guint                   num_vfs,
-                            NMOptionBool            autoprobe,
-                            NMPlatformAsyncCallback callback,
-                            gpointer                data,
-                            GCancellable *          cancellable)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    gs_free_error GError *error       = NULL;
-    nm_auto_close int     dirfd       = -1;
-    int                   current_autoprobe;
-    guint                 i, total;
-    gint64                current_num;
-    char                  ifname[IFNAMSIZ];
-    gpointer              packed;
-    const char *          values[3];
-    char                  buf[64];
-
-    g_return_if_fail(callback || !data);
-    g_return_if_fail(cancellable);
-
-    if (!nm_platform_netns_push(platform, &netns)) {
-        g_set_error_literal(&error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_UNKNOWN,
-                            "couldn't change namespace");
-        goto out_idle;
-    }
-
-    dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname);
-    if (!dirfd) {
-        g_set_error_literal(&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "couldn't open netdir");
-        goto out_idle;
-    }
-
-    total = nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_totalvfs"),
-        10,
-        0,
-        G_MAXUINT,
-        0);
-    if (!errno && num_vfs > total) {
-        _LOGW("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
-        num_vfs = total;
-    }
-
-    /*
-     * Take special care when setting new values:
-     *  - don't touch anything if the right values are already set
-     *  - to change the number of VFs or autoprobe we need to destroy existing VFs
-     *  - the autoprobe setting is irrelevant when numvfs is zero
-     */
-    current_num = nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"),
-        10,
-        0,
-        G_MAXUINT,
-        -1);
-    current_autoprobe = nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"),
-        10,
-        0,
-        1,
-        -1);
-
-    if (current_autoprobe == -1 && errno == ENOENT) {
-        /* older kernel versions don't have this sysctl. Assume the value is
-         * "1". */
-        current_autoprobe = 1;
-    }
-
-    if (current_num == num_vfs
-        && (autoprobe == NM_OPTION_BOOL_DEFAULT || current_autoprobe == autoprobe))
-        goto out_idle;
-
-    if (NM_IN_SET(autoprobe, NM_OPTION_BOOL_TRUE, NM_OPTION_BOOL_FALSE)
-        && current_autoprobe != autoprobe
-        && !nm_platform_sysctl_set(
-            NM_PLATFORM_GET,
-            NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"),
-            nm_sprintf_buf(buf, "%d", (int) autoprobe))) {
-        g_set_error(&error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "couldn't set SR-IOV drivers-autoprobe to %d: %s",
-                    (int) autoprobe,
-                    nm_strerror_native(errno));
-        goto out_idle;
-    }
-
-    if (current_num == 0 && num_vfs == 0)
-        goto out_idle;
-
-    i = 0;
-    if (current_num != 0)
-        values[i++] = "0";
-    if (num_vfs != 0)
-        values[i++] = nm_sprintf_bufa(32, "%u", num_vfs);
-    values[i++] = NULL;
-
-    sysctl_set_async(platform,
-                     NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"),
-                     values,
-                     callback,
-                     data,
-                     cancellable);
-    return;
-
-out_idle:
-    if (callback) {
-        packed = nm_utils_user_data_pack(g_object_ref(platform),
-                                         g_steal_pointer(&error),
-                                         callback,
-                                         data);
-        nm_utils_invoke_on_idle(cancellable, sriov_idle_cb, packed);
-    }
-}
-
-static gboolean
-link_set_sriov_vfs(NMPlatform *platform, int ifindex, const NMPlatformVF *const *vfs)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    struct nlattr *              list, *info, *vlan_list;
-    guint                        i;
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg)
-        g_return_val_if_reached(-NME_BUG);
-
-    if (!(list = nla_nest_start(nlmsg, IFLA_VFINFO_LIST)))
-        goto nla_put_failure;
-
-    for (i = 0; vfs[i]; i++) {
-        const NMPlatformVF *vf = vfs[i];
-
-        if (!(info = nla_nest_start(nlmsg, IFLA_VF_INFO)))
-            goto nla_put_failure;
-
-        if (vf->spoofchk >= 0) {
-            struct _ifla_vf_setting ivs = {0};
-
-            ivs.vf      = vf->index;
-            ivs.setting = vf->spoofchk;
-            NLA_PUT(nlmsg, IFLA_VF_SPOOFCHK, sizeof(ivs), &ivs);
-        }
-
-        if (vf->trust >= 0) {
-            struct _ifla_vf_setting ivs = {0};
-
-            ivs.vf      = vf->index;
-            ivs.setting = vf->trust;
-            NLA_PUT(nlmsg, IFLA_VF_TRUST, sizeof(ivs), &ivs);
-        }
-
-        if (vf->mac.len) {
-            struct ifla_vf_mac ivm = {0};
-
-            ivm.vf = vf->index;
-            memcpy(ivm.mac, vf->mac.data, vf->mac.len);
-            NLA_PUT(nlmsg, IFLA_VF_MAC, sizeof(ivm), &ivm);
-        }
-
-        if (vf->min_tx_rate || vf->max_tx_rate) {
-            struct _ifla_vf_rate ivr = {0};
-
-            ivr.vf          = vf->index;
-            ivr.min_tx_rate = vf->min_tx_rate;
-            ivr.max_tx_rate = vf->max_tx_rate;
-            NLA_PUT(nlmsg, IFLA_VF_RATE, sizeof(ivr), &ivr);
-        }
-
-        /* Kernel only supports one VLAN per VF now. If this
-         * changes in the future, we need to figure out how to
-         * clear existing VLANs and set new ones in one message
-         * with the new API.*/
-        if (vf->num_vlans > 1) {
-            _LOGW("multiple VLANs per VF are not supported at the moment");
-            return FALSE;
-        } else {
-            struct _ifla_vf_vlan_info ivvi = {0};
-
-            if (!(vlan_list = nla_nest_start(nlmsg, IFLA_VF_VLAN_LIST)))
-                goto nla_put_failure;
-
-            ivvi.vf = vf->index;
-            if (vf->num_vlans == 1) {
-                ivvi.vlan       = vf->vlans[0].id;
-                ivvi.qos        = vf->vlans[0].qos;
-                ivvi.vlan_proto = htons(vf->vlans[0].proto_ad ? ETH_P_8021AD : ETH_P_8021Q);
-            } else {
-                /* Clear existing VLAN */
-                ivvi.vlan       = 0;
-                ivvi.qos        = 0;
-                ivvi.vlan_proto = htons(ETH_P_8021Q);
-            }
-
-            NLA_PUT(nlmsg, IFLA_VF_VLAN_INFO, sizeof(ivvi), &ivvi);
-            nla_nest_end(nlmsg, vlan_list);
-        }
-        nla_nest_end(nlmsg, info);
-    }
-    nla_nest_end(nlmsg, list);
-
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-link_set_bridge_vlans(NMPlatform *                       platform,
-                      int                                ifindex,
-                      gboolean                           on_master,
-                      const NMPlatformBridgeVlan *const *vlans)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    struct nlattr *              list;
-    struct bridge_vlan_info      vinfo = {};
-    guint                        i;
-
-    nlmsg =
-        _nl_msg_new_link_full(vlans ? RTM_SETLINK : RTM_DELLINK, 0, ifindex, NULL, AF_BRIDGE, 0, 0);
-    if (!nlmsg)
-        g_return_val_if_reached(-NME_BUG);
-
-    if (!(list = nla_nest_start(nlmsg, IFLA_AF_SPEC)))
-        goto nla_put_failure;
-
-    NLA_PUT_U16(nlmsg, IFLA_BRIDGE_FLAGS, on_master ? BRIDGE_FLAGS_MASTER : BRIDGE_FLAGS_SELF);
-
-    if (vlans) {
-        /* Add VLANs */
-        for (i = 0; vlans[i]; i++) {
-            const NMPlatformBridgeVlan *vlan     = vlans[i];
-            gboolean                    is_range = vlan->vid_start != vlan->vid_end;
-
-            vinfo.vid   = vlan->vid_start;
-            vinfo.flags = is_range ? BRIDGE_VLAN_INFO_RANGE_BEGIN : 0;
-
-            if (vlan->untagged)
-                vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
-            if (vlan->pvid)
-                vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
-
-            NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo);
-
-            if (is_range) {
-                vinfo.vid   = vlan->vid_end;
-                vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
-                NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo);
-            }
-        }
-    } else {
-        /* Flush existing VLANs */
-        vinfo.vid   = 1;
-        vinfo.flags = BRIDGE_VLAN_INFO_RANGE_BEGIN;
-        NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo);
-
-        vinfo.vid   = 4094;
-        vinfo.flags = BRIDGE_VLAN_INFO_RANGE_END;
-        NLA_PUT(nlmsg, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo);
-    }
-
-    nla_nest_end(nlmsg, list);
-
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static char *
-link_get_physical_port_id(NMPlatform *platform, int ifindex)
-{
-    nm_auto_close int dirfd = -1;
-    char              ifname_verified[IFNAMSIZ];
-
-    dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified);
-    if (dirfd < 0)
-        return NULL;
-    return sysctl_get(platform, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "phys_port_id"));
-}
-
-static guint
-link_get_dev_id(NMPlatform *platform, int ifindex)
-{
-    nm_auto_close int dirfd = -1;
-    char              ifname_verified[IFNAMSIZ];
-
-    dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified);
-    if (dirfd < 0)
-        return 0;
-    return nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "dev_id"),
-        16,
-        0,
-        G_MAXUINT16,
-        0);
-}
-
-static gboolean
-link_tun_add(NMPlatform *            platform,
-             const char *            name,
-             const NMPlatformLnkTun *props,
-             const NMPlatformLink ** out_link,
-             int *                   out_fd)
-{
-    const NMPObject * obj;
-    struct ifreq      ifr = {};
-    nm_auto_close int fd  = -1;
-
-    nm_assert(NM_IN_SET(props->type, IFF_TAP, IFF_TUN));
-    nm_assert(props->persist || out_fd);
-
-    fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
-    if (fd < 0)
-        return FALSE;
-
-    nm_utils_ifname_cpy(ifr.ifr_name, name);
-    ifr.ifr_flags = ((short) props->type) | ((short) IFF_TUN_EXCL)
-                    | (!props->pi ? (short) IFF_NO_PI : (short) 0)
-                    | (props->vnet_hdr ? (short) IFF_VNET_HDR : (short) 0)
-                    | (props->multi_queue ? (short) NM_IFF_MULTI_QUEUE : (short) 0);
-    if (ioctl(fd, TUNSETIFF, &ifr))
-        return FALSE;
-
-    if (props->owner_valid) {
-        if (ioctl(fd, TUNSETOWNER, (uid_t) props->owner))
-            return FALSE;
-    }
-
-    if (props->group_valid) {
-        if (ioctl(fd, TUNSETGROUP, (gid_t) props->group))
-            return FALSE;
-    }
-
-    if (props->persist) {
-        if (ioctl(fd, TUNSETPERSIST, 1))
-            return FALSE;
-    }
-
-    do_request_link(platform, 0, name);
-    obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform),
-                                     0,
-                                     name,
-                                     FALSE,
-                                     NM_LINK_TYPE_TUN,
-                                     NULL,
-                                     NULL);
-
-    if (!obj)
-        return FALSE;
-
-    NM_SET_OUT(out_link, &obj->link);
-    NM_SET_OUT(out_fd, nm_steal_fd(&fd));
-    return TRUE;
-}
-
-static void
-_vlan_change_vlan_qos_mapping_create(gboolean                is_ingress_map,
-                                     gboolean                reset_all,
-                                     const NMVlanQosMapping *current_map,
-                                     guint                   current_n_map,
-                                     const NMVlanQosMapping *set_map,
-                                     guint                   set_n_map,
-                                     NMVlanQosMapping **     out_map,
-                                     guint *                 out_n_map)
-{
-    NMVlanQosMapping *map;
-    guint             i, j, len;
-    const guint       INGRESS_RANGE_LEN = 8;
-
-    nm_assert(out_map && !*out_map);
-    nm_assert(out_n_map && !*out_n_map);
-
-    if (!reset_all)
-        current_n_map = 0;
-    else if (is_ingress_map)
-        current_n_map = INGRESS_RANGE_LEN;
-
-    len = current_n_map + set_n_map;
-
-    if (len == 0)
-        return;
-
-    map = g_new(NMVlanQosMapping, len);
-
-    if (current_n_map) {
-        if (is_ingress_map) {
-            /* For the ingress-map, there are only 8 entries (0 to 7).
-             * When the user requests to reset all entries, we don't actually
-             * need the cached entries, we can just explicitly clear all possible
-             * ones.
-             *
-             * That makes only a real difference in case our cache is out-of-date.
-             *
-             * For the egress map we cannot do that, because there are far too
-             * many. There we can only clear the entries that we know about. */
-            for (i = 0; i < INGRESS_RANGE_LEN; i++) {
-                map[i].from = i;
-                map[i].to   = 0;
-            }
-        } else {
-            for (i = 0; i < current_n_map; i++) {
-                map[i].from = current_map[i].from;
-                map[i].to   = 0;
-            }
-        }
-    }
-    if (set_n_map)
-        memcpy(&map[current_n_map], set_map, sizeof(*set_map) * set_n_map);
-
-    g_qsort_with_data(map, len, sizeof(*map), _vlan_qos_mapping_cmp_from, NULL);
-
-    for (i = 0, j = 0; i < len; i++) {
-        if ((is_ingress_map && !VLAN_XGRESS_PRIO_VALID(map[i].from))
-            || (!is_ingress_map && !VLAN_XGRESS_PRIO_VALID(map[i].to)))
-            continue;
-        if (j > 0 && map[j - 1].from == map[i].from)
-            map[j - 1] = map[i];
-        else
-            map[j++] = map[i];
-    }
-
-    *out_map   = map;
-    *out_n_map = j;
-}
-
-static gboolean
-link_vlan_change(NMPlatform *            platform,
-                 int                     ifindex,
-                 NMVlanFlags             flags_mask,
-                 NMVlanFlags             flags_set,
-                 gboolean                ingress_reset_all,
-                 const NMVlanQosMapping *ingress_map,
-                 gsize                   n_ingress_map,
-                 gboolean                egress_reset_all,
-                 const NMVlanQosMapping *egress_map,
-                 gsize                   n_egress_map)
-{
-    const NMPObject *            obj_cache;
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    const NMPObjectLnkVlan *     lnk;
-    guint                        new_n_ingress_map = 0;
-    guint                        new_n_egress_map  = 0;
-    gs_free NMVlanQosMapping *new_ingress_map      = NULL;
-    gs_free NMVlanQosMapping *new_egress_map       = NULL;
-
-    obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex);
-    if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) {
-        _LOGD("link: change %d: %s: link does not exist", ifindex, "vlan");
-        return FALSE;
-    }
-
-    lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL;
-
-    flags_set &= flags_mask;
-
-    _vlan_change_vlan_qos_mapping_create(TRUE,
-                                         ingress_reset_all,
-                                         lnk ? lnk->ingress_qos_map : NULL,
-                                         lnk ? lnk->n_ingress_qos_map : 0,
-                                         ingress_map,
-                                         n_ingress_map,
-                                         &new_ingress_map,
-                                         &new_n_ingress_map);
-
-    _vlan_change_vlan_qos_mapping_create(FALSE,
-                                         egress_reset_all,
-                                         lnk ? lnk->egress_qos_map : NULL,
-                                         lnk ? lnk->n_egress_qos_map : 0,
-                                         egress_map,
-                                         n_egress_map,
-                                         &new_egress_map,
-                                         &new_n_egress_map);
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg
-        || !_nl_msg_new_link_set_linkinfo_vlan(nlmsg,
-                                               -1,
-                                               flags_mask,
-                                               flags_set,
-                                               new_ingress_map,
-                                               new_n_ingress_map,
-                                               new_egress_map,
-                                               new_n_egress_map))
-        g_return_val_if_reached(FALSE);
-
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-}
-
-static gboolean
-link_enslave(NMPlatform *platform, int master, int slave)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg   = NULL;
-    int                          ifindex = slave;
-
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
-    if (!nlmsg)
-        return FALSE;
-
-    NLA_PUT_U32(nlmsg, IFLA_MASTER, master);
-
-    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-link_release(NMPlatform *platform, int master, int slave)
-{
-    return link_enslave(platform, 0, slave);
-}
-
-/*****************************************************************************/
-
-static gboolean
-_infiniband_partition_action(NMPlatform *           platform,
-                             InfinibandAction       action,
-                             int                    parent,
-                             int                    p_key,
-                             const NMPlatformLink **out_link)
-{
-    nm_auto_close int dirfd = -1;
-    char              ifname_parent[IFNAMSIZ];
-    const NMPObject * obj;
-    char              id[20];
-    char              name[IFNAMSIZ];
-    gboolean          success;
-
-    nm_assert(NM_IN_SET(action, INFINIBAND_ACTION_CREATE_CHILD, INFINIBAND_ACTION_DELETE_CHILD));
-    nm_assert(p_key > 0 && p_key <= 0xffff && p_key != 0x8000);
-
-    dirfd = nm_platform_sysctl_open_netdir(platform, parent, ifname_parent);
-    if (dirfd < 0) {
-        errno = ENOENT;
-        return FALSE;
-    }
-
-    nm_sprintf_buf(id, "0x%04x", p_key);
-    if (action == INFINIBAND_ACTION_CREATE_CHILD)
-        success =
-            nm_platform_sysctl_set(platform,
-                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "create_child"),
-                                   id);
-    else
-        success =
-            nm_platform_sysctl_set(platform,
-                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "delete_child"),
-                                   id);
-
-    if (!success) {
-        if (action == INFINIBAND_ACTION_DELETE_CHILD && errno == ENODEV)
-            return TRUE;
-        return FALSE;
-    }
-
-    nm_utils_new_infiniband_name(name, ifname_parent, p_key);
-    do_request_link(platform, 0, name);
-
-    if (action == INFINIBAND_ACTION_DELETE_CHILD)
-        return TRUE;
-
-    obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform),
-                                     0,
-                                     name,
-                                     FALSE,
-                                     NM_LINK_TYPE_INFINIBAND,
-                                     NULL,
-                                     NULL);
-    if (out_link)
-        *out_link = obj ? &obj->link : NULL;
-    return !!obj;
-}
-
-static gboolean
-infiniband_partition_add(NMPlatform *           platform,
-                         int                    parent,
-                         int                    p_key,
-                         const NMPlatformLink **out_link)
-{
-    return _infiniband_partition_action(platform,
-                                        INFINIBAND_ACTION_CREATE_CHILD,
-                                        parent,
-                                        p_key,
-                                        out_link);
-}
-
-static gboolean
-infiniband_partition_delete(NMPlatform *platform, int parent, int p_key)
-{
-    return _infiniband_partition_action(platform,
-                                        INFINIBAND_ACTION_DELETE_CHILD,
-                                        parent,
-                                        p_key,
-                                        NULL);
-}
-
-/*****************************************************************************/
-
-static GObject *
-get_ext_data(NMPlatform *platform, int ifindex)
-{
-    const NMPObject *obj;
-
-    obj = nmp_cache_lookup_link(nm_platform_get_cache(platform), ifindex);
-    if (!obj)
-        return NULL;
-
-    return obj->_link.ext_data;
-}
-
-/*****************************************************************************/
-
-#define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \
-    nm_auto_pop_netns NMPNetns *netns = NULL;                          \
-    NMWifiUtils *               wifi_data;                             \
-    if (!nm_platform_netns_push(platform, &netns))                     \
-        return retval;                                                 \
-    wifi_data = NM_WIFI_UTILS(get_ext_data(platform, ifindex));        \
-    if (!wifi_data)                                                    \
-        return retval;
-
-static gboolean
-wifi_get_capabilities(NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    if (caps)
-        *caps = nm_wifi_utils_get_caps(wifi_data);
-    return TRUE;
-}
-
-static guint32
-wifi_get_frequency(NMPlatform *platform, int ifindex)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0);
-    return nm_wifi_utils_get_freq(wifi_data);
-}
-
-static gboolean
-wifi_get_station(NMPlatform * platform,
-                 int          ifindex,
-                 NMEtherAddr *out_bssid,
-                 int *        out_quality,
-                 guint32 *    out_rate)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_get_station(wifi_data, out_bssid, out_quality, out_rate);
-}
-
-static NM80211Mode
-wifi_get_mode(NMPlatform *platform, int ifindex)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, NM_802_11_MODE_UNKNOWN);
-    return nm_wifi_utils_get_mode(wifi_data);
-}
-
-static void
-wifi_set_mode(NMPlatform *platform, int ifindex, NM80211Mode mode)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, );
-    nm_wifi_utils_set_mode(wifi_data, mode);
-}
-
-static void
-wifi_set_powersave(NMPlatform *platform, int ifindex, guint32 powersave)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, );
-    nm_wifi_utils_set_powersave(wifi_data, powersave);
-}
-
-static guint32
-wifi_find_frequency(NMPlatform *platform, int ifindex, const guint32 *freqs)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0);
-    return nm_wifi_utils_find_freq(wifi_data, freqs);
-}
-
-static void
-wifi_indicate_addressing_running(NMPlatform *platform, int ifindex, gboolean running)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, );
-    nm_wifi_utils_indicate_addressing_running(wifi_data, running);
-}
-
-static NMSettingWirelessWakeOnWLan
-wifi_get_wake_on_wlan(NMPlatform *platform, int ifindex)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_get_wake_on_wlan(wifi_data);
-}
-
-static gboolean
-wifi_set_wake_on_wlan(NMPlatform *platform, int ifindex, NMSettingWirelessWakeOnWLan wowl)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_set_wake_on_wlan(wifi_data, wowl);
-}
-
-/*****************************************************************************/
-
-static gboolean
-link_can_assume(NMPlatform *platform, int ifindex)
-{
-    NMPLookup        lookup;
-    const NMPObject *link, *o;
-    NMDedupMultiIter iter;
-    NMPCache *       cache = nm_platform_get_cache(platform);
-
-    if (ifindex <= 0)
-        return FALSE;
-
-    link = nm_platform_link_get_obj(platform, ifindex, TRUE);
-    if (!link)
-        return FALSE;
-
-    if (!NM_FLAGS_HAS(link->link.n_ifi_flags, IFF_UP))
-        return FALSE;
-
-    if (link->link.master > 0)
-        return TRUE;
-
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex);
-    if (nmp_cache_lookup(cache, &lookup))
-        return TRUE;
-
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex);
-    nmp_cache_iter_for_each (&iter, nmp_cache_lookup(cache, &lookup), &o) {
-        nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP6_ADDRESS);
-        if (!IN6_IS_ADDR_LINKLOCAL(&o->ip6_address.address))
-            return TRUE;
-    }
-    return FALSE;
-}
-
-/*****************************************************************************/
-
-static guint32
-mesh_get_channel(NMPlatform *platform, int ifindex)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, 0);
-    return nm_wifi_utils_get_mesh_channel(wifi_data);
-}
-
-static gboolean
-mesh_set_channel(NMPlatform *platform, int ifindex, guint32 channel)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_set_mesh_channel(wifi_data, channel);
-}
-
-static gboolean
-mesh_set_ssid(NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_set_mesh_ssid(wifi_data, ssid, len);
-}
-
-/*****************************************************************************/
-
-#define WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, retval)             \
-    NMWpanUtils *wpan_data = NM_WPAN_UTILS(get_ext_data(platform, ifindex)); \
-    if (!wpan_data)                                                          \
-        return retval;
-
-static guint16
-wpan_get_pan_id(NMPlatform *platform, int ifindex)
-{
-    WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, G_MAXINT16);
-    return nm_wpan_utils_get_pan_id(wpan_data);
-}
-
-static gboolean
-wpan_set_pan_id(NMPlatform *platform, int ifindex, guint16 pan_id)
-{
-    WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE);
-    return nm_wpan_utils_set_pan_id(wpan_data, pan_id);
-}
-
-static guint16
-wpan_get_short_addr(NMPlatform *platform, int ifindex)
-{
-    WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, G_MAXINT16);
-    return nm_wpan_utils_get_short_addr(wpan_data);
-}
-
-static gboolean
-wpan_set_short_addr(NMPlatform *platform, int ifindex, guint16 short_addr)
-{
-    WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE);
-    return nm_wpan_utils_set_short_addr(wpan_data, short_addr);
-}
-
-static gboolean
-wpan_set_channel(NMPlatform *platform, int ifindex, guint8 page, guint8 channel)
-{
-    WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, FALSE);
-    return nm_wpan_utils_set_channel(wpan_data, page, channel);
-}
-
-/*****************************************************************************/
-
-static gboolean
-link_get_wake_on_lan(NMPlatform *platform, int ifindex)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMLinkType                  type  = nm_platform_link_get_type(platform, ifindex);
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return FALSE;
-
-    if (type == NM_LINK_TYPE_ETHERNET)
-        return nmp_utils_ethtool_get_wake_on_lan(ifindex);
-    else if (type == NM_LINK_TYPE_WIFI) {
-        NMWifiUtils *wifi_data = NM_WIFI_UTILS(get_ext_data(platform, ifindex));
-
-        if (!wifi_data)
-            return FALSE;
-
-        return !NM_IN_SET(nm_wifi_utils_get_wake_on_wlan(wifi_data),
-                          NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE,
-                          NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE);
-
-    } else
-        return FALSE;
-}
-
-static gboolean
-link_get_driver_info(NMPlatform *platform,
-                     int         ifindex,
-                     char **     out_driver_name,
-                     char **     out_driver_version,
-                     char **     out_fw_version)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMPUtilsEthtoolDriverInfo   driver_info;
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return FALSE;
-
-    if (!nmp_utils_ethtool_get_driver_info(ifindex, &driver_info))
-        return FALSE;
-    NM_SET_OUT(out_driver_name, g_strdup(driver_info.driver));
-    NM_SET_OUT(out_driver_version, g_strdup(driver_info.version));
-    NM_SET_OUT(out_fw_version, g_strdup(driver_info.fw_version));
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-static gboolean
-ip4_address_add(NMPlatform *platform,
-                int         ifindex,
-                in_addr_t   addr,
-                guint8      plen,
-                in_addr_t   peer_addr,
-                in_addr_t   broadcast_address,
-                guint32     lifetime,
-                guint32     preferred,
-                guint32     flags,
-                const char *label)
-{
-    NMPObject                    obj_id;
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-    nlmsg = _nl_msg_new_address(RTM_NEWADDR,
-                                NLM_F_CREATE | NLM_F_REPLACE,
-                                AF_INET,
-                                ifindex,
-                                &addr,
-                                plen,
-                                &peer_addr,
-                                flags,
-                                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);
-    return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0);
-}
-
-static gboolean
-ip6_address_add(NMPlatform *    platform,
-                int             ifindex,
-                struct in6_addr addr,
-                guint8          plen,
-                struct in6_addr peer_addr,
-                guint32         lifetime,
-                guint32         preferred,
-                guint32         flags)
-{
-    NMPObject                    obj_id;
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-    nlmsg = _nl_msg_new_address(RTM_NEWADDR,
-                                NLM_F_CREATE | NLM_F_REPLACE,
-                                AF_INET6,
-                                ifindex,
-                                &addr,
-                                plen,
-                                IN6_IS_ADDR_UNSPECIFIED(&peer_addr) ? NULL : &peer_addr,
-                                flags,
-                                RT_SCOPE_UNIVERSE,
-                                lifetime,
-                                preferred,
-                                0,
-                                NULL);
-
-    nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr);
-    return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0);
-}
-
-static gboolean
-ip4_address_delete(NMPlatform *platform,
-                   int         ifindex,
-                   in_addr_t   addr,
-                   guint8      plen,
-                   in_addr_t   peer_address)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    NMPObject                    obj_id;
-
-    nlmsg = _nl_msg_new_address(RTM_DELADDR,
-                                0,
-                                AF_INET,
-                                ifindex,
-                                &addr,
-                                plen,
-                                &peer_address,
-                                0,
-                                RT_SCOPE_NOWHERE,
-                                NM_PLATFORM_LIFETIME_PERMANENT,
-                                NM_PLATFORM_LIFETIME_PERMANENT,
-                                0,
-                                NULL);
-    if (!nlmsg)
-        g_return_val_if_reached(FALSE);
-
-    nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, addr, plen, peer_address);
-    return do_delete_object(platform, &obj_id, nlmsg);
-}
-
-static gboolean
-ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    NMPObject                    obj_id;
-
-    nlmsg = _nl_msg_new_address(RTM_DELADDR,
-                                0,
-                                AF_INET6,
-                                ifindex,
-                                &addr,
-                                plen,
-                                NULL,
-                                0,
-                                RT_SCOPE_NOWHERE,
-                                NM_PLATFORM_LIFETIME_PERMANENT,
-                                NM_PLATFORM_LIFETIME_PERMANENT,
-                                0,
-                                NULL);
-    if (!nlmsg)
-        g_return_val_if_reached(FALSE);
-
-    nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr);
-    return do_delete_object(platform, &obj_id, nlmsg);
-}
-
-/*****************************************************************************/
-
-static int
-ip_route_add(NMPlatform *             platform,
-             NMPNlmFlags              flags,
-             int                      addr_family,
-             const NMPlatformIPRoute *route)
-{
-    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-    NMPObject                    obj;
-
-    nmp_object_stackinit(&obj,
-                         NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)),
-                         (const NMPlatformObject *) route);
-
-    nm_platform_ip_route_normalize(addr_family, NMP_OBJECT_CAST_IP_ROUTE(&obj));
-
-    nlmsg = _nl_msg_new_route(RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj);
-    if (!nlmsg)
-        g_return_val_if_reached(-NME_BUG);
-    return do_add_addrroute(platform,
-                            &obj,
-                            nlmsg,
-                            NM_FLAGS_HAS(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE));
-}
-
-static gboolean
-object_delete(NMPlatform *platform, const NMPObject *obj)
-{
-    nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
-    nm_auto_nlmsg struct nl_msg *   nlmsg          = NULL;
-
-    if (!NMP_OBJECT_IS_STACKINIT(obj))
-        obj_keep_alive = nmp_object_ref(obj);
-
-    switch (NMP_OBJECT_GET_TYPE(obj)) {
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        nlmsg = _nl_msg_new_route(RTM_DELROUTE, 0, obj);
-        break;
-    case NMP_OBJECT_TYPE_ROUTING_RULE:
-        nlmsg = _nl_msg_new_routing_rule(RTM_DELRULE, 0, NMP_OBJECT_CAST_ROUTING_RULE(obj));
-        break;
-    case NMP_OBJECT_TYPE_QDISC:
-        nlmsg = _nl_msg_new_qdisc(RTM_DELQDISC, 0, NMP_OBJECT_CAST_QDISC(obj));
-        break;
-    case NMP_OBJECT_TYPE_TFILTER:
-        nlmsg = _nl_msg_new_tfilter(RTM_DELTFILTER, 0, NMP_OBJECT_CAST_TFILTER(obj));
-        break;
-    default:
-        break;
-    }
-
-    if (!nlmsg)
-        g_return_val_if_reached(FALSE);
-    return do_delete_object(platform, obj, nlmsg);
-}
-
-/*****************************************************************************/
-
-static int
-ip_route_get(NMPlatform *  platform,
-             int           addr_family,
-             gconstpointer address,
-             int           oif_ifindex,
-             NMPObject **  out_route)
-{
-    const gboolean          is_v4     = (addr_family == AF_INET);
-    const int               addr_len  = is_v4 ? 4 : 16;
-    int                     try_count = 0;
-    WaitForNlResponseResult seq_result;
-    int                     nle;
-    nm_auto_nmpobj NMPObject *route = NULL;
-
-    nm_assert(NM_IS_LINUX_PLATFORM(platform));
-    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
-    nm_assert(address);
-
-    do {
-        struct {
-            struct nlmsghdr n;
-            struct rtmsg    r;
-            char            buf[64];
-        } req = {
-            .n.nlmsg_len   = NLMSG_LENGTH(sizeof(struct rtmsg)),
-            .n.nlmsg_flags = NLM_F_REQUEST,
-            .n.nlmsg_type  = RTM_GETROUTE,
-            .r.rtm_family  = addr_family,
-            .r.rtm_tos     = 0,
-            .r.rtm_dst_len = is_v4 ? 32 : 128,
-            .r.rtm_flags   = 0x1000 /* RTM_F_LOOKUP_TABLE */,
-        };
-
-        nm_clear_pointer(&route, nmp_object_unref);
-
-        if (!_nl_addattr_l(&req.n, sizeof(req), RTA_DST, address, addr_len))
-            nm_assert_not_reached();
-
-        if (oif_ifindex > 0) {
-            gint32 ii = oif_ifindex;
-
-            if (!_nl_addattr_l(&req.n, sizeof(req), RTA_OIF, &ii, sizeof(ii)))
-                nm_assert_not_reached();
-        }
-
-        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-        nle        = _nl_send_nlmsghdr(platform,
-                                &req.n,
-                                &seq_result,
-                                NULL,
-                                DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET,
-                                &route);
-        if (nle < 0) {
-            _LOGE("get-route: failure sending netlink request \"%s\" (%d)",
-                  nm_strerror_native(-nle),
-                  -nle);
-            return -NME_UNSPEC;
-        }
-
-        delayed_action_handle_all(platform, FALSE);
-
-        /* Retry, if we failed due to a cache resync. That can happen when the netlink
-         * socket fills up and we lost the response. */
-    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC && ++try_count < 10);
-
-    if (seq_result < 0) {
-        /* negative seq_result is an errno from kernel. Map it to negative
-         * int (which are also errno). */
-        return (int) seq_result;
-    }
-
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
-        if (route) {
-            NM_SET_OUT(out_route, g_steal_pointer(&route));
-            return 0;
-        }
-        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
-    }
-
-    return -NME_UNSPEC;
-}
-
-/*****************************************************************************/
-
-static int
-routing_rule_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformRoutingRule *routing_rule)
-{
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    nm_auto_nlmsg struct nl_msg *msg        = NULL;
-    gs_free char *               errmsg     = NULL;
-    char                         s_buf[256];
-    int                          nle;
-
-    msg = _nl_msg_new_routing_rule(RTM_NEWRULE, flags, routing_rule);
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        _LOGE("do-add-rule: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
-        return -NME_PL_NETLINK;
-    }
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
-           "do-add-rule: %s",
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
-
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-        return 0;
-    if (seq_result < 0)
-        return seq_result;
-    return -NME_UNSPEC;
-}
-
-/*****************************************************************************/
-
-static int
-qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
-{
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char *               errmsg     = NULL;
-    int                          nle;
-    char                         s_buf[256];
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-    /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
-     * is undefined. */
-
-    msg = _nl_msg_new_qdisc(RTM_NEWQDISC, flags, qdisc);
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        _LOGE("do-add-qdisc: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
-        return -NME_PL_NETLINK;
-    }
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
-           "do-add-qdisc: %s",
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
-
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-        return 0;
-    if (seq_result < 0)
-        return seq_result;
-    return -NME_UNSPEC;
-}
-
-/*****************************************************************************/
-
-static int
-tfilter_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformTfilter *tfilter)
-{
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char *               errmsg     = NULL;
-    int                          nle;
-    char                         s_buf[256];
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-    /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
-     * and tfilter.action.kind is undefined. */
-
-    msg = _nl_msg_new_tfilter(RTM_NEWTFILTER, flags, tfilter);
-
-    event_handler_read_netlink(platform, FALSE);
-
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
-    if (nle < 0) {
-        _LOGE("do-add-tfilter: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
-        return -NME_PL_NETLINK;
-    }
-
-    delayed_action_handle_all(platform, FALSE);
-
-    nm_assert(seq_result);
-
-    _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
-           "do-add-tfilter: %s",
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
-
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-        return 0;
-
-    return -NME_UNSPEC;
-}
-
-/*****************************************************************************/
-
-static gboolean
-event_handler(int fd, GIOCondition io_condition, gpointer user_data)
-{
-    delayed_action_handle_all(NM_PLATFORM(user_data), TRUE);
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-/* copied from libnl3's recvmsgs() */
-static int
-event_handler_recvmsgs(NMPlatform *platform, gboolean handle_events)
-{
-    NMLinuxPlatformPrivate *    priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    struct nl_sock *            sk   = priv->nlh;
-    int                         n;
-    int                         err         = 0;
-    gboolean                    multipart   = 0;
-    gboolean                    interrupted = FALSE;
-    struct nlmsghdr *           hdr;
-    WaitForNlResponseResult     seq_result;
-    struct sockaddr_nl          nla = {0};
-    struct ucred                creds;
-    gboolean                    creds_has;
-    nm_auto_free unsigned char *buf = NULL;
-
-continue_reading:
-    nm_clear_pointer(&buf, free);
-    n = nl_recv(sk, &nla, &buf, &creds, &creds_has);
-
-    if (n <= 0) {
-        if (n == -NME_NL_MSG_TRUNC) {
-            int buf_size;
-
-            /* the message receive buffer was too small. We lost one message, which
-             * is unfortunate. Try to double the buffer size for the next time. */
-            buf_size = nl_socket_get_msg_buf_size(sk);
-            if (buf_size < 512 * 1024) {
-                buf_size *= 2;
-                _LOGT("netlink: recvmsg: increase message buffer size for recvmsg() to %d bytes",
-                      buf_size);
-                if (nl_socket_set_msg_buf_size(sk, buf_size) < 0)
-                    nm_assert_not_reached();
-                if (!handle_events)
-                    goto continue_reading;
-            }
-        }
-
-        return n;
-    }
-
-    hdr = (struct nlmsghdr *) buf;
-    while (nlmsg_ok(hdr, n)) {
-        nm_auto_nlmsg struct nl_msg *msg               = NULL;
-        gboolean                     abort_parsing     = FALSE;
-        gboolean                     process_valid_msg = FALSE;
-        guint32                      seq_number;
-        char                         buf_nlmsghdr[400];
-        const char *                 extack_msg = NULL;
-
-        msg = nlmsg_alloc_convert(hdr);
-
-        nlmsg_set_proto(msg, NETLINK_ROUTE);
-        nlmsg_set_src(msg, &nla);
-
-        if (!creds_has || creds.pid) {
-            if (!creds_has)
-                _LOGT("netlink: recvmsg: received message without credentials");
-            else
-                _LOGT("netlink: recvmsg: received non-kernel message (pid %d)", creds.pid);
-            err = 0;
-            goto stop;
-        }
-
-        _LOGt("netlink: recvmsg: new message %s",
-              nl_nlmsghdr_to_str(hdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)));
-
-        nlmsg_set_creds(msg, &creds);
-
-        if (hdr->nlmsg_flags & NLM_F_MULTI)
-            multipart = TRUE;
-
-        if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
-            /*
-             * We have to continue reading to clear
-             * all messages until a NLMSG_DONE is
-             * received and report the inconsistency.
-             */
-            interrupted = TRUE;
-        }
-
-        /* Other side wishes to see an ack for this message */
-        if (hdr->nlmsg_flags & NLM_F_ACK) {
-            /* FIXME: implement */
-        }
-
-        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
-
-        if (hdr->nlmsg_type == NLMSG_DONE) {
-            /* messages terminates a multipart message, this is
-             * usually the end of a message and therefore we slip
-             * out of the loop by default. the user may overrule
-             * this action by skipping this packet. */
-            multipart  = FALSE;
-            seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
-        } else if (hdr->nlmsg_type == NLMSG_NOOP) {
-            /* Message to be ignored, the default action is to
-             * skip this message if no callback is specified. The
-             * user may overrule this action by returning
-             * NL_PROCEED. */
-        } else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
-            /* Data got lost, report back to user. The default action is to
-             * quit parsing. The user may overrule this action by returning
-             * NL_SKIP or NL_PROCEED (dangerous) */
-            err           = -NME_NL_MSG_OVERFLOW;
-            abort_parsing = TRUE;
-        } else if (hdr->nlmsg_type == NLMSG_ERROR) {
-            /* Message carries a nlmsgerr */
-            struct nlmsgerr *e = nlmsg_data(hdr);
-
-            if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
-                /* Truncated error message, the default action
-                 * is to stop parsing. The user may overrule
-                 * this action by returning NL_SKIP or
-                 * NL_PROCEED (dangerous) */
-                err           = -NME_NL_MSG_TRUNC;
-                abort_parsing = TRUE;
-            } else if (e->error) {
-                int errsv = nm_errno_native(e->error);
-
-                if (NM_FLAGS_HAS(hdr->nlmsg_flags, NLM_F_ACK_TLVS)
-                    && hdr->nlmsg_len >= sizeof(*e) + e->msg.nlmsg_len) {
-                    static const struct nla_policy policy[] = {
-                        [NLMSGERR_ATTR_MSG]  = {.type = NLA_STRING},
-                        [NLMSGERR_ATTR_OFFS] = {.type = NLA_U32},
-                    };
-                    struct nlattr *tb[G_N_ELEMENTS(policy)];
-                    struct nlattr *tlvs;
-
-                    tlvs = (struct nlattr *) ((char *) e + sizeof(*e) + e->msg.nlmsg_len
-                                              - NLMSG_HDRLEN);
-                    if (nla_parse_arr(tb,
-                                      tlvs,
-                                      hdr->nlmsg_len - sizeof(*e) - e->msg.nlmsg_len,
-                                      policy)
-                        >= 0) {
-                        if (tb[NLMSGERR_ATTR_MSG])
-                            extack_msg = nla_get_string(tb[NLMSGERR_ATTR_MSG]);
-                    }
-                }
-
-                /* Error message reported back from kernel. */
-                _LOGD("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
-                      nm_strerror_native(errsv),
-                      errsv,
-                      NM_PRINT_FMT_QUOTED(extack_msg, " \"", extack_msg, "\"", ""),
-                      nlmsg_hdr(msg)->nlmsg_seq);
-                seq_result = -NM_ERRNO_NATIVE(errsv);
-            } else
-                seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
-        } else
-            process_valid_msg = TRUE;
-
-        seq_number = nlmsg_hdr(msg)->nlmsg_seq;
-
-        /* check whether the seq number is different from before, and
-         * whether the previous number (@nlh_seq_last_seen) is a pending
-         * refresh-all request. In that case, the pending request is thereby
-         * completed.
-         *
-         * We must do that before processing the message with event_valid_msg(),
-         * because we must track the completion of the pending request before that. */
-        event_seq_check_refresh_all(platform, seq_number);
-
-        if (process_valid_msg) {
-            /* Valid message (not checking for MULTIPART bit to
-             * get along with broken kernels. NL_SKIP has no
-             * effect on this.  */
-
-            event_valid_msg(platform, msg, handle_events);
-
-            seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
-        }
-
-        event_seq_check(platform, seq_number, seq_result, extack_msg);
-
-        if (abort_parsing)
-            goto stop;
-
-        err = 0;
-        hdr = nlmsg_next(hdr, &n);
-    }
-
-    if (multipart) {
-        /* Multipart message not yet complete, continue reading */
-        goto continue_reading;
-    }
-stop:
-    if (!handle_events) {
-        /* when we don't handle events, we want to drain all messages from the socket
-         * without handling the messages (but still check for sequence numbers).
-         * Repeat reading. */
-        goto continue_reading;
-    }
-
-    if (interrupted)
-        return -NME_NL_DUMP_INTR;
-    return err;
-}
-
-/*****************************************************************************/
-
-static gboolean
-event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks)
-{
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMLinuxPlatformPrivate *    priv  = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    int                         r;
-    struct pollfd               pfd;
-    gboolean                    any = FALSE;
-    int                         timeout_msec;
-    struct {
-        guint32 seq_number;
-        gint64  timeout_abs_ns;
-        gint64  now_ns;
-    } next;
-
-    if (!nm_platform_netns_push(platform, &netns)) {
-        delayed_action_wait_for_nl_response_complete_all(platform,
-                                                         WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS);
-        return FALSE;
-    }
-
-    for (;;) {
-        for (;;) {
-            int nle;
-
-            nle = event_handler_recvmsgs(platform, TRUE);
-
-            if (nle < 0) {
-                switch (nle) {
-                case -EAGAIN:
-                    goto after_read;
-                case -NME_NL_DUMP_INTR:
-                    _LOGD("netlink: read: uncritical failure to retrieve incoming events: %s (%d)",
-                          nm_strerror(nle),
-                          nle);
-                    break;
-                case -NME_NL_MSG_TRUNC:
-                case -ENOBUFS:
-                    _LOGI("netlink: read: %s. Need to resynchronize platform cache", ({
-                              const char *_reason = "unknown";
-                              switch (nle) {
-                              case -NME_NL_MSG_TRUNC:
-                                  _reason = "message truncated";
-                                  break;
-                              case -ENOBUFS:
-                                  _reason = "too many netlink events";
-                                  break;
-                              }
-                              _reason;
-                          }));
-                    event_handler_recvmsgs(platform, FALSE);
-                    delayed_action_wait_for_nl_response_complete_all(
-                        platform,
-                        WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
-
-                    delayed_action_schedule(platform,
-                                            DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-                                            NULL);
-                    break;
-                default:
-                    _LOGE("netlink: read: failed to retrieve incoming events: %s (%d)",
-                          nm_strerror(nle),
-                          nle);
-                    break;
-                }
-            }
-            any = TRUE;
-        }
-
-after_read:
-
-        if (!NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
-            return any;
-
-        delayed_action_wait_for_nl_response_complete_check(platform,
-                                                           WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN,
-                                                           &next.seq_number,
-                                                           &next.timeout_abs_ns,
-                                                           &next.now_ns);
-
-        if (!wait_for_acks
-            || !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
-            return any;
-
-        nm_assert(next.seq_number);
-        nm_assert(next.now_ns > 0);
-        nm_assert(next.timeout_abs_ns > next.now_ns);
-
-        _LOGT("netlink: read: wait for ACK for sequence number %u...", next.seq_number);
-
-        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_msec));
-
-        if (r == 0) {
-            /* timeout and there is nothing to read. */
-            goto after_read;
-        }
-
-        if (r < 0) {
-            int errsv = errno;
-
-            if (errsv != EINTR) {
-                _LOGE("netlink: read: poll failed with %s", nm_strerror_native(errsv));
-                delayed_action_wait_for_nl_response_complete_all(
-                    platform,
-                    WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL);
-                return any;
-            }
-            /* Continue to read again, even if there might be nothing to read after EINTR. */
-        }
-    }
-}
-
-/*****************************************************************************/
-
-static void
-cache_update_link_udev(NMPlatform *platform, int ifindex, struct udev_device *udevice)
-{
-    nm_auto_nmpobj const NMPObject *obj_old = NULL;
-    nm_auto_nmpobj const NMPObject *obj_new = NULL;
-    NMPCacheOpsType                 cache_op;
-
-    cache_op = nmp_cache_update_link_udev(nm_platform_get_cache(platform),
-                                          ifindex,
-                                          udevice,
-                                          &obj_old,
-                                          &obj_new);
-
-    if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
-        nm_auto_pop_netns NMPNetns *netns = NULL;
-
-        cache_on_change(platform, cache_op, obj_old, obj_new);
-        if (!nm_platform_netns_push(platform, &netns))
-            return;
-        nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, obj_new);
-    }
-}
-
-static void
-udev_device_added(NMPlatform *platform, struct udev_device *udevice)
-{
-    const char *ifname;
-    const char *ifindex_s;
-    int         ifindex;
-
-    ifname = udev_device_get_sysname(udevice);
-    if (!ifname) {
-        _LOGD("udev-add: failed to get device's interface");
-        return;
-    }
-
-    ifindex_s = udev_device_get_property_value(udevice, "IFINDEX");
-    if (!ifindex_s) {
-        _LOGW("udev-add[%s]failed to get device's ifindex", ifname);
-        return;
-    }
-    ifindex = _nm_utils_ascii_str_to_int64(ifindex_s, 10, 1, G_MAXINT, 0);
-    if (ifindex <= 0) {
-        _LOGW("udev-add[%s]: retrieved invalid IFINDEX=%d", ifname, ifindex);
-        return;
-    }
-
-    if (!udev_device_get_syspath(udevice)) {
-        _LOGD("udev-add[%s,%d]: couldn't determine device path; ignoring...", ifname, ifindex);
-        return;
-    }
-
-    _LOGT("udev-add[%s,%d]: device added", ifname, ifindex);
-    cache_update_link_udev(platform, ifindex, udevice);
-}
-
-static gboolean
-_udev_device_removed_match_link(const NMPObject *obj, gpointer udevice)
-{
-    return obj->_link.udev.device == udevice;
-}
-
-static void
-udev_device_removed(NMPlatform *platform, struct udev_device *udevice)
-{
-    const char *ifindex_s;
-    int         ifindex = 0;
-
-    ifindex_s = udev_device_get_property_value(udevice, "IFINDEX");
-    ifindex   = _nm_utils_ascii_str_to_int64(ifindex_s, 10, 1, G_MAXINT, 0);
-    if (ifindex <= 0) {
-        const NMPObject *obj;
-
-        obj = nmp_cache_lookup_link_full(nm_platform_get_cache(platform),
-                                         0,
-                                         NULL,
-                                         FALSE,
-                                         NM_LINK_TYPE_NONE,
-                                         _udev_device_removed_match_link,
-                                         udevice);
-        if (obj)
-            ifindex = obj->link.ifindex;
-    }
-
-    _LOGD("udev-remove: IFINDEX=%d", ifindex);
-    if (ifindex <= 0)
-        return;
-
-    cache_update_link_udev(platform, ifindex, NULL);
-}
-
-static void
-handle_udev_event(NMUdevClient *udev_client, struct udev_device *udevice, gpointer user_data)
-{
-    nm_auto_pop_netns NMPNetns *netns    = NULL;
-    NMPlatform *                platform = NM_PLATFORM(user_data);
-    const char *                subsys;
-    const char *                ifindex;
-    guint64                     seqnum;
-    const char *                action;
-
-    action = udev_device_get_action(udevice);
-    g_return_if_fail(action);
-
-    subsys = udev_device_get_subsystem(udevice);
-    g_return_if_fail(nm_streq0(subsys, "net"));
-
-    if (!nm_platform_netns_push(platform, &netns))
-        return;
-
-    ifindex = udev_device_get_property_value(udevice, "IFINDEX");
-    seqnum  = udev_device_get_seqnum(udevice);
-    _LOGD("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT,
-          action,
-          subsys,
-          udev_device_get_sysname(udevice),
-          ifindex ?: "unknown",
-          seqnum);
-
-    if (NM_IN_STRSET(action, "add", "move"))
-        udev_device_added(platform, udevice);
-    else if (NM_IN_STRSET(action, "remove"))
-        udev_device_removed(platform, udevice);
-}
-
-/*****************************************************************************/
-
-void
-nm_linux_platform_setup(void)
-{
-    nm_platform_setup(nm_linux_platform_new(FALSE, FALSE));
-}
-
-/*****************************************************************************/
-
-static void
-nm_linux_platform_init(NMLinuxPlatform *self)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(self);
-
-    priv->delayed_action.list_master_connected = g_ptr_array_new();
-    priv->delayed_action.list_refresh_link     = g_ptr_array_new();
-    priv->delayed_action.list_wait_for_nl_response =
-        g_array_new(FALSE, TRUE, sizeof(DelayedActionWaitForNlResponseData));
-}
-
-static void
-constructed(GObject *_object)
-{
-    NMPlatform *            platform = NM_PLATFORM(_object);
-    NMLinuxPlatformPrivate *priv     = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    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(NM_MAKE_STRV("net"), handle_udev_event, platform);
-    }
-
-    _LOGD("create (%s netns, %s, %s udev)",
-          !platform->_netns ? "ignore" : "use",
-          !platform->_netns && nmp_netns_is_initial()
-              ? "initial netns"
-              : (!nmp_netns_get_current()
-                     ? "no netns support"
-                     : nm_sprintf_bufa(100,
-                                       "in netns[%p]%s",
-                                       nmp_netns_get_current(),
-                                       nmp_netns_get_current() == nmp_netns_get_initial() ? "/main"
-                                                                                          : "")),
-          nm_platform_get_use_udev(platform) ? "use" : "no");
-
-    priv->genl = nl_socket_alloc();
-    g_assert(priv->genl);
-
-    nle = nl_connect(priv->genl, NETLINK_GENERIC);
-    if (nle) {
-        _LOGE("unable to connect the generic netlink socket \"%s\" (%d)", nm_strerror(nle), -nle);
-        nl_socket_free(priv->genl);
-        priv->genl = NULL;
-    }
-
-    priv->nlh = nl_socket_alloc();
-    g_assert(priv->nlh);
-
-    nle = nl_connect(priv->nlh, NETLINK_ROUTE);
-    g_assert(!nle);
-    nle = nl_socket_set_passcred(priv->nlh, 1);
-    g_assert(!nle);
-
-    /* No blocking for event socket, so that we can drain it safely. */
-    nle = nl_socket_set_nonblocking(priv->nlh);
-    g_assert(!nle);
-
-    /* use 8 MB for receive socket kernel queue. */
-    nle = nl_socket_set_buffer_size(priv->nlh, 8 * 1024 * 1024, 0);
-    g_assert(!nle);
-
-    nle = nl_socket_set_ext_ack(priv->nlh, TRUE);
-    if (nle)
-        _LOGD("could not enable extended acks on netlink socket");
-
-    /* explicitly set the msg buffer size and disable MSG_PEEK.
-     * If we later encounter NME_NL_MSG_TRUNC, we will adjust the buffer size. */
-    nl_socket_disable_msg_peek(priv->nlh);
-    nle = nl_socket_set_msg_buf_size(priv->nlh, 32 * 1024);
-    g_assert(!nle);
-
-    nle = nl_socket_add_memberships(priv->nlh,
-                                    RTNLGRP_IPV4_IFADDR,
-                                    RTNLGRP_IPV4_ROUTE,
-                                    RTNLGRP_IPV4_RULE,
-                                    RTNLGRP_IPV6_RULE,
-                                    RTNLGRP_IPV6_IFADDR,
-                                    RTNLGRP_IPV6_ROUTE,
-                                    RTNLGRP_LINK,
-                                    RTNLGRP_TC,
-                                    0);
-    g_assert(!nle);
-
-    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);
-
-    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);
-
-    _LOGD("populate platform cache");
-    delayed_action_schedule(
-        platform,
-        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-        NULL);
-
-    delayed_action_handle_all(platform, FALSE);
-
-    /* Set up udev monitoring */
-    if (priv->udev_client) {
-        struct udev_enumerate * enumerator;
-        struct udev_list_entry *devices, *l;
-
-        /* And read initial device list */
-        enumerator = nm_udev_client_enumerate_new(priv->udev_client);
-
-        udev_enumerate_add_match_is_initialized(enumerator);
-
-        udev_enumerate_scan_devices(enumerator);
-
-        devices = udev_enumerate_get_list_entry(enumerator);
-        for (l = devices; l; l = udev_list_entry_get_next(l)) {
-            struct udev_device *udevice;
-
-            udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator),
-                                                   udev_list_entry_get_name(l));
-            if (!udevice)
-                continue;
-
-            udev_device_added(platform, udevice);
-            udev_device_unref(udevice);
-        }
-
-        udev_enumerate_unref(enumerator);
-    }
-}
-
-/* Similar to systemd's path_is_read_only_fs(), at
- * https://github.com/systemd/systemd/blob/v246/src/basic/stat-util.c#L132 */
-static int
-path_is_read_only_fs(const char *path)
-{
-    struct statvfs st;
-
-    if (statvfs(path, &st) < 0)
-        return -errno;
-
-    if (st.f_flag & ST_RDONLY)
-        return TRUE;
-
-    /* On NFS, statvfs() might not reflect whether we can actually
-	 * write to the remote share. Let's try again with
-	 * access(W_OK) which is more reliable, at least sometimes. */
-    if (access(path, W_OK) < 0 && errno == EROFS)
-        return TRUE;
-
-    return FALSE;
-}
-
-NMPlatform *
-nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support)
-{
-    gboolean use_udev = FALSE;
-
-    if (nmp_netns_is_initial() && path_is_read_only_fs("/sys") == FALSE)
-        use_udev = TRUE;
-
-    return g_object_new(NM_TYPE_LINUX_PLATFORM,
-                        NM_PLATFORM_LOG_WITH_PTR,
-                        log_with_ptr,
-                        NM_PLATFORM_USE_UDEV,
-                        use_udev,
-                        NM_PLATFORM_NETNS_SUPPORT,
-                        netns_support,
-                        NULL);
-}
-
-static void
-dispose(GObject *object)
-{
-    NMPlatform *            platform = NM_PLATFORM(object);
-    NMLinuxPlatformPrivate *priv     = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-
-    _LOGD("dispose");
-
-    delayed_action_wait_for_nl_response_complete_all(platform,
-                                                     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
-
-    priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
-    g_ptr_array_set_size(priv->delayed_action.list_master_connected, 0);
-    g_ptr_array_set_size(priv->delayed_action.list_refresh_link, 0);
-
-    G_OBJECT_CLASS(nm_linux_platform_parent_class)->dispose(object);
-}
-
-static void
-finalize(GObject *object)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(object);
-
-    g_ptr_array_unref(priv->delayed_action.list_master_connected);
-    g_ptr_array_unref(priv->delayed_action.list_refresh_link);
-    g_array_unref(priv->delayed_action.list_wait_for_nl_response);
-
-    nl_socket_free(priv->genl);
-
-    nm_clear_g_source_inst(&priv->event_source);
-
-    nl_socket_free(priv->nlh);
-
-    if (priv->sysctl_get_prev_values) {
-        sysctl_clear_cache_list = g_slist_remove(sysctl_clear_cache_list, object);
-        g_hash_table_destroy(priv->sysctl_get_prev_values);
-    }
-
-    priv->udev_client = nm_udev_client_destroy(priv->udev_client);
-
-    G_OBJECT_CLASS(nm_linux_platform_parent_class)->finalize(object);
-}
-
-static void
-nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
-{
-    GObjectClass *   object_class   = G_OBJECT_CLASS(klass);
-    NMPlatformClass *platform_class = NM_PLATFORM_CLASS(klass);
-
-    object_class->constructed = constructed;
-    object_class->dispose     = dispose;
-    object_class->finalize    = finalize;
-
-    platform_class->sysctl_set       = sysctl_set;
-    platform_class->sysctl_set_async = sysctl_set_async;
-    platform_class->sysctl_get       = sysctl_get;
-
-    platform_class->link_add    = link_add;
-    platform_class->link_delete = link_delete;
-
-    platform_class->link_refresh = link_refresh;
-
-    platform_class->link_set_netns = link_set_netns;
-
-    platform_class->link_set_up    = link_set_up;
-    platform_class->link_set_down  = link_set_down;
-    platform_class->link_set_arp   = link_set_arp;
-    platform_class->link_set_noarp = link_set_noarp;
-
-    platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled;
-    platform_class->link_set_token               = link_set_token;
-
-    platform_class->link_set_address            = link_set_address;
-    platform_class->link_get_permanent_address  = link_get_permanent_address;
-    platform_class->link_set_mtu                = link_set_mtu;
-    platform_class->link_set_name               = link_set_name;
-    platform_class->link_set_sriov_params_async = link_set_sriov_params_async;
-    platform_class->link_set_sriov_vfs          = link_set_sriov_vfs;
-    platform_class->link_set_bridge_vlans       = link_set_bridge_vlans;
-
-    platform_class->link_get_physical_port_id = link_get_physical_port_id;
-    platform_class->link_get_dev_id           = link_get_dev_id;
-    platform_class->link_get_wake_on_lan      = link_get_wake_on_lan;
-    platform_class->link_get_driver_info      = link_get_driver_info;
-
-    platform_class->link_supports_carrier_detect = link_supports_carrier_detect;
-    platform_class->link_supports_vlans          = link_supports_vlans;
-    platform_class->link_supports_sriov          = link_supports_sriov;
-
-    platform_class->link_enslave = link_enslave;
-    platform_class->link_release = link_release;
-
-    platform_class->link_can_assume = link_can_assume;
-
-    platform_class->link_vlan_change      = link_vlan_change;
-    platform_class->link_wireguard_change = link_wireguard_change;
-
-    platform_class->infiniband_partition_add    = infiniband_partition_add;
-    platform_class->infiniband_partition_delete = infiniband_partition_delete;
-
-    platform_class->wifi_get_capabilities            = wifi_get_capabilities;
-    platform_class->wifi_get_frequency               = wifi_get_frequency;
-    platform_class->wifi_get_station                 = wifi_get_station;
-    platform_class->wifi_get_mode                    = wifi_get_mode;
-    platform_class->wifi_set_mode                    = wifi_set_mode;
-    platform_class->wifi_set_powersave               = wifi_set_powersave;
-    platform_class->wifi_find_frequency              = wifi_find_frequency;
-    platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
-    platform_class->wifi_get_wake_on_wlan            = wifi_get_wake_on_wlan;
-    platform_class->wifi_set_wake_on_wlan            = wifi_set_wake_on_wlan;
-
-    platform_class->mesh_get_channel = mesh_get_channel;
-    platform_class->mesh_set_channel = mesh_set_channel;
-    platform_class->mesh_set_ssid    = mesh_set_ssid;
-
-    platform_class->wpan_get_pan_id     = wpan_get_pan_id;
-    platform_class->wpan_set_pan_id     = wpan_set_pan_id;
-    platform_class->wpan_get_short_addr = wpan_get_short_addr;
-    platform_class->wpan_set_short_addr = wpan_set_short_addr;
-    platform_class->wpan_set_channel    = wpan_set_channel;
-
-    platform_class->link_tun_add = link_tun_add;
-
-    platform_class->object_delete      = object_delete;
-    platform_class->ip4_address_add    = ip4_address_add;
-    platform_class->ip6_address_add    = ip6_address_add;
-    platform_class->ip4_address_delete = ip4_address_delete;
-    platform_class->ip6_address_delete = ip6_address_delete;
-
-    platform_class->ip_route_add = ip_route_add;
-    platform_class->ip_route_get = ip_route_get;
-
-    platform_class->routing_rule_add = routing_rule_add;
-
-    platform_class->qdisc_add   = qdisc_add;
-    platform_class->tfilter_add = tfilter_add;
-
-    platform_class->process_events = process_events;
-}
diff --git a/src/core/platform/nm-linux-platform.h b/src/core/platform/nm-linux-platform.h
deleted file mode 100644
index 6837e64a..00000000
--- a/src/core/platform/nm-linux-platform.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2012 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_LINUX_PLATFORM_H__
-#define __NETWORKMANAGER_LINUX_PLATFORM_H__
-
-#include "nm-platform.h"
-
-#define NM_TYPE_LINUX_PLATFORM (nm_linux_platform_get_type())
-#define NM_LINUX_PLATFORM(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatform))
-#define NM_LINUX_PLATFORM_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass))
-#define NM_IS_LINUX_PLATFORM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_LINUX_PLATFORM))
-#define NM_IS_LINUX_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_LINUX_PLATFORM))
-#define NM_LINUX_PLATFORM_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformClass))
-
-typedef struct _NMLinuxPlatform      NMLinuxPlatform;
-typedef struct _NMLinuxPlatformClass NMLinuxPlatformClass;
-
-GType nm_linux_platform_get_type(void);
-
-NMPlatform *nm_linux_platform_new(gboolean log_with_ptr, gboolean netns_support);
-
-void nm_linux_platform_setup(void);
-
-#endif /* __NETWORKMANAGER_LINUX_PLATFORM_H__ */
diff --git a/src/core/platform/nm-platform-private.h b/src/core/platform/nm-platform-private.h
deleted file mode 100644
index cf805689..00000000
--- a/src/core/platform/nm-platform-private.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2017 Red Hat, Inc.
- */
-
-#ifndef __NM_PLATFORM_PRIVATE_H__
-#define __NM_PLATFORM_PRIVATE_H__
-
-#include "nm-platform.h"
-#include "nmp-object.h"
-
-NMPCache *nm_platform_get_cache(NMPlatform *self);
-
-#define NMTST_ASSERT_PLATFORM_NETNS_CURRENT(platform)                                          \
-    G_STMT_START                                                                               \
-    {                                                                                          \
-        NMPlatform *_platform = (platform);                                                    \
-                                                                                               \
-        nm_assert(NM_IS_PLATFORM(_platform));                                                  \
-        nm_assert(NM_IN_SET(nm_platform_netns_get(_platform), NULL, nmp_netns_get_current())); \
-    }                                                                                          \
-    G_STMT_END
-
-void nm_platform_cache_update_emit_signal(NMPlatform *     platform,
-                                          NMPCacheOpsType  cache_op,
-                                          const NMPObject *obj_old,
-                                          const NMPObject *obj_new);
-
-#endif /* __NM_PLATFORM_PRIVATE_H__ */
diff --git a/src/core/platform/nm-platform.c b/src/core/platform/nm-platform.c
deleted file mode 100644
index 459a3307..00000000
--- a/src/core/platform/nm-platform.c
+++ /dev/null
@@ -1,9066 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2012 - 2018 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-platform.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <netdb.h>
-#include <linux/fib_rules.h>
-#include <linux/ip.h>
-#include <linux/if.h>
-#include <linux/if_tun.h>
-#include <linux/if_tunnel.h>
-#include <linux/rtnetlink.h>
-#include <linux/tc_act/tc_mirred.h>
-#include <libudev.h>
-
-#include "nm-utils.h"
-#include "nm-core-internal.h"
-#include "nm-glib-aux/nm-dedup-multi.h"
-#include "nm-udev-aux/nm-udev-utils.h"
-#include "nm-glib-aux/nm-secret-utils.h"
-
-#include "nm-core-utils.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "nm-platform-private.h"
-#include "nmp-object.h"
-#include "nm-platform/nmp-netns.h"
-
-/*****************************************************************************/
-
-G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr)
-                == G_STRUCT_OFFSET(NMPlatformIP4Address, address));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr)
-                == G_STRUCT_OFFSET(NMPlatformIP6Address, address));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr)
-                == G_STRUCT_OFFSET(NMPlatformIP4Route, network));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr)
-                == G_STRUCT_OFFSET(NMPlatformIP6Route, network));
-
-G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIP4Route));
-G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIP6Route));
-G_STATIC_ASSERT(_nm_alignof(NMPlatformIPRoute) == _nm_alignof(NMPlatformIPXRoute));
-
-G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIP4Address));
-G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIP6Address));
-G_STATIC_ASSERT(_nm_alignof(NMPlatformIPAddress) == _nm_alignof(NMPlatformIPXAddress));
-
-/*****************************************************************************/
-
-G_STATIC_ASSERT(sizeof(((NMPLinkAddress *) NULL)->data) == NM_UTILS_HWADDR_LEN_MAX);
-G_STATIC_ASSERT(sizeof(((NMPlatformLink *) NULL)->l_address.data) == NM_UTILS_HWADDR_LEN_MAX);
-G_STATIC_ASSERT(sizeof(((NMPlatformLink *) NULL)->l_broadcast.data) == NM_UTILS_HWADDR_LEN_MAX);
-
-static const char *
-_nmp_link_address_to_string(const NMPLinkAddress *addr,
-                            char                  buf[static(NM_UTILS_HWADDR_LEN_MAX * 3)])
-{
-    nm_assert(addr);
-
-    if (addr->len > 0) {
-        if (!_nm_utils_hwaddr_ntoa(addr->data, addr->len, TRUE, buf, NM_UTILS_HWADDR_LEN_MAX * 3)) {
-            buf[0] = '\0';
-            g_return_val_if_reached(buf);
-        }
-    } else
-        buf[0] = '\0';
-
-    return buf;
-}
-
-gconstpointer
-nmp_link_address_get(const NMPLinkAddress *addr, size_t *length)
-{
-    if (!addr || addr->len <= 0) {
-        NM_SET_OUT(length, 0);
-        return NULL;
-    }
-
-    if (addr->len > NM_UTILS_HWADDR_LEN_MAX) {
-        NM_SET_OUT(length, 0);
-        g_return_val_if_reached(NULL);
-    }
-
-    NM_SET_OUT(length, addr->len);
-    return addr->data;
-}
-
-GBytes *
-nmp_link_address_get_as_bytes(const NMPLinkAddress *addr)
-{
-    gconstpointer data;
-    size_t        length;
-
-    data = nmp_link_address_get(addr, &length);
-
-    return length > 0 ? g_bytes_new(data, length) : NULL;
-}
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_PLATFORM
-#define _NMLOG_PREFIX_NAME "platform"
-
-#define NMLOG_COMMON(level, name, ...)                                                \
-    char                    __prefix[32];                                             \
-    const char *            __p_prefix = _NMLOG_PREFIX_NAME;                          \
-    const NMPlatform *const __self     = (self);                                      \
-    const char *            __name     = name;                                        \
-                                                                                      \
-    if (__self && NM_PLATFORM_GET_PRIVATE(__self)->log_with_ptr) {                    \
-        g_snprintf(__prefix, sizeof(__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-        __p_prefix = __prefix;                                                        \
-    }                                                                                 \
-    _nm_log(__level,                                                                  \
-            _NMLOG_DOMAIN,                                                            \
-            0,                                                                        \
-            __name,                                                                   \
-            NULL,                                                                     \
-            "%s: %s%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                          \
-            __p_prefix,                                                               \
-            NM_PRINT_FMT_QUOTED(__name, "(", __name, ") ", "") _NM_UTILS_MACRO_REST(__VA_ARGS__));
-
-#define _NMLOG(level, ...)                                \
-    G_STMT_START                                          \
-    {                                                     \
-        const NMLogLevel __level = (level);               \
-                                                          \
-        if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
-            NMLOG_COMMON(level, NULL, __VA_ARGS__);       \
-        }                                                 \
-    }                                                     \
-    G_STMT_END
-
-#define _NMLOG2(level, ...)                               \
-    G_STMT_START                                          \
-    {                                                     \
-        const NMLogLevel __level = (level);               \
-                                                          \
-        if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
-            NMLOG_COMMON(level, name, __VA_ARGS__);       \
-        }                                                 \
-    }                                                     \
-    G_STMT_END
-
-#define _NMLOG3(level, ...)                                                             \
-    G_STMT_START                                                                        \
-    {                                                                                   \
-        const NMLogLevel __level = (level);                                             \
-                                                                                        \
-        if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) {                               \
-            NMLOG_COMMON(level,                                                         \
-                         ifindex > 0 ? nm_platform_link_get_name(self, ifindex) : NULL, \
-                         __VA_ARGS__);                                                  \
-        }                                                                               \
-    }                                                                                   \
-    G_STMT_END
-
-/*****************************************************************************/
-
-static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = {0};
-
-enum {
-    PROP_0,
-    PROP_NETNS_SUPPORT,
-    PROP_USE_UDEV,
-    PROP_LOG_WITH_PTR,
-    LAST_PROP,
-};
-
-typedef struct _NMPlatformPrivate {
-    bool use_udev : 1;
-    bool log_with_ptr : 1;
-
-    guint              ip4_dev_route_blacklist_check_id;
-    guint              ip4_dev_route_blacklist_gc_timeout_id;
-    GHashTable *       ip4_dev_route_blacklist_hash;
-    NMDedupMultiIndex *multi_idx;
-    NMPCache *         cache;
-} NMPlatformPrivate;
-
-G_DEFINE_TYPE(NMPlatform, nm_platform, G_TYPE_OBJECT)
-
-#define NM_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMPlatform, NM_IS_PLATFORM)
-
-/*****************************************************************************/
-
-static void _ip4_dev_route_blacklist_schedule(NMPlatform *self);
-
-/*****************************************************************************/
-
-gboolean
-nm_platform_get_use_udev(NMPlatform *self)
-{
-    return NM_PLATFORM_GET_PRIVATE(self)->use_udev;
-}
-
-gboolean
-nm_platform_get_log_with_ptr(NMPlatform *self)
-{
-    return NM_PLATFORM_GET_PRIVATE(self)->log_with_ptr;
-}
-
-/*****************************************************************************/
-
-guint
-_nm_platform_signal_id_get(NMPlatformSignalIdType signal_type)
-{
-    nm_assert(signal_type > 0 && signal_type != NM_PLATFORM_SIGNAL_ID_NONE
-              && signal_type < _NM_PLATFORM_SIGNAL_ID_LAST);
-
-    return signals[signal_type];
-}
-
-/*****************************************************************************/
-
-/* Singleton NMPlatform subclass instance and cached class object */
-NM_DEFINE_SINGLETON_INSTANCE(NMPlatform);
-
-NM_DEFINE_SINGLETON_REGISTER(NMPlatform);
-
-/* Just always initialize a @klass instance. NM_PLATFORM_GET_CLASS()
- * is only a plain read on the self instance, which the compiler
- * like can optimize out.
- */
-#define _CHECK_SELF_VOID(self, klass)           \
-    NMPlatformClass *klass;                     \
-    do {                                        \
-        g_return_if_fail(NM_IS_PLATFORM(self)); \
-        klass = NM_PLATFORM_GET_CLASS(self);    \
-        (void) klass;                           \
-    } while (0)
-
-#define _CHECK_SELF(self, klass, err_val)                    \
-    NMPlatformClass *klass;                                  \
-    do {                                                     \
-        g_return_val_if_fail(NM_IS_PLATFORM(self), err_val); \
-        klass = NM_PLATFORM_GET_CLASS(self);                 \
-        (void) klass;                                        \
-    } while (0)
-
-#define _CHECK_SELF_NETNS(self, klass, netns, err_val)       \
-    nm_auto_pop_netns NMPNetns *netns = NULL;                \
-    NMPlatformClass *           klass;                       \
-    do {                                                     \
-        g_return_val_if_fail(NM_IS_PLATFORM(self), err_val); \
-        klass = NM_PLATFORM_GET_CLASS(self);                 \
-        (void) klass;                                        \
-        if (!nm_platform_netns_push(self, &netns))           \
-            return (err_val);                                \
-    } while (0)
-
-/**
- * nm_platform_setup:
- * @instance: the #NMPlatform instance
- *
- * Failing to set up #NMPlatform singleton results in a fatal error,
- * as well as trying to initialize it multiple times without freeing
- * it.
- *
- * NetworkManager will typically use only one platform object during
- * its run. Test programs might want to switch platform implementations,
- * though.
- */
-void
-nm_platform_setup(NMPlatform *instance)
-{
-    g_return_if_fail(NM_IS_PLATFORM(instance));
-    g_return_if_fail(!singleton_instance);
-
-    singleton_instance = instance;
-
-    nm_singleton_instance_register();
-
-    nm_log_dbg(LOGD_CORE,
-               "setup %s singleton (" NM_HASH_OBFUSCATE_PTR_FMT ")",
-               "NMPlatform",
-               NM_HASH_OBFUSCATE_PTR(instance));
-}
-
-/**
- * nm_platform_get:
- * @self: platform instance
- *
- * Retrieve #NMPlatform singleton. Use this whenever you want to connect to
- * #NMPlatform signals. It is an error to call it before nm_platform_setup().
- *
- * Returns: (transfer none): The #NMPlatform singleton reference.
- */
-NMPlatform *
-nm_platform_get()
-{
-    g_assert(singleton_instance);
-
-    return singleton_instance;
-}
-
-/*****************************************************************************/
-
-NMDedupMultiIndex *
-nm_platform_get_multi_idx(NMPlatform *self)
-{
-    g_return_val_if_fail(NM_IS_PLATFORM(self), NULL);
-
-    return NM_PLATFORM_GET_PRIVATE(self)->multi_idx;
-}
-
-/*****************************************************************************/
-
-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"),
-    NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_REPLACE, "replace"),
-    NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_PREPEND, "prepend"),
-    NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_APPEND, "append"),
-    NM_UTILS_LOOKUP_ITEM(NMP_NLM_FLAG_TEST, "test"),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_F_APPEND),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_FMASK),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(NMP_NLM_FLAG_F_ECHO), );
-
-#define _nmp_nlm_flag_to_string(flags)                               \
-    ({                                                               \
-        NMPNlmFlags _flags = (flags);                                \
-                                                                     \
-        _nmp_nlm_flag_to_string_lookup(flags)                        \
-            ?: nm_sprintf_bufa(100, "new[0x%x]", (unsigned) _flags); \
-    })
-
-/*****************************************************************************/
-
-volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = {};
-
-static const struct {
-    bool        compile_time_default;
-    const char *name;
-    const char *desc;
-} _nm_platform_kernel_support_info[_NM_PLATFORM_KERNEL_SUPPORT_NUM] = {
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS] =
-        {
-            .compile_time_default = TRUE,
-            .name                 = "EXTENDED_IFA_FLAGS",
-            .desc                 = "IPv6 temporary addresses support",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL] =
-        {
-            .compile_time_default = TRUE,
-            .name                 = "USER_IPV6LL",
-            .desc                 = "IFLA_INET6_ADDR_GEN_MODE support",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF] =
-        {
-            .compile_time_default = (RTA_MAX >= 20 /* RTA_PREF */),
-            .name                 = "RTA_PREF",
-            .desc                 = "ability to set router preference for IPv6 routes",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV] =
-        {
-            .compile_time_default = (FRA_MAX >= 19 /* FRA_L3MDEV */),
-            .name                 = "FRA_L3MDEV",
-            .desc                 = "FRA_L3MDEV attribute for policy routing rules",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE] =
-        {
-            .compile_time_default = (FRA_MAX >= 20 /* FRA_UID_RANGE */),
-            .name                 = "FRA_UID_RANGE",
-            .desc                 = "FRA_UID_RANGE attribute for policy routing rules",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL] =
-        {
-            .compile_time_default = (FRA_MAX >= 21 /* FRA_PROTOCOL */),
-            .name                 = "FRA_PROTOCOL",
-            .desc                 = "FRA_PROTOCOL attribute for policy routing rules",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO] =
-        {
-            .compile_time_default = (FRA_MAX >= 22 /* FRA_IP_PROTO */),
-            .name                 = "FRA_IP_PROTO",
-            .desc = "FRA_IP_PROTO, FRA_SPORT_RANGE, FRA_DPORT_RANGE attributes for policy routing "
-                    "rules",
-        },
-    [NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED] =
-        {
-            .compile_time_default = (IFLA_BR_MAX >= 41 /* IFLA_BR_VLAN_STATS_ENABLED */),
-            .name                 = "IFLA_BR_VLAN_STATS_ENABLE",
-            .desc                 = "IFLA_BR_VLAN_STATS_ENABLE bridge link attribute",
-        },
-};
-
-int
-_nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value)
-{
-    volatile int *p_state;
-    gboolean      set_default = FALSE;
-
-    nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state));
-
-    p_state = &_nm_platform_kernel_support_state[type];
-
-    if (value == 0) {
-        set_default = TRUE;
-        value       = _nm_platform_kernel_support_info[type].compile_time_default ? 1 : -1;
-    }
-
-    nm_assert(NM_IN_SET(value, -1, 1));
-
-    if (!g_atomic_int_compare_and_exchange(p_state, 0, value)) {
-        value = g_atomic_int_get(p_state);
-        nm_assert(NM_IN_SET(value, -1, 1));
-        return value;
-    }
-
-#undef NM_THREAD_SAFE_ON_MAIN_THREAD
-#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
-
-    if (set_default) {
-        nm_log_dbg(LOGD_PLATFORM,
-                   "platform: kernel-support for %s (%s) not detected: assume %ssupported",
-                   _nm_platform_kernel_support_info[type].name,
-                   _nm_platform_kernel_support_info[type].desc,
-                   value >= 0 ? "" : "not ");
-    } else {
-        nm_log_dbg(LOGD_PLATFORM,
-                   "platform: kernel-support for %s (%s) detected: %ssupported",
-                   _nm_platform_kernel_support_info[type].name,
-                   _nm_platform_kernel_support_info[type].desc,
-                   value >= 0 ? "" : "not ");
-    }
-
-#undef NM_THREAD_SAFE_ON_MAIN_THREAD
-#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
-
-    return value;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_platform_process_events:
- * @self: platform instance
- *
- * Process pending events or handle pending delayed-actions.
- * Effectively, this reads the netlink socket and processes
- * new netlink messages. Possibly it will raise change signals.
- */
-void
-nm_platform_process_events(NMPlatform *self)
-{
-    _CHECK_SELF_VOID(self, klass);
-
-    if (klass->process_events)
-        klass->process_events(self);
-}
-
-const NMPlatformLink *
-nm_platform_process_events_ensure_link(NMPlatform *self, int ifindex, const char *ifname)
-{
-    const NMPObject *obj;
-    gboolean         refreshed = FALSE;
-
-    g_return_val_if_fail(NM_IS_PLATFORM(self), NULL);
-
-    if (ifindex <= 0 && !ifname)
-        return NULL;
-
-    /* we look into the cache, whether a link for given ifindex/ifname
-     * exits. If not, we poll the netlink socket, maybe the event
-     * with the link is waiting.
-     *
-     * Then we try again to find the object.
-     *
-     * If the link is already cached the first time, we avoid polling
-     * the netlink socket. */
-again:
-    obj = nmp_cache_lookup_link_full(
-        nm_platform_get_cache(self),
-        ifindex,
-        ifname,
-        FALSE, /* also invisible. We don't care here whether udev is ready */
-        NM_LINK_TYPE_NONE,
-        NULL,
-        NULL);
-    if (obj)
-        return NMP_OBJECT_CAST_LINK(obj);
-    if (!refreshed) {
-        refreshed = TRUE;
-        nm_platform_process_events(self);
-        goto again;
-    }
-
-    return NULL;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_platform_sysctl_open_netdir:
- * @self: platform instance
- * @ifindex: the ifindex for which to open /sys/class/net/%s
- * @out_ifname: optional output argument of the found ifname.
- *
- * Wraps nmp_utils_sysctl_open_netdir() by first changing into the right
- * network-namespace.
- *
- * Returns: on success, the open file descriptor to the /sys/class/net/%s
- *   directory.
- */
-int
-nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname)
-{
-    const char *ifname_guess;
-    _CHECK_SELF_NETNS(self, klass, netns, -1);
-
-    g_return_val_if_fail(ifindex > 0, -1);
-
-    /* we don't have an @ifname_guess argument to make the API nicer.
-     * But still do a cache-lookup first. Chances are good that we have
-     * the right ifname cached and save if_indextoname() */
-    ifname_guess = nm_platform_link_get_name(self, ifindex);
-
-    return nmp_utils_sysctl_open_netdir(ifindex, ifname_guess, out_ifname);
-}
-
-/**
- * nm_platform_sysctl_set:
- * @self: platform instance
- * @pathid: if @dirfd is present, this must be the full path that is looked up.
- *   It is required for logging.
- * @dirfd: optional file descriptor for parent directory for openat()
- * @path: Absolute option path
- * @value: Value to write
- *
- * This function is intended to be used for writing values to sysctl-style
- * virtual runtime configuration files. This includes not only /proc/sys
- * but also for example /sys/class.
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_sysctl_set(NMPlatform *self,
-                       const char *pathid,
-                       int         dirfd,
-                       const char *path,
-                       const char *value)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(path, FALSE);
-    g_return_val_if_fail(value, FALSE);
-
-    return klass->sysctl_set(self, pathid, dirfd, path, value);
-}
-
-/**
- * nm_platform_sysctl_set_async:
- * @self: platform instance
- * @pathid: if @dirfd is present, this must be the full path that is looked up
- * @dirfd: optional file descriptor for parent directory for openat()
- * @path: absolute option path
- * @values: NULL-terminated array of strings to be written
- * @callback: function called on termination
- * @data: data passed to callback function
- * @cancellable: to cancel the operation
- *
- * This function is intended to be used for writing values to sysctl-style
- * virtual runtime configuration files. This includes not only /proc/sys
- * but also for example /sys/class. The function does not block and returns
- * immediately. The callback is always invoked, and asynchronously. The file
- * is closed after writing each value and reopened to write the next one so
- * that the function can be used safely on all /proc and /sys files,
- * independently of how /proc/sys/kernel/sysctl_writes_strict is configured.
- */
-void
-nm_platform_sysctl_set_async(NMPlatform *            self,
-                             const char *            pathid,
-                             int                     dirfd,
-                             const char *            path,
-                             const char *const *     values,
-                             NMPlatformAsyncCallback callback,
-                             gpointer                data,
-                             GCancellable *          cancellable)
-{
-    _CHECK_SELF_VOID(self, klass);
-
-    klass->sysctl_set_async(self, pathid, dirfd, path, values, callback, data, cancellable);
-}
-
-gboolean
-nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NMPlatform *self, const char *iface, int value)
-{
-    const char *path;
-    gint64      cur;
-    char        buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    /* the hop-limit provided via RA is uint8. */
-    if (value > 0xFF)
-        return FALSE;
-
-    /* don't allow unreasonable small values */
-    if (value < 10)
-        return FALSE;
-
-    path = nm_utils_sysctl_ip_conf_path(AF_INET6, buf, iface, "hop_limit");
-    cur  = nm_platform_sysctl_get_int_checked(self,
-                                             NMP_SYSCTL_PATHID_ABSOLUTE(path),
-                                             10,
-                                             1,
-                                             G_MAXINT32,
-                                             -1);
-
-    /* only allow increasing the hop-limit to avoid DOS by an attacker
-     * setting a low hop-limit (CVE-2015-2924, rh#1209902) */
-
-    if (value < cur)
-        return FALSE;
-    if (value != cur) {
-        char svalue[20];
-
-        sprintf(svalue, "%d", value);
-        nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), svalue);
-    }
-
-    return TRUE;
-}
-
-gboolean
-nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NMPlatform *self,
-                                                    const char *iface,
-                                                    guint       value_ms)
-{
-    char  path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-    char  str[128];
-    guint clamped;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    if (!value_ms)
-        return TRUE;
-
-    /* RFC 4861 says the value can't be greater than one hour.
-     * Also use a reasonable lower threshold. */
-    clamped = NM_CLAMP(value_ms, 100, 3600000);
-    nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", iface);
-    nm_sprintf_buf(str, "%u", clamped);
-    if (!nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str))
-        return FALSE;
-
-    /* Set stale time in the same way as kernel */
-    nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/gc_stale_time", iface);
-    nm_sprintf_buf(str, "%u", clamped * 3 / 1000);
-
-    return nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str);
-}
-
-gboolean
-nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NMPlatform *self,
-                                                  const char *iface,
-                                                  guint       value_ms)
-{
-    char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-    char str[128];
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    if (!value_ms)
-        return TRUE;
-
-    nm_sprintf_buf(path, "/proc/sys/net/ipv6/neigh/%s/retrans_time_ms", iface);
-    nm_sprintf_buf(str, "%u", NM_CLAMP(value_ms, 10, 3600000));
-
-    return nm_platform_sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), str);
-}
-
-/**
- * nm_platform_sysctl_get:
- * @self: platform instance
- * @dirfd: if non-negative, used to lookup the path via openat().
- * @pathid: if @dirfd is present, this must be the full path that is looked up.
- *   It is required for logging.
- * @path: Absolute path to sysctl
- *
- * Returns: (transfer full): Contents of the virtual sysctl file.
- *
- * If the path does not exist, %NULL is returned and %errno set to %ENOENT.
- */
-char *
-nm_platform_sysctl_get(NMPlatform *self, const char *pathid, int dirfd, const char *path)
-{
-    _CHECK_SELF(self, klass, NULL);
-
-    g_return_val_if_fail(path, NULL);
-
-    return klass->sysctl_get(self, pathid, dirfd, path);
-}
-
-/**
- * nm_platform_sysctl_get_int32:
- * @self: platform instance
- * @pathid: if @dirfd is present, this must be the full path that is looked up.
- *   It is required for logging.
- * @dirfd: if non-negative, used to lookup the path via openat().
- * @path: Absolute path to sysctl
- * @fallback: default value, if the content of path could not be read
- * as decimal integer.
- *
- * Returns: contents of the sysctl file parsed as s32 integer, or
- * @fallback on error. On error, %errno will be set to a non-zero
- * value, on success %errno will be set to zero.
- */
-gint32
-nm_platform_sysctl_get_int32(NMPlatform *self,
-                             const char *pathid,
-                             int         dirfd,
-                             const char *path,
-                             gint32      fallback)
-{
-    return nm_platform_sysctl_get_int_checked(self,
-                                              pathid,
-                                              dirfd,
-                                              path,
-                                              10,
-                                              G_MININT32,
-                                              G_MAXINT32,
-                                              fallback);
-}
-
-/**
- * nm_platform_sysctl_get_int_checked:
- * @self: platform instance
- * @pathid: if @dirfd is present, this must be the full path that is looked up.
- *   It is required for logging.
- * @dirfd: if non-negative, used to lookup the path via openat().
- * @path: Absolute path to sysctl
- * @base: base of numeric conversion
- * @min: minimal value that is still valid
- * @max: maximal value that is still valid
- * @fallback: default value, if the content of path could not be read
- * as valid integer.
- *
- * Returns: contents of the sysctl file parsed as s64 integer, or
- * @fallback on error. On error, %errno will be set to a non-zero
- * value. On success, %errno will be set to zero. The returned value
- * will always be in the range between @min and @max
- * (inclusive) or @fallback.
- * If the file does not exist, the fallback is returned and %errno
- * is set to ENOENT.
- */
-gint64
-nm_platform_sysctl_get_int_checked(NMPlatform *self,
-                                   const char *pathid,
-                                   int         dirfd,
-                                   const char *path,
-                                   guint       base,
-                                   gint64      min,
-                                   gint64      max,
-                                   gint64      fallback)
-{
-    char * value = NULL;
-    gint32 ret;
-    int    errsv;
-
-    _CHECK_SELF(self, klass, fallback);
-
-    g_return_val_if_fail(path, fallback);
-
-    if (!path) {
-        errno = EINVAL;
-        return fallback;
-    }
-
-    value = nm_platform_sysctl_get(self, pathid, dirfd, path);
-    if (!value) {
-        /* nm_platform_sysctl_get() set errno to ENOENT if the file does not exist.
-         * Propagate/preserve that. */
-        if (errno != ENOENT)
-            errno = EINVAL;
-        return fallback;
-    }
-
-    ret   = _nm_utils_ascii_str_to_int64(value, base, min, max, fallback);
-    errsv = errno;
-    g_free(value);
-    errno = errsv;
-    return ret;
-}
-
-/*****************************************************************************/
-
-char *
-nm_platform_sysctl_ip_conf_get(NMPlatform *self,
-                               int         addr_family,
-                               const char *ifname,
-                               const char *property)
-{
-    char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-    return nm_platform_sysctl_get(
-        self,
-        NMP_SYSCTL_PATHID_ABSOLUTE(
-            nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)));
-}
-
-gint64
-nm_platform_sysctl_ip_conf_get_int_checked(NMPlatform *self,
-                                           int         addr_family,
-                                           const char *ifname,
-                                           const char *property,
-                                           guint       base,
-                                           gint64      min,
-                                           gint64      max,
-                                           gint64      fallback)
-{
-    char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-    return nm_platform_sysctl_get_int_checked(
-        self,
-        NMP_SYSCTL_PATHID_ABSOLUTE(
-            nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)),
-        base,
-        min,
-        max,
-        fallback);
-}
-
-gboolean
-nm_platform_sysctl_ip_conf_set(NMPlatform *self,
-                               int         addr_family,
-                               const char *ifname,
-                               const char *property,
-                               const char *value)
-{
-    char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-    return nm_platform_sysctl_set(
-        self,
-        NMP_SYSCTL_PATHID_ABSOLUTE(
-            nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)),
-        value);
-}
-
-gboolean
-nm_platform_sysctl_ip_conf_set_int64(NMPlatform *self,
-                                     int         addr_family,
-                                     const char *ifname,
-                                     const char *property,
-                                     gint64      value)
-{
-    char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-    char s[64];
-
-    return nm_platform_sysctl_set(
-        self,
-        NMP_SYSCTL_PATHID_ABSOLUTE(
-            nm_utils_sysctl_ip_conf_path(addr_family, buf, ifname, property)),
-        nm_sprintf_buf(s, "%" G_GINT64_FORMAT, value));
-}
-
-int
-nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(NMPlatform *self,
-                                              const char *ifname,
-                                              gboolean    consider_all,
-                                              gboolean *  out_due_to_all)
-{
-    int val, val_all;
-
-    NM_SET_OUT(out_due_to_all, FALSE);
-
-    if (!ifname)
-        return -1;
-
-    val = nm_platform_sysctl_ip_conf_get_int_checked(self,
-                                                     AF_INET,
-                                                     ifname,
-                                                     "rp_filter",
-                                                     10,
-                                                     0,
-                                                     2,
-                                                     -1);
-    if (val == -1)
-        return -1;
-
-    /* the effectively used value is the rp_filter sysctl value of MAX(all,ifname).
-     * Note that this is the numerical MAX(), despite rp_filter "1" being more strict
-     * than "2". */
-    if (val < 2 && consider_all && !nm_streq(ifname, "all")) {
-        val_all = nm_platform_sysctl_ip_conf_get_int_checked(self,
-                                                             AF_INET,
-                                                             "all",
-                                                             "rp_filter",
-                                                             10,
-                                                             0,
-                                                             2,
-                                                             val);
-        if (val_all > val) {
-            val = val_all;
-            NM_SET_OUT(out_due_to_all, TRUE);
-        }
-    }
-
-    return val;
-}
-
-/*****************************************************************************/
-
-static int
-_link_get_all_presort(gconstpointer p_a, gconstpointer p_b, gpointer sort_by_name)
-{
-    const NMPlatformLink *a = NMP_OBJECT_CAST_LINK(*((const NMPObject **) p_a));
-    const NMPlatformLink *b = NMP_OBJECT_CAST_LINK(*((const NMPObject **) p_b));
-
-    /* Loopback always first */
-    if (a->ifindex == 1)
-        return -1;
-    if (b->ifindex == 1)
-        return 1;
-
-    if (GPOINTER_TO_INT(sort_by_name)) {
-        /* Initialized links first */
-        if (a->initialized > b->initialized)
-            return -1;
-        if (a->initialized < b->initialized)
-            return 1;
-
-        return strcmp(a->name, b->name);
-    } else
-        return a->ifindex - b->ifindex;
-}
-
-/**
- * nm_platform_link_get_all:
- * @self: platform instance
- * @sort_by_name: whether to sort by name or ifindex.
- *
- * Retrieve a snapshot of configuration for all links at once. The result is
- * owned by the caller and should be freed with g_ptr_array_unref().
- */
-GPtrArray *
-nm_platform_link_get_all(NMPlatform *self, gboolean sort_by_name)
-{
-    gs_unref_ptrarray GPtrArray *links = NULL;
-    GPtrArray *                  result;
-    guint                        i, nresult;
-    gs_unref_hashtable GHashTable *unseen = NULL;
-    const NMPlatformLink *         item;
-    NMPLookup                      lookup;
-
-    _CHECK_SELF(self, klass, NULL);
-
-    nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK);
-    links = nm_dedup_multi_objs_to_ptr_array_head(nm_platform_lookup(self, &lookup), NULL, NULL);
-    if (!links)
-        return NULL;
-
-    for (i = 0; i < links->len;) {
-        if (!nmp_object_is_visible(links->pdata[i]))
-            g_ptr_array_remove_index_fast(links, i);
-        else
-            i++;
-    }
-
-    if (links->len == 0)
-        return NULL;
-
-    /* first sort the links by their ifindex or name. Below we will sort
-     * further by moving children/slaves to the end. */
-    g_ptr_array_sort_with_data(links, _link_get_all_presort, GINT_TO_POINTER(sort_by_name));
-
-    unseen = g_hash_table_new(nm_direct_hash, NULL);
-    for (i = 0; i < links->len; i++) {
-        item = NMP_OBJECT_CAST_LINK(links->pdata[i]);
-        nm_assert(item->ifindex > 0);
-        if (!g_hash_table_insert(unseen, GINT_TO_POINTER(item->ifindex), NULL))
-            nm_assert_not_reached();
-    }
-
-#if NM_MORE_ASSERTS
-    /* Ensure that link_get_all returns a consistent and valid result. */
-    for (i = 0; i < links->len; i++) {
-        item = NMP_OBJECT_CAST_LINK(links->pdata[i]);
-
-        if (!item->ifindex)
-            continue;
-        if (item->master != 0) {
-            g_warn_if_fail(item->master > 0);
-            g_warn_if_fail(item->master != item->ifindex);
-            g_warn_if_fail(g_hash_table_contains(unseen, GINT_TO_POINTER(item->master)));
-        }
-        if (item->parent != 0) {
-            if (item->parent != NM_PLATFORM_LINK_OTHER_NETNS) {
-                g_warn_if_fail(item->parent > 0);
-                g_warn_if_fail(item->parent != item->ifindex);
-                g_warn_if_fail(g_hash_table_contains(unseen, GINT_TO_POINTER(item->parent)));
-            }
-        }
-    }
-#endif
-
-    /* Re-order the links list such that children/slaves come after all ancestors */
-    nm_assert(g_hash_table_size(unseen) == links->len);
-    nresult = links->len;
-    result  = g_ptr_array_new_full(nresult, (GDestroyNotify) nmp_object_unref);
-
-    while (TRUE) {
-        gboolean found_something = FALSE;
-        guint    first_idx       = G_MAXUINT;
-
-        for (i = 0; i < links->len; i++) {
-            item = NMP_OBJECT_CAST_LINK(links->pdata[i]);
-
-            if (!item)
-                continue;
-
-            g_assert(g_hash_table_contains(unseen, GINT_TO_POINTER(item->ifindex)));
-
-            if (item->master > 0 && g_hash_table_contains(unseen, GINT_TO_POINTER(item->master)))
-                goto skip;
-            if (item->parent > 0 && g_hash_table_contains(unseen, GINT_TO_POINTER(item->parent)))
-                goto skip;
-
-            g_hash_table_remove(unseen, GINT_TO_POINTER(item->ifindex));
-            g_ptr_array_add(result, links->pdata[i]);
-            links->pdata[i] = NULL;
-            found_something = TRUE;
-            continue;
-skip:
-            if (first_idx == G_MAXUINT)
-                first_idx = i;
-        }
-
-        if (found_something) {
-            if (first_idx == G_MAXUINT)
-                break;
-        } else {
-            nm_assert(first_idx != G_MAXUINT);
-            /* There is a loop, pop the first (remaining) element from the list.
-             * This can happen for veth pairs where each peer is parent of the other end. */
-            item = NMP_OBJECT_CAST_LINK(links->pdata[first_idx]);
-            nm_assert(item);
-            g_hash_table_remove(unseen, GINT_TO_POINTER(item->ifindex));
-            g_ptr_array_add(result, links->pdata[first_idx]);
-            links->pdata[first_idx] = NULL;
-        }
-        nm_assert(result->len < nresult);
-    }
-    nm_assert(result->len == nresult);
-
-    return result;
-}
-
-/*****************************************************************************/
-
-const NMPObject *
-nm_platform_link_get_obj(NMPlatform *self, int ifindex, gboolean visible_only)
-{
-    const NMPObject *obj_cache;
-
-    _CHECK_SELF(self, klass, NULL);
-
-    obj_cache = nmp_cache_lookup_link(nm_platform_get_cache(self), ifindex);
-    if (!obj_cache || (visible_only && !nmp_object_is_visible(obj_cache)))
-        return NULL;
-    return obj_cache;
-}
-
-/*****************************************************************************/
-
-/**
- * nm_platform_link_get:
- * @self: platform instance
- * @ifindex: ifindex of the link
- *
- * Lookup the internal NMPlatformLink object.
- *
- * Returns: %NULL, if such a link exists or the internal
- * platform link object. Do not modify the returned value.
- * Also, be aware that any subsequent platform call might
- * invalidate/modify the returned instance.
- **/
-const NMPlatformLink *
-nm_platform_link_get(NMPlatform *self, int ifindex)
-{
-    return NMP_OBJECT_CAST_LINK(nm_platform_link_get_obj(self, ifindex, TRUE));
-}
-
-/**
- * nm_platform_link_get_by_ifname:
- * @self: platform instance
- * @ifname: the ifname
- *
- * Returns: the first #NMPlatformLink instance with the given name.
- **/
-const NMPlatformLink *
-nm_platform_link_get_by_ifname(NMPlatform *self, const char *ifname)
-{
-    const NMPObject *obj;
-
-    _CHECK_SELF(self, klass, NULL);
-
-    if (!ifname || !*ifname)
-        return NULL;
-
-    obj = nmp_cache_lookup_link_full(nm_platform_get_cache(self),
-                                     0,
-                                     ifname,
-                                     TRUE,
-                                     NM_LINK_TYPE_NONE,
-                                     NULL,
-                                     NULL);
-    return NMP_OBJECT_CAST_LINK(obj);
-}
-
-struct _nm_platform_link_get_by_address_data {
-    gconstpointer data;
-    guint8        len;
-};
-
-static gboolean
-_nm_platform_link_get_by_address_match_link(const NMPObject *                             obj,
-                                            struct _nm_platform_link_get_by_address_data *d)
-{
-    return obj->link.l_address.len == d->len && !memcmp(obj->link.l_address.data, d->data, d->len);
-}
-
-/**
- * nm_platform_link_get_by_address:
- * @self: platform instance
- * @address: a pointer to the binary hardware address
- * @length: the size of @address in bytes
- *
- * Returns: the first #NMPlatformLink object with a matching
- * address.
- **/
-const NMPlatformLink *
-nm_platform_link_get_by_address(NMPlatform *  self,
-                                NMLinkType    link_type,
-                                gconstpointer address,
-                                size_t        length)
-{
-    const NMPObject *                            obj;
-    struct _nm_platform_link_get_by_address_data d = {
-        .data = address,
-        .len  = length,
-    };
-
-    _CHECK_SELF(self, klass, NULL);
-
-    if (length == 0)
-        return NULL;
-
-    if (length > NM_UTILS_HWADDR_LEN_MAX)
-        g_return_val_if_reached(NULL);
-    if (!address)
-        g_return_val_if_reached(NULL);
-
-    obj = nmp_cache_lookup_link_full(nm_platform_get_cache(self),
-                                     0,
-                                     NULL,
-                                     TRUE,
-                                     link_type,
-                                     (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link,
-                                     &d);
-    return NMP_OBJECT_CAST_LINK(obj);
-}
-
-static int
-_link_add_check_existing(NMPlatform *           self,
-                         const char *           name,
-                         NMLinkType             type,
-                         const NMPlatformLink **out_link)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get_by_ifname(self, name);
-    if (pllink) {
-        gboolean wrong_type;
-
-        wrong_type = type != NM_LINK_TYPE_NONE && pllink->type != type;
-        _LOG2D("link: skip adding link due to existing interface of type %s%s%s",
-               nm_link_type_to_string(pllink->type),
-               wrong_type ? ", expected " : "",
-               wrong_type ? nm_link_type_to_string(type) : "");
-        if (out_link)
-            *out_link = pllink;
-        if (wrong_type)
-            return -NME_PL_WRONG_TYPE;
-        return -NME_PL_EXISTS;
-    }
-    if (out_link)
-        *out_link = NULL;
-    return 0;
-}
-
-/**
- * nm_platform_link_add:
- * @self: platform instance
- * @type: Interface type
- * @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
- * @type, return -NME_PL_EXISTS and returns the link
- * in @out_link.  If the interface already exists and is not of type @type,
- * return -NME_PL_WRONG_TYPE.
- *
- * Any link-changed ADDED signal will be emitted directly, before this
- * function finishes.
- *
- * Returns: the negative nm-error on failure.
- */
-int
-nm_platform_link_add(NMPlatform *           self,
-                     NMLinkType             type,
-                     const char *           name,
-                     int                    parent,
-                     const void *           address,
-                     size_t                 address_len,
-                     guint32                mtu,
-                     gconstpointer          extra_data,
-                     const NMPlatformLink **out_link)
-{
-    int  r;
-    char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
-    char mtu_buf[16];
-    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(parent >= 0, -NME_BUG);
-
-    r = _link_add_check_existing(self, name, type, out_link);
-    if (r < 0)
-        return r;
-
-    _LOG2D("link: adding link: "
-           "%s "    /* type */
-           "\"%s\"" /* name */
-           "%s%s"   /* parent */
-           "%s%s"   /* address */
-           "%s%s"   /* mtu */
-           "%s"     /* extra_data */
-           "",
-           nm_link_type_to_string(type),
-           name,
-           parent > 0 ? ", parent " : "",
-           parent > 0 ? nm_sprintf_buf(parent_buf, "%d", parent) : "",
-           address ? ", address: " : "",
-           address ? _nm_utils_hwaddr_ntoa(address, address_len, FALSE, addr_buf, sizeof(addr_buf))
-                   : "",
-           mtu ? ", mtu: " : "",
-           mtu ? nm_sprintf_buf(mtu_buf, "%u", mtu) : "",
-           ({
-               char *buf_p   = buf;
-               gsize buf_len = sizeof(buf);
-
-               buf[0] = '\0';
-
-               switch (type) {
-               case NM_LINK_TYPE_BRIDGE:
-                   nm_utils_strbuf_append_str(&buf_p, &buf_len, ", ");
-                   nm_platform_lnk_bridge_to_string((const NMPlatformLnkBridge *) extra_data,
-                                                    buf_p,
-                                                    buf_len);
-                   break;
-               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, mtu, extra_data, out_link);
-}
-
-/**
- * nm_platform_link_delete:
- * @self: platform instance
- * @ifindex: Interface index
- */
-gboolean
-nm_platform_link_delete(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    _LOG3D("link: deleting");
-    return klass->link_delete(self, ifindex);
-}
-
-/**
- * nm_platform_link_set_netns:
- * @self: platform instance
- * @ifindex: Interface index
- * @netns_fd: the file descriptor for the new netns.
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(netns_fd > 0, FALSE);
-
-    _LOG3D("link: move link to network namespace with fd %d", netns_fd);
-    return klass->link_set_netns(self, ifindex, netns_fd);
-}
-
-/**
- * nm_platform_link_get_index:
- * @self: platform instance
- * @name: Interface name
- *
- * Returns: The interface index corresponding to the given interface name
- * or 0. Interface name is owned by #NMPlatform, don't free it.
- */
-int
-nm_platform_link_get_ifindex(NMPlatform *self, const char *name)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get_by_ifname(self, name);
-    return pllink ? pllink->ifindex : 0;
-}
-
-const char *
-nm_platform_if_indextoname(NMPlatform *self, int ifindex, char out_ifname[static 16 /* IFNAMSIZ */])
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    return nmp_utils_if_indextoname(ifindex, out_ifname);
-}
-
-int
-nm_platform_if_nametoindex(NMPlatform *self, const char *ifname)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    return nmp_utils_if_nametoindex(ifname);
-}
-
-/**
- * nm_platform_link_get_name:
- * @self: platform instance
- * @name: Interface name
- *
- * Returns: The interface name corresponding to the given interface index
- * or %NULL.
- */
-const char *
-nm_platform_link_get_name(NMPlatform *self, int ifindex)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, ifindex);
-    return pllink ? pllink->name : NULL;
-}
-
-/**
- * nm_platform_link_get_type:
- * @self: platform instance
- * @ifindex: Interface index.
- *
- * Returns: Link type constant as defined in nm-platform.h. On error,
- * NM_LINK_TYPE_NONE is returned.
- */
-NMLinkType
-nm_platform_link_get_type(NMPlatform *self, int ifindex)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, ifindex);
-    return pllink ? pllink->type : NM_LINK_TYPE_NONE;
-}
-
-/**
- * nm_platform_link_get_type_name:
- * @self: platform instance
- * @ifindex: Interface index.
- *
- * Returns: A string describing the type of link. In some cases this
- * may be more specific than nm_platform_link_get_type(), but in
- * other cases it may not. On error, %NULL is returned.
- */
-const char *
-nm_platform_link_get_type_name(NMPlatform *self, int ifindex)
-{
-    const NMPObject *obj;
-
-    obj = nm_platform_link_get_obj(self, ifindex, TRUE);
-    if (!obj)
-        return NULL;
-
-    if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
-        /* We could detect the @link_type. In this case the function returns
-         * our internal module names, which differs from rtnl_link_get_type():
-         *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
-         *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
-         * Note that this functions is only used by NMDeviceGeneric to
-         * set type_description. */
-        return nm_link_type_to_string(obj->link.type);
-    }
-    /* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */
-    return obj->link.kind ?: "unknown";
-}
-
-static gboolean
-link_get_udev_property(NMPlatform *self, int ifindex, const char *name, const char **out_value)
-{
-    struct udev_device *udevice = NULL;
-    const char *        uproperty;
-
-    udevice = nm_platform_link_get_udev_device(self, ifindex);
-    if (!udevice)
-        return FALSE;
-
-    uproperty = udev_device_get_property_value(udevice, name);
-    if (!uproperty)
-        return FALSE;
-
-    NM_SET_OUT(out_value, uproperty);
-    return TRUE;
-}
-
-/**
- * nm_platform_link_get_unmanaged:
- * @self: platform instance
- * @ifindex: interface index
- * @unmanaged: management status (in case %TRUE is returned)
- *
- * Returns: %TRUE if platform overrides NM default-unmanaged status,
- * %FALSE otherwise (with @unmanaged unmodified).
- */
-gboolean
-nm_platform_link_get_unmanaged(NMPlatform *self, int ifindex, gboolean *unmanaged)
-{
-    const char *value;
-
-    if (link_get_udev_property(self, ifindex, "NM_UNMANAGED", &value)) {
-        NM_SET_OUT(unmanaged, nm_udev_utils_property_as_boolean(value));
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-/**
- * nm_platform_link_is_software:
- * @self: platform instance
- * @ifindex: Interface index.
- *
- * Returns: %TRUE if ifindex belongs to a software interface, not backed by
- * a physical device.
- */
-gboolean
-nm_platform_link_is_software(NMPlatform *self, int ifindex)
-{
-    return nm_link_type_is_software(nm_platform_link_get_type(self, ifindex));
-}
-
-/**
- * nm_platform_link_supports_slaves:
- * @self: platform instance
- * @ifindex: Interface index.
- *
- * Returns: %TRUE if ifindex belongs to an interface capable of enslaving
- * other interfaces.
- */
-gboolean
-nm_platform_link_supports_slaves(NMPlatform *self, int ifindex)
-{
-    return nm_link_type_supports_slaves(nm_platform_link_get_type(self, ifindex));
-}
-
-/**
- * nm_platform_link_refresh:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Reload the cache for ifindex synchronously.
- */
-gboolean
-nm_platform_link_refresh(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    if (klass->link_refresh)
-        return klass->link_refresh(self, ifindex);
-
-    return TRUE;
-}
-
-int
-nm_platform_link_get_ifi_flags(NMPlatform *self, int ifindex, guint requested_flags)
-{
-    const NMPlatformLink *pllink;
-
-    /* include invisible links (only in netlink, not udev). */
-    pllink = NMP_OBJECT_CAST_LINK(nm_platform_link_get_obj(self, ifindex, FALSE));
-    if (!pllink)
-        return -ENODEV;
-
-    /* Errors are signaled as negative values. That means, you cannot request
-     * the most significant bit (2^31) with this API. Assert against that. */
-    nm_assert((int) requested_flags >= 0);
-    nm_assert(requested_flags < (guint) G_MAXINT);
-
-    return (int) (pllink->n_ifi_flags & requested_flags);
-}
-
-/**
- * nm_platform_link_is_up:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Check if the interface is up.
- */
-gboolean
-nm_platform_link_is_up(NMPlatform *self, int ifindex)
-{
-    return nm_platform_link_get_ifi_flags(self, ifindex, IFF_UP) == IFF_UP;
-}
-
-/**
- * nm_platform_link_is_connected:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Check if the interface is connected.
- */
-gboolean
-nm_platform_link_is_connected(NMPlatform *self, int ifindex)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, ifindex);
-    return pllink ? pllink->connected : FALSE;
-}
-
-/**
- * nm_platform_link_uses_arp:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Check if the interface is configured to use ARP.
- */
-gboolean
-nm_platform_link_uses_arp(NMPlatform *self, int ifindex)
-{
-    int f;
-
-    f = nm_platform_link_get_ifi_flags(self, ifindex, IFF_NOARP);
-
-    if (f < 0)
-        return FALSE;
-    if (f == IFF_NOARP)
-        return FALSE;
-    return TRUE;
-}
-
-/**
- * nm_platform_link_set_ipv6_token:
- * @self: platform instance
- * @ifindex: Interface index
- * @iid: Tokenized interface identifier
- *
- * Sets then IPv6 tokenized interface identifier.
- *
- * Returns: %TRUE a tokenized identifier was available
- */
-gboolean
-nm_platform_link_set_ipv6_token(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    if (klass->link_set_token)
-        return klass->link_set_token(self, ifindex, iid);
-    return FALSE;
-}
-
-const char *
-nm_platform_link_get_udi(NMPlatform *self, int ifindex)
-{
-    struct udev_device *device;
-
-    device = nm_platform_link_get_udev_device(self, ifindex);
-    return device ? udev_device_get_syspath(device) : NULL;
-}
-
-const char *
-nm_platform_link_get_path(NMPlatform *self, int ifindex)
-{
-    const char *value = NULL;
-
-    link_get_udev_property(self, ifindex, "ID_PATH", &value);
-
-    return value;
-}
-
-struct udev_device *
-nm_platform_link_get_udev_device(NMPlatform *self, int ifindex)
-{
-    const NMPObject *obj_cache;
-
-    obj_cache = nm_platform_link_get_obj(self, ifindex, FALSE);
-    return obj_cache ? obj_cache->_link.udev.device : NULL;
-}
-
-/**
- * nm_platform_link_get_user_ip6vll_enabled:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Check whether NM handles IPv6LL address creation for the link.  If the
- * platform or OS doesn't support changing the IPv6LL address mode, this call
- * will fail and return %FALSE.
- *
- * Returns: %TRUE if NM handles the IPv6LL address for @ifindex
- */
-gboolean
-nm_platform_link_get_user_ipv6ll_enabled(NMPlatform *self, int ifindex)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, ifindex);
-    if (pllink && pllink->inet6_addr_gen_mode_inv)
-        return _nm_platform_uint8_inv(pllink->inet6_addr_gen_mode_inv) == NM_IN6_ADDR_GEN_MODE_NONE;
-    return FALSE;
-}
-
-/**
- * nm_platform_link_set_user_ip6vll_enabled:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Set whether NM handles IPv6LL address creation for the link.  If the
- * platform or OS doesn't support changing the IPv6LL address mode, this call
- * will fail and return %FALSE.
- *
- * Returns: the negative nm-error on failure.
- */
-int
-nm_platform_link_set_user_ipv6ll_enabled(NMPlatform *self, int ifindex, gboolean enabled)
-{
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    g_return_val_if_fail(ifindex > 0, -NME_BUG);
-
-    return klass->link_set_user_ipv6ll_enabled(self, ifindex, enabled);
-}
-
-/**
- * nm_platform_link_set_address:
- * @self: platform instance
- * @ifindex: Interface index
- * @address: The new MAC address
- *
- * Set interface MAC address.
- */
-int
-nm_platform_link_set_address(NMPlatform *self, int ifindex, gconstpointer address, size_t length)
-{
-    gs_free char *mac = NULL;
-
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    g_return_val_if_fail(ifindex > 0, -NME_BUG);
-    g_return_val_if_fail(address, -NME_BUG);
-    g_return_val_if_fail(length > 0, -NME_BUG);
-
-    _LOG3D("link: setting hardware address to %s", (mac = nm_utils_hwaddr_ntoa(address, length)));
-    return klass->link_set_address(self, ifindex, address, length);
-}
-
-/**
- * nm_platform_link_get_address:
- * @self: platform instance
- * @ifindex: Interface index
- * @length: Pointer to a variable to store address length
- *
- * Returns: the interface hardware address as an array of bytes of
- * length @length.
- */
-gconstpointer
-nm_platform_link_get_address(NMPlatform *self, int ifindex, size_t *length)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, ifindex);
-    return nmp_link_address_get(pllink ? &pllink->l_address : NULL, length);
-}
-
-/**
- * nm_platform_link_get_permanent_address:
- * @self: platform instance
- * @ifindex: Interface index
- * @buf: buffer of at least %NM_UTILS_HWADDR_LEN_MAX bytes, on success
- * the permanent hardware address
- * @length: Pointer to a variable to store address length
- *
- * Returns: %TRUE on success, %FALSE on failure to read the permanent hardware
- * address.
- */
-gboolean
-nm_platform_link_get_permanent_address(NMPlatform *self, int ifindex, guint8 *buf, size_t *length)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    if (length)
-        *length = 0;
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(buf, FALSE);
-    g_return_val_if_fail(length, FALSE);
-
-    if (klass->link_get_permanent_address)
-        return klass->link_get_permanent_address(self, ifindex, buf, length);
-    return FALSE;
-}
-
-gboolean
-nm_platform_link_supports_carrier_detect(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    return klass->link_supports_carrier_detect(self, ifindex);
-}
-
-gboolean
-nm_platform_link_supports_vlans(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    return klass->link_supports_vlans(self, ifindex);
-}
-
-gboolean
-nm_platform_link_supports_sriov(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    return klass->link_supports_sriov(self, ifindex);
-}
-
-/**
- * nm_platform_link_set_sriov_params:
- * @self: platform instance
- * @ifindex: the index of the interface to change
- * @num_vfs: the number of VFs to create
- * @autoprobe: the new autoprobe-drivers value (pass
- *     %NM_OPTION_BOOL_DEFAULT to keep current value)
- * @callback: called when the operation finishes
- * @callback_data: data passed to @callback
- * @cancellable: cancellable to abort the operation
- *
- * Sets SR-IOV parameters asynchronously without
- * blocking the main thread. The callback function is
- * always invoked, and asynchronously.
- */
-void
-nm_platform_link_set_sriov_params_async(NMPlatform *            self,
-                                        int                     ifindex,
-                                        guint                   num_vfs,
-                                        NMOptionBool            autoprobe,
-                                        NMPlatformAsyncCallback callback,
-                                        gpointer                callback_data,
-                                        GCancellable *          cancellable)
-{
-    _CHECK_SELF_VOID(self, klass);
-
-    g_return_if_fail(ifindex > 0);
-
-    _LOG3D("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe);
-    klass->link_set_sriov_params_async(self,
-                                       ifindex,
-                                       num_vfs,
-                                       autoprobe,
-                                       callback,
-                                       callback_data,
-                                       cancellable);
-}
-
-gboolean
-nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs)
-{
-    guint i;
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    _LOG3D("link: setting VFs");
-    for (i = 0; vfs[i]; i++) {
-        const NMPlatformVF *vf = vfs[i];
-
-        _LOG3D("link:   VF %s", nm_platform_vf_to_string(vf, NULL, 0));
-    }
-
-    return klass->link_set_sriov_vfs(self, ifindex, vfs);
-}
-
-gboolean
-nm_platform_link_set_bridge_vlans(NMPlatform *                       self,
-                                  int                                ifindex,
-                                  gboolean                           on_master,
-                                  const NMPlatformBridgeVlan *const *vlans)
-{
-    guint i;
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    _LOG3D("link: %s bridge VLANs on %s",
-           vlans ? "setting" : "clearing",
-           on_master ? "master" : "self");
-    if (vlans) {
-        for (i = 0; vlans[i]; i++) {
-            const NMPlatformBridgeVlan *vlan = vlans[i];
-
-            _LOG3D("link:   bridge VLAN %s", nm_platform_bridge_vlan_to_string(vlan, NULL, 0));
-        }
-    }
-
-    return klass->link_set_bridge_vlans(self, ifindex, on_master, vlans);
-}
-
-/**
- * nm_platform_link_set_up:
- * @self: platform instance
- * @ifindex: Interface index
- * @out_no_firmware: (allow-none): if the failure reason is due to missing firmware.
- *
- * Bring the interface up.
- */
-gboolean
-nm_platform_link_set_up(NMPlatform *self, int ifindex, gboolean *out_no_firmware)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    _LOG3D("link: setting up");
-    return klass->link_set_up(self, ifindex, out_no_firmware);
-}
-
-/**
- * nm_platform_link_set_down:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Take the interface down.
- */
-gboolean
-nm_platform_link_set_down(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    _LOG3D("link: setting down");
-    return klass->link_set_down(self, ifindex);
-}
-
-/**
- * nm_platform_link_set_arp:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Enable ARP on the interface.
- */
-gboolean
-nm_platform_link_set_arp(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    _LOG3D("link: setting arp");
-    return klass->link_set_arp(self, ifindex);
-}
-
-/**
- * nm_platform_link_set_noarp:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Disable ARP on the interface.
- */
-gboolean
-nm_platform_link_set_noarp(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    _LOG3D("link: setting noarp");
-    return klass->link_set_noarp(self, ifindex);
-}
-
-/**
- * nm_platform_link_set_mtu:
- * @self: platform instance
- * @ifindex: Interface index
- * @mtu: The new MTU value
- *
- * Set interface MTU.
- */
-int
-nm_platform_link_set_mtu(NMPlatform *self, int ifindex, guint32 mtu)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-    g_return_val_if_fail(mtu > 0, FALSE);
-
-    _LOG3D("link: setting mtu %" G_GUINT32_FORMAT, mtu);
-    return klass->link_set_mtu(self, ifindex, mtu);
-}
-
-/**
- * nm_platform_link_get_mtu:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Returns: MTU value for the interface or 0 on error.
- */
-guint32
-nm_platform_link_get_mtu(NMPlatform *self, int ifindex)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, ifindex);
-    return pllink ? pllink->mtu : 0;
-}
-
-/**
- * nm_platform_link_set_name:
- * @self: platform instance
- * @ifindex: Interface index
- * @name: The new interface name
- *
- * Set interface name.
- */
-gboolean
-nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-    g_return_val_if_fail(name, FALSE);
-
-    _LOG3D("link: setting name %s", name);
-
-    if (strlen(name) + 1 > IFNAMSIZ)
-        return FALSE;
-
-    return klass->link_set_name(self, ifindex, name);
-}
-
-/**
- * nm_platform_link_get_physical_port_id:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * The physical port ID, if present, indicates some unique identifier of
- * the parent interface (eg, the physical port of which this link is a child).
- * Two links that report the same physical port ID can be assumed to be
- * children of the same physical port and may share resources that limit
- * their abilities.
- *
- * Returns: physical port ID for the interface, or %NULL on error
- * or if the interface has no physical port ID.
- */
-char *
-nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, NULL);
-
-    g_return_val_if_fail(ifindex >= 0, NULL);
-
-    if (klass->link_get_physical_port_id)
-        return klass->link_get_physical_port_id(self, ifindex);
-    return NULL;
-}
-
-/**
- * nm_platform_link_get_dev_id:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * In contrast to the physical device ID (which indicates which parent a
- * child has) the device ID differentiates sibling devices that may share
- * the same MAC address.
- *
- * Returns: device ID for the interface, or 0 on error or if the
- * interface has no device ID.
- */
-guint
-nm_platform_link_get_dev_id(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, 0);
-
-    g_return_val_if_fail(ifindex >= 0, 0);
-
-    if (klass->link_get_dev_id)
-        return klass->link_get_dev_id(self, ifindex);
-    return 0;
-}
-
-/**
- * nm_platform_link_get_wake_onlan:
- * @self: platform instance
- * @ifindex: Interface index
- *
- * Returns: the "Wake-on-LAN" status for @ifindex.
- */
-gboolean
-nm_platform_link_get_wake_on_lan(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    if (klass->link_get_wake_on_lan)
-        return klass->link_get_wake_on_lan(self, ifindex);
-    return FALSE;
-}
-
-/**
- * nm_platform_link_get_driver_info:
- * @self: platform instance
- * @ifindex: Interface index
- * @out_driver_name: (transfer full): on success, the driver name if available
- * @out_driver_version: (transfer full): on success, the driver version if available
- * @out_fw_version: (transfer full): on success, the firmware version if available
- *
- * Returns: %TRUE on success (though @out_driver_name, @out_driver_version and
- * @out_fw_version can be %NULL if no information was available), %FALSE on
- * failure.
- */
-gboolean
-nm_platform_link_get_driver_info(NMPlatform *self,
-                                 int         ifindex,
-                                 char **     out_driver_name,
-                                 char **     out_driver_version,
-                                 char **     out_fw_version)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex >= 0, FALSE);
-
-    return klass->link_get_driver_info(self,
-                                       ifindex,
-                                       out_driver_name,
-                                       out_driver_version,
-                                       out_fw_version);
-}
-
-/**
- * nm_platform_link_enslave:
- * @self: platform instance
- * @master: Interface index of the master
- * @ifindex: Interface index of the slave
- *
- * Enslave @ifindex to @master.
- */
-gboolean
-nm_platform_link_enslave(NMPlatform *self, int master, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(master > 0, FALSE);
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    _LOG3D("link: enslaving to master '%s'", nm_platform_link_get_name(self, master));
-    return klass->link_enslave(self, master, ifindex);
-}
-
-/**
- * nm_platform_link_release:
- * @self: platform instance
- * @master: Interface index of the master
- * @ifindex: Interface index of the slave
- *
- * Release @slave from @master.
- */
-gboolean
-nm_platform_link_release(NMPlatform *self, int master, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(master > 0, FALSE);
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    if (nm_platform_link_get_master(self, ifindex) != master)
-        return FALSE;
-
-    _LOG3D("link: releasing from master '%s'", nm_platform_link_get_name(self, master));
-    return klass->link_release(self, master, ifindex);
-}
-
-/**
- * nm_platform_link_get_master:
- * @self: platform instance
- * @slave: Interface index of the slave.
- *
- * Returns: Interface index of the slave's master.
- */
-int
-nm_platform_link_get_master(NMPlatform *self, int slave)
-{
-    const NMPlatformLink *pllink;
-
-    pllink = nm_platform_link_get(self, slave);
-    return pllink ? pllink->master : 0;
-}
-
-/*****************************************************************************/
-
-gboolean
-nm_platform_link_can_assume(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    if (klass->link_can_assume)
-        return klass->link_can_assume(self, ifindex);
-    g_return_val_if_reached(FALSE);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_platform_link_get_lnk:
- * @self: the platform instance
- * @ifindex: the link ifindex to lookup
- * @link_type: filter by link-type.
- * @out_link: (allow-none): returns the platform link instance
- *
- * If the function returns %NULL, that could mean that no such ifindex
- * exists, of that the link has no lnk data. You can find that out
- * by checking @out_link. @out_link will always be set if a link
- * with @ifindex exists.
- *
- * If @link_type is %NM_LINK_TYPE_NONE, the function returns the lnk
- * object if it is present. If you set link-type, you can be sure
- * that only a link type of the matching type is returned (or %NULL).
- *
- * Returns: the internal link lnk object. The returned object
- * is owned by the platform cache and must not be modified. Note
- * however, that the object is guaranteed to be immutable, so
- * you can safely take a reference and keep it for yourself
- * (but don't modify it).
- */
-const NMPObject *
-nm_platform_link_get_lnk(NMPlatform *           self,
-                         int                    ifindex,
-                         NMLinkType             link_type,
-                         const NMPlatformLink **out_link)
-{
-    const NMPObject *obj;
-
-    obj = nm_platform_link_get_obj(self, ifindex, TRUE);
-    if (!obj) {
-        NM_SET_OUT(out_link, NULL);
-        return NULL;
-    }
-
-    NM_SET_OUT(out_link, &obj->link);
-
-    if (!obj->_link.netlink.lnk)
-        return NULL;
-    if (link_type != NM_LINK_TYPE_NONE
-        && (link_type != obj->link.type
-            || link_type != NMP_OBJECT_GET_CLASS(obj->_link.netlink.lnk)->lnk_link_type))
-        return NULL;
-
-    return obj->_link.netlink.lnk;
-}
-
-static gconstpointer
-_link_get_lnk(NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
-{
-    const NMPObject *lnk;
-
-    lnk = nm_platform_link_get_lnk(self, ifindex, link_type, out_link);
-    return lnk ? &lnk->object : NULL;
-}
-
-const NMPlatformLnkBridge *
-nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_BRIDGE, out_link);
-}
-
-const NMPlatformLnkGre *
-nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GRE, out_link);
-}
-
-const NMPlatformLnkGre *
-nm_platform_link_get_lnk_gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_GRETAP, out_link);
-}
-
-const NMPlatformLnkInfiniband *
-nm_platform_link_get_lnk_infiniband(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_INFINIBAND, out_link);
-}
-
-const NMPlatformLnkIp6Tnl *
-nm_platform_link_get_lnk_ip6tnl(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6TNL, out_link);
-}
-
-const NMPlatformLnkIp6Tnl *
-nm_platform_link_get_lnk_ip6gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6GRE, out_link);
-}
-
-const NMPlatformLnkIp6Tnl *
-nm_platform_link_get_lnk_ip6gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IP6GRETAP, out_link);
-}
-
-const NMPlatformLnkIpIp *
-nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IPIP, out_link);
-}
-
-const NMPlatformLnkMacsec *
-nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACSEC, out_link);
-}
-
-const NMPlatformLnkMacvlan *
-nm_platform_link_get_lnk_macvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_MACVLAN, out_link);
-}
-
-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);
-}
-
-const NMPlatformLnkSit *
-nm_platform_link_get_lnk_sit(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_SIT, out_link);
-}
-
-const NMPlatformLnkTun *
-nm_platform_link_get_lnk_tun(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_TUN, out_link);
-}
-
-const NMPlatformLnkVlan *
-nm_platform_link_get_lnk_vlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    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)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_VXLAN, out_link);
-}
-
-const NMPlatformLnkWireGuard *
-nm_platform_link_get_lnk_wireguard(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
-{
-    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_WIREGUARD, out_link);
-}
-
-/*****************************************************************************/
-
-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"),
-    NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT, "has-listen-port"),
-    NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK, "has-fwmark"), );
-
-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"),
-    NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL, "ka"),
-    NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT, "ep"),
-    NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS, "aips"),
-    NM_UTILS_FLAGS2STR(NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS, "remove-aips"), );
-
-int
-nm_platform_link_wireguard_change(NMPlatform *                              self,
-                                  int                                       ifindex,
-                                  const NMPlatformLnkWireGuard *            lnk_wireguard,
-                                  const NMPWireGuardPeer *                  peers,
-                                  const NMPlatformWireGuardChangePeerFlags *peer_flags,
-                                  guint                                     peers_len,
-                                  NMPlatformWireGuardChangeFlags            change_flags)
-{
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    nm_assert(klass->link_wireguard_change);
-
-    if (_LOGD_ENABLED()) {
-        char buf_lnk[256];
-        char buf_peers[512];
-        char buf_change_flags[100];
-
-        buf_peers[0] = '\0';
-        if (peers_len > 0) {
-            char *b   = buf_peers;
-            gsize len = sizeof(buf_peers);
-            guint i;
-
-            nm_utils_strbuf_append_str(&b, &len, " { ");
-            for (i = 0; i < peers_len; i++) {
-                nm_utils_strbuf_append_str(&b, &len, " { ");
-                nm_platform_wireguard_peer_to_string(&peers[i], b, len);
-                nm_utils_strbuf_seek_end(&b, &len);
-                if (peer_flags) {
-                    nm_utils_strbuf_append(
-                        &b,
-                        &len,
-                        " (%s)",
-                        _wireguard_change_peer_flags_to_string(peer_flags[i],
-                                                               buf_change_flags,
-                                                               sizeof(buf_change_flags)));
-                }
-                nm_utils_strbuf_append_str(&b, &len, " } ");
-            }
-            nm_utils_strbuf_append_str(&b, &len, "}");
-        }
-
-        _LOG3D("link: change wireguard ifindex %d, %s, (%s), %u peers%s",
-               ifindex,
-               nm_platform_lnk_wireguard_to_string(lnk_wireguard, buf_lnk, sizeof(buf_lnk)),
-               _wireguard_change_flags_to_string(change_flags,
-                                                 buf_change_flags,
-                                                 sizeof(buf_change_flags)),
-               peers_len,
-               buf_peers);
-    }
-
-    return klass->link_wireguard_change(self,
-                                        ifindex,
-                                        lnk_wireguard,
-                                        peers,
-                                        peer_flags,
-                                        peers_len,
-                                        change_flags);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_platform_link_tun_add:
- * @self: platform instance
- * @name: new interface name
- * @tap: whether the interface is a TAP
- * @owner: interface owner or -1
- * @group: interface group or -1
- * @pi: whether to clear the IFF_NO_PI flag
- * @vnet_hdr: whether to set the IFF_VNET_HDR flag
- * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
- * @out_link: on success, the link object
- * @out_fd: (allow-none): if give, return the file descriptor for the
- *   created device. Note that when creating a non-persistent device,
- *   this argument is mandatory, otherwise it makes no sense
- *   to create such an interface.
- *   The caller is responsible for closing this file descriptor.
- *
- * Create a TUN or TAP interface.
- */
-int
-nm_platform_link_tun_add(NMPlatform *            self,
-                         const char *            name,
-                         const NMPlatformLnkTun *props,
-                         const NMPlatformLink ** out_link,
-                         int *                   out_fd)
-{
-    char b[255];
-    int  r;
-
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    g_return_val_if_fail(name, -NME_BUG);
-    g_return_val_if_fail(props, -NME_BUG);
-    g_return_val_if_fail(NM_IN_SET(props->type, IFF_TUN, IFF_TAP), -NME_BUG);
-
-    /* creating a non-persistent device requires that the caller handles
-     * the file descriptor. */
-    g_return_val_if_fail(props->persist || out_fd, -NME_BUG);
-
-    NM_SET_OUT(out_fd, -1);
-
-    r = _link_add_check_existing(self, name, NM_LINK_TYPE_TUN, out_link);
-    if (r < 0)
-        return r;
-
-    _LOG2D("link: adding link %s", nm_platform_lnk_tun_to_string(props, b, sizeof(b)));
-
-    if (!klass->link_tun_add(self, name, props, out_link, out_fd))
-        return -NME_UNSPEC;
-    return 0;
-}
-
-gboolean
-nm_platform_link_6lowpan_get_properties(NMPlatform *self, int ifindex, int *out_parent)
-{
-    const NMPlatformLink *plink;
-
-    plink = nm_platform_link_get(self, ifindex);
-    if (!plink)
-        return FALSE;
-
-    if (plink->type != NM_LINK_TYPE_6LOWPAN)
-        return FALSE;
-
-    if (plink->parent != 0) {
-        NM_SET_OUT(out_parent, plink->parent);
-        return TRUE;
-    }
-
-    /* As of 4.16 kernel does not expose the peer_ifindex as IFA_LINK.
-     * Find the WPAN device with the same MAC address. */
-    if (out_parent) {
-        const NMPlatformLink *parent_plink;
-
-        parent_plink = nm_platform_link_get_by_address(self,
-                                                       NM_LINK_TYPE_WPAN,
-                                                       plink->l_address.data,
-                                                       plink->l_address.len);
-        NM_SET_OUT(out_parent, parent_plink ? parent_plink->ifindex : -1);
-    }
-
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-static gboolean
-link_set_option(NMPlatform *self,
-                int         ifindex,
-                const char *category,
-                const char *option,
-                const char *value)
-{
-    nm_auto_close int dirfd = -1;
-    char              ifname_verified[IFNAMSIZ];
-    const char *      path;
-
-    if (!category || !option)
-        return FALSE;
-
-    dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified);
-    if (dirfd < 0)
-        return FALSE;
-
-    path =
-        nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option);
-    return nm_platform_sysctl_set(self,
-                                  NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path),
-                                  value);
-}
-
-static char *
-link_get_option(NMPlatform *self, int ifindex, const char *category, const char *option)
-{
-    nm_auto_close int dirfd = -1;
-    char              ifname_verified[IFNAMSIZ];
-    const char *      path;
-
-    if (!category || !option)
-        return NULL;
-
-    dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified);
-    if (dirfd < 0)
-        return NULL;
-
-    path =
-        nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option);
-    return nm_platform_sysctl_get(self,
-                                  NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path));
-}
-
-static const char *
-master_category(NMPlatform *self, int master)
-{
-    switch (nm_platform_link_get_type(self, master)) {
-    case NM_LINK_TYPE_BRIDGE:
-        return "bridge";
-    case NM_LINK_TYPE_BOND:
-        return "bonding";
-    default:
-        return NULL;
-    }
-}
-
-static const char *
-slave_category(NMPlatform *self, int slave)
-{
-    int master = nm_platform_link_get_master(self, slave);
-
-    if (master <= 0)
-        return NULL;
-
-    switch (nm_platform_link_get_type(self, master)) {
-    case NM_LINK_TYPE_BRIDGE:
-        return "brport";
-    default:
-        return NULL;
-    }
-}
-
-gboolean
-nm_platform_sysctl_master_set_option(NMPlatform *self,
-                                     int         ifindex,
-                                     const char *option,
-                                     const char *value)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(option, FALSE);
-    g_return_val_if_fail(value, FALSE);
-
-    return link_set_option(self, ifindex, master_category(self, ifindex), option, value);
-}
-
-char *
-nm_platform_sysctl_master_get_option(NMPlatform *self, int ifindex, const char *option)
-{
-    _CHECK_SELF(self, klass, NULL);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(option, FALSE);
-
-    return link_get_option(self, ifindex, master_category(self, ifindex), option);
-}
-
-gboolean
-nm_platform_sysctl_slave_set_option(NMPlatform *self,
-                                    int         ifindex,
-                                    const char *option,
-                                    const char *value)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(option, FALSE);
-    g_return_val_if_fail(value, FALSE);
-
-    return link_set_option(self, ifindex, slave_category(self, ifindex), option, value);
-}
-
-char *
-nm_platform_sysctl_slave_get_option(NMPlatform *self, int ifindex, const char *option)
-{
-    _CHECK_SELF(self, klass, NULL);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(option, FALSE);
-
-    return link_get_option(self, ifindex, slave_category(self, ifindex), option);
-}
-
-/*****************************************************************************/
-
-gboolean
-nm_platform_link_vlan_change(NMPlatform *            self,
-                             int                     ifindex,
-                             NMVlanFlags             flags_mask,
-                             NMVlanFlags             flags_set,
-                             gboolean                ingress_reset_all,
-                             const NMVlanQosMapping *ingress_map,
-                             gsize                   n_ingress_map,
-                             gboolean                egress_reset_all,
-                             const NMVlanQosMapping *egress_map,
-                             gsize                   n_egress_map)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    nm_assert(klass->link_vlan_change);
-
-    g_return_val_if_fail(!n_ingress_map || ingress_map, FALSE);
-    g_return_val_if_fail(!n_egress_map || egress_map, FALSE);
-
-    flags_set &= flags_mask;
-
-    if (_LOGD_ENABLED()) {
-        char  buf[512];
-        char *b = buf;
-        gsize len, i;
-
-        b[0] = '\0';
-        len  = sizeof(buf);
-
-        if (flags_mask)
-            nm_utils_strbuf_append(&b,
-                                   &len,
-                                   " flags 0x%x/0x%x",
-                                   (unsigned) flags_set,
-                                   (unsigned) flags_mask);
-
-        if (ingress_reset_all || n_ingress_map) {
-            nm_utils_strbuf_append_str(&b, &len, " ingress-qos-map");
-            nm_platform_vlan_qos_mapping_to_string("", ingress_map, n_ingress_map, b, len);
-            i = strlen(b);
-            b += i;
-            len -= i;
-            if (ingress_reset_all)
-                nm_utils_strbuf_append_str(&b, &len, " (reset-all)");
-        }
-
-        if (egress_reset_all || n_egress_map) {
-            nm_utils_strbuf_append_str(&b, &len, " egress-qos-map");
-            nm_platform_vlan_qos_mapping_to_string("", egress_map, n_egress_map, b, len);
-            i = strlen(b);
-            b += i;
-            len -= i;
-            if (egress_reset_all)
-                nm_utils_strbuf_append_str(&b, &len, " (reset-all)");
-        }
-
-        _LOG3D("link: change vlan %s", buf);
-    }
-    return klass->link_vlan_change(self,
-                                   ifindex,
-                                   flags_mask,
-                                   flags_set,
-                                   ingress_reset_all,
-                                   ingress_map,
-                                   n_ingress_map,
-                                   egress_reset_all,
-                                   egress_map,
-                                   n_egress_map);
-}
-
-gboolean
-nm_platform_link_vlan_set_ingress_map(NMPlatform *self, int ifindex, int from, int to)
-{
-    NMVlanQosMapping map = {
-        .from = from,
-        .to   = to,
-    };
-
-    return nm_platform_link_vlan_change(self, ifindex, 0, 0, FALSE, &map, 1, FALSE, NULL, 0);
-}
-
-gboolean
-nm_platform_link_vlan_set_egress_map(NMPlatform *self, int ifindex, int from, int to)
-{
-    NMVlanQosMapping map = {
-        .from = from,
-        .to   = to,
-    };
-
-    return nm_platform_link_vlan_change(self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1);
-}
-
-static int
-_infiniband_add_add_or_delete(NMPlatform *           self,
-                              int                    ifindex,
-                              int                    p_key,
-                              gboolean               add,
-                              const NMPlatformLink **out_link)
-{
-    char                  name[IFNAMSIZ];
-    const NMPlatformLink *parent_link;
-    int                   r;
-
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    g_return_val_if_fail(ifindex >= 0, -NME_BUG);
-    g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, -NME_BUG);
-
-    /* the special keys 0x0000 and 0x8000 are not allowed. */
-    if (NM_IN_SET(p_key, 0, 0x8000))
-        return -NME_UNSPEC;
-
-    parent_link = nm_platform_link_get(self, ifindex);
-    if (!parent_link)
-        return -NME_PL_NOT_FOUND;
-
-    if (parent_link->type != NM_LINK_TYPE_INFINIBAND)
-        return -NME_PL_WRONG_TYPE;
-
-    nm_utils_new_infiniband_name(name, parent_link->name, p_key);
-
-    if (add) {
-        r = _link_add_check_existing(self, name, NM_LINK_TYPE_INFINIBAND, out_link);
-        if (r < 0)
-            return r;
-
-        _LOG3D("link: adding infiniband partition %s, key %d", name, p_key);
-        if (!klass->infiniband_partition_add(self, ifindex, p_key, out_link))
-            return -NME_UNSPEC;
-    } else {
-        _LOG3D("link: deleting infiniband partition %s, key %d", name, p_key);
-
-        if (!klass->infiniband_partition_delete(self, ifindex, p_key))
-            return -NME_UNSPEC;
-    }
-
-    return 0;
-}
-
-int
-nm_platform_link_infiniband_add(NMPlatform *           self,
-                                int                    parent,
-                                int                    p_key,
-                                const NMPlatformLink **out_link)
-{
-    return _infiniband_add_add_or_delete(self, parent, p_key, TRUE, out_link);
-}
-
-int
-nm_platform_link_infiniband_delete(NMPlatform *self, int parent, int p_key)
-{
-    return _infiniband_add_add_or_delete(self, parent, p_key, FALSE, NULL);
-}
-
-gboolean
-nm_platform_link_infiniband_get_properties(NMPlatform * self,
-                                           int          ifindex,
-                                           int *        out_parent,
-                                           int *        out_p_key,
-                                           const char **out_mode)
-{
-    nm_auto_close int              dirfd = -1;
-    char                           ifname_verified[IFNAMSIZ];
-    const NMPlatformLnkInfiniband *plnk;
-    const NMPlatformLink *         plink;
-    char *                         contents;
-    const char *                   mode;
-    int                            p_key = 0;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    plnk = nm_platform_link_get_lnk_infiniband(self, ifindex, &plink);
-
-    if (!plink || plink->type != NM_LINK_TYPE_INFINIBAND)
-        return FALSE;
-
-    if (plnk) {
-        NM_SET_OUT(out_parent, plink->parent);
-        NM_SET_OUT(out_p_key, plnk->p_key);
-        NM_SET_OUT(out_mode, plnk->mode);
-        return TRUE;
-    }
-
-    /* Could not get the link information via netlink. To support older kernels,
-     * fallback to reading sysfs. */
-
-    dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname_verified);
-    if (dirfd < 0)
-        return FALSE;
-
-    contents =
-        nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode"));
-    if (!contents)
-        return FALSE;
-    if (strstr(contents, "datagram"))
-        mode = "datagram";
-    else if (strstr(contents, "connected"))
-        mode = "connected";
-    else
-        mode = NULL;
-    g_free(contents);
-
-    p_key =
-        nm_platform_sysctl_get_int_checked(self,
-                                           NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "pkey"),
-                                           16,
-                                           0,
-                                           0xFFFF,
-                                           -1);
-    if (p_key < 0)
-        return FALSE;
-
-    NM_SET_OUT(out_parent, plink->parent);
-    NM_SET_OUT(out_p_key, p_key);
-    NM_SET_OUT(out_mode, mode);
-    return TRUE;
-}
-
-gboolean
-nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_peer_ifindex)
-{
-    const NMPlatformLink *plink;
-    int                   peer_ifindex;
-
-    plink = nm_platform_link_get(self, ifindex);
-    if (!plink)
-        return FALSE;
-
-    if (plink->type != NM_LINK_TYPE_VETH)
-        return FALSE;
-
-    if (plink->parent != 0) {
-        NM_SET_OUT(out_peer_ifindex, plink->parent);
-        return TRUE;
-    }
-
-    /* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */
-    if (out_peer_ifindex) {
-        nm_auto_pop_netns NMPNetns *netns = NULL;
-
-        if (!nm_platform_netns_push(self, &netns))
-            return FALSE;
-        peer_ifindex = nmp_utils_ethtool_get_peer_ifindex(plink->ifindex);
-        if (peer_ifindex <= 0)
-            return FALSE;
-
-        *out_peer_ifindex = peer_ifindex;
-    }
-    return TRUE;
-}
-
-/**
- * nm_platform_link_tun_get_properties:
- * @self: the #NMPlatform instance
- * @ifindex: the ifindex to look up
- * @out_properties: (out) (allow-none): return the read properties
- *
- * Only recent versions of kernel export tun properties via netlink.
- * So, if that's the case, then we have the NMPlatformLnkTun instance
- * in the platform cache ready to return. Otherwise, this function
- * falls back reading sysctl to obtain the tun properties. That
- * is racy, because querying sysctl means that the object might
- * be already removed from cache (while NM didn't yet process the
- * netlink message).
- *
- * Hence, to lookup the tun properties, you always need to use this
- * function, and use it with care knowing that it might obtain its
- * data by reading sysctl. Note that we don't want to add this workaround
- * to the platform cache itself, because the cache should (mainly)
- * contain data from netlink. To access the sysctl side channel, the
- * user needs to do explicitly.
- *
- * Returns: #TRUE, if the properties could be read. */
-gboolean
-nm_platform_link_tun_get_properties(NMPlatform *self, int ifindex, NMPlatformLnkTun *out_properties)
-{
-    const NMPObject *plobj;
-    const NMPObject *pllnk;
-    char             ifname[IFNAMSIZ];
-    gint64           owner;
-    gint64           group;
-    gint64           flags;
-
-    /* we consider also invisible links (those that are not yet in udev). */
-    plobj = nm_platform_link_get_obj(self, ifindex, FALSE);
-    if (!plobj)
-        return FALSE;
-
-    if (NMP_OBJECT_CAST_LINK(plobj)->type != NM_LINK_TYPE_TUN)
-        return FALSE;
-
-    pllnk = plobj->_link.netlink.lnk;
-    if (pllnk) {
-        nm_assert(NMP_OBJECT_GET_TYPE(pllnk) == NMP_OBJECT_TYPE_LNK_TUN);
-        nm_assert(NMP_OBJECT_GET_CLASS(pllnk)->lnk_link_type == NM_LINK_TYPE_TUN);
-
-        /* recent kernels expose tun properties via netlink and thus we have them
-         * in the platform cache. */
-        NM_SET_OUT(out_properties, pllnk->lnk_tun);
-        return TRUE;
-    }
-
-    /* fallback to reading sysctl. */
-    {
-        nm_auto_close int dirfd = -1;
-
-        dirfd = nm_platform_sysctl_open_netdir(self, ifindex, ifname);
-        if (dirfd < 0)
-            return FALSE;
-
-        owner = nm_platform_sysctl_get_int_checked(self,
-                                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "owner"),
-                                                   10,
-                                                   -1,
-                                                   G_MAXUINT32,
-                                                   -2);
-        if (owner == -2)
-            return FALSE;
-
-        group = nm_platform_sysctl_get_int_checked(self,
-                                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "group"),
-                                                   10,
-                                                   -1,
-                                                   G_MAXUINT32,
-                                                   -2);
-        if (group == -2)
-            return FALSE;
-
-        flags =
-            nm_platform_sysctl_get_int_checked(self,
-                                               NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "tun_flags"),
-                                               16,
-                                               0,
-                                               G_MAXINT64,
-                                               -1);
-        if (flags == -1)
-            return FALSE;
-    }
-
-    if (out_properties) {
-        memset(out_properties, 0, sizeof(*out_properties));
-        if (owner != -1) {
-            out_properties->owner_valid = TRUE;
-            out_properties->owner       = owner;
-        }
-        if (group != -1) {
-            out_properties->group_valid = TRUE;
-            out_properties->group       = group;
-        }
-        out_properties->type        = (flags & TUN_TYPE_MASK);
-        out_properties->pi          = !(flags & IFF_NO_PI);
-        out_properties->vnet_hdr    = !!(flags & IFF_VNET_HDR);
-        out_properties->multi_queue = !!(flags & NM_IFF_MULTI_QUEUE);
-        out_properties->persist     = !!(flags & IFF_PERSIST);
-    }
-    return TRUE;
-}
-
-gboolean
-nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wifi_get_capabilities(self, ifindex, caps);
-}
-
-guint32
-nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, 0);
-
-    g_return_val_if_fail(ifindex > 0, 0);
-
-    return klass->wifi_get_frequency(self, ifindex);
-}
-
-gboolean
-nm_platform_wifi_get_station(NMPlatform * self,
-                             int          ifindex,
-                             NMEtherAddr *out_bssid,
-                             int *        out_quality,
-                             guint32 *    out_rate)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wifi_get_station(self, ifindex, out_bssid, out_quality, out_rate);
-}
-
-NM80211Mode
-nm_platform_wifi_get_mode(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, NM_802_11_MODE_UNKNOWN);
-
-    g_return_val_if_fail(ifindex > 0, NM_802_11_MODE_UNKNOWN);
-
-    return klass->wifi_get_mode(self, ifindex);
-}
-
-void
-nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, NM80211Mode mode)
-{
-    _CHECK_SELF_VOID(self, klass);
-
-    g_return_if_fail(ifindex > 0);
-
-    klass->wifi_set_mode(self, ifindex, mode);
-}
-
-static void
-wifi_set_powersave(NMPlatform *p, int ifindex, guint32 powersave)
-{
-    /* empty */
-}
-
-void
-nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave)
-{
-    _CHECK_SELF_VOID(self, klass);
-
-    g_return_if_fail(ifindex > 0);
-
-    klass->wifi_set_powersave(self, ifindex, powersave);
-}
-
-guint32
-nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs)
-{
-    _CHECK_SELF(self, klass, 0);
-
-    g_return_val_if_fail(ifindex > 0, 0);
-    g_return_val_if_fail(freqs != NULL, 0);
-
-    return klass->wifi_find_frequency(self, ifindex, freqs);
-}
-
-void
-nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running)
-{
-    _CHECK_SELF_VOID(self, klass);
-
-    g_return_if_fail(ifindex > 0);
-
-    klass->wifi_indicate_addressing_running(self, ifindex, running);
-}
-
-NMSettingWirelessWakeOnWLan
-nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wifi_get_wake_on_wlan(self, ifindex);
-}
-
-gboolean
-nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wifi_set_wake_on_wlan(self, ifindex, wowl);
-}
-
-guint32
-nm_platform_mesh_get_channel(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, 0);
-
-    g_return_val_if_fail(ifindex > 0, 0);
-
-    return klass->mesh_get_channel(self, ifindex);
-}
-
-gboolean
-nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->mesh_set_channel(self, ifindex, channel);
-}
-
-gboolean
-nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(ssid != NULL, FALSE);
-
-    return klass->mesh_set_ssid(self, ifindex, ssid, len);
-}
-
-guint16
-nm_platform_wpan_get_pan_id(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wpan_get_pan_id(self, ifindex);
-}
-
-gboolean
-nm_platform_wpan_set_pan_id(NMPlatform *self, int ifindex, guint16 pan_id)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wpan_set_pan_id(self, ifindex, pan_id);
-}
-
-guint16
-nm_platform_wpan_get_short_addr(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wpan_get_short_addr(self, ifindex);
-}
-
-gboolean
-nm_platform_wpan_set_short_addr(NMPlatform *self, int ifindex, guint16 short_addr)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wpan_set_short_addr(self, ifindex, short_addr);
-}
-
-gboolean
-nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 channel)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wpan_set_channel(self, ifindex, page, channel);
-}
-
-#define TO_STRING_DEV_BUF_SIZE (5 + 15 + 1)
-static const char *
-_to_string_dev(NMPlatform *self, int ifindex, char *buf, size_t size)
-{
-    g_assert(buf && size >= TO_STRING_DEV_BUF_SIZE);
-
-    if (ifindex) {
-        const char *name = ifindex > 0 && self ? nm_platform_link_get_name(self, ifindex) : NULL;
-        char *      buf2;
-
-        strcpy(buf, " dev ");
-        buf2 = buf + 5;
-        size -= 5;
-
-        if (name)
-            g_strlcpy(buf2, name, size);
-        else
-            g_snprintf(buf2, size, "%d", ifindex);
-    } else
-        buf[0] = 0;
-
-    return buf;
-}
-
-#define TO_STRING_IFA_FLAGS_BUF_SIZE 256
-
-static const char *
-_to_string_ifa_flags(guint32 ifa_flags, char *buf, gsize size)
-{
-#define S_FLAGS_PREFIX " flags "
-    nm_assert(buf && size >= TO_STRING_IFA_FLAGS_BUF_SIZE && size > NM_STRLEN(S_FLAGS_PREFIX));
-
-    if (!ifa_flags)
-        buf[0] = '\0';
-    else {
-        nm_platform_addr_flags2str(ifa_flags,
-                                   &buf[NM_STRLEN(S_FLAGS_PREFIX)],
-                                   size - NM_STRLEN(S_FLAGS_PREFIX));
-        if (buf[NM_STRLEN(S_FLAGS_PREFIX)] == '\0')
-            buf[0] = '\0';
-        else
-            memcpy(buf, S_FLAGS_PREFIX, NM_STRLEN(S_FLAGS_PREFIX));
-    }
-    return buf;
-}
-
-/*****************************************************************************/
-
-gboolean
-nm_platform_ethtool_set_wake_on_lan(NMPlatform *             self,
-                                    int                      ifindex,
-                                    _NMSettingWiredWakeOnLan wol,
-                                    const char *             wol_password)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return nmp_utils_ethtool_set_wake_on_lan(ifindex, wol, wol_password);
-}
-
-gboolean
-nm_platform_ethtool_set_link_settings(NMPlatform *             self,
-                                      int                      ifindex,
-                                      gboolean                 autoneg,
-                                      guint32                  speed,
-                                      NMPlatformLinkDuplexType duplex)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return nmp_utils_ethtool_set_link_settings(ifindex, autoneg, speed, duplex);
-}
-
-gboolean
-nm_platform_ethtool_get_link_settings(NMPlatform *              self,
-                                      int                       ifindex,
-                                      gboolean *                out_autoneg,
-                                      guint32 *                 out_speed,
-                                      NMPlatformLinkDuplexType *out_duplex)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return nmp_utils_ethtool_get_link_settings(ifindex, out_autoneg, out_speed, out_duplex);
-}
-
-/*****************************************************************************/
-
-NMEthtoolFeatureStates *
-nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, NULL);
-
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    return nmp_utils_ethtool_get_features(ifindex);
-}
-
-gboolean
-nm_platform_ethtool_set_features(
-    NMPlatform *                  self,
-    int                           ifindex,
-    const NMEthtoolFeatureStates *features,
-    const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
-    gboolean            do_set /* or reset */)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return nmp_utils_ethtool_set_features(ifindex, features, requested, do_set);
-}
-
-gboolean
-nm_platform_ethtool_get_link_coalesce(NMPlatform *            self,
-                                      int                     ifindex,
-                                      NMEthtoolCoalesceState *coalesce)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(coalesce, FALSE);
-
-    return nmp_utils_ethtool_get_coalesce(ifindex, coalesce);
-}
-
-gboolean
-nm_platform_ethtool_set_coalesce(NMPlatform *                  self,
-                                 int                           ifindex,
-                                 const NMEthtoolCoalesceState *coalesce)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return nmp_utils_ethtool_set_coalesce(ifindex, coalesce);
-}
-
-gboolean
-nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(ring, FALSE);
-
-    return nmp_utils_ethtool_get_ring(ifindex, ring);
-}
-
-gboolean
-nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring)
-{
-    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return nmp_utils_ethtool_set_ring(ifindex, ring);
-}
-
-/*****************************************************************************/
-
-const NMDedupMultiHeadEntry *
-nm_platform_lookup_all(NMPlatform *self, NMPCacheIdType cache_id_type, const NMPObject *obj)
-{
-    return nmp_cache_lookup_all(nm_platform_get_cache(self), cache_id_type, obj);
-}
-
-const NMDedupMultiEntry *
-nm_platform_lookup_entry(NMPlatform *self, NMPCacheIdType cache_id_type, const NMPObject *obj)
-{
-    return nmp_cache_lookup_entry_with_idx_type(nm_platform_get_cache(self), cache_id_type, obj);
-}
-
-const NMDedupMultiHeadEntry *
-nm_platform_lookup(NMPlatform *self, const NMPLookup *lookup)
-{
-    return nmp_cache_lookup(nm_platform_get_cache(self), lookup);
-}
-
-gboolean
-nm_platform_lookup_predicate_routes_main(const NMPObject *obj, gpointer user_data)
-{
-    nm_assert(
-        NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
-    return nm_platform_route_table_is_main(
-        nm_platform_ip_route_get_effective_table(&obj->ip_route));
-}
-
-gboolean
-nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel(const NMPObject *obj,
-                                                            gpointer         user_data)
-{
-    nm_assert(
-        NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
-    return nm_platform_route_table_is_main(nm_platform_ip_route_get_effective_table(&obj->ip_route))
-           && obj->ip_route.rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL;
-}
-
-/**
- * nm_platform_lookup_clone:
- * @self:
- * @lookup:
- * @predicate: if given, only objects for which @predicate returns %TRUE are included
- *   in the result.
- * @user_data: user data for @predicate
- *
- * Returns the result of lookup in a GPtrArray. The result array contains
- * references objects from the cache, its destroy function will unref them.
- *
- * The user must unref the GPtrArray, which will also unref the NMPObject
- * elements.
- *
- * The elements in the array *must* not be modified.
- *
- * Returns: the result of the lookup.
- */
-GPtrArray *
-nm_platform_lookup_clone(NMPlatform *           self,
-                         const NMPLookup *      lookup,
-                         NMPObjectPredicateFunc predicate,
-                         gpointer               user_data)
-{
-    return nm_dedup_multi_objs_to_ptr_array_head(nm_platform_lookup(self, lookup),
-                                                 (NMDedupMultiFcnSelectPredicate) predicate,
-                                                 user_data);
-}
-
-void
-nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen)
-{
-    nm_assert(plen <= 32);
-
-    addr->address      = address;
-    addr->peer_address = address;
-    addr->plen         = plen;
-}
-
-const struct in6_addr *
-nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr)
-{
-    if (IN6_IS_ADDR_UNSPECIFIED(&addr->peer_address)
-        || IN6_ARE_ADDR_EQUAL(&addr->peer_address, &addr->address))
-        return &addr->address;
-    return &addr->peer_address;
-}
-
-gboolean
-nm_platform_ip6_address_match(const NMPlatformIP6Address *addr, NMPlatformMatchFlags match_flag)
-{
-    nm_assert(!NM_FLAGS_ANY(
-        match_flag,
-        ~(NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)));
-    nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY));
-    nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY));
-
-    if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) {
-        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL))
-            return FALSE;
-    } else {
-        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL))
-            return FALSE;
-    }
-
-    if (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED)) {
-        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED))
-            return FALSE;
-    } else if (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE)
-               && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
-        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE))
-            return FALSE;
-    } else {
-        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
-            return FALSE;
-    }
-
-    return TRUE;
-}
-
-gboolean
-nm_platform_ip4_address_add(NMPlatform *self,
-                            int         ifindex,
-                            in_addr_t   address,
-                            guint8      plen,
-                            in_addr_t   peer_address,
-                            in_addr_t   broadcast_address,
-                            guint32     lifetime,
-                            guint32     preferred,
-                            guint32     flags,
-                            const char *label)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(plen <= 32, FALSE);
-    g_return_val_if_fail(lifetime > 0, FALSE);
-    g_return_val_if_fail(preferred <= lifetime, FALSE);
-    g_return_val_if_fail(!label || strlen(label) < sizeof(((NMPlatformIP4Address *) NULL)->label),
-                         FALSE);
-
-    if (_LOGD_ENABLED()) {
-        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,
-                                  broadcast_address,
-                                  lifetime,
-                                  preferred,
-                                  flags,
-                                  label);
-}
-
-gboolean
-nm_platform_ip6_address_add(NMPlatform *    self,
-                            int             ifindex,
-                            struct in6_addr address,
-                            guint8          plen,
-                            struct in6_addr peer_address,
-                            guint32         lifetime,
-                            guint32         preferred,
-                            guint32         flags)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(plen <= 128, FALSE);
-    g_return_val_if_fail(lifetime > 0, FALSE);
-    g_return_val_if_fail(preferred <= lifetime, FALSE);
-
-    if (_LOGD_ENABLED()) {
-        NMPlatformIP6Address addr = {0};
-
-        addr.ifindex      = ifindex;
-        addr.address      = address;
-        addr.peer_address = peer_address;
-        addr.plen         = plen;
-        addr.timestamp    = 0; /* set it to zero, which to_string will treat as *now* */
-        addr.lifetime     = lifetime;
-        addr.preferred    = preferred;
-        addr.n_ifa_flags  = flags;
-
-        _LOG3D("address: adding or updating IPv6 address: %s",
-               nm_platform_ip6_address_to_string(&addr, NULL, 0));
-    }
-    return klass
-        ->ip6_address_add(self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
-}
-
-gboolean
-nm_platform_ip4_address_delete(NMPlatform *self,
-                               int         ifindex,
-                               in_addr_t   address,
-                               guint8      plen,
-                               in_addr_t   peer_address)
-{
-    char str_dev[TO_STRING_DEV_BUF_SIZE];
-    char b1[NM_UTILS_INET_ADDRSTRLEN];
-    char b2[NM_UTILS_INET_ADDRSTRLEN];
-    char str_peer[INET_ADDRSTRLEN + 50];
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(plen <= 32, FALSE);
-
-    _LOG3D("address: deleting IPv4 address %s/%d, %s%s",
-           _nm_utils_inet4_ntop(address, b1),
-           plen,
-           peer_address != address
-               ? nm_sprintf_buf(str_peer, "peer %s, ", _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);
-}
-
-gboolean
-nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen)
-{
-    char str_dev[TO_STRING_DEV_BUF_SIZE];
-    char sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(plen <= 128, FALSE);
-
-    _LOG3D("address: deleting IPv6 address %s/%d, %s",
-           _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);
-}
-
-const NMPlatformIP4Address *
-nm_platform_ip4_address_get(NMPlatform *self,
-                            int         ifindex,
-                            in_addr_t   address,
-                            guint8      plen,
-                            in_addr_t   peer_address)
-{
-    NMPObject        obj_id;
-    const NMPObject *obj;
-
-    _CHECK_SELF(self, klass, NULL);
-
-    g_return_val_if_fail(plen <= 32, NULL);
-
-    nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, address, plen, peer_address);
-    obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id);
-    nm_assert(!obj || nmp_object_is_visible(obj));
-    return NMP_OBJECT_CAST_IP4_ADDRESS(obj);
-}
-
-const NMPlatformIP6Address *
-nm_platform_ip6_address_get(NMPlatform *self, int ifindex, const struct in6_addr *address)
-{
-    NMPObject        obj_id;
-    const NMPObject *obj;
-
-    _CHECK_SELF(self, klass, NULL);
-
-    nm_assert(address);
-
-    nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, address);
-    obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id);
-    nm_assert(!obj || nmp_object_is_visible(obj));
-    return NMP_OBJECT_CAST_IP6_ADDRESS(obj);
-}
-
-static gboolean
-_addr_array_clean_expired(int          addr_family,
-                          int          ifindex,
-                          GPtrArray *  array,
-                          guint32      now,
-                          GHashTable **idx)
-{
-    guint    i;
-    gboolean any_addrs = FALSE;
-
-    nm_assert_addr_family(addr_family);
-    nm_assert(ifindex > 0);
-    nm_assert(now > 0);
-
-    if (!array)
-        return FALSE;
-
-    /* remove all addresses that are already expired. */
-    for (i = 0; i < array->len; i++) {
-        const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(array->pdata[i]);
-
-#if NM_MORE_ASSERTS > 10
-        nm_assert(a);
-        nm_assert(a->ifindex == ifindex);
-        {
-            const NMPObject *o = NMP_OBJECT_UP_CAST(a);
-            guint            j;
-
-            nm_assert(NMP_OBJECT_GET_CLASS(o)->addr_family == addr_family);
-            for (j = i + 1; j < array->len; j++) {
-                const NMPObject *o2 = array->pdata[j];
-
-                nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_GET_TYPE(o2));
-                nm_assert(!nmp_object_id_equal(o, o2));
-            }
-        }
-#endif
-
-        if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) {
-            /* temporary addresses are never added explicitly by NetworkManager but
-             * kernel adds them via mngtempaddr flag.
-             *
-             * We drop them from this list. */
-            goto clear_and_next;
-        }
-
-        if (!nm_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, now, NULL))
-            goto clear_and_next;
-
-        if (idx) {
-            if (G_UNLIKELY(!*idx)) {
-                *idx = g_hash_table_new((GHashFunc) nmp_object_id_hash,
-                                        (GEqualFunc) nmp_object_id_equal);
-            }
-            if (!g_hash_table_add(*idx, (gpointer) NMP_OBJECT_UP_CAST(a)))
-                nm_assert_not_reached();
-        }
-        any_addrs = TRUE;
-        continue;
-
-clear_and_next:
-        nmp_object_unref(g_steal_pointer(&array->pdata[i]));
-    }
-
-    return any_addrs;
-}
-
-static gboolean
-ip4_addr_subnets_is_plain_address(const GPtrArray *addresses, gconstpointer needle)
-{
-    return needle >= (gconstpointer) &addresses->pdata[0]
-           && needle < (gconstpointer) &addresses->pdata[addresses->len];
-}
-
-static const NMPObject **
-ip4_addr_subnets_addr_list_get(const GPtrArray *addr_list, guint idx)
-{
-    nm_assert(addr_list);
-    nm_assert(addr_list->len > 1);
-    nm_assert(idx < addr_list->len);
-    nm_assert(addr_list->pdata[idx]);
-    nm_assert(!(*((gpointer *) addr_list->pdata[idx]))
-              || NMP_OBJECT_CAST_IP4_ADDRESS(*((gpointer *) addr_list->pdata[idx])));
-    nm_assert(idx == 0 || ip4_addr_subnets_addr_list_get(addr_list, idx - 1));
-    return addr_list->pdata[idx];
-}
-
-static void
-ip4_addr_subnets_destroy_index(GHashTable *subnets, const GPtrArray *addresses)
-{
-    GHashTableIter iter;
-    gpointer       p;
-
-    if (!subnets)
-        return;
-
-    g_hash_table_iter_init(&iter, subnets);
-    while (g_hash_table_iter_next(&iter, NULL, &p)) {
-        if (!ip4_addr_subnets_is_plain_address(addresses, p))
-            g_ptr_array_free((GPtrArray *) p, TRUE);
-    }
-
-    g_hash_table_unref(subnets);
-}
-
-static GHashTable *
-ip4_addr_subnets_build_index(const GPtrArray *addresses,
-                             gboolean         consider_flags,
-                             gboolean         full_index)
-{
-    GHashTable *subnets;
-    guint       i;
-
-    nm_assert(addresses && addresses->len);
-
-    subnets = g_hash_table_new(nm_direct_hash, NULL);
-
-    /* Build a hash table of all addresses per subnet */
-    for (i = 0; i < addresses->len; i++) {
-        const NMPlatformIP4Address *address;
-        gpointer                    p_address;
-        GPtrArray *                 addr_list;
-        guint32                     net;
-        int                         position;
-        gpointer                    p;
-
-        if (!addresses->pdata[i])
-            continue;
-
-        p_address = &addresses->pdata[i];
-        address   = NMP_OBJECT_CAST_IP4_ADDRESS(addresses->pdata[i]);
-
-        net = address->address & _nm_utils_ip4_prefix_to_netmask(address->plen);
-        if (!g_hash_table_lookup_extended(subnets, GUINT_TO_POINTER(net), NULL, &p)) {
-            g_hash_table_insert(subnets, GUINT_TO_POINTER(net), p_address);
-            continue;
-        }
-        nm_assert(p);
-
-        if (full_index) {
-            if (ip4_addr_subnets_is_plain_address(addresses, p)) {
-                addr_list = g_ptr_array_new();
-                g_hash_table_insert(subnets, GUINT_TO_POINTER(net), addr_list);
-                g_ptr_array_add(addr_list, p);
-            } else
-                addr_list = p;
-
-            if (!consider_flags || NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_SECONDARY))
-                position = -1; /* append */
-            else
-                position = 0; /* prepend */
-            g_ptr_array_insert(addr_list, position, p_address);
-        } else {
-            /* we only care about the primary. No need to track the secondaries
-             * as a GPtrArray. */
-            nm_assert(ip4_addr_subnets_is_plain_address(addresses, p));
-            if (consider_flags && !NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_SECONDARY)) {
-                g_hash_table_insert(subnets, GUINT_TO_POINTER(net), p_address);
-            }
-        }
-    }
-
-    return subnets;
-}
-
-/**
- * ip4_addr_subnets_is_secondary:
- * @address: an address
- * @subnets: the hash table mapping subnets to addresses
- * @addresses: array of addresses in the hash table
- * @out_addr_list: array of addresses belonging to the same subnet
- *
- * Checks whether @address is secondary and returns in @out_addr_list the list of addresses
- * belonging to the same subnet, if it contains other elements.
- *
- * Returns: %TRUE if the address is secondary, %FALSE otherwise
- */
-static gboolean
-ip4_addr_subnets_is_secondary(const NMPObject * address,
-                              GHashTable *      subnets,
-                              const GPtrArray * addresses,
-                              const GPtrArray **out_addr_list)
-{
-    const NMPlatformIP4Address *a;
-    const GPtrArray *           addr_list;
-    gconstpointer               p;
-    guint32                     net;
-    const NMPObject **          o;
-
-    a = NMP_OBJECT_CAST_IP4_ADDRESS(address);
-
-    net = a->address & _nm_utils_ip4_prefix_to_netmask(a->plen);
-    p   = g_hash_table_lookup(subnets, GUINT_TO_POINTER(net));
-    nm_assert(p);
-    if (!ip4_addr_subnets_is_plain_address(addresses, p)) {
-        addr_list = p;
-        nm_assert(addr_list->len > 1);
-        NM_SET_OUT(out_addr_list, addr_list);
-        o = ip4_addr_subnets_addr_list_get(addr_list, 0);
-        nm_assert(o && *o);
-        if (*o != address)
-            return TRUE;
-    } else {
-        NM_SET_OUT(out_addr_list, NULL);
-        return address != *((gconstpointer *) p);
-    }
-    return FALSE;
-}
-
-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_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 p_a, gconstpointer p_b, gpointer increasing)
-{
-    const NMPlatformIP6Address *a;
-    const NMPlatformIP6Address *b;
-
-    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;
-}
-
-/**
- * nm_platform_ip_address_sync:
- * @self: platform instance
- * @addr_family: the address family AF_INET or AF_INET6.
- * @ifindex: Interface index
- * @known_addresses: List of addresses. The list will be modified and only
- *   addresses that were successfully added will be kept in the list.
- *   That means, expired addresses and addresses that could not be added
- *   will be dropped.
- *   Hence, the input argument @known_addresses is also an output argument
- *   telling which addresses were successfully added.
- *   Addresses are removed by unrefing the instance via nmp_object_unref()
- *   and leaving a NULL tombstone.
- * @addresses_prune: (allow-none): the list of addresses to delete.
- *   If platform has such an address configured, it will be deleted
- *   at the beginning of the sync. Note that the array will be modified
- *   by the function.
- *   Note that the addresses must be properly sorted, by their priority.
- *   Create this list with nm_platform_ip_address_get_prune_list() which
- *   gets the sorting right.
- *
- * A convenience function to synchronize addresses for a specific interface
- * with the least possible disturbance. It simply removes addresses that are
- * not listed and adds addresses that are.
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_ip_address_sync(NMPlatform *self,
-                            int         addr_family,
-                            int         ifindex,
-                            GPtrArray * known_addresses,
-                            GPtrArray * addresses_prune)
-{
-    const gint32       now                             = nm_utils_get_monotonic_timestamp_sec();
-    const int          IS_IPv4                         = NM_IS_IPv4(addr_family);
-    gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
-    GPtrArray *                    plat_addresses;
-    GHashTable *                   known_subnets = NULL;
-    guint32                        ifa_flags;
-    guint                          i_plat;
-    guint                          i_know;
-    guint                          i;
-    guint                          j;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    /* The order we want to enforce is only among addresses with the same
-     * scope, as the kernel keeps addresses sorted by scope. Therefore,
-     * apply the same sorting to known addresses, so that we don't try to
-     * unnecessary change the order of addresses with different scopes. */
-    if (!IS_IPv4) {
-        if (known_addresses)
-            g_ptr_array_sort_with_data(known_addresses,
-                                       ip6_address_scope_cmp,
-                                       GINT_TO_POINTER(TRUE));
-    }
-
-    if (!_addr_array_clean_expired(addr_family,
-                                   ifindex,
-                                   known_addresses,
-                                   now,
-                                   &known_addresses_idx))
-        known_addresses = NULL;
-
-    /* @plat_addresses must be sorted in decreasing priority order (highest priority addresses first), contrary to
-     * @known_addresses which is in increasing priority order (lowest priority addresses first). */
-    plat_addresses = addresses_prune;
-
-    if (nm_g_ptr_array_len(plat_addresses) > 0) {
-        /* Delete unknown addresses */
-        if (IS_IPv4) {
-            GHashTable *plat_subnets;
-
-            plat_subnets = ip4_addr_subnets_build_index(plat_addresses, TRUE, TRUE);
-
-            for (i = 0; i < plat_addresses->len; i++) {
-                const NMPObject *           plat_obj;
-                const NMPlatformIP4Address *plat_address;
-                const GPtrArray *           addr_list;
-
-                plat_obj = plat_addresses->pdata[i];
-                if (!plat_obj) {
-                    /* Already deleted */
-                    continue;
-                }
-
-                plat_address = NMP_OBJECT_CAST_IP4_ADDRESS(plat_obj);
-
-                if (known_addresses) {
-                    const NMPObject *o;
-
-                    o = g_hash_table_lookup(known_addresses_idx, plat_obj);
-                    if (o) {
-                        gboolean secondary;
-
-                        if (!known_subnets)
-                            known_subnets =
-                                ip4_addr_subnets_build_index(known_addresses, FALSE, FALSE);
-
-                        secondary =
-                            ip4_addr_subnets_is_secondary(o, known_subnets, known_addresses, NULL);
-                        if (secondary == NM_FLAGS_HAS(plat_address->n_ifa_flags, IFA_F_SECONDARY)) {
-                            /* if we have an existing known-address, with matching secondary role,
-                             * do not delete the platform-address. */
-                            continue;
-                        }
-                    }
-                }
-
-                nm_platform_ip4_address_delete(self,
-                                               ifindex,
-                                               plat_address->address,
-                                               plat_address->plen,
-                                               plat_address->peer_address);
-
-                if (!ip4_addr_subnets_is_secondary(plat_obj,
-                                                   plat_subnets,
-                                                   plat_addresses,
-                                                   &addr_list)
-                    && addr_list) {
-                    /* If we just deleted a primary addresses and there were
-                     * secondary ones the kernel can do two things, depending on
-                     * version and sysctl setting: delete also secondary addresses
-                     * or promote a secondary to primary. Ensure that secondary
-                     * addresses are deleted, so that we can start with a clean
-                     * slate and add addresses in the right order. */
-                    for (j = 1; j < addr_list->len; j++) {
-                        const NMPObject **o;
-
-                        o = ip4_addr_subnets_addr_list_get(addr_list, j);
-                        nm_assert(o);
-
-                        if (*o) {
-                            const NMPlatformIP4Address *a;
-
-                            a = NMP_OBJECT_CAST_IP4_ADDRESS(*o);
-                            nm_platform_ip4_address_delete(self,
-                                                           ifindex,
-                                                           a->address,
-                                                           a->plen,
-                                                           a->peer_address);
-                            nmp_object_unref(*o);
-                            *o = NULL;
-                        }
-                    }
-                }
-            }
-            ip4_addr_subnets_destroy_index(plat_subnets, plat_addresses);
-        } else {
-            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;
-
-            /* First, compare every address whether it is still a "known address", that is, whether
-             * to keep it or to delete it.
-             *
-             * If we don't find a matching valid address in @known_addresses, we will delete
-             * plat_addr.
-             *
-             * Certain addresses, like temporary addresses, are ignored by this function
-             * if not run with full_sync. These addresses are usually not managed by NetworkManager
-             * directly, or at least, they are not managed via nm_platform_ip6_address_sync().
-             * Only in full_sync mode, we really want to get rid of them (usually, when we take
-             * the interface down).
-             *
-             * Note that we mark handled addresses by setting it to %NULL in @plat_addresses array. */
-            for (i_plat = 0; i_plat < plat_addresses->len; i_plat++) {
-                const NMPObject *           plat_obj = plat_addresses->pdata[i_plat];
-                const NMPObject *           know_obj;
-                const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj);
-
-                if (known_addresses_idx) {
-                    know_obj = g_hash_table_lookup(known_addresses_idx, plat_obj);
-                    if (know_obj
-                        && plat_addr->plen == NMP_OBJECT_CAST_IP6_ADDRESS(know_obj)->plen) {
-                        /* technically, plen is not part of the ID for IPv6 addresses and thus
-                         * @plat_addr is essentially the same address as @know_addr (regrading
-                         * its identity, not its other attributes).
-                         * However, we cannot modify an existing addresses' plen without
-                         * removing and readding it. Thus, only keep plat_addr, if the plen
-                         * matches.
-                         *
-                         * keep this one, and continue */
-                        continue;
-                    }
-                }
-
-                nm_platform_ip6_address_delete(self, ifindex, plat_addr->address, plat_addr->plen);
-                nmp_object_unref(g_steal_pointer(&plat_addresses->pdata[i_plat]));
-            }
-
-            /* Next, we must preserve the priority of the routes. That is, source address
-             * 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 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
-             * 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;
-
-                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 (!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 (!know_addr)
-                            continue;
-
-                        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);
-next_plat:;
-            }
-        }
-    }
-
-    if (!known_addresses)
-        return TRUE;
-
-    if (IS_IPv4)
-        ip4_addr_subnets_destroy_index(known_subnets, known_addresses);
-
-    ifa_flags = nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)
-                    ? IFA_F_NOPREFIXROUTE
-                    : 0;
-
-    /* Add missing addresses. New addresses are added by kernel with top
-     * priority.
-     */
-    for (i_know = 0; i_know < known_addresses->len; i_know++) {
-        const NMPlatformIPXAddress *known_address;
-        const NMPObject *           o;
-        guint32                     lifetime;
-        guint32                     preferred;
-
-        o = known_addresses->pdata[i_know];
-        if (!o)
-            continue;
-
-        nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
-
-        known_address = NMP_OBJECT_CAST_IPX_ADDRESS(o);
-
-        lifetime = nm_utils_lifetime_get(known_address->ax.timestamp,
-                                         known_address->ax.lifetime,
-                                         known_address->ax.preferred,
-                                         now,
-                                         &preferred);
-        nm_assert(lifetime > 0);
-
-        if (IS_IPv4) {
-            if (!nm_platform_ip4_address_add(
-                    self,
-                    ifindex,
-                    known_address->a4.address,
-                    known_address->a4.plen,
-                    known_address->a4.peer_address,
-                    nm_platform_ip4_broadcast_address_from_addr(&known_address->a4),
-                    lifetime,
-                    preferred,
-                    ifa_flags,
-                    known_address->a4.label)) {
-                /* ignore error, for unclear reasons. */
-            }
-        } else {
-            if (!nm_platform_ip6_address_add(self,
-                                             ifindex,
-                                             known_address->a6.address,
-                                             known_address->a6.plen,
-                                             known_address->a6.peer_address,
-                                             lifetime,
-                                             preferred,
-                                             ifa_flags | known_address->a6.n_ifa_flags))
-                return FALSE;
-        }
-    }
-
-    return TRUE;
-}
-
-gboolean
-nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex)
-{
-    gboolean success = TRUE;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET))
-        success &= nm_platform_ip4_address_sync(self, ifindex, NULL);
-    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6))
-        success &= nm_platform_ip6_address_sync(self, ifindex, NULL, TRUE);
-    return success;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_err_inval_due_to_ipv6_tentative_pref_src(NMPlatform *self, const NMPObject *obj)
-{
-    const NMPlatformIP6Route *  r;
-    const NMPlatformIP6Address *a;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(NMP_OBJECT_IS_VALID(obj));
-
-    /* trying to add an IPv6 route with pref-src fails, if the address is
-     * still tentative (rh#1452684). We need to hack around that.
-     *
-     * Detect it, by guessing whether that's the case. */
-
-    if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP6_ROUTE)
-        return FALSE;
-
-    r = NMP_OBJECT_CAST_IP6_ROUTE(obj);
-
-    /* we only allow this workaround for routes added manually by the user. */
-    if (r->rt_source != NM_IP_CONFIG_SOURCE_USER)
-        return FALSE;
-
-    if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src))
-        return FALSE;
-
-    a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src);
-    if (!a)
-        return FALSE;
-    if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE)
-        || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED))
-        return FALSE;
-
-    return TRUE;
-}
-
-GPtrArray *
-nm_platform_ip_address_get_prune_list(NMPlatform *self,
-                                      int         addr_family,
-                                      int         ifindex,
-                                      gboolean    exclude_ipv6_temporary_addrs)
-{
-    const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
-    const NMDedupMultiHeadEntry *head_entry;
-    NMPLookup                    lookup;
-    GPtrArray *                  result;
-    CList *                      iter;
-
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(NM_IS_IPv4(addr_family)), ifindex);
-
-    head_entry = nm_platform_lookup(self, &lookup);
-
-    if (!head_entry)
-        return NULL;
-
-    result = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nmp_object_unref);
-
-    c_list_for_each (iter, &head_entry->lst_entries_head) {
-        const NMPObject *obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj;
-
-        if (!IS_IPv4) {
-            if (exclude_ipv6_temporary_addrs
-                && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_TEMPORARY))
-                continue;
-        }
-
-        g_ptr_array_add(result, (gpointer) nmp_object_ref(obj));
-    }
-
-    if (result->len == 0) {
-        g_ptr_array_unref(result);
-        return NULL;
-    }
-    return result;
-}
-
-GPtrArray *
-nm_platform_ip_route_get_prune_list(NMPlatform *           self,
-                                    int                    addr_family,
-                                    int                    ifindex,
-                                    NMIPRouteTableSyncMode route_table_sync)
-{
-    NMPLookup                    lookup;
-    GPtrArray *                  routes_prune;
-    const NMDedupMultiHeadEntry *head_entry;
-    CList *                      iter;
-    NMPlatformIP4Route           rt_local4;
-    NMPlatformIP6Route           rt_local6;
-    const NMPlatformLink *       pllink;
-    const NMPlatformLnkVrf *     lnk_vrf;
-    guint32                      local_table;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
-    nm_assert(NM_IN_SET(route_table_sync,
-                        NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
-                        NM_IP_ROUTE_TABLE_SYNC_MODE_FULL,
-                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL,
-                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE));
-
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), ifindex);
-    head_entry = nm_platform_lookup(self, &lookup);
-    if (!head_entry)
-        return NULL;
-
-    lnk_vrf = nm_platform_link_get_lnk_vrf(self, ifindex, &pllink);
-    if (!lnk_vrf && pllink && pllink->master > 0)
-        lnk_vrf = nm_platform_link_get_lnk_vrf(self, pllink->master, NULL);
-    local_table = lnk_vrf ? lnk_vrf->table : RT_TABLE_LOCAL;
-
-    rt_local4.plen = 0;
-    rt_local6.plen = 0;
-
-    routes_prune = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nm_dedup_multi_obj_unref);
-
-    c_list_for_each (iter, &head_entry->lst_entries_head) {
-        const NMPObject *         obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj;
-        const NMPlatformIPXRoute *rt  = NMP_OBJECT_CAST_IPX_ROUTE(obj);
-
-        switch (route_table_sync) {
-        case NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN:
-            if (!nm_platform_route_table_is_main(nm_platform_ip_route_get_effective_table(&rt->rx)))
-                continue;
-            break;
-        case NM_IP_ROUTE_TABLE_SYNC_MODE_FULL:
-            if (nm_platform_ip_route_get_effective_table(&rt->rx) == RT_TABLE_LOCAL)
-                continue;
-            break;
-        case NM_IP_ROUTE_TABLE_SYNC_MODE_ALL:
-
-            /* FIXME: we should better handle routes that are automatically added by kernel.
-             *
-             * For now, make a good guess which are those routes and exclude them from
-             * pruning them. */
-
-            if (NM_IS_IPv4(addr_family)) {
-                /* for each IPv4 address kernel adds a route like
-                 *
-                 *  local $ADDR dev $IFACE table local proto kernel scope host src $PRIMARY_ADDR
-                 *
-                 * Check whether route could be of that kind. */
-                if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table
-                    && rt->rx.plen == 32 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL
-                    && rt->rx.metric == 0
-                    && rt->r4.scope_inv == nm_platform_route_scope_inv(RT_SCOPE_HOST)
-                    && rt->r4.gateway == INADDR_ANY) {
-                    if (rt_local4.plen == 0) {
-                        rt_local4 = (NMPlatformIP4Route){
-                            .ifindex       = ifindex,
-                            .type_coerced  = nm_platform_route_type_coerce(RTN_LOCAL),
-                            .plen          = 32,
-                            .rt_source     = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL,
-                            .metric        = 0,
-                            .table_coerced = nm_platform_route_table_coerce(local_table),
-                            .scope_inv     = nm_platform_route_scope_inv(RT_SCOPE_HOST),
-                            .gateway       = INADDR_ANY,
-                        };
-                    }
-
-                    /* the possible "network" depends on the addresses we have. We don't check that
-                     * carefully. If the other parameters match, we assume that this route is the one
-                     * generated by kernel. */
-                    rt_local4.network  = rt->r4.network;
-                    rt_local4.pref_src = rt->r4.pref_src;
-
-                    /* to be more confident about comparing the value, use our nm_platform_ip4_route_cmp()
-                     * implementation. That will also consider parameters that we leave unspecified here. */
-                    if (nm_platform_ip4_route_cmp(&rt->r4,
-                                                  &rt_local4,
-                                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-                        == 0)
-                        continue;
-                }
-            } else {
-                /* for each IPv6 address (that is no longer tentative) kernel adds a route like
-                 *
-                 *  local $ADDR dev $IFACE table local proto kernel metric 0 pref medium
-                 *
-                 * Same as for the IPv4 case. */
-                if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table
-                    && rt->rx.plen == 128 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL
-                    && rt->rx.metric == 0 && rt->r6.rt_pref == NM_ICMPV6_ROUTER_PREF_MEDIUM
-                    && IN6_IS_ADDR_UNSPECIFIED(&rt->r6.gateway)) {
-                    if (rt_local6.plen == 0) {
-                        rt_local6 = (NMPlatformIP6Route){
-                            .ifindex       = ifindex,
-                            .type_coerced  = nm_platform_route_type_coerce(RTN_LOCAL),
-                            .plen          = 128,
-                            .rt_source     = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL,
-                            .metric        = 0,
-                            .table_coerced = nm_platform_route_table_coerce(local_table),
-                            .rt_pref       = NM_ICMPV6_ROUTER_PREF_MEDIUM,
-                            .gateway       = IN6ADDR_ANY_INIT,
-                        };
-                    }
-
-                    rt_local6.network = rt->r6.network;
-
-                    if (nm_platform_ip6_route_cmp(&rt->r6,
-                                                  &rt_local6,
-                                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-                        == 0)
-                        continue;
-                }
-            }
-            break;
-
-        case NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE:
-            break;
-
-        default:
-            nm_assert_not_reached();
-            break;
-        }
-
-        g_ptr_array_add(routes_prune, (gpointer) nmp_object_ref(obj));
-    }
-
-    if (routes_prune->len == 0) {
-        g_ptr_array_unref(routes_prune);
-        return NULL;
-    }
-    return routes_prune;
-}
-
-/**
- * nm_platform_ip_route_sync:
- * @self: the #NMPlatform instance.
- * @addr_family: AF_INET or AF_INET6.
- * @ifindex: the @ifindex for which the routes are to be added.
- * @routes: (allow-none): a list of routes to configure. Must contain
- *   NMPObject instances of routes, according to @addr_family.
- * @routes_prune: (allow-none): the list of routes to delete.
- *   If platform has such a route configured, it will be deleted
- *   at the end of the operation. Note that if @routes contains
- *   the same route, then it will not be deleted. @routes overrules
- *   @routes_prune list.
- * @out_temporary_not_available: (allow-none) (out): routes that could
- *   currently not be synced. The caller shall keep them and try later again.
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_ip_route_sync(NMPlatform *self,
-                          int         addr_family,
-                          int         ifindex,
-                          GPtrArray * routes,
-                          GPtrArray * routes_prune,
-                          GPtrArray **out_temporary_not_available)
-{
-    const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
-    const NMPlatformVTableRoute *vt;
-    gs_unref_hashtable GHashTable *routes_idx = NULL;
-    const NMPObject *              conf_o;
-    const NMDedupMultiEntry *      plat_entry;
-    guint                          i;
-    int                            i_type;
-    gboolean                       success = TRUE;
-    char                           sbuf1[sizeof(_nm_utils_to_string_buffer)];
-    char                           sbuf2[sizeof(_nm_utils_to_string_buffer)];
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(ifindex > 0);
-
-    vt = &nm_platform_vtable_route.vx[IS_IPv4];
-
-    for (i_type = 0; routes && i_type < 2; i_type++) {
-        for (i = 0; i < routes->len; i++) {
-            int      r, r2;
-            gboolean gateway_route_added = FALSE;
-
-            conf_o = routes->pdata[i];
-
-#define VTABLE_IS_DEVICE_ROUTE(vt, o)                          \
-    (vt->is_ip4 ? (NMP_OBJECT_CAST_IP4_ROUTE(o)->gateway == 0) \
-                : IN6_IS_ADDR_UNSPECIFIED(&NMP_OBJECT_CAST_IP6_ROUTE(o)->gateway))
-
-            if ((i_type == 0 && !VTABLE_IS_DEVICE_ROUTE(vt, conf_o))
-                || (i_type == 1 && VTABLE_IS_DEVICE_ROUTE(vt, conf_o))) {
-                /* we add routes in two runs over @i_type.
-                 *
-                 * First device routes, then gateway routes. */
-                continue;
-            }
-
-            if (!routes_idx) {
-                routes_idx = g_hash_table_new((GHashFunc) nmp_object_id_hash,
-                                              (GEqualFunc) nmp_object_id_equal);
-            }
-            if (!g_hash_table_insert(routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
-                _LOG3D("route-sync: skip adding duplicate route %s",
-                       nmp_object_to_string(conf_o,
-                                            NMP_OBJECT_TO_STRING_PUBLIC,
-                                            sbuf1,
-                                            sizeof(sbuf1)));
-                continue;
-            }
-
-            if (!IS_IPv4
-                && nm_platform_ip6_route_get_effective_metric(NMP_OBJECT_CAST_IP6_ROUTE(conf_o))
-                       == 0) {
-                /* User space cannot add routes with metric 0. However, kernel can, and we might track such
-                 * routes in @route as they are present external. Skip them silently. */
-                continue;
-            }
-
-            plat_entry = nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
-            if (plat_entry) {
-                const NMPObject *plat_o;
-
-                plat_o = plat_entry->obj;
-
-                if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
-                                  NMP_OBJECT_CAST_IPX_ROUTE(plat_o),
-                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-                    == 0)
-                    continue;
-
-                /* we need to replace the existing route with a (slightly) different
-                 * one. Delete it first. */
-                if (!nm_platform_object_delete(self, plat_o)) {
-                    /* ignore error. */
-                }
-            }
-
-sync_route_add:
-            r = nm_platform_ip_route_add(self,
-                                         NMP_NLM_FLAG_APPEND
-                                             | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-                                         conf_o);
-            if (r < 0) {
-                if (r == -EEXIST) {
-                    /* Don't fail for EEXIST. It's not clear that the existing route
-                     * is identical to the one that we were about to add. However,
-                     * above we should have deleted conflicting (non-identical) routes. */
-                    if (_LOGD_ENABLED()) {
-                        plat_entry =
-                            nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
-                        if (!plat_entry) {
-                            _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
-                                   "cannot find such a route",
-                                   nmp_object_to_string(conf_o,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf1,
-                                                        sizeof(sbuf1)));
-                        } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
-                                                 NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
-                                                 NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-                                   != 0) {
-                            _LOG3D("route-sync: adding route %s failed due to existing "
-                                   "(different!) route %s",
-                                   nmp_object_to_string(conf_o,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf1,
-                                                        sizeof(sbuf1)),
-                                   nmp_object_to_string(plat_entry->obj,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf2,
-                                                        sizeof(sbuf2)));
-                        }
-                    }
-                } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
-                    _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                } else if (r == -EINVAL && out_temporary_not_available
-                           && _err_inval_due_to_ipv6_tentative_pref_src(self, conf_o)) {
-                    _LOG3D("route-sync: ignore failure to add IPv6 route with tentative IPv6 "
-                           "pref-src: %s: %s",
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                    if (!*out_temporary_not_available)
-                        *out_temporary_not_available =
-                            g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref);
-                    g_ptr_array_add(*out_temporary_not_available,
-                                    (gpointer) nmp_object_ref(conf_o));
-                } else if (!gateway_route_added
-                           && ((r == -ENETUNREACH && vt->is_ip4
-                                && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway)
-                               || (r == -EHOSTUNREACH && !vt->is_ip4
-                                   && !IN6_IS_ADDR_UNSPECIFIED(
-                                       &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) {
-                    NMPObject oo;
-
-                    if (vt->is_ip4) {
-                        const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o);
-
-                        nmp_object_stackinit(
-                            &oo,
-                            NMP_OBJECT_TYPE_IP4_ROUTE,
-                            &((NMPlatformIP4Route){
-                                .ifindex       = rt->ifindex,
-                                .network       = rt->gateway,
-                                .plen          = 32,
-                                .metric        = nm_platform_ip4_route_get_effective_metric(rt),
-                                .rt_source     = rt->rt_source,
-                                .table_coerced = nm_platform_ip_route_get_effective_table(
-                                    NM_PLATFORM_IP_ROUTE_CAST(rt)),
-                            }));
-                    } else {
-                        const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o);
-
-                        nmp_object_stackinit(
-                            &oo,
-                            NMP_OBJECT_TYPE_IP6_ROUTE,
-                            &((NMPlatformIP6Route){
-                                .ifindex       = rt->ifindex,
-                                .network       = rt->gateway,
-                                .plen          = 128,
-                                .metric        = nm_platform_ip6_route_get_effective_metric(rt),
-                                .rt_source     = rt->rt_source,
-                                .table_coerced = nm_platform_ip_route_get_effective_table(
-                                    NM_PLATFORM_IP_ROUTE_CAST(rt)),
-                            }));
-                    }
-
-                    _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct "
-                           "route to gateway %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r),
-                           nmp_object_to_string(&oo,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf2,
-                                                sizeof(sbuf2)));
-
-                    r2 = nm_platform_ip_route_add(self,
-                                                  NMP_NLM_FLAG_APPEND
-                                                      | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-                                                  &oo);
-
-                    if (r2 < 0) {
-                        _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s",
-                               vt->is_ip4 ? '4' : '6',
-                               nmp_object_to_string(conf_o,
-                                                    NMP_OBJECT_TO_STRING_PUBLIC,
-                                                    sbuf1,
-                                                    sizeof(sbuf1)),
-                               nm_strerror(r2));
-                    }
-
-                    gateway_route_added = TRUE;
-                    goto sync_route_add;
-                } else {
-                    _LOG3W("route-sync: failure to add IPv%c route: %s: %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                    success = FALSE;
-                }
-            }
-        }
-    }
-
-    if (routes_prune) {
-        for (i = 0; i < routes_prune->len; i++) {
-            const NMPObject *prune_o;
-
-            prune_o = routes_prune->pdata[i];
-
-            nm_assert((NM_IS_IPv4(addr_family)
-                       && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP4_ROUTE)
-                      || (!NM_IS_IPv4(addr_family)
-                          && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP6_ROUTE));
-
-            if (routes_idx && g_hash_table_lookup(routes_idx, prune_o))
-                continue;
-
-            if (!nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, prune_o))
-                continue;
-
-            if (!nm_platform_object_delete(self, prune_o)) {
-                /* ignore error... */
-            }
-        }
-    }
-
-    return success;
-}
-
-gboolean
-nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex)
-{
-    gboolean success = TRUE;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) {
-        gs_unref_ptrarray GPtrArray *routes_prune = NULL;
-
-        routes_prune = nm_platform_ip_route_get_prune_list(self,
-                                                           AF_INET,
-                                                           ifindex,
-                                                           NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE);
-        success &= nm_platform_ip_route_sync(self, AF_INET, ifindex, NULL, routes_prune, NULL);
-    }
-    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
-        gs_unref_ptrarray GPtrArray *routes_prune = NULL;
-
-        routes_prune = nm_platform_ip_route_get_prune_list(self,
-                                                           AF_INET6,
-                                                           ifindex,
-                                                           NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE);
-        success &= nm_platform_ip_route_sync(self, AF_INET6, ifindex, NULL, routes_prune, NULL);
-    }
-    return success;
-}
-
-/*****************************************************************************/
-
-static guint8
-_ip_route_scope_inv_get_normalized(const NMPlatformIP4Route *route)
-{
-    /* in kernel, you cannot set scope to RT_SCOPE_NOWHERE (255).
-     * That means, in NM, we treat RT_SCOPE_NOWHERE as unset, and detect
-     * it based on the presence of the gateway. In other words, when adding
-     * a route with scope RT_SCOPE_NOWHERE (in NetworkManager) to kernel,
-     * the resulting scope will be either "link" or "universe" (depending
-     * on the gateway).
-     *
-     * Note that internally, we track @scope_inv is the inverse of scope,
-     * so that the default equals zero (~(RT_SCOPE_NOWHERE)).
-     **/
-    if (route->scope_inv == 0) {
-        if (route->type_coerced == nm_platform_route_type_coerce(RTN_LOCAL))
-            return nm_platform_route_scope_inv(RT_SCOPE_HOST);
-        else {
-            return nm_platform_route_scope_inv(!route->gateway ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE);
-        }
-    }
-    return route->scope_inv;
-}
-
-static guint8
-_route_pref_normalize(guint8 pref)
-{
-    /* for kernel (and ICMPv6) pref can only have one of 3 values. Normalize. */
-    return NM_IN_SET(pref, NM_ICMPV6_ROUTER_PREF_LOW, NM_ICMPV6_ROUTER_PREF_HIGH)
-               ? pref
-               : NM_ICMPV6_ROUTER_PREF_MEDIUM;
-}
-
-/**
- * nm_platform_ip_route_normalize:
- * @addr_family: AF_INET or AF_INET6
- * @route: an NMPlatformIP4Route or NMPlatformIP6Route instance, depending on @addr_family.
- *
- * Adding a route to kernel via nm_platform_ip_route_add() will normalize/coerce some
- * properties of the route. This function modifies (normalizes) the route like it
- * would be done by adding the route in kernel.
- *
- * Note that this function is related to NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY
- * in that if two routes compare semantically equal, after normalizing they also shall
- * compare equal with NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL.
- */
-void
-nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route)
-{
-    NMPlatformIP4Route *r4;
-    NMPlatformIP6Route *r6;
-
-    route->table_coerced =
-        nm_platform_route_table_coerce(nm_platform_ip_route_get_effective_table(route));
-    route->table_any = FALSE;
-
-    route->rt_source = nmp_utils_ip_config_source_round_trip_rtprot(route->rt_source);
-
-    switch (addr_family) {
-    case AF_INET:
-        r4                = (NMPlatformIP4Route *) route;
-        route->metric     = nm_platform_ip4_route_get_effective_metric(r4);
-        route->metric_any = FALSE;
-        r4->network       = nm_utils_ip4_address_clear_host_address(r4->network, r4->plen);
-        r4->scope_inv     = _ip_route_scope_inv_get_normalized(r4);
-        break;
-    case AF_INET6:
-        r6                = (NMPlatformIP6Route *) route;
-        route->metric     = nm_platform_ip6_route_get_effective_metric(r6);
-        route->metric_any = FALSE;
-        nm_utils_ip6_address_clear_host_address(&r6->network, &r6->network, r6->plen);
-        nm_utils_ip6_address_clear_host_address(&r6->src, &r6->src, r6->src_plen);
-        break;
-    default:
-        nm_assert_not_reached();
-        break;
-    }
-}
-
-static int
-_ip_route_add(NMPlatform *self, NMPNlmFlags flags, int addr_family, gconstpointer route)
-{
-    char sbuf[sizeof(_nm_utils_to_string_buffer)];
-    int  ifindex;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    nm_assert(route);
-    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
-
-    ifindex = ((const NMPlatformIPRoute *) route)->ifindex;
-    _LOG3D("route: %-10s IPv%c route: %s",
-           _nmp_nlm_flag_to_string(flags & NMP_NLM_FLAG_FMASK),
-           nm_utils_addr_family_to_char(addr_family),
-           NM_IS_IPv4(addr_family) ? nm_platform_ip4_route_to_string(route, sbuf, sizeof(sbuf))
-                                   : nm_platform_ip6_route_to_string(route, sbuf, sizeof(sbuf)));
-
-    return klass->ip_route_add(self, flags, addr_family, route);
-}
-
-int
-nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route)
-{
-    int addr_family;
-
-    switch (NMP_OBJECT_GET_TYPE(route)) {
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-        addr_family = AF_INET;
-        break;
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        addr_family = AF_INET6;
-        break;
-    default:
-        g_return_val_if_reached(FALSE);
-    }
-
-    return _ip_route_add(self, flags, addr_family, NMP_OBJECT_CAST_IP_ROUTE(route));
-}
-
-int
-nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route)
-{
-    return _ip_route_add(self, flags, AF_INET, route);
-}
-
-int
-nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route)
-{
-    return _ip_route_add(self, flags, AF_INET6, route);
-}
-
-gboolean
-nm_platform_object_delete(NMPlatform *self, const NMPObject *obj)
-{
-    int ifindex;
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    switch (NMP_OBJECT_GET_TYPE(obj)) {
-    case NMP_OBJECT_TYPE_ROUTING_RULE:
-        _LOGD("%s: delete %s",
-              NMP_OBJECT_GET_CLASS(obj)->obj_type_name,
-              nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-        break;
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-    case NMP_OBJECT_TYPE_QDISC:
-    case NMP_OBJECT_TYPE_TFILTER:
-        ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj)->ifindex;
-        _LOG3D("%s: delete %s",
-               NMP_OBJECT_GET_CLASS(obj)->obj_type_name,
-               nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-        break;
-    default:
-        g_return_val_if_reached(FALSE);
-    }
-
-    return klass->object_delete(self, obj);
-}
-
-/*****************************************************************************/
-
-int
-nm_platform_ip_route_get(NMPlatform *  self,
-                         int           addr_family,
-                         gconstpointer address /* in_addr_t or struct in6_addr */,
-                         int           oif_ifindex,
-                         NMPObject **  out_route)
-{
-    nm_auto_nmpobj NMPObject *route = NULL;
-    int                       result;
-    char                      buf[NM_UTILS_INET_ADDRSTRLEN];
-    char                      buf_oif[64];
-
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(address, -NME_BUG);
-    g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), -NME_BUG);
-
-    _LOGT("route: get IPv%c route for: %s%s",
-          nm_utils_addr_family_to_char(addr_family),
-          inet_ntop(addr_family, address, buf, sizeof(buf)),
-          oif_ifindex > 0 ? nm_sprintf_buf(buf_oif, " oif %d", oif_ifindex) : "");
-
-    if (!klass->ip_route_get)
-        result = -NME_PL_OPNOTSUPP;
-    else {
-        result = klass->ip_route_get(self, addr_family, address, oif_ifindex, &route);
-    }
-
-    if (result < 0) {
-        nm_assert(!route);
-        _LOGW("route: get IPv%c route for: %s failed with %s",
-              nm_utils_addr_family_to_char(addr_family),
-              inet_ntop(addr_family, address, buf, sizeof(buf)),
-              nm_strerror(result));
-    } else {
-        nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(route),
-                            NMP_OBJECT_TYPE_IP4_ROUTE,
-                            NMP_OBJECT_TYPE_IP6_ROUTE));
-        nm_assert(!NMP_OBJECT_IS_STACKINIT(route));
-        nm_assert(route->parent._ref_count == 1);
-        _LOGD("route: get IPv%c route for: %s succeeded: %s",
-              nm_utils_addr_family_to_char(addr_family),
-              inet_ntop(addr_family, address, buf, sizeof(buf)),
-              nmp_object_to_string(route, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-        NM_SET_OUT(out_route, g_steal_pointer(&route));
-    }
-    return result;
-}
-
-/*****************************************************************************/
-
-#define IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS ((int) 1500)
-#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_msec)
-{
-    return timeout_msec >> 1;
-}
-
-static gint64
-_ip4_dev_route_blacklist_timeout_ms_marked(gint64 timeout_msec)
-{
-    return !!(timeout_msec & ((gint64) 1));
-}
-
-static gboolean
-_ip4_dev_route_blacklist_check_cb(gpointer user_data)
-{
-    NMPlatform *       self = user_data;
-    NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
-    GHashTableIter     iter;
-    const NMPObject *  p_obj;
-    gint64 *           p_timeout_ms;
-    gint64             now_ms;
-
-    priv->ip4_dev_route_blacklist_check_id = 0;
-
-again:
-    if (!priv->ip4_dev_route_blacklist_hash)
-        goto out;
-
-    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)) {
-        if (!_ip4_dev_route_blacklist_timeout_ms_marked(*p_timeout_ms))
-            continue;
-
-        /* unmark because we checked it. */
-        *p_timeout_ms = *p_timeout_ms & ~((gint64) 1);
-
-        if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms))
-            continue;
-
-        if (!nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, p_obj))
-            continue;
-
-        _LOGT("ip4-dev-route: delete %s",
-              nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-        nm_platform_object_delete(self, p_obj);
-        goto again;
-    }
-
-out:
-    return G_SOURCE_REMOVE;
-}
-
-static void
-_ip4_dev_route_blacklist_check_schedule(NMPlatform *self)
-{
-    NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    if (!priv->ip4_dev_route_blacklist_check_id) {
-        priv->ip4_dev_route_blacklist_check_id =
-            g_idle_add_full(G_PRIORITY_HIGH, _ip4_dev_route_blacklist_check_cb, self, NULL);
-    }
-}
-
-static void
-_ip4_dev_route_blacklist_notify_route(NMPlatform *self, const NMPObject *obj)
-{
-    NMPlatformPrivate *priv;
-    const NMPObject *  p_obj;
-    gint64 *           p_timeout_ms;
-    gint64             now_ms;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE);
-
-    priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    nm_assert(priv->ip4_dev_route_blacklist_gc_timeout_id);
-
-    if (!g_hash_table_lookup_extended(priv->ip4_dev_route_blacklist_hash,
-                                      obj,
-                                      (gpointer *) &p_obj,
-                                      (gpointer *) &p_timeout_ms))
-        return;
-
-    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;
-    }
-
-    if (_ip4_dev_route_blacklist_timeout_ms_marked(*p_timeout_ms)) {
-        nm_assert(priv->ip4_dev_route_blacklist_check_id);
-        return;
-    }
-
-    /* We cannot delete it right away because we are in the process of receiving netlink messages.
-     * It may be possible to do so, but complicated and error prone.
-     *
-     * Instead, we mark the entry and schedule an idle action (with high priority). */
-    *p_timeout_ms = (*p_timeout_ms) | ((gint64) 1);
-    _ip4_dev_route_blacklist_check_schedule(self);
-}
-
-static gboolean
-_ip4_dev_route_blacklist_gc_timeout_handle(gpointer user_data)
-{
-    NMPlatform *       self = user_data;
-    NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
-    GHashTableIter     iter;
-    const NMPObject *  p_obj;
-    gint64 *           p_timeout_ms;
-    gint64             now_ms;
-
-    nm_assert(priv->ip4_dev_route_blacklist_gc_timeout_id);
-
-    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)) {
-        if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get(*p_timeout_ms)) {
-            _LOGT("ip4-dev-route: cleanup %s",
-                  nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-            g_hash_table_iter_remove(&iter);
-        }
-    }
-
-    _ip4_dev_route_blacklist_schedule(self);
-    return G_SOURCE_CONTINUE;
-}
-
-static void
-_ip4_dev_route_blacklist_schedule(NMPlatform *self)
-{
-    NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    if (!priv->ip4_dev_route_blacklist_hash
-        || g_hash_table_size(priv->ip4_dev_route_blacklist_hash) == 0) {
-        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) {
-            /* this timeout is only to garbage collect the expired entries from priv->ip4_dev_route_blacklist_hash.
-             * It can run infrequently, and it doesn't hurt if expired entries linger around a bit
-             * longer then necessary. */
-            priv->ip4_dev_route_blacklist_gc_timeout_id =
-                g_timeout_add_seconds(IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S,
-                                      _ip4_dev_route_blacklist_gc_timeout_handle,
-                                      self);
-        }
-    }
-}
-
-/**
- * nm_platform_ip4_dev_route_blacklist_set:
- * @self:
- * @ifindex:
- * @ip4_dev_route_blacklist:
- *
- * When adding an IP address, kernel automatically adds a device route.
- * This can be suppressed via the IFA_F_NOPREFIXROUTE address flag. For proper
- * IPv6 support, we require kernel support for IFA_F_NOPREFIXROUTE and always
- * add the device route manually.
- *
- * For IPv4, this flag is rather new and we don't rely on it yet. We want to use
- * it (but currently still don't). So, for IPv4, kernel possibly adds a device
- * route, however it has a wrong metric of zero. We add our own device route (with
- * proper metric), but need to delete the route that kernel adds.
- *
- * The problem is, that kernel does not immediately add the route, when adding
- * the address. It only shows up some time later. So, we register here a list
- * of blacklisted routes, and when they show up within a time out, we assume it's
- * the kernel generated one, and we delete it.
- *
- * Eventually, we want to get rid of this and use IFA_F_NOPREFIXROUTE for IPv4
- * routes as well.
- */
-void
-nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self,
-                                        int         ifindex,
-                                        GPtrArray * ip4_dev_route_blacklist)
-{
-    NMPlatformPrivate *priv;
-    GHashTableIter     iter;
-    const NMPObject *  p_obj;
-    guint              i;
-    gint64             timeout_msec;
-    gint64             timeout_msec_val;
-    gint64 *           p_timeout_ms;
-    gboolean           needs_check = FALSE;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(ifindex > 0);
-
-    /* TODO: the blacklist should be maintained by NML3Cfg. */
-
-    priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    /* first, expire all for current ifindex... */
-    if (priv->ip4_dev_route_blacklist_hash) {
-        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)) {
-            if (NMP_OBJECT_CAST_IP4_ROUTE(p_obj)->ifindex == ifindex) {
-                /* we could g_hash_table_iter_remove(&iter) the current entry.
-                 * Instead, just expire it and let _ip4_dev_route_blacklist_gc_timeout_handle()
-                 * handle it.
-                 *
-                 * The assumption is, that ip4_dev_route_blacklist contains the very same entry
-                 * again, with a new timeout. So, we can un-expire it below. */
-                *p_timeout_ms = 0;
-            }
-        }
-    }
-
-    if (ip4_dev_route_blacklist && ip4_dev_route_blacklist->len > 0) {
-        if (!priv->ip4_dev_route_blacklist_hash) {
-            priv->ip4_dev_route_blacklist_hash =
-                g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
-                                      (GEqualFunc) nmp_object_id_equal,
-                                      (GDestroyNotify) nmp_object_unref,
-                                      nm_g_slice_free_fcn_gint64);
-        }
-
-        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;
-
-            needs_check = TRUE;
-            o           = ip4_dev_route_blacklist->pdata[i];
-            if (g_hash_table_lookup_extended(priv->ip4_dev_route_blacklist_hash,
-                                             o,
-                                             (gpointer *) &p_obj,
-                                             (gpointer *) &p_timeout_ms)) {
-                if (nmp_object_equal(p_obj, o)) {
-                    /* 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_msec_val;
-                    continue;
-                }
-            }
-
-            _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_msec_val;
-            g_hash_table_replace(priv->ip4_dev_route_blacklist_hash,
-                                 (gpointer) nmp_object_ref(o),
-                                 p_timeout_ms);
-        }
-    }
-
-    _ip4_dev_route_blacklist_schedule(self);
-
-    if (needs_check)
-        _ip4_dev_route_blacklist_check_schedule(self);
-}
-
-/*****************************************************************************/
-
-int
-nm_platform_routing_rule_add(NMPlatform *                 self,
-                             NMPNlmFlags                  flags,
-                             const NMPlatformRoutingRule *routing_rule)
-{
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    g_return_val_if_fail(routing_rule, -NME_BUG);
-
-    _LOGD("routing-rule: adding or updating: %s",
-          nm_platform_routing_rule_to_string(routing_rule, NULL, 0));
-    return klass->routing_rule_add(self, flags, routing_rule);
-}
-
-/*****************************************************************************/
-
-int
-nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
-{
-    int ifindex = qdisc->ifindex;
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
-     * is undefined. */
-
-    _LOG3D("adding or updating a qdisc: %s", nm_platform_qdisc_to_string(qdisc, NULL, 0));
-    return klass->qdisc_add(self, flags, qdisc);
-}
-
-/**
- * nm_platform_qdisc_sync:
- * @self: the #NMPlatform instance
- * @ifindex: the ifindex where to configure the qdiscs.
- * @known_qdiscs: the list of qdiscs (#NMPObject).
- *
- * The function promises not to take any reference to the qdisc
- * instances from @known_qdiscs, nor to keep them around after
- * the function returns. This is important, because it allows the
- * caller to pass NMPlatformQdisc instances which "kind" string
- * have a limited lifetime.
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_qdisc_sync(NMPlatform *self, int ifindex, GPtrArray *known_qdiscs)
-{
-    gs_unref_ptrarray GPtrArray *plat_qdiscs = NULL;
-    NMPLookup                    lookup;
-    guint                        i;
-    gboolean                     success            = TRUE;
-    gs_unref_hashtable GHashTable *known_qdiscs_idx = NULL;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(ifindex > 0);
-
-    known_qdiscs_idx =
-        g_hash_table_new((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal);
-    if (known_qdiscs) {
-        for (i = 0; i < known_qdiscs->len; i++) {
-            const NMPObject *q = g_ptr_array_index(known_qdiscs, i);
-
-            if (!g_hash_table_insert(known_qdiscs_idx, (gpointer) q, (gpointer) q)) {
-                _LOGW("duplicate qdisc %s", nm_platform_qdisc_to_string(&q->qdisc, NULL, 0));
-                return FALSE;
-            }
-        }
-    }
-
-    plat_qdiscs =
-        nm_platform_lookup_clone(self,
-                                 nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex),
-                                 NULL,
-                                 NULL);
-    if (plat_qdiscs) {
-        for (i = 0; i < plat_qdiscs->len; i++) {
-            const NMPObject *p = g_ptr_array_index(plat_qdiscs, i);
-            const NMPObject *k;
-
-            /* look up known qdisc with same parent */
-            k = g_hash_table_lookup(known_qdiscs_idx, p);
-
-            if (k) {
-                const NMPlatformQdisc *qdisc_k = NMP_OBJECT_CAST_QDISC(k);
-                const NMPlatformQdisc *qdisc_p = NMP_OBJECT_CAST_QDISC(p);
-
-                /* check other fields */
-                if (nm_platform_qdisc_cmp_full(qdisc_k, qdisc_p, FALSE) != 0
-                    || (qdisc_k->handle != qdisc_p->handle && qdisc_k != 0)) {
-                    k = NULL;
-                }
-            }
-
-            if (k) {
-                g_hash_table_remove(known_qdiscs_idx, k);
-            } else {
-                /* can't delete qdisc with zero handle */
-                if (TC_H_MAJ(p->qdisc.handle) != 0) {
-                    success &= nm_platform_object_delete(self, p);
-                }
-            }
-        }
-    }
-
-    if (known_qdiscs) {
-        for (i = 0; i < known_qdiscs->len; i++) {
-            const NMPObject *q = g_ptr_array_index(known_qdiscs, i);
-
-            if (g_hash_table_contains(known_qdiscs_idx, q)) {
-                success &=
-                    (nm_platform_qdisc_add(self, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_QDISC(q)) >= 0);
-            }
-        }
-    }
-
-    return success;
-}
-
-/*****************************************************************************/
-
-int
-nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter)
-{
-    int ifindex = tfilter->ifindex;
-    _CHECK_SELF(self, klass, -NME_BUG);
-
-    /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
-     * and tfilter.action.kind is undefined. */
-
-    _LOG3D("adding or updating a tfilter: %s", nm_platform_tfilter_to_string(tfilter, NULL, 0));
-    return klass->tfilter_add(self, flags, tfilter);
-}
-
-/**
- * nm_platform_qdisc_sync:
- * @self: the #NMPlatform instance
- * @ifindex: the ifindex where to configure the qdiscs.
- * @known_tfilters: the list of tfilters (#NMPObject).
- *
- * The function promises not to take any reference to the tfilter
- * instances from @known_tfilters, nor to keep them around after
- * the function returns. This is important, because it allows the
- * caller to pass NMPlatformTfilter instances which "kind" string
- * have a limited lifetime.
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_platform_tfilter_sync(NMPlatform *self, int ifindex, GPtrArray *known_tfilters)
-{
-    gs_unref_ptrarray GPtrArray *plat_tfilters = NULL;
-    NMPLookup                    lookup;
-    guint                        i;
-    gboolean                     success              = TRUE;
-    gs_unref_hashtable GHashTable *known_tfilters_idx = NULL;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(ifindex > 0);
-
-    known_tfilters_idx =
-        g_hash_table_new((GHashFunc) nmp_object_id_hash, (GEqualFunc) nmp_object_id_equal);
-
-    if (known_tfilters) {
-        for (i = 0; i < known_tfilters->len; i++) {
-            const NMPObject *q = g_ptr_array_index(known_tfilters, i);
-
-            g_hash_table_insert(known_tfilters_idx, (gpointer) q, (gpointer) q);
-        }
-    }
-
-    plat_tfilters =
-        nm_platform_lookup_clone(self,
-                                 nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_TFILTER, ifindex),
-                                 NULL,
-                                 NULL);
-
-    if (plat_tfilters) {
-        for (i = 0; i < plat_tfilters->len; i++) {
-            const NMPObject *q = g_ptr_array_index(plat_tfilters, i);
-
-            if (!g_hash_table_lookup(known_tfilters_idx, q))
-                success &= nm_platform_object_delete(self, q);
-        }
-    }
-
-    if (known_tfilters) {
-        for (i = 0; i < known_tfilters->len; i++) {
-            const NMPObject *q = g_ptr_array_index(known_tfilters, i);
-
-            success &=
-                (nm_platform_tfilter_add(self, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_TFILTER(q)) >= 0);
-        }
-    }
-
-    return success;
-}
-
-/*****************************************************************************/
-
-const char *
-nm_platform_vlan_qos_mapping_to_string(const char *            name,
-                                       const NMVlanQosMapping *map,
-                                       gsize                   n_map,
-                                       char *                  buf,
-                                       gsize                   len)
-{
-    gsize i;
-    char *b;
-
-    nm_utils_to_string_buffer_init(&buf, &len);
-
-    if (!n_map) {
-        nm_utils_strbuf_append_str(&buf, &len, "");
-        return buf;
-    }
-
-    if (!map)
-        g_return_val_if_reached("");
-
-    b = buf;
-
-    if (name) {
-        nm_utils_strbuf_append_str(&b, &len, name);
-        nm_utils_strbuf_append_str(&b, &len, " {");
-    } else
-        nm_utils_strbuf_append_c(&b, &len, '{');
-
-    for (i = 0; i < n_map; i++)
-        nm_utils_strbuf_append(&b, &len, " %u:%u", map[i].from, map[i].to);
-    nm_utils_strbuf_append_str(&b, &len, " }");
-    return buf;
-}
-
-static const char *
-_lifetime_to_string(guint32 timestamp, guint32 lifetime, gint32 now, char *buf, size_t buf_size)
-{
-    if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT)
-        return "forever";
-
-    g_snprintf(buf,
-               buf_size,
-               "%usec",
-               nm_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now));
-    return buf;
-}
-
-static const char *
-_lifetime_summary_to_string(gint32  now,
-                            guint32 timestamp,
-                            guint32 preferred,
-                            guint32 lifetime,
-                            char *  buf,
-                            size_t  buf_size)
-{
-    g_snprintf(buf,
-               buf_size,
-               " lifetime %d-%u[%u,%u]",
-               (signed) now,
-               (unsigned) timestamp,
-               (unsigned) preferred,
-               (unsigned) lifetime);
-    return buf;
-}
-
-/**
- * nm_platform_link_to_string:
- * @route: pointer to NMPlatformLink address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
- * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
- *
- * A method for converting an link struct into a string representation.
- *
- * Returns: a string representation of the link.
- */
-const char *
-nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len)
-{
-    char        master[20];
-    char        parent[20];
-    char        str_flags[1 + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 1];
-    char        str_highlighted_flags[50];
-    char *      s;
-    gsize       l;
-    char        str_addrmode[30];
-    char        str_address[NM_UTILS_HWADDR_LEN_MAX * 3];
-    char        str_broadcast[NM_UTILS_HWADDR_LEN_MAX * 3];
-    char        str_inet6_token[NM_UTILS_INET_ADDRSTRLEN];
-    const char *str_link_type;
-
-    if (!nm_utils_to_string_buffer_init_null(link, &buf, &len))
-        return buf;
-
-    s = str_highlighted_flags;
-    l = sizeof(str_highlighted_flags);
-    if (NM_FLAGS_HAS(link->n_ifi_flags, IFF_NOARP))
-        nm_utils_strbuf_append_str(&s, &l, "NOARP,");
-    if (NM_FLAGS_HAS(link->n_ifi_flags, IFF_UP))
-        nm_utils_strbuf_append_str(&s, &l, "UP");
-    else
-        nm_utils_strbuf_append_str(&s, &l, "DOWN");
-    if (link->connected)
-        nm_utils_strbuf_append_str(&s, &l, ",LOWER_UP");
-    nm_assert(s > str_highlighted_flags && l > 0);
-
-    if (link->n_ifi_flags) {
-        str_flags[0] = ';';
-        nm_platform_link_flags2str(link->n_ifi_flags, &str_flags[1], sizeof(str_flags) - 1);
-    } else
-        str_flags[0] = '\0';
-
-    if (link->master)
-        g_snprintf(master, sizeof(master), " master %d", link->master);
-    else
-        master[0] = 0;
-
-    if (link->parent > 0)
-        g_snprintf(parent, sizeof(parent), "@%d", link->parent);
-    else if (link->parent == NM_PLATFORM_LINK_OTHER_NETNS)
-        g_strlcpy(parent, "@other-netns", sizeof(parent));
-    else
-        parent[0] = 0;
-
-    _nmp_link_address_to_string(&link->l_address, str_address);
-    _nmp_link_address_to_string(&link->l_broadcast, str_broadcast);
-
-    str_link_type = nm_link_type_to_string(link->type);
-
-    g_snprintf(
-        buf,
-        len,
-        "%d: "    /* ifindex */
-        "%s"      /* name */
-        "%s"      /* parent */
-        " <%s%s>" /* flags */
-        " mtu %d"
-        "%s"      /* master */
-        " arp %u" /* arptype */
-        " %s"     /* link->type */
-        "%s%s"    /* kind */
-        "%s"      /* is-in-udev */
-        "%s%s"    /* addr-gen-mode */
-        "%s%s"    /* l_address */
-        "%s%s"    /* l_broadcast */
-        "%s%s"    /* inet6_token */
-        "%s%s"    /* driver */
-        " rx:%" G_GUINT64_FORMAT ",%" G_GUINT64_FORMAT " tx:%" G_GUINT64_FORMAT
-        ",%" G_GUINT64_FORMAT,
-        link->ifindex,
-        link->name,
-        parent,
-        str_highlighted_flags,
-        str_flags,
-        link->mtu,
-        master,
-        link->arptype,
-        str_link_type ?: "???",
-        link->kind ? (g_strcmp0(str_link_type, link->kind) ? "/" : "*") : "?",
-        link->kind && g_strcmp0(str_link_type, link->kind) ? link->kind : "",
-        link->initialized ? " init" : " not-init",
-        link->inet6_addr_gen_mode_inv ? " addrgenmode " : "",
-        link->inet6_addr_gen_mode_inv ? nm_platform_link_inet6_addrgenmode2str(
-            _nm_platform_uint8_inv(link->inet6_addr_gen_mode_inv),
-            str_addrmode,
-            sizeof(str_addrmode))
-                                      : "",
-        str_address[0] ? " addr " : "",
-        str_address[0] ? str_address : "",
-        str_broadcast[0] ? " brd " : "",
-        str_broadcast[0] ? str_broadcast : "",
-        link->inet6_token.id ? " inet6token " : "",
-        link->inet6_token.id
-            ? nm_utils_inet6_interface_identifier_to_token(link->inet6_token, str_inet6_token)
-            : "",
-        link->driver ? " driver " : "",
-        link->driver ?: "",
-        link->rx_packets,
-        link->rx_bytes,
-        link->tx_packets,
-        link->tx_bytes);
-    return buf;
-}
-
-const NMPlatformLnkBridge nm_platform_lnk_bridge_default = {
-    .forward_delay                 = NM_BRIDGE_FORWARD_DELAY_DEF_SYS,
-    .hello_time                    = NM_BRIDGE_HELLO_TIME_DEF_SYS,
-    .max_age                       = NM_BRIDGE_MAX_AGE_DEF_SYS,
-    .ageing_time                   = NM_BRIDGE_AGEING_TIME_DEF_SYS,
-    .stp_state                     = FALSE,
-    .priority                      = NM_BRIDGE_PRIORITY_DEF,
-    .vlan_protocol                 = 0x8100,
-    .vlan_stats_enabled            = NM_BRIDGE_VLAN_STATS_ENABLED_DEF,
-    .group_fwd_mask                = 0,
-    .group_addr                    = NM_ETHER_ADDR_INIT(NM_BRIDGE_GROUP_ADDRESS_DEF_BIN),
-    .mcast_snooping                = NM_BRIDGE_MULTICAST_SNOOPING_DEF,
-    .mcast_router                  = 1,
-    .mcast_query_use_ifaddr        = NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF,
-    .mcast_querier                 = NM_BRIDGE_MULTICAST_QUERIER_DEF,
-    .mcast_hash_max                = NM_BRIDGE_MULTICAST_HASH_MAX_DEF,
-    .mcast_last_member_count       = NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF,
-    .mcast_startup_query_count     = NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF,
-    .mcast_last_member_interval    = NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF,
-    .mcast_membership_interval     = NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF,
-    .mcast_querier_interval        = NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF,
-    .mcast_query_interval          = NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF,
-    .mcast_query_response_interval = NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF,
-    .mcast_startup_query_interval  = NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF,
-};
-
-const char *
-nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len)
-{
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(buf,
-               len,
-               "forward_delay %u"
-               " hello_time %u"
-               " max_age %u"
-               " ageing_time %u"
-               " stp_state %d"
-               " priority %u"
-               " vlan_protocol %u"
-               " vlan_stats_enabled %d"
-               " group_fwd_mask %#x"
-               " group_address " NM_ETHER_ADDR_FORMAT_STR " mcast_snooping %d"
-               " mcast_router %u"
-               " mcast_query_use_ifaddr %d"
-               " mcast_querier %d"
-               " mcast_hash_max %u"
-               " mcast_last_member_count %u"
-               " mcast_startup_query_count %u"
-               " mcast_last_member_interval %" G_GUINT64_FORMAT
-               " mcast_membership_interval %" G_GUINT64_FORMAT
-               " mcast_querier_interval %" G_GUINT64_FORMAT
-               " mcast_query_interval %" G_GUINT64_FORMAT
-               " mcast_query_response_interval %" G_GUINT64_FORMAT
-               " mcast_startup_query_interval %" G_GUINT64_FORMAT "",
-               lnk->forward_delay,
-               lnk->hello_time,
-               lnk->max_age,
-               lnk->ageing_time,
-               (int) lnk->stp_state,
-               lnk->priority,
-               lnk->vlan_protocol,
-               (int) lnk->vlan_stats_enabled,
-               lnk->group_fwd_mask,
-               NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr),
-               (int) lnk->mcast_snooping,
-               lnk->mcast_router,
-               (int) lnk->mcast_query_use_ifaddr,
-               (int) lnk->mcast_querier,
-               lnk->mcast_hash_max,
-               lnk->mcast_last_member_count,
-               lnk->mcast_startup_query_count,
-               lnk->mcast_last_member_interval,
-               lnk->mcast_membership_interval,
-               lnk->mcast_querier_interval,
-               lnk->mcast_query_interval,
-               lnk->mcast_query_response_interval,
-               lnk->mcast_startup_query_interval);
-    return buf;
-}
-
-const char *
-nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len)
-{
-    char str_local[30];
-    char str_local1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_remote[30];
-    char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_ttl[30];
-    char str_tos[30];
-    char str_parent_ifindex[30];
-    char str_input_flags[30];
-    char str_output_flags[30];
-    char str_input_key[30];
-    char str_input_key1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_output_key[30];
-    char str_output_key1[NM_UTILS_INET_ADDRSTRLEN];
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(
-        buf,
-        len,
-        "gre%s" /* is_tap */
-        "%s"    /* remote */
-        "%s"    /* local */
-        "%s"    /* parent_ifindex */
-        "%s"    /* ttl */
-        "%s"    /* tos */
-        "%s"    /* path_mtu_discovery */
-        "%s"    /* iflags */
-        "%s"    /* oflags */
-        "%s"    /* ikey */
-        "%s"    /* okey */
-        "",
-        lnk->is_tap ? "tap" : "",
-        lnk->remote ? nm_sprintf_buf(str_remote,
-                                     " remote %s",
-                                     _nm_utils_inet4_ntop(lnk->remote, str_remote1))
-                    : "",
-        lnk->local
-            ? nm_sprintf_buf(str_local, " local %s", _nm_utils_inet4_ntop(lnk->local, str_local1))
-            : "",
-        lnk->parent_ifindex ? nm_sprintf_buf(str_parent_ifindex, " dev %d", lnk->parent_ifindex)
-                            : "",
-        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))
-            : "");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_infiniband_to_string(const NMPlatformLnkInfiniband *lnk, char *buf, gsize len)
-{
-    char str_p_key[64];
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(buf,
-               len,
-               "infiniband"
-               "%s"   /* p_key */
-               "%s%s" /* mode */
-               "",
-               lnk->p_key ? nm_sprintf_buf(str_p_key, " pkey %d", lnk->p_key) : "",
-               lnk->mode ? " mode " : "",
-               lnk->mode ?: "");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_ip6tnl_to_string(const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len)
-{
-    char  str_local[30];
-    char  str_local1[NM_UTILS_INET_ADDRSTRLEN];
-    char  str_remote[30];
-    char  str_remote1[NM_UTILS_INET_ADDRSTRLEN];
-    char  str_ttl[30];
-    char  str_tclass[30];
-    char  str_flow[30];
-    char  str_encap[30];
-    char  str_proto[30];
-    char  str_parent_ifindex[30];
-    char *str_type;
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    if (lnk->is_gre)
-        str_type = lnk->is_tap ? "ip6gretap" : "ip6gre";
-    else
-        str_type = "ip6tnl";
-
-    g_snprintf(
-        buf,
-        len,
-        "%s" /* type */
-        "%s" /* remote */
-        "%s" /* local */
-        "%s" /* parent_ifindex */
-        "%s" /* ttl */
-        "%s" /* tclass */
-        "%s" /* encap limit */
-        "%s" /* flow label */
-        "%s" /* proto */
-        " 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)),
-        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),
-        nm_sprintf_buf(str_encap, " encap-limit %u", lnk->encap_limit),
-        nm_sprintf_buf(str_flow, " flow-label 0x05%x", lnk->flow_label),
-        nm_sprintf_buf(str_proto, " proto %u", lnk->proto),
-        (guint) lnk->flags);
-    return buf;
-}
-
-const char *
-nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *buf, gsize len)
-{
-    char str_local[30];
-    char str_local1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_remote[30];
-    char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_ttl[30];
-    char str_tos[30];
-    char str_parent_ifindex[30];
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(
-        buf,
-        len,
-        "ipip"
-        "%s" /* remote */
-        "%s" /* local */
-        "%s" /* parent_ifindex */
-        "%s" /* ttl */
-        "%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->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");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len)
-{
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(buf,
-               len,
-               "macsec "
-               "sci %016llx "
-               "protect %s "
-               "cipher %016llx "
-               "icvlen %u "
-               "encodingsa %u "
-               "validate %u "
-               "encrypt %s "
-               "send_sci %s "
-               "end_station %s "
-               "scb %s "
-               "replay %s",
-               (unsigned long long) lnk->sci,
-               lnk->protect ? "on" : "off",
-               (unsigned long long) lnk->cipher_suite,
-               lnk->icv_length,
-               lnk->encoding_sa,
-               lnk->validation,
-               lnk->encrypt ? "on" : "off",
-               lnk->include_sci ? "on" : "off",
-               lnk->es ? "on" : "off",
-               lnk->scb ? "on" : "off",
-               lnk->replay_protect ? "on" : "off");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len)
-{
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(buf,
-               len,
-               "%s mode %u %s",
-               lnk->tap ? "macvtap" : "macvlan",
-               lnk->mode,
-               lnk->no_promisc ? "not-promisc" : "promisc");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len)
-{
-    char str_local[30];
-    char str_local1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_remote[30];
-    char str_remote1[NM_UTILS_INET_ADDRSTRLEN];
-    char str_ttl[30];
-    char str_tos[30];
-    char str_flags[30];
-    char str_proto[30];
-    char str_parent_ifindex[30];
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    g_snprintf(
-        buf,
-        len,
-        "sit"
-        "%s" /* remote */
-        "%s" /* local */
-        "%s" /* parent_ifindex */
-        "%s" /* ttl */
-        "%s" /* tos */
-        "%s" /* path_mtu_discovery */
-        "%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->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->flags ? nm_sprintf_buf(str_flags, " flags 0x%x", lnk->flags) : "",
-        lnk->proto ? nm_sprintf_buf(str_proto, " proto 0x%x", lnk->proto) : "");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_tun_to_string(const NMPlatformLnkTun *lnk, char *buf, gsize len)
-{
-    char        str_owner[50];
-    char        str_group[50];
-    char        str_type[50];
-    const char *type;
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    if (lnk->type == IFF_TUN)
-        type = "tun";
-    else if (lnk->type == IFF_TAP)
-        type = "tap";
-    else
-        type = nm_sprintf_buf(str_type, "tun type %u", (guint) lnk->type);
-
-    g_snprintf(buf,
-               len,
-               "%s" /* type */
-               "%s" /* pi */
-               "%s" /* vnet_hdr */
-               "%s" /* multi_queue */
-               "%s" /* persist */
-               "%s" /* owner */
-               "%s" /* group */
-               "",
-               type,
-               lnk->pi ? " pi" : "",
-               lnk->vnet_hdr ? " vnet_hdr" : "",
-               lnk->multi_queue ? " multi_queue" : "",
-               lnk->persist ? " persist" : "",
-               lnk->owner_valid ? nm_sprintf_buf(str_owner, " owner %u", (guint) lnk->owner) : "",
-               lnk->group_valid ? nm_sprintf_buf(str_group, " group %u", (guint) lnk->group) : "");
-    return buf;
-}
-
-const char *
-nm_platform_lnk_vlan_to_string(const NMPlatformLnkVlan *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, "vlan %u", lnk->id);
-    if (lnk->flags)
-        nm_utils_strbuf_append(&b, &len, " flags 0x%x", lnk->flags);
-    return buf;
-}
-
-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];
-    char str_group6[100];
-    char str_local[100];
-    char str_local6[100];
-    char str_dev[25];
-    char str_limit[25];
-    char str_src_port[35];
-    char str_dst_port[25];
-    char str_tos[25];
-    char str_ttl[25];
-    char sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    if (lnk->group == 0)
-        str_group[0] = '\0';
-    else {
-        g_snprintf(str_group,
-                   sizeof(str_group),
-                   " %s %s",
-                   IN_MULTICAST(ntohl(lnk->group)) ? "group" : "remote",
-                   _nm_utils_inet4_ntop(lnk->group, sbuf));
-    }
-    if (IN6_IS_ADDR_UNSPECIFIED(&lnk->group6))
-        str_group6[0] = '\0';
-    else {
-        g_snprintf(str_group6,
-                   sizeof(str_group6),
-                   " %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));
-    }
-
-    if (lnk->local == 0)
-        str_local[0] = '\0';
-    else {
-        g_snprintf(str_local,
-                   sizeof(str_local),
-                   " local %s",
-                   _nm_utils_inet4_ntop(lnk->local, sbuf));
-    }
-    if (IN6_IS_ADDR_UNSPECIFIED(&lnk->local6))
-        str_local6[0] = '\0';
-    else {
-        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));
-    }
-
-    g_snprintf(
-        buf,
-        len,
-        "vxlan"
-        " id %u"     /* id */
-        "%s%s"       /* group/group6 */
-        "%s%s"       /* local/local6 */
-        "%s"         /* dev */
-        "%s"         /* src_port_min/src_port_max */
-        "%s"         /* dst_port */
-        "%s"         /* learning */
-        "%s"         /* proxy */
-        "%s"         /* rsc */
-        "%s"         /* l2miss */
-        "%s"         /* l3miss */
-        "%s"         /* tos */
-        "%s"         /* ttl */
-        " ageing %u" /* ageing */
-        "%s"         /* limit */
-        "",
-        (guint) lnk->id,
-        str_group,
-        str_group6,
-        str_local,
-        str_local6,
-        lnk->parent_ifindex ? nm_sprintf_buf(str_dev, " dev %d", lnk->parent_ifindex) : "",
-        lnk->src_port_min || lnk->src_port_max
-            ? nm_sprintf_buf(str_src_port, " srcport %u %u", lnk->src_port_min, lnk->src_port_max)
-            : "",
-        lnk->dst_port ? nm_sprintf_buf(str_dst_port, " dstport %u", lnk->dst_port) : "",
-        !lnk->learning ? " nolearning" : "",
-        lnk->proxy ? " proxy" : "",
-        lnk->rsc ? " rsc" : "",
-        lnk->l2miss ? " l2miss" : "",
-        lnk->l3miss ? " l3miss" : "",
-        lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf(str_tos, " tos %#x", lnk->tos),
-        lnk->ttl ? nm_sprintf_buf(str_ttl, " ttl %u", lnk->ttl) : "",
-        lnk->ageing,
-        lnk->limit ? nm_sprintf_buf(str_limit, " maxaddr %u", lnk->limit) : "");
-    return buf;
-}
-
-const char *
-nm_platform_wireguard_peer_to_string(const NMPWireGuardPeer *peer, char *buf, gsize len)
-{
-    char *        buf0           = buf;
-    gs_free char *public_key_b64 = NULL;
-    char          s_sockaddr[NM_UTILS_INET_ADDRSTRLEN + 100];
-    char          s_endpoint[20 + sizeof(s_sockaddr)];
-    char          s_addr[NM_UTILS_INET_ADDRSTRLEN];
-    char          s_keepalive[100];
-    guint         i;
-
-    nm_utils_to_string_buffer_init(&buf, &len);
-
-    public_key_b64 = g_base64_encode(peer->public_key, sizeof(peer->public_key));
-
-    if (peer->endpoint.sa.sa_family != AF_UNSPEC) {
-        nm_sprintf_buf(
-            s_endpoint,
-            " endpoint %s",
-            nm_sock_addr_union_to_string(&peer->endpoint, s_sockaddr, sizeof(s_sockaddr)));
-    } else
-        s_endpoint[0] = '\0';
-
-    nm_utils_strbuf_append(
-        &buf,
-        &len,
-        "public-key %s"
-        "%s"                                                   /* preshared-key */
-        "%s"                                                   /* endpoint */
-        " rx %" G_GUINT64_FORMAT " tx %" G_GUINT64_FORMAT "%s" /* persistent-keepalive */
-        "%s",                                                  /* allowed-ips */
-        public_key_b64,
-        nm_utils_memeqzero_secret(peer->preshared_key, sizeof(peer->preshared_key))
-            ? ""
-            : " preshared-key (hidden)",
-        s_endpoint,
-        peer->rx_bytes,
-        peer->tx_bytes,
-        peer->persistent_keepalive_interval > 0
-            ? nm_sprintf_buf(s_keepalive,
-                             " keepalive %u",
-                             (guint) peer->persistent_keepalive_interval)
-            : "",
-        peer->allowed_ips_len > 0 ? " allowed-ips" : "");
-
-    for (i = 0; i < peer->allowed_ips_len; i++) {
-        const NMPWireGuardAllowedIP *allowed_ip = &peer->allowed_ips[i];
-
-        nm_utils_strbuf_append(&buf,
-                               &len,
-                               " %s/%u",
-                               nm_utils_inet_ntop(allowed_ip->family, &allowed_ip->addr, s_addr),
-                               allowed_ip->mask);
-    }
-
-    return buf0;
-}
-
-const char *
-nm_platform_lnk_wireguard_to_string(const NMPlatformLnkWireGuard *lnk, char *buf, gsize len)
-{
-    gs_free char *public_b64 = NULL;
-
-    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
-        return buf;
-
-    if (!nm_utils_memeqzero(lnk->public_key, sizeof(lnk->public_key)))
-        public_b64 = g_base64_encode(lnk->public_key, sizeof(lnk->public_key));
-
-    g_snprintf(buf,
-               len,
-               "wireguard"
-               "%s%s" /* public-key */
-               "%s"   /* private-key */
-               " listen-port %u"
-               " fwmark 0x%x",
-               public_b64 ? " public-key " : "",
-               public_b64 ?: "",
-               nm_utils_memeqzero_secret(lnk->private_key, sizeof(lnk->private_key))
-                   ? ""
-                   : " private-key (hidden)",
-               lnk->listen_port,
-               lnk->fwmark);
-
-    return buf;
-}
-
-/**
- * nm_platform_ip4_address_to_string:
- * @route: pointer to NMPlatformIP4Address address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
- * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
- *
- * A method for converting an address struct into a string representation.
- *
- * Example output: ""
- *
- * Returns: a string representation of the address.
- */
-const char *
-nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len)
-{
-    char        s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
-    char        s_address[INET_ADDRSTRLEN];
-    char        s_peer[INET_ADDRSTRLEN];
-    char        str_dev[TO_STRING_DEV_BUF_SIZE];
-    char        str_label[32];
-    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_sec();
-    in_addr_t   broadcast_address;
-    char        str_broadcast[INET_ADDRSTRLEN];
-
-    if (!nm_utils_to_string_buffer_init_null(address, &buf, &len))
-        return buf;
-
-    inet_ntop(AF_INET, &address->address, s_address, sizeof(s_address));
-
-    if (address->peer_address != address->address) {
-        inet_ntop(AF_INET, &address->peer_address, s_peer, sizeof(s_peer));
-        str_peer = g_strconcat(" ptp ", s_peer, NULL);
-    }
-
-    _to_string_dev(NULL, address->ifindex, str_dev, sizeof(str_dev));
-
-    if (*address->label)
-        g_snprintf(str_label, sizeof(str_label), " label %s", address->label);
-    else
-        str_label[0] = 0;
-
-    str_lft_p = _lifetime_to_string(address->timestamp,
-                                    address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT,
-                                    now,
-                                    str_lft,
-                                    sizeof(str_lft)),
-    str_pref_p =
-        (address->lifetime == address->preferred)
-            ? str_lft_p
-            : (_lifetime_to_string(address->timestamp,
-                                   address->lifetime ? MIN(address->preferred, address->lifetime)
-                                                     : NM_PLATFORM_LIFETIME_PERMANENT,
-                                   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"
-        "%s%s" /* broadcast */
-        " lft %s"
-        " pref %s"
-        "%s" /* time */
-        "%s" /* peer  */
-        "%s" /* dev */
-        "%s" /* flags */
-        "%s" /* label */
-        " src %s"
-        "%s" /* external */
-        "%s" /* ip4acd_not_ready */
-        "",
-        s_address,
-        address->plen,
-        broadcast_address != 0u || address->use_ip4_broadcast_address
-            ? (address->use_ip4_broadcast_address ? " brd " : " brd* ")
-            : "",
-        broadcast_address != 0u || address->use_ip4_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)),
-        str_label,
-        nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
-        address->external ? " ext" : "",
-        address->ip4acd_not_ready ? " ip4acd-not-ready" : "");
-    g_free(str_peer);
-    return buf;
-}
-
-NM_UTILS_FLAGS2STR_DEFINE(nm_platform_link_flags2str,
-                          unsigned,
-                          NM_UTILS_FLAGS2STR(IFF_LOOPBACK, "loopback"),
-                          NM_UTILS_FLAGS2STR(IFF_BROADCAST, "broadcast"),
-                          NM_UTILS_FLAGS2STR(IFF_POINTOPOINT, "pointopoint"),
-                          NM_UTILS_FLAGS2STR(IFF_MULTICAST, "multicast"),
-                          NM_UTILS_FLAGS2STR(IFF_NOARP, "noarp"),
-                          NM_UTILS_FLAGS2STR(IFF_ALLMULTI, "allmulti"),
-                          NM_UTILS_FLAGS2STR(IFF_PROMISC, "promisc"),
-                          NM_UTILS_FLAGS2STR(IFF_MASTER, "master"),
-                          NM_UTILS_FLAGS2STR(IFF_SLAVE, "slave"),
-                          NM_UTILS_FLAGS2STR(IFF_DEBUG, "debug"),
-                          NM_UTILS_FLAGS2STR(IFF_DYNAMIC, "dynamic"),
-                          NM_UTILS_FLAGS2STR(IFF_AUTOMEDIA, "automedia"),
-                          NM_UTILS_FLAGS2STR(IFF_PORTSEL, "portsel"),
-                          NM_UTILS_FLAGS2STR(IFF_NOTRAILERS, "notrailers"),
-                          NM_UTILS_FLAGS2STR(IFF_UP, "up"),
-                          NM_UTILS_FLAGS2STR(IFF_RUNNING, "running"),
-                          NM_UTILS_FLAGS2STR(IFF_LOWER_UP, "lowerup"),
-                          NM_UTILS_FLAGS2STR(IFF_DORMANT, "dormant"),
-                          NM_UTILS_FLAGS2STR(IFF_ECHO, "echo"), );
-
-NM_UTILS_ENUM2STR_DEFINE(nm_platform_link_inet6_addrgenmode2str,
-                         guint8,
-                         NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_NONE, "none"),
-                         NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_EUI64, "eui64"),
-                         NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"),
-                         NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_RANDOM, "random"), );
-
-NM_UTILS_FLAGS2STR_DEFINE(nm_platform_addr_flags2str,
-                          unsigned,
-                          NM_UTILS_FLAGS2STR(IFA_F_SECONDARY, "secondary"),
-                          NM_UTILS_FLAGS2STR(IFA_F_NODAD, "nodad"),
-                          NM_UTILS_FLAGS2STR(IFA_F_OPTIMISTIC, "optimistic"),
-                          NM_UTILS_FLAGS2STR(IFA_F_HOMEADDRESS, "homeaddress"),
-                          NM_UTILS_FLAGS2STR(IFA_F_DEPRECATED, "deprecated"),
-                          NM_UTILS_FLAGS2STR(IFA_F_PERMANENT, "permanent"),
-                          NM_UTILS_FLAGS2STR(IFA_F_MANAGETEMPADDR, "mngtmpaddr"),
-                          NM_UTILS_FLAGS2STR(IFA_F_NOPREFIXROUTE, "noprefixroute"),
-                          NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"), );
-
-NM_UTILS_ENUM2STR_DEFINE(nm_platform_route_scope2str,
-                         int,
-                         NM_UTILS_ENUM2STR(RT_SCOPE_NOWHERE, "nowhere"),
-                         NM_UTILS_ENUM2STR(RT_SCOPE_HOST, "host"),
-                         NM_UTILS_ENUM2STR(RT_SCOPE_LINK, "link"),
-                         NM_UTILS_ENUM2STR(RT_SCOPE_SITE, "site"),
-                         NM_UTILS_ENUM2STR(RT_SCOPE_UNIVERSE, "global"), );
-
-/**
- * nm_platform_ip6_address_to_string:
- * @route: pointer to NMPlatformIP6Address address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
- * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
- *
- * A method for converting an address struct into a string representation.
- *
- * Example output: "2001:db8:0:f101::1/64 lft 4294967295 pref 4294967295 time 16922666 on dev em1"
- *
- * Returns: a string representation of the address.
- */
-const char *
-nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len)
-{
-    char        s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
-    char        s_address[INET6_ADDRSTRLEN];
-    char        s_peer[INET6_ADDRSTRLEN];
-    char        str_lft[30], str_pref[30], str_time[50], s_source[50];
-    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_sec();
-
-    if (!nm_utils_to_string_buffer_init_null(address, &buf, &len))
-        return buf;
-
-    inet_ntop(AF_INET6, &address->address, s_address, sizeof(s_address));
-
-    if (!IN6_IS_ADDR_UNSPECIFIED(&address->peer_address)) {
-        inet_ntop(AF_INET6, &address->peer_address, s_peer, sizeof(s_peer));
-        str_peer = g_strconcat(" ptp ", s_peer, NULL);
-    }
-
-    _to_string_dev(NULL, address->ifindex, str_dev, sizeof(str_dev));
-
-    str_lft_p = _lifetime_to_string(address->timestamp,
-                                    address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT,
-                                    now,
-                                    str_lft,
-                                    sizeof(str_lft)),
-    str_pref_p =
-        (address->lifetime == address->preferred)
-            ? str_lft_p
-            : (_lifetime_to_string(address->timestamp,
-                                   address->lifetime ? MIN(address->preferred, address->lifetime)
-                                                     : NM_PLATFORM_LIFETIME_PERMANENT,
-                                   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));
-
-    g_snprintf(
-        buf,
-        len,
-        "%s/%d lft %s pref %s%s%s%s%s src %s%s",
-        s_address,
-        address->plen,
-        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)),
-        nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
-        address->external ? " ext" : "");
-    g_free(str_peer);
-    return buf;
-}
-
-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"),
-                                 NM_UTILS_FLAGS2STR(8 /*RTNH_F_OFFLOAD*/, "offload"),
-                                 NM_UTILS_FLAGS2STR(16 /*RTNH_F_LINKDOWN*/, "linkdown"),
-                                 NM_UTILS_FLAGS2STR(32 /*RTNH_F_UNRESOLVED*/, "unresolved"),
-
-                                 NM_UTILS_FLAGS2STR(RTM_F_NOTIFY, "notify"),
-                                 NM_UTILS_FLAGS2STR(RTM_F_CLONED, "cloned"),
-                                 NM_UTILS_FLAGS2STR(RTM_F_EQUALIZE, "equalize"),
-                                 NM_UTILS_FLAGS2STR(RTM_F_PREFIX, "prefix"),
-                                 NM_UTILS_FLAGS2STR(0x1000 /*RTM_F_LOOKUP_TABLE*/, "lookup-table"),
-                                 NM_UTILS_FLAGS2STR(0x2000 /*RTM_F_FIB_MATCH*/, "fib-match"), );
-
-#define _RTM_FLAGS_TO_STRING_MAXLEN 200
-
-static const char *
-_rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags)
-{
-    const char *buf0 = buf;
-
-    nm_assert(buf_size >= _RTM_FLAGS_TO_STRING_MAXLEN);
-
-    if (!rtm_flags)
-        return "";
-
-    nm_utils_strbuf_append_str(&buf, &buf_size, " rtm_flags ");
-    _rtm_flags_to_string(rtm_flags, buf, buf_size);
-    nm_assert(strlen(buf) < buf_size);
-    return buf0;
-}
-
-/**
- * nm_platform_ip4_route_to_string:
- * @route: pointer to NMPlatformIP4Route route structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
- * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
- *
- * A method for converting a route struct into a string representation.
- *
- * Example output: "192.168.1.0/24 via 0.0.0.0 dev em1 metric 0 mss 0"
- *
- * Returns: a string representation of the route.
- */
-const char *
-nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len)
-{
-    char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
-    char s_pref_src[INET_ADDRSTRLEN];
-    char str_dev[TO_STRING_DEV_BUF_SIZE];
-    char str_table[30];
-    char str_scope[30], s_source[50];
-    char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
-    char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
-    char str_type[30];
-    char str_metric[30];
-
-    if (!nm_utils_to_string_buffer_init_null(route, &buf, &len))
-        return buf;
-
-    inet_ntop(AF_INET, &route->network, s_network, sizeof(s_network));
-    inet_ntop(AF_INET, &route->gateway, s_gateway, sizeof(s_gateway));
-
-    _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev));
-
-    g_snprintf(
-        buf,
-        len,
-        "type %s " /* type */
-        "%s"       /* table */
-        "%s/%d"
-        " via %s"
-        "%s"
-        " metric %s"
-        " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */
-        "%s"                                   /* rtm_flags */
-        "%s%s"                                 /* scope */
-        "%s%s"                                 /* pref-src */
-        "%s"                                   /* tos */
-        "%s"                                   /* window */
-        "%s"                                   /* cwnd */
-        "%s"                                   /* initcwnd */
-        "%s"                                   /* initrwnd */
-        "%s"                                   /* mtu */
-        "",
-        nm_utils_route_type2str(nm_platform_route_type_uncoerce(route->type_coerced),
-                                str_type,
-                                sizeof(str_type)),
-        route->table_any
-            ? "table ?? "
-            : (route->table_coerced
-                   ? nm_sprintf_buf(str_table,
-                                    "table %u ",
-                                    nm_platform_route_table_uncoerce(route->table_coerced, FALSE))
-                   : ""),
-        s_network,
-        route->plen,
-        s_gateway,
-        str_dev,
-        route->metric_any
-            ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
-            : nm_sprintf_buf(str_metric, "%u", route->metric),
-        route->mss,
-        nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)),
-        _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags),
-        route->scope_inv ? " scope " : "",
-        route->scope_inv
-            ? (nm_platform_route_scope2str(nm_platform_route_scope_inv(route->scope_inv),
-                                           str_scope,
-                                           sizeof(str_scope)))
-            : "",
-        route->pref_src ? " pref-src " : "",
-        route->pref_src ? inet_ntop(AF_INET, &route->pref_src, s_pref_src, sizeof(s_pref_src)) : "",
-        route->tos ? nm_sprintf_buf(str_tos, " tos 0x%x", (unsigned) route->tos) : "",
-        route->window || route->lock_window ? nm_sprintf_buf(str_window,
-                                                             " window %s%" G_GUINT32_FORMAT,
-                                                             route->lock_window ? "lock " : "",
-                                                             route->window)
-                                            : "",
-        route->cwnd || route->lock_cwnd ? nm_sprintf_buf(str_cwnd,
-                                                         " cwnd %s%" G_GUINT32_FORMAT,
-                                                         route->lock_cwnd ? "lock " : "",
-                                                         route->cwnd)
-                                        : "",
-        route->initcwnd || route->lock_initcwnd
-            ? nm_sprintf_buf(str_initcwnd,
-                             " initcwnd %s%" G_GUINT32_FORMAT,
-                             route->lock_initcwnd ? "lock " : "",
-                             route->initcwnd)
-            : "",
-        route->initrwnd || route->lock_initrwnd
-            ? nm_sprintf_buf(str_initrwnd,
-                             " initrwnd %s%" G_GUINT32_FORMAT,
-                             route->lock_initrwnd ? "lock " : "",
-                             route->initrwnd)
-            : "",
-        route->mtu || route->lock_mtu ? nm_sprintf_buf(str_mtu,
-                                                       " mtu %s%" G_GUINT32_FORMAT,
-                                                       route->lock_mtu ? "lock " : "",
-                                                       route->mtu)
-                                      : "");
-    return buf;
-}
-
-/**
- * nm_platform_ip6_route_to_string:
- * @route: pointer to NMPlatformIP6Route route structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
- * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
- *
- * A method for converting a route struct into a string representation.
- *
- * Example output: "ff02::fb/128 via :: dev em1 metric 0"
- *
- * Returns: a string representation of the route.
- */
-const char *
-nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len)
-{
-    char s_network[INET6_ADDRSTRLEN];
-    char s_gateway[INET6_ADDRSTRLEN];
-    char s_pref_src[INET6_ADDRSTRLEN];
-    char s_src_all[INET6_ADDRSTRLEN + 40];
-    char s_src[INET6_ADDRSTRLEN];
-    char str_type[30];
-    char str_table[30];
-    char str_pref[40];
-    char str_pref2[30];
-    char str_dev[TO_STRING_DEV_BUF_SIZE];
-    char s_source[50];
-    char str_window[32];
-    char str_cwnd[32];
-    char str_initcwnd[32];
-    char str_initrwnd[32];
-    char str_mtu[32];
-    char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
-    char str_metric[30];
-
-    if (!nm_utils_to_string_buffer_init_null(route, &buf, &len))
-        return buf;
-
-    inet_ntop(AF_INET6, &route->network, s_network, sizeof(s_network));
-    inet_ntop(AF_INET6, &route->gateway, s_gateway, sizeof(s_gateway));
-
-    if (IN6_IS_ADDR_UNSPECIFIED(&route->pref_src))
-        s_pref_src[0] = 0;
-    else
-        inet_ntop(AF_INET6, &route->pref_src, s_pref_src, sizeof(s_pref_src));
-
-    _to_string_dev(NULL, route->ifindex, str_dev, sizeof(str_dev));
-
-    g_snprintf(
-        buf,
-        len,
-        "type %s " /* type */
-        "%s"       /* table */
-        "%s/%d"
-        " via %s"
-        "%s"
-        " metric %s"
-        " mss %" G_GUINT32_FORMAT " rt-src %s" /* protocol */
-        "%s"                                   /* source */
-        "%s"                                   /* rtm_flags */
-        "%s%s"                                 /* pref-src */
-        "%s"                                   /* window */
-        "%s"                                   /* cwnd */
-        "%s"                                   /* initcwnd */
-        "%s"                                   /* initrwnd */
-        "%s"                                   /* mtu */
-        "%s"                                   /* pref */
-        "",
-        nm_utils_route_type2str(nm_platform_route_type_uncoerce(route->type_coerced),
-                                str_type,
-                                sizeof(str_type)),
-        route->table_any
-            ? "table ?? "
-            : (route->table_coerced
-                   ? nm_sprintf_buf(str_table,
-                                    "table %u ",
-                                    nm_platform_route_table_uncoerce(route->table_coerced, FALSE))
-                   : ""),
-        s_network,
-        route->plen,
-        s_gateway,
-        str_dev,
-        route->metric_any
-            ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
-            : nm_sprintf_buf(str_metric, "%u", route->metric),
-        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)
-            : "",
-        _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags),
-        s_pref_src[0] ? " pref-src " : "",
-        s_pref_src[0] ? s_pref_src : "",
-        route->window || route->lock_window ? nm_sprintf_buf(str_window,
-                                                             " window %s%" G_GUINT32_FORMAT,
-                                                             route->lock_window ? "lock " : "",
-                                                             route->window)
-                                            : "",
-        route->cwnd || route->lock_cwnd ? nm_sprintf_buf(str_cwnd,
-                                                         " cwnd %s%" G_GUINT32_FORMAT,
-                                                         route->lock_cwnd ? "lock " : "",
-                                                         route->cwnd)
-                                        : "",
-        route->initcwnd || route->lock_initcwnd
-            ? nm_sprintf_buf(str_initcwnd,
-                             " initcwnd %s%" G_GUINT32_FORMAT,
-                             route->lock_initcwnd ? "lock " : "",
-                             route->initcwnd)
-            : "",
-        route->initrwnd || route->lock_initrwnd
-            ? nm_sprintf_buf(str_initrwnd,
-                             " initrwnd %s%" G_GUINT32_FORMAT,
-                             route->lock_initrwnd ? "lock " : "",
-                             route->initrwnd)
-            : "",
-        route->mtu || route->lock_mtu ? nm_sprintf_buf(str_mtu,
-                                                       " mtu %s%" G_GUINT32_FORMAT,
-                                                       route->lock_mtu ? "lock " : "",
-                                                       route->mtu)
-                                      : "",
-        route->rt_pref ? nm_sprintf_buf(
-            str_pref,
-            " pref %s",
-            nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2)))
-                       : "");
-
-    return buf;
-}
-
-static void
-_routing_rule_addr_to_string(char **         buf,
-                             gsize *         len,
-                             int             addr_family,
-                             const NMIPAddr *addr,
-                             guint8          plen,
-                             gboolean        is_src)
-{
-    char     s_addr[NM_UTILS_INET_ADDRSTRLEN];
-    gboolean is_zero;
-    gsize    addr_size;
-
-    nm_assert_addr_family(addr_family);
-    nm_assert(addr);
-
-    addr_size = nm_utils_addr_family_to_size(addr_family);
-
-    is_zero = nm_utils_memeqzero(addr, addr_size);
-
-    if (plen == 0 && is_zero) {
-        if (is_src)
-            nm_utils_strbuf_append_str(buf, len, " from all");
-        else
-            nm_utils_strbuf_append_str(buf, len, "");
-        return;
-    }
-
-    nm_utils_strbuf_append_str(buf, len, is_src ? " from " : " to ");
-
-    nm_utils_strbuf_append_str(buf, len, nm_utils_inet_ntop(addr_family, addr, s_addr));
-
-    if (plen != (addr_size * 8))
-        nm_utils_strbuf_append(buf, len, "/%u", plen);
-}
-
-static void
-_routing_rule_port_range_to_string(char **                   buf,
-                                   gsize *                   len,
-                                   const NMFibRulePortRange *port_range,
-                                   const char *              name)
-{
-    if (port_range->start == 0 && port_range->end == 0)
-        nm_utils_strbuf_append_str(buf, len, "");
-    else {
-        nm_utils_strbuf_append(buf, len, " %s %u", name, port_range->start);
-        if (port_range->start != port_range->end)
-            nm_utils_strbuf_append(buf, len, "-%u", port_range->end);
-    }
-}
-
-const char *
-nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len)
-{
-    const char *buf0;
-    guint32     rr_flags;
-
-    if (!nm_utils_to_string_buffer_init_null(routing_rule, &buf, &len))
-        return buf;
-
-    if (!NM_IN_SET(routing_rule->addr_family, AF_INET, AF_INET6)) {
-        /* invalid addr-family. The other fields are undefined. */
-        if (routing_rule->addr_family == AF_UNSPEC)
-            g_snprintf(buf, len, "[routing-rule]");
-        else
-            g_snprintf(buf, len, "[routing-rule family:%u]", routing_rule->addr_family);
-        return buf;
-    }
-
-    buf0 = buf;
-
-    rr_flags = routing_rule->flags;
-
-    rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_INVERT);
-    nm_utils_strbuf_append(&buf,
-                           &len,
-                           "[%c] " /* addr-family */
-                           "%u:"   /* priority */
-                           "%s",   /* not/FIB_RULE_INVERT */
-                           nm_utils_addr_family_to_char(routing_rule->addr_family),
-                           routing_rule->priority,
-                           (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_INVERT) ? " not" : ""));
-
-    _routing_rule_addr_to_string(&buf,
-                                 &len,
-                                 routing_rule->addr_family,
-                                 &routing_rule->src,
-                                 routing_rule->src_len,
-                                 TRUE);
-
-    _routing_rule_addr_to_string(&buf,
-                                 &len,
-                                 routing_rule->addr_family,
-                                 &routing_rule->dst,
-                                 routing_rule->dst_len,
-                                 FALSE);
-
-    if (routing_rule->tos)
-        nm_utils_strbuf_append(&buf, &len, " tos 0x%02x", routing_rule->tos);
-
-    if (routing_rule->fwmark != 0 || routing_rule->fwmask != 0) {
-        nm_utils_strbuf_append(&buf, &len, " fwmark %#x", (unsigned) routing_rule->fwmark);
-        if (routing_rule->fwmark != 0xFFFFFFFFu)
-            nm_utils_strbuf_append(&buf, &len, "/%#x", (unsigned) routing_rule->fwmask);
-    }
-
-    if (routing_rule->iifname[0]) {
-        nm_utils_strbuf_append(&buf, &len, " iif %s", routing_rule->iifname);
-        rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_IIF_DETACHED);
-        if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_IIF_DETACHED))
-            nm_utils_strbuf_append_str(&buf, &len, " [detached]");
-    }
-
-    if (routing_rule->oifname[0]) {
-        nm_utils_strbuf_append(&buf, &len, " oif %s", routing_rule->oifname);
-        rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_OIF_DETACHED);
-        if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_OIF_DETACHED))
-            nm_utils_strbuf_append_str(&buf, &len, " [detached]");
-    }
-
-    if (routing_rule->l3mdev != 0) {
-        if (routing_rule->l3mdev == 1)
-            nm_utils_strbuf_append_str(&buf, &len, " lookup [l3mdev-table]");
-        else {
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   " lookup [l3mdev-table/%u]",
-                                   (unsigned) routing_rule->l3mdev);
-        }
-    }
-
-    if (routing_rule->uid_range_has || routing_rule->uid_range.start
-        || routing_rule->uid_range.end) {
-        nm_utils_strbuf_append(&buf,
-                               &len,
-                               " uidrange %u-%u%s",
-                               routing_rule->uid_range.start,
-                               routing_rule->uid_range.end,
-                               routing_rule->uid_range_has ? "" : "(?)");
-    }
-
-    if (routing_rule->ip_proto != 0) {
-        /* we don't call getprotobynumber(), just print the numeric value.
-         * This differs from what ip-rule prints. */
-        nm_utils_strbuf_append(&buf, &len, " ipproto %u", routing_rule->ip_proto);
-    }
-
-    _routing_rule_port_range_to_string(&buf, &len, &routing_rule->sport_range, "sport");
-
-    _routing_rule_port_range_to_string(&buf, &len, &routing_rule->dport_range, "dport");
-
-    if (routing_rule->tun_id != 0) {
-        nm_utils_strbuf_append(&buf, &len, " tun_id %" G_GUINT64_FORMAT, routing_rule->tun_id);
-    }
-
-    if (routing_rule->table != 0) {
-        nm_utils_strbuf_append(&buf, &len, " lookup %u", routing_rule->table);
-    }
-
-    if (routing_rule->suppress_prefixlen_inverse != 0) {
-        nm_utils_strbuf_append(&buf,
-                               &len,
-                               " suppress_prefixlen %d",
-                               (int) (~routing_rule->suppress_prefixlen_inverse));
-    }
-
-    if (routing_rule->suppress_ifgroup_inverse != 0) {
-        nm_utils_strbuf_append(&buf,
-                               &len,
-                               " suppress_ifgroup %d",
-                               (int) (~routing_rule->suppress_ifgroup_inverse));
-    }
-
-    if (routing_rule->flow) {
-        /* FRA_FLOW is only for IPv4, but we want to print the value for all address-families,
-         * to see when it is set. In practice, this should not be set except for IPv4.
-         *
-         * We don't follow the style how ip-rule prints flow/realms. It's confusing. Just
-         * print the value hex. */
-        nm_utils_strbuf_append(&buf, &len, " realms 0x%08x", routing_rule->flow);
-    }
-
-    if (routing_rule->action == RTN_NAT) {
-        G_STATIC_ASSERT_EXPR(RTN_NAT == 10);
-
-        /* NAT is deprecated for many years. We don't support RTA_GATEWAY/FRA_UNUSED2
-         * for the gateway, and so do recent kernels ignore that parameter. */
-        nm_utils_strbuf_append_str(&buf, &len, " masquerade");
-    } else if (routing_rule->action == FR_ACT_GOTO) {
-        if (routing_rule->goto_target != 0)
-            nm_utils_strbuf_append(&buf, &len, " goto %u", routing_rule->goto_target);
-        else
-            nm_utils_strbuf_append_str(&buf, &len, " goto none");
-        rr_flags = NM_FLAGS_UNSET(rr_flags, FIB_RULE_UNRESOLVED);
-        if (NM_FLAGS_HAS(routing_rule->flags, FIB_RULE_UNRESOLVED))
-            nm_utils_strbuf_append_str(&buf, &len, " unresolved");
-    } else if (routing_rule->action != FR_ACT_TO_TBL) {
-        const char *ss;
-        char        ss_buf[60];
-
-#define _V(v1, v2) ((sizeof(char[(((int) (v1)) == ((int) (v2))) ? 1 : -1]) * 0) + (v1))
-        switch (routing_rule->action) {
-        case _V(FR_ACT_UNSPEC, RTN_UNSPEC):
-            ss = "none";
-            break;
-        case _V(FR_ACT_TO_TBL, RTN_UNICAST):
-            ss = "unicast";
-            break;
-        case _V(FR_ACT_GOTO, RTN_LOCAL):
-            ss = "local";
-            break;
-        case _V(FR_ACT_NOP, RTN_BROADCAST):
-            ss = "nop";
-            break;
-        case _V(FR_ACT_RES3, RTN_ANYCAST):
-            ss = "anycast";
-            break;
-        case _V(FR_ACT_RES4, RTN_MULTICAST):
-            ss = "multicast";
-            break;
-        case _V(FR_ACT_BLACKHOLE, RTN_BLACKHOLE):
-            ss = "blackhole";
-            break;
-        case _V(FR_ACT_UNREACHABLE, RTN_UNREACHABLE):
-            ss = "unreachable";
-            break;
-        case _V(FR_ACT_PROHIBIT, RTN_PROHIBIT):
-            ss = "prohibit";
-            break;
-        case RTN_THROW:
-            ss = "throw";
-            break;
-        case RTN_NAT:
-            ss = "nat";
-            break;
-        case RTN_XRESOLVE:
-            ss = "xresolve";
-            break;
-        default:
-            ss = nm_sprintf_buf(ss_buf, "action-%u", routing_rule->action);
-            break;
-        }
-#undef _V
-        nm_utils_strbuf_append(&buf, &len, " %s", ss);
-    }
-
-    if (routing_rule->protocol != RTPROT_UNSPEC)
-        nm_utils_strbuf_append(&buf, &len, " protocol %u", routing_rule->protocol);
-
-    if (routing_rule->goto_target != 0 && routing_rule->action != FR_ACT_GOTO) {
-        /* a trailing target is set for an unexpected action. Print it. */
-        nm_utils_strbuf_append(&buf, &len, " goto-target %u", routing_rule->goto_target);
-    }
-
-    if (rr_flags != 0) {
-        /* we have some flags we didn't print about yet. */
-        nm_utils_strbuf_append(&buf, &len, " remaining-flags %x", rr_flags);
-    }
-
-    return buf0;
-}
-
-const char *
-nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len)
-{
-    char        str_dev[TO_STRING_DEV_BUF_SIZE];
-    const char *buf0;
-
-    if (!nm_utils_to_string_buffer_init_null(qdisc, &buf, &len))
-        return buf;
-
-    buf0 = buf;
-
-    nm_utils_strbuf_append(&buf,
-                           &len,
-                           "%s%s family %u handle %x parent %x info %x",
-                           qdisc->kind,
-                           _to_string_dev(NULL, qdisc->ifindex, str_dev, sizeof(str_dev)),
-                           qdisc->addr_family,
-                           qdisc->handle,
-                           qdisc->parent,
-                           qdisc->info);
-
-    if (nm_streq0(qdisc->kind, "fq_codel")) {
-        if (qdisc->fq_codel.limit)
-            nm_utils_strbuf_append(&buf, &len, " limit %u", qdisc->fq_codel.limit);
-        if (qdisc->fq_codel.flows)
-            nm_utils_strbuf_append(&buf, &len, " flows %u", qdisc->fq_codel.flows);
-        if (qdisc->fq_codel.target)
-            nm_utils_strbuf_append(&buf, &len, " target %u", qdisc->fq_codel.target);
-        if (qdisc->fq_codel.interval)
-            nm_utils_strbuf_append(&buf, &len, " interval %u", qdisc->fq_codel.interval);
-        if (qdisc->fq_codel.quantum)
-            nm_utils_strbuf_append(&buf, &len, " quantum %u", qdisc->fq_codel.quantum);
-        if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
-            nm_utils_strbuf_append(&buf, &len, " ce_threshold %u", qdisc->fq_codel.ce_threshold);
-        if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
-            nm_utils_strbuf_append(&buf, &len, " memory_limit %u", qdisc->fq_codel.memory_limit);
-        if (qdisc->fq_codel.ecn)
-            nm_utils_strbuf_append(&buf, &len, " ecn");
-    } else if (nm_streq0(qdisc->kind, "sfq")) {
-        if (qdisc->sfq.quantum)
-            nm_utils_strbuf_append(&buf, &len, " quantum %u", qdisc->sfq.quantum);
-        if (qdisc->sfq.perturb_period)
-            nm_utils_strbuf_append(&buf, &len, " perturb %d", qdisc->sfq.perturb_period);
-        if (qdisc->sfq.limit)
-            nm_utils_strbuf_append(&buf, &len, " limit %u", (guint) qdisc->sfq.limit);
-        if (qdisc->sfq.divisor)
-            nm_utils_strbuf_append(&buf, &len, " divisor %u", qdisc->sfq.divisor);
-        if (qdisc->sfq.flows)
-            nm_utils_strbuf_append(&buf, &len, " flows %u", qdisc->sfq.flows);
-        if (qdisc->sfq.depth)
-            nm_utils_strbuf_append(&buf, &len, " depth %u", qdisc->sfq.depth);
-    } else if (nm_streq0(qdisc->kind, "tbf")) {
-        nm_utils_strbuf_append(&buf, &len, " rate %" G_GUINT64_FORMAT, qdisc->tbf.rate);
-        nm_utils_strbuf_append(&buf, &len, " burst %u", qdisc->tbf.burst);
-        if (qdisc->tbf.limit)
-            nm_utils_strbuf_append(&buf, &len, " limit %u", qdisc->tbf.limit);
-        if (qdisc->tbf.latency)
-            nm_utils_strbuf_append(&buf, &len, " latency %uns", qdisc->tbf.latency);
-    }
-
-    return buf0;
-}
-
-void
-nm_platform_qdisc_hash_update(const NMPlatformQdisc *obj, NMHashState *h)
-{
-    nm_hash_update_str0(h, obj->kind);
-    nm_hash_update_vals(h, obj->ifindex, obj->addr_family, obj->handle, obj->parent, obj->info);
-    if (nm_streq0(obj->kind, "fq_codel")) {
-        nm_hash_update_vals(h,
-                            obj->fq_codel.limit,
-                            obj->fq_codel.flows,
-                            obj->fq_codel.target,
-                            obj->fq_codel.interval,
-                            obj->fq_codel.quantum,
-                            obj->fq_codel.ce_threshold,
-                            obj->fq_codel.memory_limit,
-                            NM_HASH_COMBINE_BOOLS(guint8, obj->fq_codel.ecn));
-    } else if (nm_streq0(obj->kind, "sfq")) {
-        nm_hash_update_vals(h,
-                            obj->sfq.quantum,
-                            obj->sfq.perturb_period,
-                            obj->sfq.limit,
-                            obj->sfq.divisor,
-                            obj->sfq.flows,
-                            obj->sfq.depth);
-    } else if (nm_streq0(obj->kind, "tbf")) {
-        nm_hash_update_vals(h, obj->tbf.rate, obj->tbf.burst, obj->tbf.limit, obj->tbf.latency);
-    }
-}
-
-int
-nm_platform_qdisc_cmp_full(const NMPlatformQdisc *a,
-                           const NMPlatformQdisc *b,
-                           gboolean               compare_handle)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, ifindex);
-    NM_CMP_FIELD(a, b, parent);
-    NM_CMP_FIELD_STR_INTERNED(a, b, kind);
-    NM_CMP_FIELD(a, b, addr_family);
-    if (compare_handle)
-        NM_CMP_FIELD(a, b, handle);
-    NM_CMP_FIELD(a, b, info);
-
-    if (nm_streq0(a->kind, "fq_codel")) {
-        NM_CMP_FIELD(a, b, fq_codel.limit);
-        NM_CMP_FIELD(a, b, fq_codel.flows);
-        NM_CMP_FIELD(a, b, fq_codel.target);
-        NM_CMP_FIELD(a, b, fq_codel.interval);
-        NM_CMP_FIELD(a, b, fq_codel.quantum);
-        NM_CMP_FIELD(a, b, fq_codel.ce_threshold);
-        NM_CMP_FIELD(a, b, fq_codel.memory_limit);
-        NM_CMP_FIELD_UNSAFE(a, b, fq_codel.ecn);
-    } else if (nm_streq0(a->kind, "sfq")) {
-        NM_CMP_FIELD(a, b, sfq.quantum);
-        NM_CMP_FIELD(a, b, sfq.perturb_period);
-        NM_CMP_FIELD(a, b, sfq.limit);
-        NM_CMP_FIELD(a, b, sfq.flows);
-        NM_CMP_FIELD(a, b, sfq.divisor);
-        NM_CMP_FIELD(a, b, sfq.depth);
-    } else if (nm_streq0(a->kind, "tbf")) {
-        NM_CMP_FIELD(a, b, tbf.rate);
-        NM_CMP_FIELD(a, b, tbf.burst);
-        NM_CMP_FIELD(a, b, tbf.limit);
-        NM_CMP_FIELD(a, b, tbf.latency);
-    }
-
-    return 0;
-}
-
-int
-nm_platform_qdisc_cmp(const NMPlatformQdisc *a, const NMPlatformQdisc *b)
-{
-    return nm_platform_qdisc_cmp_full(a, b, TRUE);
-}
-
-const char *
-nm_platform_tfilter_to_string(const NMPlatformTfilter *tfilter, char *buf, gsize len)
-{
-    char  str_dev[TO_STRING_DEV_BUF_SIZE];
-    char  act_buf[300];
-    char *p;
-    gsize l;
-
-    if (!nm_utils_to_string_buffer_init_null(tfilter, &buf, &len))
-        return buf;
-
-    if (tfilter->action.kind) {
-        p = act_buf;
-        l = sizeof(act_buf);
-
-        nm_utils_strbuf_append(&p, &l, " \"%s\"", tfilter->action.kind);
-        if (nm_streq(tfilter->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
-            gs_free char *t = NULL;
-
-            nm_utils_strbuf_append(
-                &p,
-                &l,
-                " (\"%s\")",
-                nm_utils_str_utf8safe_escape(tfilter->action.kind,
-                                             NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
-                                                 | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
-                                             &t));
-        } else if (nm_streq(tfilter->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
-            nm_utils_strbuf_append(&p,
-                                   &l,
-                                   "%s%s%s%s dev %d",
-                                   tfilter->action.mirred.ingress ? " ingress" : "",
-                                   tfilter->action.mirred.egress ? " egress" : "",
-                                   tfilter->action.mirred.mirror ? " mirror" : "",
-                                   tfilter->action.mirred.redirect ? " redirect" : "",
-                                   tfilter->action.mirred.ifindex);
-        }
-    } else
-        act_buf[0] = '\0';
-
-    g_snprintf(buf,
-               len,
-               "%s%s family %u handle %x parent %x info %x%s",
-               tfilter->kind,
-               _to_string_dev(NULL, tfilter->ifindex, str_dev, sizeof(str_dev)),
-               tfilter->addr_family,
-               tfilter->handle,
-               tfilter->parent,
-               tfilter->info,
-               act_buf);
-
-    return buf;
-}
-
-void
-nm_platform_tfilter_hash_update(const NMPlatformTfilter *obj, NMHashState *h)
-{
-    nm_hash_update_str0(h, obj->kind);
-    nm_hash_update_vals(h, obj->ifindex, obj->addr_family, obj->handle, obj->parent, obj->info);
-    if (obj->action.kind) {
-        nm_hash_update_str(h, obj->action.kind);
-        if (nm_streq(obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
-            nm_hash_update_strarr(h, obj->action.simple.sdata);
-        } else if (nm_streq(obj->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
-            nm_hash_update_vals(h,
-                                obj->action.mirred.ifindex,
-                                NM_HASH_COMBINE_BOOLS(guint8,
-                                                      obj->action.mirred.ingress,
-                                                      obj->action.mirred.egress,
-                                                      obj->action.mirred.mirror,
-                                                      obj->action.mirred.redirect));
-        }
-    }
-}
-
-int
-nm_platform_tfilter_cmp(const NMPlatformTfilter *a, const NMPlatformTfilter *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, ifindex);
-    NM_CMP_FIELD(a, b, parent);
-    NM_CMP_FIELD_STR_INTERNED(a, b, kind);
-    NM_CMP_FIELD(a, b, addr_family);
-    NM_CMP_FIELD(a, b, handle);
-    NM_CMP_FIELD(a, b, info);
-
-    NM_CMP_FIELD_STR_INTERNED(a, b, action.kind);
-    if (a->action.kind) {
-        if (nm_streq(a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
-            NM_CMP_FIELD_STR(a, b, action.simple.sdata);
-        } else if (nm_streq(a->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
-            NM_CMP_FIELD(a, b, action.mirred.ifindex);
-            NM_CMP_FIELD_UNSAFE(a, b, action.mirred.ingress);
-            NM_CMP_FIELD_UNSAFE(a, b, action.mirred.egress);
-            NM_CMP_FIELD_UNSAFE(a, b, action.mirred.mirror);
-            NM_CMP_FIELD_UNSAFE(a, b, action.mirred.redirect);
-        }
-    }
-
-    return 0;
-}
-
-const char *
-nm_platform_vf_to_string(const NMPlatformVF *vf, char *buf, gsize len)
-{
-    char                 str_mac[128], mac[128];
-    char                 str_spoof_check[64];
-    char                 str_trust[64];
-    char                 str_min_tx_rate[64];
-    char                 str_max_tx_rate[64];
-    nm_auto_free_gstring GString *gstr_vlans = NULL;
-    guint                         i;
-
-    if (!nm_utils_to_string_buffer_init_null(vf, &buf, &len))
-        return buf;
-
-    if (vf->mac.len) {
-        _nm_utils_hwaddr_ntoa(vf->mac.data, vf->mac.len, TRUE, mac, sizeof(mac));
-        nm_sprintf_buf(str_mac, " mac %s", mac);
-    } else
-        str_mac[0] = '\0';
-
-    if (vf->num_vlans) {
-        gstr_vlans = g_string_new("");
-        for (i = 0; i < vf->num_vlans; i++) {
-            g_string_append_printf(gstr_vlans, " vlan %u", (unsigned) vf->vlans[i].id);
-            if (vf->vlans[i].qos)
-                g_string_append_printf(gstr_vlans, " qos %u", (unsigned) vf->vlans[i].qos);
-            if (vf->vlans[i].proto_ad)
-                g_string_append(gstr_vlans, " proto 802.1ad");
-        }
-    }
-
-    g_snprintf(buf,
-               len,
-               "%u"  /* index */
-               "%s"  /* MAC */
-               "%s"  /* spoof check */
-               "%s"  /* trust */
-               "%s"  /* min tx rate */
-               "%s"  /* max tx rate */
-               "%s", /* VLANs */
-               vf->index,
-               str_mac,
-               vf->spoofchk >= 0 ? nm_sprintf_buf(str_spoof_check, " spoofchk %d", vf->spoofchk)
-                                 : "",
-               vf->trust >= 0 ? nm_sprintf_buf(str_trust, " trust %d", vf->trust) : "",
-               vf->min_tx_rate
-                   ? nm_sprintf_buf(str_min_tx_rate, " min_tx_rate %u", (unsigned) vf->min_tx_rate)
-                   : "",
-               vf->max_tx_rate
-                   ? nm_sprintf_buf(str_max_tx_rate, " max_tx_rate %u", (unsigned) vf->max_tx_rate)
-                   : "",
-               gstr_vlans ? gstr_vlans->str : "");
-
-    return buf;
-}
-
-const char *
-nm_platform_bridge_vlan_to_string(const NMPlatformBridgeVlan *vlan, char *buf, gsize len)
-{
-    char str_vid_end[64];
-
-    if (!nm_utils_to_string_buffer_init_null(vlan, &buf, &len))
-        return buf;
-
-    g_snprintf(buf,
-               len,
-               "%u"
-               "%s"
-               "%s"
-               "%s",
-               vlan->vid_start,
-               vlan->vid_start != vlan->vid_end ? nm_sprintf_buf(str_vid_end, "-%u", vlan->vid_end)
-                                                : "",
-               vlan->pvid ? " PVID" : "",
-               vlan->untagged ? " untagged" : "");
-
-    return buf;
-}
-
-void
-nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->ifindex,
-                        obj->master,
-                        obj->parent,
-                        obj->n_ifi_flags,
-                        obj->mtu,
-                        obj->type,
-                        obj->arptype,
-                        obj->inet6_addr_gen_mode_inv,
-                        obj->inet6_token,
-                        obj->rx_packets,
-                        obj->rx_bytes,
-                        obj->tx_packets,
-                        obj->tx_bytes,
-                        NM_HASH_COMBINE_BOOLS(guint8, obj->connected, obj->initialized));
-    nm_hash_update_strarr(h, obj->name);
-    nm_hash_update_str0(h, obj->kind);
-    nm_hash_update_str0(h, obj->driver);
-    /* nm_hash_update_mem() also hashes the length obj->addr.len */
-    nm_hash_update_mem(h,
-                       obj->l_address.data,
-                       NM_MIN(obj->l_address.len, sizeof(obj->l_address.data)));
-    nm_hash_update_mem(h,
-                       obj->l_broadcast.data,
-                       NM_MIN(obj->l_broadcast.len, sizeof(obj->l_broadcast.data)));
-}
-
-int
-nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, ifindex);
-    NM_CMP_FIELD(a, b, type);
-    NM_CMP_FIELD_STR(a, b, name);
-    NM_CMP_FIELD(a, b, master);
-    NM_CMP_FIELD(a, b, parent);
-    NM_CMP_FIELD(a, b, n_ifi_flags);
-    NM_CMP_FIELD_UNSAFE(a, b, connected);
-    NM_CMP_FIELD(a, b, mtu);
-    NM_CMP_FIELD_BOOL(a, b, initialized);
-    NM_CMP_FIELD(a, b, arptype);
-    NM_CMP_FIELD(a, b, l_address.len);
-    NM_CMP_FIELD(a, b, l_broadcast.len);
-    NM_CMP_FIELD(a, b, inet6_addr_gen_mode_inv);
-    NM_CMP_FIELD_STR_INTERNED(a, b, kind);
-    NM_CMP_FIELD_STR_INTERNED(a, b, driver);
-    if (a->l_address.len)
-        NM_CMP_FIELD_MEMCMP_LEN(a, b, l_address.data, a->l_address.len);
-    if (a->l_broadcast.len)
-        NM_CMP_FIELD_MEMCMP_LEN(a, b, l_broadcast.data, a->l_broadcast.len);
-    NM_CMP_FIELD_MEMCMP(a, b, inet6_token);
-    NM_CMP_FIELD(a, b, rx_packets);
-    NM_CMP_FIELD(a, b, rx_bytes);
-    NM_CMP_FIELD(a, b, tx_packets);
-    NM_CMP_FIELD(a, b, tx_bytes);
-    return 0;
-}
-
-void
-nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->forward_delay,
-                        obj->hello_time,
-                        obj->max_age,
-                        obj->ageing_time,
-                        obj->priority,
-                        obj->vlan_protocol,
-                        obj->group_fwd_mask,
-                        obj->group_addr,
-                        obj->mcast_hash_max,
-                        obj->mcast_last_member_count,
-                        obj->mcast_startup_query_count,
-                        obj->mcast_last_member_interval,
-                        obj->mcast_membership_interval,
-                        obj->mcast_querier_interval,
-                        obj->mcast_query_interval,
-                        obj->mcast_router,
-                        obj->mcast_query_response_interval,
-                        obj->mcast_startup_query_interval,
-                        NM_HASH_COMBINE_BOOLS(guint8,
-                                              obj->stp_state,
-                                              obj->mcast_querier,
-                                              obj->mcast_query_use_ifaddr,
-                                              obj->mcast_snooping,
-                                              obj->vlan_stats_enabled));
-}
-
-int
-nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, forward_delay);
-    NM_CMP_FIELD(a, b, hello_time);
-    NM_CMP_FIELD(a, b, max_age);
-    NM_CMP_FIELD(a, b, ageing_time);
-    NM_CMP_FIELD_BOOL(a, b, stp_state);
-    NM_CMP_FIELD(a, b, priority);
-    NM_CMP_FIELD(a, b, vlan_protocol);
-    NM_CMP_FIELD_BOOL(a, b, vlan_stats_enabled);
-    NM_CMP_FIELD(a, b, group_fwd_mask);
-    NM_CMP_FIELD_MEMCMP(a, b, group_addr);
-    NM_CMP_FIELD_BOOL(a, b, mcast_snooping);
-    NM_CMP_FIELD(a, b, mcast_router);
-    NM_CMP_FIELD_BOOL(a, b, mcast_query_use_ifaddr);
-    NM_CMP_FIELD_BOOL(a, b, mcast_querier);
-    NM_CMP_FIELD(a, b, mcast_hash_max);
-    NM_CMP_FIELD(a, b, mcast_last_member_count);
-    NM_CMP_FIELD(a, b, mcast_startup_query_count);
-    NM_CMP_FIELD(a, b, mcast_last_member_interval);
-    NM_CMP_FIELD(a, b, mcast_membership_interval);
-    NM_CMP_FIELD(a, b, mcast_querier_interval);
-    NM_CMP_FIELD(a, b, mcast_query_interval);
-    NM_CMP_FIELD(a, b, mcast_query_response_interval);
-    NM_CMP_FIELD(a, b, mcast_startup_query_interval);
-
-    return 0;
-}
-
-void
-nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->local,
-                        obj->remote,
-                        obj->parent_ifindex,
-                        obj->input_flags,
-                        obj->output_flags,
-                        obj->input_key,
-                        obj->output_key,
-                        obj->ttl,
-                        obj->tos,
-                        (bool) obj->path_mtu_discovery,
-                        (bool) obj->is_tap);
-}
-
-int
-nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, parent_ifindex);
-    NM_CMP_FIELD(a, b, input_flags);
-    NM_CMP_FIELD(a, b, output_flags);
-    NM_CMP_FIELD(a, b, input_key);
-    NM_CMP_FIELD(a, b, output_key);
-    NM_CMP_FIELD(a, b, local);
-    NM_CMP_FIELD(a, b, remote);
-    NM_CMP_FIELD(a, b, ttl);
-    NM_CMP_FIELD(a, b, tos);
-    NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery);
-    NM_CMP_FIELD_BOOL(a, b, is_tap);
-    return 0;
-}
-
-void
-nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h)
-{
-    nm_hash_update_val(h, obj->p_key);
-    nm_hash_update_str0(h, obj->mode);
-}
-
-int
-nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, p_key);
-    NM_CMP_FIELD_STR_INTERNED(a, b, mode);
-    return 0;
-}
-
-void
-nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->local,
-                        obj->remote,
-                        obj->parent_ifindex,
-                        obj->ttl,
-                        obj->tclass,
-                        obj->encap_limit,
-                        obj->proto,
-                        obj->flow_label,
-                        obj->flags,
-                        obj->input_flags,
-                        obj->output_flags,
-                        obj->input_key,
-                        obj->output_key,
-                        (bool) obj->is_gre,
-                        (bool) obj->is_tap);
-}
-
-int
-nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, parent_ifindex);
-    NM_CMP_FIELD_MEMCMP(a, b, local);
-    NM_CMP_FIELD_MEMCMP(a, b, remote);
-    NM_CMP_FIELD(a, b, ttl);
-    NM_CMP_FIELD(a, b, tclass);
-    NM_CMP_FIELD(a, b, encap_limit);
-    NM_CMP_FIELD(a, b, flow_label);
-    NM_CMP_FIELD(a, b, proto);
-    NM_CMP_FIELD(a, b, flags);
-    NM_CMP_FIELD(a, b, input_flags);
-    NM_CMP_FIELD(a, b, output_flags);
-    NM_CMP_FIELD(a, b, input_key);
-    NM_CMP_FIELD(a, b, output_key);
-    NM_CMP_FIELD_BOOL(a, b, is_gre);
-    NM_CMP_FIELD_BOOL(a, b, is_tap);
-    return 0;
-}
-
-void
-nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->local,
-                        obj->remote,
-                        obj->parent_ifindex,
-                        obj->ttl,
-                        obj->tos,
-                        (bool) obj->path_mtu_discovery);
-}
-
-int
-nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, parent_ifindex);
-    NM_CMP_FIELD(a, b, local);
-    NM_CMP_FIELD(a, b, remote);
-    NM_CMP_FIELD(a, b, ttl);
-    NM_CMP_FIELD(a, b, tos);
-    NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery);
-    return 0;
-}
-
-void
-nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->parent_ifindex,
-                        obj->sci,
-                        obj->cipher_suite,
-                        obj->window,
-                        obj->icv_length,
-                        obj->encoding_sa,
-                        obj->validation,
-                        NM_HASH_COMBINE_BOOLS(guint8,
-                                              obj->encrypt,
-                                              obj->protect,
-                                              obj->include_sci,
-                                              obj->es,
-                                              obj->scb,
-                                              obj->replay_protect));
-}
-
-int
-nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, parent_ifindex);
-    NM_CMP_FIELD(a, b, sci);
-    NM_CMP_FIELD(a, b, icv_length);
-    NM_CMP_FIELD(a, b, cipher_suite);
-    NM_CMP_FIELD(a, b, window);
-    NM_CMP_FIELD(a, b, encoding_sa);
-    NM_CMP_FIELD(a, b, validation);
-    NM_CMP_FIELD_UNSAFE(a, b, encrypt);
-    NM_CMP_FIELD_UNSAFE(a, b, protect);
-    NM_CMP_FIELD_UNSAFE(a, b, include_sci);
-    NM_CMP_FIELD_UNSAFE(a, b, es);
-    NM_CMP_FIELD_UNSAFE(a, b, scb);
-    NM_CMP_FIELD_UNSAFE(a, b, replay_protect);
-    return 0;
-}
-
-void
-nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h, obj->mode, NM_HASH_COMBINE_BOOLS(guint8, obj->no_promisc, obj->tap));
-}
-
-int
-nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, mode);
-    NM_CMP_FIELD_UNSAFE(a, b, no_promisc);
-    NM_CMP_FIELD_UNSAFE(a, b, tap);
-    return 0;
-}
-
-void
-nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->local,
-                        obj->remote,
-                        obj->parent_ifindex,
-                        obj->flags,
-                        obj->ttl,
-                        obj->tos,
-                        obj->proto,
-                        (bool) obj->path_mtu_discovery);
-}
-
-int
-nm_platform_lnk_sit_cmp(const NMPlatformLnkSit *a, const NMPlatformLnkSit *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, parent_ifindex);
-    NM_CMP_FIELD(a, b, local);
-    NM_CMP_FIELD(a, b, remote);
-    NM_CMP_FIELD(a, b, ttl);
-    NM_CMP_FIELD(a, b, tos);
-    NM_CMP_FIELD_BOOL(a, b, path_mtu_discovery);
-    NM_CMP_FIELD(a, b, flags);
-    NM_CMP_FIELD(a, b, proto);
-    return 0;
-}
-
-void
-nm_platform_lnk_tun_hash_update(const NMPlatformLnkTun *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->type,
-                        obj->owner,
-                        obj->group,
-                        NM_HASH_COMBINE_BOOLS(guint8,
-                                              obj->owner_valid,
-                                              obj->group_valid,
-                                              obj->pi,
-                                              obj->vnet_hdr,
-                                              obj->multi_queue,
-                                              obj->persist));
-}
-
-int
-nm_platform_lnk_tun_cmp(const NMPlatformLnkTun *a, const NMPlatformLnkTun *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, type);
-    NM_CMP_FIELD(a, b, owner);
-    NM_CMP_FIELD(a, b, group);
-    NM_CMP_FIELD_BOOL(a, b, owner_valid);
-    NM_CMP_FIELD_BOOL(a, b, group_valid);
-    NM_CMP_FIELD_BOOL(a, b, pi);
-    NM_CMP_FIELD_BOOL(a, b, vnet_hdr);
-    NM_CMP_FIELD_BOOL(a, b, multi_queue);
-    NM_CMP_FIELD_BOOL(a, b, persist);
-    return 0;
-}
-
-void
-nm_platform_lnk_vlan_hash_update(const NMPlatformLnkVlan *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h, obj->id, obj->flags);
-}
-
-int
-nm_platform_lnk_vlan_cmp(const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, id);
-    NM_CMP_FIELD(a, b, flags);
-    return 0;
-}
-
-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,
-                        obj->group6,
-                        obj->local6,
-                        obj->group,
-                        obj->local,
-                        obj->parent_ifindex,
-                        obj->id,
-                        obj->ageing,
-                        obj->limit,
-                        obj->dst_port,
-                        obj->src_port_min,
-                        obj->src_port_max,
-                        obj->tos,
-                        obj->ttl,
-                        NM_HASH_COMBINE_BOOLS(guint8,
-                                              obj->learning,
-                                              obj->proxy,
-                                              obj->rsc,
-                                              obj->l2miss,
-                                              obj->l3miss));
-}
-
-int
-nm_platform_lnk_vxlan_cmp(const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, parent_ifindex);
-    NM_CMP_FIELD(a, b, id);
-    NM_CMP_FIELD(a, b, group);
-    NM_CMP_FIELD(a, b, local);
-    NM_CMP_FIELD_MEMCMP(a, b, group6);
-    NM_CMP_FIELD_MEMCMP(a, b, local6);
-    NM_CMP_FIELD(a, b, tos);
-    NM_CMP_FIELD(a, b, ttl);
-    NM_CMP_FIELD_BOOL(a, b, learning);
-    NM_CMP_FIELD(a, b, ageing);
-    NM_CMP_FIELD(a, b, limit);
-    NM_CMP_FIELD(a, b, dst_port);
-    NM_CMP_FIELD(a, b, src_port_min);
-    NM_CMP_FIELD(a, b, src_port_max);
-    NM_CMP_FIELD_BOOL(a, b, proxy);
-    NM_CMP_FIELD_BOOL(a, b, rsc);
-    NM_CMP_FIELD_BOOL(a, b, l2miss);
-    NM_CMP_FIELD_BOOL(a, b, l3miss);
-    return 0;
-}
-
-void
-nm_platform_lnk_wireguard_hash_update(const NMPlatformLnkWireGuard *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h, obj->listen_port, obj->fwmark);
-    nm_hash_update(h, obj->private_key, sizeof(obj->private_key));
-    nm_hash_update(h, obj->public_key, sizeof(obj->public_key));
-}
-
-int
-nm_platform_lnk_wireguard_cmp(const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, listen_port);
-    NM_CMP_FIELD(a, b, fwmark);
-    NM_CMP_FIELD_MEMCMP(a, b, private_key);
-    NM_CMP_FIELD_MEMCMP(a, b, public_key);
-    return 0;
-}
-
-static int
-_address_pretty_sort_get_prio_4(in_addr_t addr)
-{
-    if (nm_utils_ip4_address_is_link_local(addr))
-        return 0;
-    return 1;
-}
-
-int
-nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1,
-                                        const NMPlatformIP4Address *a2)
-{
-    in_addr_t n1;
-    in_addr_t n2;
-
-    nm_assert(a1);
-    nm_assert(a2);
-
-    /* Sort by address type. For example link local will
-     * be sorted *after* a global address. */
-    NM_CMP_DIRECT(_address_pretty_sort_get_prio_4(a2->address),
-                  _address_pretty_sort_get_prio_4(a1->address));
-
-    /* Sort the addresses based on their source. */
-    NM_CMP_DIRECT(a2->addr_source, a1->addr_source);
-
-    NM_CMP_DIRECT((a2->label[0] == '\0'), (a1->label[0] == '\0'));
-
-    /* Finally, sort addresses lexically. We compare only the
-     * network part so that the order of addresses in the same
-     * subnet (and thus also the primary/secondary role) is
-     * preserved.
-     */
-    n1 = a1->address & _nm_utils_ip4_prefix_to_netmask(a1->plen);
-    n2 = a2->address & _nm_utils_ip4_prefix_to_netmask(a2->plen);
-    NM_CMP_DIRECT_MEMCMP(&n1, &n2, sizeof(guint32));
-    return 0;
-}
-
-static int
-_address_pretty_sort_get_prio_6(const struct in6_addr *addr)
-{
-    if (IN6_IS_ADDR_V4MAPPED(addr))
-        return 0;
-    if (IN6_IS_ADDR_V4COMPAT(addr))
-        return 1;
-    if (IN6_IS_ADDR_UNSPECIFIED(addr))
-        return 2;
-    if (IN6_IS_ADDR_LOOPBACK(addr))
-        return 3;
-    if (IN6_IS_ADDR_LINKLOCAL(addr))
-        return 4;
-    if (IN6_IS_ADDR_SITELOCAL(addr))
-        return 5;
-    return 6;
-}
-
-int
-nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1,
-                                        const NMPlatformIP6Address *a2,
-                                        gboolean                    prefer_temp)
-{
-    gboolean ipv6_privacy1;
-    gboolean ipv6_privacy2;
-
-    nm_assert(a1);
-    nm_assert(a2);
-
-    /* tentative addresses are always sorted back... */
-    /* sort tentative addresses after non-tentative. */
-    NM_CMP_DIRECT(NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE),
-                  NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_TENTATIVE));
-
-    /* Sort by address type. For example link local will
-     * be sorted *after* site local or global. */
-    NM_CMP_DIRECT(_address_pretty_sort_get_prio_6(&a2->address),
-                  _address_pretty_sort_get_prio_6(&a1->address));
-
-    ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY);
-    ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY);
-    if (ipv6_privacy1 || ipv6_privacy2) {
-        gboolean public1 = TRUE;
-        gboolean public2 = TRUE;
-
-        if (ipv6_privacy1) {
-            if (a1->n_ifa_flags & IFA_F_TEMPORARY)
-                public1 = prefer_temp;
-            else
-                public1 = !prefer_temp;
-        }
-        if (ipv6_privacy2) {
-            if (a2->n_ifa_flags & IFA_F_TEMPORARY)
-                public2 = prefer_temp;
-            else
-                public2 = !prefer_temp;
-        }
-
-        NM_CMP_DIRECT(public2, public1);
-    }
-
-    /* Sort the addresses based on their source. */
-    NM_CMP_DIRECT(a2->addr_source, a1->addr_source);
-
-    /* sort permanent addresses before non-permanent. */
-    NM_CMP_DIRECT(NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_PERMANENT),
-                  NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_PERMANENT));
-
-    /* finally sort addresses lexically */
-    NM_CMP_DIRECT_IN6ADDR(&a1->address, &a2->address);
-    NM_CMP_DIRECT_MEMCMP(a1, a2, sizeof(*a1));
-    return 0;
-}
-
-void
-nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->ifindex,
-                        obj->addr_source,
-                        obj->use_ip4_broadcast_address ? obj->broadcast_address : ((in_addr_t) 0u),
-                        obj->timestamp,
-                        obj->lifetime,
-                        obj->preferred,
-                        obj->n_ifa_flags,
-                        obj->plen,
-                        obj->address,
-                        obj->peer_address,
-                        NM_HASH_COMBINE_BOOLS(guint8,
-                                              obj->external,
-                                              obj->use_ip4_broadcast_address,
-                                              obj->ip4acd_not_ready));
-    nm_hash_update_strarr(h, obj->label);
-}
-
-int
-nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
-{
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, ifindex);
-    NM_CMP_FIELD(a, b, address);
-    NM_CMP_FIELD(a, b, plen);
-    NM_CMP_FIELD(a, b, peer_address);
-    NM_CMP_FIELD_UNSAFE(a, b, use_ip4_broadcast_address);
-    if (a->use_ip4_broadcast_address)
-        NM_CMP_FIELD(a, b, broadcast_address);
-    NM_CMP_FIELD(a, b, addr_source);
-    NM_CMP_FIELD(a, b, timestamp);
-    NM_CMP_FIELD(a, b, lifetime);
-    NM_CMP_FIELD(a, b, preferred);
-    NM_CMP_FIELD(a, b, n_ifa_flags);
-    NM_CMP_FIELD_STR(a, b, label);
-    NM_CMP_FIELD_UNSAFE(a, b, external);
-    NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready);
-    return 0;
-}
-
-void
-nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h)
-{
-    nm_hash_update_vals(h,
-                        obj->ifindex,
-                        obj->addr_source,
-                        obj->timestamp,
-                        obj->lifetime,
-                        obj->preferred,
-                        obj->n_ifa_flags,
-                        obj->plen,
-                        obj->address,
-                        obj->peer_address,
-                        NM_HASH_COMBINE_BOOLS(guint8, obj->external));
-}
-
-int
-nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
-{
-    const struct in6_addr *p_a, *p_b;
-
-    NM_CMP_SELF(a, b);
-    NM_CMP_FIELD(a, b, ifindex);
-    NM_CMP_FIELD_MEMCMP(a, b, address);
-    NM_CMP_FIELD(a, b, plen);
-    p_a = nm_platform_ip6_address_get_peer(a);
-    p_b = nm_platform_ip6_address_get_peer(b);
-    NM_CMP_DIRECT_MEMCMP(p_a, p_b, sizeof(*p_a));
-    NM_CMP_FIELD(a, b, addr_source);
-    NM_CMP_FIELD(a, b, timestamp);
-    NM_CMP_FIELD(a, b, lifetime);
-    NM_CMP_FIELD(a, b, preferred);
-    NM_CMP_FIELD(a, b, n_ifa_flags);
-    NM_CMP_FIELD_UNSAFE(a, b, external);
-    return 0;
-}
-
-void
-nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
-                                  NMPlatformIPRouteCmpType  cmp_type,
-                                  NMHashState *             h)
-{
-    switch (cmp_type) {
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
-        nm_hash_update_vals(
-            h,
-            nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
-            nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
-            obj->plen,
-            obj->metric,
-            obj->tos,
-            NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any));
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
-        nm_hash_update_vals(
-            h,
-            obj->type_coerced,
-            nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
-            nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
-            obj->plen,
-            obj->metric,
-            obj->tos,
-            /* on top of WEAK_ID: */
-            obj->ifindex,
-            nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
-            _ip_route_scope_inv_get_normalized(obj),
-            obj->gateway,
-            obj->mss,
-            obj->pref_src,
-            obj->window,
-            obj->cwnd,
-            obj->initcwnd,
-            obj->initrwnd,
-            obj->mtu,
-            obj->r_rtm_flags & RTNH_F_ONLINK,
-            NM_HASH_COMBINE_BOOLS(guint8,
-                                  obj->metric_any,
-                                  obj->table_any,
-                                  obj->lock_window,
-                                  obj->lock_cwnd,
-                                  obj->lock_initcwnd,
-                                  obj->lock_initrwnd,
-                                  obj->lock_mtu));
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
-        nm_hash_update_vals(
-            h,
-            obj->type_coerced,
-            nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
-            obj->ifindex,
-            nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
-            obj->plen,
-            obj->metric,
-            obj->gateway,
-            nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
-            _ip_route_scope_inv_get_normalized(obj),
-            obj->tos,
-            obj->mss,
-            obj->pref_src,
-            obj->window,
-            obj->cwnd,
-            obj->initcwnd,
-            obj->initrwnd,
-            obj->mtu,
-            obj->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK),
-            NM_HASH_COMBINE_BOOLS(guint8,
-                                  obj->metric_any,
-                                  obj->table_any,
-                                  obj->lock_window,
-                                  obj->lock_cwnd,
-                                  obj->lock_initcwnd,
-                                  obj->lock_initrwnd,
-                                  obj->lock_mtu));
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
-        nm_hash_update_vals(h,
-                            obj->type_coerced,
-                            obj->table_coerced,
-                            obj->ifindex,
-                            obj->network,
-                            obj->plen,
-                            obj->metric,
-                            obj->gateway,
-                            obj->rt_source,
-                            obj->scope_inv,
-                            obj->tos,
-                            obj->mss,
-                            obj->pref_src,
-                            obj->window,
-                            obj->cwnd,
-                            obj->initcwnd,
-                            obj->initrwnd,
-                            obj->mtu,
-                            obj->r_rtm_flags,
-                            NM_HASH_COMBINE_BOOLS(guint8,
-                                                  obj->metric_any,
-                                                  obj->table_any,
-                                                  obj->lock_window,
-                                                  obj->lock_cwnd,
-                                                  obj->lock_initcwnd,
-                                                  obj->lock_initrwnd,
-                                                  obj->lock_mtu));
-        break;
-    }
-}
-
-int
-nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
-                          const NMPlatformIP4Route *b,
-                          NMPlatformIPRouteCmpType  cmp_type)
-{
-    NM_CMP_SELF(a, b);
-    switch (cmp_type) {
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
-        NM_CMP_FIELD_UNSAFE(a, b, table_any);
-        NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)),
-                      nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b)));
-        NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen));
-        NM_CMP_FIELD(a, b, plen);
-        NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        NM_CMP_FIELD(a, b, metric);
-        NM_CMP_FIELD(a, b, tos);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
-            NM_CMP_FIELD(a, b, ifindex);
-            NM_CMP_FIELD(a, b, type_coerced);
-            NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source),
-                          nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source));
-            NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a),
-                          _ip_route_scope_inv_get_normalized(b));
-            NM_CMP_FIELD(a, b, gateway);
-            NM_CMP_FIELD(a, b, mss);
-            NM_CMP_FIELD(a, b, pref_src);
-            NM_CMP_FIELD(a, b, window);
-            NM_CMP_FIELD(a, b, cwnd);
-            NM_CMP_FIELD(a, b, initcwnd);
-            NM_CMP_FIELD(a, b, initrwnd);
-            NM_CMP_FIELD(a, b, mtu);
-            NM_CMP_DIRECT(a->r_rtm_flags & RTNH_F_ONLINK, b->r_rtm_flags & RTNH_F_ONLINK);
-            NM_CMP_FIELD_UNSAFE(a, b, lock_window);
-            NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd);
-            NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd);
-            NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd);
-            NM_CMP_FIELD_UNSAFE(a, b, lock_mtu);
-        }
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
-        NM_CMP_FIELD(a, b, type_coerced);
-        NM_CMP_FIELD_UNSAFE(a, b, table_any);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
-            NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)),
-                          nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b)));
-        } else
-            NM_CMP_FIELD(a, b, table_coerced);
-        NM_CMP_FIELD(a, b, ifindex);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-            NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen));
-        else
-            NM_CMP_FIELD(a, b, network);
-        NM_CMP_FIELD(a, b, plen);
-        NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        NM_CMP_FIELD(a, b, metric);
-        NM_CMP_FIELD(a, b, gateway);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
-            NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source),
-                          nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source));
-            NM_CMP_DIRECT(_ip_route_scope_inv_get_normalized(a),
-                          _ip_route_scope_inv_get_normalized(b));
-        } else {
-            NM_CMP_FIELD(a, b, rt_source);
-            NM_CMP_FIELD(a, b, scope_inv);
-        }
-        NM_CMP_FIELD(a, b, mss);
-        NM_CMP_FIELD(a, b, pref_src);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
-            NM_CMP_DIRECT(a->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK),
-                          b->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK));
-        } else
-            NM_CMP_FIELD(a, b, r_rtm_flags);
-        NM_CMP_FIELD(a, b, tos);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_window);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_mtu);
-        NM_CMP_FIELD(a, b, window);
-        NM_CMP_FIELD(a, b, cwnd);
-        NM_CMP_FIELD(a, b, initcwnd);
-        NM_CMP_FIELD(a, b, initrwnd);
-        NM_CMP_FIELD(a, b, mtu);
-        break;
-    }
-    return 0;
-}
-
-void
-nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
-                                  NMPlatformIPRouteCmpType  cmp_type,
-                                  NMHashState *             h)
-{
-    struct in6_addr a1, a2;
-
-    switch (cmp_type) {
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
-        nm_hash_update_vals(
-            h,
-            nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
-            *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
-            obj->plen,
-            obj->metric,
-            *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
-            obj->src_plen,
-            NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any));
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
-        nm_hash_update_vals(
-            h,
-            obj->type_coerced,
-            nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
-            *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
-            obj->plen,
-            obj->metric,
-            *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
-            obj->src_plen,
-            NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any),
-            /* on top of WEAK_ID: */
-            obj->ifindex,
-            obj->gateway);
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
-        nm_hash_update_vals(
-            h,
-            obj->type_coerced,
-            nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
-            obj->ifindex,
-            *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
-            obj->plen,
-            obj->metric,
-            obj->gateway,
-            obj->pref_src,
-            *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
-            obj->src_plen,
-            nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
-            obj->mss,
-            obj->r_rtm_flags & RTM_F_CLONED,
-            NM_HASH_COMBINE_BOOLS(guint8,
-                                  obj->metric_any,
-                                  obj->table_any,
-                                  obj->lock_window,
-                                  obj->lock_cwnd,
-                                  obj->lock_initcwnd,
-                                  obj->lock_initrwnd,
-                                  obj->lock_mtu),
-            obj->window,
-            obj->cwnd,
-            obj->initcwnd,
-            obj->initrwnd,
-            obj->mtu,
-            _route_pref_normalize(obj->rt_pref));
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
-        nm_hash_update_vals(h,
-                            obj->type_coerced,
-                            obj->table_coerced,
-                            obj->ifindex,
-                            obj->network,
-                            obj->metric,
-                            obj->gateway,
-                            obj->pref_src,
-                            obj->src,
-                            obj->src_plen,
-                            obj->rt_source,
-                            obj->mss,
-                            obj->r_rtm_flags,
-                            NM_HASH_COMBINE_BOOLS(guint8,
-                                                  obj->metric_any,
-                                                  obj->table_any,
-                                                  obj->lock_window,
-                                                  obj->lock_cwnd,
-                                                  obj->lock_initcwnd,
-                                                  obj->lock_initrwnd,
-                                                  obj->lock_mtu),
-                            obj->window,
-                            obj->cwnd,
-                            obj->initcwnd,
-                            obj->initrwnd,
-                            obj->mtu,
-                            obj->rt_pref);
-        break;
-    }
-}
-
-int
-nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
-                          const NMPlatformIP6Route *b,
-                          NMPlatformIPRouteCmpType  cmp_type)
-{
-    NM_CMP_SELF(a, b);
-    switch (cmp_type) {
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
-        NM_CMP_FIELD_UNSAFE(a, b, table_any);
-        NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)),
-                      nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b)));
-        NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen));
-        NM_CMP_FIELD(a, b, plen);
-        NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        NM_CMP_FIELD(a, b, metric);
-        NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen));
-        NM_CMP_FIELD(a, b, src_plen);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
-            NM_CMP_FIELD(a, b, ifindex);
-            NM_CMP_FIELD(a, b, type_coerced);
-            NM_CMP_FIELD_IN6ADDR(a, b, gateway);
-        }
-        break;
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
-    case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
-        NM_CMP_FIELD(a, b, type_coerced);
-        NM_CMP_FIELD_UNSAFE(a, b, table_any);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
-            NM_CMP_DIRECT(nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(a)),
-                          nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(b)));
-        } else
-            NM_CMP_FIELD(a, b, table_coerced);
-        NM_CMP_FIELD(a, b, ifindex);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-            NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen));
-        else
-            NM_CMP_FIELD_IN6ADDR(a, b, network);
-        NM_CMP_FIELD(a, b, plen);
-        NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        NM_CMP_FIELD(a, b, metric);
-        NM_CMP_FIELD_IN6ADDR(a, b, gateway);
-        NM_CMP_FIELD_IN6ADDR(a, b, pref_src);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
-            NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen));
-            NM_CMP_FIELD(a, b, src_plen);
-            NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source),
-                          nmp_utils_ip_config_source_round_trip_rtprot(b->rt_source));
-        } else {
-            NM_CMP_FIELD_IN6ADDR(a, b, src);
-            NM_CMP_FIELD(a, b, src_plen);
-            NM_CMP_FIELD(a, b, rt_source);
-        }
-        NM_CMP_FIELD(a, b, mss);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
-            NM_CMP_DIRECT(a->r_rtm_flags & RTM_F_CLONED, b->r_rtm_flags & RTM_F_CLONED);
-        } else
-            NM_CMP_FIELD(a, b, r_rtm_flags);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_window);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_cwnd);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_initcwnd);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_initrwnd);
-        NM_CMP_FIELD_UNSAFE(a, b, lock_mtu);
-        NM_CMP_FIELD(a, b, window);
-        NM_CMP_FIELD(a, b, cwnd);
-        NM_CMP_FIELD(a, b, initcwnd);
-        NM_CMP_FIELD(a, b, initrwnd);
-        NM_CMP_FIELD(a, b, mtu);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-            NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref));
-        else
-            NM_CMP_FIELD(a, b, rt_pref);
-        break;
-    }
-    return 0;
-}
-
-#define _ROUTING_RULE_FLAGS_IGNORE \
-    (FIB_RULE_UNRESOLVED | FIB_RULE_IIF_DETACHED | FIB_RULE_OIF_DETACHED)
-
-#define _routing_rule_compare(cmp_type, kernel_support_type) \
-    ((cmp_type) == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL    \
-     || nm_platform_kernel_support_get(kernel_support_type))
-
-void
-nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj,
-                                     NMPlatformRoutingRuleCmpType cmp_type,
-                                     NMHashState *                h)
-{
-    gboolean cmp_full = TRUE;
-    gsize    addr_size;
-    guint32  flags_mask = G_MAXUINT32;
-
-    if (G_UNLIKELY(!NM_IN_SET(obj->addr_family, AF_INET, AF_INET6))) {
-        /* the address family is not one of the supported ones. That means, the
-         * instance will only compare equal to itself (pointer-equality). */
-        nm_hash_update_val(h, (gconstpointer) obj);
-        return;
-    }
-
-    switch (cmp_type) {
-    case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID:
-
-        flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE;
-
-        /* fall-through */
-    case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY:
-
-        cmp_full = FALSE;
-
-        /* fall-through */
-    case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL:
-
-        nm_hash_update_vals(
-            h,
-            obj->addr_family,
-            obj->tun_id,
-            obj->table,
-            obj->flags & flags_mask,
-            obj->priority,
-            obj->fwmark,
-            obj->fwmask,
-            ((cmp_full
-              || (cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY
-                  && obj->action == FR_ACT_GOTO))
-                 ? obj->goto_target
-                 : (guint32) 0u),
-            ((cmp_full || obj->addr_family == AF_INET) ? obj->flow : (guint32) 0u),
-            NM_HASH_COMBINE_BOOLS(
-                guint8,
-                (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)
-                     ? obj->uid_range_has
-                     : FALSE)),
-            obj->suppress_prefixlen_inverse,
-            obj->suppress_ifgroup_inverse,
-            (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)
-                 ? (cmp_full ? (guint16) obj->l3mdev : (guint16) !!obj->l3mdev)
-                 : G_MAXUINT16),
-            obj->action,
-            obj->tos,
-            obj->src_len,
-            obj->dst_len,
-            (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL)
-                 ? (guint16) obj->protocol
-                 : G_MAXUINT16));
-        addr_size = nm_utils_addr_family_to_size(obj->addr_family);
-        if (cmp_full || obj->src_len > 0)
-            nm_hash_update(h, &obj->src, addr_size);
-        if (cmp_full || obj->dst_len > 0)
-            nm_hash_update(h, &obj->dst, addr_size);
-        if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) {
-            if (cmp_full || obj->uid_range_has)
-                nm_hash_update_valp(h, &obj->uid_range);
-        }
-        if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) {
-            nm_hash_update_val(h, obj->ip_proto);
-            nm_hash_update_valp(h, &obj->sport_range);
-            nm_hash_update_valp(h, &obj->dport_range);
-        }
-        nm_hash_update_str(h, obj->iifname);
-        nm_hash_update_str(h, obj->oifname);
-        return;
-    }
-
-    nm_assert_not_reached();
-}
-
-int
-nm_platform_routing_rule_cmp(const NMPlatformRoutingRule *a,
-                             const NMPlatformRoutingRule *b,
-                             NMPlatformRoutingRuleCmpType cmp_type)
-{
-    gboolean cmp_full = TRUE;
-    gsize    addr_size;
-    bool     valid;
-    guint32  flags_mask = G_MAXUINT32;
-
-    NM_CMP_SELF(a, b);
-
-    valid = NM_IN_SET(a->addr_family, AF_INET, AF_INET6);
-    NM_CMP_DIRECT(valid, (bool) NM_IN_SET(b->addr_family, AF_INET, AF_INET6));
-
-    if (G_UNLIKELY(!valid)) {
-        /* the address family is not one of the supported ones. That means, the
-         * instance will only compare equal to itself. */
-        NM_CMP_DIRECT((uintptr_t) a, (uintptr_t) b);
-        nm_assert_not_reached();
-        return 0;
-    }
-
-    switch (cmp_type) {
-    case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID:
-
-        flags_mask &= ~_ROUTING_RULE_FLAGS_IGNORE;
-
-        /* fall-through */
-    case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY:
-
-        cmp_full = FALSE;
-
-        /* fall-through */
-    case NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL:
-        NM_CMP_FIELD(a, b, addr_family);
-        NM_CMP_FIELD(a, b, action);
-        NM_CMP_FIELD(a, b, priority);
-        NM_CMP_FIELD(a, b, tun_id);
-
-        if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV)) {
-            if (cmp_full)
-                NM_CMP_FIELD(a, b, l3mdev);
-            else
-                NM_CMP_FIELD_BOOL(a, b, l3mdev);
-        }
-
-        NM_CMP_FIELD(a, b, table);
-
-        NM_CMP_DIRECT(a->flags & flags_mask, b->flags & flags_mask);
-
-        NM_CMP_FIELD(a, b, fwmark);
-        NM_CMP_FIELD(a, b, fwmask);
-
-        if (cmp_full
-            || (cmp_type == NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY
-                && a->action == FR_ACT_GOTO))
-            NM_CMP_FIELD(a, b, goto_target);
-
-        NM_CMP_FIELD(a, b, suppress_prefixlen_inverse);
-        NM_CMP_FIELD(a, b, suppress_ifgroup_inverse);
-        NM_CMP_FIELD(a, b, tos);
-
-        if (cmp_full || a->addr_family == AF_INET)
-            NM_CMP_FIELD(a, b, flow);
-
-        if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL))
-            NM_CMP_FIELD(a, b, protocol);
-
-        if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO)) {
-            NM_CMP_FIELD(a, b, ip_proto);
-            NM_CMP_FIELD(a, b, sport_range.start);
-            NM_CMP_FIELD(a, b, sport_range.end);
-            NM_CMP_FIELD(a, b, dport_range.start);
-            NM_CMP_FIELD(a, b, dport_range.end);
-        }
-
-        addr_size = nm_utils_addr_family_to_size(a->addr_family);
-
-        NM_CMP_FIELD(a, b, src_len);
-        if (cmp_full || a->src_len > 0)
-            NM_CMP_FIELD_MEMCMP_LEN(a, b, src, addr_size);
-
-        NM_CMP_FIELD(a, b, dst_len);
-        if (cmp_full || a->dst_len > 0)
-            NM_CMP_FIELD_MEMCMP_LEN(a, b, dst, addr_size);
-
-        if (_routing_rule_compare(cmp_type, NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE)) {
-            NM_CMP_FIELD_UNSAFE(a, b, uid_range_has);
-            if (cmp_full || a->uid_range_has) {
-                NM_CMP_FIELD(a, b, uid_range.start);
-                NM_CMP_FIELD(a, b, uid_range.end);
-            }
-        }
-
-        NM_CMP_FIELD_STR(a, b, iifname);
-        NM_CMP_FIELD_STR(a, b, oifname);
-        return 0;
-    }
-
-    nm_assert_not_reached();
-    return 0;
-}
-
-/**
- * nm_platform_ip_address_cmp_expiry:
- * @a: a NMPlatformIPAddress to compare
- * @b: the other NMPlatformIPAddress to compare
- *
- * Compares two addresses and returns which one has a longer remaining lifetime.
- * If both addresses have the same lifetime, look at the remaining preferred time.
- *
- * For comparison, only the timestamp, lifetime and preferred fields are considered.
- * If they compare equal (== 0), their other fields were not considered.
- *
- * Returns: -1, 0, or 1 according to the comparison
- **/
-int
-nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b)
-{
-    gint64 ta = 0, tb = 0;
-
-    NM_CMP_SELF(a, b);
-
-    if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0)
-        ta = G_MAXINT64;
-    else if (a->timestamp)
-        ta = ((gint64) a->timestamp) + a->lifetime;
-
-    if (b->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0)
-        tb = G_MAXINT64;
-    else if (b->timestamp)
-        tb = ((gint64) b->timestamp) + b->lifetime;
-
-    if (ta == tb) {
-        /* if the lifetime is equal, compare the preferred time. */
-        ta = tb = 0;
-
-        if (a->preferred == NM_PLATFORM_LIFETIME_PERMANENT
-            || a->lifetime == 0 /* lifetime==0 means permanent! */)
-            ta = G_MAXINT64;
-        else if (a->timestamp)
-            ta = ((gint64) a->timestamp) + a->preferred;
-
-        if (b->preferred == NM_PLATFORM_LIFETIME_PERMANENT || b->lifetime == 0)
-            tb = G_MAXINT64;
-        else if (b->timestamp)
-            tb = ((gint64) b->timestamp) + b->preferred;
-
-        if (ta == tb)
-            return 0;
-    }
-
-    return ta < tb ? -1 : 1;
-}
-
-/*****************************************************************************/
-
-GHashTable *
-nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex)
-{
-    const NMDedupMultiHeadEntry *head_entry;
-    NMDedupMultiIter             iter;
-    const NMPObject *            obj;
-    NMPLookup                    lookup;
-    GHashTable *                 hash;
-
-    g_return_val_if_fail(NM_IS_PLATFORM(self), NULL);
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex);
-
-    head_entry = nmp_cache_lookup(NM_PLATFORM_GET_PRIVATE(self)->cache, &lookup);
-
-    if (!head_entry)
-        return NULL;
-
-    hash = g_hash_table_new(nm_direct_hash, NULL);
-
-    nmp_cache_iter_for_each (&iter, head_entry, &obj) {
-        const NMPlatformIP4Address *a = NMP_OBJECT_CAST_IP4_ADDRESS(obj);
-
-        g_hash_table_add(hash, GUINT_TO_POINTER(a->address));
-    }
-
-    return hash;
-}
-
-/*****************************************************************************/
-
-const char *
-nm_platform_signal_change_type_to_string(NMPlatformSignalChangeType change_type)
-{
-    switch (change_type) {
-    case NM_PLATFORM_SIGNAL_ADDED:
-        return "added";
-    case NM_PLATFORM_SIGNAL_CHANGED:
-        return "changed";
-    case NM_PLATFORM_SIGNAL_REMOVED:
-        return "removed";
-    default:
-        g_return_val_if_reached("UNKNOWN");
-    }
-}
-
-static void
-log_link(NMPlatform *               self,
-         NMPObjectType              obj_type,
-         int                        ifindex,
-         NMPlatformLink *           device,
-         NMPlatformSignalChangeType change_type,
-         gpointer                   user_data)
-{
-    _LOG3D("signal: link %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_link_to_string(device, NULL, 0));
-}
-
-static void
-log_ip4_address(NMPlatform *               self,
-                NMPObjectType              obj_type,
-                int                        ifindex,
-                NMPlatformIP4Address *     address,
-                NMPlatformSignalChangeType change_type,
-                gpointer                   user_data)
-{
-    _LOG3D("signal: address 4 %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip4_address_to_string(address, NULL, 0));
-}
-
-static void
-log_ip6_address(NMPlatform *               self,
-                NMPObjectType              obj_type,
-                int                        ifindex,
-                NMPlatformIP6Address *     address,
-                NMPlatformSignalChangeType change_type,
-                gpointer                   user_data)
-{
-    _LOG3D("signal: address 6 %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip6_address_to_string(address, NULL, 0));
-}
-
-static void
-log_ip4_route(NMPlatform *               self,
-              NMPObjectType              obj_type,
-              int                        ifindex,
-              NMPlatformIP4Route *       route,
-              NMPlatformSignalChangeType change_type,
-              gpointer                   user_data)
-{
-    _LOG3D("signal: route   4 %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip4_route_to_string(route, NULL, 0));
-}
-
-static void
-log_ip6_route(NMPlatform *               self,
-              NMPObjectType              obj_type,
-              int                        ifindex,
-              NMPlatformIP6Route *       route,
-              NMPlatformSignalChangeType change_type,
-              gpointer                   user_data)
-{
-    _LOG3D("signal: route   6 %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip6_route_to_string(route, NULL, 0));
-}
-
-static void
-log_routing_rule(NMPlatform *               self,
-                 NMPObjectType              obj_type,
-                 int                        ifindex,
-                 NMPlatformRoutingRule *    routing_rule,
-                 NMPlatformSignalChangeType change_type,
-                 gpointer                   user_data)
-{
-    /* routing rules don't have an ifindex. We probably should refactor the signals that are emitted for platform changes. */
-    _LOG3D("signal: rt-rule %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_routing_rule_to_string(routing_rule, NULL, 0));
-}
-
-static void
-log_qdisc(NMPlatform *               self,
-          NMPObjectType              obj_type,
-          int                        ifindex,
-          NMPlatformQdisc *          qdisc,
-          NMPlatformSignalChangeType change_type,
-          gpointer                   user_data)
-{
-    _LOG3D("signal: qdisc %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_qdisc_to_string(qdisc, NULL, 0));
-}
-
-static void
-log_tfilter(NMPlatform *               self,
-            NMPObjectType              obj_type,
-            int                        ifindex,
-            NMPlatformTfilter *        tfilter,
-            NMPlatformSignalChangeType change_type,
-            gpointer                   user_data)
-{
-    _LOG3D("signal: tfilter %7s: %s",
-           nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_tfilter_to_string(tfilter, NULL, 0));
-}
-
-/*****************************************************************************/
-
-void
-nm_platform_cache_update_emit_signal(NMPlatform *     self,
-                                     NMPCacheOpsType  cache_op,
-                                     const NMPObject *obj_old,
-                                     const NMPObject *obj_new)
-{
-    gboolean         visible_new;
-    gboolean         visible_old;
-    const NMPObject *o;
-    const NMPClass * klass;
-    int              ifindex;
-
-    nm_assert(NM_IN_SET((NMPlatformSignalChangeType) cache_op,
-                        NM_PLATFORM_SIGNAL_NONE,
-                        NM_PLATFORM_SIGNAL_ADDED,
-                        NM_PLATFORM_SIGNAL_CHANGED,
-                        NM_PLATFORM_SIGNAL_REMOVED));
-
-    ASSERT_nmp_cache_ops(nm_platform_get_cache(self), cache_op, obj_old, obj_new);
-
-    NMTST_ASSERT_PLATFORM_NETNS_CURRENT(self);
-
-    switch (cache_op) {
-    case NMP_CACHE_OPS_ADDED:
-        if (!nmp_object_is_visible(obj_new))
-            return;
-        o = obj_new;
-        break;
-    case NMP_CACHE_OPS_UPDATED:
-        visible_old = nmp_object_is_visible(obj_old);
-        visible_new = nmp_object_is_visible(obj_new);
-        if (!visible_old && visible_new) {
-            o        = obj_new;
-            cache_op = NMP_CACHE_OPS_ADDED;
-        } else if (visible_old && !visible_new) {
-            o        = obj_old;
-            cache_op = NMP_CACHE_OPS_REMOVED;
-        } else if (!visible_new) {
-            /* it was invisible and stayed invisible. Nothing to do. */
-            return;
-        } else
-            o = obj_new;
-        break;
-    case NMP_CACHE_OPS_REMOVED:
-        if (!nmp_object_is_visible(obj_old))
-            return;
-        o = obj_old;
-        break;
-    default:
-        nm_assert(cache_op == NMP_CACHE_OPS_UNCHANGED);
-        return;
-    }
-
-    klass = NMP_OBJECT_GET_CLASS(o);
-
-    if (klass->obj_type == NMP_OBJECT_TYPE_ROUTING_RULE)
-        ifindex = 0;
-    else
-        ifindex = NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(o)->ifindex;
-
-    if (klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
-        && NM_PLATFORM_GET_PRIVATE(self)->ip4_dev_route_blacklist_gc_timeout_id
-        && NM_IN_SET(cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED))
-        _ip4_dev_route_blacklist_notify_route(self, o);
-
-    _LOG3t("emit signal %s %s: %s",
-           klass->signal_type,
-           nm_platform_signal_change_type_to_string((NMPlatformSignalChangeType) cache_op),
-           nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-
-    nmp_object_ref(o);
-    g_signal_emit(self,
-                  _nm_platform_signal_id_get(klass->signal_type_id),
-                  0,
-                  (int) klass->obj_type,
-                  ifindex,
-                  &o->object,
-                  (int) cache_op);
-    nmp_object_unref(o);
-}
-
-/*****************************************************************************/
-
-NMPCache *
-nm_platform_get_cache(NMPlatform *self)
-{
-    return NM_PLATFORM_GET_PRIVATE(self)->cache;
-}
-
-NMPNetns *
-nm_platform_netns_get(NMPlatform *self)
-{
-    _CHECK_SELF(self, klass, NULL);
-
-    return self->_netns;
-}
-
-gboolean
-nm_platform_netns_push(NMPlatform *self, NMPNetns **netns)
-{
-    g_return_val_if_fail(NM_IS_PLATFORM(self), FALSE);
-
-    if (self->_netns && !nmp_netns_push(self->_netns)) {
-        NM_SET_OUT(netns, NULL);
-        return FALSE;
-    }
-
-    NM_SET_OUT(netns, self->_netns);
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-const _NMPlatformVTableRouteUnion nm_platform_vtable_route = {
-    .v4 =
-        {
-            .is_ip4          = TRUE,
-            .obj_type        = NMP_OBJECT_TYPE_IP4_ROUTE,
-            .addr_family     = AF_INET,
-            .sizeof_route    = sizeof(NMPlatformIP4Route),
-            .route_cmp       = (int (*)(const NMPlatformIPXRoute *a,
-                                  const NMPlatformIPXRoute *b,
-                                  NMPlatformIPRouteCmpType  cmp_type)) nm_platform_ip4_route_cmp,
-            .route_to_string = (const char *(*) (const NMPlatformIPXRoute *route,
-                                                 char *                    buf,
-                                                 gsize len)) nm_platform_ip4_route_to_string,
-        },
-    .v6 =
-        {
-            .is_ip4          = FALSE,
-            .obj_type        = NMP_OBJECT_TYPE_IP6_ROUTE,
-            .addr_family     = AF_INET6,
-            .sizeof_route    = sizeof(NMPlatformIP6Route),
-            .route_cmp       = (int (*)(const NMPlatformIPXRoute *a,
-                                  const NMPlatformIPXRoute *b,
-                                  NMPlatformIPRouteCmpType  cmp_type)) nm_platform_ip6_route_cmp,
-            .route_to_string = (const char *(*) (const NMPlatformIPXRoute *route,
-                                                 char *                    buf,
-                                                 gsize len)) nm_platform_ip6_route_to_string,
-        },
-};
-
-/*****************************************************************************/
-
-static void
-set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-    NMPlatform *       self = NM_PLATFORM(object);
-    NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    switch (prop_id) {
-    case PROP_NETNS_SUPPORT:
-        /* construct-only */
-        if (g_value_get_boolean(value)) {
-            NMPNetns *netns;
-
-            netns = nmp_netns_get_current();
-            if (netns)
-                self->_netns = g_object_ref(netns);
-        }
-        break;
-    case PROP_USE_UDEV:
-        /* construct-only */
-        priv->use_udev = g_value_get_boolean(value);
-        break;
-    case PROP_LOG_WITH_PTR:
-        /* construct-only */
-        priv->log_with_ptr = g_value_get_boolean(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void
-nm_platform_init(NMPlatform *self)
-{
-    self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_PLATFORM, NMPlatformPrivate);
-}
-
-static GObject *
-constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params)
-{
-    GObject *          object;
-    NMPlatform *       self;
-    NMPlatformPrivate *priv;
-
-    object = G_OBJECT_CLASS(nm_platform_parent_class)
-                 ->constructor(type, n_construct_params, construct_params);
-    self = NM_PLATFORM(object);
-    priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    priv->multi_idx = nm_dedup_multi_index_new();
-
-    priv->cache = nmp_cache_new(priv->multi_idx, priv->use_udev);
-
-    return object;
-}
-
-static void
-finalize(GObject *object)
-{
-    NMPlatform *       self = NM_PLATFORM(object);
-    NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
-
-    nm_clear_g_source(&priv->ip4_dev_route_blacklist_check_id);
-    nm_clear_g_source(&priv->ip4_dev_route_blacklist_gc_timeout_id);
-    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);
-}
-
-static void
-nm_platform_class_init(NMPlatformClass *platform_class)
-{
-    GObjectClass *object_class = G_OBJECT_CLASS(platform_class);
-
-    g_type_class_add_private(object_class, sizeof(NMPlatformPrivate));
-
-    object_class->constructor  = constructor;
-    object_class->set_property = set_property;
-    object_class->finalize     = finalize;
-
-    platform_class->wifi_set_powersave = wifi_set_powersave;
-
-    g_object_class_install_property(
-        object_class,
-        PROP_NETNS_SUPPORT,
-        g_param_spec_boolean(NM_PLATFORM_NETNS_SUPPORT,
-                             "",
-                             "",
-                             NM_PLATFORM_NETNS_SUPPORT_DEFAULT,
-                             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property(
-        object_class,
-        PROP_USE_UDEV,
-        g_param_spec_boolean(NM_PLATFORM_USE_UDEV,
-                             "",
-                             "",
-                             FALSE,
-                             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-    g_object_class_install_property(
-        object_class,
-        PROP_LOG_WITH_PTR,
-        g_param_spec_boolean(NM_PLATFORM_LOG_WITH_PTR,
-                             "",
-                             "",
-                             TRUE,
-                             G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-#define SIGNAL(signal, signal_id, method)                                                \
-    G_STMT_START                                                                         \
-    {                                                                                    \
-        signals[signal] =                                                                \
-            g_signal_new_class_handler("" signal_id "",                                  \
-                                       G_OBJECT_CLASS_TYPE(object_class),                \
-                                       G_SIGNAL_RUN_FIRST,                               \
-                                       G_CALLBACK(method),                               \
-                                       NULL,                                             \
-                                       NULL,                                             \
-                                       NULL,                                             \
-                                       G_TYPE_NONE,                                      \
-                                       4,                                                \
-                                       G_TYPE_INT, /* (int) NMPObjectType */             \
-                                       G_TYPE_INT, /* ifindex */                         \
-                                       G_TYPE_POINTER /* const NMPObject * */,           \
-                                       G_TYPE_INT /* (int) NMPlatformSignalChangeType */ \
-            );                                                                           \
-    }                                                                                    \
-    G_STMT_END
-
-    /* Signals */
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_LINK, NM_PLATFORM_SIGNAL_LINK_CHANGED, log_link);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
-           NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
-           log_ip4_address);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
-           NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
-           log_ip6_address);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, log_ip4_route);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, log_ip6_route);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_ROUTING_RULE,
-           NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED,
-           log_routing_rule);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_QDISC, NM_PLATFORM_SIGNAL_QDISC_CHANGED, log_qdisc);
-    SIGNAL(NM_PLATFORM_SIGNAL_ID_TFILTER, NM_PLATFORM_SIGNAL_TFILTER_CHANGED, log_tfilter);
-}
diff --git a/src/core/platform/nm-platform.h b/src/core/platform/nm-platform.h
deleted file mode 100644
index d400c568..00000000
--- a/src/core/platform/nm-platform.h
+++ /dev/null
@@ -1,2372 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2009 - 2018 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_PLATFORM_H__
-#define __NETWORKMANAGER_PLATFORM_H__
-
-#include "nm-dbus-interface.h"
-#include "nm-core-types-internal.h"
-
-#include "nm-platform/nmp-base.h"
-#include "nm-base/nm-base.h"
-
-#include "nm-core-utils.h"
-#include "nm-setting-vlan.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-ip-tunnel.h"
-
-#define NM_TYPE_PLATFORM (nm_platform_get_type())
-#define NM_PLATFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PLATFORM, NMPlatform))
-#define NM_PLATFORM_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PLATFORM, NMPlatformClass))
-#define NM_IS_PLATFORM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PLATFORM))
-#define NM_IS_PLATFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PLATFORM))
-#define NM_PLATFORM_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PLATFORM, NMPlatformClass))
-
-#define NM_PLATFORM_NETNS_SUPPORT_DEFAULT FALSE
-
-/*****************************************************************************/
-
-#define NM_PLATFORM_NETNS_SUPPORT "netns-support"
-#define NM_PLATFORM_USE_UDEV      "use-udev"
-#define NM_PLATFORM_LOG_WITH_PTR  "log-with-ptr"
-
-/*****************************************************************************/
-
-/* IFNAMSIZ is both defined in <linux/if.h> and <net/if.h>. In the past, these
- * headers conflicted, so we cannot simply include either of them in a header-file.*/
-#define NMP_IFNAMSIZ 16
-
-/*****************************************************************************/
-
-struct _NMPWireGuardPeer;
-
-struct udev_device;
-
-typedef gboolean (*NMPObjectPredicateFunc)(const NMPObject *obj, gpointer user_data);
-
-/* workaround for older libnl version, that does not define these flags. */
-#ifndef IFA_F_MANAGETEMPADDR
-    #define IFA_F_MANAGETEMPADDR 0x100
-#endif
-#ifndef IFA_F_NOPREFIXROUTE
-    #define IFA_F_NOPREFIXROUTE 0x200
-#endif
-
-#define NM_RT_SCOPE_LINK 253 /* RT_SCOPE_LINK */
-
-/* Define of the IN6_ADDR_GEN_MODE_* values to workaround old kernel headers
- * that don't define it. */
-#define NM_IN6_ADDR_GEN_MODE_UNKNOWN        255 /* no corresponding value.  */
-#define NM_IN6_ADDR_GEN_MODE_EUI64          0 /* IN6_ADDR_GEN_MODE_EUI64 */
-#define NM_IN6_ADDR_GEN_MODE_NONE           1 /* IN6_ADDR_GEN_MODE_NONE */
-#define NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY 2 /* IN6_ADDR_GEN_MODE_STABLE_PRIVACY */
-#define NM_IN6_ADDR_GEN_MODE_RANDOM         3 /* IN6_ADDR_GEN_MODE_RANDOM */
-
-#define NM_IFF_MULTI_QUEUE 0x0100 /* IFF_MULTI_QUEUE */
-
-/* Redefine this in host's endianness */
-#define NM_GRE_KEY 0x2000
-
-typedef enum {
-    NMP_NLM_FLAG_F_ECHO = 0x08, /* NLM_F_ECHO, Echo this request */
-
-    /* use our own platform enum for the nlmsg-flags. Otherwise, we'd have
-     * to include <linux/netlink.h> */
-    NMP_NLM_FLAG_F_REPLACE = 0x100, /* NLM_F_REPLACE, Override existing */
-    NMP_NLM_FLAG_F_EXCL    = 0x200, /* NLM_F_EXCL, Do not touch, if it exists */
-    NMP_NLM_FLAG_F_CREATE  = 0x400, /* NLM_F_CREATE, Create, if it does not exist */
-    NMP_NLM_FLAG_F_APPEND  = 0x800, /* NLM_F_APPEND, Add to end of list */
-
-    NMP_NLM_FLAG_FMASK = 0xFFFF, /* a mask for all NMP_NLM_FLAG_F_* flags */
-
-    /* instructs NM to suppress logging an error message for any failures
-     * received from kernel.
-     *
-     * It will still log with debug-level, and it will still log
-     * other failures aside the kernel response. */
-    NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE = 0x10000,
-
-    /* the following aliases correspond to iproute2's `ip route CMD` for
-     * RTM_NEWROUTE, with CMD being one of add, change, replace, prepend,
-     * append and test. */
-    NMP_NLM_FLAG_ADD     = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_EXCL,
-    NMP_NLM_FLAG_CHANGE  = NMP_NLM_FLAG_F_REPLACE,
-    NMP_NLM_FLAG_REPLACE = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_REPLACE,
-    NMP_NLM_FLAG_PREPEND = NMP_NLM_FLAG_F_CREATE,
-    NMP_NLM_FLAG_APPEND  = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_APPEND,
-    NMP_NLM_FLAG_TEST    = NMP_NLM_FLAG_F_EXCL,
-} NMPNlmFlags;
-
-typedef enum {
-    /* compare fields which kernel considers as similar routes.
-     * It is a looser comparisong then NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID
-     * and means that `ip route add` would fail to add two routes
-     * that have the same NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID.
-     * On the other hand, `ip route append` would allow that, as
-     * long as NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID differs. */
-    NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID,
-
-    /* compare two routes as kernel would allow to add them with
-     * `ip route append`. In other words, kernel does not allow you to
-     * add two routes (at the same time) which compare equal according
-     * to NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID.
-     *
-     * For the ID we can only recognize route fields that we actually implement.
-     * However, kernel supports more routing options, some of them also part of
-     * the ID. NetworkManager is oblivious to these options and will wrongly think
-     * that two routes are identical, while they are not. That can lead to an
-     * inconsistent platform cache. Not much what we can do about that, except
-     * implementing all options that kernel supports *sigh*. See rh#1337860.
-     */
-    NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID,
-
-    /* compare all fields as they make sense for kernel. For example,
-     * a route destination 192.168.1.5/24 is not accepted by kernel and
-     * we treat it identical to 192.168.1.0/24. Semantically these
-     * routes are identical, but NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL will
-     * report them as different.
-     *
-     * The result shall be identical to call first nm_platform_ip_route_normalize()
-     * on both routes and then doing a full comparison. */
-    NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY,
-
-    /* compare all fields. This should have the same effect as memcmp(),
-     * except allowing for undefined data in holes between field alignment.
-     */
-    NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
-
-} NMPlatformIPRouteCmpType;
-
-typedef enum {
-    NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
-
-    NM_PLATFORM_ROUTING_RULE_CMP_TYPE_SEMANTICALLY,
-
-    NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL,
-} NMPlatformRoutingRuleCmpType;
-
-typedef struct {
-    union {
-        guint8      data[20 /* NM_UTILS_HWADDR_LEN_MAX */];
-        NMEtherAddr ether_addr;
-    };
-    guint8 len;
-} NMPLinkAddress;
-
-/* assert that NMEtherAddr does not affect the alignment of NMPLinkAddress struct. */
-G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1);
-G_STATIC_ASSERT(_nm_alignof(NMPLinkAddress) == 1);
-
-gconstpointer nmp_link_address_get(const NMPLinkAddress *addr, size_t *length);
-GBytes *      nmp_link_address_get_as_bytes(const NMPLinkAddress *addr);
-
-typedef enum {
-
-    /* match-flags are strictly inclusive. That means,
-     * by default nothing is matched, but if you enable a particular
-     * flag, a candidate that matches passes the check.
-     *
-     * In other words: adding more flags can only extend the result
-     * set of matching objects.
-     *
-     * Also, the flags form partitions. Like, an address can be either of
-     * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for
-     * the ADDRSTATE match types.
-     */
-    NM_PLATFORM_MATCH_WITH_NONE = 0,
-
-    NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL    = (1LL << 0),
-    NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL = (1LL << 1),
-    NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY =
-        NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL,
-
-    NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL    = (1LL << 2),
-    NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE = (1LL << 3),
-    NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED = (1LL << 4),
-    NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY      = NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
-                                            | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE
-                                            | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED,
-} NMPlatformMatchFlags;
-
-#define NM_PLATFORM_LINK_OTHER_NETNS (-1)
-
-struct _NMPlatformObject {
-    /* the object type has no fields of its own, it is only used to having
-     * a special pointer type that can be used to indicate "any" type. */
-    char _dummy_don_t_use_me;
-};
-
-#define __NMPlatformObjWithIfindex_COMMON \
-    int ifindex;                          \
-    ;
-
-struct _NMPlatformObjWithIfindex {
-    __NMPlatformObjWithIfindex_COMMON;
-};
-
-struct _NMPlatformLink {
-    __NMPlatformObjWithIfindex_COMMON;
-    char       name[NMP_IFNAMSIZ];
-    NMLinkType type;
-
-    /* rtnl_link_get_type(), IFLA_INFO_KIND. */
-    /* NMPlatform initializes this field with a static string. */
-    const char *kind;
-
-    /* NMPlatform initializes this field with a static string. */
-    const char *driver;
-
-    int master;
-
-    /* rtnl_link_get_link(), IFLA_LINK.
-     * If IFLA_LINK_NETNSID indicates that the parent is in another namespace,
-     * this field be set to (negative) NM_PLATFORM_LINK_OTHER_NETNS. */
-    int parent;
-
-    /* IFF_* flags. Note that the flags in 'struct ifinfomsg' are declared as 'unsigned'. */
-    guint n_ifi_flags;
-
-    guint mtu;
-
-    /* rtnl_link_get_arptype(), ifinfomsg.ifi_type. */
-    guint32 arptype;
-
-    /* IFLA_ADDRESS */
-    NMPLinkAddress l_address;
-
-    /* IFLA_BROADCAST */
-    NMPLinkAddress l_broadcast;
-
-    /* rtnl_link_inet6_get_token(), IFLA_INET6_TOKEN */
-    NMUtilsIPv6IfaceId inet6_token;
-
-    /* The bitwise inverse of rtnl_link_inet6_get_addr_gen_mode(). It is inverse
-     * to have a default of 0 -- meaning: unspecified. That way, a struct
-     * initialized with memset(0) has and unset value.*/
-    guint8 inet6_addr_gen_mode_inv;
-
-    /* Statistics */
-    guint64 rx_packets;
-    guint64 rx_bytes;
-    guint64 tx_packets;
-    guint64 tx_bytes;
-
-    /* @connected is mostly identical to (@n_ifi_flags & IFF_UP). Except for bridge/bond masters,
-     * where we coerce the link as disconnect if it has no slaves. */
-    bool connected : 1;
-
-    bool initialized : 1;
-};
-
-typedef enum { /*< skip >*/
-               NM_PLATFORM_SIGNAL_ID_NONE,
-               NM_PLATFORM_SIGNAL_ID_LINK,
-               NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
-               NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
-               NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
-               NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
-               NM_PLATFORM_SIGNAL_ID_ROUTING_RULE,
-               NM_PLATFORM_SIGNAL_ID_QDISC,
-               NM_PLATFORM_SIGNAL_ID_TFILTER,
-               _NM_PLATFORM_SIGNAL_ID_LAST,
-} NMPlatformSignalIdType;
-
-guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type);
-
-typedef enum {
-    NM_PLATFORM_SIGNAL_NONE,
-    NM_PLATFORM_SIGNAL_ADDED,
-    NM_PLATFORM_SIGNAL_CHANGED,
-    NM_PLATFORM_SIGNAL_REMOVED,
-} NMPlatformSignalChangeType;
-
-#define NM_PLATFORM_IP_ADDRESS_CAST(address) \
-    NM_CONSTCAST(NMPlatformIPAddress,        \
-                 (address),                  \
-                 NMPlatformIPXAddress,       \
-                 NMPlatformIP4Address,       \
-                 NMPlatformIP6Address)
-
-#define __NMPlatformIPAddress_COMMON                                                         \
-    __NMPlatformObjWithIfindex_COMMON;                                                       \
-    NMIPConfigSource addr_source;                                                            \
-                                                                                             \
-    /* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*().
-     *
-     * The rules are:
-     * 1 @lifetime==0: @timestamp and @preferred is irrelevant (but mostly set to 0 too). Such addresses
-     *   are permanent. This rule is so that unset addresses (calloc) are permanent by default.
-     * 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_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().
-     *
-     * Non permanent addresses from DHCP/RA might have the @timestamp set to the moment of when the
-     * lease was received. Addresses from kernel might have the @timestamp based on the last modification
-     * time of the addresses. But don't rely on this behaviour, the @timestamp is only defined for anchoring
-     * @lifetime and @preferred.
-     */ \
-    guint32 timestamp;                                                                       \
-    guint32 lifetime;  /* seconds since timestamp */                                         \
-    guint32 preferred; /* seconds since timestamp */                                         \
-                                                                                             \
-    /* ifa_flags in 'struct ifaddrmsg' from <linux/if_addr.h>, extended to 32 bit by
-     * IFA_FLAGS attribute. */         \
-    guint32 n_ifa_flags;                                                                     \
-                                                                                             \
-    guint8 plen;                                                                             \
-                                                                                             \
-    /* FIXME(l3cfg): the external marker won't be necessary anymore, because we only
-     * merge addresses we care about, and ignore (don't remove) external addresses. */         \
-    bool external : 1;                                                                       \
-                                                                                             \
-    bool use_ip4_broadcast_address : 1;                                                      \
-                                                                                             \
-    /* Whether the address is ready to be configured. By default, an address is, but this
-     * flag may indicate that the address is just for tracking purpose only, but the ACD
-     * state is not yet ready for the address to be configured. */    \
-    bool ip4acd_not_ready : 1;                                                               \
-    ;
-
-/**
- * NMPlatformIPAddress:
- *
- * Common parts of NMPlatformIP4Address and NMPlatformIP6Address.
- **/
-typedef struct {
-    __NMPlatformIPAddress_COMMON;
-    union {
-        guint8  address_ptr[1];
-        guint32 __dummy_for_32bit_alignment;
-    };
-} NMPlatformIPAddress;
-
-/**
- * NMPlatformIP4Address:
- * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
- **/
-struct _NMPlatformIP4Address {
-    __NMPlatformIPAddress_COMMON;
-
-    /* The local address IFA_LOCAL. */
-    in_addr_t address;
-
-    /* The IFA_ADDRESS PTP peer address. This field is rather important, because
-     * it constitutes the identifier for the IPv4 address (e.g. you can add two
-     * addresses that only differ by their peer's network-part.
-     *
-     * Beware that for most cases, NetworkManager doesn't want to set an explicit
-     * peer-address. However, that corresponds to setting the peer address to @address
-     * itself. Leaving peer-address unset/zero, means explicitly setting the peer
-     * address to 0.0.0.0, which you probably don't want.
-     * */
-    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_sec()
- **/
-struct _NMPlatformIP6Address {
-    __NMPlatformIPAddress_COMMON;
-    struct in6_addr address;
-    struct in6_addr peer_address;
-};
-
-typedef union {
-    NMPlatformIPAddress  ax;
-    NMPlatformIP4Address a4;
-    NMPlatformIP6Address a6;
-} NMPlatformIPXAddress;
-
-#undef __NMPlatformIPAddress_COMMON
-
-#define NM_PLATFORM_IP4_ADDRESS_INIT(...) (&((const NMPlatformIP4Address){__VA_ARGS__}))
-
-#define NM_PLATFORM_IP6_ADDRESS_INIT(...) (&((const NMPlatformIP6Address){__VA_ARGS__}))
-
-/* Default value for adding an IPv4 route. This is also what iproute2 does.
- * Note that contrary to IPv6, you can add routes with metric 0 and it is even
- * the default.
- */
-#define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4 ((guint32) 0u)
-
-/* Default value for adding an IPv6 route. This is also what iproute2 does.
- * Adding an IPv6 route with metric 0, kernel translates to IP6_RT_PRIO_USER (1024).
- *
- * Note that kernel doesn't allow adding IPv6 routes with metric zero via netlink.
- * It however can itself add routes with metric zero. */
-#define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6 ((guint32) 1024u)
-
-/* For IPv4, kernel adds a device route (subnet routes) with metric 0 when user
- * configures addresses. */
-#define NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE ((guint32) 0u)
-
-#define __NMPlatformIPRoute_COMMON                                                        \
-    __NMPlatformObjWithIfindex_COMMON;                                                    \
-                                                                                          \
-    /* The NMIPConfigSource. For routes that we receive from cache this corresponds
-     * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values).
-     * When adding a route, the source will be coerced to the protocol using
-     * nmp_utils_ip_config_source_coerce_to_rtprot().
-     *
-     * rtm_protocol is part of the primary key of an IPv4 route (meaning, you can add
-     * two IPv4 routes that only differ in their rtm_protocol. For IPv6, that is not
-     * the case.
-     *
-     * When deleting an IPv4/IPv6 route, the rtm_protocol field must match (even
-     * if it is not part of the primary key for IPv6) -- unless rtm_protocol is set
-     * to zero, in which case the first matching route (with proto ignored) is deleted. */       \
-    NMIPConfigSource rt_source;                                                           \
-                                                                                          \
-    guint8 plen;                                                                          \
-                                                                                          \
-    /* RTA_METRICS:
-     *
-     * For IPv4 routes, these properties are part of their
-     * ID (meaning: you can add otherwise identical IPv4 routes that
-     * only differ by the metric property).
-     * On the other hand, for IPv6 you cannot add two IPv6 routes that only differ
-     * by an RTA_METRICS property.
-     *
-     * When deleting a route, kernel seems to ignore the RTA_METRICS properties.
-     * That is a problem/bug for IPv4 because you cannot explicitly select which
-     * route to delete. Kernel just picks the first. See rh#1475642. */                                                                       \
-                                                                                          \
-    /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */                              \
-    bool lock_window : 1;                                                                 \
-    bool lock_cwnd : 1;                                                                   \
-    bool lock_initcwnd : 1;                                                               \
-    bool lock_initrwnd : 1;                                                               \
-    bool lock_mtu : 1;                                                                    \
-                                                                                          \
-    /* if TRUE, the "metric" field is interpreted as an offset that is added to a default
-     * metric. For example, form a DHCP lease we don't know the actually used metric, because
-     * that is determined by upper layers (the configuration). However, we have a default
-     * metric that should be used. So we set "metric_any" to %TRUE, which means to use
-     * the default metric. However, we still treat the "metric" field as an offset that
-     * will be added to the default metric. In most case, you want that "metric" is zero
-     * when setting "metric_any". */ \
-    bool metric_any : 1;                                                                  \
-                                                                                          \
-    /* like "metric_any", the table is determined by other layers of the code.
-     * This field overrides "table_coerced" field. If "table_any" is true, then
-     * the "table_coerced" field is ignored (unlike for the metric). */            \
-    bool table_any : 1;                                                                   \
-                                                                                          \
-    /* rtnh_flags
-     *
-     * Routes with rtm_flags RTM_F_CLONED are hidden by platform and
-     * do not exist from the point-of-view of platform users.
-     * Such a route is not alive, according to nmp_object_is_alive().
-     *
-     * NOTE: currently we ignore all flags except RTM_F_CLONED
-     * and RTNH_F_ONLINK.
-     * We also may not properly consider the flags as part of the ID
-     * in route-cmp. */                                                                         \
-    unsigned r_rtm_flags;                                                                 \
-                                                                                          \
-    /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */                                      \
-    guint32 mss;                                                                          \
-                                                                                          \
-    /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */                                      \
-    guint32 window;                                                                       \
-                                                                                          \
-    /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */                                          \
-    guint32 cwnd;                                                                         \
-                                                                                          \
-    /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */                                  \
-    guint32 initcwnd;                                                                     \
-                                                                                          \
-    /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */                                  \
-    guint32 initrwnd;                                                                     \
-                                                                                          \
-    /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */                                            \
-    guint32 mtu;                                                                          \
-                                                                                          \
-    /* RTA_PRIORITY (iproute2: metric)
-     * If "metric_any" is %TRUE, then this is interpreted as an offset that will be
-     * added to a default base metric. In such cases, the offset is usually zero. */                                                    \
-    guint32 metric;                                                                       \
-                                                                                          \
-    /* rtm_table, RTA_TABLE.
-     *
-     * This is not the original table ID. Instead, 254 (RT_TABLE_MAIN) and
-     * zero (RT_TABLE_UNSPEC) are swapped, so that the default is the main
-     * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */                                                              \
-    guint32 table_coerced;                                                                \
-                                                                                          \
-    /* rtm_type.
-     *
-     * This is not the original type, if type_coerced is 0 then
-     * it means RTN_UNSPEC otherwise the type value is preserved.
-     * */                                                                          \
-    guint8 type_coerced;                                                                  \
-                                                                                          \
-    /*end*/
-
-typedef struct {
-    __NMPlatformIPRoute_COMMON;
-    union {
-        guint8  network_ptr[1];
-        guint32 __dummy_for_32bit_alignment;
-    };
-} NMPlatformIPRoute;
-
-#define NM_PLATFORM_IP_ROUTE_CAST(route) \
-    NM_CONSTCAST(NMPlatformIPRoute,      \
-                 (route),                \
-                 NMPlatformIPXRoute,     \
-                 NMPlatformIP4Route,     \
-                 NMPlatformIP6Route)
-
-#define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) (NM_PLATFORM_IP_ROUTE_CAST(route)->plen <= 0)
-
-struct _NMPlatformIP4Route {
-    __NMPlatformIPRoute_COMMON;
-    in_addr_t network;
-
-    /* RTA_GATEWAY. The gateway is part of the primary key for a route */
-    in_addr_t gateway;
-
-    /* RTA_PREFSRC (called "src" by iproute2).
-     *
-     * pref_src is part of the ID of an IPv4 route. When deleting a route,
-     * pref_src must match, unless set to 0.0.0.0 to match any. */
-    in_addr_t pref_src;
-
-    /* rtm_tos (iproute2: tos)
-     *
-     * For IPv4, tos is part of the weak-id (like metric).
-     *
-     * For IPv6, tos is ignored by kernel.  */
-    guint8 tos;
-
-    /* The bitwise inverse of the route scope rtm_scope. It is inverted so that the
-     * default value (RT_SCOPE_NOWHERE) is zero. Use nm_platform_route_scope_inv()
-     * to convert back and forth between the inverse representation and the
-     * real value.
-     *
-     * rtm_scope is part of the primary key for IPv4 routes. When deleting a route,
-     * the scope must match, unless it is left at RT_SCOPE_NOWHERE, in which case the first
-     * matching route is deleted.
-     *
-     * For IPv6 routes, the scope is ignored and kernel always assumes global scope.
-     * Hence, this field is only in NMPlatformIP4Route. */
-    guint8 scope_inv;
-};
-
-struct _NMPlatformIP6Route {
-    __NMPlatformIPRoute_COMMON;
-    struct in6_addr network;
-
-    /* RTA_GATEWAY. The gateway is part of the primary key for a route */
-    struct in6_addr gateway;
-
-    /* RTA_PREFSRC (called "src" by iproute2).
-     *
-     * pref_src is not part of the ID for an IPv6 route. You cannot add two
-     * routes that only differ by pref_src.
-     *
-     * When deleting a route, pref_src is ignored by kernel. */
-    struct in6_addr pref_src;
-
-    /* RTA_SRC and rtm_src_len (called "from" by iproute2).
-     *
-     * Kernel clears the host part of src/src_plen.
-     *
-     * src/src_plen is part of the ID of a route just like network/plen. That is,
-     * Not only `ip route append`, but also `ip route add` allows to add routes that only
-     * differ in their src/src_plen.
-     */
-    struct in6_addr src;
-    guint8          src_plen;
-
-    /* RTA_PREF router preference.
-     *
-     * The type is guint8 to keep the struct size small. But the values are compatible with
-     * the NMIcmpv6RouterPref enum. */
-    guint8 rt_pref;
-};
-
-typedef union {
-    NMPlatformIPRoute  rx;
-    NMPlatformIP4Route r4;
-    NMPlatformIP6Route r6;
-} NMPlatformIPXRoute;
-
-#undef __NMPlatformIPRoute_COMMON
-
-typedef struct {
-    /* struct fib_rule_uid_range */
-    guint32 start;
-    guint32 end;
-} NMFibRuleUidRange;
-
-typedef struct {
-    /* struct fib_rule_port_range */
-    guint16 start;
-    guint16 end;
-} NMFibRulePortRange;
-
-typedef struct {
-    NMIPAddr           src;                        /* FRA_SRC */
-    NMIPAddr           dst;                        /* FRA_DST */
-    guint64            tun_id;                     /* betoh64(FRA_TUN_ID) */
-    guint32            table;                      /* (struct fib_rule_hdr).table, FRA_TABLE */
-    guint32            flags;                      /* (struct fib_rule_hdr).flags */
-    guint32            priority;                   /* RA_PRIORITY */
-    guint32            fwmark;                     /* FRA_FWMARK */
-    guint32            fwmask;                     /* FRA_FWMASK */
-    guint32            goto_target;                /* FRA_GOTO */
-    guint32            flow;                       /* FRA_FLOW */
-    guint32            suppress_prefixlen_inverse; /* ~(FRA_SUPPRESS_PREFIXLEN) */
-    guint32            suppress_ifgroup_inverse;   /* ~(FRA_SUPPRESS_IFGROUP) */
-    NMFibRuleUidRange  uid_range;                  /* FRA_UID_RANGE */
-    NMFibRulePortRange sport_range;                /* FRA_SPORT_RANGE */
-    NMFibRulePortRange dport_range;                /* FRA_DPORT_RANGE */
-    char               iifname[NMP_IFNAMSIZ];      /* FRA_IIFNAME */
-    char               oifname[NMP_IFNAMSIZ];      /* FRA_OIFNAME */
-    guint8             addr_family;                /* (struct fib_rule_hdr).family */
-    guint8             action;                     /* (struct fib_rule_hdr).action */
-    guint8             tos;                        /* (struct fib_rule_hdr).tos */
-    guint8             src_len;                    /* (struct fib_rule_hdr).src_len */
-    guint8             dst_len;                    /* (struct fib_rule_hdr).dst_len */
-    guint8             l3mdev;                     /* FRA_L3MDEV */
-    guint8             protocol;                   /* FRA_PROTOCOL */
-    guint8             ip_proto;                   /* FRA_IP_PROTO */
-
-    bool uid_range_has : 1; /* has(FRA_UID_RANGE) */
-} NMPlatformRoutingRule;
-
-#define NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET (~((guint32) 0))
-
-#define NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED ((guint32) 0x83126E97u)
-
-G_STATIC_ASSERT(((((guint64) NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) * 1000u) >> 10)
-                == (guint64) INT_MAX);
-
-typedef struct {
-    guint32 limit;
-    guint32 flows;
-    guint32 target;
-    guint32 interval;
-    guint32 quantum;
-
-    /* TCA_FQ_CODEL_CE_THRESHOLD: kernel internally stores this value as
-     * ((val64 * NSEC_PER_USEC) >> CODEL_SHIFT). The default value (in
-     * the domain with this coercion) is CODEL_DISABLED_THRESHOLD (INT_MAX).
-     * That means, "disabled" is expressed on RTM_NEWQDISC netlink API by absence of the
-     * netlink attribute but also as the special value 0x83126E97u
-     * (NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED).
-     * Beware: zero is not the default you must always explicitly set this value. */
-    guint32 ce_threshold;
-
-    /* TCA_FQ_CODEL_MEMORY_LIMIT: note that only values <= 2^31 are accepted by kernel
-     * and kernel defaults to 32MB.
-     * Note that we use the special value NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET
-     * to indicate that no explicit limit is set (when we send a RTM_NEWQDISC request).
-     * This will cause kernel to choose the default (32MB).
-     * Beware: zero is not the default you must always explicitly set this value. */
-    guint32 memory_limit;
-
-    bool ecn : 1;
-} NMPlatformQdiscFqCodel;
-
-typedef struct {
-    unsigned quantum;
-    int      perturb_period;
-    guint32  limit;
-    unsigned divisor;
-    unsigned flows;
-    unsigned depth;
-} NMPlatformQdiscSfq;
-
-typedef struct {
-    guint64 rate;
-    guint32 burst;
-    guint32 limit;
-    guint32 latency;
-} NMPlatformQdiscTbf;
-
-typedef struct {
-    __NMPlatformObjWithIfindex_COMMON;
-
-    /* beware, kind is embedded in an NMPObject, hence you must
-     * take care of the lifetime of the string. */
-    const char *kind;
-
-    int     addr_family;
-    guint32 handle;
-    guint32 parent;
-    guint32 info;
-    union {
-        NMPlatformQdiscFqCodel fq_codel;
-        NMPlatformQdiscSfq     sfq;
-        NMPlatformQdiscTbf     tbf;
-    };
-} NMPlatformQdisc;
-
-typedef struct {
-    char sdata[32];
-} NMPlatformActionSimple;
-
-typedef struct {
-    int  ifindex;
-    bool egress : 1;
-    bool ingress : 1;
-    bool mirror : 1;
-    bool redirect : 1;
-} NMPlatformActionMirred;
-
-typedef struct {
-    /* beware, kind is embedded in an NMPObject, hence you must
-     * take care of the lifetime of the string. */
-    const char *kind;
-
-    union {
-        NMPlatformActionSimple simple;
-        NMPlatformActionMirred mirred;
-    };
-} NMPlatformAction;
-
-#define NM_PLATFORM_ACTION_KIND_SIMPLE "simple"
-#define NM_PLATFORM_ACTION_KIND_MIRRED "mirred"
-
-typedef struct {
-    __NMPlatformObjWithIfindex_COMMON;
-
-    /* beware, kind is embedded in an NMPObject, hence you must
-     * take care of the lifetime of the string. */
-    const char *kind;
-
-    int              addr_family;
-    guint32          handle;
-    guint32          parent;
-    guint32          info;
-    NMPlatformAction action;
-} NMPlatformTfilter;
-
-#undef __NMPlatformObjWithIfindex_COMMON
-
-typedef struct {
-    gboolean      is_ip4;
-    NMPObjectType obj_type;
-    int           addr_family;
-    gsize         sizeof_route;
-    int (*route_cmp)(const NMPlatformIPXRoute *a,
-                     const NMPlatformIPXRoute *b,
-                     NMPlatformIPRouteCmpType  cmp_type);
-    const char *(*route_to_string)(const NMPlatformIPXRoute *route, char *buf, gsize len);
-} NMPlatformVTableRoute;
-
-typedef union {
-    struct {
-        NMPlatformVTableRoute v6;
-        NMPlatformVTableRoute v4;
-    };
-    NMPlatformVTableRoute vx[2];
-} _NMPlatformVTableRouteUnion;
-
-extern const _NMPlatformVTableRouteUnion nm_platform_vtable_route;
-
-typedef struct {
-    guint16 id;
-    guint32 qos;
-    bool    proto_ad : 1;
-} NMPlatformVFVlan;
-
-typedef struct {
-    guint32           index;
-    guint32           min_tx_rate;
-    guint32           max_tx_rate;
-    guint             num_vlans;
-    NMPlatformVFVlan *vlans;
-    struct {
-        guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */
-        guint8 len;
-    } mac;
-    gint8 spoofchk;
-    gint8 trust;
-} NMPlatformVF;
-
-typedef struct {
-    guint16 vid_start;
-    guint16 vid_end;
-    bool    untagged : 1;
-    bool    pvid : 1;
-} NMPlatformBridgeVlan;
-
-typedef struct {
-    NMEtherAddr group_addr;
-    bool        mcast_querier : 1;
-    bool        mcast_query_use_ifaddr : 1;
-    bool        mcast_snooping : 1;
-    bool        stp_state : 1;
-    bool        vlan_stats_enabled : 1;
-    guint16     group_fwd_mask;
-    guint16     priority;
-    guint16     vlan_protocol;
-    guint32     ageing_time;
-    guint32     forward_delay;
-    guint32     hello_time;
-    guint32     max_age;
-    guint32     mcast_last_member_count;
-    guint32     mcast_startup_query_count;
-    guint32     mcast_hash_max;
-    guint64     mcast_last_member_interval;
-    guint64     mcast_membership_interval;
-    guint64     mcast_querier_interval;
-    guint64     mcast_query_interval;
-    guint64     mcast_query_response_interval;
-    guint64     mcast_startup_query_interval;
-    guint8      mcast_router;
-} NMPlatformLnkBridge;
-
-extern const NMPlatformLnkBridge nm_platform_lnk_bridge_default;
-
-typedef struct {
-    in_addr_t local;
-    in_addr_t remote;
-    int       parent_ifindex;
-    guint16   input_flags;
-    guint16   output_flags;
-    guint32   input_key;
-    guint32   output_key;
-    guint8    ttl;
-    guint8    tos;
-    bool      path_mtu_discovery : 1;
-    bool      is_tap : 1;
-} NMPlatformLnkGre;
-
-typedef struct {
-    int         p_key;
-    const char *mode;
-} NMPlatformLnkInfiniband;
-
-typedef struct {
-    struct in6_addr local;
-    struct in6_addr remote;
-    int             parent_ifindex;
-    guint8          ttl;
-    guint8          tclass;
-    guint8          encap_limit;
-    guint8          proto;
-    guint           flow_label;
-    guint32         flags;
-
-    /* IP6GRE only */
-    guint32 input_key;
-    guint32 output_key;
-    guint16 input_flags;
-    guint16 output_flags;
-    bool    is_tap : 1;
-    bool    is_gre : 1;
-} NMPlatformLnkIp6Tnl;
-
-typedef struct {
-    in_addr_t local;
-    in_addr_t remote;
-    int       parent_ifindex;
-    guint8    ttl;
-    guint8    tos;
-    bool      path_mtu_discovery : 1;
-} NMPlatformLnkIpIp;
-
-typedef struct {
-    int     parent_ifindex;
-    guint64 sci; /* host byte order */
-    guint64 cipher_suite;
-    guint32 window;
-    guint8  icv_length;
-    guint8  encoding_sa;
-    guint8  validation;
-    bool    encrypt : 1;
-    bool    protect : 1;
-    bool    include_sci : 1;
-    bool    es : 1;
-    bool    scb : 1;
-    bool    replay_protect : 1;
-} NMPlatformLnkMacsec;
-
-typedef struct {
-    guint mode;
-    bool  no_promisc : 1;
-    bool  tap : 1;
-} NMPlatformLnkMacvlan;
-
-typedef struct {
-    in_addr_t local;
-    in_addr_t remote;
-    int       parent_ifindex;
-    guint16   flags;
-    guint8    ttl;
-    guint8    tos;
-    guint8    proto;
-    bool      path_mtu_discovery : 1;
-} NMPlatformLnkSit;
-
-typedef struct {
-    guint32 owner;
-    guint32 group;
-
-    guint8 type;
-
-    bool owner_valid : 1;
-    bool group_valid : 1;
-
-    bool pi : 1;
-    bool vnet_hdr : 1;
-    bool multi_queue : 1;
-    bool persist : 1;
-} NMPlatformLnkTun;
-
-typedef struct {
-    /* rtnl_link_vlan_get_id(), IFLA_VLAN_ID */
-    guint16     id;
-    NMVlanFlags flags;
-} NMPlatformLnkVlan;
-
-typedef struct {
-    guint32 table;
-} NMPlatformLnkVrf;
-
-typedef struct {
-    struct in6_addr group6;
-    struct in6_addr local6;
-    in_addr_t       group;
-    in_addr_t       local;
-    int             parent_ifindex;
-    guint32         id;
-    guint32         ageing;
-    guint32         limit;
-    guint16         dst_port;
-    guint16         src_port_min;
-    guint16         src_port_max;
-    guint8          tos;
-    guint8          ttl;
-    bool            learning : 1;
-    bool            proxy : 1;
-    bool            rsc : 1;
-    bool            l2miss : 1;
-    bool            l3miss : 1;
-} NMPlatformLnkVxlan;
-
-#define NMP_WIREGUARD_PUBLIC_KEY_LEN    32
-#define NMP_WIREGUARD_SYMMETRIC_KEY_LEN 32
-
-typedef struct {
-    guint32 fwmark;
-    guint16 listen_port;
-    guint8  private_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
-    guint8  public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
-} NMPlatformLnkWireGuard;
-
-typedef enum {
-    NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE            = 0,
-    NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS   = (1LL << 0),
-    NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY = (1LL << 1),
-    NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT = (1LL << 2),
-    NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK      = (1LL << 3),
-} NMPlatformWireGuardChangeFlags;
-
-typedef enum {
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE                   = 0,
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME              = (1LL << 0),
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY      = (1LL << 1),
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL = (1LL << 2),
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT           = (1LL << 3),
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS         = (1LL << 4),
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS     = (1LL << 5),
-
-    NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT =
-        NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY
-        | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL
-        | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT
-        | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS,
-
-} NMPlatformWireGuardChangePeerFlags;
-
-typedef void (*NMPlatformAsyncCallback)(GError *error, gpointer user_data);
-
-/*****************************************************************************/
-
-typedef enum {
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS,
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL,
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF,
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV,
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE,
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_PROTOCOL,
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED,
-
-    /* this also includes FRA_SPORT_RANGE and FRA_DPORT_RANGE which
-     * were added at the same time. */
-    NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_IP_PROTO,
-
-    _NM_PLATFORM_KERNEL_SUPPORT_NUM,
-} NMPlatformKernelSupportType;
-
-extern volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPORT_NUM];
-
-int _nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value);
-
-static inline gboolean
-_nm_platform_kernel_support_detected(NMPlatformKernelSupportType type)
-{
-    nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state));
-
-    return G_LIKELY(g_atomic_int_get(&_nm_platform_kernel_support_state[type]) != 0);
-}
-
-static inline NMOptionBool
-nm_platform_kernel_support_get_full(NMPlatformKernelSupportType type, gboolean init_if_not_set)
-{
-    int v;
-
-    nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < G_N_ELEMENTS(_nm_platform_kernel_support_state));
-
-    v = g_atomic_int_get(&_nm_platform_kernel_support_state[type]);
-    if (G_UNLIKELY(v == 0)) {
-        if (!init_if_not_set)
-            return NM_OPTION_BOOL_DEFAULT;
-        v = _nm_platform_kernel_support_init(type, 0);
-    }
-    return (v >= 0);
-}
-
-static inline gboolean
-nm_platform_kernel_support_get(NMPlatformKernelSupportType type)
-{
-    return nm_platform_kernel_support_get_full(type, TRUE) != NM_OPTION_BOOL_FALSE;
-}
-
-/*****************************************************************************/
-
-struct _NMPlatformPrivate;
-
-struct _NMPlatform {
-    GObject                    parent;
-    NMPNetns *                 _netns;
-    struct _NMPlatformPrivate *_priv;
-};
-
-typedef struct {
-    GObjectClass parent;
-
-    gboolean (*sysctl_set)(NMPlatform *self,
-                           const char *pathid,
-                           int         dirfd,
-                           const char *path,
-                           const char *value);
-    void (*sysctl_set_async)(NMPlatform *            self,
-                             const char *            pathid,
-                             int                     dirfd,
-                             const char *            path,
-                             const char *const *     values,
-                             NMPlatformAsyncCallback callback,
-                             gpointer                data,
-                             GCancellable *          cancellable);
-    char *(*sysctl_get)(NMPlatform *self, const char *pathid, int dirfd, const char *path);
-
-    void (*refresh_all)(NMPlatform *self, NMPObjectType obj_type);
-    void (*process_events)(NMPlatform *self);
-
-    int (*link_add)(NMPlatform *           self,
-                    NMLinkType             type,
-                    const char *           name,
-                    int                    parent,
-                    const void *           address,
-                    size_t                 address_len,
-                    guint32                mtu,
-                    gconstpointer          extra_data,
-                    const NMPlatformLink **out_link);
-    gboolean (*link_delete)(NMPlatform *self, int ifindex);
-    gboolean (*link_refresh)(NMPlatform *self, int ifindex);
-    gboolean (*link_set_netns)(NMPlatform *self, int ifindex, int netns_fd);
-    gboolean (*link_set_up)(NMPlatform *self, int ifindex, gboolean *out_no_firmware);
-    gboolean (*link_set_down)(NMPlatform *self, int ifindex);
-    gboolean (*link_set_arp)(NMPlatform *self, int ifindex);
-    gboolean (*link_set_noarp)(NMPlatform *self, int ifindex);
-
-    int (*link_set_user_ipv6ll_enabled)(NMPlatform *self, int ifindex, gboolean enabled);
-    gboolean (*link_set_token)(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
-
-    gboolean (*link_get_permanent_address)(NMPlatform *self,
-                                           int         ifindex,
-                                           guint8 *    buf,
-                                           size_t *    length);
-    int (*link_set_address)(NMPlatform *self, int ifindex, gconstpointer address, size_t length);
-    int (*link_set_mtu)(NMPlatform *self, int ifindex, guint32 mtu);
-    gboolean (*link_set_name)(NMPlatform *self, int ifindex, const char *name);
-    void (*link_set_sriov_params_async)(NMPlatform *            self,
-                                        int                     ifindex,
-                                        guint                   num_vfs,
-                                        NMOptionBool            autoprobe,
-                                        NMPlatformAsyncCallback callback,
-                                        gpointer                callback_data,
-                                        GCancellable *          cancellable);
-    gboolean (*link_set_sriov_vfs)(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
-    gboolean (*link_set_bridge_vlans)(NMPlatform *                       self,
-                                      int                                ifindex,
-                                      gboolean                           on_master,
-                                      const NMPlatformBridgeVlan *const *vlans);
-
-    char *(*link_get_physical_port_id)(NMPlatform *self, int ifindex);
-    guint (*link_get_dev_id)(NMPlatform *self, int ifindex);
-    gboolean (*link_get_wake_on_lan)(NMPlatform *self, int ifindex);
-    gboolean (*link_get_driver_info)(NMPlatform *self,
-                                     int         ifindex,
-                                     char **     out_driver_name,
-                                     char **     out_driver_version,
-                                     char **     out_fw_version);
-
-    gboolean (*link_supports_carrier_detect)(NMPlatform *self, int ifindex);
-    gboolean (*link_supports_vlans)(NMPlatform *self, int ifindex);
-    gboolean (*link_supports_sriov)(NMPlatform *self, int ifindex);
-
-    gboolean (*link_enslave)(NMPlatform *self, int master, int slave);
-    gboolean (*link_release)(NMPlatform *self, int master, int slave);
-
-    gboolean (*link_can_assume)(NMPlatform *self, int ifindex);
-
-    int (*link_wireguard_change)(NMPlatform *                              self,
-                                 int                                       ifindex,
-                                 const NMPlatformLnkWireGuard *            lnk_wireguard,
-                                 const struct _NMPWireGuardPeer *          peers,
-                                 const NMPlatformWireGuardChangePeerFlags *peer_flags,
-                                 guint                                     peers_len,
-                                 NMPlatformWireGuardChangeFlags            change_flags);
-
-    gboolean (*link_vlan_change)(NMPlatform *            self,
-                                 int                     ifindex,
-                                 NMVlanFlags             flags_mask,
-                                 NMVlanFlags             flags_set,
-                                 gboolean                ingress_reset_all,
-                                 const NMVlanQosMapping *ingress_map,
-                                 gsize                   n_ingress_map,
-                                 gboolean                egress_reset_all,
-                                 const NMVlanQosMapping *egress_map,
-                                 gsize                   n_egress_map);
-    gboolean (*link_tun_add)(NMPlatform *            self,
-                             const char *            name,
-                             const NMPlatformLnkTun *props,
-                             const NMPlatformLink ** out_link,
-                             int *                   out_fd);
-
-    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);
-
-    gboolean (*wifi_get_capabilities)(NMPlatform *              self,
-                                      int                       ifindex,
-                                      NMDeviceWifiCapabilities *caps);
-    gboolean (*wifi_get_station)(NMPlatform * self,
-                                 int          ifindex,
-                                 NMEtherAddr *out_bssid,
-                                 int *        out_quality,
-                                 guint32 *    out_rate);
-    gboolean (*wifi_get_bssid)(NMPlatform *self, int ifindex, guint8 *bssid);
-    guint32 (*wifi_get_frequency)(NMPlatform *self, int ifindex);
-    int (*wifi_get_quality)(NMPlatform *self, int ifindex);
-    guint32 (*wifi_get_rate)(NMPlatform *self, int ifindex);
-    NM80211Mode (*wifi_get_mode)(NMPlatform *self, int ifindex);
-    void (*wifi_set_mode)(NMPlatform *self, int ifindex, NM80211Mode mode);
-    void (*wifi_set_powersave)(NMPlatform *self, int ifindex, guint32 powersave);
-    guint32 (*wifi_find_frequency)(NMPlatform *self, int ifindex, const guint32 *freqs);
-    void (*wifi_indicate_addressing_running)(NMPlatform *self, int ifindex, gboolean running);
-    NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan)(NMPlatform *self, int ifindex);
-    gboolean (*wifi_set_wake_on_wlan)(NMPlatform *                self,
-                                      int                         ifindex,
-                                      NMSettingWirelessWakeOnWLan wowl);
-
-    guint32 (*mesh_get_channel)(NMPlatform *self, int ifindex);
-    gboolean (*mesh_set_channel)(NMPlatform *self, int ifindex, guint32 channel);
-    gboolean (*mesh_set_ssid)(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
-
-    guint16 (*wpan_get_pan_id)(NMPlatform *self, int ifindex);
-    gboolean (*wpan_set_pan_id)(NMPlatform *self, int ifindex, guint16 pan_id);
-    guint16 (*wpan_get_short_addr)(NMPlatform *self, int ifindex);
-    gboolean (*wpan_set_short_addr)(NMPlatform *self, int ifindex, guint16 short_addr);
-    gboolean (*wpan_set_channel)(NMPlatform *self, int ifindex, guint8 page, guint8 channel);
-
-    gboolean (*object_delete)(NMPlatform *self, const NMPObject *obj);
-
-    gboolean (*ip4_address_add)(NMPlatform *self,
-                                int         ifindex,
-                                in_addr_t   address,
-                                guint8      plen,
-                                in_addr_t   peer_address,
-                                in_addr_t   broadcast_address,
-                                guint32     lifetime,
-                                guint32     preferred_lft,
-                                guint32     flags,
-                                const char *label);
-    gboolean (*ip6_address_add)(NMPlatform *    self,
-                                int             ifindex,
-                                struct in6_addr address,
-                                guint8          plen,
-                                struct in6_addr peer_address,
-                                guint32         lifetime,
-                                guint32         preferred_lft,
-                                guint32         flags);
-    gboolean (*ip4_address_delete)(NMPlatform *self,
-                                   int         ifindex,
-                                   in_addr_t   address,
-                                   guint8      plen,
-                                   in_addr_t   peer_address);
-    gboolean (*ip6_address_delete)(NMPlatform *    self,
-                                   int             ifindex,
-                                   struct in6_addr address,
-                                   guint8          plen);
-
-    int (*ip_route_add)(NMPlatform *             self,
-                        NMPNlmFlags              flags,
-                        int                      addr_family,
-                        const NMPlatformIPRoute *route);
-    int (*ip_route_get)(NMPlatform *  self,
-                        int           addr_family,
-                        gconstpointer address,
-                        int           oif_ifindex,
-                        NMPObject **  out_route);
-
-    int (*routing_rule_add)(NMPlatform *                 self,
-                            NMPNlmFlags                  flags,
-                            const NMPlatformRoutingRule *routing_rule);
-
-    int (*qdisc_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc);
-
-    int (*tfilter_add)(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter);
-} NMPlatformClass;
-
-/* NMPlatform signals
- *
- * Each signal handler is called with a type-specific object that provides
- * key attributes that constitute identity of the object. They may also
- * provide additional attributes for convenience.
- *
- * The object only intended to be used by the signal handler to determine
- * the current values. It is no longer valid after the signal handler exits
- * but you are free to copy the provided information and use it for later
- * reference.
- */
-#define NM_PLATFORM_SIGNAL_LINK_CHANGED         "link-changed"
-#define NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED  "ip4-address-changed"
-#define NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED  "ip6-address-changed"
-#define NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED    "ip4-route-changed"
-#define NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED    "ip6-route-changed"
-#define NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED "routing-rule-changed"
-#define NM_PLATFORM_SIGNAL_QDISC_CHANGED        "qdisc-changed"
-#define NM_PLATFORM_SIGNAL_TFILTER_CHANGED      "tfilter-changed"
-
-const char *nm_platform_signal_change_type_to_string(NMPlatformSignalChangeType change_type);
-
-/*****************************************************************************/
-
-GType nm_platform_get_type(void);
-
-void        nm_platform_setup(NMPlatform *instance);
-NMPlatform *nm_platform_get(void);
-
-#define NM_PLATFORM_GET (nm_platform_get())
-
-/*****************************************************************************/
-
-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
- *   from rtm_table/RTA_TABLE.
- *
- * Returns: returns the coerced table id, that can be stored in
- *   NMPlatformIPRoute.table_coerced.
- */
-static inline guint32
-nm_platform_route_table_coerce(guint32 table)
-{
-    /* For kernel, the default table is RT_TABLE_MAIN (254).
-     * We want that in NMPlatformIPRoute.table_coerced a numeric
-     * zero is the default. Hence, @table_coerced swaps the
-     * value 0 and 254. Use nm_platform_route_table_coerce()
-     * and nm_platform_route_table_uncoerce() to convert between
-     * the two domains. */
-    switch (table) {
-    case 0 /* RT_TABLE_UNSPEC */:
-        return 254;
-    case 254 /* RT_TABLE_MAIN */:
-        return 0;
-    default:
-        return table;
-    }
-}
-
-/**
- * nm_platform_route_table_uncoerce:
- * @table_coerced: the route table, in its coerced value
- * @normalize: whether to normalize RT_TABLE_UNSPEC to
- *   RT_TABLE_MAIN. For kernel, routes with a table id
- *   RT_TABLE_UNSPEC do not exist and are treated like
- *   RT_TABLE_MAIN.
- *
- * Returns: reverts the coerced table ID in NMPlatformIPRoute.table_coerced
- *   to the original value as kernel understands it.
- */
-static inline guint32
-nm_platform_route_table_uncoerce(guint32 table_coerced, gboolean normalize)
-{
-    /* this undoes nm_platform_route_table_coerce().  */
-    switch (table_coerced) {
-    case 0 /* RT_TABLE_UNSPEC */:
-        return 254;
-    case 254 /* RT_TABLE_MAIN */:
-        return normalize ? 254 : 0;
-    default:
-        return table_coerced;
-    }
-}
-
-static inline gboolean
-nm_platform_route_table_is_main(guint32 table)
-{
-    /* same as
-     *   nm_platform_route_table_uncoerce (table, TRUE) == RT_TABLE_MAIN
-     * and
-     *   nm_platform_route_table_uncoerce (nm_platform_route_table_coerce (table), TRUE) == RT_TABLE_MAIN
-     *
-     * That is, the function operates the same on @table and its coerced
-     * form.
-     */
-    return table == 0 || table == 254;
-}
-
-/**
- * nm_platform_route_scope_inv:
- * @scope: the route scope, either its original value, or its inverse.
- *
- * This function is useful, because the constants such as RT_SCOPE_NOWHERE
- * are 'int', so ~scope also gives an 'int'. This function gets the type
- * casts to guint8 right.
- *
- * Returns: the bitwise inverse of the route scope.
- * */
-#define nm_platform_route_scope_inv _nm_platform_uint8_inv
-static inline guint8
-_nm_platform_uint8_inv(guint8 scope)
-{
-    return (guint8) ~scope;
-}
-
-/**
- * nm_platform_route_type_coerce:
- * @table: the route type, in its original value.
- *
- * Returns: returns the coerced type, that can be stored in
- *   NMPlatformIPRoute.type_coerced.
- */
-static inline guint8
-nm_platform_route_type_coerce(guint8 type)
-{
-    switch (type) {
-    case 0 /* RTN_UNSPEC */:
-        return 1;
-    case 1 /* RTN_UNICAST */:
-        return 0;
-    default:
-        return type;
-    }
-}
-
-/**
- * nm_platform_route_type_uncoerce:
- * @table: the type table, in its coerced value
- *
- * Returns: reverts the coerced type in NMPlatformIPRoute.type_coerced
- *   to the original value as kernel understands it.
- */
-static inline guint8
-nm_platform_route_type_uncoerce(guint8 type_coerced)
-{
-    return nm_platform_route_type_coerce(type_coerced);
-}
-
-gboolean nm_platform_get_use_udev(NMPlatform *self);
-gboolean nm_platform_get_log_with_ptr(NMPlatform *self);
-
-NMPNetns *nm_platform_netns_get(NMPlatform *self);
-gboolean  nm_platform_netns_push(NMPlatform *self, NMPNetns **netns);
-
-const char *nm_link_type_to_string(NMLinkType link_type);
-
-#define NMP_SYSCTL_PATHID_ABSOLUTE(path) ((const char *) NULL), -1, (path)
-
-#define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path)                        \
-    nm_sprintf_buf_unsafe_a(NM_STRLEN("net:/sys/class/net//\0") + NMP_IFNAMSIZ + ({ \
-                                const gsize _l = strlen(path);                      \
-                                                                                    \
-                                nm_assert(_l < 200);                                \
-                                _l;                                                 \
-                            }),                                                     \
-                            "net:/sys/class/net/%s/%s",                             \
-                            (ifname),                                               \
-                            (path)),                                                \
-        (dirfd), (path)
-
-#define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path)                            \
-    nm_sprintf_bufa(NM_STRLEN("net:/sys/class/net//" path "/\0") + NMP_IFNAMSIZ, \
-                    "net:/sys/class/net/%s/%s",                                  \
-                    (ifname),                                                    \
-                    path),                                                       \
-        (dirfd), ("" path "")
-
-int      nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname);
-gboolean nm_platform_sysctl_set(NMPlatform *self,
-                                const char *pathid,
-                                int         dirfd,
-                                const char *path,
-                                const char *value);
-void     nm_platform_sysctl_set_async(NMPlatform *            self,
-                                      const char *            pathid,
-                                      int                     dirfd,
-                                      const char *            path,
-                                      const char *const *     values,
-                                      NMPlatformAsyncCallback callback,
-                                      gpointer                data,
-                                      GCancellable *          cancellable);
-char *   nm_platform_sysctl_get(NMPlatform *self, const char *pathid, int dirfd, const char *path);
-gint32   nm_platform_sysctl_get_int32(NMPlatform *self,
-                                      const char *pathid,
-                                      int         dirfd,
-                                      const char *path,
-                                      gint32      fallback);
-gint64   nm_platform_sysctl_get_int_checked(NMPlatform *self,
-                                            const char *pathid,
-                                            int         dirfd,
-                                            const char *path,
-                                            guint       base,
-                                            gint64      min,
-                                            gint64      max,
-                                            gint64      fallback);
-
-char *nm_platform_sysctl_ip_conf_get(NMPlatform *self,
-                                     int         addr_family,
-                                     const char *ifname,
-                                     const char *property);
-
-gint64 nm_platform_sysctl_ip_conf_get_int_checked(NMPlatform *self,
-                                                  int         addr_family,
-                                                  const char *ifname,
-                                                  const char *property,
-                                                  guint       base,
-                                                  gint64      min,
-                                                  gint64      max,
-                                                  gint64      fallback);
-
-gboolean nm_platform_sysctl_ip_conf_set(NMPlatform *self,
-                                        int         addr_family,
-                                        const char *ifname,
-                                        const char *property,
-                                        const char *value);
-
-gboolean nm_platform_sysctl_ip_conf_set_int64(NMPlatform *self,
-                                              int         addr_family,
-                                              const char *ifname,
-                                              const char *property,
-                                              gint64      value);
-
-gboolean
-nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(NMPlatform *self, const char *iface, int value);
-gboolean nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(NMPlatform *self,
-                                                             const char *iface,
-                                                             guint       value_ms);
-gboolean nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(NMPlatform *self,
-                                                           const char *iface,
-                                                           guint       value_ms);
-int      nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(NMPlatform *platform,
-                                                       const char *iface,
-                                                       gboolean    consider_all,
-                                                       gboolean *  out_due_to_all);
-
-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, int ifindex, gboolean visible_only);
-const NMPlatformLink *nm_platform_link_get(NMPlatform *self, int ifindex);
-const NMPlatformLink *nm_platform_link_get_by_ifname(NMPlatform *self, const char *ifname);
-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_add(NMPlatform *           self,
-                         NMLinkType             type,
-                         const char *           name,
-                         int                    parent,
-                         const void *           address,
-                         size_t                 address_len,
-                         guint32                mtu,
-                         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, 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, 0, NULL, out_link);
-}
-
-static inline int
-nm_platform_link_bridge_add(NMPlatform *               self,
-                            const char *               name,
-                            const void *               address,
-                            size_t                     address_len,
-                            guint32                    mtu,
-                            const NMPlatformLnkBridge *props,
-                            const NMPlatformLink **    out_link)
-{
-    return nm_platform_link_add(self,
-                                NM_LINK_TYPE_BRIDGE,
-                                name,
-                                0,
-                                address,
-                                address_len,
-                                mtu,
-                                props,
-                                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, 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, 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, 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,
-                                0,
-                                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, 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,
-                                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, 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, 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,
-                                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, 0, props, out_link);
-}
-
-static inline int
-nm_platform_link_ip6gre_add(NMPlatform *               self,
-                            const char *               name,
-                            const void *               address,
-                            size_t                     address_len,
-                            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,
-                                address,
-                                address_len,
-                                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, 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,
-                                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,
-                                0,
-                                props,
-                                out_link);
-}
-
-gboolean nm_platform_link_delete(NMPlatform *self, int ifindex);
-
-gboolean nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd);
-
-struct _NMDedupMultiHeadEntry;
-struct _NMPLookup;
-const struct _NMDedupMultiHeadEntry *nm_platform_lookup(NMPlatform *             self,
-                                                        const struct _NMPLookup *lookup);
-
-#define nm_platform_iter_obj_for_each(iter, self, lookup, obj)                   \
-    for (nm_dedup_multi_iter_init((iter), nm_platform_lookup((self), (lookup))); \
-         nm_platform_dedup_multi_iter_next_obj((iter), (obj), NMP_OBJECT_TYPE_UNKNOWN);)
-
-gboolean nm_platform_lookup_predicate_routes_main(const NMPObject *obj, gpointer user_data);
-gboolean nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel(const NMPObject *obj,
-                                                                     gpointer         user_data);
-
-GPtrArray *nm_platform_lookup_clone(NMPlatform *             self,
-                                    const struct _NMPLookup *lookup,
-                                    NMPObjectPredicateFunc   predicate,
-                                    gpointer                 user_data);
-
-/* convenience methods to lookup the link and access fields of NMPlatformLink. */
-int         nm_platform_link_get_ifindex(NMPlatform *self, const char *name);
-const char *nm_platform_link_get_name(NMPlatform *self, int ifindex);
-NMLinkType  nm_platform_link_get_type(NMPlatform *self, int ifindex);
-gboolean    nm_platform_link_is_software(NMPlatform *self, int ifindex);
-int         nm_platform_link_get_ifi_flags(NMPlatform *self, int ifindex, guint requested_flags);
-gboolean    nm_platform_link_is_up(NMPlatform *self, int ifindex);
-gboolean    nm_platform_link_is_connected(NMPlatform *self, int ifindex);
-gboolean    nm_platform_link_uses_arp(NMPlatform *self, int ifindex);
-guint32     nm_platform_link_get_mtu(NMPlatform *self, int ifindex);
-gboolean    nm_platform_link_get_user_ipv6ll_enabled(NMPlatform *self, int ifindex);
-
-gconstpointer nm_platform_link_get_address(NMPlatform *self, int ifindex, size_t *length);
-
-int nm_platform_link_get_master(NMPlatform *self, int slave);
-
-gboolean nm_platform_link_can_assume(NMPlatform *self, int ifindex);
-
-gboolean    nm_platform_link_get_unmanaged(NMPlatform *self, int ifindex, gboolean *unmanaged);
-gboolean    nm_platform_link_supports_slaves(NMPlatform *self, int ifindex);
-const char *nm_platform_link_get_type_name(NMPlatform *self, int ifindex);
-
-gboolean nm_platform_link_refresh(NMPlatform *self, int ifindex);
-void     nm_platform_process_events(NMPlatform *self);
-
-const NMPlatformLink *
-nm_platform_process_events_ensure_link(NMPlatform *self, int ifindex, const char *ifname);
-
-gboolean nm_platform_link_set_up(NMPlatform *self, int ifindex, gboolean *out_no_firmware);
-gboolean nm_platform_link_set_down(NMPlatform *self, int ifindex);
-gboolean nm_platform_link_set_arp(NMPlatform *self, int ifindex);
-gboolean nm_platform_link_set_noarp(NMPlatform *self, int ifindex);
-
-const char *nm_platform_link_get_udi(NMPlatform *self, int ifindex);
-const char *nm_platform_link_get_path(NMPlatform *self, int ifindex);
-
-struct udev_device *nm_platform_link_get_udev_device(NMPlatform *self, int ifindex);
-
-int      nm_platform_link_set_user_ipv6ll_enabled(NMPlatform *self, int ifindex, gboolean enabled);
-gboolean nm_platform_link_set_ipv6_token(NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
-
-gboolean
-nm_platform_link_get_permanent_address(NMPlatform *self, int ifindex, guint8 *buf, size_t *length);
-int nm_platform_link_set_address(NMPlatform *self, int ifindex, const void *address, size_t length);
-int nm_platform_link_set_mtu(NMPlatform *self, int ifindex, guint32 mtu);
-gboolean nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name);
-
-void nm_platform_link_set_sriov_params_async(NMPlatform *            self,
-                                             int                     ifindex,
-                                             guint                   num_vfs,
-                                             NMOptionBool            autoprobe,
-                                             NMPlatformAsyncCallback callback,
-                                             gpointer                callback_data,
-                                             GCancellable *          cancellable);
-
-gboolean
-nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
-gboolean nm_platform_link_set_bridge_vlans(NMPlatform *                       self,
-                                           int                                ifindex,
-                                           gboolean                           on_master,
-                                           const NMPlatformBridgeVlan *const *vlans);
-
-char *   nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex);
-guint    nm_platform_link_get_dev_id(NMPlatform *self, int ifindex);
-gboolean nm_platform_link_get_wake_on_lan(NMPlatform *self, int ifindex);
-gboolean nm_platform_link_get_driver_info(NMPlatform *self,
-                                          int         ifindex,
-                                          char **     out_driver_name,
-                                          char **     out_driver_version,
-                                          char **     out_fw_version);
-
-gboolean nm_platform_link_supports_carrier_detect(NMPlatform *self, int ifindex);
-gboolean nm_platform_link_supports_vlans(NMPlatform *self, int ifindex);
-gboolean nm_platform_link_supports_sriov(NMPlatform *self, int ifindex);
-
-gboolean nm_platform_link_enslave(NMPlatform *self, int master, int slave);
-gboolean nm_platform_link_release(NMPlatform *self, int master, int slave);
-
-gboolean nm_platform_sysctl_master_set_option(NMPlatform *self,
-                                              int         ifindex,
-                                              const char *option,
-                                              const char *value);
-char *   nm_platform_sysctl_master_get_option(NMPlatform *self, int ifindex, const char *option);
-gboolean nm_platform_sysctl_slave_set_option(NMPlatform *self,
-                                             int         ifindex,
-                                             const char *option,
-                                             const char *value);
-char *   nm_platform_sysctl_slave_get_option(NMPlatform *self, int ifindex, const char *option);
-
-const NMPObject *nm_platform_link_get_lnk(NMPlatform *           self,
-                                          int                    ifindex,
-                                          NMLinkType             link_type,
-                                          const NMPlatformLink **out_link);
-const NMPlatformLnkBridge *
-nm_platform_link_get_lnk_bridge(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkGre *
-nm_platform_link_get_lnk_gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkGre *
-nm_platform_link_get_lnk_gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkIp6Tnl *
-nm_platform_link_get_lnk_ip6tnl(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkIp6Tnl *
-nm_platform_link_get_lnk_ip6gre(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkIp6Tnl *
-nm_platform_link_get_lnk_ip6gretap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkIpIp *
-nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkInfiniband *
-nm_platform_link_get_lnk_infiniband(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-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 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);
-
-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,
-                                      int                     ifindex,
-                                      NMVlanFlags             flags_mask,
-                                      NMVlanFlags             flags_set,
-                                      gboolean                ingress_reset_all,
-                                      const NMVlanQosMapping *ingress_map,
-                                      gsize                   n_ingress_map,
-                                      gboolean                egress_reset_all,
-                                      const NMVlanQosMapping *egress_map,
-                                      gsize                   n_egress_map);
-
-int      nm_platform_link_infiniband_add(NMPlatform *           self,
-                                         int                    parent,
-                                         int                    p_key,
-                                         const NMPlatformLink **out_link);
-int      nm_platform_link_infiniband_delete(NMPlatform *self, int parent, int p_key);
-gboolean nm_platform_link_infiniband_get_properties(NMPlatform * self,
-                                                    int          ifindex,
-                                                    int *        parent,
-                                                    int *        p_key,
-                                                    const char **mode);
-
-gboolean nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_peer_ifindex);
-gboolean nm_platform_link_tun_get_properties(NMPlatform *      self,
-                                             int               ifindex,
-                                             NMPlatformLnkTun *out_properties);
-
-gboolean
-nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
-guint32     nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex);
-gboolean    nm_platform_wifi_get_station(NMPlatform * self,
-                                         int          ifindex,
-                                         NMEtherAddr *out_bssid,
-                                         int *        out_quality,
-                                         guint32 *    out_rate);
-NM80211Mode nm_platform_wifi_get_mode(NMPlatform *self, int ifindex);
-void        nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, NM80211Mode mode);
-void        nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave);
-guint32     nm_platform_wifi_find_frequency(NMPlatform *self, int ifindex, const guint32 *freqs);
-void nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex, gboolean running);
-NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan(NMPlatform *self, int ifindex);
-gboolean
-nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl);
-
-guint32  nm_platform_mesh_get_channel(NMPlatform *self, int ifindex);
-gboolean nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel);
-gboolean nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
-
-guint16  nm_platform_wpan_get_pan_id(NMPlatform *self, int ifindex);
-gboolean nm_platform_wpan_set_pan_id(NMPlatform *self, int ifindex, guint16 pan_id);
-guint16  nm_platform_wpan_get_short_addr(NMPlatform *self, int ifindex);
-gboolean nm_platform_wpan_set_short_addr(NMPlatform *self, int ifindex, guint16 short_addr);
-gboolean nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page, guint8 channel);
-
-void nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);
-const struct in6_addr *nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr);
-
-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_sit_add(NMPlatform *            self,
-                                  const char *            name,
-                                  const NMPlatformLnkSit *props,
-                                  const NMPlatformLink ** out_link);
-int      nm_platform_link_tun_add(NMPlatform *            self,
-                                  const char *            name,
-                                  const NMPlatformLnkTun *props,
-                                  const NMPlatformLink ** out_link,
-                                  int *                   out_fd);
-gboolean nm_platform_link_6lowpan_get_properties(NMPlatform *self, int ifindex, int *out_parent);
-
-int
-nm_platform_link_wireguard_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-
-int nm_platform_link_wireguard_change(NMPlatform *                              self,
-                                      int                                       ifindex,
-                                      const NMPlatformLnkWireGuard *            lnk_wireguard,
-                                      const struct _NMPWireGuardPeer *          peers,
-                                      const NMPlatformWireGuardChangePeerFlags *peer_flags,
-                                      guint                                     peers_len,
-                                      NMPlatformWireGuardChangeFlags            change_flags);
-
-const NMPlatformIP6Address *
-nm_platform_ip6_address_get(NMPlatform *self, int ifindex, const struct in6_addr *address);
-
-gboolean nm_platform_object_delete(NMPlatform *self, const NMPObject *route);
-
-gboolean nm_platform_ip4_address_add(NMPlatform *self,
-                                     int         ifindex,
-                                     in_addr_t   address,
-                                     guint8      plen,
-                                     in_addr_t   peer_address,
-                                     in_addr_t   broadcast_address,
-                                     guint32     lifetime,
-                                     guint32     preferred_lft,
-                                     guint32     flags,
-                                     const char *label);
-gboolean nm_platform_ip6_address_add(NMPlatform *    self,
-                                     int             ifindex,
-                                     struct in6_addr address,
-                                     guint8          plen,
-                                     struct in6_addr peer_address,
-                                     guint32         lifetime,
-                                     guint32         preferred_lft,
-                                     guint32         flags);
-gboolean nm_platform_ip4_address_delete(NMPlatform *self,
-                                        int         ifindex,
-                                        in_addr_t   address,
-                                        guint8      plen,
-                                        in_addr_t   peer_address);
-gboolean
-nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
-
-gboolean nm_platform_ip_address_sync(NMPlatform *self,
-                                     int         addr_family,
-                                     int         ifindex,
-                                     GPtrArray * known_addresses,
-                                     GPtrArray * addresses_prune);
-
-GPtrArray *nm_platform_ip_address_get_prune_list(NMPlatform *self,
-                                                 int         addr_family,
-                                                 int         ifindex,
-                                                 gboolean    exclude_ipv6_temporary_addrs);
-
-static inline gboolean
-_nm_platform_ip_address_sync(NMPlatform *self,
-                             int         addr_family,
-                             int         ifindex,
-                             GPtrArray * known_addresses,
-                             gboolean    full_sync)
-{
-    gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
-
-    addresses_prune = nm_platform_ip_address_get_prune_list(self, addr_family, ifindex, !full_sync);
-    return nm_platform_ip_address_sync(self,
-                                       addr_family,
-                                       ifindex,
-                                       known_addresses,
-                                       addresses_prune);
-}
-
-static inline gboolean
-nm_platform_ip4_address_sync(NMPlatform *self, int ifindex, GPtrArray *known_addresses)
-{
-    return _nm_platform_ip_address_sync(self, AF_INET, ifindex, known_addresses, TRUE);
-}
-
-static inline gboolean
-nm_platform_ip6_address_sync(NMPlatform *self,
-                             int         ifindex,
-                             GPtrArray * known_addresses,
-                             gboolean    full_sync)
-{
-    return _nm_platform_ip_address_sync(self, AF_INET6, ifindex, known_addresses, full_sync);
-}
-
-gboolean nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex);
-
-static inline gconstpointer
-nm_platform_ip_address_get_peer_address(int addr_family, const NMPlatformIPAddress *addr)
-{
-    nm_assert_addr_family(addr_family);
-    nm_assert(addr);
-
-    if (NM_IS_IPv4(addr_family))
-        return &((NMPlatformIP4Address *) addr)->peer_address;
-    return &((NMPlatformIP6Address *) addr)->peer_address;
-}
-
-void nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route);
-
-static inline guint32
-nm_platform_ip4_route_get_effective_metric(const NMPlatformIP4Route *r)
-{
-    nm_assert(r);
-
-    return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, r->metric)
-                         : r->metric;
-}
-
-static inline guint32
-nm_platform_ip6_route_get_effective_metric(const NMPlatformIP6Route *r)
-{
-    nm_assert(r);
-
-    return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, r->metric)
-                         : r->metric;
-}
-
-static inline guint32
-nm_platform_ip_route_get_effective_table(const NMPlatformIPRoute *r)
-{
-    nm_assert(r);
-    nm_assert(!r->table_any || r->table_coerced == 0);
-
-    return r->table_any ? 254u /* RT_TABLE_MAIN */
-                        : nm_platform_route_table_uncoerce(r->table_coerced, TRUE);
-}
-
-static inline gconstpointer
-nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route)
-{
-    nm_assert_addr_family(addr_family);
-    nm_assert(route);
-
-    if (NM_IS_IPv4(addr_family))
-        return &((NMPlatformIP4Route *) route)->gateway;
-    return &((NMPlatformIP6Route *) route)->gateway;
-}
-
-int nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route);
-int nm_platform_ip4_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
-int nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
-
-GPtrArray *nm_platform_ip_route_get_prune_list(NMPlatform *           self,
-                                               int                    addr_family,
-                                               int                    ifindex,
-                                               NMIPRouteTableSyncMode route_table_sync);
-
-gboolean nm_platform_ip_route_sync(NMPlatform *self,
-                                   int         addr_family,
-                                   int         ifindex,
-                                   GPtrArray * routes,
-                                   GPtrArray * routes_prune,
-                                   GPtrArray **out_temporary_not_available);
-
-gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex);
-
-int nm_platform_ip_route_get(NMPlatform *  self,
-                             int           addr_family,
-                             gconstpointer address,
-                             int           oif_ifindex,
-                             NMPObject **  out_route);
-
-int nm_platform_routing_rule_add(NMPlatform *                 self,
-                                 NMPNlmFlags                  flags,
-                                 const NMPlatformRoutingRule *routing_rule);
-
-int      nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc);
-gboolean nm_platform_qdisc_sync(NMPlatform *self, int ifindex, GPtrArray *known_qdiscs);
-
-int nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter);
-gboolean nm_platform_tfilter_sync(NMPlatform *self, int ifindex, GPtrArray *known_tfilters);
-
-const char *nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len);
-const char *nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsize len);
-const char *nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len);
-const char *
-nm_platform_lnk_infiniband_to_string(const NMPlatformLnkInfiniband *lnk, char *buf, gsize len);
-const char *nm_platform_lnk_ip6tnl_to_string(const NMPlatformLnkIp6Tnl *lnk, char *buf, gsize len);
-const char *nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *buf, gsize len);
-const char *nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len);
-const char *
-nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len);
-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);
-const char *
-nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len);
-const char *nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsize len);
-const char *nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsize len);
-const char *
-nm_platform_routing_rule_to_string(const NMPlatformRoutingRule *routing_rule, char *buf, gsize len);
-const char *nm_platform_qdisc_to_string(const NMPlatformQdisc *qdisc, char *buf, gsize len);
-const char *nm_platform_tfilter_to_string(const NMPlatformTfilter *tfilter, char *buf, gsize len);
-const char *nm_platform_vf_to_string(const NMPlatformVF *vf, char *buf, gsize len);
-const char *
-nm_platform_bridge_vlan_to_string(const NMPlatformBridgeVlan *vlan, char *buf, gsize len);
-
-const char *nm_platform_vlan_qos_mapping_to_string(const char *            name,
-                                                   const NMVlanQosMapping *map,
-                                                   gsize                   n_map,
-                                                   char *                  buf,
-                                                   gsize                   len);
-
-const char *
-nm_platform_wireguard_peer_to_string(const struct _NMPWireGuardPeer *peer, char *buf, gsize len);
-
-int nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b);
-int nm_platform_lnk_bridge_cmp(const NMPlatformLnkBridge *a, const NMPlatformLnkBridge *b);
-int nm_platform_lnk_gre_cmp(const NMPlatformLnkGre *a, const NMPlatformLnkGre *b);
-int nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a,
-                                   const NMPlatformLnkInfiniband *b);
-int nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b);
-int nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b);
-int nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b);
-int nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b);
-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);
-int nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b);
-
-int nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1,
-                                            const NMPlatformIP4Address *a2);
-
-int nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1,
-                                            const NMPlatformIP6Address *a2,
-                                            gboolean                    prefer_temp);
-
-GHashTable *nm_platform_ip4_address_addr_to_hash(NMPlatform *self, int ifindex);
-
-int nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
-                              const NMPlatformIP4Route *b,
-                              NMPlatformIPRouteCmpType  cmp_type);
-int nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
-                              const NMPlatformIP6Route *b,
-                              NMPlatformIPRouteCmpType  cmp_type);
-
-static inline int
-nm_platform_ip4_route_cmp_full(const NMPlatformIP4Route *a, const NMPlatformIP4Route *b)
-{
-    return nm_platform_ip4_route_cmp(a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
-}
-
-static inline int
-nm_platform_ip6_route_cmp_full(const NMPlatformIP6Route *a, const NMPlatformIP6Route *b)
-{
-    return nm_platform_ip6_route_cmp(a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
-}
-
-int nm_platform_routing_rule_cmp(const NMPlatformRoutingRule *a,
-                                 const NMPlatformRoutingRule *b,
-                                 NMPlatformRoutingRuleCmpType cmp_type);
-
-static inline int
-nm_platform_routing_rule_cmp_full(const NMPlatformRoutingRule *a, const NMPlatformRoutingRule *b)
-{
-    return nm_platform_routing_rule_cmp(a, b, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL);
-}
-
-int nm_platform_qdisc_cmp(const NMPlatformQdisc *a, const NMPlatformQdisc *b);
-int nm_platform_qdisc_cmp_full(const NMPlatformQdisc *a,
-                               const NMPlatformQdisc *b,
-                               gboolean               compare_handle);
-int nm_platform_tfilter_cmp(const NMPlatformTfilter *a, const NMPlatformTfilter *b);
-
-void nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h);
-void nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h);
-void nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h);
-void nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
-                                       NMPlatformIPRouteCmpType  cmp_type,
-                                       NMHashState *             h);
-void nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
-                                       NMPlatformIPRouteCmpType  cmp_type,
-                                       NMHashState *             h);
-void nm_platform_routing_rule_hash_update(const NMPlatformRoutingRule *obj,
-                                          NMPlatformRoutingRuleCmpType cmp_type,
-                                          NMHashState *                h);
-void nm_platform_lnk_bridge_hash_update(const NMPlatformLnkBridge *obj, NMHashState *h);
-void nm_platform_lnk_gre_hash_update(const NMPlatformLnkGre *obj, NMHashState *h);
-void nm_platform_lnk_infiniband_hash_update(const NMPlatformLnkInfiniband *obj, NMHashState *h);
-void nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashState *h);
-void nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h);
-void nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h);
-void nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h);
-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);
-
-void nm_platform_qdisc_hash_update(const NMPlatformQdisc *obj, NMHashState *h);
-void nm_platform_tfilter_hash_update(const NMPlatformTfilter *obj, NMHashState *h);
-
-#define NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN ((gsize) 162)
-
-const char *nm_platform_link_flags2str(unsigned flags, char *buf, gsize len);
-const char *nm_platform_link_inet6_addrgenmode2str(guint8 mode, char *buf, gsize len);
-const char *nm_platform_addr_flags2str(unsigned flags, char *buf, gsize len);
-const char *nm_platform_route_scope2str(int scope, char *buf, gsize len);
-
-int nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b);
-
-gboolean nm_platform_ethtool_set_wake_on_lan(NMPlatform *             self,
-                                             int                      ifindex,
-                                             _NMSettingWiredWakeOnLan wol,
-                                             const char *             wol_password);
-gboolean nm_platform_ethtool_set_link_settings(NMPlatform *             self,
-                                               int                      ifindex,
-                                               gboolean                 autoneg,
-                                               guint32                  speed,
-                                               NMPlatformLinkDuplexType duplex);
-gboolean nm_platform_ethtool_get_link_settings(NMPlatform *              self,
-                                               int                       ifindex,
-                                               gboolean *                out_autoneg,
-                                               guint32 *                 out_speed,
-                                               NMPlatformLinkDuplexType *out_duplex);
-
-NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex);
-gboolean                nm_platform_ethtool_set_features(
-                   NMPlatform *                  self,
-                   int                           ifindex,
-                   const NMEthtoolFeatureStates *features,
-                   const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
-                   gboolean            do_set /* or reset */);
-
-gboolean nm_platform_ethtool_get_link_coalesce(NMPlatform *            self,
-                                               int                     ifindex,
-                                               NMEthtoolCoalesceState *coalesce);
-
-gboolean nm_platform_ethtool_set_coalesce(NMPlatform *                  self,
-                                          int                           ifindex,
-                                          const NMEthtoolCoalesceState *coalesce);
-
-gboolean nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring);
-
-gboolean
-nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring);
-
-void nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self,
-                                             int         ifindex,
-                                             GPtrArray * ip4_dev_route_blacklist);
-
-struct _NMDedupMultiIndex *nm_platform_get_multi_idx(NMPlatform *self);
-
-/*****************************************************************************/
-
-gboolean nm_platform_ip6_address_match(const NMPlatformIP6Address *addr,
-                                       NMPlatformMatchFlags        match_flag);
-
-#endif /* __NETWORKMANAGER_PLATFORM_H__ */
diff --git a/src/core/platform/nmp-object.c b/src/core/platform/nmp-object.c
deleted file mode 100644
index 6a26595e..00000000
--- a/src/core/platform/nmp-object.c
+++ /dev/null
@@ -1,3457 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2015 - 2018 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nmp-object.h"
-
-#include <unistd.h>
-#include <linux/rtnetlink.h>
-#include <linux/if.h>
-#include <libudev.h>
-
-#include "nm-utils.h"
-#include "nm-glib-aux/nm-secret-utils.h"
-
-#include "nm-core-utils.h"
-#include "nm-platform/nm-platform-utils.h"
-
-#include "wifi/nm-wifi-utils.h"
-#include "wpan/nm-wpan-utils.h"
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN LOGD_PLATFORM
-#define _NMLOG(level, obj, ...)                                               \
-    G_STMT_START                                                              \
-    {                                                                         \
-        const NMLogLevel __level = (level);                                   \
-                                                                              \
-        if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) {                     \
-            const NMPObject *const __obj = (obj);                             \
-                                                                              \
-            _nm_log(__level,                                                  \
-                    _NMLOG_DOMAIN,                                            \
-                    0,                                                        \
-                    NULL,                                                     \
-                    NULL,                                                     \
-                    "nmp-object[%p/%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                    __obj,                                                    \
-                    (__obj ? NMP_OBJECT_GET_CLASS(__obj)->obj_type_name       \
-                           : "???") _NM_UTILS_MACRO_REST(__VA_ARGS__));       \
-        }                                                                     \
-    }                                                                         \
-    G_STMT_END
-
-/*****************************************************************************/
-
-typedef struct {
-    NMDedupMultiIdxType parent;
-    NMPCacheIdType      cache_id_type;
-} DedupMultiIdxType;
-
-struct _NMPCache {
-    /* the cache contains only one hash table for all object types, and similarly
-     * it contains only one NMMultiIndex.
-     * This works, because different object types don't ever compare equal and
-     * because their index ids also don't overlap.
-     *
-     * For routes and addresses, the cache contains an address if (and only if) the
-     * object was reported via netlink.
-     * For links, the cache contain a link if it was reported by either netlink
-     * or udev. That means, a link object can be alive, even if it was already
-     * removed via netlink.
-     *
-     * This effectively merges the udev-device cache into the NMPCache.
-     */
-
-    NMDedupMultiIndex *multi_idx;
-
-    /* an idx_type entry for each NMP_CACHE_ID_TYPE. Note that NONE (zero)
-     * is skipped, so the index is shifted by one: idx_type[cache_id_type - 1].
-     *
-     * Don't bother, use _idx_type_get() instead! */
-    DedupMultiIdxType idx_types[NMP_CACHE_ID_TYPE_MAX];
-
-    gboolean use_udev;
-};
-
-/*****************************************************************************/
-
-int
-nm_sock_addr_union_cmp(const NMSockAddrUnion *a, const NMSockAddrUnion *b)
-{
-    nm_assert(!a || NM_IN_SET(a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
-    nm_assert(!b || NM_IN_SET(b->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    NM_CMP_SELF(a, b);
-
-    NM_CMP_FIELD(a, b, sa.sa_family);
-    switch (a->sa.sa_family) {
-    case AF_INET:
-        NM_CMP_DIRECT(ntohl(a->in.sin_addr.s_addr), ntohl(b->in.sin_addr.s_addr));
-        NM_CMP_DIRECT(htons(a->in.sin_port), htons(b->in.sin_port));
-        break;
-    case AF_INET6:
-        NM_CMP_DIRECT_IN6ADDR(&a->in6.sin6_addr, &b->in6.sin6_addr);
-        NM_CMP_DIRECT(htons(a->in6.sin6_port), htons(b->in6.sin6_port));
-        NM_CMP_FIELD(a, b, in6.sin6_scope_id);
-        NM_CMP_FIELD(a, b, in6.sin6_flowinfo);
-        break;
-    }
-    return 0;
-}
-
-void
-nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h)
-{
-    if (!a) {
-        nm_hash_update_val(h, 1241364739u);
-        return;
-    }
-
-    nm_assert(NM_IN_SET(a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    switch (a->sa.sa_family) {
-    case AF_INET:
-        nm_hash_update_vals(h, a->in.sin_family, a->in.sin_addr.s_addr, a->in.sin_port);
-        return;
-    case AF_INET6:
-        nm_hash_update_vals(h,
-                            a->in6.sin6_family,
-                            a->in6.sin6_addr,
-                            a->in6.sin6_port,
-                            a->in6.sin6_scope_id,
-                            a->in6.sin6_flowinfo);
-        return;
-    default:
-        nm_hash_update_val(h, a->sa.sa_family);
-        return;
-    }
-}
-
-/**
- * nm_sock_addr_union_cpy:
- * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
- *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
- *   error).
- * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in
- *   memory. If not %NULL, the buffer must be at least large enough to contain
- *   sa.sa_family, and then, depending on sa.sa_family, it must be large enough
- *   to hold struct sockaddr_in or struct sockaddr_in6.
- *
- * @dst will always be fully initialized (including setting all un-used bytes to zero).
- */
-void
-nm_sock_addr_union_cpy(NMSockAddrUnion *dst,
-                       gconstpointer    src /* unaligned (const NMSockAddrUnion *) */)
-{
-    struct sockaddr sa;
-    gsize           src_len;
-
-    nm_assert(dst);
-
-    *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
-
-    if (!src)
-        return;
-
-    memcpy(&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof(sa.sa_family));
-
-    if (sa.sa_family == AF_INET)
-        src_len = sizeof(struct sockaddr_in);
-    else if (sa.sa_family == AF_INET6)
-        src_len = sizeof(struct sockaddr_in6);
-    else
-        return;
-
-    memcpy(dst, src, src_len);
-    nm_assert(dst->sa.sa_family == sa.sa_family);
-}
-
-/**
- * nm_sock_addr_union_cpy_untrusted:
- * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
- *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
- *   error).
- * @src: the source buffer with an sockaddr to copy. It may be unaligned in
- *   memory.
- * @src_len: the length of @src in bytes.
- *
- * The function requires @src_len to be either sizeof(struct sockaddr_in) or sizeof (struct sockaddr_in6).
- * If that's the case, then @src will be interpreted as such structure (unaligned), and
- * accessed. It will check sa.sa_family to match the expected sizes, and if it does, the
- * struct will be copied.
- *
- * On any failure, @dst will be set to sa.sa_family AF_UNSPEC.
- * @dst will always be fully initialized (including setting all un-used bytes to zero).
- */
-void
-nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst,
-                                 gconstpointer    src /* unaligned (const NMSockAddrUnion *) */,
-                                 gsize            src_len)
-{
-    int             f_expected;
-    struct sockaddr sa;
-
-    nm_assert(dst);
-
-    *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
-
-    if (src_len == sizeof(struct sockaddr_in))
-        f_expected = AF_INET;
-    else if (src_len == sizeof(struct sockaddr_in6))
-        f_expected = AF_INET6;
-    else
-        return;
-
-    memcpy(&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof(sa.sa_family));
-
-    if (sa.sa_family != f_expected)
-        return;
-
-    memcpy(dst, src, src_len);
-    nm_assert(dst->sa.sa_family == sa.sa_family);
-}
-
-const char *
-nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len)
-{
-    char s_addr[NM_UTILS_INET_ADDRSTRLEN];
-    char s_scope_id[40];
-
-    if (!nm_utils_to_string_buffer_init_null(sa, &buf, &len))
-        return buf;
-
-    /* maybe we should use getnameinfo(), but here implement it ourself.
-     *
-     * We want to see the actual bytes for debugging (as we understand them),
-     * and now what getnameinfo() makes of it. Also, it's simpler this way. */
-
-    switch (sa->sa.sa_family) {
-    case AF_INET:
-        g_snprintf(buf,
-                   len,
-                   "%s:%u",
-                   _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),
-                   (sa->in6.sin6_scope_id != 0
-                        ? nm_sprintf_buf(s_scope_id, "%u", sa->in6.sin6_scope_id)
-                        : ""),
-                   (guint) htons(sa->in6.sin6_port));
-        break;
-    case AF_UNSPEC:
-        g_snprintf(buf, len, "unspec");
-        break;
-    default:
-        g_snprintf(buf, len, "{addr-family:%u}", (unsigned) sa->sa.sa_family);
-        break;
-    }
-
-    return buf;
-}
-
-/*****************************************************************************/
-
-static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class;
-
-static void
-_idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type,
-                        const NMDedupMultiObj *    obj,
-                        NMHashState *              h)
-{
-    const NMPObject *o = (NMPObject *) obj;
-
-    nm_assert(idx_type && idx_type->klass == &_dedup_multi_idx_type_class);
-    nm_assert(NMP_OBJECT_GET_TYPE(o) != NMP_OBJECT_TYPE_UNKNOWN);
-
-    nmp_object_id_hash_update(o, h);
-}
-
-static gboolean
-_idx_obj_id_equal(const NMDedupMultiIdxType *idx_type,
-                  const NMDedupMultiObj *    obj_a,
-                  const NMDedupMultiObj *    obj_b)
-{
-    const NMPObject *o_a = (NMPObject *) obj_a;
-    const NMPObject *o_b = (NMPObject *) obj_b;
-
-    nm_assert(idx_type && idx_type->klass == &_dedup_multi_idx_type_class);
-    nm_assert(NMP_OBJECT_GET_TYPE(o_a) != NMP_OBJECT_TYPE_UNKNOWN);
-    nm_assert(NMP_OBJECT_GET_TYPE(o_b) != NMP_OBJECT_TYPE_UNKNOWN);
-
-    return nmp_object_id_equal(o_a, o_b);
-}
-
-static guint
-_idx_obj_part(const DedupMultiIdxType *idx_type,
-              const NMPObject *        obj_a,
-              const NMPObject *        obj_b,
-              NMHashState *            h)
-{
-    NMPObjectType obj_type;
-
-    /* the hash/equals functions are strongly related. So, keep them
-     * side-by-side and do it all in _idx_obj_part(). */
-
-    nm_assert(idx_type);
-    nm_assert(idx_type->parent.klass == &_dedup_multi_idx_type_class);
-    nm_assert(obj_a);
-    nm_assert(NMP_OBJECT_GET_TYPE(obj_a) != NMP_OBJECT_TYPE_UNKNOWN);
-    nm_assert(!obj_b || (NMP_OBJECT_GET_TYPE(obj_b) != NMP_OBJECT_TYPE_UNKNOWN));
-    nm_assert(!h || !obj_b);
-
-    switch (idx_type->cache_id_type) {
-    case NMP_CACHE_ID_TYPE_OBJECT_TYPE:
-        if (obj_b)
-            return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b);
-        if (h) {
-            nm_hash_update_vals(h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE(obj_a));
-        }
-        return 1;
-
-    case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME:
-        if (NMP_OBJECT_GET_TYPE(obj_a) != NMP_OBJECT_TYPE_LINK) {
-            /* first check, whether obj_a is suitable for this idx_type.
-             * If not, return 0 (which is correct for partitionable(), hash() and equal()
-             * functions. */
-            if (h)
-                nm_hash_update_val(h, obj_a);
-            return 0;
-        }
-        if (obj_b) {
-            /* we are in equal() mode. Compare obj_b with obj_a. */
-            return NMP_OBJECT_GET_TYPE(obj_b) == NMP_OBJECT_TYPE_LINK
-                   && nm_streq(obj_a->link.name, obj_b->link.name);
-        }
-        if (h) {
-            nm_hash_update_val(h, idx_type->cache_id_type);
-            nm_hash_update_strarr(h, obj_a->link.name);
-        }
-        /* just return 1, to indicate that obj_a is partitionable by this idx_type. */
-        return 1;
-
-    case NMP_CACHE_ID_TYPE_DEFAULT_ROUTES:
-        if (!NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a),
-                       NMP_OBJECT_TYPE_IP4_ROUTE,
-                       NMP_OBJECT_TYPE_IP6_ROUTE)
-            || !NM_PLATFORM_IP_ROUTE_IS_DEFAULT(&obj_a->ip_route)
-            || !nmp_object_is_visible(obj_a)) {
-            if (h)
-                nm_hash_update_val(h, obj_a);
-            return 0;
-        }
-        if (obj_b) {
-            return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b)
-                   && NM_PLATFORM_IP_ROUTE_IS_DEFAULT(&obj_b->ip_route)
-                   && nmp_object_is_visible(obj_b);
-        }
-        if (h) {
-            nm_hash_update_vals(h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE(obj_a));
-        }
-        return 1;
-
-    case NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX:
-        if (!NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_a),
-                       NMP_OBJECT_TYPE_IP4_ADDRESS,
-                       NMP_OBJECT_TYPE_IP6_ADDRESS,
-                       NMP_OBJECT_TYPE_IP4_ROUTE,
-                       NMP_OBJECT_TYPE_IP6_ROUTE,
-                       NMP_OBJECT_TYPE_QDISC,
-                       NMP_OBJECT_TYPE_TFILTER)
-            || !nmp_object_is_visible(obj_a)) {
-            if (h)
-                nm_hash_update_val(h, obj_a);
-            return 0;
-        }
-        nm_assert(NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex > 0);
-        if (obj_b) {
-            return NMP_OBJECT_GET_TYPE(obj_a) == NMP_OBJECT_GET_TYPE(obj_b)
-                   && NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_a)->ifindex
-                          == NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj_b)->ifindex
-                   && nmp_object_is_visible(obj_b);
-        }
-        if (h) {
-            nm_hash_update_vals(h, idx_type->cache_id_type, obj_a->obj_with_ifindex.ifindex);
-        }
-        return 1;
-
-    case NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID:
-        obj_type = NMP_OBJECT_GET_TYPE(obj_a);
-        if (!NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
-            || NMP_OBJECT_CAST_IP_ROUTE(obj_a)->ifindex <= 0) {
-            if (h)
-                nm_hash_update_val(h, obj_a);
-            return 0;
-        }
-        if (obj_b) {
-            return obj_type == NMP_OBJECT_GET_TYPE(obj_b)
-                   && NMP_OBJECT_CAST_IP_ROUTE(obj_b)->ifindex > 0
-                   && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
-                           ? (nm_platform_ip4_route_cmp(&obj_a->ip4_route,
-                                                        &obj_b->ip4_route,
-                                                        NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID)
-                              == 0)
-                           : (nm_platform_ip6_route_cmp(&obj_a->ip6_route,
-                                                        &obj_b->ip6_route,
-                                                        NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID)
-                              == 0));
-        }
-        if (h) {
-            nm_hash_update_val(h, idx_type->cache_id_type);
-            if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE)
-                nm_platform_ip4_route_hash_update(&obj_a->ip4_route,
-                                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID,
-                                                  h);
-            else
-                nm_platform_ip6_route_hash_update(&obj_a->ip6_route,
-                                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID,
-                                                  h);
-        }
-        return 1;
-
-    case NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY:
-        obj_type = NMP_OBJECT_GET_TYPE(obj_a);
-        /* currently, only routing rules are supported for this cache-id-type. */
-        if (obj_type != NMP_OBJECT_TYPE_ROUTING_RULE
-            || !NM_IN_SET(obj_a->routing_rule.addr_family, AF_INET, AF_INET6)) {
-            if (h)
-                nm_hash_update_val(h, obj_a);
-            return 0;
-        }
-        if (obj_b) {
-            return NMP_OBJECT_GET_TYPE(obj_b) == NMP_OBJECT_TYPE_ROUTING_RULE
-                   && obj_a->routing_rule.addr_family == obj_b->routing_rule.addr_family;
-        }
-        if (h) {
-            nm_hash_update_vals(h, idx_type->cache_id_type, obj_a->routing_rule.addr_family);
-        }
-        return 1;
-
-    case NMP_CACHE_ID_TYPE_NONE:
-    case __NMP_CACHE_ID_TYPE_MAX:
-        break;
-    }
-    nm_assert_not_reached();
-    return 0;
-}
-
-static gboolean
-_idx_obj_partitionable(const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj)
-{
-    return _idx_obj_part((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, NULL) != 0;
-}
-
-static void
-_idx_obj_partition_hash_update(const NMDedupMultiIdxType *idx_type,
-                               const NMDedupMultiObj *    obj,
-                               NMHashState *              h)
-{
-    _idx_obj_part((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, h);
-}
-
-static gboolean
-_idx_obj_partition_equal(const NMDedupMultiIdxType *idx_type,
-                         const NMDedupMultiObj *    obj_a,
-                         const NMDedupMultiObj *    obj_b)
-{
-    return _idx_obj_part((DedupMultiIdxType *) idx_type,
-                         (NMPObject *) obj_a,
-                         (NMPObject *) obj_b,
-                         NULL);
-}
-
-static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class = {
-    .idx_obj_id_hash_update        = _idx_obj_id_hash_update,
-    .idx_obj_id_equal              = _idx_obj_id_equal,
-    .idx_obj_partitionable         = _idx_obj_partitionable,
-    .idx_obj_partition_hash_update = _idx_obj_partition_hash_update,
-    .idx_obj_partition_equal       = _idx_obj_partition_equal,
-};
-
-static void
-_dedup_multi_idx_type_init(DedupMultiIdxType *idx_type, NMPCacheIdType cache_id_type)
-{
-    nm_dedup_multi_idx_type_init((NMDedupMultiIdxType *) idx_type, &_dedup_multi_idx_type_class);
-    idx_type->cache_id_type = cache_id_type;
-}
-
-/*****************************************************************************/
-
-static void
-_vlan_xgress_qos_mappings_hash_update(guint n_map, const NMVlanQosMapping *map, NMHashState *h)
-{
-    /* ensure no padding. */
-    G_STATIC_ASSERT(sizeof(NMVlanQosMapping) == 2 * sizeof(guint32));
-
-    nm_hash_update_val(h, n_map);
-    if (n_map)
-        nm_hash_update(h, map, n_map * sizeof(*map));
-}
-
-static int
-_vlan_xgress_qos_mappings_cmp(guint                   n_map,
-                              const NMVlanQosMapping *map1,
-                              const NMVlanQosMapping *map2)
-{
-    guint i;
-
-    for (i = 0; i < n_map; i++) {
-        if (map1[i].from != map2[i].from)
-            return map1[i].from < map2[i].from ? -1 : 1;
-        if (map1[i].to != map2[i].to)
-            return map1[i].to < map2[i].to ? -1 : 1;
-    }
-    return 0;
-}
-
-static void
-_vlan_xgress_qos_mappings_cpy(guint *                 dst_n_map,
-                              NMVlanQosMapping **     dst_map,
-                              guint                   src_n_map,
-                              const NMVlanQosMapping *src_map)
-{
-    if (src_n_map == 0) {
-        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) {
-        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);
-    }
-}
-
-/*****************************************************************************/
-
-static void
-_wireguard_allowed_ip_hash_update(const NMPWireGuardAllowedIP *ip, NMHashState *h)
-{
-    nm_hash_update_vals(h, ip->family, ip->mask);
-
-    if (ip->family == AF_INET)
-        nm_hash_update_val(h, ip->addr.addr4);
-    else if (ip->family == AF_INET6)
-        nm_hash_update_val(h, ip->addr.addr6);
-}
-
-static int
-_wireguard_allowed_ip_cmp(const NMPWireGuardAllowedIP *a, const NMPWireGuardAllowedIP *b)
-{
-    NM_CMP_SELF(a, b);
-
-    NM_CMP_FIELD(a, b, family);
-    NM_CMP_FIELD(a, b, mask);
-
-    if (a->family == AF_INET)
-        NM_CMP_FIELD(a, b, addr.addr4);
-    else if (a->family == AF_INET6)
-        NM_CMP_FIELD_IN6ADDR(a, b, addr.addr6);
-
-    return 0;
-}
-
-static void
-_wireguard_peer_hash_update(const NMPWireGuardPeer *peer, NMHashState *h)
-{
-    guint i;
-
-    nm_hash_update(h, peer->public_key, sizeof(peer->public_key));
-    nm_hash_update(h, peer->preshared_key, sizeof(peer->preshared_key));
-    nm_hash_update_vals(h,
-                        peer->persistent_keepalive_interval,
-                        peer->allowed_ips_len,
-                        peer->rx_bytes,
-                        peer->tx_bytes,
-                        peer->last_handshake_time.tv_sec,
-                        peer->last_handshake_time.tv_nsec);
-
-    nm_sock_addr_union_hash_update(&peer->endpoint, h);
-
-    for (i = 0; i < peer->allowed_ips_len; i++)
-        _wireguard_allowed_ip_hash_update(&peer->allowed_ips[i], h);
-}
-
-static int
-_wireguard_peer_cmp(const NMPWireGuardPeer *a, const NMPWireGuardPeer *b)
-{
-    guint i;
-
-    NM_CMP_SELF(a, b);
-
-    NM_CMP_FIELD(a, b, last_handshake_time.tv_sec);
-    NM_CMP_FIELD(a, b, last_handshake_time.tv_nsec);
-    NM_CMP_FIELD(a, b, rx_bytes);
-    NM_CMP_FIELD(a, b, tx_bytes);
-    NM_CMP_FIELD(a, b, allowed_ips_len);
-    NM_CMP_FIELD(a, b, persistent_keepalive_interval);
-    NM_CMP_FIELD(a, b, endpoint.sa.sa_family);
-    NM_CMP_FIELD_MEMCMP(a, b, public_key);
-    NM_CMP_FIELD_MEMCMP(a, b, preshared_key);
-
-    NM_CMP_RETURN(nm_sock_addr_union_cmp(&a->endpoint, &b->endpoint));
-
-    for (i = 0; i < a->allowed_ips_len; i++) {
-        NM_CMP_RETURN(_wireguard_allowed_ip_cmp(&a->allowed_ips[i], &b->allowed_ips[i]));
-    }
-
-    return 0;
-}
-
-/*****************************************************************************/
-
-static const char *
-_link_get_driver(struct udev_device *udevice, const char *kind, int ifindex)
-{
-    const char *driver = NULL;
-
-    nm_assert(kind == g_intern_string(kind));
-
-    if (udevice) {
-        driver = nmp_utils_udev_get_driver(udevice);
-        if (driver)
-            return driver;
-    }
-
-    if (kind)
-        return kind;
-
-    if (ifindex > 0) {
-        NMPUtilsEthtoolDriverInfo driver_info;
-
-        if (nmp_utils_ethtool_get_driver_info(ifindex, &driver_info)) {
-            if (driver_info.driver[0])
-                return g_intern_string(driver_info.driver);
-        }
-    }
-
-    return "unknown";
-}
-
-void
-_nmp_object_fixup_link_udev_fields(NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev)
-{
-    const char *driver      = NULL;
-    gboolean    initialized = FALSE;
-    NMPObject * obj;
-
-    nm_assert(obj_orig || *obj_new);
-    nm_assert(obj_new);
-    nm_assert(!obj_orig || NMP_OBJECT_GET_TYPE(obj_orig) == NMP_OBJECT_TYPE_LINK);
-    nm_assert(!*obj_new || NMP_OBJECT_GET_TYPE(*obj_new) == NMP_OBJECT_TYPE_LINK);
-
-    obj = *obj_new ?: obj_orig;
-
-    /* The link contains internal fields that are combined by
-     * properties from netlink and udev. Update those properties */
-
-    /* When a link is not in netlink, its udev fields don't matter. */
-    if (obj->_link.netlink.is_in_netlink) {
-        driver = _link_get_driver(obj->_link.udev.device, obj->link.kind, obj->link.ifindex);
-        if (obj->_link.udev.device)
-            initialized = TRUE;
-        else if (!use_udev) {
-            /* If we don't use udev, we immediately mark the link as initialized.
-             *
-             * For that, we consult @use_udev argument, that is cached via
-             * nmp_cache_use_udev_get(). It is on purpose not to test
-             * for a writable /sys on every call. A minor reason for that is
-             * performance, but the real reason is reproducibility.
-             * */
-            initialized = TRUE;
-        }
-    }
-
-    if (nm_streq0(obj->link.driver, driver) && obj->link.initialized == initialized)
-        return;
-
-    if (!*obj_new)
-        obj = *obj_new = nmp_object_clone(obj, FALSE);
-
-    obj->link.driver      = driver;
-    obj->link.initialized = initialized;
-}
-
-static void
-_nmp_object_fixup_link_master_connected(NMPObject **    obj_new,
-                                        NMPObject *     obj_orig,
-                                        const NMPCache *cache)
-{
-    NMPObject *obj;
-
-    nm_assert(obj_orig || *obj_new);
-    nm_assert(obj_new);
-    nm_assert(!obj_orig || NMP_OBJECT_GET_TYPE(obj_orig) == NMP_OBJECT_TYPE_LINK);
-    nm_assert(!*obj_new || NMP_OBJECT_GET_TYPE(*obj_new) == NMP_OBJECT_TYPE_LINK);
-
-    obj = *obj_new ?: obj_orig;
-
-    if (nmp_cache_link_connected_needs_toggle(cache, obj, NULL, NULL)) {
-        if (!*obj_new)
-            obj = *obj_new = nmp_object_clone(obj, FALSE);
-        obj->link.connected = !obj->link.connected;
-    }
-}
-
-/*****************************************************************************/
-
-static void
-_vt_cmd_obj_dispose_link(NMPObject *obj)
-{
-    if (obj->_link.udev.device) {
-        udev_device_unref(obj->_link.udev.device);
-        obj->_link.udev.device = NULL;
-    }
-    g_clear_object(&obj->_link.ext_data);
-    nmp_object_unref(obj->_link.netlink.lnk);
-}
-
-static void
-_vt_cmd_obj_dispose_lnk_vlan(NMPObject *obj)
-{
-    g_free((gpointer) obj->_lnk_vlan.ingress_qos_map);
-    g_free((gpointer) obj->_lnk_vlan.egress_qos_map);
-}
-
-static void
-_wireguard_clear(NMPObjectLnkWireGuard *lnk)
-{
-    guint i;
-
-    nm_explicit_bzero(lnk->_public.private_key, sizeof(lnk->_public.private_key));
-    for (i = 0; i < lnk->peers_len; i++) {
-        NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &lnk->peers[i];
-
-        nm_explicit_bzero(peer->preshared_key, sizeof(peer->preshared_key));
-    }
-    g_free((gpointer) lnk->peers);
-    g_free((gpointer) lnk->_allowed_ips_buf);
-}
-
-static void
-_vt_cmd_obj_dispose_lnk_wireguard(NMPObject *obj)
-{
-    _wireguard_clear(&obj->_lnk_wireguard);
-}
-
-static NMPObject *
-_nmp_object_new_from_class(const NMPClass *klass)
-{
-    NMPObject *obj;
-
-    nm_assert(klass);
-    nm_assert(klass->sizeof_data > 0);
-    nm_assert(klass->sizeof_public > 0 && klass->sizeof_public <= klass->sizeof_data);
-
-    obj         = g_slice_alloc0(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object));
-    obj->_class = klass;
-    obj->parent._ref_count = 1;
-    return obj;
-}
-
-NMPObject *
-nmp_object_new(NMPObjectType obj_type, gconstpointer plobj)
-{
-    const NMPClass *klass = nmp_class_from_type(obj_type);
-    NMPObject *     obj;
-
-    obj = _nmp_object_new_from_class(klass);
-    if (plobj)
-        memcpy(&obj->object, plobj, klass->sizeof_public);
-    return obj;
-}
-
-NMPObject *
-nmp_object_new_link(int ifindex)
-{
-    NMPObject *obj;
-
-    obj               = nmp_object_new(NMP_OBJECT_TYPE_LINK, NULL);
-    obj->link.ifindex = ifindex;
-    return obj;
-}
-
-/*****************************************************************************/
-
-static void
-_nmp_object_stackinit_from_class(NMPObject *obj, const NMPClass *klass)
-{
-    nm_assert(obj);
-    nm_assert(klass);
-
-    *obj = (NMPObject){
-        .parent =
-            {
-                .klass      = (const NMDedupMultiObjClass *) klass,
-                ._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
-            },
-    };
-}
-
-static NMPObject *
-_nmp_object_stackinit_from_type(NMPObject *obj, NMPObjectType obj_type)
-{
-    const NMPClass *klass;
-
-    nm_assert(obj);
-    klass = nmp_class_from_type(obj_type);
-    nm_assert(klass);
-
-    *obj = (NMPObject){
-        .parent =
-            {
-                .klass      = (const NMDedupMultiObjClass *) klass,
-                ._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
-            },
-    };
-    return obj;
-}
-
-const NMPObject *
-nmp_object_stackinit(NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj)
-{
-    const NMPClass *klass = nmp_class_from_type(obj_type);
-
-    _nmp_object_stackinit_from_class(obj, klass);
-    if (plobj)
-        memcpy(&obj->object, plobj, klass->sizeof_public);
-    return obj;
-}
-
-const NMPObject *
-nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src)
-{
-    const NMPClass *klass;
-
-    nm_assert(NMP_OBJECT_IS_VALID(src));
-    nm_assert(obj);
-
-    klass = NMP_OBJECT_GET_CLASS(src);
-    _nmp_object_stackinit_from_class(obj, klass);
-    if (klass->cmd_plobj_id_copy)
-        klass->cmd_plobj_id_copy(&obj->object, &src->object);
-    return obj;
-}
-
-const NMPObject *
-nmp_object_stackinit_id_link(NMPObject *obj, int ifindex)
-{
-    _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_LINK);
-    obj->link.ifindex = ifindex;
-    return obj;
-}
-
-const NMPObject *
-nmp_object_stackinit_id_ip4_address(NMPObject *obj,
-                                    int        ifindex,
-                                    guint32    address,
-                                    guint8     plen,
-                                    guint32    peer_address)
-{
-    _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP4_ADDRESS);
-    obj->ip4_address.ifindex      = ifindex;
-    obj->ip4_address.address      = address;
-    obj->ip4_address.plen         = plen;
-    obj->ip4_address.peer_address = peer_address;
-    return obj;
-}
-
-const NMPObject *
-nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address)
-{
-    _nmp_object_stackinit_from_type(obj, NMP_OBJECT_TYPE_IP6_ADDRESS);
-    obj->ip4_address.ifindex = ifindex;
-    if (address)
-        obj->ip6_address.address = *address;
-    return obj;
-}
-
-/*****************************************************************************/
-
-const char *
-nmp_object_to_string(const NMPObject *     obj,
-                     NMPObjectToStringMode to_string_mode,
-                     char *                buf,
-                     gsize                 buf_size)
-{
-    const NMPClass *klass;
-    char            buf2[sizeof(_nm_utils_to_string_buffer)];
-
-    if (!nm_utils_to_string_buffer_init_null(obj, &buf, &buf_size))
-        return buf;
-
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL);
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    if (klass->cmd_obj_to_string)
-        return klass->cmd_obj_to_string(obj, to_string_mode, buf, buf_size);
-
-    switch (to_string_mode) {
-    case NMP_OBJECT_TO_STRING_ID:
-        if (!klass->cmd_plobj_to_string_id) {
-            g_snprintf(buf, buf_size, "%p", obj);
-            return buf;
-        }
-        return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size);
-    case NMP_OBJECT_TO_STRING_ALL:
-        g_snprintf(
-            buf,
-            buf_size,
-            "[%s,%p,%u,%calive,%cvisible; %s]",
-            klass->obj_type_name,
-            obj,
-            obj->parent._ref_count,
-            nmp_object_is_alive(obj) ? '+' : '-',
-            nmp_object_is_visible(obj) ? '+' : '-',
-            NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf2, sizeof(buf2)));
-        return buf;
-    case NMP_OBJECT_TO_STRING_PUBLIC:
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size);
-        return buf;
-    default:
-        g_return_val_if_reached("ERROR");
-    }
-}
-
-static const char *
-_vt_cmd_obj_to_string_link(const NMPObject *     obj,
-                           NMPObjectToStringMode to_string_mode,
-                           char *                buf,
-                           gsize                 buf_size)
-{
-    const NMPClass *klass = NMP_OBJECT_GET_CLASS(obj);
-    char *          b     = buf;
-
-    switch (to_string_mode) {
-    case NMP_OBJECT_TO_STRING_ID:
-        return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size);
-    case NMP_OBJECT_TO_STRING_ALL:
-        nm_utils_strbuf_append(&b,
-                               &buf_size,
-                               "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; ",
-                               klass->obj_type_name,
-                               obj,
-                               obj->parent._ref_count,
-                               nmp_object_is_alive(obj) ? '+' : '-',
-                               nmp_object_is_visible(obj) ? '+' : '-',
-                               obj->_link.netlink.is_in_netlink ? '+' : '-',
-                               obj->_link.udev.device);
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size);
-        nm_utils_strbuf_seek_end(&b, &buf_size);
-        if (obj->_link.netlink.lnk) {
-            nm_utils_strbuf_append_str(&b, &buf_size, "; ");
-            nmp_object_to_string(obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_ALL, b, buf_size);
-            nm_utils_strbuf_seek_end(&b, &buf_size);
-        }
-        nm_utils_strbuf_append_c(&b, &buf_size, ']');
-        return buf;
-    case NMP_OBJECT_TO_STRING_PUBLIC:
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size);
-        if (obj->_link.netlink.lnk) {
-            nm_utils_strbuf_seek_end(&b, &buf_size);
-            nm_utils_strbuf_append_str(&b, &buf_size, "; ");
-            nmp_object_to_string(obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, b, buf_size);
-        }
-        return buf;
-    default:
-        g_return_val_if_reached("ERROR");
-    }
-}
-
-static const char *
-_vt_cmd_obj_to_string_lnk_vlan(const NMPObject *     obj,
-                               NMPObjectToStringMode to_string_mode,
-                               char *                buf,
-                               gsize                 buf_size)
-{
-    const NMPClass *klass;
-    char            buf2[sizeof(_nm_utils_to_string_buffer)];
-    char *          b;
-    gsize           l;
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    switch (to_string_mode) {
-    case NMP_OBJECT_TO_STRING_ID:
-        g_snprintf(buf, buf_size, "%p", obj);
-        return buf;
-    case NMP_OBJECT_TO_STRING_ALL:
-
-        g_snprintf(buf,
-                   buf_size,
-                   "[%s,%p,%u,%calive,%cvisible; %s]",
-                   klass->obj_type_name,
-                   obj,
-                   obj->parent._ref_count,
-                   nmp_object_is_alive(obj) ? '+' : '-',
-                   nmp_object_is_visible(obj) ? '+' : '-',
-                   nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2)));
-        return buf;
-    case NMP_OBJECT_TO_STRING_PUBLIC:
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size);
-
-        b = buf;
-        l = strlen(b);
-        b += l;
-        buf_size -= l;
-
-        if (obj->_lnk_vlan.n_ingress_qos_map) {
-            nm_platform_vlan_qos_mapping_to_string(" ingress-qos-map",
-                                                   obj->_lnk_vlan.ingress_qos_map,
-                                                   obj->_lnk_vlan.n_ingress_qos_map,
-                                                   b,
-                                                   buf_size);
-            l = strlen(b);
-            b += l;
-            buf_size -= l;
-        }
-        if (obj->_lnk_vlan.n_egress_qos_map) {
-            nm_platform_vlan_qos_mapping_to_string(" egress-qos-map",
-                                                   obj->_lnk_vlan.egress_qos_map,
-                                                   obj->_lnk_vlan.n_egress_qos_map,
-                                                   b,
-                                                   buf_size);
-            l = strlen(b);
-            b += l;
-            buf_size -= l;
-        }
-
-        return buf;
-    default:
-        g_return_val_if_reached("ERROR");
-    }
-}
-
-static const char *
-_vt_cmd_obj_to_string_lnk_wireguard(const NMPObject *     obj,
-                                    NMPObjectToStringMode to_string_mode,
-                                    char *                buf,
-                                    gsize                 buf_size)
-{
-    const NMPClass *klass;
-    char            buf2[sizeof(_nm_utils_to_string_buffer)];
-    char *          b;
-    guint           i;
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    switch (to_string_mode) {
-    case NMP_OBJECT_TO_STRING_ID:
-        g_snprintf(buf, buf_size, "%p", obj);
-        return buf;
-    case NMP_OBJECT_TO_STRING_ALL:
-        b = buf;
-
-        nm_utils_strbuf_append(
-            &b,
-            &buf_size,
-            "[%s,%p,%u,%calive,%cvisible; %s"
-            "%s",
-            klass->obj_type_name,
-            obj,
-            obj->parent._ref_count,
-            nmp_object_is_alive(obj) ? '+' : '-',
-            nmp_object_is_visible(obj) ? '+' : '-',
-            nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2)),
-            obj->_lnk_wireguard.peers_len > 0 ? " peers {" : "");
-
-        for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) {
-            const NMPWireGuardPeer *peer = &obj->_lnk_wireguard.peers[i];
-
-            nm_utils_strbuf_append_str(&b, &buf_size, " { ");
-            nm_platform_wireguard_peer_to_string(peer, b, buf_size);
-            nm_utils_strbuf_seek_end(&b, &buf_size);
-            nm_utils_strbuf_append_str(&b, &buf_size, " }");
-        }
-        if (obj->_lnk_wireguard.peers_len)
-            nm_utils_strbuf_append_str(&b, &buf_size, " }");
-
-        return buf;
-    case NMP_OBJECT_TO_STRING_PUBLIC:
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size);
-
-        return buf;
-    default:
-        g_return_val_if_reached("ERROR");
-    }
-}
-
-#define _vt_cmd_plobj_to_string_id(type, plat_type, ...)                                  \
-    static const char *_vt_cmd_plobj_to_string_id_##type(const NMPlatformObject *_obj,    \
-                                                         char *                  buf,     \
-                                                         gsize                   buf_len) \
-    {                                                                                     \
-        plat_type *const obj = (plat_type *) _obj;                                        \
-        _nm_unused char  buf1[NM_UTILS_INET_ADDRSTRLEN];                                  \
-        _nm_unused char  buf2[NM_UTILS_INET_ADDRSTRLEN];                                  \
-                                                                                          \
-        g_snprintf(buf, buf_len, __VA_ARGS__);                                            \
-        return buf;                                                                       \
-    }                                                                                     \
-    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
-
-_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,
-                           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));
-
-_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);
-
-void
-nmp_object_hash_update(const NMPObject *obj, NMHashState *h)
-{
-    const NMPClass *klass;
-
-    g_return_if_fail(NMP_OBJECT_IS_VALID(obj));
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    nm_hash_update_val(h, klass->obj_type);
-    if (klass->cmd_obj_hash_update)
-        klass->cmd_obj_hash_update(obj, h);
-    else if (klass->cmd_plobj_hash_update)
-        klass->cmd_plobj_hash_update(&obj->object, h);
-    else
-        nm_hash_update_val(h, obj);
-}
-
-static void
-_vt_cmd_obj_hash_update_link(const NMPObject *obj, NMHashState *h)
-{
-    nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LINK);
-
-    nm_platform_link_hash_update(&obj->link, h);
-    nm_hash_update_vals(h,
-                        obj->_link.netlink.is_in_netlink,
-                        obj->_link.wireguard_family_id,
-                        obj->_link.udev.device);
-    if (obj->_link.netlink.lnk)
-        nmp_object_hash_update(obj->_link.netlink.lnk, h);
-}
-
-static void
-_vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, NMHashState *h)
-{
-    nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_VLAN);
-
-    nm_platform_lnk_vlan_hash_update(&obj->lnk_vlan, h);
-    _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_ingress_qos_map,
-                                          obj->_lnk_vlan.ingress_qos_map,
-                                          h);
-    _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_egress_qos_map,
-                                          obj->_lnk_vlan.egress_qos_map,
-                                          h);
-}
-
-static void
-_vt_cmd_obj_hash_update_lnk_wireguard(const NMPObject *obj, NMHashState *h)
-{
-    guint i;
-
-    nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD);
-
-    nm_platform_lnk_wireguard_hash_update(&obj->lnk_wireguard, h);
-
-    nm_hash_update_val(h, obj->_lnk_wireguard.peers_len);
-    for (i = 0; i < obj->_lnk_wireguard.peers_len; i++)
-        _wireguard_peer_hash_update(&obj->_lnk_wireguard.peers[i], h);
-}
-
-int
-nmp_object_cmp(const NMPObject *obj1, const NMPObject *obj2)
-{
-    const NMPClass *klass1, *klass2;
-
-    NM_CMP_SELF(obj1, obj2);
-
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), -1);
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), 1);
-
-    klass1 = NMP_OBJECT_GET_CLASS(obj1);
-    klass2 = NMP_OBJECT_GET_CLASS(obj2);
-
-    if (klass1 != klass2) {
-        nm_assert(klass1->obj_type != klass2->obj_type);
-        return klass1->obj_type < klass2->obj_type ? -1 : 1;
-    }
-
-    if (klass1->cmd_obj_cmp)
-        return klass1->cmd_obj_cmp(obj1, obj2);
-    return klass1->cmd_plobj_cmp(&obj1->object, &obj2->object);
-}
-
-static int
-_vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2)
-{
-    NM_CMP_RETURN(nm_platform_link_cmp(&obj1->link, &obj2->link));
-    NM_CMP_DIRECT(obj1->_link.netlink.is_in_netlink, obj2->_link.netlink.is_in_netlink);
-    NM_CMP_RETURN(nmp_object_cmp(obj1->_link.netlink.lnk, obj2->_link.netlink.lnk));
-    NM_CMP_DIRECT(obj1->_link.wireguard_family_id, obj2->_link.wireguard_family_id);
-
-    if (obj1->_link.udev.device != obj2->_link.udev.device) {
-        if (!obj1->_link.udev.device)
-            return -1;
-        if (!obj2->_link.udev.device)
-            return 1;
-
-        /* Only compare based on pointer values. That is ugly because it's not a
-         * stable sort order.
-         *
-         * Have this check as very last. */
-        return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1;
-    }
-
-    return 0;
-}
-
-static int
-_vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2)
-{
-    int c;
-
-    c = nm_platform_lnk_vlan_cmp(&obj1->lnk_vlan, &obj2->lnk_vlan);
-    if (c)
-        return c;
-
-    if (obj1->_lnk_vlan.n_ingress_qos_map != obj2->_lnk_vlan.n_ingress_qos_map)
-        return obj1->_lnk_vlan.n_ingress_qos_map < obj2->_lnk_vlan.n_ingress_qos_map ? -1 : 1;
-    if (obj1->_lnk_vlan.n_egress_qos_map != obj2->_lnk_vlan.n_egress_qos_map)
-        return obj1->_lnk_vlan.n_egress_qos_map < obj2->_lnk_vlan.n_egress_qos_map ? -1 : 1;
-
-    c = _vlan_xgress_qos_mappings_cmp(obj1->_lnk_vlan.n_ingress_qos_map,
-                                      obj1->_lnk_vlan.ingress_qos_map,
-                                      obj2->_lnk_vlan.ingress_qos_map);
-    if (c)
-        return c;
-    c = _vlan_xgress_qos_mappings_cmp(obj1->_lnk_vlan.n_egress_qos_map,
-                                      obj1->_lnk_vlan.egress_qos_map,
-                                      obj2->_lnk_vlan.egress_qos_map);
-
-    return c;
-}
-
-static int
-_vt_cmd_obj_cmp_lnk_wireguard(const NMPObject *obj1, const NMPObject *obj2)
-{
-    guint i;
-
-    NM_CMP_RETURN(nm_platform_lnk_wireguard_cmp(&obj1->lnk_wireguard, &obj2->lnk_wireguard));
-
-    NM_CMP_FIELD(obj1, obj2, _lnk_wireguard.peers_len);
-
-    for (i = 0; i < obj1->_lnk_wireguard.peers_len; i++)
-        NM_CMP_RETURN(
-            _wireguard_peer_cmp(&obj1->_lnk_wireguard.peers[i], &obj2->_lnk_wireguard.peers[i]));
-
-    return 0;
-}
-
-/* @src is a const object, which is not entirely correct for link types, where
- * we increase the ref count for src->_link.udev.device.
- * Hence, nmp_object_copy() can violate the const promise of @src.
- * */
-void
-nmp_object_copy(NMPObject *dst, const NMPObject *src, gboolean id_only)
-{
-    g_return_if_fail(NMP_OBJECT_IS_VALID(dst));
-    g_return_if_fail(NMP_OBJECT_IS_VALID(src));
-    g_return_if_fail(!NMP_OBJECT_IS_STACKINIT(dst));
-
-    if (src != dst) {
-        const NMPClass *klass = NMP_OBJECT_GET_CLASS(dst);
-
-        g_return_if_fail(klass == NMP_OBJECT_GET_CLASS(src));
-
-        if (id_only) {
-            if (klass->cmd_plobj_id_copy)
-                klass->cmd_plobj_id_copy(&dst->object, &src->object);
-        } else if (klass->cmd_obj_copy)
-            klass->cmd_obj_copy(dst, src);
-        else
-            memcpy(&dst->object, &src->object, klass->sizeof_data);
-    }
-}
-
-static void
-_vt_cmd_obj_copy_link(NMPObject *dst, const NMPObject *src)
-{
-    if (dst->_link.udev.device != src->_link.udev.device) {
-        if (src->_link.udev.device)
-            udev_device_ref(src->_link.udev.device);
-        if (dst->_link.udev.device)
-            udev_device_unref(dst->_link.udev.device);
-        dst->_link.udev.device = src->_link.udev.device;
-    }
-    if (dst->_link.netlink.lnk != src->_link.netlink.lnk) {
-        if (src->_link.netlink.lnk)
-            nmp_object_ref(src->_link.netlink.lnk);
-        if (dst->_link.netlink.lnk)
-            nmp_object_unref(dst->_link.netlink.lnk);
-        dst->_link.netlink.lnk = src->_link.netlink.lnk;
-    }
-    if (dst->_link.ext_data != src->_link.ext_data) {
-        if (dst->_link.ext_data)
-            g_clear_object(&dst->_link.ext_data);
-        if (src->_link.ext_data)
-            dst->_link.ext_data = g_object_ref(src->_link.ext_data);
-    }
-    dst->_link = src->_link;
-}
-
-static void
-_vt_cmd_obj_copy_lnk_vlan(NMPObject *dst, const NMPObject *src)
-{
-    dst->lnk_vlan = src->lnk_vlan;
-    _vlan_xgress_qos_mappings_cpy(
-        &dst->_lnk_vlan.n_ingress_qos_map,
-        NM_UNCONST_PPTR(NMVlanQosMapping, &dst->_lnk_vlan.ingress_qos_map),
-        src->_lnk_vlan.n_ingress_qos_map,
-        src->_lnk_vlan.ingress_qos_map);
-    _vlan_xgress_qos_mappings_cpy(&dst->_lnk_vlan.n_egress_qos_map,
-                                  NM_UNCONST_PPTR(NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map),
-                                  src->_lnk_vlan.n_egress_qos_map,
-                                  src->_lnk_vlan.egress_qos_map);
-}
-
-static void
-_vt_cmd_obj_copy_lnk_wireguard(NMPObject *dst, const NMPObject *src)
-{
-    guint i;
-
-    nm_assert(dst != src);
-
-    _wireguard_clear(&dst->_lnk_wireguard);
-
-    dst->_lnk_wireguard = src->_lnk_wireguard;
-
-    dst->_lnk_wireguard.peers = nm_memdup(dst->_lnk_wireguard.peers,
-                                          sizeof(NMPWireGuardPeer) * dst->_lnk_wireguard.peers_len);
-    dst->_lnk_wireguard._allowed_ips_buf =
-        nm_memdup(dst->_lnk_wireguard._allowed_ips_buf,
-                  sizeof(NMPWireGuardAllowedIP) * dst->_lnk_wireguard._allowed_ips_buf_len);
-
-    /* all the peers' pointers point into the buffer. They need to be readjusted. */
-    for (i = 0; i < dst->_lnk_wireguard.peers_len; i++) {
-        NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &dst->_lnk_wireguard.peers[i];
-
-        if (peer->allowed_ips_len == 0) {
-            nm_assert(!peer->allowed_ips);
-            continue;
-        }
-        nm_assert(dst->_lnk_wireguard._allowed_ips_buf_len > 0);
-        nm_assert(src->_lnk_wireguard._allowed_ips_buf);
-        nm_assert(peer->allowed_ips >= src->_lnk_wireguard._allowed_ips_buf);
-        nm_assert(
-            &peer->allowed_ips[peer->allowed_ips_len]
-            <= &src->_lnk_wireguard._allowed_ips_buf[src->_lnk_wireguard._allowed_ips_buf_len]);
-
-        peer->allowed_ips =
-            &dst->_lnk_wireguard
-                 ._allowed_ips_buf[peer->allowed_ips - src->_lnk_wireguard._allowed_ips_buf];
-    }
-
-    nm_assert(nmp_object_equal(src, dst));
-}
-
-#define _vt_cmd_plobj_id_copy(type, plat_type, cmd)                                                \
-    static void _vt_cmd_plobj_id_copy_##type(NMPlatformObject *_dst, const NMPlatformObject *_src) \
-    {                                                                                              \
-        plat_type *const       dst = (plat_type *) _dst;                                           \
-        const plat_type *const src = (const plat_type *) _src;                                     \
-        {                                                                                          \
-            cmd                                                                                    \
-        }                                                                                          \
-    }                                                                                              \
-    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
-
-_vt_cmd_plobj_id_copy(link, NMPlatformLink, { dst->ifindex = src->ifindex; });
-
-_vt_cmd_plobj_id_copy(ip4_address, NMPlatformIP4Address, {
-    dst->ifindex      = src->ifindex;
-    dst->plen         = src->plen;
-    dst->address      = src->address;
-    dst->peer_address = src->peer_address;
-});
-
-_vt_cmd_plobj_id_copy(ip6_address, NMPlatformIP6Address, {
-    dst->ifindex = src->ifindex;
-    dst->address = src->address;
-});
-
-_vt_cmd_plobj_id_copy(ip4_route, NMPlatformIP4Route, {
-    *dst = *src;
-    nm_assert(nm_platform_ip4_route_cmp(dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0);
-});
-
-_vt_cmd_plobj_id_copy(ip6_route, NMPlatformIP6Route, {
-    *dst = *src;
-    nm_assert(nm_platform_ip6_route_cmp(dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0);
-});
-
-_vt_cmd_plobj_id_copy(routing_rule, NMPlatformRoutingRule, {
-    *dst = *src;
-    nm_assert(nm_platform_routing_rule_cmp(dst, src, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0);
-});
-
-/* Uses internally nmp_object_copy(), hence it also violates the const
- * promise for @obj.
- * */
-NMPObject *
-nmp_object_clone(const NMPObject *obj, gboolean id_only)
-{
-    NMPObject *dst;
-
-    if (!obj)
-        return NULL;
-
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL);
-
-    dst = _nmp_object_new_from_class(NMP_OBJECT_GET_CLASS(obj));
-    nmp_object_copy(dst, obj, id_only);
-    return dst;
-}
-
-int
-nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2)
-{
-    const NMPClass *klass, *klass2;
-
-    NM_CMP_SELF(obj1, obj2);
-
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), FALSE);
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), FALSE);
-
-    klass = NMP_OBJECT_GET_CLASS(obj1);
-    nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp);
-
-    klass2 = NMP_OBJECT_GET_CLASS(obj2);
-    nm_assert(klass);
-    if (klass != klass2) {
-        nm_assert(klass2);
-        NM_CMP_DIRECT(klass->obj_type, klass2->obj_type);
-        /* resort to pointer comparison */
-        NM_CMP_DIRECT_PTR(klass, klass2);
-        return 0;
-    }
-
-    if (!klass->cmd_plobj_id_cmp) {
-        /* the klass doesn't implement ID cmp(). That means, different objects
-         * never compare equal, but the cmp() according to their pointer value. */
-        NM_CMP_DIRECT_PTR(obj1, obj2);
-        return 0;
-    }
-
-    return klass->cmd_plobj_id_cmp(&obj1->object, &obj2->object);
-}
-
-#define _vt_cmd_plobj_id_cmp(type, plat_type, cmd)                        \
-    static int _vt_cmd_plobj_id_cmp_##type(const NMPlatformObject *_obj1, \
-                                           const NMPlatformObject *_obj2) \
-    {                                                                     \
-        const plat_type *const obj1 = (const plat_type *) _obj1;          \
-        const plat_type *const obj2 = (const plat_type *) _obj2;          \
-                                                                          \
-        NM_CMP_SELF(obj1, obj2);                                          \
-        {                                                                 \
-            cmd;                                                          \
-        }                                                                 \
-        return 0;                                                         \
-    }                                                                     \
-    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
-
-_vt_cmd_plobj_id_cmp(link, NMPlatformLink, { NM_CMP_FIELD(obj1, obj2, ifindex); });
-
-_vt_cmd_plobj_id_cmp(ip4_address, NMPlatformIP4Address, {
-    NM_CMP_FIELD(obj1, obj2, ifindex);
-    NM_CMP_FIELD(obj1, obj2, plen);
-    NM_CMP_FIELD(obj1, obj2, address);
-    /* for IPv4 addresses, you can add the same local address with differing peer-address
-     * (IFA_ADDRESS), provided that their net-part differs. */
-    NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(obj1->peer_address, obj2->peer_address, obj1->plen);
-});
-
-_vt_cmd_plobj_id_cmp(ip6_address, NMPlatformIP6Address, {
-    NM_CMP_FIELD(obj1, obj2, ifindex);
-    /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
-    NM_CMP_FIELD_IN6ADDR(obj1, obj2, address);
-});
-
-_vt_cmd_plobj_id_cmp(qdisc, NMPlatformQdisc, {
-    NM_CMP_FIELD(obj1, obj2, ifindex);
-    NM_CMP_FIELD(obj1, obj2, parent);
-});
-
-_vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, {
-    NM_CMP_FIELD(obj1, obj2, ifindex);
-    NM_CMP_FIELD(obj1, obj2, handle);
-});
-
-static int
-_vt_cmd_plobj_id_cmp_ip4_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
-{
-    return nm_platform_ip4_route_cmp((NMPlatformIP4Route *) obj1,
-                                     (NMPlatformIP4Route *) obj2,
-                                     NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
-}
-
-static int
-_vt_cmd_plobj_id_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
-{
-    return nm_platform_ip6_route_cmp((NMPlatformIP6Route *) obj1,
-                                     (NMPlatformIP6Route *) obj2,
-                                     NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
-}
-
-static int
-_vt_cmd_plobj_id_cmp_routing_rule(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
-{
-    return nm_platform_routing_rule_cmp((NMPlatformRoutingRule *) obj1,
-                                        (NMPlatformRoutingRule *) obj2,
-                                        NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID);
-}
-
-void
-nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h)
-{
-    const NMPClass *klass;
-
-    g_return_if_fail(NMP_OBJECT_IS_VALID(obj));
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp);
-
-    if (!klass->cmd_plobj_id_hash_update) {
-        /* The klass doesn't implement ID compare. It means, to use pointer
-         * equality. */
-        nm_hash_update_val(h, obj);
-        return;
-    }
-
-    nm_hash_update_val(h, klass->obj_type);
-    klass->cmd_plobj_id_hash_update(&obj->object, h);
-}
-
-guint
-nmp_object_id_hash(const NMPObject *obj)
-{
-    NMHashState h;
-
-    if (!obj)
-        return nm_hash_static(914932607u);
-
-    nm_hash_init(&h, 914932607u);
-    nmp_object_id_hash_update(obj, &h);
-    return nm_hash_complete(&h);
-}
-
-#define _vt_cmd_plobj_id_hash_update(type, plat_type, cmd)                                        \
-    static void _vt_cmd_plobj_id_hash_update_##type(const NMPlatformObject *_obj, NMHashState *h) \
-    {                                                                                             \
-        const plat_type *const obj = (const plat_type *) _obj;                                    \
-        {                                                                                         \
-            cmd;                                                                                  \
-        }                                                                                         \
-    }                                                                                             \
-    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
-
-_vt_cmd_plobj_id_hash_update(link, NMPlatformLink, { nm_hash_update_val(h, obj->ifindex); });
-
-_vt_cmd_plobj_id_hash_update(ip4_address, NMPlatformIP4Address, {
-    nm_hash_update_vals(
-        h,
-        obj->ifindex,
-        obj->plen,
-        obj->address,
-        /* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */
-        nm_utils_ip4_address_clear_host_address(obj->peer_address, obj->plen));
-});
-
-_vt_cmd_plobj_id_hash_update(ip6_address, NMPlatformIP6Address, {
-    nm_hash_update_vals(
-        h,
-        obj->ifindex,
-        /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
-        obj->address);
-});
-
-_vt_cmd_plobj_id_hash_update(ip4_route, NMPlatformIP4Route, {
-    nm_platform_ip4_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
-});
-
-_vt_cmd_plobj_id_hash_update(ip6_route, NMPlatformIP6Route, {
-    nm_platform_ip6_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
-});
-
-_vt_cmd_plobj_id_hash_update(routing_rule, NMPlatformRoutingRule, {
-    nm_platform_routing_rule_hash_update(obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, h);
-});
-
-_vt_cmd_plobj_id_hash_update(qdisc, NMPlatformQdisc, {
-    nm_hash_update_vals(h, obj->ifindex, obj->parent);
-});
-
-_vt_cmd_plobj_id_hash_update(tfilter, NMPlatformTfilter, {
-    nm_hash_update_vals(h, obj->ifindex, obj->handle);
-});
-
-static void
-_vt_cmd_plobj_hash_update_ip4_route(const NMPlatformObject *obj, NMHashState *h)
-{
-    return nm_platform_ip4_route_hash_update((const NMPlatformIP4Route *) obj,
-                                             NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
-                                             h);
-}
-
-static void
-_vt_cmd_plobj_hash_update_ip6_route(const NMPlatformObject *obj, NMHashState *h)
-{
-    return nm_platform_ip6_route_hash_update((const NMPlatformIP6Route *) obj,
-                                             NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
-                                             h);
-}
-
-static void
-_vt_cmd_plobj_hash_update_routing_rule(const NMPlatformObject *obj, NMHashState *h)
-{
-    return nm_platform_routing_rule_hash_update((const NMPlatformRoutingRule *) obj,
-                                                NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL,
-                                                h);
-}
-
-guint
-nmp_object_indirect_id_hash(gconstpointer a)
-{
-    const NMPObject *const *p_obj = a;
-
-    return nmp_object_id_hash(*p_obj);
-}
-
-gboolean
-nmp_object_indirect_id_equal(gconstpointer a, gconstpointer b)
-{
-    const NMPObject *const *p_obj_a = a;
-    const NMPObject *const *p_obj_b = b;
-
-    return nmp_object_id_equal(*p_obj_a, *p_obj_b);
-}
-
-/*****************************************************************************/
-
-gboolean
-nmp_object_is_alive(const NMPObject *obj)
-{
-    const NMPClass *klass;
-
-    /* for convenience, allow NULL. */
-    if (!obj)
-        return FALSE;
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-    return !klass->cmd_obj_is_alive || klass->cmd_obj_is_alive(obj);
-}
-
-static gboolean
-_vt_cmd_obj_is_alive_link(const NMPObject *obj)
-{
-    return NMP_OBJECT_CAST_LINK(obj)->ifindex > 0
-           && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device);
-}
-
-static gboolean
-_vt_cmd_obj_is_alive_ipx_address(const NMPObject *obj)
-{
-    return NMP_OBJECT_CAST_IP_ADDRESS(obj)->ifindex > 0;
-}
-
-static gboolean
-_vt_cmd_obj_is_alive_ipx_route(const NMPObject *obj)
-{
-    /* We want to ignore routes that are RTM_F_CLONED but we still
-     * let nmp_object_from_nl() create such route objects, instead of
-     * returning NULL right away.
-     *
-     * The idea is, that if we have the same route (according to its id)
-     * in the cache with !RTM_F_CLONED, an update that changes the route
-     * to be RTM_F_CLONED must remove the instance.
-     *
-     * If nmp_object_from_nl() would just return NULL, we couldn't look
-     * into the cache to see if it contains a route that now disappears
-     * (because it changed to be cloned).
-     *
-     * Instead we create a dead object, and nmp_cache_update_netlink()
-     * will remove the old version of the update.
-     **/
-    return NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex > 0
-           && !NM_FLAGS_HAS(obj->ip_route.r_rtm_flags, RTM_F_CLONED);
-}
-
-static gboolean
-_vt_cmd_obj_is_alive_routing_rule(const NMPObject *obj)
-{
-    return NM_IN_SET(obj->routing_rule.addr_family, AF_INET, AF_INET6);
-}
-
-static gboolean
-_vt_cmd_obj_is_alive_qdisc(const NMPObject *obj)
-{
-    return NMP_OBJECT_CAST_QDISC(obj)->ifindex > 0;
-}
-
-static gboolean
-_vt_cmd_obj_is_alive_tfilter(const NMPObject *obj)
-{
-    return NMP_OBJECT_CAST_TFILTER(obj)->ifindex > 0;
-}
-
-gboolean
-nmp_object_is_visible(const NMPObject *obj)
-{
-    const NMPClass *klass;
-
-    /* for convenience, allow NULL. */
-    if (!obj)
-        return FALSE;
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    /* a dead object is never visible. */
-    if (klass->cmd_obj_is_alive && !klass->cmd_obj_is_alive(obj))
-        return FALSE;
-
-    return !klass->cmd_obj_is_visible || klass->cmd_obj_is_visible(obj);
-}
-
-static gboolean
-_vt_cmd_obj_is_visible_link(const NMPObject *obj)
-{
-    return obj->_link.netlink.is_in_netlink && obj->link.name[0];
-}
-
-/*****************************************************************************/
-
-static const guint8 _supported_cache_ids_object[] = {
-    NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-    NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
-    0,
-};
-
-static const guint8 _supported_cache_ids_link[] = {
-    NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-    NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
-    0,
-};
-
-static const guint8 _supported_cache_ids_ipx_address[] = {
-    NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-    NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
-    0,
-};
-
-static const guint8 _supported_cache_ids_ipx_route[] = {
-    NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-    NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
-    NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
-    NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
-    0,
-};
-
-static const guint8 _supported_cache_ids_routing_rules[] = {
-    NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-    NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY,
-    0,
-};
-
-/*****************************************************************************/
-
-static void
-_vt_dedup_obj_destroy(NMDedupMultiObj *obj)
-{
-    NMPObject *     o = (NMPObject *) obj;
-    const NMPClass *klass;
-
-    nm_assert(o->parent._ref_count == 0);
-    nm_assert(!o->parent._multi_idx);
-
-    klass = o->_class;
-    if (klass->cmd_obj_dispose)
-        klass->cmd_obj_dispose(o);
-    g_slice_free1(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object), o);
-}
-
-static const NMDedupMultiObj *
-_vt_dedup_obj_clone(const NMDedupMultiObj *obj)
-{
-    return (const NMDedupMultiObj *) nmp_object_clone((const NMPObject *) obj, FALSE);
-}
-
-#define DEDUP_MULTI_OBJ_CLASS_INIT()                                                       \
-    {                                                                                      \
-        .obj_clone = _vt_dedup_obj_clone, .obj_destroy = _vt_dedup_obj_destroy,            \
-        .obj_full_hash_update =                                                            \
-            (void (*)(const NMDedupMultiObj *obj, NMHashState *h)) nmp_object_hash_update, \
-        .obj_full_equal = (gboolean(*)(const NMDedupMultiObj *obj_a,                       \
-                                       const NMDedupMultiObj *obj_b)) nmp_object_equal,    \
-    }
-
-/*****************************************************************************/
-
-static NMDedupMultiIdxType *
-_idx_type_get(const NMPCache *cache, NMPCacheIdType cache_id_type)
-{
-    nm_assert(cache);
-    nm_assert(cache_id_type > NMP_CACHE_ID_TYPE_NONE);
-    nm_assert(cache_id_type <= NMP_CACHE_ID_TYPE_MAX);
-    nm_assert((int) cache_id_type - 1 >= 0);
-    nm_assert((int) cache_id_type - 1 < G_N_ELEMENTS(cache->idx_types));
-
-    return (NMDedupMultiIdxType *) &cache->idx_types[cache_id_type - 1];
-}
-
-gboolean
-nmp_cache_use_udev_get(const NMPCache *cache)
-{
-    g_return_val_if_fail(cache, TRUE);
-
-    return cache->use_udev;
-}
-
-/*****************************************************************************/
-
-gboolean
-nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave)
-{
-    nm_assert(NMP_OBJECT_GET_TYPE(slave) == NMP_OBJECT_TYPE_LINK);
-
-    return ifindex_master > 0 && slave->link.master == ifindex_master && slave->link.connected
-           && nmp_object_is_visible(slave);
-}
-
-/**
- * nmp_cache_link_connected_needs_toggle:
- * @cache: the platform cache
- * @master: the link object, that is checked whether its connected property
- *   needs to be toggled.
- * @potential_slave: (allow-none): an additional link object that is treated
- *   as if it was inside @cache. If given, it shaddows a link in the cache
- *   with the same ifindex.
- * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave
- *   is not in the cache.
- *
- * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported
- * from netlink) and master->link.connected. For bond and bridge master, kernel reports
- * those links as IFF_LOWER_UP if they have no slaves attached. We want to present instead
- * a combined @connected flag that shows masters without slaves as down.
- *
- * Check if the connected flag of @master should be toggled according to the content
- * of @cache (including @potential_slave).
- *
- * Returns: %TRUE, if @master->link.connected should be flipped/toggled.
- **/
-gboolean
-nmp_cache_link_connected_needs_toggle(const NMPCache * cache,
-                                      const NMPObject *master,
-                                      const NMPObject *potential_slave,
-                                      const NMPObject *ignore_slave)
-{
-    gboolean is_lower_up = FALSE;
-
-    if (!master || NMP_OBJECT_GET_TYPE(master) != NMP_OBJECT_TYPE_LINK || master->link.ifindex <= 0
-        || !nmp_object_is_visible(master)
-        || !NM_IN_SET(master->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND))
-        return FALSE;
-
-    /* if native IFF_LOWER_UP is down, link.connected must also be down
-     * regardless of the slaves. */
-    if (!NM_FLAGS_HAS(master->link.n_ifi_flags, IFF_LOWER_UP))
-        return !!master->link.connected;
-
-    if (potential_slave && NMP_OBJECT_GET_TYPE(potential_slave) != NMP_OBJECT_TYPE_LINK)
-        potential_slave = NULL;
-
-    if (potential_slave
-        && nmp_cache_link_connected_for_slave(master->link.ifindex, potential_slave))
-        is_lower_up = TRUE;
-    else {
-        NMPLookup             lookup;
-        NMDedupMultiIter      iter;
-        const NMPlatformLink *link = NULL;
-
-        nmp_cache_iter_for_each_link (
-            &iter,
-            nmp_cache_lookup(cache, nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK)),
-            &link) {
-            const NMPObject *obj = NMP_OBJECT_UP_CAST((NMPlatformObject *) link);
-
-            if ((!potential_slave || potential_slave->link.ifindex != link->ifindex)
-                && ignore_slave != obj
-                && nmp_cache_link_connected_for_slave(master->link.ifindex, obj)) {
-                is_lower_up = TRUE;
-                break;
-            }
-        }
-    }
-    return !!master->link.connected != is_lower_up;
-}
-
-/**
- * nmp_cache_link_connected_needs_toggle_by_ifindex:
- * @cache:
- * @master_ifindex: the ifindex of a potential master that should be checked
- *   whether it needs toggling.
- * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle().
- *   It considers @potential_slave as being inside the cache, replacing an existing
- *   link with the same ifindex.
- * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle().
- *
- * The flag obj->link.connected depends on the state of other links in the
- * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex
- * of a master, check if the cache contains such a master link that needs
- * toggling of the connected flag.
- *
- * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled.
- *   Otherwise, return the link object from inside the cache with the given ifindex.
- *   The connected flag of that master should be toggled.
- */
-const NMPObject *
-nmp_cache_link_connected_needs_toggle_by_ifindex(const NMPCache * cache,
-                                                 int              master_ifindex,
-                                                 const NMPObject *potential_slave,
-                                                 const NMPObject *ignore_slave)
-{
-    const NMPObject *master;
-
-    if (master_ifindex > 0) {
-        master = nmp_cache_lookup_link(cache, master_ifindex);
-        if (nmp_cache_link_connected_needs_toggle(cache, master, potential_slave, ignore_slave))
-            return master;
-    }
-    return NULL;
-}
-
-/*****************************************************************************/
-
-static const NMDedupMultiEntry *
-_lookup_entry_with_idx_type(const NMPCache * cache,
-                            NMPCacheIdType   cache_id_type,
-                            const NMPObject *obj)
-{
-    const NMDedupMultiEntry *entry;
-
-    nm_assert(cache);
-    nm_assert(NMP_OBJECT_IS_VALID(obj));
-
-    entry =
-        nm_dedup_multi_index_lookup_obj(cache->multi_idx, _idx_type_get(cache, cache_id_type), obj);
-    nm_assert(!entry
-              || (NMP_OBJECT_IS_VALID(entry->obj)
-                  && NMP_OBJECT_GET_CLASS(entry->obj) == NMP_OBJECT_GET_CLASS(obj)));
-    return entry;
-}
-
-static const NMDedupMultiEntry *
-_lookup_entry(const NMPCache *cache, const NMPObject *obj)
-{
-    return _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj);
-}
-
-const NMDedupMultiEntry *
-nmp_cache_lookup_entry_with_idx_type(const NMPCache * cache,
-                                     NMPCacheIdType   cache_id_type,
-                                     const NMPObject *obj)
-{
-    g_return_val_if_fail(cache, NULL);
-    g_return_val_if_fail(obj, NULL);
-    g_return_val_if_fail(cache_id_type > NMP_CACHE_ID_TYPE_NONE
-                             && cache_id_type <= NMP_CACHE_ID_TYPE_MAX,
-                         NULL);
-
-    return _lookup_entry_with_idx_type(cache, cache_id_type, obj);
-}
-
-const NMDedupMultiEntry *
-nmp_cache_lookup_entry(const NMPCache *cache, const NMPObject *obj)
-{
-    g_return_val_if_fail(cache, NULL);
-    g_return_val_if_fail(obj, NULL);
-
-    return _lookup_entry(cache, obj);
-}
-
-const NMDedupMultiEntry *
-nmp_cache_lookup_entry_link(const NMPCache *cache, int ifindex)
-{
-    NMPObject obj_needle;
-
-    g_return_val_if_fail(cache, NULL);
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    nmp_object_stackinit_id_link(&obj_needle, ifindex);
-    return _lookup_entry(cache, &obj_needle);
-}
-
-const NMPObject *
-nmp_cache_lookup_obj(const NMPCache *cache, const NMPObject *obj)
-{
-    return nm_dedup_multi_entry_get_obj(nmp_cache_lookup_entry(cache, obj));
-}
-
-const NMPObject *
-nmp_cache_lookup_link(const NMPCache *cache, int ifindex)
-{
-    return nm_dedup_multi_entry_get_obj(nmp_cache_lookup_entry_link(cache, ifindex));
-}
-
-/*****************************************************************************/
-
-const NMDedupMultiHeadEntry *
-nmp_cache_lookup_all(const NMPCache * cache,
-                     NMPCacheIdType   cache_id_type,
-                     const NMPObject *select_obj)
-{
-    nm_assert(cache);
-    nm_assert(NMP_OBJECT_IS_VALID(select_obj));
-
-    return nm_dedup_multi_index_lookup_head(cache->multi_idx,
-                                            _idx_type_get(cache, cache_id_type),
-                                            select_obj);
-}
-
-static const NMPLookup *
-_L(const NMPLookup *lookup)
-{
-#if NM_MORE_ASSERTS
-    DedupMultiIdxType idx_type;
-
-    nm_assert(lookup);
-    _dedup_multi_idx_type_init(&idx_type, lookup->cache_id_type);
-    nm_assert(
-        idx_type.parent.klass->idx_obj_partitionable((NMDedupMultiIdxType *) &idx_type,
-                                                     (NMDedupMultiObj *) &lookup->selector_obj));
-#endif
-    return lookup;
-}
-
-const NMPLookup *
-nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type)
-{
-    nm_assert(lookup);
-
-    switch (obj_type) {
-    case NMP_OBJECT_TYPE_LINK:
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-    case NMP_OBJECT_TYPE_ROUTING_RULE:
-    case NMP_OBJECT_TYPE_QDISC:
-    case NMP_OBJECT_TYPE_TFILTER:
-        _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type);
-        lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE;
-        return _L(lookup);
-    default:
-        nm_assert_not_reached();
-        return NULL;
-    }
-}
-
-const NMPLookup *
-nmp_lookup_init_link_by_ifname(NMPLookup *lookup, const char *ifname)
-{
-    NMPObject *o;
-
-    nm_assert(lookup);
-    nm_assert(ifname);
-    nm_assert(strlen(ifname) < IFNAMSIZ);
-
-    o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_LINK);
-    if (g_strlcpy(o->link.name, ifname, sizeof(o->link.name)) >= sizeof(o->link.name))
-        g_return_val_if_reached(NULL);
-    lookup->cache_id_type = NMP_CACHE_ID_TYPE_LINK_BY_IFNAME;
-    return _L(lookup);
-}
-
-const NMPLookup *
-nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex)
-{
-    NMPObject *o;
-
-    nm_assert(lookup);
-    nm_assert(NM_IN_SET(obj_type,
-                        NMP_OBJECT_TYPE_IP4_ADDRESS,
-                        NMP_OBJECT_TYPE_IP6_ADDRESS,
-                        NMP_OBJECT_TYPE_IP4_ROUTE,
-                        NMP_OBJECT_TYPE_IP6_ROUTE,
-                        NMP_OBJECT_TYPE_QDISC,
-                        NMP_OBJECT_TYPE_TFILTER));
-
-    if (ifindex <= 0) {
-        return nmp_lookup_init_obj_type(lookup, obj_type);
-    }
-
-    o                           = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type);
-    o->obj_with_ifindex.ifindex = ifindex;
-    lookup->cache_id_type       = NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX;
-    return _L(lookup);
-}
-
-const NMPLookup *
-nmp_lookup_init_route_default(NMPLookup *lookup, NMPObjectType obj_type)
-{
-    NMPObject *o;
-
-    nm_assert(lookup);
-    nm_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
-
-    o                     = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type);
-    o->ip_route.ifindex   = 1;
-    lookup->cache_id_type = NMP_CACHE_ID_TYPE_DEFAULT_ROUTES;
-    return _L(lookup);
-}
-
-const NMPLookup *
-nmp_lookup_init_route_by_weak_id(NMPLookup *lookup, const NMPObject *obj)
-{
-    const NMPlatformIP4Route *r4;
-    const NMPlatformIP6Route *r6;
-
-    nm_assert(lookup);
-
-    switch (NMP_OBJECT_GET_TYPE(obj)) {
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-        r4 = NMP_OBJECT_CAST_IP4_ROUTE(obj);
-        return nmp_lookup_init_ip4_route_by_weak_id(lookup,
-                                                    r4->network,
-                                                    r4->plen,
-                                                    r4->metric,
-                                                    r4->tos);
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        r6 = NMP_OBJECT_CAST_IP6_ROUTE(obj);
-        return nmp_lookup_init_ip6_route_by_weak_id(lookup,
-                                                    &r6->network,
-                                                    r6->plen,
-                                                    r6->metric,
-                                                    &r6->src,
-                                                    r6->src_plen);
-    default:
-        nm_assert_not_reached();
-        return NULL;
-    }
-}
-
-const NMPLookup *
-nmp_lookup_init_ip4_route_by_weak_id(NMPLookup *lookup,
-                                     in_addr_t  network,
-                                     guint      plen,
-                                     guint32    metric,
-                                     guint8     tos)
-{
-    NMPObject *o;
-
-    nm_assert(lookup);
-
-    o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_IP4_ROUTE);
-    o->ip4_route.ifindex = 1;
-    o->ip4_route.plen    = plen;
-    o->ip4_route.metric  = metric;
-    if (network)
-        o->ip4_route.network = network;
-    o->ip4_route.tos      = tos;
-    lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID;
-    return _L(lookup);
-}
-
-const NMPLookup *
-nmp_lookup_init_ip6_route_by_weak_id(NMPLookup *            lookup,
-                                     const struct in6_addr *network,
-                                     guint                  plen,
-                                     guint32                metric,
-                                     const struct in6_addr *src,
-                                     guint8                 src_plen)
-{
-    NMPObject *o;
-
-    nm_assert(lookup);
-
-    o = _nmp_object_stackinit_from_type(&lookup->selector_obj, NMP_OBJECT_TYPE_IP6_ROUTE);
-    o->ip6_route.ifindex = 1;
-    o->ip6_route.plen    = plen;
-    o->ip6_route.metric  = metric;
-    if (network)
-        o->ip6_route.network = *network;
-    if (src)
-        o->ip6_route.src = *src;
-    o->ip6_route.src_plen = src_plen;
-    lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID;
-    return _L(lookup);
-}
-
-const NMPLookup *
-nmp_lookup_init_object_by_addr_family(NMPLookup *lookup, NMPObjectType obj_type, int addr_family)
-{
-    NMPObject *o;
-
-    nm_assert(lookup);
-    nm_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_ROUTING_RULE));
-
-    if (addr_family == AF_UNSPEC)
-        return nmp_lookup_init_obj_type(lookup, obj_type);
-
-    nm_assert_addr_family(addr_family);
-    o = _nmp_object_stackinit_from_type(&lookup->selector_obj, obj_type);
-    NMP_OBJECT_CAST_ROUTING_RULE(o)->addr_family = addr_family;
-    lookup->cache_id_type                        = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY;
-    return _L(lookup);
-}
-
-/*****************************************************************************/
-
-GArray *
-nmp_cache_lookup_to_array(const NMDedupMultiHeadEntry *head_entry,
-                          NMPObjectType                obj_type,
-                          gboolean                     visible_only)
-{
-    const NMPClass * klass = nmp_class_from_type(obj_type);
-    NMDedupMultiIter iter;
-    const NMPObject *o;
-    GArray *         array;
-
-    g_return_val_if_fail(klass, NULL);
-
-    array = g_array_sized_new(FALSE, FALSE, klass->sizeof_public, head_entry ? head_entry->len : 0);
-    nmp_cache_iter_for_each (&iter, head_entry, &o) {
-        nm_assert(NMP_OBJECT_GET_CLASS(o) == klass);
-        if (visible_only && !nmp_object_is_visible(o))
-            continue;
-        g_array_append_vals(array, &o->object, 1);
-    }
-    return array;
-}
-
-/*****************************************************************************/
-
-const NMPObject *
-nmp_cache_lookup_link_full(const NMPCache * cache,
-                           int              ifindex,
-                           const char *     ifname,
-                           gboolean         visible_only,
-                           NMLinkType       link_type,
-                           NMPObjectMatchFn match_fn,
-                           gpointer         user_data)
-{
-    NMPObject                    obj_needle;
-    const NMPObject *            obj;
-    NMDedupMultiIter             iter;
-    const NMDedupMultiHeadEntry *head_entry;
-    const NMPlatformLink *       link = NULL;
-    NMPLookup                    lookup;
-
-    if (ifindex > 0) {
-        obj = nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&obj_needle, ifindex));
-
-        if (!obj || (visible_only && !nmp_object_is_visible(obj))
-            || (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
-            || (ifname && strcmp(obj->link.name, ifname))
-            || (match_fn && !match_fn(obj, user_data)))
-            return NULL;
-        return obj;
-    } else if (!ifname && !match_fn)
-        return NULL;
-    else {
-        const NMPObject *obj_best = NULL;
-
-        if (ifname) {
-            if (strlen(ifname) >= IFNAMSIZ)
-                return NULL;
-            nmp_lookup_init_link_by_ifname(&lookup, ifname);
-        } else
-            nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK);
-
-        head_entry = nmp_cache_lookup(cache, &lookup);
-        nmp_cache_iter_for_each_link (&iter, head_entry, &link) {
-            obj = NMP_OBJECT_UP_CAST(link);
-
-            if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
-                continue;
-            if (visible_only && !nmp_object_is_visible(obj))
-                continue;
-            if (match_fn && !match_fn(obj, user_data))
-                continue;
-
-            /* if there are multiple candidates, prefer the visible ones. */
-            if (visible_only || nmp_object_is_visible(obj))
-                return obj;
-            if (!obj_best)
-                obj_best = obj;
-        }
-        return obj_best;
-    }
-}
-
-/*****************************************************************************/
-
-static NMDedupMultiIdxMode
-_obj_get_add_mode(const NMPObject *obj)
-{
-    /* new objects are usually appended to the list. Except for
-     * addresses, which are prepended during `ip address add`.
-     *
-     * Actually, for routes it is more complicated, because depending on
-     * `ip route append`, `ip route replace`, `ip route prepend`, the object
-     * will be added at the tail, at the front, or even replace an element
-     * in the list. However, that is handled separately by nmp_cache_update_netlink_route()
-     * and of no concern here. */
-    if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj),
-                  NMP_OBJECT_TYPE_IP4_ADDRESS,
-                  NMP_OBJECT_TYPE_IP6_ADDRESS))
-        return NM_DEDUP_MULTI_IDX_MODE_PREPEND;
-    return NM_DEDUP_MULTI_IDX_MODE_APPEND;
-}
-
-static void
-_idxcache_update_order_for_dump(NMPCache *cache, const NMDedupMultiEntry *entry)
-{
-    const NMPClass *         klass;
-    const guint8 *           i_idx_type;
-    const NMDedupMultiEntry *entry2;
-
-    nm_dedup_multi_entry_reorder(entry, NULL, TRUE);
-
-    klass = NMP_OBJECT_GET_CLASS(entry->obj);
-    for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) {
-        NMPCacheIdType id_type = *i_idx_type;
-
-        if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE)
-            continue;
-
-        entry2 = nm_dedup_multi_index_lookup_obj(cache->multi_idx,
-                                                 _idx_type_get(cache, id_type),
-                                                 entry->obj);
-        if (!entry2)
-            continue;
-
-        nm_assert(entry2 != entry);
-        nm_assert(entry2->obj == entry->obj);
-
-        nm_dedup_multi_entry_reorder(entry2, NULL, TRUE);
-    }
-}
-
-static void
-_idxcache_update_other_cache_ids(NMPCache *       cache,
-                                 NMPCacheIdType   cache_id_type,
-                                 const NMPObject *obj_old,
-                                 const NMPObject *obj_new,
-                                 gboolean         is_dump)
-{
-    const NMDedupMultiEntry *entry_new;
-    const NMDedupMultiEntry *entry_old;
-    const NMDedupMultiEntry *entry_order;
-    NMDedupMultiIdxType *    idx_type;
-
-    nm_assert(obj_new || obj_old);
-    nm_assert(!obj_new || NMP_OBJECT_GET_TYPE(obj_new) != NMP_OBJECT_TYPE_UNKNOWN);
-    nm_assert(!obj_old || NMP_OBJECT_GET_TYPE(obj_old) != NMP_OBJECT_TYPE_UNKNOWN);
-    nm_assert(!obj_old || !obj_new
-              || NMP_OBJECT_GET_CLASS(obj_new) == NMP_OBJECT_GET_CLASS(obj_old));
-    nm_assert(!obj_old || !obj_new || !nmp_object_equal(obj_new, obj_old));
-    nm_assert(!obj_new || obj_new == nm_dedup_multi_index_obj_find(cache->multi_idx, obj_new));
-    nm_assert(!obj_old || obj_old == nm_dedup_multi_index_obj_find(cache->multi_idx, obj_old));
-
-    idx_type = _idx_type_get(cache, cache_id_type);
-
-    if (obj_old) {
-        entry_old = nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type, obj_old);
-        if (!obj_new) {
-            if (entry_old)
-                nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old);
-            return;
-        }
-    } else
-        entry_old = NULL;
-
-    if (obj_new) {
-        if (obj_old && nm_dedup_multi_idx_type_id_equal(idx_type, obj_old, obj_new)
-            && nm_dedup_multi_idx_type_partition_equal(idx_type, obj_old, obj_new)) {
-            /* optimize. We just looked up the @obj_old entry and @obj_new compares equal
-             * according to idx_obj_id_equal(). entry_new is the same as entry_old. */
-            entry_new = entry_old;
-        } else {
-            entry_new = nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type, obj_new);
-        }
-
-        if (entry_new)
-            entry_order = entry_new;
-        else if (entry_old
-                 && nm_dedup_multi_idx_type_partition_equal(idx_type, entry_old->obj, obj_new))
-            entry_order = entry_old;
-        else
-            entry_order = NULL;
-        nm_dedup_multi_index_add_full(
-            cache->multi_idx,
-            idx_type,
-            obj_new,
-            is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE : _obj_get_add_mode(obj_new),
-            is_dump ? NULL : entry_order,
-            entry_new ?: NM_DEDUP_MULTI_ENTRY_MISSING,
-            entry_new ? entry_new->head : (entry_order ? entry_order->head : NULL),
-            &entry_new,
-            NULL);
-
-#if NM_MORE_ASSERTS
-        if (entry_new) {
-            nm_assert(idx_type->klass->idx_obj_partitionable);
-            nm_assert(idx_type->klass->idx_obj_partition_equal);
-            nm_assert(idx_type->klass->idx_obj_partitionable(idx_type, entry_new->obj));
-            nm_assert(idx_type->klass->idx_obj_partition_equal(idx_type,
-                                                               (gpointer) obj_new,
-                                                               entry_new->obj));
-        }
-#endif
-    } else
-        entry_new = NULL;
-
-    if (entry_old && entry_old != entry_new)
-        nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old);
-}
-
-static void
-_idxcache_update(NMPCache *                cache,
-                 const NMDedupMultiEntry * entry_old,
-                 NMPObject *               obj_new,
-                 gboolean                  is_dump,
-                 const NMDedupMultiEntry **out_entry_new)
-{
-    const NMPClass *         klass;
-    const guint8 *           i_idx_type;
-    NMDedupMultiIdxType *    idx_type_o     = _idx_type_get(cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE);
-    const NMDedupMultiEntry *entry_new      = NULL;
-    nm_auto_nmpobj const NMPObject *obj_old = NULL;
-
-    /* we update an object in the cache.
-     *
-     * Note that @entry_old MUST be what is currently tracked in multi_idx, and it must
-     * have the same ID as @obj_new. */
-
-    nm_assert(cache);
-    nm_assert(entry_old || obj_new);
-    nm_assert(!obj_new || nmp_object_is_alive(obj_new));
-    nm_assert(
-        !entry_old
-        || entry_old
-               == nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type_o, entry_old->obj));
-    nm_assert(!obj_new
-              || entry_old
-                     == nm_dedup_multi_index_lookup_obj(cache->multi_idx, idx_type_o, obj_new));
-    nm_assert(!entry_old || entry_old->head->idx_type == idx_type_o);
-    nm_assert(!entry_old || !obj_new
-              || nm_dedup_multi_idx_type_partition_equal(idx_type_o, entry_old->obj, obj_new));
-    nm_assert(!entry_old || !obj_new
-              || nm_dedup_multi_idx_type_id_equal(idx_type_o, entry_old->obj, obj_new));
-    nm_assert(!entry_old || !obj_new
-              || (obj_new->parent.klass == ((const NMPObject *) entry_old->obj)->parent.klass
-                  && !obj_new->parent.klass->obj_full_equal((NMDedupMultiObj *) obj_new,
-                                                            entry_old->obj)));
-
-    /* keep a reference to the pre-existing entry */
-    if (entry_old)
-        obj_old = nmp_object_ref(entry_old->obj);
-
-    /* first update the main index NMP_CACHE_ID_TYPE_OBJECT_TYPE.
-     * We already know the pre-existing @entry old, so all that
-     * nm_dedup_multi_index_add_full() effectively does, is update the
-     * obj reference.
-     *
-     * We also get the new boxed object, which we need below. */
-    if (obj_new) {
-        nm_auto_nmpobj NMPObject *obj_old2 = NULL;
-
-        nm_dedup_multi_index_add_full(cache->multi_idx,
-                                      idx_type_o,
-                                      obj_new,
-                                      is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
-                                              : _obj_get_add_mode(obj_new),
-                                      NULL,
-                                      entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
-                                      NULL,
-                                      &entry_new,
-                                      (const NMDedupMultiObj **) &obj_old2);
-        nm_assert(entry_new);
-        nm_assert(obj_old == obj_old2);
-        nm_assert(!entry_old || entry_old == entry_new);
-    } else
-        nm_dedup_multi_index_remove_entry(cache->multi_idx, entry_old);
-
-    /* now update all other indexes. We know the previously boxed entry, and the
-     * newly boxed one. */
-    klass = NMP_OBJECT_GET_CLASS(entry_new ? entry_new->obj : obj_old);
-    for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) {
-        NMPCacheIdType id_type = *i_idx_type;
-
-        if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE)
-            continue;
-        _idxcache_update_other_cache_ids(cache,
-                                         id_type,
-                                         obj_old,
-                                         entry_new ? entry_new->obj : NULL,
-                                         is_dump);
-    }
-
-    NM_SET_OUT(out_entry_new, entry_new);
-}
-
-NMPCacheOpsType
-nmp_cache_remove(NMPCache *        cache,
-                 const NMPObject * obj_needle,
-                 gboolean          equals_by_ptr,
-                 gboolean          only_dirty,
-                 const NMPObject **out_obj_old)
-{
-    const NMDedupMultiEntry *entry_old;
-    const NMPObject *        obj_old;
-
-    entry_old = _lookup_entry(cache, obj_needle);
-
-    if (!entry_old) {
-        NM_SET_OUT(out_obj_old, NULL);
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-
-    obj_old = entry_old->obj;
-
-    NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-
-    if (equals_by_ptr && obj_old != obj_needle) {
-        /* We found an identical object, but we only delete it if it's the same pointer as
-         * @obj_needle. */
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-    if (only_dirty && !entry_old->dirty) {
-        /* the entry is not dirty. Skip. */
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-    _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
-    return NMP_CACHE_OPS_REMOVED;
-}
-
-NMPCacheOpsType
-nmp_cache_remove_netlink(NMPCache *        cache,
-                         const NMPObject * obj_needle,
-                         const NMPObject **out_obj_old,
-                         const NMPObject **out_obj_new)
-{
-    const NMDedupMultiEntry *entry_old;
-    const NMDedupMultiEntry *entry_new = NULL;
-    const NMPObject *        obj_old;
-    nm_auto_nmpobj NMPObject *obj_new = NULL;
-
-    entry_old = _lookup_entry(cache, obj_needle);
-
-    if (!entry_old) {
-        NM_SET_OUT(out_obj_old, NULL);
-        NM_SET_OUT(out_obj_new, NULL);
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-
-    obj_old = entry_old->obj;
-
-    if (NMP_OBJECT_GET_TYPE(obj_needle) == NMP_OBJECT_TYPE_LINK) {
-        /* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be
-         * removed from netlink. Link objects are alive without being in netlink when they
-         * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink
-         * flag. */
-
-        NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-
-        if (!obj_old->_link.netlink.is_in_netlink) {
-            nm_assert(obj_old->_link.udev.device);
-            NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old));
-            return NMP_CACHE_OPS_UNCHANGED;
-        }
-
-        if (!obj_old->_link.udev.device) {
-            /* the update would make @obj_old invalid. Remove it. */
-            _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
-            NM_SET_OUT(out_obj_new, NULL);
-            return NMP_CACHE_OPS_REMOVED;
-        }
-
-        obj_new                              = nmp_object_clone(obj_old, FALSE);
-        obj_new->_link.netlink.is_in_netlink = FALSE;
-
-        _nmp_object_fixup_link_master_connected(&obj_new, NULL, cache);
-        _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev);
-
-        _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new);
-        NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj));
-        return NMP_CACHE_OPS_UPDATED;
-    }
-
-    NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-    NM_SET_OUT(out_obj_new, NULL);
-    _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
-    return NMP_CACHE_OPS_REMOVED;
-}
-
-/**
- * nmp_cache_update_netlink:
- * @cache: the platform cache
- * @obj_hand_over: a #NMPObject instance as received from netlink and created via
- *    nmp_object_from_nl(). Especially for link, it must not have the udev
- *    replated fields set.
- *    This instance will be modified and might be put into the cache. When
- *    calling nmp_cache_update_netlink() you hand @obj over to the cache.
- *    Except, that the cache will increment the ref count as appropriate. You
- *    must still unref the obj to release your part of the ownership.
- * @is_dump: whether this update comes during a dump of object of the same kind.
- *    kernel dumps objects in a certain order, which matters especially for routes.
- *    Before a dump we mark all objects as dirty, and remove all untouched objects
- *    afterwards. Hence, during a dump, every update should move the object to the
- *    end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
- *    instead of NM_DEDUP_MULTI_IDX_MODE_APPEND.
- * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over,
- *    that was in the cache before update. If an object is returned, the caller must
- *    unref it afterwards.
- * @out_obj_new: (allow-none) (out): return the object from the cache after update.
- *    The caller must unref this object.
- *
- * Returns: how the cache changed.
- *
- * Even if there was no change in the cache (NMP_CACHE_OPS_UNCHANGED), @out_obj_old
- * and @out_obj_new will be set accordingly.
- **/
-NMPCacheOpsType
-nmp_cache_update_netlink(NMPCache *        cache,
-                         NMPObject *       obj_hand_over,
-                         gboolean          is_dump,
-                         const NMPObject **out_obj_old,
-                         const NMPObject **out_obj_new)
-{
-    const NMDedupMultiEntry *entry_old;
-    const NMDedupMultiEntry *entry_new;
-    const NMPObject *        obj_old;
-    gboolean                 is_alive;
-
-    nm_assert(cache);
-    nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over));
-    nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over));
-    /* A link object from netlink must have the udev related fields unset.
-     * We could implement to handle that, but there is no need to support such
-     * a use-case */
-    nm_assert(NMP_OBJECT_GET_TYPE(obj_hand_over) != NMP_OBJECT_TYPE_LINK
-              || (!obj_hand_over->_link.udev.device && !obj_hand_over->link.driver));
-    nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over);
-
-    entry_old = _lookup_entry(cache, obj_hand_over);
-
-    if (!entry_old) {
-        NM_SET_OUT(out_obj_old, NULL);
-
-        if (!nmp_object_is_alive(obj_hand_over)) {
-            NM_SET_OUT(out_obj_new, NULL);
-            return NMP_CACHE_OPS_UNCHANGED;
-        }
-
-        if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_LINK) {
-            _nmp_object_fixup_link_master_connected(&obj_hand_over, NULL, cache);
-            _nmp_object_fixup_link_udev_fields(&obj_hand_over, NULL, cache->use_udev);
-        }
-
-        _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new);
-        NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj));
-        return NMP_CACHE_OPS_ADDED;
-    }
-
-    obj_old = entry_old->obj;
-
-    if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_LINK) {
-        if (!obj_hand_over->_link.netlink.is_in_netlink) {
-            if (!obj_old->_link.netlink.is_in_netlink) {
-                nm_assert(obj_old->_link.udev.device);
-                NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-                NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old));
-                return NMP_CACHE_OPS_UNCHANGED;
-            }
-            if (obj_old->_link.udev.device) {
-                /* @obj_hand_over is not in netlink.
-                 *
-                 * This is similar to nmp_cache_remove_netlink(), but there we preserve the
-                 * preexisting netlink properties. The use case of that is when kernel_get_object()
-                 * cannot load an object (based on the id of a needle).
-                 *
-                 * Here we keep the data provided from @obj_hand_over. The usecase is when receiving
-                 * a valid @obj_hand_over instance from netlink with RTM_DELROUTE.
-                 */
-                is_alive = TRUE;
-            } else
-                is_alive = FALSE;
-        } else
-            is_alive = TRUE;
-
-        if (is_alive) {
-            _nmp_object_fixup_link_master_connected(&obj_hand_over, NULL, cache);
-
-            /* Merge the netlink parts with what we have from udev. */
-            udev_device_unref(obj_hand_over->_link.udev.device);
-            obj_hand_over->_link.udev.device =
-                obj_old->_link.udev.device ? udev_device_ref(obj_old->_link.udev.device) : NULL;
-            _nmp_object_fixup_link_udev_fields(&obj_hand_over, NULL, cache->use_udev);
-
-            if (obj_hand_over->_link.netlink.lnk) {
-                nm_auto_nmpobj const NMPObject *lnk_old = obj_hand_over->_link.netlink.lnk;
-
-                /* let's dedup/intern the lnk object. */
-                obj_hand_over->_link.netlink.lnk =
-                    nm_dedup_multi_index_obj_intern(cache->multi_idx, lnk_old);
-            }
-        }
-    } else
-        is_alive = nmp_object_is_alive(obj_hand_over);
-
-    NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-
-    if (!is_alive) {
-        /* the update would make @obj_old invalid. Remove it. */
-        _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
-        NM_SET_OUT(out_obj_new, NULL);
-        return NMP_CACHE_OPS_REMOVED;
-    }
-
-    if (nmp_object_equal(obj_old, obj_hand_over)) {
-        if (is_dump)
-            _idxcache_update_order_for_dump(cache, entry_old);
-        nm_dedup_multi_entry_set_dirty(entry_old, FALSE);
-        NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old));
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-
-    _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new);
-    NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj));
-    return NMP_CACHE_OPS_UPDATED;
-}
-
-NMPCacheOpsType
-nmp_cache_update_netlink_route(NMPCache *        cache,
-                               NMPObject *       obj_hand_over,
-                               gboolean          is_dump,
-                               guint16           nlmsgflags,
-                               const NMPObject **out_obj_old,
-                               const NMPObject **out_obj_new,
-                               const NMPObject **out_obj_replace,
-                               gboolean *        out_resync_required)
-{
-    NMDedupMultiIter             iter;
-    const NMDedupMultiEntry *    entry_old;
-    const NMDedupMultiEntry *    entry_new;
-    const NMDedupMultiEntry *    entry_cur;
-    const NMDedupMultiEntry *    entry_replace;
-    const NMDedupMultiHeadEntry *head_entry;
-    gboolean                     is_alive;
-    NMPCacheOpsType              ops_type = NMP_CACHE_OPS_UNCHANGED;
-    gboolean                     resync_required;
-
-    nm_assert(cache);
-    nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over));
-    nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over));
-    /* A link object from netlink must have the udev related fields unset.
-     * We could implement to handle that, but there is no need to support such
-     * a use-case */
-    nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_hand_over),
-                        NMP_OBJECT_TYPE_IP4_ROUTE,
-                        NMP_OBJECT_TYPE_IP6_ROUTE));
-    nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over);
-
-    entry_old = _lookup_entry(cache, obj_hand_over);
-    entry_new = NULL;
-
-    NM_SET_OUT(out_obj_old, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_old)));
-
-    if (!entry_old) {
-        if (!nmp_object_is_alive(obj_hand_over))
-            goto update_done;
-
-        _idxcache_update(cache, NULL, obj_hand_over, is_dump, &entry_new);
-        ops_type = NMP_CACHE_OPS_ADDED;
-        goto update_done;
-    }
-
-    is_alive = nmp_object_is_alive(obj_hand_over);
-
-    if (!is_alive) {
-        /* the update would make @entry_old invalid. Remove it. */
-        _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
-        ops_type = NMP_CACHE_OPS_REMOVED;
-        goto update_done;
-    }
-
-    if (nmp_object_equal(entry_old->obj, obj_hand_over)) {
-        if (is_dump)
-            _idxcache_update_order_for_dump(cache, entry_old);
-        nm_dedup_multi_entry_set_dirty(entry_old, FALSE);
-        goto update_done;
-    }
-
-    _idxcache_update(cache, entry_old, obj_hand_over, is_dump, &entry_new);
-    ops_type = NMP_CACHE_OPS_UPDATED;
-
-update_done:
-    NM_SET_OUT(out_obj_new, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_new)));
-
-    /* a RTM_GETROUTE event may signal that another object was replaced.
-     * Find out whether that is the case and return it as @obj_replaced.
-     *
-     * Also, fixup the order of @entry_new within NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID
-     * index. For most parts, we don't care about the order of objects (including routes).
-     * But NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID we must keep in the correct order, to
-     * properly find @obj_replaced. */
-    resync_required = FALSE;
-    entry_replace   = NULL;
-    if (is_dump)
-        goto out;
-
-    if (!entry_new) {
-        if (NM_FLAGS_HAS(nlmsgflags, NLM_F_REPLACE)
-            && nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, obj_hand_over)) {
-            /* hm. @obj_hand_over was not added, meaning it was not alive.
-             * However, we track some other objects with the same weak-id.
-             * It's unclear what that means. To be sure, resync. */
-            resync_required = TRUE;
-        }
-        goto out;
-    }
-
-    /* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID
-     * index. For all other indexes their order becomes messed up. */
-    entry_cur =
-        _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_new->obj);
-    if (!entry_cur) {
-        nm_assert_not_reached();
-        goto out;
-    }
-    nm_assert(entry_cur->obj == entry_new->obj);
-
-    head_entry = entry_cur->head;
-    nm_assert(head_entry
-              == nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_cur->obj));
-
-    if (head_entry->len == 1) {
-        /* there is only one object, and we expect it to be @obj_new. */
-        nm_assert(nm_dedup_multi_head_entry_get_idx(head_entry, 0) == entry_cur);
-        goto out;
-    }
-
-    switch (nlmsgflags & (NLM_F_REPLACE | NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND)) {
-    case NLM_F_REPLACE:
-        /* ip route change */
-
-        /* get the first element (but skip @obj_new). */
-        nm_dedup_multi_iter_init(&iter, head_entry);
-        if (!nm_dedup_multi_iter_next(&iter))
-            nm_assert_not_reached();
-        if (iter.current == entry_cur) {
-            if (!nm_dedup_multi_iter_next(&iter))
-                nm_assert_not_reached();
-        }
-        entry_replace = iter.current;
-
-        nm_assert(entry_replace && entry_cur != entry_replace);
-
-        nm_dedup_multi_entry_reorder(entry_cur, entry_replace, FALSE);
-        break;
-    case NLM_F_CREATE | NLM_F_APPEND:
-        /* ip route append */
-        nm_dedup_multi_entry_reorder(entry_cur, NULL, TRUE);
-        break;
-    case NLM_F_CREATE:
-        /* ip route prepend */
-        nm_dedup_multi_entry_reorder(entry_cur, NULL, FALSE);
-        break;
-    default:
-        /* this is an unexpected case, probably a bug that we need to handle better. */
-        resync_required = TRUE;
-        break;
-    }
-
-out:
-    NM_SET_OUT(out_obj_replace, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_replace)));
-    NM_SET_OUT(out_resync_required, resync_required);
-    return ops_type;
-}
-
-NMPCacheOpsType
-nmp_cache_update_link_udev(NMPCache *          cache,
-                           int                 ifindex,
-                           struct udev_device *udevice,
-                           const NMPObject **  out_obj_old,
-                           const NMPObject **  out_obj_new)
-{
-    const NMPObject *obj_old;
-    nm_auto_nmpobj NMPObject *obj_new = NULL;
-    const NMDedupMultiEntry * entry_old;
-    const NMDedupMultiEntry * entry_new;
-
-    entry_old = nmp_cache_lookup_entry_link(cache, ifindex);
-
-    if (!entry_old) {
-        if (!udevice) {
-            NM_SET_OUT(out_obj_old, NULL);
-            NM_SET_OUT(out_obj_new, NULL);
-            return NMP_CACHE_OPS_UNCHANGED;
-        }
-
-        obj_new                    = nmp_object_new(NMP_OBJECT_TYPE_LINK, NULL);
-        obj_new->link.ifindex      = ifindex;
-        obj_new->_link.udev.device = udev_device_ref(udevice);
-
-        _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev);
-
-        _idxcache_update(cache, NULL, obj_new, FALSE, &entry_new);
-        NM_SET_OUT(out_obj_old, NULL);
-        NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj));
-        return NMP_CACHE_OPS_ADDED;
-    } else {
-        obj_old = entry_old->obj;
-        NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-
-        if (obj_old->_link.udev.device == udevice) {
-            NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old));
-            return NMP_CACHE_OPS_UNCHANGED;
-        }
-
-        if (!udevice && !obj_old->_link.netlink.is_in_netlink) {
-            /* the update would make @obj_old invalid. Remove it. */
-            _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
-            NM_SET_OUT(out_obj_new, NULL);
-            return NMP_CACHE_OPS_REMOVED;
-        }
-
-        obj_new = nmp_object_clone(obj_old, FALSE);
-
-        udev_device_unref(obj_new->_link.udev.device);
-        obj_new->_link.udev.device = udevice ? udev_device_ref(udevice) : NULL;
-
-        _nmp_object_fixup_link_udev_fields(&obj_new, NULL, cache->use_udev);
-
-        _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new);
-        NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj));
-        return NMP_CACHE_OPS_UPDATED;
-    }
-}
-
-NMPCacheOpsType
-nmp_cache_update_link_master_connected(NMPCache *        cache,
-                                       int               ifindex,
-                                       const NMPObject **out_obj_old,
-                                       const NMPObject **out_obj_new)
-{
-    const NMDedupMultiEntry *entry_old;
-    const NMDedupMultiEntry *entry_new = NULL;
-    const NMPObject *        obj_old;
-    nm_auto_nmpobj NMPObject *obj_new = NULL;
-
-    entry_old = nmp_cache_lookup_entry_link(cache, ifindex);
-
-    if (!entry_old) {
-        NM_SET_OUT(out_obj_old, NULL);
-        NM_SET_OUT(out_obj_new, NULL);
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-
-    obj_old = entry_old->obj;
-
-    if (!nmp_cache_link_connected_needs_toggle(cache, obj_old, NULL, NULL)) {
-        NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-        NM_SET_OUT(out_obj_new, nmp_object_ref(obj_old));
-        return NMP_CACHE_OPS_UNCHANGED;
-    }
-
-    obj_new                 = nmp_object_clone(obj_old, FALSE);
-    obj_new->link.connected = !obj_old->link.connected;
-
-    NM_SET_OUT(out_obj_old, nmp_object_ref(obj_old));
-    _idxcache_update(cache, entry_old, obj_new, FALSE, &entry_new);
-    NM_SET_OUT(out_obj_new, nmp_object_ref(entry_new->obj));
-    return NMP_CACHE_OPS_UPDATED;
-}
-
-/*****************************************************************************/
-
-void
-nmp_cache_dirty_set_all_main(NMPCache *cache, const NMPLookup *lookup)
-{
-    const NMDedupMultiHeadEntry *head_entry;
-    NMDedupMultiIter             iter;
-
-    nm_assert(cache);
-    nm_assert(lookup);
-
-    head_entry = nmp_cache_lookup(cache, lookup);
-
-    nm_dedup_multi_iter_init(&iter, head_entry);
-    while (nm_dedup_multi_iter_next(&iter)) {
-        const NMDedupMultiEntry *main_entry;
-
-        main_entry = nmp_cache_reresolve_main_entry(cache, iter.current, lookup);
-
-        nm_dedup_multi_entry_set_dirty(main_entry, TRUE);
-    }
-}
-
-/*****************************************************************************/
-
-NMPCache *
-nmp_cache_new(NMDedupMultiIndex *multi_idx, gboolean use_udev)
-{
-    NMPCache *cache = g_slice_new0(NMPCache);
-    guint     i;
-
-    for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++)
-        _dedup_multi_idx_type_init((DedupMultiIdxType *) _idx_type_get(cache, i), i);
-
-    cache->multi_idx = nm_dedup_multi_index_ref(multi_idx);
-
-    cache->use_udev = !!use_udev;
-    return cache;
-}
-
-void
-nmp_cache_free(NMPCache *cache)
-{
-    guint i;
-
-    for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++)
-        nm_dedup_multi_index_remove_idx(cache->multi_idx, _idx_type_get(cache, i));
-
-    nm_dedup_multi_index_unref(cache->multi_idx);
-
-    g_slice_free(NMPCache, cache);
-}
-
-/*****************************************************************************/
-
-void
-nmtst_assert_nmp_cache_is_consistent(const NMPCache *cache)
-{}
-
-/*****************************************************************************/
-
-/* below, ensure that addr_family get's automatically initialize to AF_UNSPEC. */
-G_STATIC_ASSERT(AF_UNSPEC == 0);
-
-typedef const char *(*CmdPlobjToStringFunc)(const NMPlatformObject *obj, char *buf, gsize len);
-typedef const char *(*CmdPlobjToStringIdFunc)(const NMPlatformObject *obj, char *buf, gsize len);
-typedef void (*CmdPlobjHashUpdateFunc)(const NMPlatformObject *obj, NMHashState *h);
-typedef int (*CmdPlobjCmpFunc)(const NMPlatformObject *obj1, const NMPlatformObject *obj2);
-
-const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
-    [NMP_OBJECT_TYPE_LINK - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_LINK,
-            .sizeof_data              = sizeof(NMPObjectLink),
-            .sizeof_public            = sizeof(NMPlatformLink),
-            .obj_type_name            = "link",
-            .rtm_gettype              = RTM_GETLINK,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_LINK,
-            .signal_type              = NM_PLATFORM_SIGNAL_LINK_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_link,
-            .cmd_obj_hash_update      = _vt_cmd_obj_hash_update_link,
-            .cmd_obj_cmp              = _vt_cmd_obj_cmp_link,
-            .cmd_obj_copy             = _vt_cmd_obj_copy_link,
-            .cmd_obj_dispose          = _vt_cmd_obj_dispose_link,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_link,
-            .cmd_obj_is_visible       = _vt_cmd_obj_is_visible_link,
-            .cmd_obj_to_string        = _vt_cmd_obj_to_string_link,
-            .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_link,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_link,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_link,
-            .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_link,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_link_to_string,
-            .cmd_plobj_hash_update    = (CmdPlobjHashUpdateFunc) nm_platform_link_hash_update,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_link_cmp,
-        },
-    [NMP_OBJECT_TYPE_IP4_ADDRESS - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_IP4_ADDRESS,
-            .sizeof_data              = sizeof(NMPObjectIP4Address),
-            .sizeof_public            = sizeof(NMPlatformIP4Address),
-            .obj_type_name            = "ip4-address",
-            .addr_family              = AF_INET,
-            .rtm_gettype              = RTM_GETADDR,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
-            .signal_type              = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_ipx_address,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_ipx_address,
-            .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_ip4_address,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_ip4_address,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_address,
-            .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_ip4_address,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_ip4_address_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_ip4_address_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_ip4_address_cmp,
-        },
-    [NMP_OBJECT_TYPE_IP6_ADDRESS
-        - 1] = {.parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-                .obj_type                 = NMP_OBJECT_TYPE_IP6_ADDRESS,
-                .sizeof_data              = sizeof(NMPObjectIP6Address),
-                .sizeof_public            = sizeof(NMPlatformIP6Address),
-                .obj_type_name            = "ip6-address",
-                .addr_family              = AF_INET6,
-                .rtm_gettype              = RTM_GETADDR,
-                .signal_type_id           = NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
-                .signal_type              = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
-                .supported_cache_ids      = _supported_cache_ids_ipx_address,
-                .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_ipx_address,
-                .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_ip6_address,
-                .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_ip6_address,
-                .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_address,
-                .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_ip6_address,
-                .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_ip6_address_to_string,
-                .cmd_plobj_hash_update =
-                    (CmdPlobjHashUpdateFunc) nm_platform_ip6_address_hash_update,
-                .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_ip6_address_cmp},
-    [NMP_OBJECT_TYPE_IP4_ROUTE - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_IP4_ROUTE,
-            .sizeof_data              = sizeof(NMPObjectIP4Route),
-            .sizeof_public            = sizeof(NMPlatformIP4Route),
-            .obj_type_name            = "ip4-route",
-            .addr_family              = AF_INET,
-            .rtm_gettype              = RTM_GETROUTE,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
-            .signal_type              = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_ipx_route,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_ipx_route,
-            .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_ip4_route,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_ip4_route,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_route,
-            .cmd_plobj_to_string_id   = (CmdPlobjToStringIdFunc) nm_platform_ip4_route_to_string,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_ip4_route_to_string,
-            .cmd_plobj_hash_update    = _vt_cmd_plobj_hash_update_ip4_route,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_ip4_route_cmp_full,
-        },
-    [NMP_OBJECT_TYPE_IP6_ROUTE - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_IP6_ROUTE,
-            .sizeof_data              = sizeof(NMPObjectIP6Route),
-            .sizeof_public            = sizeof(NMPlatformIP6Route),
-            .obj_type_name            = "ip6-route",
-            .addr_family              = AF_INET6,
-            .rtm_gettype              = RTM_GETROUTE,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
-            .signal_type              = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_ipx_route,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_ipx_route,
-            .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_ip6_route,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_ip6_route,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_route,
-            .cmd_plobj_to_string_id   = (CmdPlobjToStringIdFunc) nm_platform_ip6_route_to_string,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_ip6_route_to_string,
-            .cmd_plobj_hash_update    = _vt_cmd_plobj_hash_update_ip6_route,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_ip6_route_cmp_full,
-        },
-    [NMP_OBJECT_TYPE_ROUTING_RULE - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_ROUTING_RULE,
-            .sizeof_data              = sizeof(NMPObjectRoutingRule),
-            .sizeof_public            = sizeof(NMPlatformRoutingRule),
-            .obj_type_name            = "routing-rule",
-            .rtm_gettype              = RTM_GETRULE,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_ROUTING_RULE,
-            .signal_type              = NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_routing_rules,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_routing_rule,
-            .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_routing_rule,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_routing_rule,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_routing_rule,
-            .cmd_plobj_to_string_id   = (CmdPlobjToStringIdFunc) nm_platform_routing_rule_to_string,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_routing_rule_to_string,
-            .cmd_plobj_hash_update    = _vt_cmd_plobj_hash_update_routing_rule,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_routing_rule_cmp_full,
-        },
-    [NMP_OBJECT_TYPE_QDISC - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_QDISC,
-            .sizeof_data              = sizeof(NMPObjectQdisc),
-            .sizeof_public            = sizeof(NMPlatformQdisc),
-            .obj_type_name            = "qdisc",
-            .rtm_gettype              = RTM_GETQDISC,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_QDISC,
-            .signal_type              = NM_PLATFORM_SIGNAL_QDISC_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_object,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_qdisc,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_qdisc,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_qdisc,
-            .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_qdisc,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_qdisc_to_string,
-            .cmd_plobj_hash_update    = (CmdPlobjHashUpdateFunc) nm_platform_qdisc_hash_update,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_qdisc_cmp,
-        },
-    [NMP_OBJECT_TYPE_TFILTER - 1] =
-        {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_TFILTER,
-            .sizeof_data              = sizeof(NMPObjectTfilter),
-            .sizeof_public            = sizeof(NMPlatformTfilter),
-            .obj_type_name            = "tfilter",
-            .rtm_gettype              = RTM_GETTFILTER,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_TFILTER,
-            .signal_type              = NM_PLATFORM_SIGNAL_TFILTER_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_object,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_tfilter,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_tfilter,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_tfilter,
-            .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_tfilter,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_tfilter_to_string,
-            .cmd_plobj_hash_update    = (CmdPlobjHashUpdateFunc) nm_platform_tfilter_hash_update,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_tfilter_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_BRIDGE - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_BRIDGE,
-            .sizeof_data           = sizeof(NMPObjectLnkBridge),
-            .sizeof_public         = sizeof(NMPlatformLnkBridge),
-            .obj_type_name         = "bridge",
-            .lnk_link_type         = NM_LINK_TYPE_BRIDGE,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_bridge_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_bridge_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_bridge_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_GRE - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_GRE,
-            .sizeof_data           = sizeof(NMPObjectLnkGre),
-            .sizeof_public         = sizeof(NMPlatformLnkGre),
-            .obj_type_name         = "gre",
-            .lnk_link_type         = NM_LINK_TYPE_GRE,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_gre_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_gre_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_gre_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_GRETAP - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_GRETAP,
-            .sizeof_data           = sizeof(NMPObjectLnkGre),
-            .sizeof_public         = sizeof(NMPlatformLnkGre),
-            .obj_type_name         = "gretap",
-            .lnk_link_type         = NM_LINK_TYPE_GRETAP,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_gre_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_gre_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_gre_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] =
-        {
-            .parent              = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type            = NMP_OBJECT_TYPE_LNK_INFINIBAND,
-            .sizeof_data         = sizeof(NMPObjectLnkInfiniband),
-            .sizeof_public       = sizeof(NMPlatformLnkInfiniband),
-            .obj_type_name       = "infiniband",
-            .lnk_link_type       = NM_LINK_TYPE_INFINIBAND,
-            .cmd_plobj_to_string = (CmdPlobjToStringFunc) nm_platform_lnk_infiniband_to_string,
-            .cmd_plobj_hash_update =
-                (CmdPlobjHashUpdateFunc) nm_platform_lnk_infiniband_hash_update,
-            .cmd_plobj_cmp = (CmdPlobjCmpFunc) nm_platform_lnk_infiniband_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_IP6TNL - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_IP6TNL,
-            .sizeof_data           = sizeof(NMPObjectLnkIp6Tnl),
-            .sizeof_public         = sizeof(NMPlatformLnkIp6Tnl),
-            .obj_type_name         = "ip6tnl",
-            .lnk_link_type         = NM_LINK_TYPE_IP6TNL,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_IP6GRE - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_IP6GRE,
-            .sizeof_data           = sizeof(NMPObjectLnkIp6Tnl),
-            .sizeof_public         = sizeof(NMPlatformLnkIp6Tnl),
-            .obj_type_name         = "ip6gre",
-            .lnk_link_type         = NM_LINK_TYPE_IP6GRE,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_IP6GRETAP - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_IP6GRETAP,
-            .sizeof_data           = sizeof(NMPObjectLnkIp6Tnl),
-            .sizeof_public         = sizeof(NMPlatformLnkIp6Tnl),
-            .obj_type_name         = "ip6gretap",
-            .lnk_link_type         = NM_LINK_TYPE_IP6GRETAP,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_ip6tnl_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ip6tnl_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_ip6tnl_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_IPIP - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_IPIP,
-            .sizeof_data           = sizeof(NMPObjectLnkIpIp),
-            .sizeof_public         = sizeof(NMPlatformLnkIpIp),
-            .obj_type_name         = "ipip",
-            .lnk_link_type         = NM_LINK_TYPE_IPIP,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_ipip_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ipip_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_ipip_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_MACSEC - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_MACSEC,
-            .sizeof_data           = sizeof(NMPObjectLnkMacsec),
-            .sizeof_public         = sizeof(NMPlatformLnkMacsec),
-            .obj_type_name         = "macsec",
-            .lnk_link_type         = NM_LINK_TYPE_MACSEC,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_macsec_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macsec_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_macsec_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_MACVLAN - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_MACVLAN,
-            .sizeof_data           = sizeof(NMPObjectLnkMacvlan),
-            .sizeof_public         = sizeof(NMPlatformLnkMacvlan),
-            .obj_type_name         = "macvlan",
-            .lnk_link_type         = NM_LINK_TYPE_MACVLAN,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_macvlan_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macvlan_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_macvlan_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_MACVTAP - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_MACVTAP,
-            .sizeof_data           = sizeof(NMPObjectLnkMacvtap),
-            .sizeof_public         = sizeof(NMPlatformLnkMacvlan),
-            .obj_type_name         = "macvtap",
-            .lnk_link_type         = NM_LINK_TYPE_MACVTAP,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_macvlan_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_macvlan_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_macvlan_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_SIT - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_SIT,
-            .sizeof_data           = sizeof(NMPObjectLnkSit),
-            .sizeof_public         = sizeof(NMPlatformLnkSit),
-            .obj_type_name         = "sit",
-            .lnk_link_type         = NM_LINK_TYPE_SIT,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_sit_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_sit_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_sit_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_TUN - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_TUN,
-            .sizeof_data           = sizeof(NMPObjectLnkTun),
-            .sizeof_public         = sizeof(NMPlatformLnkTun),
-            .obj_type_name         = "tun",
-            .lnk_link_type         = NM_LINK_TYPE_TUN,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_tun_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_tun_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_tun_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_VLAN - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_VLAN,
-            .sizeof_data           = sizeof(NMPObjectLnkVlan),
-            .sizeof_public         = sizeof(NMPlatformLnkVlan),
-            .obj_type_name         = "vlan",
-            .lnk_link_type         = NM_LINK_TYPE_VLAN,
-            .cmd_obj_hash_update   = _vt_cmd_obj_hash_update_lnk_vlan,
-            .cmd_obj_cmp           = _vt_cmd_obj_cmp_lnk_vlan,
-            .cmd_obj_copy          = _vt_cmd_obj_copy_lnk_vlan,
-            .cmd_obj_dispose       = _vt_cmd_obj_dispose_lnk_vlan,
-            .cmd_obj_to_string     = _vt_cmd_obj_to_string_lnk_vlan,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_vlan_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vlan_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) 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   = (CmdPlobjToStringFunc) nm_platform_lnk_vrf_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vrf_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_vrf_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_VXLAN - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_VXLAN,
-            .sizeof_data           = sizeof(NMPObjectLnkVxlan),
-            .sizeof_public         = sizeof(NMPlatformLnkVxlan),
-            .obj_type_name         = "vxlan",
-            .lnk_link_type         = NM_LINK_TYPE_VXLAN,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_vxlan_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vxlan_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_vxlan_cmp,
-        },
-    [NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] =
-        {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_WIREGUARD,
-            .sizeof_data           = sizeof(NMPObjectLnkWireGuard),
-            .sizeof_public         = sizeof(NMPlatformLnkWireGuard),
-            .obj_type_name         = "wireguard",
-            .lnk_link_type         = NM_LINK_TYPE_WIREGUARD,
-            .cmd_obj_hash_update   = _vt_cmd_obj_hash_update_lnk_wireguard,
-            .cmd_obj_cmp           = _vt_cmd_obj_cmp_lnk_wireguard,
-            .cmd_obj_copy          = _vt_cmd_obj_copy_lnk_wireguard,
-            .cmd_obj_dispose       = _vt_cmd_obj_dispose_lnk_wireguard,
-            .cmd_obj_to_string     = _vt_cmd_obj_to_string_lnk_wireguard,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_wireguard_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_wireguard_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_wireguard_cmp,
-        },
-};
diff --git a/src/core/platform/nmp-object.h b/src/core/platform/nmp-object.h
deleted file mode 100644
index 19f6bcd7..00000000
--- a/src/core/platform/nmp-object.h
+++ /dev/null
@@ -1,1144 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2015 - 2018 Red Hat, Inc.
- */
-
-#ifndef __NMP_OBJECT_H__
-#define __NMP_OBJECT_H__
-
-#include <netinet/in.h>
-
-#include "nm-glib-aux/nm-obj.h"
-#include "nm-glib-aux/nm-dedup-multi.h"
-#include "nm-platform.h"
-
-struct udev_device;
-
-/*****************************************************************************/
-
-/* "struct __kernel_timespec" uses "long long", but we use gint64. In practice,
- * these are the same types. */
-G_STATIC_ASSERT(sizeof(long long) == sizeof(gint64));
-
-typedef struct {
-    /* like "struct __kernel_timespec". */
-    gint64 tv_sec;
-    gint64 tv_nsec;
-} NMPTimespec64;
-
-/*****************************************************************************/
-
-typedef union {
-    struct sockaddr     sa;
-    struct sockaddr_in  in;
-    struct sockaddr_in6 in6;
-} NMSockAddrUnion;
-
-G_STATIC_ASSERT(sizeof(NMSockAddrUnion) == sizeof(((NMSockAddrUnion *) NULL)->in6));
-
-/* we initialize the largest union member, to ensure that all fields are initialized. */
-
-#define NM_SOCK_ADDR_UNION_INIT_UNSPEC \
-    {                                  \
-        .in6 = {                       \
-            .sin6_family = AF_UNSPEC,  \
-        },                             \
-    }
-
-int nm_sock_addr_union_cmp(const NMSockAddrUnion *a, const NMSockAddrUnion *b);
-
-void nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h);
-
-void nm_sock_addr_union_cpy(NMSockAddrUnion *dst,
-                            gconstpointer    src /* unaligned (const NMSockAddrUnion *) */);
-
-void nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst,
-                                      gconstpointer src /* unaligned (const NMSockAddrUnion *) */,
-                                      gsize         src_len);
-
-const char *nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len);
-
-/*****************************************************************************/
-
-typedef struct {
-    NMIPAddr addr;
-    guint8   family;
-    guint8   mask;
-} NMPWireGuardAllowedIP;
-
-typedef struct _NMPWireGuardPeer {
-    NMSockAddrUnion endpoint;
-
-    NMPTimespec64 last_handshake_time;
-
-    guint64 rx_bytes;
-    guint64 tx_bytes;
-
-    union {
-        const NMPWireGuardAllowedIP *allowed_ips;
-        guint                        _construct_idx_start;
-    };
-    union {
-        guint allowed_ips_len;
-        guint _construct_idx_end;
-    };
-
-    guint16 persistent_keepalive_interval;
-
-    guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
-    guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN];
-} NMPWireGuardPeer;
-
-/*****************************************************************************/
-
-typedef enum { /*< skip >*/
-               NMP_OBJECT_TO_STRING_ID,
-               NMP_OBJECT_TO_STRING_PUBLIC,
-               NMP_OBJECT_TO_STRING_ALL,
-} NMPObjectToStringMode;
-
-typedef enum { /*< skip >*/
-               NMP_CACHE_OPS_UNCHANGED = NM_PLATFORM_SIGNAL_NONE,
-               NMP_CACHE_OPS_ADDED     = NM_PLATFORM_SIGNAL_ADDED,
-               NMP_CACHE_OPS_UPDATED   = NM_PLATFORM_SIGNAL_CHANGED,
-               NMP_CACHE_OPS_REMOVED   = NM_PLATFORM_SIGNAL_REMOVED,
-} NMPCacheOpsType;
-
-/* The NMPCacheIdType are the different index types.
- *
- * An object of a certain object-type, can be candidate to being
- * indexed by a certain NMPCacheIdType or not. For example, all
- * objects are indexed via an index of type NMP_CACHE_ID_TYPE_OBJECT_TYPE,
- * but only route objects can be indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT.
- *
- * Of one index type, there can be multiple indexes or not.
- * For example, of the index type NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX there
- * are multiple instances (for different route/addresses, v4/v6, per-ifindex).
- *
- * But one object, can only be indexed by one particular index of a
- * type. For example, a certain address instance is only indexed by
- * the index NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX with
- * matching v4/v6 and ifindex -- or maybe not at all if it isn't visible.
- * */
-typedef enum { /*< skip >*/
-               NMP_CACHE_ID_TYPE_NONE,
-
-               /* all the objects of a certain type.
-     *
-     * This index is special. It is the only one that contains *all* object.
-     * Other indexes may consider some object as non "partitionable", hence
-     * they don't track all objects.
-     *
-     * Hence, this index type is used when looking at all objects (still
-     * partitioned by type).
-     *
-     * Also, note that links may be considered invisible. This index type
-     * expose all links, even invisible ones. For addresses/routes, this
-     * distinction doesn't exist, as all addresses/routes that are alive
-     * are visible as well. */
-               NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-
-               /* index for the link objects by ifname. */
-               NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
-
-               /* indices for the visible default-routes, ignoring ifindex.
-     * This index only contains two partitions: all visible default-routes,
-     * separate for IPv4 and IPv6. */
-               NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
-
-               /* all the objects that have an ifindex (by object-type) for an ifindex. */
-               NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
-
-               /* Consider all the destination fields of a route, that is, the ID without the ifindex
-     * and gateway (meaning: network/plen,metric).
-     * The reason for this is that `ip route change` can replace an existing route
-     * and modify its ifindex/gateway. Effectively, that means it deletes an existing
-     * route and adds a different one (as the ID of the route changes). However, it only
-     * sends one RTM_NEWADDR notification without notifying about the deletion. We detect
-     * that by having this index to contain overlapping routes which require special
-     * cache-resync. */
-               NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
-
-               /* a filter for objects that track an explicit address family.
-     *
-     * Note that currently on NMPObjectRoutingRule is indexed by this filter. */
-               NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY,
-
-               __NMP_CACHE_ID_TYPE_MAX,
-               NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1,
-} NMPCacheIdType;
-
-typedef struct {
-    NMDedupMultiObjClass   parent;
-    const char *           obj_type_name;
-    const char *           signal_type;
-    const guint8 *         supported_cache_ids;
-    int                    sizeof_data;
-    int                    sizeof_public;
-    int                    addr_family;
-    int                    rtm_gettype;
-    NMPObjectType          obj_type;
-    NMPlatformSignalIdType signal_type_id;
-
-    /* Only for NMPObjectLnk* types. */
-    NMLinkType lnk_link_type;
-
-    void (*cmd_obj_hash_update)(const NMPObject *obj, NMHashState *h);
-    int (*cmd_obj_cmp)(const NMPObject *obj1, const NMPObject *obj2);
-    void (*cmd_obj_copy)(NMPObject *dst, const NMPObject *src);
-    void (*cmd_obj_dispose)(NMPObject *obj);
-    gboolean (*cmd_obj_is_alive)(const NMPObject *obj);
-    gboolean (*cmd_obj_is_visible)(const NMPObject *obj);
-    const char *(*cmd_obj_to_string)(const NMPObject *     obj,
-                                     NMPObjectToStringMode to_string_mode,
-                                     char *                buf,
-                                     gsize                 buf_size);
-
-    /* functions that operate on NMPlatformObject */
-    void (*cmd_plobj_id_copy)(NMPlatformObject *dst, const NMPlatformObject *src);
-    int (*cmd_plobj_id_cmp)(const NMPlatformObject *obj1, const NMPlatformObject *obj2);
-    void (*cmd_plobj_id_hash_update)(const NMPlatformObject *obj, NMHashState *h);
-    const char *(*cmd_plobj_to_string_id)(const NMPlatformObject *obj, char *buf, gsize buf_size);
-    const char *(*cmd_plobj_to_string)(const NMPlatformObject *obj, char *buf, gsize len);
-    void (*cmd_plobj_hash_update)(const NMPlatformObject *obj, NMHashState *h);
-    int (*cmd_plobj_cmp)(const NMPlatformObject *obj1, const NMPlatformObject *obj2);
-} NMPClass;
-
-extern const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX];
-
-typedef struct {
-    NMPlatformLink _public;
-
-    struct {
-        bool is_in_netlink;
-
-        /* Additional data that depends on the link-type (IFLA_INFO_DATA) */
-        const NMPObject *lnk;
-    } netlink;
-
-    struct {
-        /* note that "struct udev_device" references the library context
-         * "struct udev", but doesn't own it.
-         *
-         * Hence, the udev.device shall not be used after the library
-         * context is destroyed.
-         *
-         * In case of NMPObjectLink instances that you obtained from the
-         * platform cache, that means that you shall no keep references
-         * to those instances that outlife the NMPlatform instance.
-         *
-         * In practice, the requirement is less strict and you'll be even
-         * fine if the platform instance (and the "struct udev" instance)
-         * are already destroyed while you still hold onto a reference to
-         * the NMPObjectLink instance. Just don't make use of udev functions
-         * that cause access to the udev library context.
-         */
-        struct udev_device *device;
-    } udev;
-
-    /* Auxiliary data object for Wi-Fi and WPAN */
-    GObject *ext_data;
-
-    /* FIXME: not every NMPObjectLink should pay the price for tracking
-     * the wireguard family id. This should be tracked via ext_data, which
-     * would be exactly the right place. */
-    int wireguard_family_id;
-} NMPObjectLink;
-
-typedef struct {
-    NMPlatformLnkBridge _public;
-} NMPObjectLnkBridge;
-
-typedef struct {
-    NMPlatformLnkGre _public;
-} NMPObjectLnkGre;
-
-typedef struct {
-    NMPlatformLnkInfiniband _public;
-} NMPObjectLnkInfiniband;
-
-typedef struct {
-    NMPlatformLnkIp6Tnl _public;
-} NMPObjectLnkIp6Tnl;
-
-typedef struct {
-    NMPlatformLnkIpIp _public;
-} NMPObjectLnkIpIp;
-
-typedef struct {
-    NMPlatformLnkMacsec _public;
-} NMPObjectLnkMacsec;
-
-typedef struct {
-    NMPlatformLnkMacvlan _public;
-} NMPObjectLnkMacvlan;
-
-typedef NMPObjectLnkMacvlan NMPObjectLnkMacvtap;
-
-typedef struct {
-    NMPlatformLnkSit _public;
-} NMPObjectLnkSit;
-
-typedef struct {
-    NMPlatformLnkTun _public;
-} NMPObjectLnkTun;
-
-typedef struct {
-    NMPlatformLnkVlan _public;
-
-    guint                   n_ingress_qos_map;
-    guint                   n_egress_qos_map;
-    const NMVlanQosMapping *ingress_qos_map;
-    const NMVlanQosMapping *egress_qos_map;
-} NMPObjectLnkVlan;
-
-typedef struct {
-    NMPlatformLnkVrf _public;
-} NMPObjectLnkVrf;
-
-typedef struct {
-    NMPlatformLnkVxlan _public;
-} NMPObjectLnkVxlan;
-
-typedef struct {
-    NMPlatformLnkWireGuard       _public;
-    const NMPWireGuardPeer *     peers;
-    const NMPWireGuardAllowedIP *_allowed_ips_buf;
-    guint                        peers_len;
-    guint                        _allowed_ips_buf_len;
-} NMPObjectLnkWireGuard;
-
-typedef struct {
-    NMPlatformIP4Address _public;
-} NMPObjectIP4Address;
-
-typedef struct {
-    NMPlatformIP4Route _public;
-} NMPObjectIP4Route;
-
-typedef struct {
-    NMPlatformIP6Address _public;
-} NMPObjectIP6Address;
-
-typedef struct {
-    NMPlatformIP6Route _public;
-} NMPObjectIP6Route;
-
-typedef struct {
-    NMPlatformRoutingRule _public;
-} NMPObjectRoutingRule;
-
-typedef struct {
-    NMPlatformQdisc _public;
-} NMPObjectQdisc;
-
-typedef struct {
-    NMPlatformTfilter _public;
-} NMPObjectTfilter;
-
-struct _NMPObject {
-    union {
-        NMDedupMultiObj parent;
-        const NMPClass *_class;
-    };
-    union {
-        NMPlatformObject object;
-
-        NMPlatformObjWithIfindex obj_with_ifindex;
-
-        NMPlatformLink link;
-        NMPObjectLink  _link;
-
-        NMPlatformLnkBridge lnk_bridge;
-        NMPObjectLnkBridge  _lnk_bridge;
-
-        NMPlatformLnkGre lnk_gre;
-        NMPObjectLnkGre  _lnk_gre;
-
-        NMPlatformLnkInfiniband lnk_infiniband;
-        NMPObjectLnkInfiniband  _lnk_infiniband;
-
-        NMPlatformLnkIpIp lnk_ipip;
-        NMPObjectLnkIpIp  _lnk_ipip;
-
-        NMPlatformLnkIp6Tnl lnk_ip6tnl;
-        NMPObjectLnkIp6Tnl  _lnk_ip6tnl;
-
-        NMPlatformLnkMacsec lnk_macsec;
-        NMPObjectLnkMacsec  _lnk_macsec;
-
-        NMPlatformLnkMacvlan lnk_macvlan;
-        NMPObjectLnkMacvlan  _lnk_macvlan;
-
-        NMPlatformLnkSit lnk_sit;
-        NMPObjectLnkSit  _lnk_sit;
-
-        NMPlatformLnkTun lnk_tun;
-        NMPObjectLnkTun  _lnk_tun;
-
-        NMPlatformLnkVlan lnk_vlan;
-        NMPObjectLnkVlan  _lnk_vlan;
-
-        NMPlatformLnkVrf lnk_vrf;
-        NMPObjectLnkVrf  _lnk_vrf;
-
-        NMPlatformLnkVxlan lnk_vxlan;
-        NMPObjectLnkVxlan  _lnk_vxlan;
-
-        NMPlatformLnkWireGuard lnk_wireguard;
-        NMPObjectLnkWireGuard  _lnk_wireguard;
-
-        NMPlatformIPAddress  ip_address;
-        NMPlatformIPXAddress ipx_address;
-        NMPlatformIP4Address ip4_address;
-        NMPlatformIP6Address ip6_address;
-        NMPObjectIP4Address  _ip4_address;
-        NMPObjectIP6Address  _ip6_address;
-
-        NMPlatformIPRoute  ip_route;
-        NMPlatformIPXRoute ipx_route;
-        NMPlatformIP4Route ip4_route;
-        NMPlatformIP6Route ip6_route;
-        NMPObjectIP4Route  _ip4_route;
-        NMPObjectIP6Route  _ip6_route;
-
-        NMPlatformRoutingRule routing_rule;
-        NMPObjectRoutingRule  _routing_rule;
-
-        NMPlatformQdisc   qdisc;
-        NMPObjectQdisc    _qdisc;
-        NMPlatformTfilter tfilter;
-        NMPObjectTfilter  _tfilter;
-    };
-};
-
-/*****************************************************************************/
-
-static inline gboolean
-NMP_CLASS_IS_VALID(const NMPClass *klass)
-{
-    return klass >= &_nmp_classes[0] && klass <= &_nmp_classes[G_N_ELEMENTS(_nmp_classes)]
-           && ((((char *) klass) - ((char *) _nmp_classes)) % (sizeof(_nmp_classes[0]))) == 0;
-}
-
-static inline const NMPClass *
-nmp_class_from_type(NMPObjectType obj_type)
-{
-    nm_assert(obj_type > 0);
-    nm_assert(obj_type <= G_N_ELEMENTS(_nmp_classes));
-    nm_assert(_nmp_classes[obj_type - 1].obj_type == obj_type);
-    nm_assert(NMP_CLASS_IS_VALID(&_nmp_classes[obj_type - 1]));
-
-    return &_nmp_classes[obj_type - 1];
-}
-
-static inline NMPObject *
-NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj)
-{
-    NMPObject *obj;
-
-    obj = plobj ? (NMPObject *) (&(((char *) plobj)[-((int) G_STRUCT_OFFSET(NMPObject, object))]))
-                : NULL;
-    nm_assert(!obj || (obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID(obj->_class)));
-    return obj;
-}
-#define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST((const NMPlatformObject *) (plobj)))
-
-static inline gboolean
-NMP_OBJECT_IS_VALID(const NMPObject *obj)
-{
-    nm_assert(!obj || (obj && obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID(obj->_class)));
-
-    /* There isn't really much to check. Either @obj is NULL, or we must
-     * assume that it points to valid memory. */
-    return obj != NULL;
-}
-
-static inline gboolean
-NMP_OBJECT_IS_STACKINIT(const NMPObject *obj)
-{
-    nm_assert(!obj || NMP_OBJECT_IS_VALID(obj));
-
-    return obj && obj->parent._ref_count == NM_OBJ_REF_COUNT_STACKINIT;
-}
-
-static inline const NMPClass *
-NMP_OBJECT_GET_CLASS(const NMPObject *obj)
-{
-    nm_assert(NMP_OBJECT_IS_VALID(obj));
-
-    return obj->_class;
-}
-
-static inline NMPObjectType
-NMP_OBJECT_GET_TYPE(const NMPObject *obj)
-{
-    nm_assert(!obj || NMP_OBJECT_IS_VALID(obj));
-
-    return obj ? obj->_class->obj_type : NMP_OBJECT_TYPE_UNKNOWN;
-}
-
-static inline gboolean
-_NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type)
-{
-    switch (obj_type) {
-    case NMP_OBJECT_TYPE_LINK:
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-
-    case NMP_OBJECT_TYPE_QDISC:
-
-    case NMP_OBJECT_TYPE_TFILTER:
-
-    case NMP_OBJECT_TYPE_LNK_BRIDGE:
-    case NMP_OBJECT_TYPE_LNK_GRE:
-    case NMP_OBJECT_TYPE_LNK_GRETAP:
-    case NMP_OBJECT_TYPE_LNK_INFINIBAND:
-    case NMP_OBJECT_TYPE_LNK_IP6TNL:
-    case NMP_OBJECT_TYPE_LNK_IP6GRE:
-    case NMP_OBJECT_TYPE_LNK_IP6GRETAP:
-    case NMP_OBJECT_TYPE_LNK_IPIP:
-    case NMP_OBJECT_TYPE_LNK_MACSEC:
-    case NMP_OBJECT_TYPE_LNK_MACVLAN:
-    case NMP_OBJECT_TYPE_LNK_MACVTAP:
-    case NMP_OBJECT_TYPE_LNK_SIT:
-    case NMP_OBJECT_TYPE_LNK_TUN:
-    case NMP_OBJECT_TYPE_LNK_VLAN:
-    case NMP_OBJECT_TYPE_LNK_VRF:
-    case NMP_OBJECT_TYPE_LNK_VXLAN:
-    case NMP_OBJECT_TYPE_LNK_WIREGUARD:
-        return TRUE;
-
-    case NMP_OBJECT_TYPE_ROUTING_RULE:
-        return FALSE;
-
-    case NMP_OBJECT_TYPE_UNKNOWN:
-    case __NMP_OBJECT_TYPE_LAST:
-        break;
-    }
-    nm_assert_not_reached();
-    return FALSE;
-}
-
-#define NMP_OBJECT_CAST_OBJECT(obj)                                       \
-    ({                                                                    \
-        typeof(obj) _obj = (obj);                                         \
-                                                                          \
-        nm_assert (   !_obj \
-                   || nmp_class_from_type (NMP_OBJECT_GET_TYPE (_obj)))); \
-        _obj ? &NM_CONSTCAST(NMPObject, _obj)->object : NULL;             \
-    })
-
-#define NMP_OBJECT_CAST_OBJ_WITH_IFINDEX(obj)                                                \
-    ({                                                                                       \
-        typeof(obj) _obj = (obj);                                                            \
-                                                                                             \
-        nm_assert(!_obj || _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMP_OBJECT_GET_TYPE(_obj))); \
-        _obj ? &NM_CONSTCAST(NMPObject, _obj)->obj_with_ifindex : NULL;                      \
-    })
-
-#define _NMP_OBJECT_CAST(obj, field, ...)                                      \
-    ({                                                                         \
-        typeof(obj) _obj = (obj);                                              \
-                                                                               \
-        nm_assert(!_obj || NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj), __VA_ARGS__)); \
-        _obj ? &NM_CONSTCAST(NMPObject, _obj)->field : NULL;                   \
-    })
-
-#define NMP_OBJECT_CAST_LINK(obj) _NMP_OBJECT_CAST(obj, link, NMP_OBJECT_TYPE_LINK)
-#define NMP_OBJECT_CAST_IP_ADDRESS(obj) \
-    _NMP_OBJECT_CAST(obj, ip_address, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)
-#define NMP_OBJECT_CAST_IPX_ADDRESS(obj) \
-    _NMP_OBJECT_CAST(obj, ipx_address, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)
-#define NMP_OBJECT_CAST_IP4_ADDRESS(obj) \
-    _NMP_OBJECT_CAST(obj, ip4_address, NMP_OBJECT_TYPE_IP4_ADDRESS)
-#define NMP_OBJECT_CAST_IP6_ADDRESS(obj) \
-    _NMP_OBJECT_CAST(obj, ip6_address, NMP_OBJECT_TYPE_IP6_ADDRESS)
-#define NMP_OBJECT_CAST_IP_ROUTE(obj) \
-    _NMP_OBJECT_CAST(obj, ip_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
-#define NMP_OBJECT_CAST_IPX_ROUTE(obj) \
-    _NMP_OBJECT_CAST(obj, ipx_route, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
-#define NMP_OBJECT_CAST_IP4_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip4_route, NMP_OBJECT_TYPE_IP4_ROUTE)
-#define NMP_OBJECT_CAST_IP6_ROUTE(obj) _NMP_OBJECT_CAST(obj, ip6_route, NMP_OBJECT_TYPE_IP6_ROUTE)
-#define NMP_OBJECT_CAST_ROUTING_RULE(obj) \
-    _NMP_OBJECT_CAST(obj, routing_rule, NMP_OBJECT_TYPE_ROUTING_RULE)
-#define NMP_OBJECT_CAST_QDISC(obj)   _NMP_OBJECT_CAST(obj, qdisc, NMP_OBJECT_TYPE_QDISC)
-#define NMP_OBJECT_CAST_TFILTER(obj) _NMP_OBJECT_CAST(obj, tfilter, NMP_OBJECT_TYPE_TFILTER)
-#define NMP_OBJECT_CAST_LNK_WIREGUARD(obj) \
-    _NMP_OBJECT_CAST(obj, lnk_wireguard, NMP_OBJECT_TYPE_LNK_WIREGUARD)
-#define NMP_OBJECT_CAST_LNK_BRIDGE(obj) \
-    _NMP_OBJECT_CAST(obj, lnk_bridge, NMP_OBJECT_TYPE_LNK_BRIDGE)
-
-static inline int
-NMP_OBJECT_TYPE_TO_ADDR_FAMILY(NMPObjectType obj_type)
-{
-    return nmp_class_from_type(obj_type)->addr_family;
-}
-
-static inline int
-NMP_OBJECT_GET_ADDR_FAMILY(const NMPObject *obj)
-{
-    return NMP_OBJECT_GET_CLASS(obj)->addr_family;
-}
-
-static inline const NMPObject *
-nmp_object_ref(const NMPObject *obj)
-{
-    if (!obj) {
-        /* for convenience, allow NULL. */
-        return NULL;
-    }
-
-    /* ref and unref accept const pointers. NMPObject is supposed to be shared
-     * and kept immutable. Disallowing to take/return a reference to a const
-     * NMPObject is cumbersome, because callers are precisely expected to
-     * keep a ref on the otherwise immutable object. */
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj), NULL);
-    g_return_val_if_fail(obj->parent._ref_count != NM_OBJ_REF_COUNT_STACKINIT, NULL);
-
-    return (const NMPObject *) nm_dedup_multi_obj_ref((const NMDedupMultiObj *) obj);
-}
-
-static inline void
-nmp_object_unref(const NMPObject *obj)
-{
-    if (obj) {
-        nm_assert(NMP_OBJECT_IS_VALID(obj));
-
-        nm_dedup_multi_obj_unref((const NMDedupMultiObj *) obj);
-    }
-}
-
-#define nm_clear_nmp_object(ptr)       \
-    ({                                 \
-        typeof(ptr) _ptr = (ptr);      \
-        typeof(*_ptr) _pptr;           \
-        gboolean _changed = FALSE;     \
-                                       \
-        if (_ptr && (_pptr = *_ptr)) { \
-            *_ptr = NULL;              \
-            nmp_object_unref(_pptr);   \
-            _changed = TRUE;           \
-        }                              \
-        _changed;                      \
-    })
-
-static inline gboolean
-nmp_object_ref_set(const NMPObject **pp, const NMPObject *obj)
-{
-    gboolean         _changed = FALSE;
-    const NMPObject *p;
-
-    nm_assert(!pp || !*pp || NMP_OBJECT_IS_VALID(*pp));
-    nm_assert(!obj || NMP_OBJECT_IS_VALID(obj));
-
-    if (pp && ((p = *pp) != obj)) {
-        nmp_object_ref(obj);
-        *pp = obj;
-        nmp_object_unref(p);
-        _changed = TRUE;
-    }
-    return _changed;
-}
-
-NMPObject *nmp_object_new(NMPObjectType obj_type, gconstpointer plobj);
-NMPObject *nmp_object_new_link(int ifindex);
-
-const NMPObject *nmp_object_stackinit(NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj);
-
-static inline NMPObject *
-nmp_object_stackinit_obj(NMPObject *obj, const NMPObject *src)
-{
-    return obj == src
-               ? obj
-               : (NMPObject *) nmp_object_stackinit(obj, NMP_OBJECT_GET_TYPE(src), &src->object);
-}
-
-const NMPObject *nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src);
-const NMPObject *nmp_object_stackinit_id_link(NMPObject *obj, int ifindex);
-const NMPObject *nmp_object_stackinit_id_ip4_address(NMPObject *obj,
-                                                     int        ifindex,
-                                                     guint32    address,
-                                                     guint8     plen,
-                                                     guint32    peer_address);
-const NMPObject *
-nmp_object_stackinit_id_ip6_address(NMPObject *obj, int ifindex, const struct in6_addr *address);
-
-const char *nmp_object_to_string(const NMPObject *     obj,
-                                 NMPObjectToStringMode to_string_mode,
-                                 char *                buf,
-                                 gsize                 buf_size);
-void        nmp_object_hash_update(const NMPObject *obj, NMHashState *h);
-int         nmp_object_cmp(const NMPObject *obj1, const NMPObject *obj2);
-
-static inline gboolean
-nmp_object_equal(const NMPObject *obj1, const NMPObject *obj2)
-{
-    return nmp_object_cmp(obj1, obj2) == 0;
-}
-
-void       nmp_object_copy(NMPObject *dst, const NMPObject *src, gboolean id_only);
-NMPObject *nmp_object_clone(const NMPObject *obj, gboolean id_only);
-
-int   nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2);
-void  nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h);
-guint nmp_object_id_hash(const NMPObject *obj);
-
-static inline gboolean
-nmp_object_id_equal(const NMPObject *obj1, const NMPObject *obj2)
-{
-    return nmp_object_id_cmp(obj1, obj2) == 0;
-}
-
-guint    nmp_object_indirect_id_hash(gconstpointer a);
-gboolean nmp_object_indirect_id_equal(gconstpointer a, gconstpointer b);
-
-gboolean nmp_object_is_alive(const NMPObject *obj);
-gboolean nmp_object_is_visible(const NMPObject *obj);
-
-void
-_nmp_object_fixup_link_udev_fields(NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev);
-
-static inline void
-_nm_auto_nmpobj_cleanup(gpointer p)
-{
-    nmp_object_unref(*((const NMPObject **) p));
-}
-#define nm_auto_nmpobj nm_auto(_nm_auto_nmpobj_cleanup)
-
-typedef struct _NMPCache NMPCache;
-
-typedef void (*NMPCachePreHook)(NMPCache *       cache,
-                                const NMPObject *old,
-                                const NMPObject *new,
-                                NMPCacheOpsType ops_type,
-                                gpointer        user_data);
-typedef gboolean (*NMPObjectMatchFn)(const NMPObject *obj, gpointer user_data);
-
-const NMDedupMultiEntry *nmp_cache_lookup_entry(const NMPCache *cache, const NMPObject *obj);
-const NMDedupMultiEntry *nmp_cache_lookup_entry_with_idx_type(const NMPCache * cache,
-                                                              NMPCacheIdType   cache_id_type,
-                                                              const NMPObject *obj);
-const NMDedupMultiEntry *nmp_cache_lookup_entry_link(const NMPCache *cache, int ifindex);
-const NMPObject *        nmp_cache_lookup_obj(const NMPCache *cache, const NMPObject *obj);
-const NMPObject *        nmp_cache_lookup_link(const NMPCache *cache, int ifindex);
-
-typedef struct _NMPLookup NMPLookup;
-
-struct _NMPLookup {
-    NMPCacheIdType cache_id_type;
-    NMPObject      selector_obj;
-};
-
-const NMDedupMultiHeadEntry *nmp_cache_lookup_all(const NMPCache * cache,
-                                                  NMPCacheIdType   cache_id_type,
-                                                  const NMPObject *select_obj);
-
-static inline const NMDedupMultiHeadEntry *
-nmp_cache_lookup(const NMPCache *cache, const NMPLookup *lookup)
-{
-    return nmp_cache_lookup_all(cache, lookup->cache_id_type, &lookup->selector_obj);
-}
-
-const NMPLookup *nmp_lookup_init_obj_type(NMPLookup *lookup, NMPObjectType obj_type);
-const NMPLookup *nmp_lookup_init_link_by_ifname(NMPLookup *lookup, const char *ifname);
-const NMPLookup *nmp_lookup_init_object(NMPLookup *lookup, NMPObjectType obj_type, int ifindex);
-const NMPLookup *nmp_lookup_init_route_default(NMPLookup *lookup, NMPObjectType obj_type);
-const NMPLookup *nmp_lookup_init_route_by_weak_id(NMPLookup *lookup, const NMPObject *obj);
-const NMPLookup *nmp_lookup_init_ip4_route_by_weak_id(NMPLookup *lookup,
-                                                      in_addr_t  network,
-                                                      guint      plen,
-                                                      guint32    metric,
-                                                      guint8     tos);
-const NMPLookup *nmp_lookup_init_ip6_route_by_weak_id(NMPLookup *            lookup,
-                                                      const struct in6_addr *network,
-                                                      guint                  plen,
-                                                      guint32                metric,
-                                                      const struct in6_addr *src,
-                                                      guint8                 src_plen);
-const NMPLookup *
-nmp_lookup_init_object_by_addr_family(NMPLookup *lookup, NMPObjectType obj_type, int addr_family);
-
-GArray *nmp_cache_lookup_to_array(const NMDedupMultiHeadEntry *head_entry,
-                                  NMPObjectType                obj_type,
-                                  gboolean                     visible_only);
-
-static inline gboolean
-nmp_cache_iter_next(NMDedupMultiIter *iter, const NMPObject **out_obj)
-{
-    gboolean has_next;
-
-    has_next = nm_dedup_multi_iter_next(iter);
-    nm_assert(!has_next || NMP_OBJECT_IS_VALID(iter->current->obj));
-    if (out_obj)
-        *out_obj = has_next ? iter->current->obj : NULL;
-    return has_next;
-}
-
-static inline gboolean
-nmp_cache_iter_next_link(NMDedupMultiIter *iter, const NMPlatformLink **out_obj)
-{
-    gboolean has_next;
-
-    has_next = nm_dedup_multi_iter_next(iter);
-    nm_assert(!has_next || NMP_OBJECT_GET_TYPE(iter->current->obj) == NMP_OBJECT_TYPE_LINK);
-    if (out_obj)
-        *out_obj = has_next ? &(((const NMPObject *) iter->current->obj)->link) : NULL;
-    return has_next;
-}
-
-#define nmp_cache_iter_for_each(iter, head, obj) \
-    for (nm_dedup_multi_iter_init((iter), (head)); nmp_cache_iter_next((iter), (obj));)
-
-#define nmp_cache_iter_for_each_link(iter, head, obj) \
-    for (nm_dedup_multi_iter_init((iter), (head)); nmp_cache_iter_next_link((iter), (obj));)
-
-const NMPObject *nmp_cache_lookup_link_full(const NMPCache * cache,
-                                            int              ifindex,
-                                            const char *     ifname,
-                                            gboolean         visible_only,
-                                            NMLinkType       link_type,
-                                            NMPObjectMatchFn match_fn,
-                                            gpointer         user_data);
-
-gboolean         nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave);
-gboolean         nmp_cache_link_connected_needs_toggle(const NMPCache * cache,
-                                                       const NMPObject *master,
-                                                       const NMPObject *potential_slave,
-                                                       const NMPObject *ignore_slave);
-const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex(const NMPCache * cache,
-                                                                  int              master_ifindex,
-                                                                  const NMPObject *potential_slave,
-                                                                  const NMPObject *ignore_slave);
-
-gboolean nmp_cache_use_udev_get(const NMPCache *cache);
-
-void nmtst_assert_nmp_cache_is_consistent(const NMPCache *cache);
-
-NMPCacheOpsType nmp_cache_remove(NMPCache *        cache,
-                                 const NMPObject * obj_needle,
-                                 gboolean          equals_by_ptr,
-                                 gboolean          only_dirty,
-                                 const NMPObject **out_obj_old);
-NMPCacheOpsType nmp_cache_remove_netlink(NMPCache *        cache,
-                                         const NMPObject * obj_needle,
-                                         const NMPObject **out_obj_old,
-                                         const NMPObject **out_obj_new);
-NMPCacheOpsType nmp_cache_update_netlink(NMPCache *        cache,
-                                         NMPObject *       obj_hand_over,
-                                         gboolean          is_dump,
-                                         const NMPObject **out_obj_old,
-                                         const NMPObject **out_obj_new);
-NMPCacheOpsType nmp_cache_update_netlink_route(NMPCache *        cache,
-                                               NMPObject *       obj_hand_over,
-                                               gboolean          is_dump,
-                                               guint16           nlmsgflags,
-                                               const NMPObject **out_obj_old,
-                                               const NMPObject **out_obj_new,
-                                               const NMPObject **out_obj_replace,
-                                               gboolean *        out_resync_required);
-NMPCacheOpsType nmp_cache_update_link_udev(NMPCache *          cache,
-                                           int                 ifindex,
-                                           struct udev_device *udevice,
-                                           const NMPObject **  out_obj_old,
-                                           const NMPObject **  out_obj_new);
-NMPCacheOpsType nmp_cache_update_link_master_connected(NMPCache *        cache,
-                                                       int               ifindex,
-                                                       const NMPObject **out_obj_old,
-                                                       const NMPObject **out_obj_new);
-
-static inline const NMDedupMultiEntry *
-nmp_cache_reresolve_main_entry(NMPCache *               cache,
-                               const NMDedupMultiEntry *entry,
-                               const NMPLookup *        lookup)
-{
-    const NMDedupMultiEntry *main_entry;
-
-    nm_assert(cache);
-    nm_assert(entry);
-    nm_assert(lookup);
-
-    if (lookup->cache_id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) {
-        nm_assert(entry == nmp_cache_lookup_entry(cache, entry->obj));
-        return entry;
-    }
-
-    /* we only track the dirty flag for the OBJECT-TYPE index. That means,
-     * for other lookup types we need to check the dirty flag of the main-entry. */
-    main_entry = nmp_cache_lookup_entry(cache, entry->obj);
-
-    nm_assert(main_entry);
-    nm_assert(main_entry->obj == entry->obj);
-
-    return main_entry;
-}
-
-void nmp_cache_dirty_set_all_main(NMPCache *cache, const NMPLookup *lookup);
-
-NMPCache *nmp_cache_new(NMDedupMultiIndex *multi_idx, gboolean use_udev);
-void      nmp_cache_free(NMPCache *cache);
-
-static inline void
-ASSERT_nmp_cache_ops(const NMPCache * cache,
-                     NMPCacheOpsType  ops_type,
-                     const NMPObject *obj_old,
-                     const NMPObject *obj_new)
-{
-#if NM_MORE_ASSERTS
-    nm_assert(cache);
-    nm_assert(obj_old || obj_new);
-    nm_assert(!obj_old
-              || (NMP_OBJECT_IS_VALID(obj_old) && !NMP_OBJECT_IS_STACKINIT(obj_old)
-                  && nmp_object_is_alive(obj_old)));
-    nm_assert(!obj_new
-              || (NMP_OBJECT_IS_VALID(obj_new) && !NMP_OBJECT_IS_STACKINIT(obj_new)
-                  && nmp_object_is_alive(obj_new)));
-
-    switch (ops_type) {
-    case NMP_CACHE_OPS_UNCHANGED:
-        nm_assert(obj_old == obj_new);
-        break;
-    case NMP_CACHE_OPS_ADDED:
-        nm_assert(!obj_old && obj_new);
-        break;
-    case NMP_CACHE_OPS_UPDATED:
-        nm_assert(obj_old && obj_new && obj_old != obj_new);
-        break;
-    case NMP_CACHE_OPS_REMOVED:
-        nm_assert(obj_old && !obj_new);
-        break;
-    default:
-        nm_assert_not_reached();
-    }
-
-    nm_assert(obj_new == NULL || obj_old == NULL || nmp_object_id_equal(obj_new, obj_old));
-    nm_assert(!obj_old || !obj_new
-              || NMP_OBJECT_GET_CLASS(obj_old) == NMP_OBJECT_GET_CLASS(obj_new));
-
-    nm_assert(obj_new == nmp_cache_lookup_obj(cache, obj_new ?: obj_old));
-#endif
-}
-
-const NMDedupMultiHeadEntry *
-nm_platform_lookup_all(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj);
-
-const NMDedupMultiEntry *
-nm_platform_lookup_entry(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj);
-
-static inline const NMPObject *
-nm_platform_lookup_obj(NMPlatform *platform, NMPCacheIdType cache_id_type, const NMPObject *obj)
-{
-    return nm_dedup_multi_entry_get_obj(nm_platform_lookup_entry(platform, cache_id_type, obj));
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_obj_type(NMPlatform *platform, NMPObjectType obj_type)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_obj_type(&lookup, obj_type);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_link_by_ifname(NMPlatform *platform, const char *ifname)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_link_by_ifname(&lookup, ifname);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_object(NMPlatform *platform, NMPObjectType obj_type, int ifindex)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_object(&lookup, obj_type, ifindex);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-static inline GPtrArray *
-nm_platform_lookup_object_clone(NMPlatform *           platform,
-                                NMPObjectType          obj_type,
-                                int                    ifindex,
-                                NMPObjectPredicateFunc predicate,
-                                gpointer               user_data)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_object(&lookup, obj_type, ifindex);
-    return nm_platform_lookup_clone(platform, &lookup, predicate, user_data);
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_route_default(NMPlatform *platform, NMPObjectType obj_type)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_route_default(&lookup, obj_type);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-static inline GPtrArray *
-nm_platform_lookup_route_default_clone(NMPlatform *           platform,
-                                       NMPObjectType          obj_type,
-                                       NMPObjectPredicateFunc predicate,
-                                       gpointer               user_data)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_route_default(&lookup, obj_type);
-    return nm_platform_lookup_clone(platform, &lookup, predicate, user_data);
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_ip4_route_by_weak_id(NMPlatform *platform,
-                                        in_addr_t   network,
-                                        guint       plen,
-                                        guint32     metric,
-                                        guint8      tos)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_ip4_route_by_weak_id(&lookup, network, plen, metric, tos);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_ip6_route_by_weak_id(NMPlatform *           platform,
-                                        const struct in6_addr *network,
-                                        guint                  plen,
-                                        guint32                metric,
-                                        const struct in6_addr *src,
-                                        guint8                 src_plen)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_ip6_route_by_weak_id(&lookup, network, plen, metric, src, src_plen);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_object_by_addr_family(NMPlatform *  platform,
-                                         NMPObjectType obj_type,
-                                         int           addr_family)
-{
-    NMPLookup lookup;
-
-    nmp_lookup_init_object_by_addr_family(&lookup, obj_type, addr_family);
-    return nm_platform_lookup(platform, &lookup);
-}
-
-/*****************************************************************************/
-
-static inline const char *
-nmp_object_link_get_ifname(const NMPObject *obj)
-{
-    if (!obj)
-        return NULL;
-    return NMP_OBJECT_CAST_LINK(obj)->name;
-}
-
-static inline gboolean
-nmp_object_ip_route_is_best_defaut_route(const NMPObject *obj)
-{
-    const NMPlatformIPRoute *r = NMP_OBJECT_CAST_IP_ROUTE(obj);
-
-    /* return whether @obj is considered a default-route.
-     *
-     * NMIP4Config/NMIP6Config tracks the (best) default-route explicitly, because
-     * at various places we act differently depending on whether there is a default-route
-     * configured.
-     *
-     * Note that this only considers the main routing table. */
-    return r && NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r)
-           && nm_platform_route_table_is_main(r->table_coerced)
-           && r->type_coerced == nm_platform_route_type_coerce(1 /* RTN_UNICAST */);
-}
-
-static inline gboolean
-nmp_object_ip6_address_is_not_link_local(const NMPObject *obj)
-{
-    return !IN6_IS_ADDR_LINKLOCAL(&NMP_OBJECT_CAST_IP6_ADDRESS(obj)->address);
-}
-
-/*****************************************************************************/
-
-static inline gboolean
-nm_platform_dedup_multi_iter_next_obj(NMDedupMultiIter *ipconf_iter,
-                                      const NMPObject **out_obj,
-                                      NMPObjectType     assert_obj_type)
-{
-    gboolean has_next;
-
-    has_next = nm_dedup_multi_iter_next(ipconf_iter);
-    nm_assert(assert_obj_type == NMP_OBJECT_TYPE_UNKNOWN || !has_next
-              || NMP_OBJECT_GET_TYPE(ipconf_iter->current->obj) == assert_obj_type);
-    NM_SET_OUT(out_obj, has_next ? ipconf_iter->current->obj : NULL);
-    return has_next;
-}
-
-#define _nm_platform_dedup_multi_iter_next(ipconf_iter, out_obj, field, ...)                  \
-    ({                                                                                        \
-        NMDedupMultiIter *const                           _ipconf_iter = (ipconf_iter);       \
-        const typeof(((NMPObject *) NULL)->field) **const _out_obj     = (out_obj);           \
-        gboolean                                          _has_next;                          \
-                                                                                              \
-        if (G_LIKELY(nm_dedup_multi_iter_next(_ipconf_iter))) {                               \
-            if (_out_obj) {                                                                   \
-                *_out_obj = _NMP_OBJECT_CAST(_ipconf_iter->current->obj, field, __VA_ARGS__); \
-            } else {                                                                          \
-                nm_assert(                                                                    \
-                    NM_IN_SET(NMP_OBJECT_GET_TYPE(_ipconf_iter->current->obj), __VA_ARGS__)); \
-            }                                                                                 \
-            _has_next = TRUE;                                                                 \
-        } else {                                                                              \
-            if (_out_obj)                                                                     \
-                *_out_obj = NULL;                                                             \
-            _has_next = FALSE;                                                                \
-        }                                                                                     \
-        _has_next;                                                                            \
-    })
-
-#define nm_platform_dedup_multi_iter_next_ip_address(ipconf_iter, out_obj) \
-    _nm_platform_dedup_multi_iter_next((ipconf_iter),                      \
-                                       (out_obj),                          \
-                                       ip_address,                         \
-                                       NMP_OBJECT_TYPE_IP4_ADDRESS,        \
-                                       NMP_OBJECT_TYPE_IP6_ADDRESS)
-
-#define nm_platform_dedup_multi_iter_next_ip4_address(ipconf_iter, out_obj) \
-    _nm_platform_dedup_multi_iter_next((ipconf_iter),                       \
-                                       (out_obj),                           \
-                                       ip4_address,                         \
-                                       NMP_OBJECT_TYPE_IP4_ADDRESS)
-
-#define nm_platform_dedup_multi_iter_next_ip6_address(ipconf_iter, out_obj) \
-    _nm_platform_dedup_multi_iter_next((ipconf_iter),                       \
-                                       (out_obj),                           \
-                                       ip6_address,                         \
-                                       NMP_OBJECT_TYPE_IP6_ADDRESS)
-
-#define nm_platform_dedup_multi_iter_next_ip_route(ipconf_iter, out_obj) \
-    _nm_platform_dedup_multi_iter_next((ipconf_iter),                    \
-                                       (out_obj),                        \
-                                       ip_route,                         \
-                                       NMP_OBJECT_TYPE_IP4_ROUTE,        \
-                                       NMP_OBJECT_TYPE_IP6_ROUTE)
-
-#define nm_platform_dedup_multi_iter_next_ip4_route(ipconf_iter, out_obj) \
-    _nm_platform_dedup_multi_iter_next((ipconf_iter),                     \
-                                       (out_obj),                         \
-                                       ip4_route,                         \
-                                       NMP_OBJECT_TYPE_IP4_ROUTE)
-
-#define nm_platform_dedup_multi_iter_next_ip6_route(ipconf_iter, out_obj) \
-    _nm_platform_dedup_multi_iter_next((ipconf_iter),                     \
-                                       (out_obj),                         \
-                                       ip6_route,                         \
-                                       NMP_OBJECT_TYPE_IP6_ROUTE)
-
-#endif /* __NMP_OBJECT_H__ */
diff --git a/src/core/platform/nmp-rules-manager.c b/src/core/platform/nmp-rules-manager.c
deleted file mode 100644
index 0c617b80..00000000
--- a/src/core/platform/nmp-rules-manager.c
+++ /dev/null
@@ -1,808 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nmp-rules-manager.h"
-
-#include <linux/fib_rules.h>
-#include <linux/rtnetlink.h>
-
-#include "nm-std-aux/c-list-util.h"
-#include "nmp-object.h"
-
-/*****************************************************************************/
-
-struct _NMPRulesManager {
-    NMPlatform *platform;
-    GHashTable *by_obj;
-    GHashTable *by_user_tag;
-    GHashTable *by_data;
-    guint       ref_count;
-};
-
-/*****************************************************************************/
-
-static void _rules_init(NMPRulesManager *self);
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_PLATFORM
-#define _NMLOG_PREFIX_NAME "rules-manager"
-
-#define _NMLOG(level, ...)                                                 \
-    G_STMT_START                                                           \
-    {                                                                      \
-        const NMLogLevel __level = (level);                                \
-                                                                           \
-        if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) {                  \
-            _nm_log(__level,                                               \
-                    _NMLOG_DOMAIN,                                         \
-                    0,                                                     \
-                    NULL,                                                  \
-                    NULL,                                                  \
-                    "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),             \
-                    _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        }                                                                  \
-    }                                                                      \
-    G_STMT_END
-
-/*****************************************************************************/
-
-static gboolean
-NMP_IS_RULES_MANAGER(gpointer self)
-{
-    return self && ((NMPRulesManager *) self)->ref_count > 0
-           && NM_IS_PLATFORM(((NMPRulesManager *) self)->platform);
-}
-
-#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr(1240261787u, (user_tag))
-
-/*****************************************************************************/
-
-typedef struct {
-    const NMPObject *obj;
-    gconstpointer    user_tag;
-    CList            obj_lst;
-    CList            user_tag_lst;
-
-    /* track_priority_val zero is special: those are weakly tracked rules.
-     * That means: NetworkManager will restore them only if it removed them earlier.
-     * But it will not remove or add them otherwise.
-     *
-     * Otherwise, the track_priority_val goes together with track_priority_present.
-     * In case of one rule being tracked multiple times (with different priorities),
-     * the one with higher priority wins. See _rules_obj_get_best_data().
-     * Then, the winning present state either enforces that the rule is present
-     * or absent.
-     *
-     * If a rules is not tracked at all, it is ignored by NetworkManager. Assuming
-     * that it was added externally by the user. But unlike weakly tracked rules,
-     * NM will *not* restore such rules if NetworkManager themself removed them. */
-    guint32 track_priority_val;
-    bool    track_priority_present : 1;
-
-    bool dirty : 1;
-} RulesData;
-
-typedef enum {
-    CONFIG_STATE_NONE          = 0,
-    CONFIG_STATE_ADDED_BY_US   = 1,
-    CONFIG_STATE_REMOVED_BY_US = 2,
-
-    /* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE).
-     *
-     * Maybe we would only need to track whether we touched the rule at all. But we
-     * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US)
-     * or did we remove it (CONFIG_STATE_REMOVED_BY_US)?
-     * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively
-     * add/remove it, but whenever we are about to undo the add/remove, we need to do it.
-     * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally
-     * force the state next time when necessary. */
-    CONFIG_STATE_OWNED_BY_US = 3,
-} ConfigState;
-
-typedef struct {
-    const NMPObject *obj;
-    CList            obj_lst_head;
-
-    /* indicates whether we configured/removed the rule (during sync()). We need that, so
-     * if the rule gets untracked, that we know to remove/restore it.
-     *
-     * This makes NMPRulesManager stateful (beyond the configuration that indicates
-     * which rules are tracked).
-     * After a restart, NetworkManager would no longer remember which rules were added
-     * by us.
-     *
-     * That is partially fixed by NetworkManager taking over the rules that it
-     * actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */
-    ConfigState config_state;
-} RulesObjData;
-
-typedef struct {
-    gconstpointer user_tag;
-    CList         user_tag_lst_head;
-} RulesUserTagData;
-
-/*****************************************************************************/
-
-static void _rules_data_untrack(NMPRulesManager *self,
-                                RulesData *      rules_data,
-                                gboolean         remove_user_tag_data,
-                                gboolean         make_owned_by_us);
-
-/*****************************************************************************/
-
-static void
-_rules_data_assert(const RulesData *rules_data, gboolean linked)
-{
-    nm_assert(rules_data);
-    nm_assert(NMP_OBJECT_GET_TYPE(rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
-    nm_assert(nmp_object_is_visible(rules_data->obj));
-    nm_assert(rules_data->user_tag);
-    nm_assert(!linked || !c_list_is_empty(&rules_data->obj_lst));
-    nm_assert(!linked || !c_list_is_empty(&rules_data->user_tag_lst));
-}
-
-static guint
-_rules_data_hash(gconstpointer data)
-{
-    const RulesData *rules_data = data;
-    NMHashState      h;
-
-    _rules_data_assert(rules_data, FALSE);
-
-    nm_hash_init(&h, 269297543u);
-    nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(rules_data->obj),
-                                         NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
-                                         &h);
-    nm_hash_update_val(&h, rules_data->user_tag);
-    return nm_hash_complete(&h);
-}
-
-static gboolean
-_rules_data_equal(gconstpointer data_a, gconstpointer data_b)
-{
-    const RulesData *rules_data_a = data_a;
-    const RulesData *rules_data_b = data_b;
-
-    _rules_data_assert(rules_data_a, FALSE);
-    _rules_data_assert(rules_data_b, FALSE);
-
-    return rules_data_a->user_tag == rules_data_b->user_tag
-           && (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(rules_data_a->obj),
-                                            NMP_OBJECT_CAST_ROUTING_RULE(rules_data_b->obj),
-                                            NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID)
-               == 0);
-}
-
-static void
-_rules_data_destroy(gpointer data)
-{
-    RulesData *rules_data = data;
-
-    _rules_data_assert(rules_data, FALSE);
-
-    c_list_unlink_stale(&rules_data->obj_lst);
-    c_list_unlink_stale(&rules_data->user_tag_lst);
-    nmp_object_unref(rules_data->obj);
-    g_slice_free(RulesData, rules_data);
-}
-
-static const RulesData *
-_rules_obj_get_best_data(RulesObjData *obj_data)
-{
-    RulesData *      rules_data;
-    const RulesData *rd_best = NULL;
-
-    c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) {
-        _rules_data_assert(rules_data, TRUE);
-
-        if (rd_best) {
-            if (rd_best->track_priority_val > rules_data->track_priority_val)
-                continue;
-            if (rd_best->track_priority_val == rules_data->track_priority_val) {
-                if (rd_best->track_priority_present || !rules_data->track_priority_present) {
-                    /* if the priorities are identical, then "present" wins over
-                     * "!present" (absent). */
-                    continue;
-                }
-            }
-        }
-
-        rd_best = rules_data;
-    }
-
-    return rd_best;
-}
-
-static guint
-_rules_obj_hash(gconstpointer data)
-{
-    const RulesObjData *obj_data = data;
-    NMHashState         h;
-
-    nm_hash_init(&h, 432817559u);
-    nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj),
-                                         NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
-                                         &h);
-    return nm_hash_complete(&h);
-}
-
-static gboolean
-_rules_obj_equal(gconstpointer data_a, gconstpointer data_b)
-{
-    const RulesObjData *obj_data_a = data_a;
-    const RulesObjData *obj_data_b = data_b;
-
-    return (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj_data_a->obj),
-                                         NMP_OBJECT_CAST_ROUTING_RULE(obj_data_b->obj),
-                                         NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID)
-            == 0);
-}
-
-static void
-_rules_obj_destroy(gpointer data)
-{
-    RulesObjData *obj_data = data;
-
-    c_list_unlink_stale(&obj_data->obj_lst_head);
-    nmp_object_unref(obj_data->obj);
-    g_slice_free(RulesObjData, obj_data);
-}
-
-static guint
-_rules_user_tag_hash(gconstpointer data)
-{
-    const RulesUserTagData *user_tag_data = data;
-
-    return nm_hash_val(644693447u, user_tag_data->user_tag);
-}
-
-static gboolean
-_rules_user_tag_equal(gconstpointer data_a, gconstpointer data_b)
-{
-    const RulesUserTagData *user_tag_data_a = data_a;
-    const RulesUserTagData *user_tag_data_b = data_b;
-
-    return user_tag_data_a->user_tag == user_tag_data_b->user_tag;
-}
-
-static void
-_rules_user_tag_destroy(gpointer data)
-{
-    RulesUserTagData *user_tag_data = data;
-
-    c_list_unlink_stale(&user_tag_data->user_tag_lst_head);
-    g_slice_free(RulesUserTagData, user_tag_data);
-}
-
-static RulesData *
-_rules_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user_tag)
-{
-    RulesData rules_data_needle = {
-        .obj      = obj,
-        .user_tag = user_tag,
-    };
-
-    return g_hash_table_lookup(by_data, &rules_data_needle);
-}
-
-/**
- * nmp_rules_manager_track:
- * @self: the #NMPRulesManager instance
- * @routing_rule: the #NMPlatformRoutingRule to track or untrack
- * @track_priority: the priority for tracking the rule. Note that
- *   negative values indicate a forced absence of the rule. Priorities
- *   are compared with their absolute values (with higher absolute
- *   value being more important). For example, if you track the same
- *   rule twice, once with priority -5 and +10, then the rule is
- *   present (because the positive number is more important).
- *   The special value 0 indicates weakly-tracked rules.
- * @user_tag: the tag associated with tracking this rule. The same tag
- *   must be used to untrack the rule later.
- * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag
- *   for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(),
- *   because it enforces ownership of the now tracked rule. On the other hand,
- *   a plain nmp_rules_manager_untrack() merely forgets about the tracking.
- *   The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG.
- */
-void
-nmp_rules_manager_track(NMPRulesManager *            self,
-                        const NMPlatformRoutingRule *routing_rule,
-                        gint32                       track_priority,
-                        gconstpointer                user_tag,
-                        gconstpointer                user_tag_untrack)
-{
-    NMPObject         obj_stack;
-    const NMPObject * p_obj_stack;
-    RulesData *       rules_data;
-    RulesObjData *    obj_data;
-    RulesUserTagData *user_tag_data;
-    gboolean          changed = FALSE;
-    guint32           track_priority_val;
-    gboolean          track_priority_present;
-
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-    g_return_if_fail(routing_rule);
-    g_return_if_fail(user_tag);
-    nm_assert(track_priority != G_MININT32);
-
-    _rules_init(self);
-
-    p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule);
-
-    nm_assert(nmp_object_is_visible(p_obj_stack));
-
-    if (track_priority >= 0) {
-        track_priority_val     = track_priority;
-        track_priority_present = TRUE;
-    } else {
-        track_priority_val     = -track_priority;
-        track_priority_present = FALSE;
-    }
-
-    rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag);
-
-    if (!rules_data) {
-        rules_data  = g_slice_new(RulesData);
-        *rules_data = (RulesData){
-            .obj      = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform),
-                                                   p_obj_stack),
-            .user_tag = user_tag,
-            .track_priority_val     = track_priority_val,
-            .track_priority_present = track_priority_present,
-            .dirty                  = FALSE,
-        };
-        g_hash_table_add(self->by_data, rules_data);
-
-        obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj);
-        if (!obj_data) {
-            obj_data  = g_slice_new(RulesObjData);
-            *obj_data = (RulesObjData){
-                .obj          = nmp_object_ref(rules_data->obj),
-                .obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head),
-                .config_state = CONFIG_STATE_NONE,
-            };
-            g_hash_table_add(self->by_obj, obj_data);
-        }
-        c_list_link_tail(&obj_data->obj_lst_head, &rules_data->obj_lst);
-
-        user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag);
-        if (!user_tag_data) {
-            user_tag_data  = g_slice_new(RulesUserTagData);
-            *user_tag_data = (RulesUserTagData){
-                .user_tag          = user_tag,
-                .user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head),
-            };
-            g_hash_table_add(self->by_user_tag, user_tag_data);
-        }
-        c_list_link_tail(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst);
-        changed = TRUE;
-    } else {
-        rules_data->dirty = FALSE;
-        if (rules_data->track_priority_val != track_priority_val
-            || rules_data->track_priority_present != track_priority_present) {
-            rules_data->track_priority_val     = track_priority_val;
-            rules_data->track_priority_present = track_priority_present;
-            changed                            = TRUE;
-        }
-    }
-
-    if (user_tag_untrack) {
-        if (user_tag != user_tag_untrack) {
-            RulesData *rules_data_untrack;
-
-            rules_data_untrack = _rules_data_lookup(self->by_data, p_obj_stack, user_tag_untrack);
-            if (rules_data_untrack)
-                _rules_data_untrack(self, rules_data_untrack, FALSE, TRUE);
-        } else
-            nm_assert_not_reached();
-    }
-
-    _rules_data_assert(rules_data, TRUE);
-
-    if (changed) {
-        _LOGD("routing-rule: track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] \"%s\")",
-              _USER_TAG_LOG(rules_data->user_tag),
-              (rules_data->track_priority_val == 0
-                   ? ""
-                   : (rules_data->track_priority_present ? "+" : "-")),
-              (guint) rules_data->track_priority_val,
-              nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-    }
-}
-
-static void
-_rules_data_untrack(NMPRulesManager *self,
-                    RulesData *      rules_data,
-                    gboolean         remove_user_tag_data,
-                    gboolean         make_owned_by_us)
-{
-    RulesObjData *obj_data;
-
-    nm_assert(NMP_IS_RULES_MANAGER(self));
-    _rules_data_assert(rules_data, TRUE);
-    nm_assert(self->by_data);
-    nm_assert(g_hash_table_lookup(self->by_data, rules_data) == rules_data);
-
-    _LOGD("routing-rule: untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] \"%s\"",
-          _USER_TAG_LOG(rules_data->user_tag),
-          nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-
-#if NM_MORE_ASSERTS
-    {
-        RulesUserTagData *user_tag_data;
-
-        user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag);
-        nm_assert(user_tag_data);
-        nm_assert(c_list_contains(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst));
-    }
-#endif
-
-    nm_assert(!c_list_is_empty(&rules_data->user_tag_lst));
-
-    obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj);
-    nm_assert(obj_data);
-    nm_assert(c_list_contains(&obj_data->obj_lst_head, &rules_data->obj_lst));
-    nm_assert(obj_data == g_hash_table_lookup(self->by_obj, &rules_data->obj));
-
-    if (make_owned_by_us) {
-        if (obj_data->config_state == CONFIG_STATE_NONE) {
-            /* we need to mark this entry that it requires a touch on the next
-             * sync. */
-            obj_data->config_state = CONFIG_STATE_OWNED_BY_US;
-        }
-    } else if (remove_user_tag_data && c_list_length_is(&rules_data->user_tag_lst, 1))
-        g_hash_table_remove(self->by_user_tag, &rules_data->user_tag);
-
-    /* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry
-     * around for the next sync -- so that we can undo what we did earlier. */
-    if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&rules_data->obj_lst, 1))
-        g_hash_table_remove(self->by_obj, &rules_data->obj);
-
-    g_hash_table_remove(self->by_data, rules_data);
-}
-
-void
-nmp_rules_manager_untrack(NMPRulesManager *            self,
-                          const NMPlatformRoutingRule *routing_rule,
-                          gconstpointer                user_tag)
-{
-    NMPObject        obj_stack;
-    const NMPObject *p_obj_stack;
-    RulesData *      rules_data;
-
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-    g_return_if_fail(routing_rule);
-    g_return_if_fail(user_tag);
-
-    _rules_init(self);
-
-    p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule);
-
-    nm_assert(nmp_object_is_visible(p_obj_stack));
-
-    rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag);
-    if (rules_data)
-        _rules_data_untrack(self, rules_data, TRUE, FALSE);
-}
-
-void
-nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag)
-{
-    RulesData *       rules_data;
-    RulesUserTagData *user_tag_data;
-
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-    g_return_if_fail(user_tag);
-
-    if (!self->by_data)
-        return;
-
-    user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag);
-    if (!user_tag_data)
-        return;
-
-    c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst)
-        rules_data->dirty = TRUE;
-}
-
-void
-nmp_rules_manager_untrack_all(NMPRulesManager *self,
-                              gconstpointer    user_tag,
-                              gboolean         all /* or only dirty */)
-{
-    RulesData *       rules_data;
-    RulesData *       rules_data_safe;
-    RulesUserTagData *user_tag_data;
-
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-    g_return_if_fail(user_tag);
-
-    if (!self->by_data)
-        return;
-
-    user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag);
-    if (!user_tag_data)
-        return;
-
-    c_list_for_each_entry_safe (rules_data,
-                                rules_data_safe,
-                                &user_tag_data->user_tag_lst_head,
-                                user_tag_lst) {
-        if (all || rules_data->dirty)
-            _rules_data_untrack(self, rules_data, FALSE, FALSE);
-    }
-    if (c_list_is_empty(&user_tag_data->user_tag_lst_head))
-        g_hash_table_remove(self->by_user_tag, user_tag_data);
-}
-
-void
-nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules)
-{
-    const NMDedupMultiHeadEntry *pl_head_entry;
-    NMDedupMultiIter             pl_iter;
-    const NMPObject *            plobj;
-    gs_unref_ptrarray GPtrArray *rules_to_delete = NULL;
-    RulesObjData *               obj_data;
-    GHashTableIter               h_iter;
-    guint                        i;
-    const RulesData *            rd_best;
-
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-
-    if (!self->by_data)
-        return;
-
-    _LOGD("sync%s", keep_deleted_rules ? " (don't remove any rules)" : "");
-
-    pl_head_entry = nm_platform_lookup_obj_type(self->platform, NMP_OBJECT_TYPE_ROUTING_RULE);
-    if (pl_head_entry) {
-        nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) {
-            obj_data = g_hash_table_lookup(self->by_obj, &plobj);
-
-            if (!obj_data) {
-                /* this rule is not tracked. It was externally added, hence we
-                 * ignore it. */
-                continue;
-            }
-
-            rd_best = _rules_obj_get_best_data(obj_data);
-            if (rd_best) {
-                if (rd_best->track_priority_present) {
-                    if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
-                        obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
-                    continue;
-                }
-                if (rd_best->track_priority_val == 0) {
-                    if (!NM_IN_SET(obj_data->config_state,
-                                   CONFIG_STATE_ADDED_BY_US,
-                                   CONFIG_STATE_OWNED_BY_US)) {
-                        obj_data->config_state = CONFIG_STATE_NONE;
-                        continue;
-                    }
-                    obj_data->config_state = CONFIG_STATE_NONE;
-                }
-            }
-
-            if (keep_deleted_rules) {
-                _LOGD("forget/leak rule added by us: %s",
-                      nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-                continue;
-            }
-
-            if (!rules_to_delete)
-                rules_to_delete = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
-
-            g_ptr_array_add(rules_to_delete, (gpointer) nmp_object_ref(plobj));
-
-            obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
-        }
-    }
-
-    if (rules_to_delete) {
-        for (i = 0; i < rules_to_delete->len; i++)
-            nm_platform_object_delete(self->platform, rules_to_delete->pdata[i]);
-    }
-
-    g_hash_table_iter_init(&h_iter, self->by_obj);
-    while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_data, NULL)) {
-        rd_best = _rules_obj_get_best_data(obj_data);
-
-        if (!rd_best) {
-            g_hash_table_iter_remove(&h_iter);
-            continue;
-        }
-
-        if (!rd_best->track_priority_present) {
-            if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
-                obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
-            continue;
-        }
-        if (rd_best->track_priority_val == 0) {
-            if (!NM_IN_SET(obj_data->config_state,
-                           CONFIG_STATE_REMOVED_BY_US,
-                           CONFIG_STATE_OWNED_BY_US)) {
-                obj_data->config_state = CONFIG_STATE_NONE;
-                continue;
-            }
-            obj_data->config_state = CONFIG_STATE_NONE;
-        }
-
-        plobj =
-            nm_platform_lookup_obj(self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj);
-        if (plobj)
-            continue;
-
-        obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
-        nm_platform_routing_rule_add(self->platform,
-                                     NMP_NLM_FLAG_ADD,
-                                     NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj));
-    }
-}
-
-void
-nmp_rules_manager_track_from_platform(NMPRulesManager *self,
-                                      NMPlatform *     platform,
-                                      int              addr_family,
-                                      gint32           tracking_priority,
-                                      gconstpointer    user_tag)
-{
-    NMPLookup                    lookup;
-    const NMDedupMultiHeadEntry *head_entry;
-    NMDedupMultiIter             iter;
-    const NMPObject *            o;
-
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-
-    if (!platform)
-        platform = self->platform;
-    else
-        g_return_if_fail(NM_IS_PLATFORM(platform));
-
-    nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
-    head_entry = nm_platform_lookup(platform, &lookup);
-    nmp_cache_iter_for_each (&iter, head_entry, &o) {
-        const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(o);
-
-        if (addr_family != AF_UNSPEC && rr->addr_family != addr_family)
-            continue;
-
-        nmp_rules_manager_track(self, rr, tracking_priority, user_tag, NULL);
-    }
-}
-
-/*****************************************************************************/
-
-void
-nmp_rules_manager_track_default(NMPRulesManager *self,
-                                int              addr_family,
-                                gint32           track_priority,
-                                gconstpointer    user_tag)
-{
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-
-    nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
-
-    /* track the default rules. See also `man ip-rule`. */
-
-    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) {
-        nmp_rules_manager_track(self,
-                                &((NMPlatformRoutingRule){
-                                    .addr_family = AF_INET,
-                                    .priority    = 0,
-                                    .table       = RT_TABLE_LOCAL,
-                                    .action      = FR_ACT_TO_TBL,
-                                    .protocol    = RTPROT_KERNEL,
-                                }),
-                                track_priority,
-                                user_tag,
-                                NULL);
-        nmp_rules_manager_track(self,
-                                &((NMPlatformRoutingRule){
-                                    .addr_family = AF_INET,
-                                    .priority    = 32766,
-                                    .table       = RT_TABLE_MAIN,
-                                    .action      = FR_ACT_TO_TBL,
-                                    .protocol    = RTPROT_KERNEL,
-                                }),
-                                track_priority,
-                                user_tag,
-                                NULL);
-        nmp_rules_manager_track(self,
-                                &((NMPlatformRoutingRule){
-                                    .addr_family = AF_INET,
-                                    .priority    = 32767,
-                                    .table       = RT_TABLE_DEFAULT,
-                                    .action      = FR_ACT_TO_TBL,
-                                    .protocol    = RTPROT_KERNEL,
-                                }),
-                                track_priority,
-                                user_tag,
-                                NULL);
-    }
-    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
-        nmp_rules_manager_track(self,
-                                &((NMPlatformRoutingRule){
-                                    .addr_family = AF_INET6,
-                                    .priority    = 0,
-                                    .table       = RT_TABLE_LOCAL,
-                                    .action      = FR_ACT_TO_TBL,
-                                    .protocol    = RTPROT_KERNEL,
-                                }),
-                                track_priority,
-                                user_tag,
-                                NULL);
-        nmp_rules_manager_track(self,
-                                &((NMPlatformRoutingRule){
-                                    .addr_family = AF_INET6,
-                                    .priority    = 32766,
-                                    .table       = RT_TABLE_MAIN,
-                                    .action      = FR_ACT_TO_TBL,
-                                    .protocol    = RTPROT_KERNEL,
-                                }),
-                                track_priority,
-                                user_tag,
-                                NULL);
-    }
-}
-
-static void
-_rules_init(NMPRulesManager *self)
-{
-    if (self->by_data)
-        return;
-
-    self->by_data =
-        g_hash_table_new_full(_rules_data_hash, _rules_data_equal, NULL, _rules_data_destroy);
-    self->by_obj =
-        g_hash_table_new_full(_rules_obj_hash, _rules_obj_equal, NULL, _rules_obj_destroy);
-    self->by_user_tag = g_hash_table_new_full(_rules_user_tag_hash,
-                                              _rules_user_tag_equal,
-                                              NULL,
-                                              _rules_user_tag_destroy);
-}
-
-/*****************************************************************************/
-
-NMPRulesManager *
-nmp_rules_manager_new(NMPlatform *platform)
-{
-    NMPRulesManager *self;
-
-    g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL);
-
-    self  = g_slice_new(NMPRulesManager);
-    *self = (NMPRulesManager){
-        .ref_count = 1,
-        .platform  = g_object_ref(platform),
-    };
-    return self;
-}
-
-void
-nmp_rules_manager_ref(NMPRulesManager *self)
-{
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-
-    self->ref_count++;
-}
-
-void
-nmp_rules_manager_unref(NMPRulesManager *self)
-{
-    g_return_if_fail(NMP_IS_RULES_MANAGER(self));
-
-    if (--self->ref_count > 0)
-        return;
-
-    if (self->by_data) {
-        g_hash_table_destroy(self->by_user_tag);
-        g_hash_table_destroy(self->by_obj);
-        g_hash_table_destroy(self->by_data);
-    }
-    g_object_unref(self->platform);
-    g_slice_free(NMPRulesManager, self);
-}
diff --git a/src/core/platform/nmp-rules-manager.h b/src/core/platform/nmp-rules-manager.h
deleted file mode 100644
index 69cf9075..00000000
--- a/src/core/platform/nmp-rules-manager.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#ifndef __NMP_RULES_MANAGER_H__
-#define __NMP_RULES_MANAGER_H__
-
-#include "nm-platform.h"
-
-/*****************************************************************************/
-
-#define NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_rules_manager_new)
-
-typedef struct _NMPRulesManager NMPRulesManager;
-
-NMPRulesManager *nmp_rules_manager_new(NMPlatform *platform);
-
-void nmp_rules_manager_ref(NMPRulesManager *self);
-void nmp_rules_manager_unref(NMPRulesManager *self);
-
-#define nm_auto_unref_rules_manager nm_auto(_nmp_rules_manager_unref)
-NM_AUTO_DEFINE_FCN0(NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_manager_unref);
-
-void nmp_rules_manager_track(NMPRulesManager *            self,
-                             const NMPlatformRoutingRule *routing_rule,
-                             gint32                       track_priority,
-                             gconstpointer                user_tag,
-                             gconstpointer                user_tag_untrack);
-
-void nmp_rules_manager_track_default(NMPRulesManager *self,
-                                     int              addr_family,
-                                     gint32           track_priority,
-                                     gconstpointer    user_tag);
-
-void nmp_rules_manager_track_from_platform(NMPRulesManager *self,
-                                           NMPlatform *     platform,
-                                           int              addr_family,
-                                           gint32           tracking_priority,
-                                           gconstpointer    user_tag);
-
-void nmp_rules_manager_untrack(NMPRulesManager *            self,
-                               const NMPlatformRoutingRule *routing_rule,
-                               gconstpointer                user_tag);
-
-void nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag);
-
-void nmp_rules_manager_untrack_all(NMPRulesManager *self,
-                                   gconstpointer    user_tag,
-                                   gboolean         all /* or only dirty */);
-
-void nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules);
-
-/*****************************************************************************/
-
-#endif /* __NMP_RULES_MANAGER_H__ */
diff --git a/src/core/platform/tests/monitor.c b/src/core/platform/tests/monitor.c
index abdf2cd5..ff6fae65 100644
--- a/src/core/platform/tests/monitor.c
+++ b/src/core/platform/tests/monitor.c
@@ -8,7 +8,7 @@
 #include <stdlib.h>
 #include <syslog.h>
 
-#include "platform/nm-linux-platform.h"
+#include "libnm-platform/nm-linux-platform.h"
 
 #include "nm-test-utils-core.h"
 
diff --git a/src/core/platform/tests/test-address.c b/src/core/platform/tests/test-address.c
index d4d67105..4e1d1d6e 100644
--- a/src/core/platform/tests/test-address.c
+++ b/src/core/platform/tests/test-address.c
@@ -235,7 +235,7 @@ test_ip4_address_general_2(void)
     /* Looks like addresses are not announced by kernel when the interface
      * is down. Link-local IPv6 address is automatically added.
      */
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, DEVICE_IFINDEX, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, DEVICE_IFINDEX, IFF_UP, TRUE) >= 0);
 
     /* Add/delete notification */
     nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL);
@@ -325,7 +325,7 @@ test_ip4_address_peer(void)
 
     g_assert(addr != addr_peer);
 
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, TRUE) >= 0);
     accept_signals(address_removed, 0, G_MAXINT);
     accept_signals(address_added, 0, G_MAXINT);
 
@@ -397,7 +397,7 @@ test_ip4_address_peer_zero(void)
     peers[1] = addr_peer;
     peers[2] = 0;
 
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, TRUE) >= 0);
 
     nmtst_rand_perm(NULL, r_peers, peers, sizeof(peers[0]), G_N_ELEMENTS(peers));
     for (i = 0; i < G_N_ELEMENTS(peers); i++) {
diff --git a/src/core/platform/tests/test-cleanup.c b/src/core/platform/tests/test-cleanup.c
index b54eca34..e13bb8c8 100644
--- a/src/core/platform/tests/test-cleanup.c
+++ b/src/core/platform/tests/test-cleanup.c
@@ -44,9 +44,12 @@ test_cleanup_internal(void)
     g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL)));
     accept_signal(link_added);
     free_signal(link_added);
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET,
-                                     nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME),
-                                     NULL));
+    g_assert(
+        nm_platform_link_change_flags(NM_PLATFORM_GET,
+                                      nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME),
+                                      IFF_UP,
+                                      TRUE)
+        >= 0);
     ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME);
     g_assert(ifindex > 0);
 
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index 1b977145..2fc9e836 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -1413,6 +1413,48 @@ nmtstp_ip6_address_del(NMPlatform *    platform,
     }                                                                                       \
     G_STMT_END
 
+/* Due to rounding errors with clock_t_to_jiffies()/jiffies_to_clock_t(), kernel cannot
+ * store all requested values. That means, when we try to configure a bridge with
+ * the @requested values, the actually configured settings are slightly off, as
+ * @kernel.
+ *
+ * This function takes @requested and returns it as @dst output. All fields
+ * that might be mangled by kernel (according to @kernel) are adjusted. The
+ * result is almost identical to @requested, but some fields might be adjusted
+ * to their @kernel value. */
+const NMPlatformLnkBridge *
+nmtstp_link_bridge_normalize_jiffies_time(const NMPlatformLnkBridge *requested,
+                                          const NMPlatformLnkBridge *kernel,
+                                          NMPlatformLnkBridge *      dst)
+{
+    g_assert(requested);
+    g_assert(dst);
+    g_assert(kernel);
+
+    if (dst != requested)
+        *dst = *requested;
+
+#define _normalize_field(dst, kernel, field)                                         \
+    G_STMT_START                                                                     \
+    {                                                                                \
+        (dst)->field = nmtstp_normalize_jiffies_time((dst)->field, (kernel)->field); \
+    }                                                                                \
+    G_STMT_END
+
+    _normalize_field(dst, kernel, forward_delay);
+    _normalize_field(dst, kernel, hello_time);
+    _normalize_field(dst, kernel, max_age);
+    _normalize_field(dst, kernel, ageing_time);
+    _normalize_field(dst, kernel, mcast_last_member_interval);
+    _normalize_field(dst, kernel, mcast_membership_interval);
+    _normalize_field(dst, kernel, mcast_querier_interval);
+    _normalize_field(dst, kernel, mcast_query_interval);
+    _normalize_field(dst, kernel, mcast_query_response_interval);
+    _normalize_field(dst, kernel, mcast_startup_query_interval);
+
+    return dst;
+}
+
 const NMPlatformLink *
 nmtstp_link_bridge_add(NMPlatform *               platform,
                        gboolean                   external_command,
@@ -1421,7 +1463,8 @@ nmtstp_link_bridge_add(NMPlatform *               platform,
 {
     const NMPlatformLink *     pllink = NULL;
     const NMPlatformLnkBridge *ll     = NULL;
-    int                        r      = 0;
+    NMPlatformLnkBridge        lnk_normalized;
+    int                        r = 0;
 
     g_assert(nm_utils_ifname_valid_kernel(name, NULL));
 
@@ -1542,10 +1585,9 @@ nmtstp_link_bridge_add(NMPlatform *               platform,
 
     ll = NMP_OBJECT_CAST_LNK_BRIDGE(NMP_OBJECT_UP_CAST(pllink)->_link.netlink.lnk);
 
-    /* account for roundtrip rounding error with clock_t_to_jiffies()/jiffies_to_clock_t(). */
-    g_assert_cmpint(lnk->forward_delay, >=, ll->forward_delay - 1);
-    g_assert_cmpint(lnk->forward_delay, <=, ll->forward_delay);
+    lnk = nmtstp_link_bridge_normalize_jiffies_time(lnk, ll, &lnk_normalized);
 
+    g_assert_cmpint(lnk->forward_delay, ==, ll->forward_delay);
     g_assert_cmpint(lnk->hello_time, ==, ll->hello_time);
     g_assert_cmpint(lnk->max_age, ==, ll->max_age);
     g_assert_cmpint(lnk->ageing_time, ==, ll->ageing_time);
@@ -2246,9 +2288,9 @@ nmtstp_link_set_updown(NMPlatform *platform, gboolean external_command, int ifin
         nmtstp_run_command_check("ip link set %s %s", ifname, up ? "up" : "down");
     } else {
         if (up)
-            g_assert(nm_platform_link_set_up(platform, ifindex, NULL));
+            g_assert(nm_platform_link_change_flags(platform, ifindex, IFF_UP, TRUE) >= 0);
         else
-            g_assert(nm_platform_link_set_down(platform, ifindex));
+            g_assert(nm_platform_link_change_flags(platform, ifindex, IFF_UP, FALSE) >= 0);
     }
 
     /* Let's wait until we get the result */
diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h
index 618645fe..1fb97d24 100644
--- a/src/core/platform/tests/test-common.h
+++ b/src/core/platform/tests/test-common.h
@@ -11,10 +11,10 @@
 #include <linux/if_link.h>
 #include <linux/ip6_tunnel.h>
 
-#include "platform/nm-platform.h"
-#include "platform/nmp-object.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-platform/nmp-object.h"
 #include "platform/nm-fake-platform.h"
-#include "platform/nm-linux-platform.h"
+#include "libnm-platform/nm-linux-platform.h"
 
 #include "nm-test-utils-core.h"
 
@@ -22,6 +22,24 @@
 
 /*****************************************************************************/
 
+#define nmtstp_normalize_jiffies_time(requested_value, kernel_value)                             \
+    ({                                                                                           \
+        typeof(kernel_value)  _kernel_value    = (kernel_value);                                 \
+        typeof(_kernel_value) _requested_value = (requested_value);                              \
+                                                                                                 \
+        /* kernel stores some values (like bridge's forward_delay) in jiffies. When converting
+         * back and forth (clock_t_to_jiffies()/jiffies_to_clock_t()), the value reported back
+         * to user space may have rounding errors (of +/- 1), depending on CONFIG_HZ setting.
+         *
+         * Normalize the requested_value to the kernel_value, if it look as if a rounding
+         * error happens. If the difference is larger than +/- 1, no normalization happens! */   \
+                                                                                                 \
+        ((_requested_value >= (NM_MAX(_kernel_value, 1) - 1))                                    \
+         && (_requested_value <= (NM_MIN(_kernel_value, ~((typeof(_kernel_value)) 0) - 1) + 1))) \
+            ? _kernel_value                                                                      \
+            : _requested_value;                                                                  \
+    })
+
 #define _NMLOG_PREFIX_NAME "platform-test"
 #define _NMLOG_DOMAIN      LOGD_PLATFORM
 #define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, __VA_ARGS__)
@@ -437,6 +455,11 @@ gboolean nmtstp_kernel_support_get(NMPlatformKernelSupportType type);
 void
 nmtstp_link_set_updown(NMPlatform *platform, gboolean external_command, int ifindex, gboolean up);
 
+const NMPlatformLnkBridge *
+nmtstp_link_bridge_normalize_jiffies_time(const NMPlatformLnkBridge *requested,
+                                          const NMPlatformLnkBridge *kernel,
+                                          NMPlatformLnkBridge *      dst);
+
 const NMPlatformLink *nmtstp_link_bridge_add(NMPlatform *               platform,
                                              gboolean                   external_command,
                                              const char *               name,
@@ -519,7 +542,7 @@ _nmtstp_env1_wrapper_setup(const NmtstTestData *test_data)
     g_assert_cmpint(NMTSTP_ENV1_IFINDEX, ==, -1);
 
     if (GPOINTER_TO_INT(p_ifup))
-        g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, *p_ifindex, NULL));
+        g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, *p_ifindex, IFF_UP, TRUE) >= 0);
 
     nm_platform_process_events(NM_PLATFORM_GET);
 
diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index 3aaa3759..f6c3841e 100644
--- a/src/core/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -11,11 +11,11 @@
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
-#include "nm-glib-aux/nm-io-utils.h"
-#include "nm-base/nm-ethtool-base.h"
-#include "platform/nmp-object.h"
-#include "nm-platform/nmp-netns.h"
-#include "nm-platform/nm-platform-utils.h"
+#include "libnm-glib-aux/nm-io-utils.h"
+#include "libnm-base/nm-ethtool-base.h"
+#include "libnm-platform/nmp-object.h"
+#include "libnm-platform/nmp-netns.h"
+#include "libnm-platform/nm-platform-utils.h"
 
 #include "test-common.h"
 #include "nm-test-utils-core.h"
@@ -48,13 +48,15 @@ test_bogus(void)
     g_assert(!nm_platform_link_get_type(NM_PLATFORM_GET, BOGUS_IFINDEX));
     g_assert(!nm_platform_link_get_type_name(NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-    g_assert(!nm_platform_link_set_up(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
+    g_assert(!(nm_platform_link_change_flags(NM_PLATFORM_GET, BOGUS_IFINDEX, IFF_UP, TRUE) >= 0));
 
-    g_assert(!nm_platform_link_set_down(NM_PLATFORM_GET, BOGUS_IFINDEX));
+    g_assert(!(nm_platform_link_change_flags(NM_PLATFORM_GET, BOGUS_IFINDEX, IFF_UP, FALSE) >= 0));
 
-    g_assert(!nm_platform_link_set_arp(NM_PLATFORM_GET, BOGUS_IFINDEX));
+    g_assert(
+        !(nm_platform_link_change_flags(NM_PLATFORM_GET, BOGUS_IFINDEX, IFF_NOARP, TRUE) >= 0));
 
-    g_assert(!nm_platform_link_set_noarp(NM_PLATFORM_GET, BOGUS_IFINDEX));
+    g_assert(
+        !(nm_platform_link_change_flags(NM_PLATFORM_GET, BOGUS_IFINDEX, IFF_NOARP, FALSE) >= 0));
 
     g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, BOGUS_IFINDEX));
     g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -147,7 +149,8 @@ software_add(NMLinkType link_type, const char *name)
                                                 NM_PLATFORM_SIGNAL_CHANGED,
                                                 link_callback,
                                                 parent_ifindex);
-            g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, parent_ifindex, NULL));
+            g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, parent_ifindex, IFF_UP, TRUE)
+                     >= 0);
             if (was_up) {
                 /* when NM is running in the background, it will mess with addrgenmode which might cause additional signals. */
                 accept_signals(parent_changed, 0, 1);
@@ -233,7 +236,7 @@ test_slave(int master, int type, SignalData *master_changed)
      * See https://bugzilla.redhat.com/show_bug.cgi?id=910348
      */
     g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
-    g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0);
     g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
     ensure_no_signal(link_changed);
 
@@ -263,7 +266,7 @@ test_slave(int master, int type, SignalData *master_changed)
                      &test_link_changed_signal_arg2);
 
     /* Set master up */
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, master, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, master, IFF_UP, TRUE) >= 0);
     g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, master));
     accept_signals(master_changed, 1, 3);
 
@@ -284,7 +287,7 @@ test_slave(int master, int type, SignalData *master_changed)
     switch (nm_platform_link_get_type(NM_PLATFORM_GET, master)) {
     case NM_LINK_TYPE_BOND:
     case NM_LINK_TYPE_TEAM:
-        g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex));
+        g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0);
         accept_signal(link_changed);
         accept_signals(master_changed, 0, 3);
         break;
@@ -317,7 +320,7 @@ test_slave(int master, int type, SignalData *master_changed)
     }
 
     /* Set slave up and see if master gets up too */
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, TRUE) >= 0);
     g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex));
     g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, master));
     accept_signals(link_changed, 1, 3);
@@ -439,10 +442,10 @@ test_software(NMLinkType link_type, const char *link_typename)
 
     /* Set ARP/NOARP */
     g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex));
-    g_assert(nm_platform_link_set_noarp(NM_PLATFORM_GET, ifindex));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_NOARP, TRUE) >= 0);
     g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex));
     accept_signals(link_changed, 1, 2);
-    g_assert(nm_platform_link_set_arp(NM_PLATFORM_GET, ifindex));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_NOARP, FALSE) >= 0);
     g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex));
     accept_signal(link_changed);
 
@@ -672,21 +675,21 @@ test_internal(void)
     /* Up/connected */
     g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
     g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex));
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, TRUE) >= 0);
     g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
     g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex));
     accept_signals(link_changed, 1, 2);
-    g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0);
     g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
     g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex));
     accept_signal(link_changed);
 
     /* arp/noarp */
     g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex));
-    g_assert(nm_platform_link_set_arp(NM_PLATFORM_GET, ifindex));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_NOARP, FALSE) >= 0);
     g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex));
     accept_signal(link_changed);
-    g_assert(nm_platform_link_set_noarp(NM_PLATFORM_GET, ifindex));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_NOARP, TRUE) >= 0);
     g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex));
     accept_signal(link_changed);
 
@@ -1303,7 +1306,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "gre0")) {
             /* Seems that the ip_gre module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0;
         }
 
         if (!nmtstp_link_gre_add(NULL, ext, DEVICE_NAME, &lnk_gre)) {
@@ -1330,7 +1333,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "gretap0")) {
             /* Seems that the ip_gre module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0;
         }
 
         if (!nmtstp_link_gre_add(NULL, ext, DEVICE_NAME, &lnk_gre)) {
@@ -1350,7 +1353,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "tunl0")) {
             /* Seems that the ipip module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ipip", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "ipip", NULL) != 0;
         }
 
         lnk_ipip.local              = nmtst_inet4_from_string("1.2.3.4");
@@ -1376,7 +1379,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6tnl0")) {
             /* Seems that the ip6_tunnel module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_tunnel", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "ip6_tunnel", NULL) != 0;
         }
 
         switch (test_data->test_mode) {
@@ -1418,7 +1421,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6gre0")) {
             /* Seems that the ip6_tunnel module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0;
         }
 
         lnk_ip6tnl.local          = *nmtst_inet6_from_string("fd01::42");
@@ -1445,7 +1448,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6gre0")) {
             /* Seems that the ip6_tunnel module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0;
         }
 
         lnk_ip6tnl.local          = *nmtst_inet6_from_string("fe80::abcd");
@@ -1526,7 +1529,7 @@ test_software_detect(gconstpointer user_data)
 
         if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "sit0")) {
             /* Seems that the sit module is not loaded... try to load it. */
-            gracefully_skip = nm_utils_modprobe(NULL, TRUE, "sit", NULL) != 0;
+            gracefully_skip = nmp_utils_modprobe(NULL, TRUE, "sit", NULL) != 0;
         }
 
         if (!nmtstp_link_sit_add(NULL, ext, DEVICE_NAME, &lnk_sit)) {
@@ -1679,17 +1682,25 @@ test_software_detect(gconstpointer user_data)
         case NM_LINK_TYPE_BRIDGE:
         {
             const NMPlatformLnkBridge *plnk = &lnk->lnk_bridge;
+            NMPlatformLnkBridge        lnk_bridge_norm_stack;
+            const NMPlatformLnkBridge *lnk_bridge_norm;
 
             g_assert(plnk == nm_platform_link_get_lnk_bridge(NM_PLATFORM_GET, ifindex, NULL));
-            g_assert_cmpint(nm_platform_lnk_bridge_cmp(&lnk_bridge, plnk), ==, 0);
-            g_assert_cmpint(plnk->forward_delay, ==, 1560);
-            g_assert_cmpint(plnk->hello_time, ==, 150);
-            g_assert_cmpint(plnk->max_age, ==, 2100);
-            g_assert_cmpint(plnk->ageing_time, ==, 2200);
+
+            lnk_bridge_norm = nmtstp_link_bridge_normalize_jiffies_time(&lnk_bridge,
+                                                                        plnk,
+                                                                        &lnk_bridge_norm_stack);
+
+            g_assert_cmpint(nm_platform_lnk_bridge_cmp(lnk_bridge_norm, plnk), ==, 0);
+
+            g_assert_cmpint(plnk->forward_delay, ==, lnk_bridge_norm->forward_delay);
+            g_assert_cmpint(plnk->hello_time, ==, lnk_bridge_norm->hello_time);
+            g_assert_cmpint(plnk->max_age, ==, lnk_bridge_norm->max_age);
+            g_assert_cmpint(plnk->ageing_time, ==, lnk_bridge_norm->ageing_time);
             g_assert_cmpint(plnk->stp_state, ==, TRUE);
             g_assert_cmpint(plnk->priority, ==, 22);
             g_assert_cmpint(plnk->vlan_protocol, ==, 0x8100);
-            g_assert_cmpint(plnk->vlan_stats_enabled, ==, lnk_bridge.vlan_stats_enabled);
+            g_assert_cmpint(plnk->vlan_stats_enabled, ==, lnk_bridge_norm->vlan_stats_enabled);
             g_assert_cmpint(plnk->group_fwd_mask, ==, 8);
             g_assert_cmpint(plnk->mcast_snooping, ==, TRUE);
             g_assert_cmpint(plnk->mcast_router, ==, 1);
@@ -1698,13 +1709,22 @@ test_software_detect(gconstpointer user_data)
             g_assert_cmpint(plnk->mcast_hash_max, ==, 1024);
             g_assert_cmpint(plnk->mcast_last_member_count, ==, 2);
             g_assert_cmpint(plnk->mcast_startup_query_count, ==, 3);
-            g_assert_cmpint(plnk->mcast_last_member_interval, ==, 5000);
-            g_assert_cmpint(plnk->mcast_membership_interval, ==, 25000);
-            g_assert_cmpint(plnk->mcast_querier_interval, ==, 26000);
-            g_assert_cmpint(plnk->mcast_query_interval, ==, 12000);
-            g_assert_cmpint(plnk->mcast_query_response_interval, ==, 5200);
-            g_assert_cmpint(plnk->mcast_startup_query_interval, ==, 3000);
-            g_assert_cmpint(nm_platform_lnk_bridge_cmp(&lnk_bridge, plnk), ==, 0);
+            g_assert_cmpint(plnk->mcast_last_member_interval,
+                            ==,
+                            lnk_bridge_norm->mcast_last_member_interval);
+            g_assert_cmpint(plnk->mcast_membership_interval,
+                            ==,
+                            lnk_bridge_norm->mcast_membership_interval);
+            g_assert_cmpint(plnk->mcast_querier_interval,
+                            ==,
+                            lnk_bridge_norm->mcast_querier_interval);
+            g_assert_cmpint(plnk->mcast_query_interval, ==, lnk_bridge_norm->mcast_query_interval);
+            g_assert_cmpint(plnk->mcast_query_response_interval,
+                            ==,
+                            lnk_bridge_norm->mcast_query_response_interval);
+            g_assert_cmpint(plnk->mcast_startup_query_interval,
+                            ==,
+                            lnk_bridge_norm->mcast_startup_query_interval);
             break;
         }
         case NM_LINK_TYPE_GRE:
@@ -2049,7 +2069,7 @@ _assert_xgress_qos_mappings_impl(int ifindex, gboolean is_ingress_map, int n_ent
     _assert_xgress_qos_mappings(ifindex, FALSE, n_entries, __VA_ARGS__)
 
 static void
-_assert_vlan_flags(int ifindex, NMVlanFlags flags)
+_assert_vlan_flags(int ifindex, _NMVlanFlags flags)
 {
     const NMPlatformLnkVlan *plnk;
 
@@ -2362,8 +2382,8 @@ test_vlan_set_xgress(void)
 
         g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET,
                                               ifindex,
-                                              NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP,
-                                              NM_VLAN_FLAG_REORDER_HEADERS,
+                                              _NM_VLAN_FLAG_REORDER_HEADERS | _NM_VLAN_FLAG_GVRP,
+                                              _NM_VLAN_FLAG_REORDER_HEADERS,
                                               TRUE,
                                               ingress_map,
                                               G_N_ELEMENTS(ingress_map),
@@ -2372,7 +2392,7 @@ test_vlan_set_xgress(void)
                                               G_N_ELEMENTS(egress_map)));
         _assert_ingress_qos_mappings(ifindex, 2, 4, 1, 6, 12);
         _assert_egress_qos_mappings(ifindex, 2, 1, 5, 3232, 7);
-        _assert_vlan_flags(ifindex, NM_VLAN_FLAG_REORDER_HEADERS);
+        _assert_vlan_flags(ifindex, _NM_VLAN_FLAG_REORDER_HEADERS);
     }
 
     {
@@ -2393,8 +2413,8 @@ test_vlan_set_xgress(void)
 
         g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET,
                                               ifindex,
-                                              NM_VLAN_FLAG_GVRP,
-                                              NM_VLAN_FLAG_GVRP,
+                                              _NM_VLAN_FLAG_GVRP,
+                                              _NM_VLAN_FLAG_GVRP,
                                               FALSE,
                                               ingress_map,
                                               G_N_ELEMENTS(ingress_map),
@@ -2403,7 +2423,7 @@ test_vlan_set_xgress(void)
                                               G_N_ELEMENTS(egress_map)));
         _assert_ingress_qos_mappings(ifindex, 2, 4, 1, 6, 12);
         _assert_egress_qos_mappings(ifindex, 2, 1, 7, 64, 4);
-        _assert_vlan_flags(ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP);
+        _assert_vlan_flags(ifindex, _NM_VLAN_FLAG_REORDER_HEADERS | _NM_VLAN_FLAG_GVRP);
     }
 
     nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE);
diff --git a/src/core/platform/tests/test-nmp-object.c b/src/core/platform/tests/test-nmp-object.c
index 310611ee..6d2236ec 100644
--- a/src/core/platform/tests/test-nmp-object.c
+++ b/src/core/platform/tests/test-nmp-object.c
@@ -8,8 +8,8 @@
 #include <libudev.h>
 #include <linux/pkt_sched.h>
 
-#include "platform/nmp-object.h"
-#include "nm-udev-aux/nm-udev-utils.h"
+#include "libnm-platform/nmp-object.h"
+#include "libnm-udev-aux/nm-udev-utils.h"
 
 #include "nm-test-utils-core.h"
 
diff --git a/src/core/platform/tests/test-platform-general.c b/src/core/platform/tests/test-platform-general.c
index b426cc65..05158228 100644
--- a/src/core/platform/tests/test-platform-general.c
+++ b/src/core/platform/tests/test-platform-general.c
@@ -7,8 +7,8 @@
 
 #include <linux/rtnetlink.h>
 
-#include "nm-platform/nm-platform-utils.h"
-#include "platform/nm-linux-platform.h"
+#include "libnm-platform/nm-platform-utils.h"
+#include "libnm-platform/nm-linux-platform.h"
 
 #include "nm-test-utils-core.h"
 
@@ -584,7 +584,7 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
     nmtst_stable_rand(710086081, rand_map, sizeof(rand_map[0]) * N_ADDRESSES);
 
     for (i = 0; i < N_ADDRESSES; i++) {
-        NMPlatformIPXAddress *a = (gpointer)(&addresses[i * ELM_SIZE]);
+        NMPlatformIPXAddress *a = (gpointer) (&addresses[i * ELM_SIZE]);
         guint64               r = rand_map[i];
         struct in6_addr *     a6;
 
@@ -684,10 +684,10 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
                       (gpointer) test_data);
 
     for (i = 0; i < N_ADDRESSES; i++) {
-        const NMPlatformIPXAddress *a = (gconstpointer)(&addresses[i * ELM_SIZE]);
+        const NMPlatformIPXAddress *a = (gconstpointer) (&addresses[i * ELM_SIZE]);
 
         for (j = i + 1; j < N_ADDRESSES; j++) {
-            const NMPlatformIPXAddress *b = (gconstpointer)(&addresses[j * ELM_SIZE]);
+            const NMPlatformIPXAddress *b = (gconstpointer) (&addresses[j * ELM_SIZE]);
             int                         c1;
             int                         c2;
 
diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index 645bb5b1..4b1db7fa 100644
--- a/src/core/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -9,8 +9,8 @@
 #include <linux/fib_rules.h>
 
 #include "nm-core-utils.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "platform/nmp-rules-manager.h"
+#include "libnm-platform/nm-platform-utils.h"
+#include "libnm-platform/nmp-rules-manager.h"
 
 #include "test-common.h"
 
@@ -332,30 +332,33 @@ test_ip4_route(void)
     /* Test route listing */
     routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex);
     memset(rts, 0, sizeof(rts));
-    rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
-    rts[0].network   = gateway;
-    rts[0].plen      = 32;
-    rts[0].ifindex   = ifindex;
-    rts[0].gateway   = INADDR_ANY;
-    rts[0].metric    = metric;
-    rts[0].mss       = mss;
-    rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK);
-    rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
-    rts[1].network   = network;
-    rts[1].plen      = plen;
-    rts[1].ifindex   = ifindex;
-    rts[1].gateway   = gateway;
-    rts[1].metric    = metric;
-    rts[1].mss       = mss;
-    rts[1].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
-    rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
-    rts[2].network   = 0;
-    rts[2].plen      = 0;
-    rts[2].ifindex   = ifindex;
-    rts[2].gateway   = gateway;
-    rts[2].metric    = metric;
-    rts[2].mss       = mss;
-    rts[2].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
+    rts[0].rt_source   = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+    rts[0].network     = gateway;
+    rts[0].plen        = 32;
+    rts[0].ifindex     = ifindex;
+    rts[0].gateway     = INADDR_ANY;
+    rts[0].metric      = metric;
+    rts[0].mss         = mss;
+    rts[0].is_external = TRUE;
+    rts[0].scope_inv   = nm_platform_route_scope_inv(RT_SCOPE_LINK);
+    rts[1].rt_source   = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+    rts[1].network     = network;
+    rts[1].plen        = plen;
+    rts[1].ifindex     = ifindex;
+    rts[1].gateway     = gateway;
+    rts[1].metric      = metric;
+    rts[1].mss         = mss;
+    rts[1].scope_inv   = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
+    rts[1].is_external = TRUE;
+    rts[2].rt_source   = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+    rts[2].network     = 0;
+    rts[2].plen        = 0;
+    rts[2].ifindex     = ifindex;
+    rts[2].gateway     = gateway;
+    rts[2].metric      = metric;
+    rts[2].mss         = mss;
+    rts[2].scope_inv   = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE);
+    rts[2].is_external = TRUE;
     g_assert_cmpint(routes->len, ==, 3);
     nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata,
                                          rts,
@@ -489,30 +492,33 @@ test_ip6_route(void)
     /* Test route listing */
     routes = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex);
     memset(rts, 0, sizeof(rts));
-    rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
-    rts[0].network   = gateway;
-    rts[0].plen      = 128;
-    rts[0].ifindex   = ifindex;
-    rts[0].gateway   = in6addr_any;
-    rts[0].pref_src  = in6addr_any;
-    rts[0].metric    = metric;
-    rts[0].mss       = mss;
-    rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
-    rts[1].network   = network;
-    rts[1].plen      = plen;
-    rts[1].ifindex   = ifindex;
-    rts[1].gateway   = gateway;
-    rts[1].pref_src  = pref_src;
-    rts[1].metric    = metric;
-    rts[1].mss       = mss;
-    rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
-    rts[2].network   = in6addr_any;
-    rts[2].plen      = 0;
-    rts[2].ifindex   = ifindex;
-    rts[2].gateway   = gateway;
-    rts[2].pref_src  = in6addr_any;
-    rts[2].metric    = metric;
-    rts[2].mss       = mss;
+    rts[0].rt_source   = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+    rts[0].network     = gateway;
+    rts[0].plen        = 128;
+    rts[0].ifindex     = ifindex;
+    rts[0].gateway     = in6addr_any;
+    rts[0].pref_src    = in6addr_any;
+    rts[0].metric      = metric;
+    rts[0].mss         = mss;
+    rts[0].is_external = TRUE;
+    rts[1].rt_source   = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+    rts[1].network     = network;
+    rts[1].plen        = plen;
+    rts[1].ifindex     = ifindex;
+    rts[1].gateway     = gateway;
+    rts[1].pref_src    = pref_src;
+    rts[1].metric      = metric;
+    rts[1].mss         = mss;
+    rts[1].is_external = TRUE;
+    rts[2].rt_source   = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER);
+    rts[2].network     = in6addr_any;
+    rts[2].plen        = 0;
+    rts[2].ifindex     = ifindex;
+    rts[2].gateway     = gateway;
+    rts[2].pref_src    = in6addr_any;
+    rts[2].metric      = metric;
+    rts[2].mss         = mss;
+    rts[2].is_external = TRUE;
     g_assert_cmpint(routes->len, ==, 3);
     nmtst_platform_ip6_routes_equal_aptr((const NMPObject *const *) routes->pdata,
                                          rts,
@@ -709,6 +715,7 @@ test_ip4_route_options(gconstpointer test_data)
     for (i = 0; i < rts_n; i++) {
         rts_cmp[i] = rts_add[i];
         nm_platform_ip_route_normalize(AF_INET, NM_PLATFORM_IP_ROUTE_CAST(&rts_cmp[i]));
+        rts_cmp[i].is_external = TRUE;
     }
 
     routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, IFINDEX);
@@ -880,6 +887,7 @@ test_ip6_route_options(gconstpointer test_data)
     for (i = 0; i < rts_n; i++) {
         rts_cmp[i] = rts_add[i];
         nm_platform_ip_route_normalize(AF_INET6, NM_PLATFORM_IP_ROUTE_CAST(&rts_cmp[i]));
+        rts_cmp[i].is_external = TRUE;
     }
 
     routes = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, IFINDEX);
diff --git a/src/core/platform/tests/test-tc.c b/src/core/platform/tests/test-tc.c
index d15c3d76..cd9536cc 100644
--- a/src/core/platform/tests/test-tc.c
+++ b/src/core/platform/tests/test-tc.c
@@ -5,9 +5,9 @@
 #include <linux/pkt_sched.h>
 
 #include "nm-test-utils-core.h"
-#include "platform/nmp-object.h"
-#include "nm-platform/nmp-netns.h"
-#include "nm-platform/nm-platform-utils.h"
+#include "libnm-platform/nmp-object.h"
+#include "libnm-platform/nmp-netns.h"
+#include "libnm-platform/nm-platform-utils.h"
 #include "test-common.h"
 
 static NMPObject *
diff --git a/src/core/platform/wifi/nm-wifi-utils-nl80211.c b/src/core/platform/wifi/nm-wifi-utils-nl80211.c
deleted file mode 100644
index ad067248..00000000
--- a/src/core/platform/wifi/nm-wifi-utils-nl80211.c
+++ /dev/null
@@ -1,909 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2005 - 2018 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- * Copyright (C) 2011 Intel Corporation. All rights reserved.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-wifi-utils-nl80211.h"
-
-#include <sys/ioctl.h>
-#include <net/ethernet.h>
-#include <unistd.h>
-#include <linux/nl80211.h>
-#include <linux/if.h>
-
-#include "nm-platform/nm-netlink.h"
-#include "nm-wifi-utils-private.h"
-#include "platform/nm-platform.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "nm-utils.h"
-
-#define _NMLOG_PREFIX_NAME "wifi-nl80211"
-#define _NMLOG_DOMAIN      LOGD_PLATFORM | LOGD_WIFI
-#define _NMLOG(level, ...)                                                             \
-    G_STMT_START                                                                       \
-    {                                                                                  \
-        char        _ifname_buf[IFNAMSIZ];                                             \
-        const char *_ifname =                                                          \
-            self ? nmp_utils_if_indextoname(self->parent.ifindex, _ifname_buf) : NULL; \
-                                                                                       \
-        nm_log((level),                                                                \
-               _NMLOG_DOMAIN,                                                          \
-               _ifname ?: NULL,                                                        \
-               NULL,                                                                   \
-               "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                        \
-               _NMLOG_PREFIX_NAME,                                                     \
-               NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "")                    \
-                   _NM_UTILS_MACRO_REST(__VA_ARGS__));                                 \
-    }                                                                                  \
-    G_STMT_END
-
-typedef struct {
-    NMWifiUtils     parent;
-    struct nl_sock *nl_sock;
-    guint32 *       freqs;
-    int             id;
-    int             num_freqs;
-    int             phy;
-    bool            can_wowlan : 1;
-} NMWifiUtilsNl80211;
-
-typedef struct {
-    NMWifiUtilsClass parent;
-} NMWifiUtilsNl80211Class;
-
-G_DEFINE_TYPE(NMWifiUtilsNl80211, nm_wifi_utils_nl80211, NM_TYPE_WIFI_UTILS)
-
-static int
-ack_handler(struct nl_msg *msg, void *arg)
-{
-    int *done = arg;
-    *done     = 1;
-    return NL_STOP;
-}
-
-static int
-finish_handler(struct nl_msg *msg, void *arg)
-{
-    int *done = arg;
-    *done     = 1;
-    return NL_SKIP;
-}
-
-static int
-error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
-{
-    int *done = arg;
-    *done     = err->error;
-    return NL_SKIP;
-}
-
-static struct nl_msg *
-_nl80211_alloc_msg(int id, int ifindex, int phy, guint32 cmd, guint32 flags)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-    msg = nlmsg_alloc();
-    genlmsg_put(msg, 0, 0, id, 0, flags, cmd, 0);
-    NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
-    if (phy != -1)
-        NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, phy);
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static struct nl_msg *
-nl80211_alloc_msg(NMWifiUtilsNl80211 *self, guint32 cmd, guint32 flags)
-{
-    return _nl80211_alloc_msg(self->id, self->parent.ifindex, self->phy, cmd, flags);
-}
-
-static int
-nl80211_send_and_recv(NMWifiUtilsNl80211 *self,
-                      struct nl_msg *     msg,
-                      int (*valid_handler)(struct nl_msg *, void *),
-                      void *valid_data)
-{
-    int                err;
-    int                done = 0;
-    const struct nl_cb cb   = {
-        .err_cb     = error_handler,
-        .err_arg    = &done,
-        .finish_cb  = finish_handler,
-        .finish_arg = &done,
-        .ack_cb     = ack_handler,
-        .ack_arg    = &done,
-        .valid_cb   = valid_handler,
-        .valid_arg  = valid_data,
-    };
-
-    g_return_val_if_fail(msg != NULL, -ENOMEM);
-
-    err = nl_send_auto(self->nl_sock, msg);
-    if (err < 0)
-        return err;
-
-    /* Loop until one of our NL callbacks says we're done; on success
-     * done will be 1, on error it will be < 0.
-     */
-    while (!done) {
-        err = nl_recvmsgs(self->nl_sock, &cb);
-        if (err < 0 && err != -EAGAIN) {
-            /* Kernel scan list can change while we are dumping it, as new scan
-             * results from H/W can arrive. BSS info is assured to be consistent
-             * and we don't need consistent view of whole scan list. Hence do
-             * not warn on DUMP_INTR error for get scan command.
-             */
-            if (err == -NME_NL_DUMP_INTR
-                && genlmsg_hdr(nlmsg_hdr(msg))->cmd == NL80211_CMD_GET_SCAN)
-                break;
-
-            _LOGW("nl_recvmsgs() error: (%d) %s", err, nm_strerror(err));
-            break;
-        }
-    }
-
-    if (err >= 0 && done < 0)
-        err = done;
-    return err;
-}
-
-static void
-dispose(GObject *object)
-{
-    NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211(object);
-
-    nm_clear_g_free(&self->freqs);
-}
-
-struct nl80211_iface_info {
-    NM80211Mode mode;
-    uint32_t    freq;
-};
-
-static int
-nl80211_iface_info_handler(struct nl_msg *msg, void *arg)
-{
-    struct nl80211_iface_info *info = arg;
-    struct genlmsghdr *        gnlh = nlmsg_data(nlmsg_hdr(msg));
-    struct nlattr *            tb[NL80211_ATTR_MAX + 1];
-
-    if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
-        return NL_SKIP;
-
-    if (!tb[NL80211_ATTR_IFTYPE])
-        return NL_SKIP;
-
-    switch (nla_get_u32(tb[NL80211_ATTR_IFTYPE])) {
-    case NL80211_IFTYPE_ADHOC:
-        info->mode = NM_802_11_MODE_ADHOC;
-        break;
-    case NL80211_IFTYPE_AP:
-        info->mode = NM_802_11_MODE_AP;
-        break;
-    case NL80211_IFTYPE_STATION:
-        info->mode = NM_802_11_MODE_INFRA;
-        break;
-    case NL80211_IFTYPE_MESH_POINT:
-        info->mode = NM_802_11_MODE_MESH;
-        break;
-    }
-
-    if (tb[NL80211_ATTR_WIPHY_FREQ] != NULL)
-        info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
-
-    return NL_SKIP;
-}
-
-static NM80211Mode
-wifi_nl80211_get_mode(NMWifiUtils *data)
-{
-    NMWifiUtilsNl80211 *      self       = (NMWifiUtilsNl80211 *) data;
-    struct nl80211_iface_info iface_info = {
-        .mode = NM_802_11_MODE_UNKNOWN,
-    };
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0);
-
-    if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0)
-        return NM_802_11_MODE_UNKNOWN;
-
-    return iface_info.mode;
-}
-
-static gboolean
-wifi_nl80211_set_mode(NMWifiUtils *data, const NM80211Mode mode)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-    int                          err;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0);
-
-    switch (mode) {
-    case NM_802_11_MODE_INFRA:
-        NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_STATION);
-        break;
-    case NM_802_11_MODE_ADHOC:
-        NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_ADHOC);
-        break;
-    case NM_802_11_MODE_AP:
-        NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP);
-        break;
-    case NM_802_11_MODE_MESH:
-        NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT);
-        break;
-    default:
-        g_assert_not_reached();
-    }
-
-    err = nl80211_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-wifi_nl80211_set_powersave(NMWifiUtils *data, guint32 powersave)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-    int                          err;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_SET_POWER_SAVE, 0);
-    NLA_PUT_U32(msg,
-                NL80211_ATTR_PS_STATE,
-                powersave == 1 ? NL80211_PS_ENABLED : NL80211_PS_DISABLED);
-    err = nl80211_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static int
-nl80211_get_wake_on_wlan_handler(struct nl_msg *msg, void *arg)
-{
-    NMSettingWirelessWakeOnWLan *wowl = arg;
-    struct nlattr *              attrs[NL80211_ATTR_MAX + 1];
-    struct nlattr *              trig[NUM_NL80211_WOWLAN_TRIG];
-    struct genlmsghdr *          gnlh = nlmsg_data(nlmsg_hdr(msg));
-
-    nla_parse_arr(attrs, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
-
-    if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS])
-        return NL_SKIP;
-
-    nla_parse_arr(trig,
-                  nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
-                  nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
-                  NULL);
-
-    *wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
-    if (trig[NL80211_WOWLAN_TRIG_ANY])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY;
-    if (trig[NL80211_WOWLAN_TRIG_DISCONNECT])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT;
-    if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC;
-    if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE;
-    if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST;
-    if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE;
-    if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE;
-    if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION])
-        *wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP;
-
-    return NL_SKIP;
-}
-
-static NMSettingWirelessWakeOnWLan
-wifi_nl80211_get_wake_on_wlan(NMWifiUtils *data)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    NMSettingWirelessWakeOnWLan  wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WOWLAN, 0);
-
-    nl80211_send_and_recv(self, msg, nl80211_get_wake_on_wlan_handler, &wowl);
-
-    return wowl;
-}
-
-static gboolean
-wifi_nl80211_set_wake_on_wlan(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-    struct nlattr *              triggers;
-    int                          err;
-
-    if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
-        return TRUE;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WOWLAN, 0);
-
-    triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
-    if (!triggers)
-        goto nla_put_failure;
-
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY);
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT);
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT);
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE);
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST);
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE);
-    if (NM_FLAGS_HAS(wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE))
-        NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE);
-
-    nla_nest_end(msg, triggers);
-
-    err = nl80211_send_and_recv(self, msg, NULL, NULL);
-
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static guint32
-wifi_nl80211_get_freq(NMWifiUtils *data)
-{
-    NMWifiUtilsNl80211 *         self       = (NMWifiUtilsNl80211 *) data;
-    struct nl80211_iface_info    iface_info = {};
-    nm_auto_nlmsg struct nl_msg *msg        = NULL;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0);
-
-    if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0)
-        return 0;
-
-    return iface_info.freq;
-}
-
-static guint32
-wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs)
-{
-    NMWifiUtilsNl80211 *self = (NMWifiUtilsNl80211 *) data;
-    int                 i;
-
-    for (i = 0; i < self->num_freqs; i++) {
-        while (*freqs) {
-            if (self->freqs[i] == *freqs)
-                return *freqs;
-            freqs++;
-        }
-    }
-    return 0;
-}
-
-/* @divisor: pass what value @xbm should be divided by to get dBm */
-static guint32
-nl80211_xbm_to_percent(gint32 xbm, guint32 divisor)
-{
-#define NOISE_FLOOR_DBM -90
-#define SIGNAL_MAX_DBM  -20
-
-    xbm /= divisor;
-    xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM);
-
-    return 100
-           - 70
-                 * (((float) SIGNAL_MAX_DBM - (float) xbm)
-                    / ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM));
-}
-
-struct nl80211_station_info {
-    gboolean valid;
-    guint8   bssid[ETH_ALEN];
-    guint32  txrate;
-    gboolean txrate_valid;
-    guint8   signal;
-    gboolean signal_valid;
-};
-
-static int
-nl80211_station_dump_handler(struct nl_msg *msg, void *arg)
-{
-    static const struct nla_policy stats_policy[] = {
-        [NL80211_STA_INFO_INACTIVE_TIME]     = {.type = NLA_U32},
-        [NL80211_STA_INFO_RX_BYTES]          = {.type = NLA_U32},
-        [NL80211_STA_INFO_TX_BYTES]          = {.type = NLA_U32},
-        [NL80211_STA_INFO_RX_PACKETS]        = {.type = NLA_U32},
-        [NL80211_STA_INFO_TX_PACKETS]        = {.type = NLA_U32},
-        [NL80211_STA_INFO_SIGNAL]            = {.type = NLA_U8},
-        [NL80211_STA_INFO_TX_BITRATE]        = {.type = NLA_NESTED},
-        [NL80211_STA_INFO_LLID]              = {.type = NLA_U16},
-        [NL80211_STA_INFO_PLID]              = {.type = NLA_U16},
-        [NL80211_STA_INFO_PLINK_STATE]       = {.type = NLA_U8},
-        [NL80211_STA_INFO_STA_FLAGS]         = {.minlen = sizeof(struct nl80211_sta_flag_update)},
-        [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = {.type = NLA_U8},
-    };
-    static const struct nla_policy rate_policy[] = {
-        [NL80211_RATE_INFO_BITRATE]      = {.type = NLA_U16},
-        [NL80211_RATE_INFO_MCS]          = {.type = NLA_U8},
-        [NL80211_RATE_INFO_40_MHZ_WIDTH] = {.type = NLA_FLAG},
-        [NL80211_RATE_INFO_SHORT_GI]     = {.type = NLA_FLAG},
-    };
-    struct nlattr *              rinfo[G_N_ELEMENTS(rate_policy)];
-    struct nlattr *              sinfo[G_N_ELEMENTS(stats_policy)];
-    struct nl80211_station_info *info = arg;
-    struct nlattr *              tb[NL80211_ATTR_MAX + 1];
-    struct genlmsghdr *          gnlh = nlmsg_data(nlmsg_hdr(msg));
-
-    if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
-        return NL_SKIP;
-
-    if (tb[NL80211_ATTR_MAC] == NULL)
-        return NL_SKIP;
-
-    if (tb[NL80211_ATTR_STA_INFO] == NULL)
-        return NL_SKIP;
-
-    if (nla_parse_nested_arr(sinfo, tb[NL80211_ATTR_STA_INFO], stats_policy))
-        return NL_SKIP;
-
-    if (sinfo[NL80211_STA_INFO_STA_FLAGS] != NULL) {
-        const struct nl80211_sta_flag_update *flags = nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]);
-
-        if (flags->mask & ~flags->set & (1 << NL80211_STA_FLAG_ASSOCIATED))
-            return NL_SKIP;
-    }
-
-    memcpy(info->bssid, nla_data(tb[NL80211_ATTR_MAC]), ETH_ALEN);
-    info->valid = TRUE;
-
-    if (sinfo[NL80211_STA_INFO_TX_BITRATE] != NULL
-        && !nla_parse_nested_arr(rinfo, sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy)
-        && rinfo[NL80211_RATE_INFO_BITRATE] != NULL) {
-        /* convert from nl80211's units of 100kbps to NM's kbps */
-        info->txrate       = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100;
-        info->txrate_valid = TRUE;
-    }
-
-    if (sinfo[NL80211_STA_INFO_SIGNAL] != NULL) {
-        info->signal =
-            nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]), 1);
-        info->signal_valid = TRUE;
-    } else if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG] != NULL) {
-        /* Fall back to beacon signal strength */
-        info->signal =
-            nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]),
-                                   1);
-        info->signal_valid = TRUE;
-    }
-
-    return NL_SKIP;
-}
-
-static gboolean
-wifi_nl80211_get_station(NMWifiUtils *data,
-                         NMEtherAddr *out_bssid,
-                         int *        out_quality,
-                         guint32 *    out_rate)
-{
-    NMWifiUtilsNl80211 *         self     = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg      = NULL;
-    struct nl80211_station_info  sta_info = {};
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_STATION, NLM_F_DUMP);
-
-    nl80211_send_and_recv(self, msg, nl80211_station_dump_handler, &sta_info);
-
-    if (!sta_info.valid || (out_quality && !sta_info.signal_valid)
-        || (out_rate && !sta_info.txrate_valid))
-        return FALSE;
-
-    if (out_bssid)
-        memcpy(out_bssid, sta_info.bssid, ETH_ALEN);
-
-    if (out_quality)
-        *out_quality = sta_info.signal;
-
-    if (out_rate)
-        *out_rate = sta_info.txrate;
-
-    return TRUE;
-}
-
-static gboolean
-wifi_nl80211_indicate_addressing_running(NMWifiUtils *data, gboolean running)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-    int                          err;
-
-    msg = nl80211_alloc_msg(self,
-                            running ? 98 /* NL80211_CMD_CRIT_PROTOCOL_START */
-                                    : 99 /* NL80211_CMD_CRIT_PROTOCOL_STOP */,
-                            0);
-    /* Despite the DHCP name, we're using this for any type of IP addressing,
-     * DHCPv4, DHCPv6, and IPv6 SLAAC.
-     */
-    NLA_PUT_U16(msg, 179 /* NL80211_ATTR_CRIT_PROT_ID */, 1 /* NL80211_CRIT_PROTO_DHCP */);
-    if (running) {
-        /* Give DHCP 5 seconds to complete */
-        NLA_PUT_U16(msg, 180 /* NL80211_ATTR_MAX_CRIT_PROT_DURATION */, 5000);
-    }
-
-    err = nl80211_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-struct nl80211_device_info {
-    NMWifiUtilsNl80211 *self;
-    int                 phy;
-    guint32 *           freqs;
-    int                 num_freqs;
-    guint32             freq;
-    guint32             caps;
-    gboolean            can_scan;
-    gboolean            can_scan_ssid;
-    gboolean            supported;
-    gboolean            success;
-    gboolean            can_wowlan;
-};
-
-#define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00
-#define WLAN_CIPHER_SUITE_WEP40     0x000FAC01
-#define WLAN_CIPHER_SUITE_TKIP      0x000FAC02
-#define WLAN_CIPHER_SUITE_CCMP      0x000FAC04
-#define WLAN_CIPHER_SUITE_WEP104    0x000FAC05
-#define WLAN_CIPHER_SUITE_AES_CMAC  0x000FAC06
-#define WLAN_CIPHER_SUITE_GCMP      0x000FAC08
-#define WLAN_CIPHER_SUITE_SMS4      0x00147201
-
-static int
-nl80211_wiphy_info_handler(struct nl_msg *msg, void *arg)
-{
-    static const struct nla_policy freq_policy[] = {
-        [NL80211_FREQUENCY_ATTR_FREQ]     = {.type = NLA_U32},
-        [NL80211_FREQUENCY_ATTR_DISABLED] = {.type = NLA_FLAG},
-#ifdef NL80211_FREQUENCY_ATTR_NO_IR
-        [NL80211_FREQUENCY_ATTR_NO_IR] = {.type = NLA_FLAG},
-#else
-        [NL80211_FREQUENCY_ATTR_PASSIVE_SCAN] = {.type = NLA_FLAG},
-        [NL80211_FREQUENCY_ATTR_NO_IBSS]      = {.type = NLA_FLAG},
-#endif
-        [NL80211_FREQUENCY_ATTR_RADAR]        = {.type = NLA_FLAG},
-        [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = {.type = NLA_U32},
-    };
-    struct nlattr *             tb[NL80211_ATTR_MAX + 1];
-    struct genlmsghdr *         gnlh = nlmsg_data(nlmsg_hdr(msg));
-    struct nl80211_device_info *info = arg;
-    NMWifiUtilsNl80211 *        self = info->self;
-    struct nlattr *             tb_band[NL80211_BAND_ATTR_MAX + 1];
-    struct nlattr *             tb_freq[G_N_ELEMENTS(freq_policy)];
-    struct nlattr *             nl_band;
-    struct nlattr *             nl_freq;
-    int                         rem_freq;
-    int                         rem_band;
-    int                         freq_idx;
-
-#ifdef NL80211_FREQUENCY_ATTR_NO_IR
-    G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR
-                         && NL80211_FREQUENCY_ATTR_NO_IBSS == NL80211_FREQUENCY_ATTR_NO_IR);
-#else
-    G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN != NL80211_FREQUENCY_ATTR_NO_IBSS);
-#endif
-
-    if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
-        return NL_SKIP;
-
-    if (tb[NL80211_ATTR_WIPHY] == NULL || tb[NL80211_ATTR_WIPHY_BANDS] == NULL)
-        return NL_SKIP;
-
-    info->phy = nla_get_u32(tb[NL80211_ATTR_WIPHY]);
-
-    if (tb[NL80211_ATTR_WIPHY_FREQ])
-        info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
-    else
-        info->freq = 0;
-
-    if (tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) {
-        info->can_scan_ssid = nla_get_u8(tb[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) > 0;
-    } else {
-        /* old kernel that only had mac80211, so assume it can */
-        info->can_scan_ssid = TRUE;
-    }
-
-    if (tb[NL80211_ATTR_SUPPORTED_COMMANDS]) {
-        struct nlattr *nl_cmd;
-        int            i;
-
-        nla_for_each_nested (nl_cmd, tb[NL80211_ATTR_SUPPORTED_COMMANDS], i) {
-            switch (nla_get_u32(nl_cmd)) {
-            case NL80211_CMD_TRIGGER_SCAN:
-                info->can_scan = TRUE;
-                break;
-            case NL80211_CMD_CONNECT:
-            case NL80211_CMD_AUTHENTICATE:
-                /* Only devices that support CONNECT or AUTH actually support
-                 * 802.11, unlike say ipw2x00 (up to at least kernel 3.4) which
-                 * has minimal info support, but no actual command support.
-                 * This check mirrors what wpa_supplicant does to determine
-                 * whether or not to use the nl80211 driver.
-                 */
-                info->supported = TRUE;
-                break;
-            default:
-                break;
-            }
-        }
-    }
-
-    /* Find number of supported frequencies */
-    info->num_freqs = 0;
-
-    nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-        if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0)
-            return NL_SKIP;
-
-        nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) {
-            if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0)
-                continue;
-
-            if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
-                continue;
-
-            info->num_freqs++;
-        }
-    }
-
-    /* Read supported frequencies */
-    info->freqs = g_malloc0(sizeof(guint32) * info->num_freqs);
-
-    freq_idx = 0;
-    nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
-        if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0)
-            return NL_SKIP;
-
-        nla_for_each_nested (nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) {
-            if (nla_parse_nested_arr(tb_freq, nl_freq, freq_policy) < 0)
-                continue;
-
-            if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ])
-                continue;
-
-            info->freqs[freq_idx] = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
-
-            info->caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID;
-
-            if (info->freqs[freq_idx] > 2400 && info->freqs[freq_idx] < 2500)
-                info->caps |= NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
-            if (info->freqs[freq_idx] > 4900 && info->freqs[freq_idx] < 6000)
-                info->caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
-
-            freq_idx++;
-        }
-    }
-
-    /* Read security/encryption support */
-    if (tb[NL80211_ATTR_CIPHER_SUITES]) {
-        guint32 *ciphers = nla_data(tb[NL80211_ATTR_CIPHER_SUITES]);
-        guint    i, num;
-
-        num = nla_len(tb[NL80211_ATTR_CIPHER_SUITES]) / sizeof(guint32);
-        for (i = 0; i < num; i++) {
-            switch (ciphers[i]) {
-            case WLAN_CIPHER_SUITE_WEP40:
-                info->caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP40;
-                break;
-            case WLAN_CIPHER_SUITE_WEP104:
-                info->caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP104;
-                break;
-            case WLAN_CIPHER_SUITE_TKIP:
-                info->caps |= (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_WPA);
-                break;
-            case WLAN_CIPHER_SUITE_CCMP:
-                info->caps |= (NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN);
-                break;
-            case WLAN_CIPHER_SUITE_AES_CMAC:
-            case WLAN_CIPHER_SUITE_GCMP:
-            case WLAN_CIPHER_SUITE_SMS4:
-                break;
-            default:
-                _LOGD("don't know the meaning of NL80211_ATTR_CIPHER_SUITE %#8.8x.", ciphers[i]);
-                break;
-            }
-        }
-    }
-
-    if (tb[NL80211_ATTR_SUPPORTED_IFTYPES]) {
-        struct nlattr *nl_mode;
-        int            i;
-
-        nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) {
-            switch (nla_type(nl_mode)) {
-            case NL80211_IFTYPE_AP:
-                info->caps |= NM_WIFI_DEVICE_CAP_AP;
-                break;
-            case NL80211_IFTYPE_ADHOC:
-                info->caps |= NM_WIFI_DEVICE_CAP_ADHOC;
-                break;
-            case NL80211_IFTYPE_MESH_POINT:
-                info->caps |= NM_WIFI_DEVICE_CAP_MESH;
-                break;
-            }
-        }
-    }
-
-    if (tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED])
-        info->can_wowlan = TRUE;
-
-    if (tb[NL80211_ATTR_SUPPORT_IBSS_RSN])
-        info->caps |= NM_WIFI_DEVICE_CAP_IBSS_RSN;
-
-    info->success = TRUE;
-
-    return NL_SKIP;
-}
-
-static guint32
-wifi_nl80211_get_mesh_channel(NMWifiUtils *data)
-{
-    NMWifiUtilsNl80211 *         self        = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg         = NULL;
-    struct nl80211_device_info   device_info = {.self = self};
-    int                          i;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0);
-
-    if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) {
-        _LOGW("NL80211_CMD_GET_WIPHY request failed");
-        return 0;
-    }
-
-    for (i = 0; i < self->num_freqs; i++) {
-        if (device_info.freq == self->freqs[i])
-            return i + 1;
-    }
-    return 0;
-}
-
-static gboolean
-wifi_nl80211_set_mesh_channel(NMWifiUtils *data, guint32 channel)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-    int                          err;
-
-    if (channel > self->num_freqs)
-        return FALSE;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_SET_WIPHY, 0);
-    NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, self->freqs[channel - 1]);
-    err = nl80211_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static gboolean
-wifi_nl80211_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len)
-{
-    NMWifiUtilsNl80211 *         self = (NMWifiUtilsNl80211 *) data;
-    nm_auto_nlmsg struct nl_msg *msg  = NULL;
-    int                          err;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_SET_INTERFACE, 0);
-    NLA_PUT(msg, NL80211_ATTR_MESH_ID, len, ssid);
-    err = nl80211_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-static void
-nm_wifi_utils_nl80211_init(NMWifiUtilsNl80211 *self)
-{}
-
-static void
-nm_wifi_utils_nl80211_class_init(NMWifiUtilsNl80211Class *klass)
-{
-    GObjectClass *    object_class     = G_OBJECT_CLASS(klass);
-    NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass);
-
-    object_class->dispose = dispose;
-
-    wifi_utils_class->get_mode                    = wifi_nl80211_get_mode;
-    wifi_utils_class->set_mode                    = wifi_nl80211_set_mode;
-    wifi_utils_class->set_powersave               = wifi_nl80211_set_powersave;
-    wifi_utils_class->get_wake_on_wlan            = wifi_nl80211_get_wake_on_wlan,
-    wifi_utils_class->set_wake_on_wlan            = wifi_nl80211_set_wake_on_wlan,
-    wifi_utils_class->get_freq                    = wifi_nl80211_get_freq;
-    wifi_utils_class->find_freq                   = wifi_nl80211_find_freq;
-    wifi_utils_class->get_station                 = wifi_nl80211_get_station;
-    wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running;
-    wifi_utils_class->get_mesh_channel            = wifi_nl80211_get_mesh_channel;
-    wifi_utils_class->set_mesh_channel            = wifi_nl80211_set_mesh_channel;
-    wifi_utils_class->set_mesh_ssid               = wifi_nl80211_set_mesh_ssid;
-}
-
-NMWifiUtils *
-nm_wifi_utils_nl80211_new(int ifindex, struct nl_sock *genl)
-{
-    gs_unref_object NMWifiUtilsNl80211 *self        = NULL;
-    nm_auto_nlmsg struct nl_msg *       msg         = NULL;
-    struct nl80211_device_info          device_info = {};
-
-    if (!genl)
-        return NULL;
-
-    self = g_object_new(NM_TYPE_WIFI_UTILS_NL80211, NULL);
-
-    self->parent.ifindex = ifindex;
-    self->nl_sock        = genl;
-
-    self->id = genl_ctrl_resolve(self->nl_sock, "nl80211");
-    if (self->id < 0) {
-        _LOGD("genl_ctrl_resolve: failed to resolve \"nl80211\"");
-        return NULL;
-    }
-
-    self->phy = -1;
-
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_WIPHY, 0);
-
-    device_info.self = self;
-    if (nl80211_send_and_recv(self, msg, nl80211_wiphy_info_handler, &device_info) < 0) {
-        _LOGD("NL80211_CMD_GET_WIPHY request failed");
-        return NULL;
-    }
-
-    if (!device_info.success) {
-        _LOGD("NL80211_CMD_GET_WIPHY request indicated failure");
-        return NULL;
-    }
-
-    if (!device_info.supported) {
-        _LOGD("driver does not fully support nl80211, falling back to WEXT");
-        return NULL;
-    }
-
-    if (!device_info.can_scan_ssid) {
-        _LOGE("driver does not support SSID scans");
-        return NULL;
-    }
-
-    if (device_info.num_freqs == 0 || device_info.freqs == NULL) {
-        _LOGE("driver reports no supported frequencies");
-        return NULL;
-    }
-
-    if (device_info.caps == 0) {
-        _LOGE("driver doesn't report support of any encryption");
-        return NULL;
-    }
-
-    self->phy         = device_info.phy;
-    self->freqs       = device_info.freqs;
-    self->num_freqs   = device_info.num_freqs;
-    self->parent.caps = device_info.caps;
-    self->can_wowlan  = device_info.can_wowlan;
-
-    _LOGD("using nl80211 for Wi-Fi device control");
-    return (NMWifiUtils *) g_steal_pointer(&self);
-}
diff --git a/src/core/platform/wifi/nm-wifi-utils-nl80211.h b/src/core/platform/wifi/nm-wifi-utils-nl80211.h
deleted file mode 100644
index 37c32ed7..00000000
--- a/src/core/platform/wifi/nm-wifi-utils-nl80211.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2011 Intel Corporation. All rights reserved.
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#ifndef __WIFI_UTILS_NL80211_H__
-#define __WIFI_UTILS_NL80211_H__
-
-#include "nm-wifi-utils.h"
-#include "nm-platform/nm-netlink.h"
-
-#define NM_TYPE_WIFI_UTILS_NL80211 (nm_wifi_utils_nl80211_get_type())
-#define NM_WIFI_UTILS_NL80211(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211))
-#define NM_WIFI_UTILS_NL80211_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class))
-#define NM_IS_WIFI_UTILS_NL80211(obj) \
-    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS_NL80211))
-#define NM_IS_WIFI_UTILS_NL80211_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS_NL80211))
-#define NM_WIFI_UTILS_NL80211_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class))
-
-GType nm_wifi_utils_nl80211_get_type(void);
-
-NMWifiUtils *nm_wifi_utils_nl80211_new(int ifindex, struct nl_sock *genl);
-
-#endif /* __WIFI_UTILS_NL80211_H__ */
diff --git a/src/core/platform/wifi/nm-wifi-utils-private.h b/src/core/platform/wifi/nm-wifi-utils-private.h
deleted file mode 100644
index 9ed57453..00000000
--- a/src/core/platform/wifi/nm-wifi-utils-private.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2011 - 2018 Red Hat, Inc.
- */
-
-#ifndef __WIFI_UTILS_PRIVATE_H__
-#define __WIFI_UTILS_PRIVATE_H__
-
-#include "nm-dbus-interface.h"
-#include "nm-wifi-utils.h"
-
-typedef struct {
-    GObjectClass parent;
-
-    NM80211Mode (*get_mode)(NMWifiUtils *data);
-
-    gboolean (*set_mode)(NMWifiUtils *data, const NM80211Mode mode);
-
-    /* Set power saving mode on an interface */
-    gboolean (*set_powersave)(NMWifiUtils *data, guint32 powersave);
-
-    /* Get WakeOnWLAN configuration on an interface */
-    NMSettingWirelessWakeOnWLan (*get_wake_on_wlan)(NMWifiUtils *data);
-
-    /* Set WakeOnWLAN mode on an interface */
-    gboolean (*set_wake_on_wlan)(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl);
-
-    /* Return current frequency in MHz (really associated BSS frequency) */
-    guint32 (*get_freq)(NMWifiUtils *data);
-
-    /* Return first supported frequency in the zero-terminated list */
-    guint32 (*find_freq)(NMWifiUtils *data, const guint32 *freqs);
-
-    /*
-     * @out_bssid: must be NULL or an ETH_ALEN-byte buffer
-     * @out_quality: receives signal strength percentage 0 - 100% for the current BSSID, if not NULL
-     * @out_rate: receives current bitrate in Kbps if not NULL
-     *
-     * Returns %TRUE on succcess, %FALSE on errors or if not associated.
-     */
-    gboolean (*get_station)(NMWifiUtils *data,
-                            NMEtherAddr *out_bssid,
-                            int *        out_quality,
-                            guint32 *    out_rate);
-
-    /* OLPC Mesh-only functions */
-
-    guint32 (*get_mesh_channel)(NMWifiUtils *data);
-
-    /* channel == 0 means "auto channel" */
-    gboolean (*set_mesh_channel)(NMWifiUtils *data, guint32 channel);
-
-    /* ssid == NULL means "auto SSID" */
-    gboolean (*set_mesh_ssid)(NMWifiUtils *data, const guint8 *ssid, gsize len);
-
-    gboolean (*indicate_addressing_running)(NMWifiUtils *data, gboolean running);
-} NMWifiUtilsClass;
-
-struct NMWifiUtils {
-    GObject parent;
-
-    int                      ifindex;
-    NMDeviceWifiCapabilities caps;
-};
-
-#endif /* __WIFI_UTILS_PRIVATE_H__ */
diff --git a/src/core/platform/wifi/nm-wifi-utils-wext.c b/src/core/platform/wifi/nm-wifi-utils-wext.c
deleted file mode 100644
index a44fcf62..00000000
--- a/src/core/platform/wifi/nm-wifi-utils-wext.c
+++ /dev/null
@@ -1,836 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2005 - 2018 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-wifi-utils-wext.h"
-
-#include <sys/ioctl.h>
-#include <net/ethernet.h>
-#include <unistd.h>
-
-/* Hacks necessary to #include wireless.h; yay for WEXT */
-#ifndef __user
-    #define __user
-#endif
-#include <sys/types.h>
-#include <linux/types.h>
-#include <sys/socket.h>
-#include <linux/wireless.h>
-
-#include "nm-wifi-utils-private.h"
-#include "nm-utils.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "nm-core-internal.h"
-#include "nm-core-utils.h"
-
-typedef struct {
-    NMWifiUtils       parent;
-    int               fd;
-    struct iw_quality max_qual;
-    gint8             num_freqs;
-    guint32           freqs[IW_MAX_FREQUENCIES];
-} NMWifiUtilsWext;
-
-typedef struct {
-    NMWifiUtilsClass parent;
-} NMWifiUtilsWextClass;
-
-G_DEFINE_TYPE(NMWifiUtilsWext, nm_wifi_utils_wext, NM_TYPE_WIFI_UTILS)
-
-/* Until a new wireless-tools comes out that has the defs and the structure,
- * need to copy them here.
- */
-/* Scan capability flags - in (struct iw_range *)->scan_capa */
-#define NM_IW_SCAN_CAPA_NONE  0x00
-#define NM_IW_SCAN_CAPA_ESSID 0x01
-
-struct iw_range_with_scan_capa {
-    guint32 throughput;
-    guint32 min_nwid;
-    guint32 max_nwid;
-    guint16 old_num_channels;
-    guint8  old_num_frequency;
-
-    guint8 scan_capa;
-    /* don't need the rest... */
-};
-
-#define _NMLOG_PREFIX_NAME "wifi-wext"
-#define _NMLOG(level, domain, ...)                                    \
-    G_STMT_START                                                      \
-    {                                                                 \
-        nm_log((level),                                               \
-               (domain),                                              \
-               NULL,                                                  \
-               NULL,                                                  \
-               "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),             \
-               _NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-    }                                                                 \
-    G_STMT_END
-
-static guint32
-iw_freq_to_uint32(const struct iw_freq *freq)
-{
-    if (freq->e == 0) {
-        /* Some drivers report channel not frequency.  Convert to a
-         * frequency; but this assumes that the device is in b/g mode.
-         */
-        if ((freq->m >= 1) && (freq->m <= 13))
-            return 2407 + (5 * freq->m);
-        else if (freq->m == 14)
-            return 2484;
-    }
-    return (guint32)((((double) freq->m) * nm_utils_exp10(freq->e)) / 1000000.0);
-}
-
-static void
-dispose(GObject *object)
-{
-    NMWifiUtilsWext *wext = NM_WIFI_UTILS_WEXT(object);
-
-    wext->fd = nm_close(wext->fd);
-}
-
-static gboolean
-get_ifname(int ifindex, char *buffer, const char *op)
-{
-    int errsv;
-
-    if (!nmp_utils_if_indextoname(ifindex, buffer)) {
-        errsv = errno;
-        _LOGW(LOGD_PLATFORM | LOGD_WIFI,
-              "error getting interface name for ifindex %d, operation '%s': %s (%d)",
-              ifindex,
-              op,
-              nm_strerror_native(errsv),
-              errsv);
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static NM80211Mode
-wifi_wext_get_mode_ifname(NMWifiUtils *data, const char *ifname)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    int              errsv;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-
-    if (ioctl(wext->fd, SIOCGIWMODE, &wrq) < 0) {
-        errsv = errno;
-        if (errsv != ENODEV) {
-            _LOGW(LOGD_PLATFORM | LOGD_WIFI, "(%s): error %d getting card mode", ifname, errsv);
-        }
-        return NM_802_11_MODE_UNKNOWN;
-    }
-
-    switch (wrq.u.mode) {
-    case IW_MODE_ADHOC:
-        return NM_802_11_MODE_ADHOC;
-    case IW_MODE_MASTER:
-        return NM_802_11_MODE_AP;
-    case IW_MODE_INFRA:
-    case IW_MODE_AUTO: /* hack for WEXT devices reporting IW_MODE_AUTO */
-        return NM_802_11_MODE_INFRA;
-    default:
-        break;
-    }
-    return NM_802_11_MODE_UNKNOWN;
-}
-
-static NM80211Mode
-wifi_wext_get_mode(NMWifiUtils *data)
-{
-    char ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "get-mode"))
-        return FALSE;
-
-    return wifi_wext_get_mode_ifname(data, ifname);
-}
-
-static gboolean
-wifi_wext_set_mode(NMWifiUtils *data, const NM80211Mode mode)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    char             ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "set-mode"))
-        return FALSE;
-
-    if (wifi_wext_get_mode_ifname(data, ifname) == mode)
-        return TRUE;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    switch (mode) {
-    case NM_802_11_MODE_ADHOC:
-        wrq.u.mode = IW_MODE_ADHOC;
-        break;
-    case NM_802_11_MODE_AP:
-        wrq.u.mode = IW_MODE_MASTER;
-        break;
-    case NM_802_11_MODE_INFRA:
-        wrq.u.mode = IW_MODE_INFRA;
-        break;
-    default:
-        g_warn_if_reached();
-        return FALSE;
-    }
-
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    if (ioctl(wext->fd, SIOCSIWMODE, &wrq) < 0) {
-        if (errno != ENODEV) {
-            _LOGE(LOGD_PLATFORM | LOGD_WIFI, "(%s): error setting mode %d", ifname, mode);
-        }
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static gboolean
-wifi_wext_set_powersave(NMWifiUtils *data, guint32 powersave)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    char             ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "set-powersave"))
-        return FALSE;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    if (powersave == 1) {
-        wrq.u.power.flags = IW_POWER_ALL_R;
-    } else
-        wrq.u.power.disabled = 1;
-
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    if (ioctl(wext->fd, SIOCSIWPOWER, &wrq) < 0) {
-        if (errno != ENODEV) {
-            _LOGE(LOGD_PLATFORM | LOGD_WIFI,
-                  "(%s): error setting powersave %" G_GUINT32_FORMAT,
-                  ifname,
-                  powersave);
-        }
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static guint32
-wifi_wext_get_freq(NMWifiUtils *data)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    char             ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "get-freq"))
-        return FALSE;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    if (ioctl(wext->fd, SIOCGIWFREQ, &wrq) < 0) {
-        _LOGW(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): error getting frequency: %s",
-              ifname,
-              nm_strerror_native(errno));
-        return 0;
-    }
-
-    return iw_freq_to_uint32(&wrq.u.freq);
-}
-
-static guint32
-wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    int              i;
-
-    for (i = 0; i < wext->num_freqs; i++) {
-        while (*freqs) {
-            if (wext->freqs[i] == *freqs)
-                return *freqs;
-            freqs++;
-        }
-    }
-    return 0;
-}
-
-static gboolean
-wifi_wext_get_bssid(NMWifiUtils *data, NMEtherAddr *out_bssid)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    char             ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "get-bssid"))
-        return FALSE;
-
-    memset(&wrq, 0, sizeof(wrq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    if (ioctl(wext->fd, SIOCGIWAP, &wrq) < 0) {
-        _LOGW(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): error getting associated BSSID: %s",
-              ifname,
-              nm_strerror_native(errno));
-        return FALSE;
-    }
-    memcpy(out_bssid, &(wrq.u.ap_addr.sa_data), ETH_ALEN);
-    return TRUE;
-}
-
-static guint32
-wifi_wext_get_rate(NMWifiUtils *data)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    int              err;
-    char             ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "get-rate"))
-        return FALSE;
-
-    memset(&wrq, 0, sizeof(wrq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    err = ioctl(wext->fd, SIOCGIWRATE, &wrq);
-    return ((err == 0) ? wrq.u.bitrate.value / 1000 : 0);
-}
-
-static int
-wext_qual_to_percent(const struct iw_quality *qual, const struct iw_quality *max_qual)
-{
-    int percent       = -1;
-    int level_percent = -1;
-
-    g_return_val_if_fail(qual != NULL, -1);
-    g_return_val_if_fail(max_qual != NULL, -1);
-
-    /* Magically convert the many different WEXT quality representations to a percentage */
-
-    _LOGD(LOGD_WIFI,
-          "QL: qual %d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X  ** MAX: qual "
-          "%d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X",
-          (__s8) qual->qual,
-          qual->qual,
-          qual->qual,
-          (__s8) qual->level,
-          qual->level,
-          qual->level,
-          (__s8) qual->noise,
-          qual->noise,
-          qual->noise,
-          qual->updated,
-          (__s8) max_qual->qual,
-          max_qual->qual,
-          max_qual->qual,
-          (__s8) max_qual->level,
-          max_qual->level,
-          max_qual->level,
-          (__s8) max_qual->noise,
-          max_qual->noise,
-          max_qual->noise,
-          max_qual->updated);
-
-    /* Try using the card's idea of the signal quality first as long as it tells us what the max quality is.
-     * Drivers that fill in quality values MUST treat them as percentages, ie the "Link Quality" MUST be
-     * bounded by 0 and max_qual->qual, and MUST change in a linear fashion.  Within those bounds, drivers
-     * are free to use whatever they want to calculate "Link Quality".
-     */
-    if ((max_qual->qual != 0) && !(max_qual->updated & IW_QUAL_QUAL_INVALID)
-        && !(qual->updated & IW_QUAL_QUAL_INVALID))
-        percent = (int) (100 * ((double) qual->qual / (double) max_qual->qual));
-
-    /* If the driver doesn't specify a complete and valid quality, we have two options:
-     *
-     * 1) dBm: driver must specify max_qual->level = 0, and have valid values for
-     *        qual->level and (qual->noise OR max_qual->noise)
-     * 2) raw RSSI: driver must specify max_qual->level > 0, and have valid values for
-     *        qual->level and max_qual->level
-     *
-     * This is the WEXT spec.  If this interpretation is wrong, I'll fix it.  Otherwise,
-     * If drivers don't conform to it, they are wrong and need to be fixed.
-     */
-
-    if ((max_qual->level == 0)
-        && !(max_qual->updated & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level == 0 */
-        && !(qual->updated & IW_QUAL_LEVEL_INVALID)     /* Must have valid qual->level */
-        && (((max_qual->noise > 0)
-             && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) /* Must have valid max_qual->noise */
-            || ((qual->noise > 0)
-                && !(qual->updated & IW_QUAL_NOISE_INVALID))) /*    OR valid qual->noise */
-    ) {
-/* Absolute power values (dBm) */
-
-/* Reasonable fallbacks for dumb drivers that don't specify either level. */
-#define FALLBACK_NOISE_FLOOR_DBM -90
-#define FALLBACK_SIGNAL_MAX_DBM  -20
-        int max_level = FALLBACK_SIGNAL_MAX_DBM;
-        int noise     = FALLBACK_NOISE_FLOOR_DBM;
-        int level     = qual->level - 0x100;
-
-        level = CLAMP(level, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM);
-
-        if ((qual->noise > 0) && !(qual->updated & IW_QUAL_NOISE_INVALID))
-            noise = qual->noise - 0x100;
-        else if ((max_qual->noise > 0) && !(max_qual->updated & IW_QUAL_NOISE_INVALID))
-            noise = max_qual->noise - 0x100;
-        noise = CLAMP(noise, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM - 1);
-
-        /* A sort of signal-to-noise ratio calculation */
-        level_percent = (int) (100
-                               - 70
-                                     * (((double) max_level - (double) level)
-                                        / ((double) max_level - (double) noise)));
-        _LOGD(LOGD_WIFI,
-              "QL1: level_percent is %d.  max_level %d, level %d, noise_floor %d.",
-              level_percent,
-              max_level,
-              level,
-              noise);
-    } else if ((max_qual->level != 0)
-               && !(max_qual->updated
-                    & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level as upper bound */
-               && !(qual->updated & IW_QUAL_LEVEL_INVALID)) {
-        /* Relative power values (RSSI) */
-
-        int level = qual->level;
-
-        /* Signal level is relavtive (0 -> max_qual->level) */
-        level         = CLAMP(level, 0, max_qual->level);
-        level_percent = (int) (100 * ((double) level / (double) max_qual->level));
-        _LOGD(LOGD_WIFI,
-              "QL2: level_percent is %d.  max_level %d, level %d.",
-              level_percent,
-              max_qual->level,
-              level);
-    } else if (percent == -1) {
-        _LOGD(LOGD_WIFI,
-              "QL: Could not get quality %% value from driver.  Driver is probably buggy.");
-    }
-
-    /* If the quality percent was 0 or doesn't exist, then try to use signal levels instead */
-    if ((percent < 1) && (level_percent >= 0))
-        percent = level_percent;
-
-    _LOGD(LOGD_WIFI, "QL: Final quality percent is %d (%d).", percent, CLAMP(percent, 0, 100));
-    return (CLAMP(percent, 0, 100));
-}
-
-static int
-wifi_wext_get_qual(NMWifiUtils *data)
-{
-    NMWifiUtilsWext *    wext = (NMWifiUtilsWext *) data;
-    struct iwreq         wrq;
-    struct iw_statistics stats;
-    char                 ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "get-qual"))
-        return FALSE;
-
-    memset(&stats, 0, sizeof(stats));
-    wrq.u.data.pointer = &stats;
-    wrq.u.data.length  = sizeof(stats);
-    wrq.u.data.flags   = 1; /* Clear updated flag */
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-
-    if (ioctl(wext->fd, SIOCGIWSTATS, &wrq) < 0) {
-        _LOGW(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): error getting signal strength: %s",
-              ifname,
-              nm_strerror_native(errno));
-        return -1;
-    }
-
-    return wext_qual_to_percent(&stats.qual, &wext->max_qual);
-}
-
-static gboolean
-wifi_wext_get_station(NMWifiUtils *data,
-                      NMEtherAddr *out_bssid,
-                      int *        out_quality,
-                      guint32 *    out_rate)
-{
-    NMEtherAddr local_addr;
-
-    if (!out_bssid && !out_quality && !out_rate) {
-        /* hm, the caller requested no parameter at all?
-         * Don't simply return TRUE, but at least check that
-         * we can successfully fetch the bssid. */
-        out_bssid = &local_addr;
-    }
-
-    if (out_bssid) {
-        if (!wifi_wext_get_bssid(data, out_bssid))
-            return FALSE;
-    }
-    if (out_quality) {
-        *out_quality = wifi_wext_get_qual(data);
-        if (*out_quality < 0)
-            return FALSE;
-    }
-    if (out_rate) {
-        *out_rate = wifi_wext_get_rate(data);
-        if (*out_rate == 0)
-            return FALSE;
-    }
-    return TRUE;
-}
-
-/*****************************************************************************/
-/* OLPC Mesh-only functions */
-
-static guint32
-wifi_wext_get_mesh_channel(NMWifiUtils *data)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    guint32          freq;
-    int              i;
-
-    freq = nm_wifi_utils_get_freq(data);
-    for (i = 0; i < wext->num_freqs; i++) {
-        if (freq == wext->freqs[i])
-            return i + 1;
-    }
-    return 0;
-}
-
-static gboolean
-wifi_wext_set_mesh_channel(NMWifiUtils *data, guint32 channel)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    char             ifname[IFNAMSIZ];
-
-    if (!get_ifname(data->ifindex, ifname, "set-mesh-channel"))
-        return FALSE;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-
-    if (channel > 0) {
-        wrq.u.freq.flags = IW_FREQ_FIXED;
-        wrq.u.freq.e     = 0;
-        wrq.u.freq.m     = channel;
-    }
-
-    if (ioctl(wext->fd, SIOCSIWFREQ, &wrq) < 0) {
-        _LOGE(LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
-              "(%s): error setting channel to %d: %s",
-              ifname,
-              channel,
-              nm_strerror_native(errno));
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static gboolean
-wifi_wext_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len)
-{
-    NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
-    struct iwreq     wrq;
-    char             buf[IW_ESSID_MAX_SIZE + 1];
-    char             ifname[IFNAMSIZ];
-    int              errsv;
-
-    if (!get_ifname(data->ifindex, ifname, "set-mesh-ssid"))
-        return FALSE;
-
-    memset(buf, 0, sizeof(buf));
-    memcpy(buf, ssid, MIN(sizeof(buf) - 1, len));
-
-    wrq.u.essid.pointer = (caddr_t) buf;
-    wrq.u.essid.length  = len;
-    wrq.u.essid.flags   = (len > 0) ? 1 : 0; /* 1=enable SSID, 0=disable/any */
-
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    if (ioctl(wext->fd, SIOCSIWESSID, &wrq) == 0)
-        return TRUE;
-
-    errsv = errno;
-    if (errsv != ENODEV) {
-        gs_free char *ssid_str = NULL;
-
-        _LOGE(LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
-              "(%s): error setting SSID to %s: %s",
-              ifname,
-              (ssid_str = _nm_utils_ssid_to_string_arr(ssid, len)),
-              nm_strerror_native(errsv));
-    }
-
-    return FALSE;
-}
-
-/*****************************************************************************/
-
-static gboolean
-wext_can_scan_ifname(NMWifiUtilsWext *wext, const char *ifname)
-{
-    struct iwreq wrq;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    if (ioctl(wext->fd, SIOCSIWSCAN, &wrq) < 0) {
-        if (errno == EOPNOTSUPP)
-            return FALSE;
-    }
-    return TRUE;
-}
-
-static gboolean
-wext_get_range_ifname(NMWifiUtilsWext *wext,
-                      const char *     ifname,
-                      struct iw_range *range,
-                      guint32 *        response_len)
-{
-    int          i       = 26;
-    gboolean     success = FALSE;
-    struct iwreq wrq;
-    int          errsv;
-
-    memset(&wrq, 0, sizeof(struct iwreq));
-    nm_utils_ifname_cpy(wrq.ifr_name, ifname);
-    wrq.u.data.pointer = (caddr_t) range;
-    wrq.u.data.length  = sizeof(struct iw_range);
-
-    /* Need to give some drivers time to recover after suspend/resume
-     * (ex ipw3945 takes a few seconds to talk to its regulatory daemon;
-     * see rh bz#362421)
-     */
-    while (i-- > 0) {
-        if (ioctl(wext->fd, SIOCGIWRANGE, &wrq) == 0) {
-            if (response_len)
-                *response_len = wrq.u.data.length;
-            success = TRUE;
-            break;
-        } else {
-            errsv = errno;
-            if (errsv != EAGAIN) {
-                _LOGE(LOGD_PLATFORM | LOGD_WIFI,
-                      "(%s): couldn't get driver range information (%d).",
-                      ifname,
-                      errsv);
-                break;
-            }
-        }
-
-        g_usleep(G_USEC_PER_SEC / 4);
-    }
-
-    if (i <= 0) {
-        _LOGW(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): driver took too long to respond to IWRANGE query.",
-              ifname);
-    }
-
-    return success;
-}
-
-#define WPA_CAPS                                                                              \
-    (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_WPA \
-     | NM_WIFI_DEVICE_CAP_RSN)
-
-static guint32
-wext_get_caps(NMWifiUtilsWext *wext, const char *ifname, struct iw_range *range)
-{
-    guint32 caps = NM_WIFI_DEVICE_CAP_NONE;
-
-    g_return_val_if_fail(wext != NULL, NM_WIFI_DEVICE_CAP_NONE);
-    g_return_val_if_fail(range != NULL, NM_WIFI_DEVICE_CAP_NONE);
-
-    /* All drivers should support WEP by default */
-    caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104;
-
-    if (range->enc_capa & IW_ENC_CAPA_CIPHER_TKIP)
-        caps |= NM_WIFI_DEVICE_CAP_CIPHER_TKIP;
-
-    if (range->enc_capa & IW_ENC_CAPA_CIPHER_CCMP)
-        caps |= NM_WIFI_DEVICE_CAP_CIPHER_CCMP;
-
-    if (range->enc_capa & IW_ENC_CAPA_WPA)
-        caps |= NM_WIFI_DEVICE_CAP_WPA;
-
-    if (range->enc_capa & IW_ENC_CAPA_WPA2)
-        caps |= NM_WIFI_DEVICE_CAP_RSN;
-
-    /* Check for cipher support but not WPA support */
-    if ((caps & (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
-        && !(caps & (NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN))) {
-        _LOGW(LOGD_WIFI,
-              "%s: device supports WPA ciphers but not WPA protocol; WPA unavailable.",
-              ifname);
-        caps &= ~WPA_CAPS;
-    }
-
-    /* Check for WPA support but not cipher support */
-    if ((caps & (NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN))
-        && !(caps & (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP))) {
-        _LOGW(LOGD_WIFI,
-              "%s: device supports WPA protocol but not WPA ciphers; WPA unavailable.",
-              ifname);
-        caps &= ~WPA_CAPS;
-    }
-
-    /* There's no way to detect Ad-Hoc/AP mode support with WEXT
-     * (other than actually trying to do it), so just assume that
-     * Ad-Hoc is supported and AP isn't.
-     */
-    caps |= NM_WIFI_DEVICE_CAP_ADHOC;
-
-    return caps;
-}
-
-/*****************************************************************************/
-
-static void
-nm_wifi_utils_wext_init(NMWifiUtilsWext *self)
-{}
-
-static void
-nm_wifi_utils_wext_class_init(NMWifiUtilsWextClass *klass)
-{
-    GObjectClass *    object_class     = G_OBJECT_CLASS(klass);
-    NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS(klass);
-
-    object_class->dispose = dispose;
-
-    wifi_utils_class->get_mode         = wifi_wext_get_mode;
-    wifi_utils_class->set_mode         = wifi_wext_set_mode;
-    wifi_utils_class->set_powersave    = wifi_wext_set_powersave;
-    wifi_utils_class->get_freq         = wifi_wext_get_freq;
-    wifi_utils_class->find_freq        = wifi_wext_find_freq;
-    wifi_utils_class->get_station      = wifi_wext_get_station;
-    wifi_utils_class->get_mesh_channel = wifi_wext_get_mesh_channel;
-    wifi_utils_class->set_mesh_channel = wifi_wext_set_mesh_channel;
-    wifi_utils_class->set_mesh_ssid    = wifi_wext_set_mesh_ssid;
-}
-
-NMWifiUtils *
-nm_wifi_utils_wext_new(int ifindex, gboolean check_scan)
-{
-    NMWifiUtilsWext *               wext;
-    struct iw_range                 range;
-    guint32                         response_len = 0;
-    struct iw_range_with_scan_capa *scan_capa_range;
-    int                             i;
-    gboolean                        freq_valid = FALSE, has_5ghz = FALSE, has_2ghz = FALSE;
-    char                            ifname[IFNAMSIZ];
-
-    if (!nmp_utils_if_indextoname(ifindex, ifname)) {
-        _LOGW(LOGD_PLATFORM | LOGD_WIFI, "can't determine interface name for ifindex %d", ifindex);
-        return NULL;
-    }
-
-    wext = g_object_new(NM_TYPE_WIFI_UTILS_WEXT, NULL);
-
-    wext->parent.ifindex = ifindex;
-    wext->fd             = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-    if (wext->fd < 0)
-        goto error;
-
-    memset(&range, 0, sizeof(struct iw_range));
-    if (wext_get_range_ifname(wext, ifname, &range, &response_len) == FALSE) {
-        _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): driver WEXT range request failed", ifname);
-        goto error;
-    }
-
-    if ((response_len < 300) || (range.we_version_compiled < 21)) {
-        _LOGI(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): driver WEXT version too old (got %d, expected >= 21)",
-              ifname,
-              range.we_version_compiled);
-        goto error;
-    }
-
-    wext->max_qual.qual    = range.max_qual.qual;
-    wext->max_qual.level   = range.max_qual.level;
-    wext->max_qual.noise   = range.max_qual.noise;
-    wext->max_qual.updated = range.max_qual.updated;
-
-    wext->num_freqs = MIN(range.num_frequency, IW_MAX_FREQUENCIES);
-    for (i = 0; i < wext->num_freqs; i++) {
-        wext->freqs[i] = iw_freq_to_uint32(&range.freq[i]);
-        freq_valid     = TRUE;
-        if (wext->freqs[i] > 2400 && wext->freqs[i] < 2500)
-            has_2ghz = TRUE;
-        else if (wext->freqs[i] > 4900 && wext->freqs[i] < 6000)
-            has_5ghz = TRUE;
-    }
-
-    /* Check for scanning capability; cards that can't scan are not supported */
-    if (check_scan && (wext_can_scan_ifname(wext, ifname) == FALSE)) {
-        _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): drivers that cannot scan are unsupported", ifname);
-        goto error;
-    }
-
-    /* Check for the ability to scan specific SSIDs.  Until the scan_capa
-     * field gets added to wireless-tools, need to work around that by casting
-     * to the custom structure.
-     */
-    scan_capa_range = (struct iw_range_with_scan_capa *) &range;
-    if (scan_capa_range->scan_capa & NM_IW_SCAN_CAPA_ESSID) {
-        _LOGI(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): driver supports SSID scans (scan_capa 0x%02X).",
-              ifname,
-              scan_capa_range->scan_capa);
-    } else {
-        _LOGI(LOGD_PLATFORM | LOGD_WIFI,
-              "(%s): driver does not support SSID scans (scan_capa 0x%02X).",
-              ifname,
-              scan_capa_range->scan_capa);
-    }
-
-    wext->parent.caps = wext_get_caps(wext, ifname, &range);
-    if (freq_valid)
-        wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_VALID;
-    if (has_2ghz)
-        wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
-    if (has_5ghz)
-        wext->parent.caps |= NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
-
-    _LOGI(LOGD_PLATFORM | LOGD_WIFI, "(%s): using WEXT for Wi-Fi device control", ifname);
-
-    return (NMWifiUtils *) wext;
-
-error:
-    g_object_unref(wext);
-    return NULL;
-}
-
-gboolean
-nm_wifi_utils_wext_is_wifi(const char *iface)
-{
-    int          fd;
-    struct iwreq iwr;
-    gboolean     is_wifi = FALSE;
-
-    /* performing an ioctl on a non-existing name may cause the automatic
-     * loading of kernel modules, which should be avoided.
-     *
-     * Usually, we should thus make sure that an interface with this name
-     * exists.
-     *
-     * Note that wifi_wext_is_wifi() has only one caller which just verified
-     * that an interface with this name exists.
-     */
-
-    fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-    if (fd >= 0) {
-        nm_utils_ifname_cpy(iwr.ifr_ifrn.ifrn_name, iface);
-        if (ioctl(fd, SIOCGIWNAME, &iwr) == 0)
-            is_wifi = TRUE;
-        nm_close(fd);
-    }
-    return is_wifi;
-}
diff --git a/src/core/platform/wifi/nm-wifi-utils-wext.h b/src/core/platform/wifi/nm-wifi-utils-wext.h
deleted file mode 100644
index d6f3453c..00000000
--- a/src/core/platform/wifi/nm-wifi-utils-wext.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2011 - 2018 Red Hat, Inc.
- */
-
-#ifndef __WIFI_UTILS_WEXT_H__
-#define __WIFI_UTILS_WEXT_H__
-
-#include "nm-wifi-utils.h"
-
-#define NM_TYPE_WIFI_UTILS_WEXT (nm_wifi_utils_wext_get_type())
-#define NM_WIFI_UTILS_WEXT(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWext))
-#define NM_WIFI_UTILS_WEXT_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass))
-#define NM_IS_WIFI_UTILS_WEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS_WEXT))
-#define NM_IS_WIFI_UTILS_WEXT_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS_WEXT))
-#define NM_WIFI_UTILS_WEXT_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass))
-
-GType nm_wifi_utils_wext_get_type(void);
-
-NMWifiUtils *nm_wifi_utils_wext_new(int ifindex, gboolean check_scan);
-
-gboolean nm_wifi_utils_wext_is_wifi(const char *iface);
-
-#endif /* __WIFI_UTILS_WEXT_H__ */
diff --git a/src/core/platform/wifi/nm-wifi-utils.c b/src/core/platform/wifi/nm-wifi-utils.c
deleted file mode 100644
index 49ae1811..00000000
--- a/src/core/platform/wifi/nm-wifi-utils.c
+++ /dev/null
@@ -1,211 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2005 - 2018 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-wifi-utils.h"
-
-#include <sys/stat.h>
-#include <stdio.h>
-#include <fcntl.h>
-
-#include "nm-wifi-utils-private.h"
-#include "nm-wifi-utils-nl80211.h"
-#if HAVE_WEXT
-    #include "nm-wifi-utils-wext.h"
-#endif
-#include "nm-core-utils.h"
-
-#include "nm-platform/nm-platform-utils.h"
-
-G_DEFINE_ABSTRACT_TYPE(NMWifiUtils, nm_wifi_utils, G_TYPE_OBJECT)
-
-/*****************************************************************************/
-
-static void
-nm_wifi_utils_init(NMWifiUtils *self)
-{}
-
-static void
-nm_wifi_utils_class_init(NMWifiUtilsClass *klass)
-{}
-
-NMWifiUtils *
-nm_wifi_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan)
-{
-    NMWifiUtils *ret;
-
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    ret = nm_wifi_utils_nl80211_new(ifindex, genl);
-
-#if HAVE_WEXT
-    if (ret == NULL)
-        ret = nm_wifi_utils_wext_new(ifindex, check_scan);
-#endif
-
-    return ret;
-}
-
-NMDeviceWifiCapabilities
-nm_wifi_utils_get_caps(NMWifiUtils *data)
-{
-    g_return_val_if_fail(data != NULL, NM_WIFI_DEVICE_CAP_NONE);
-
-    return data->caps;
-}
-
-NM80211Mode
-nm_wifi_utils_get_mode(NMWifiUtils *data)
-{
-    g_return_val_if_fail(data != NULL, NM_802_11_MODE_UNKNOWN);
-    return NM_WIFI_UTILS_GET_CLASS(data)->get_mode(data);
-}
-
-gboolean
-nm_wifi_utils_set_mode(NMWifiUtils *data, const NM80211Mode mode)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-    g_return_val_if_fail((mode == NM_802_11_MODE_INFRA) || (mode == NM_802_11_MODE_AP)
-                             || (mode == NM_802_11_MODE_ADHOC) || (mode == NM_802_11_MODE_MESH),
-                         FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-
-    /* nl80211 probably doesn't need this */
-    return klass->set_mode ? klass->set_mode(data, mode) : TRUE;
-}
-
-gboolean
-nm_wifi_utils_set_powersave(NMWifiUtils *data, guint32 powersave)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-    return klass->set_powersave ? klass->set_powersave(data, powersave) : TRUE;
-}
-
-NMSettingWirelessWakeOnWLan
-nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-
-    return klass->get_wake_on_wlan ? klass->get_wake_on_wlan(data)
-                                   : NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
-}
-
-gboolean
-nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-    return klass->set_wake_on_wlan ? klass->set_wake_on_wlan(data, wowl) : FALSE;
-}
-
-guint32
-nm_wifi_utils_get_freq(NMWifiUtils *data)
-{
-    g_return_val_if_fail(data != NULL, 0);
-    return NM_WIFI_UTILS_GET_CLASS(data)->get_freq(data);
-}
-
-guint32
-nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs)
-{
-    g_return_val_if_fail(data != NULL, 0);
-    g_return_val_if_fail(freqs != NULL, 0);
-    return NM_WIFI_UTILS_GET_CLASS(data)->find_freq(data, freqs);
-}
-
-gboolean
-nm_wifi_utils_get_station(NMWifiUtils *data,
-                          NMEtherAddr *out_bssid,
-                          int *        out_quality,
-                          guint32 *    out_rate)
-{
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    return NM_WIFI_UTILS_GET_CLASS(data)->get_station(data, out_bssid, out_quality, out_rate);
-}
-
-gboolean
-nm_wifi_utils_is_wifi(int dirfd, const char *ifname)
-{
-    g_return_val_if_fail(dirfd >= 0, FALSE);
-
-    if (faccessat(dirfd, "phy80211", F_OK, 0) == 0)
-        return TRUE;
-#if HAVE_WEXT
-    if (nm_wifi_utils_wext_is_wifi(ifname))
-        return TRUE;
-#endif
-    return FALSE;
-}
-
-/* OLPC Mesh-only functions */
-
-guint32
-nm_wifi_utils_get_mesh_channel(NMWifiUtils *data)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-    g_return_val_if_fail(klass->get_mesh_channel != NULL, FALSE);
-
-    return klass->get_mesh_channel(data);
-}
-
-gboolean
-nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-    g_return_val_if_fail(channel <= 13, FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-    g_return_val_if_fail(klass->set_mesh_channel != NULL, FALSE);
-
-    return klass->set_mesh_channel(data, channel);
-}
-
-gboolean
-nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-    g_return_val_if_fail(klass->set_mesh_ssid != NULL, FALSE);
-
-    return klass->set_mesh_ssid(data, ssid, len);
-}
-
-gboolean
-nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running)
-{
-    NMWifiUtilsClass *klass;
-
-    g_return_val_if_fail(data != NULL, FALSE);
-
-    klass = NM_WIFI_UTILS_GET_CLASS(data);
-    return klass->indicate_addressing_running ? klass->indicate_addressing_running(data, running)
-                                              : FALSE;
-}
diff --git a/src/core/platform/wifi/nm-wifi-utils.h b/src/core/platform/wifi/nm-wifi-utils.h
deleted file mode 100644
index a252e43e..00000000
--- a/src/core/platform/wifi/nm-wifi-utils.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2005 - 2018 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#ifndef __WIFI_UTILS_H__
-#define __WIFI_UTILS_H__
-
-#include <net/ethernet.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-wireless.h"
-#include "nm-platform/nm-netlink.h"
-
-typedef struct NMWifiUtils NMWifiUtils;
-
-#define NM_TYPE_WIFI_UTILS (nm_wifi_utils_get_type())
-#define NM_WIFI_UTILS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_UTILS, NMWifiUtils))
-#define NM_WIFI_UTILS_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass))
-#define NM_IS_WIFI_UTILS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WIFI_UTILS))
-#define NM_IS_WIFI_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WIFI_UTILS))
-#define NM_WIFI_UTILS_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass))
-
-GType nm_wifi_utils_get_type(void);
-
-gboolean nm_wifi_utils_is_wifi(int dirfd, const char *ifname);
-
-NMWifiUtils *nm_wifi_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan);
-
-NMDeviceWifiCapabilities nm_wifi_utils_get_caps(NMWifiUtils *data);
-
-NM80211Mode nm_wifi_utils_get_mode(NMWifiUtils *data);
-
-gboolean nm_wifi_utils_set_mode(NMWifiUtils *data, const NM80211Mode mode);
-
-/* Returns frequency in MHz */
-guint32 nm_wifi_utils_get_freq(NMWifiUtils *data);
-
-/* Return the first supported frequency in the zero-terminated list.
- * Frequencies are specified in MHz. */
-guint32 nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs);
-
-/*
- * @out_bssid: must be NULL or an ETH_ALEN-byte buffer
- * @out_quality: receives signal quality in 0 - 100% range if not NULL
- * @out_rate: receives current bitrate in Kbps if not NULL
- *
- * Returns %TRUE on succcess.
- */
-gboolean nm_wifi_utils_get_station(NMWifiUtils *data,
-                                   NMEtherAddr *out_bssid,
-                                   int *        out_quality,
-                                   guint32 *    out_rate);
-
-/* Tells the driver DHCP or SLAAC is running */
-gboolean nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running);
-
-gboolean nm_wifi_utils_set_powersave(NMWifiUtils *data, guint32 powersave);
-
-NMSettingWirelessWakeOnWLan nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data);
-
-gboolean nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl);
-
-/* OLPC Mesh-only functions */
-guint32 nm_wifi_utils_get_mesh_channel(NMWifiUtils *data);
-
-gboolean nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel);
-
-gboolean nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len);
-
-#endif /* __WIFI_UTILS_H__ */
diff --git a/src/core/platform/wpan/nm-wpan-utils.c b/src/core/platform/wpan/nm-wpan-utils.c
deleted file mode 100644
index efb19979..00000000
--- a/src/core/platform/wpan/nm-wpan-utils.c
+++ /dev/null
@@ -1,286 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-wpan-utils.h"
-
-#include <linux/if.h>
-
-#include "nm-platform/nm-netlink.h"
-#include "platform/linux/nl802154.h"
-#include "nm-platform/nm-platform-utils.h"
-
-#define _NMLOG_PREFIX_NAME "wpan-nl802154"
-#define _NMLOG(level, domain, ...)                                                                \
-    G_STMT_START                                                                                  \
-    {                                                                                             \
-        char        _ifname_buf[IFNAMSIZ];                                                        \
-        const char *_ifname = self ? nmp_utils_if_indextoname(self->ifindex, _ifname_buf) : NULL; \
-                                                                                                  \
-        nm_log((level),                                                                           \
-               (domain),                                                                          \
-               _ifname ?: NULL,                                                                   \
-               NULL,                                                                              \
-               "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                                   \
-               _NMLOG_PREFIX_NAME,                                                                \
-               NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "")                               \
-                   _NM_UTILS_MACRO_REST(__VA_ARGS__));                                            \
-    }                                                                                             \
-    G_STMT_END
-
-/*****************************************************************************/
-
-struct NMWpanUtils {
-    GObject         parent;
-    int             ifindex;
-    struct nl_sock *nl_sock;
-    int             id;
-};
-
-typedef struct {
-    GObjectClass parent;
-} NMWpanUtilsClass;
-
-G_DEFINE_TYPE(NMWpanUtils, nm_wpan_utils, G_TYPE_OBJECT)
-
-/*****************************************************************************/
-
-static int
-ack_handler(struct nl_msg *msg, void *arg)
-{
-    int *done = arg;
-    *done     = 1;
-    return NL_STOP;
-}
-
-static int
-finish_handler(struct nl_msg *msg, void *arg)
-{
-    int *done = arg;
-    *done     = 1;
-    return NL_SKIP;
-}
-
-static int
-error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
-{
-    int *done = arg;
-    *done     = err->error;
-    return NL_SKIP;
-}
-
-static struct nl_msg *
-_nl802154_alloc_msg(int id, int ifindex, guint32 cmd, guint32 flags)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-    msg = nlmsg_alloc();
-    genlmsg_put(msg, 0, 0, id, 0, flags, cmd, 0);
-    NLA_PUT_U32(msg, NL802154_ATTR_IFINDEX, ifindex);
-    return g_steal_pointer(&msg);
-
-nla_put_failure:
-    g_return_val_if_reached(NULL);
-}
-
-static struct nl_msg *
-nl802154_alloc_msg(NMWpanUtils *self, guint32 cmd, guint32 flags)
-{
-    return _nl802154_alloc_msg(self->id, self->ifindex, cmd, flags);
-}
-
-static int
-nl802154_send_and_recv(NMWpanUtils *  self,
-                       struct nl_msg *msg,
-                       int (*valid_handler)(struct nl_msg *, void *),
-                       void *valid_data)
-{
-    int                err;
-    int                done = 0;
-    const struct nl_cb cb   = {
-        .err_cb     = error_handler,
-        .err_arg    = &done,
-        .finish_cb  = finish_handler,
-        .finish_arg = &done,
-        .ack_cb     = ack_handler,
-        .ack_arg    = &done,
-        .valid_cb   = valid_handler,
-        .valid_arg  = valid_data,
-    };
-
-    g_return_val_if_fail(msg != NULL, -ENOMEM);
-
-    err = nl_send_auto(self->nl_sock, msg);
-    if (err < 0)
-        return err;
-
-    /* Loop until one of our NL callbacks says we're done; on success
-     * done will be 1, on error it will be < 0.
-     */
-    while (!done) {
-        err = nl_recvmsgs(self->nl_sock, &cb);
-        if (err < 0 && err != -EAGAIN) {
-            _LOGW(LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s", err, nm_strerror(err));
-            break;
-        }
-    }
-
-    if (err >= 0 && done < 0)
-        err = done;
-    return err;
-}
-
-struct nl802154_interface {
-    guint16 pan_id;
-    guint16 short_addr;
-
-    gboolean valid;
-};
-
-static int
-nl802154_get_interface_handler(struct nl_msg *msg, void *arg)
-{
-    static const struct nla_policy nl802154_policy[] = {
-        [NL802154_ATTR_PAN_ID]     = {.type = NLA_U16},
-        [NL802154_ATTR_SHORT_ADDR] = {.type = NLA_U16},
-    };
-    struct nlattr *            tb[G_N_ELEMENTS(nl802154_policy)];
-    struct nl802154_interface *info = arg;
-    struct genlmsghdr *        gnlh = nlmsg_data(nlmsg_hdr(msg));
-
-    if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nl802154_policy) < 0)
-        return NL_SKIP;
-
-    if (tb[NL802154_ATTR_PAN_ID])
-        info->pan_id = le16toh(nla_get_u16(tb[NL802154_ATTR_PAN_ID]));
-
-    if (tb[NL802154_ATTR_SHORT_ADDR])
-        info->short_addr = le16toh(nla_get_u16(tb[NL802154_ATTR_SHORT_ADDR]));
-
-    info->valid = TRUE;
-
-    return NL_SKIP;
-}
-
-static void
-nl802154_get_interface(NMWpanUtils *self, struct nl802154_interface *interface)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-    memset(interface, 0, sizeof(*interface));
-
-    msg = nl802154_alloc_msg(self, NL802154_CMD_GET_INTERFACE, 0);
-
-    nl802154_send_and_recv(self, msg, nl802154_get_interface_handler, interface);
-}
-
-/*****************************************************************************/
-
-guint16
-nm_wpan_utils_get_pan_id(NMWpanUtils *self)
-{
-    struct nl802154_interface interface;
-
-    nl802154_get_interface(self, &interface);
-
-    return interface.pan_id;
-}
-
-gboolean
-nm_wpan_utils_set_pan_id(NMWpanUtils *self, guint16 pan_id)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    int                          err;
-
-    g_return_val_if_fail(self != NULL, FALSE);
-
-    msg = nl802154_alloc_msg(self, NL802154_CMD_SET_PAN_ID, 0);
-    NLA_PUT_U16(msg, NL802154_ATTR_PAN_ID, htole16(pan_id));
-    err = nl802154_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-guint16
-nm_wpan_utils_get_short_addr(NMWpanUtils *self)
-{
-    struct nl802154_interface interface;
-
-    nl802154_get_interface(self, &interface);
-
-    return interface.short_addr;
-}
-
-gboolean
-nm_wpan_utils_set_short_addr(NMWpanUtils *self, guint16 short_addr)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    int                          err;
-
-    g_return_val_if_fail(self != NULL, FALSE);
-
-    msg = nl802154_alloc_msg(self, NL802154_CMD_SET_SHORT_ADDR, 0);
-    NLA_PUT_U16(msg, NL802154_ATTR_SHORT_ADDR, htole16(short_addr));
-    err = nl802154_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-gboolean
-nm_wpan_utils_set_channel(NMWpanUtils *self, guint8 page, guint8 channel)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    int                          err;
-
-    g_return_val_if_fail(self != NULL, FALSE);
-
-    msg = nl802154_alloc_msg(self, NL802154_CMD_SET_CHANNEL, 0);
-    NLA_PUT_U8(msg, NL802154_ATTR_PAGE, page);
-    NLA_PUT_U8(msg, NL802154_ATTR_CHANNEL, channel);
-    err = nl802154_send_and_recv(self, msg, NULL, NULL);
-    return err >= 0;
-
-nla_put_failure:
-    g_return_val_if_reached(FALSE);
-}
-
-/*****************************************************************************/
-
-static void
-nm_wpan_utils_init(NMWpanUtils *self)
-{}
-
-static void
-nm_wpan_utils_class_init(NMWpanUtilsClass *klass)
-{}
-
-NMWpanUtils *
-nm_wpan_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan)
-{
-    NMWpanUtils *self;
-
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    if (!genl)
-        return NULL;
-
-    self          = g_object_new(NM_TYPE_WPAN_UTILS, NULL);
-    self->ifindex = ifindex;
-    self->nl_sock = genl;
-    self->id      = genl_ctrl_resolve(genl, "nl802154");
-
-    if (self->id < 0) {
-        _LOGD(LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
-        g_object_unref(self);
-        return NULL;
-    }
-
-    return self;
-}
diff --git a/src/core/platform/wpan/nm-wpan-utils.h b/src/core/platform/wpan/nm-wpan-utils.h
deleted file mode 100644
index 940c2c9d..00000000
--- a/src/core/platform/wpan/nm-wpan-utils.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#ifndef __WPAN_UTILS_H__
-#define __WPAN_UTILS_H__
-
-#include <net/ethernet.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-platform/nm-netlink.h"
-
-typedef struct NMWpanUtils NMWpanUtils;
-
-#define NM_TYPE_WPAN_UTILS (nm_wpan_utils_get_type())
-#define NM_WPAN_UTILS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WPAN_UTILS, NMWpanUtils))
-#define NM_WPAN_UTILS_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass))
-#define NM_IS_WPAN_UTILS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WPAN_UTILS))
-#define NM_IS_WPAN_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_WPAN_UTILS))
-#define NM_WPAN_UTILS_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass))
-
-GType nm_wpan_utils_get_type(void);
-
-NMWpanUtils *nm_wpan_utils_new(int ifindex, struct nl_sock *genl, gboolean check_scan);
-
-guint16  nm_wpan_utils_get_pan_id(NMWpanUtils *self);
-gboolean nm_wpan_utils_set_pan_id(NMWpanUtils *self, guint16 pan_id);
-
-guint16  nm_wpan_utils_get_short_addr(NMWpanUtils *self);
-gboolean nm_wpan_utils_set_short_addr(NMWpanUtils *self, guint16 short_addr);
-
-gboolean nm_wpan_utils_set_channel(NMWpanUtils *self, guint8 page, guint8 channel);
-
-#endif /* __WPAN_UTILS_H__ */