about summary refs log tree commit diff
path: root/src/libnm-platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-05-04 15:35:24 +0200
committerMichael Biebl <biebl@debian.org>2022-05-04 15:35:24 +0200
commit9959fdb2e8ddd06f2161798ca0a39c77d67c652d (patch)
tree2ce24a336d2b1c5fd5dec3090db312eded6c78ba /src/libnm-platform
parent8c623dddbdebe354cb94bfc559a5371a14865317 (diff)
New upstream version 1.37.92 upstream/1.37.92
Diffstat (limited to 'src/libnm-platform')
-rw-r--r--src/libnm-platform/nm-linux-platform.c97
-rw-r--r--src/libnm-platform/nm-platform-utils.c12
-rw-r--r--src/libnm-platform/nm-platform-utils.h2
-rw-r--r--src/libnm-platform/nm-platform.c955
-rw-r--r--src/libnm-platform/nm-platform.h144
-rw-r--r--src/libnm-platform/nmp-base.h2
-rw-r--r--src/libnm-platform/nmp-object.c106
-rw-r--r--src/libnm-platform/nmp-object.h33
-rw-r--r--src/libnm-platform/nmp-route-manager.c25
-rw-r--r--src/libnm-platform/tests/test-nm-platform.c2
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-nl80211.c153
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-private.h5
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-wext.c6
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils.c25
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils.h6
15 files changed, 1114 insertions, 459 deletions
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index ce21ebbb..40cb4396 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -1114,20 +1114,22 @@ _linktype_get_type(NMPlatform       *platform,
             return link_type;
     }
 
-    if (arptype == ARPHRD_LOOPBACK)
+    switch (arptype) {
+    case ARPHRD_LOOPBACK:
         return NM_LINK_TYPE_LOOPBACK;
-    else if (arptype == ARPHRD_INFINIBAND)
+    case ARPHRD_INFINIBAND:
         return NM_LINK_TYPE_INFINIBAND;
-    else if (arptype == ARPHRD_SIT)
+    case ARPHRD_SIT:
         return NM_LINK_TYPE_SIT;
-    else if (arptype == ARPHRD_TUNNEL6)
+    case ARPHRD_TUNNEL6:
         return NM_LINK_TYPE_IP6TNL;
-    else if (arptype == ARPHRD_PPP)
+    case ARPHRD_PPP:
         return NM_LINK_TYPE_PPP;
-    else if (arptype == ARPHRD_IEEE802154)
+    case ARPHRD_IEEE802154:
         return NM_LINK_TYPE_WPAN;
-    else if (arptype == ARPHRD_6LOWPAN)
+    case ARPHRD_6LOWPAN:
         return NM_LINK_TYPE_6LOWPAN;
+    }
 
     {
         NMPUtilsEthtoolDriverInfo driver_info;
@@ -3453,7 +3455,8 @@ _new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter *parse
                    RTN_LOCAL,
                    RTN_BLACKHOLE,
                    RTN_UNREACHABLE,
-                   RTN_PROHIBIT))
+                   RTN_PROHIBIT,
+                   RTN_THROW))
         return NULL;
 
     if (nlmsg_parse_arr(nlh, sizeof(struct rtmsg), tb, policy) < 0)
@@ -6339,6 +6342,7 @@ cache_prune_one_type(NMPlatform *platform, const NMPLookup *lookup)
 
     nm_dedup_multi_iter_init(&iter, nmp_cache_lookup(cache, lookup));
     while (nm_dedup_multi_iter_next(&iter)) {
+        char                     sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
         const NMDedupMultiEntry *main_entry;
 
         /* we only track the dirty flag for the OBJECT-TYPE index. That means,
@@ -6350,7 +6354,7 @@ cache_prune_one_type(NMPlatform *platform, const NMPLookup *lookup)
         obj = main_entry->obj;
 
         _LOGt("cache-prune: prune %s",
-              nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+              nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, sbuf, sizeof(sbuf)));
 
         {
             nm_auto_nmpobj const NMPObject *obj_old = NULL;
@@ -6390,8 +6394,8 @@ cache_on_change(NMPlatform      *platform,
                 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)];
+    char            str_buf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    char            str_buf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMPCache       *cache = nm_platform_get_cache(platform);
 
     ASSERT_nmp_cache_ops(cache, cache_op, obj_old, obj_new);
@@ -7023,6 +7027,7 @@ event_seq_check(NMPlatform             *platform,
 static void
 event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events)
 {
+    char                      sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMLinuxPlatformPrivate   *priv;
     nm_auto_nmpobj NMPObject *obj = NULL;
     NMPCacheOpsType           cache_op;
@@ -7079,8 +7084,8 @@ event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events
           is_dump ? ", in-dump" : "",
           nmp_object_to_string(obj,
                                is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
-                               NULL,
-                               0));
+                               sbuf1,
+                               sizeof(sbuf1)));
 
     while (TRUE) {
         nm_auto_nmpobj const NMPObject *obj_old = NULL;
@@ -7306,6 +7311,7 @@ do_add_addrroute(NMPlatform      *platform,
                  struct nl_msg   *nlmsg,
                  gboolean         suppress_netlink_failure)
 {
+    char                    sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
     WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
     gs_free char           *errmsg     = NULL;
     int                     nle;
@@ -7328,7 +7334,7 @@ do_add_addrroute(NMPlatform      *platform,
     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),
+              nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
               nm_strerror(nle),
               -nle);
         return -NME_PL_NETLINK;
@@ -7344,7 +7350,7 @@ do_add_addrroute(NMPlatform      *platform,
                : 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),
+           nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
            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) {
@@ -7365,6 +7371,7 @@ do_add_addrroute(NMPlatform      *platform,
 static gboolean
 do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
+    char                    sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
     WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
     gs_free char           *errmsg     = NULL;
     int                     nle;
@@ -7383,7 +7390,7 @@ do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *n
     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),
+              nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
               nm_strerror(nle),
               -nle);
         return FALSE;
@@ -7402,6 +7409,8 @@ do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *n
              && 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), ENODEV)) {
+        log_detail = ", meaning the device 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,
@@ -7413,7 +7422,7 @@ do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *n
     _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),
+           nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
            wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)),
            log_detail);
 
@@ -7534,6 +7543,21 @@ out:
 }
 
 static int
+link_change(NMPlatform *platform, NMLinkType type, int ifindex, gconstpointer extra_data)
+{
+    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+
+    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, 0);
+    if (!nlmsg)
+        return -NME_UNSPEC;
+
+    if (!_nl_msg_new_link_set_linkinfo(nlmsg, type, extra_data))
+        return -NME_UNSPEC;
+
+    return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
+}
+
+static int
 link_add(NMPlatform            *platform,
          NMLinkType             type,
          const char            *name,
@@ -8451,6 +8475,7 @@ 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;
@@ -8460,6 +8485,7 @@ 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);
 }
 
@@ -8471,6 +8497,7 @@ wifi_get_station(NMPlatform  *platform,
                  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);
 }
 
@@ -8478,6 +8505,7 @@ 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);
 }
 
@@ -8485,6 +8513,7 @@ 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);
 }
 
@@ -8492,6 +8521,7 @@ 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);
 }
 
@@ -8499,6 +8529,7 @@ 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);
 }
 
@@ -8506,6 +8537,7 @@ 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);
 }
 
@@ -8513,6 +8545,7 @@ 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);
 }
 
@@ -8520,9 +8553,26 @@ 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
+wifi_get_csme_conn_info(NMPlatform *platform, int ifindex, NMPlatformCsmeConnInfo *out_conn_info)
+{
+    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
+
+    return nm_wifi_utils_get_csme_conn_info(wifi_data, out_conn_info);
+}
+
+static gboolean
+wifi_get_device_from_csme(NMPlatform *platform, int ifindex)
+{
+    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
+
+    return nm_wifi_utils_get_device_from_csme(wifi_data);
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -8565,6 +8615,7 @@ 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);
 }
 
@@ -8572,6 +8623,7 @@ 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);
 }
 
@@ -8579,6 +8631,7 @@ 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);
 }
 
@@ -9727,13 +9780,10 @@ constructed(GObject *_object)
           fd);
 
     priv->event_source =
-        nm_g_unix_fd_source_new(fd,
+        nm_g_unix_fd_add_source(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);
+                                platform);
 
     /* complete construction of the GObject instance before populating the cache. */
     G_OBJECT_CLASS(nm_linux_platform_parent_class)->constructed(_object);
@@ -9889,6 +9939,7 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
     platform_class->sysctl_get       = sysctl_get;
 
     platform_class->link_add    = link_add;
+    platform_class->link_change = link_change;
     platform_class->link_delete = link_delete;
 
     platform_class->link_refresh = link_refresh;
@@ -9938,6 +9989,8 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
     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->wifi_get_csme_conn_info          = wifi_get_csme_conn_info;
+    platform_class->wifi_get_device_from_csme        = wifi_get_device_from_csme;
 
     platform_class->mesh_get_channel = mesh_get_channel;
     platform_class->mesh_set_channel = mesh_set_channel;
diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c
index 9ad030df..bebc53a8 100644
--- a/src/libnm-platform/nm-platform-utils.c
+++ b/src/libnm-platform/nm-platform-utils.c
@@ -2132,12 +2132,15 @@ guint32
 nmp_utils_lifetime_get(guint32  timestamp,
                        guint32  lifetime,
                        guint32  preferred,
-                       gint32   now,
+                       gint32  *cached_now,
                        guint32 *out_preferred)
 {
-    guint32 t_lifetime, t_preferred;
+    guint32 t_lifetime;
+    guint32 t_preferred;
+    gint32  now;
 
-    nm_assert(now >= 0);
+    nm_assert(cached_now);
+    nm_assert(*cached_now >= 0);
 
     if (timestamp == 0 && lifetime == 0) {
         /* We treat lifetime==0 && timestamp==0 addresses as permanent addresses to allow easy
@@ -2150,8 +2153,7 @@ nmp_utils_lifetime_get(guint32  timestamp,
         return NM_PLATFORM_LIFETIME_PERMANENT;
     }
 
-    if (now <= 0)
-        now = nm_utils_get_monotonic_timestamp_sec();
+    now = nm_utils_get_monotonic_timestamp_sec_cached(cached_now);
 
     t_lifetime = nmp_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now);
     if (!t_lifetime) {
diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h
index a9ccebb3..9f17da48 100644
--- a/src/libnm-platform/nm-platform-utils.h
+++ b/src/libnm-platform/nm-platform-utils.h
@@ -86,7 +86,7 @@ nmp_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 durati
 guint32 nmp_utils_lifetime_get(guint32  timestamp,
                                guint32  lifetime,
                                guint32  preferred,
-                               gint32   now,
+                               gint32  *cached_now,
                                guint32 *out_preferred);
 
 int nmp_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...)
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index 922f412d..8866a436 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -1304,6 +1304,45 @@ nm_platform_link_add(NMPlatform            *self,
         ->link_add(self, type, name, parent, address, address_len, mtu, extra_data, out_link);
 }
 
+int
+nm_platform_link_change(NMPlatform *self, NMLinkType type, int ifindex, gconstpointer extra_data)
+{
+    char        buf[512];
+    const char *name = nm_platform_link_get_name(self, ifindex);
+
+    _CHECK_SELF(self, klass, -NME_BUG);
+
+    _LOG2D("link: changing link: "
+           "%s "    /* type */
+           "\"%s\"" /* name */
+           "%s"     /* extra_data */
+           "",
+           nm_link_type_to_string(type),
+           name,
+           ({
+               char *buf_p   = buf;
+               gsize buf_len = sizeof(buf);
+
+               buf[0] = '\0';
+
+               switch (type) {
+               case NM_LINK_TYPE_BRIDGE:
+                   nm_strbuf_append_str(&buf_p, &buf_len, ", ");
+                   nm_platform_lnk_bridge_to_string((const NMPlatformLnkBridge *) extra_data,
+                                                    buf_p,
+                                                    buf_len);
+                   break;
+               default:
+                   nm_assert(!extra_data);
+                   break;
+               }
+
+               buf;
+           }));
+
+    return klass->link_change(self, type, ifindex, extra_data);
+}
+
 /**
  * nm_platform_link_delete:
  * @self: platform instance
@@ -1834,11 +1873,14 @@ nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF
 
     g_return_val_if_fail(ifindex > 0, FALSE);
 
-    _LOG3D("link: setting VFs");
-    for (i = 0; vfs[i]; i++) {
-        const NMPlatformVF *vf = vfs[i];
+    if (_LOGD_ENABLED()) {
+        _LOG3D("link: setting VFs");
+        for (i = 0; vfs[i]; i++) {
+            char                sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+            const NMPlatformVF *vf = vfs[i];
 
-        _LOG3D("link:   VF %s", nm_platform_vf_to_string(vf, NULL, 0));
+            _LOG3D("link:   VF %s", nm_platform_vf_to_string(vf, sbuf, sizeof(sbuf)));
+        }
     }
 
     return klass->link_set_sriov_vfs(self, ifindex, vfs);
@@ -1855,14 +1897,18 @@ nm_platform_link_set_bridge_vlans(NMPlatform                        *self,
 
     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));
+    if (_LOGD_ENABLED()) {
+        _LOG3D("link: %s bridge VLANs on %s",
+               vlans ? "setting" : "clearing",
+               on_master ? "master" : "self");
+        if (vlans) {
+            for (i = 0; vlans[i]; i++) {
+                char                        sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+                const NMPlatformBridgeVlan *vlan = vlans[i];
+
+                _LOG3D("link:   bridge VLAN %s",
+                       nm_platform_bridge_vlan_to_string(vlan, sbuf, sizeof(sbuf)));
+            }
         }
     }
 
@@ -2092,7 +2138,10 @@ nm_platform_link_release(NMPlatform *self, int master, int ifindex)
     if (nm_platform_link_get_master(self, ifindex) != master)
         return FALSE;
 
-    _LOG3D("link: releasing from master '%s'", nm_platform_link_get_name(self, master));
+    _LOG3D("link: releasing %d from master '%s' (%d)",
+           ifindex,
+           nm_platform_link_get_name(self, master),
+           master);
     return klass->link_release(self, master, ifindex);
 }
 
@@ -3058,6 +3107,28 @@ nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, _NMSettingWirel
     return klass->wifi_set_wake_on_wlan(self, ifindex, wowl);
 }
 
+gboolean
+nm_platform_wifi_get_csme_conn_info(NMPlatform             *self,
+                                    int                     ifindex,
+                                    NMPlatformCsmeConnInfo *out_conn_info)
+{
+    _CHECK_SELF(self, klass, FALSE);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    return klass->wifi_get_csme_conn_info(self, ifindex, out_conn_info);
+}
+
+gboolean
+nm_platform_wifi_get_device_from_csme(NMPlatform *self, int ifindex)
+{
+    _CHECK_SELF(self, klass, FALSE);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    return klass->wifi_get_device_from_csme(self, ifindex);
+}
+
 guint32
 nm_platform_mesh_get_channel(NMPlatform *self, int ifindex)
 {
@@ -3479,6 +3550,7 @@ nm_platform_ip4_address_add(NMPlatform *self,
                          FALSE);
 
     if (_LOGD_ENABLED()) {
+        char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
         NMPlatformIP4Address addr;
 
         addr = (NMPlatformIP4Address){
@@ -3497,7 +3569,7 @@ nm_platform_ip4_address_add(NMPlatform *self,
             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));
+               nm_platform_ip4_address_to_string(&addr, sbuf, sizeof(sbuf)));
     }
     return klass->ip4_address_add(self,
                                   ifindex,
@@ -3529,6 +3601,7 @@ nm_platform_ip6_address_add(NMPlatform     *self,
     g_return_val_if_fail(preferred <= lifetime, FALSE);
 
     if (_LOGD_ENABLED()) {
+        char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
         NMPlatformIP6Address addr = {0};
 
         addr.ifindex      = ifindex;
@@ -3541,7 +3614,7 @@ nm_platform_ip6_address_add(NMPlatform     *self,
         addr.n_ifa_flags  = flags;
 
         _LOG3D("address: adding or updating IPv6 address: %s",
-               nm_platform_ip6_address_to_string(&addr, NULL, 0));
+               nm_platform_ip6_address_to_string(&addr, sbuf, sizeof(sbuf)));
     }
     return klass
         ->ip6_address_add(self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
@@ -3592,6 +3665,41 @@ nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr ad
     return klass->ip6_address_delete(self, ifindex, address, plen);
 }
 
+const NMPObject *
+nm_platform_ip_address_get(NMPlatform                                 *self,
+                           int                                         addr_family,
+                           int                                         ifindex,
+                           gconstpointer /* (NMPlatformIPAddress *) */ needle)
+{
+    const NMPlatformIPXAddress *addr;
+    NMPObject                   obj_id;
+    const NMPObject            *obj;
+
+    nm_assert(NM_IS_PLATFORM(self));
+    nm_assert_addr_family(addr_family);
+    nm_assert(needle);
+
+    addr = needle;
+
+    if (ifindex <= 0) {
+        /* We allow the caller to override the ifindex. */
+        ifindex = addr->ax.ifindex;
+    }
+
+    if (NM_IS_IPv4(addr_family)) {
+        nmp_object_stackinit_id_ip4_address(&obj_id,
+                                            ifindex,
+                                            addr->a4.address,
+                                            addr->a4.plen,
+                                            addr->a4.peer_address);
+    } else
+        nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr->a6.address);
+
+    obj = nmp_cache_lookup_obj(nm_platform_get_cache(self), &obj_id);
+    nm_assert(!obj || nmp_object_is_visible(obj));
+    return obj;
+}
+
 const NMPlatformIP4Address *
 nm_platform_ip4_address_get(NMPlatform *self,
                             int         ifindex,
@@ -3632,7 +3740,7 @@ static gboolean
 _addr_array_clean_expired(int          addr_family,
                           int          ifindex,
                           GPtrArray   *array,
-                          guint32      now,
+                          gint32      *cached_now,
                           GHashTable **idx)
 {
     guint    i;
@@ -3640,7 +3748,8 @@ _addr_array_clean_expired(int          addr_family,
 
     nm_assert_addr_family(addr_family);
     nm_assert(ifindex > 0);
-    nm_assert(now > 0);
+    nm_assert(cached_now);
+    nm_assert(*cached_now >= 0);
 
     if (!array)
         return FALSE;
@@ -3674,17 +3783,16 @@ _addr_array_clean_expired(int          addr_family,
             goto clear_and_next;
         }
 
-        if (!nmp_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, now, NULL))
+        if (!nmp_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, cached_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();
+        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;
 
@@ -3698,8 +3806,8 @@ clear_and_next:
 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];
+    return nm_ptr_to_uintptr(needle) >= nm_ptr_to_uintptr(&addresses->pdata[0])
+           && nm_ptr_to_uintptr(needle) < nm_ptr_to_uintptr(&addresses->pdata[addresses->len]);
 }
 
 static const NMPObject **
@@ -3733,6 +3841,30 @@ ip4_addr_subnets_destroy_index(GHashTable *subnets, const GPtrArray *addresses)
     g_hash_table_unref(subnets);
 }
 
+static guint
+_ip4_addr_subnets_hash(gconstpointer ptr)
+{
+    const NMPlatformIP4Address *addr = NMP_OBJECT_CAST_IP4_ADDRESS(ptr);
+    NMHashState                 h;
+
+    nm_hash_init(&h, 3282159733);
+    nm_hash_update_vals(&h,
+                        addr->plen,
+                        nm_utils_ip4_address_clear_host_address(addr->address, addr->plen));
+    return nm_hash_complete(&h);
+}
+
+static gboolean
+_ip4_addr_subnets_equal(gconstpointer p_a, gconstpointer p_b)
+{
+    const NMPlatformIP4Address *a = NMP_OBJECT_CAST_IP4_ADDRESS(p_a);
+    const NMPlatformIP4Address *b = NMP_OBJECT_CAST_IP4_ADDRESS(p_b);
+
+    return a->plen == b->plen
+           && (nm_utils_ip4_address_clear_host_address(a->address, a->plen)
+               == nm_utils_ip4_address_clear_host_address(b->address, b->plen));
+}
+
 static GHashTable *
 ip4_addr_subnets_build_index(const GPtrArray *addresses,
                              gboolean         consider_flags,
@@ -3743,34 +3875,35 @@ ip4_addr_subnets_build_index(const GPtrArray *addresses,
 
     nm_assert(addresses && addresses->len);
 
-    subnets = g_hash_table_new(nm_direct_hash, NULL);
+    subnets = g_hash_table_new(_ip4_addr_subnets_hash, _ip4_addr_subnets_equal);
 
     /* Build a hash table of all addresses per subnet */
     for (i = 0; i < addresses->len; i++) {
+        const NMPObject           **p_obj;
+        const NMPObject            *obj;
         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]);
+        p_obj = (const NMPObject **) &addresses->pdata[i];
+        obj   = *p_obj;
 
-        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);
+        if (!g_hash_table_lookup_extended(subnets, obj, NULL, &p)) {
+            g_hash_table_insert(subnets, (gpointer) obj, p_obj);
             continue;
         }
         nm_assert(p);
 
+        address = NMP_OBJECT_CAST_IP4_ADDRESS(obj);
+
         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_hash_table_insert(subnets, (gpointer) obj, addr_list);
                 g_ptr_array_add(addr_list, p);
             } else
                 addr_list = p;
@@ -3779,13 +3912,13 @@ ip4_addr_subnets_build_index(const GPtrArray *addresses,
                 position = -1; /* append */
             else
                 position = 0; /* prepend */
-            g_ptr_array_insert(addr_list, position, p_address);
+            g_ptr_array_insert(addr_list, position, p_obj);
         } 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);
+                g_hash_table_insert(subnets, (gpointer) obj, p_obj);
             }
         }
     }
@@ -3811,16 +3944,11 @@ ip4_addr_subnets_is_secondary(const NMPObject  *address,
                               const GPtrArray  *addresses,
                               const GPtrArray **out_addr_list)
 {
-    const NMPlatformIP4Address *a;
-    const GPtrArray            *addr_list;
-    gconstpointer               p;
-    guint32                     net;
-    const NMPObject           **o;
+    const GPtrArray  *addr_list;
+    gconstpointer     p;
+    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));
+    p = g_hash_table_lookup(subnets, address);
     nm_assert(p);
     if (!ip4_addr_subnets_is_plain_address(addresses, p)) {
         addr_list = p;
@@ -3857,41 +3985,33 @@ ip6_address_scope(const NMPlatformIP6Address *a)
 }
 
 static int
-ip6_address_scope_cmp(gconstpointer p_a, gconstpointer p_b, gpointer increasing)
+ip6_address_scope_cmp_ascending(gconstpointer p_a, gconstpointer p_b, gpointer unused)
 {
-    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));
+    NM_CMP_DIRECT(ip6_address_scope(NMP_OBJECT_CAST_IP6_ADDRESS(*(const NMPObject *const *) p_a)),
+                  ip6_address_scope(NMP_OBJECT_CAST_IP6_ADDRESS(*(const NMPObject *const *) p_b)));
     return 0;
 }
 
+static int
+ip6_address_scope_cmp_descending(gconstpointer p_a, gconstpointer p_b, gpointer unused)
+{
+    return ip6_address_scope_cmp_ascending(p_b, p_a, NULL);
+}
+
 /**
  * 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.
+ * @known_addresses: List of addresses. The list will be modified and
+ *   expired addresses will be cleared (by calling nmp_object_unref()
+ *   on the array element).
  * @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.
+ *   Addresses that are both contained in @known_addresses and @addresses_prune
+ *   will be configured.
  *
  * A convenience function to synchronize addresses for a specific interface
  * with the least possible disturbance. It simply removes addresses that are
@@ -3906,11 +4026,14 @@ nm_platform_ip_address_sync(NMPlatform *self,
                             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;
+    gint32                         now     = 0;
+    const int                      IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMPLookup                      lookup;
+    const gboolean                 EXTRA_LOGGING        = FALSE;
+    gs_unref_hashtable GHashTable *known_addresses_idx  = NULL;
+    gs_unref_hashtable GHashTable *plat_addrs_to_delete = NULL;
+    gs_unref_ptrarray GPtrArray   *plat_addresses       = NULL;
+    gboolean                       success;
     guint                          i_plat;
     guint                          i_know;
     guint                          i;
@@ -3918,74 +4041,181 @@ nm_platform_ip_address_sync(NMPlatform *self,
 
     _CHECK_SELF(self, klass, FALSE);
 
+#define _plat_addrs_to_delete_ensure(ptr)                                    \
+    ({                                                                       \
+        GHashTable **_ptr = (ptr);                                           \
+                                                                             \
+        if (!*_ptr) {                                                        \
+            *_ptr = g_hash_table_new_full((GHashFunc) nmp_object_id_hash,    \
+                                          (GEqualFunc) nmp_object_id_equal,  \
+                                          (GDestroyNotify) nmp_object_unref, \
+                                          NULL);                             \
+        }                                                                    \
+        *_ptr;                                                               \
+    })
+
+    /* Disabled. Enable this for printf debugging. */
+    if (EXTRA_LOGGING) {
+        char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+        char sbuf1[50];
+
+        _LOG3T("IPv%c address sync on %d (%u addresses, %u to prune)",
+               nm_utils_addr_family_to_char(addr_family),
+               ifindex,
+               nm_g_ptr_array_len(known_addresses),
+               nm_g_ptr_array_len(addresses_prune));
+        for (i = 0; known_addresses && i < known_addresses->len; i++) {
+            _LOG3T("  address#%u: %s%s",
+                   i,
+                   nmp_object_to_string(known_addresses->pdata[i],
+                                        NMP_OBJECT_TO_STRING_ALL,
+                                        sbuf,
+                                        sizeof(sbuf)),
+                   IS_IPv4 ? ""
+                           : nm_sprintf_buf(sbuf1,
+                                            " (scope %d)",
+                                            (int) ip6_address_scope(NMP_OBJECT_CAST_IP6_ADDRESS(
+                                                known_addresses->pdata[i]))));
+        }
+        for (i = 0; addresses_prune && i < addresses_prune->len; i++) {
+            _LOG3T("  prune  #%u: %s",
+                   i,
+                   nmp_object_to_string(addresses_prune->pdata[i],
+                                        NMP_OBJECT_TO_STRING_ALL,
+                                        sbuf,
+                                        sizeof(sbuf)));
+        }
+    }
+
+    /* @known_addresses are in decreasing priority order (highest priority addresses first). */
+
     /* 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));
+            g_ptr_array_sort_with_data(known_addresses, ip6_address_scope_cmp_descending, NULL);
     }
 
     if (!_addr_array_clean_expired(addr_family,
                                    ifindex,
                                    known_addresses,
-                                   now,
+                                   &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(addresses_prune) > 0) {
+        /* First delete addresses that we should prune (and which are no longer tracked
+         * as @known_addresses. */
+        for (i = 0; i < addresses_prune->len; i++) {
+            const NMPObject *prune_obj = addresses_prune->pdata[i];
+
+            nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(prune_obj),
+                                NMP_OBJECT_TYPE_IP4_ADDRESS,
+                                NMP_OBJECT_TYPE_IP6_ADDRESS));
+
+            if (nm_g_hash_table_contains(known_addresses_idx, prune_obj))
+                continue;
+
+            nm_platform_ip_address_delete(self,
+                                          addr_family,
+                                          ifindex,
+                                          NMP_OBJECT_CAST_IP_ADDRESS(prune_obj));
+        }
+    }
+
+    /* ensure we have the platform cache up to date. */
+    nm_platform_process_events(self);
+
+    /* @plat_addresses for IPv6 must be sorted in decreasing priority order (highest priority addresses first).
+     * IPv4 are probably unsorted or sorted with lowest priority first, but their order doesn't matter because
+     * we check the "secondary" flag. */
+    plat_addresses = nm_platform_lookup_clone(
+        self,
+        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex),
+        NULL,
+        NULL);
+
+    if (EXTRA_LOGGING && plat_addresses) {
+        for (i = 0; i < plat_addresses->len; i++) {
+            char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+            char sbuf1[50];
+
+            _LOG3T("  platform#%u: %s%s",
+                   i,
+                   nmp_object_to_string(plat_addresses->pdata[i],
+                                        NMP_OBJECT_TO_STRING_ALL,
+                                        sbuf,
+                                        sizeof(sbuf)),
+                   IS_IPv4 ? ""
+                           : nm_sprintf_buf(sbuf1,
+                                            " (scope %d)",
+                                            (int) ip6_address_scope(NMP_OBJECT_CAST_IP6_ADDRESS(
+                                                plat_addresses->pdata[i]))));
+        }
+    }
 
     if (nm_g_ptr_array_len(plat_addresses) > 0) {
-        /* Delete unknown addresses */
+        /* Delete addresses that interfere with our intended order. */
         if (IS_IPv4) {
-            GHashTable *plat_subnets;
+            GHashTable   *known_subnets = NULL;
+            GHashTable   *plat_subnets;
+            gs_free bool *plat_handled_to_free = NULL;
+            bool         *plat_handled         = NULL;
+
+            /* For IPv4, we only consider it a conflict for addresses in the same
+             * subnet. That's where kernel will assign a primary/secondary flag.
+             * For different subnets, we don't define the order. */
 
             plat_subnets = ip4_addr_subnets_build_index(plat_addresses, TRUE, TRUE);
 
             for (i = 0; i < plat_addresses->len; i++) {
-                const NMPObject            *plat_obj;
+                const NMPObject            *plat_obj = plat_addresses->pdata[i];
+                const NMPObject            *known_obj;
                 const NMPlatformIP4Address *plat_address;
                 const GPtrArray            *addr_list;
+                gboolean                    secondary;
 
-                plat_obj = plat_addresses->pdata[i];
-                if (!plat_obj) {
-                    /* Already deleted */
+                if (plat_handled && plat_handled[i])
                     continue;
-                }
 
-                plat_address = NMP_OBJECT_CAST_IP4_ADDRESS(plat_obj);
+                known_obj = nm_g_hash_table_lookup(known_addresses_idx, plat_obj);
 
-                if (known_addresses) {
-                    const NMPObject *o;
+                if (!known_obj) {
+                    /* this address is added externally. Even if it's presence would mess
+                     * with our desired order, we cannot delete it. Skip it. */
+                    if (!plat_handled) {
+                        plat_handled = nm_malloc0_maybe_a(300,
+                                                          sizeof(bool) * plat_addresses->len,
+                                                          &plat_handled_to_free);
+                    }
+                    plat_handled[i] = TRUE;
+                    continue;
+                }
 
-                    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);
 
-                        if (!known_subnets)
-                            known_subnets =
-                                ip4_addr_subnets_build_index(known_addresses, FALSE, FALSE);
+                plat_address = NMP_OBJECT_CAST_IP4_ADDRESS(plat_obj);
 
-                        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;
-                        }
-                    }
+                secondary =
+                    ip4_addr_subnets_is_secondary(known_obj, 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 (!plat_handled) {
+                    plat_handled = nm_malloc0_maybe_a(300,
+                                                      sizeof(bool) * plat_addresses->len,
+                                                      &plat_handled_to_free);
+                }
+                plat_handled[i] = TRUE;
+
+                g_hash_table_add(_plat_addrs_to_delete_ensure(&plat_addrs_to_delete),
+                                 (gpointer) nmp_object_ref(plat_obj));
 
                 if (!ip4_addr_subnets_is_secondary(plat_obj,
                                                    plat_subnets,
@@ -3999,74 +4229,73 @@ nm_platform_ip_address_sync(NMPlatform *self,
                      * 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;
+                        const NMPObject **o = ip4_addr_subnets_addr_list_get(addr_list, j);
+                        guint             o_idx;
 
-                        o = ip4_addr_subnets_addr_list_get(addr_list, j);
-                        nm_assert(o);
+                        o_idx = (o - ((const NMPObject **) &plat_addresses->pdata[0]));
 
-                        if (*o) {
-                            const NMPlatformIP4Address *a;
+                        nm_assert(o_idx < plat_addresses->len);
+                        nm_assert(o == ((const NMPObject **) &plat_addresses->pdata[o_idx]));
 
-                            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;
+                        if (plat_handled[o_idx])
+                            continue;
+
+                        plat_handled[o_idx] = TRUE;
+
+                        if (!nm_g_hash_table_contains(known_addresses_idx, *o)) {
+                            /* Again, this is an external address. We cannot delete
+                             * it to fix the address order. Pass. */
+                            continue;
                         }
+
+                        g_hash_table_add(_plat_addrs_to_delete_ensure(&plat_addrs_to_delete),
+                                         (gpointer) nmp_object_ref(*o));
                     }
                 }
             }
             ip4_addr_subnets_destroy_index(plat_subnets, plat_addresses);
+            ip4_addr_subnets_destroy_index(known_subnets, known_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));
+            /* For IPv6, we only compare addresses per-scope. Addresses in different
+             * scopes don't have a defined order. */
 
-            known_addresses_len = known_addresses ? known_addresses->len : 0;
+            g_ptr_array_sort_with_data(plat_addresses, ip6_address_scope_cmp_descending, NULL);
 
-            /* 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. */
+            /* First, check that existing addresses have a matching plen as the ones
+             * we are about to configure (@known_addresses). If not, delete them. */
             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;
-                    }
+                const NMPObject *plat_obj = plat_addresses->pdata[i_plat];
+                const NMPObject *known_obj;
+
+                known_obj = nm_g_hash_table_lookup(known_addresses_idx, plat_obj);
+                if (!known_obj) {
+                    /* We don't know this address. It was added externally. Keep it configured.
+                     * We also don't want to delete the address below, so mark it as handled
+                     * by clearing the pointer. */
+                    nm_clear_pointer(&plat_addresses->pdata[i_plat], nmp_object_unref);
+                    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]));
+                if (NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj)->plen
+                    != NMP_OBJECT_CAST_IP6_ADDRESS(known_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 (w.r.t.
+                     * its identity, not its other attributes).
+                     * However, we cannot modify an existing addresses' plen without
+                     * removing and readding it. Thus, we need to delete plat_addr.
+                     *
+                     * We don't just add this address to @plat_addrs_to_delete, because
+                     * it's too different. Instead, delete and re-add below. */
+                    nm_platform_ip_address_delete(self,
+                                                  AF_INET6,
+                                                  ifindex,
+                                                  NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj));
+                    /* Mark address as handled. */
+                    nm_clear_pointer(&plat_addresses->pdata[i_plat], nmp_object_unref);
+                }
             }
 
             /* Next, we must preserve the priority of the routes. That is, source address
@@ -4077,16 +4306,17 @@ nm_platform_ip_address_sync(NMPlatform *self,
              * @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
+             * for 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;
+            i_know                 = nm_g_ptr_array_len(known_addresses);
+
             while (i_plat > 0) {
-                const NMPlatformIP6Address *plat_addr =
-                    NMP_OBJECT_CAST_IP6_ADDRESS(plat_addresses->pdata[--i_plat]);
-                IP6AddrScope plat_scope;
+                const NMPObject            *plat_obj  = plat_addresses->pdata[--i_plat];
+                const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj);
+                IP6AddrScope                plat_scope;
 
                 if (!plat_addr)
                     continue;
@@ -4099,10 +4329,9 @@ nm_platform_ip_address_sync(NMPlatform *self,
                 }
 
                 if (!delete_remaining_addrs) {
-                    delete_remaining_addrs = TRUE;
-                    for (; i_know < known_addresses_len; i_know++) {
+                    while (i_know > 0) {
                         const NMPlatformIP6Address *know_addr =
-                            NMP_OBJECT_CAST_IP6_ADDRESS(known_addresses->pdata[i_know]);
+                            NMP_OBJECT_CAST_IP6_ADDRESS(known_addresses->pdata[--i_know]);
                         IP6AddrScope know_scope;
 
                         if (!know_addr)
@@ -4114,18 +4343,18 @@ nm_platform_ip_address_sync(NMPlatform *self,
 
                         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. */
+                        /* "plat_address" has no match. "delete_remaining_addrs" will be set to TRUE and we will
+                         * delete all the remaining addresses with "cur_scope". */
                         break;
                     }
+                    delete_remaining_addrs = TRUE;
                 }
 
-                nm_platform_ip6_address_delete(self, ifindex, plat_addr->address, plat_addr->plen);
+                g_hash_table_add(_plat_addrs_to_delete_ensure(&plat_addrs_to_delete),
+                                 (gpointer) nmp_object_ref(plat_obj));
 next_plat:;
             }
         }
@@ -4134,33 +4363,69 @@ next_plat:;
     if (!known_addresses)
         return TRUE;
 
-    if (IS_IPv4)
-        ip4_addr_subnets_destroy_index(known_subnets, known_addresses);
+    success = TRUE;
 
     /* Add missing addresses. New addresses are added by kernel with top
      * priority.
      */
-    for (i_know = 0; i_know < known_addresses->len; i_know++) {
+    for (i = 0; i < known_addresses->len; i++) {
+        const NMPObject            *plat_obj;
+        const NMPObject            *known_obj;
         const NMPlatformIPXAddress *known_address;
-        const NMPObject            *o;
         guint32                     lifetime;
         guint32                     preferred;
 
-        o = known_addresses->pdata[i_know];
-        if (!o)
+        /* IPv4 addresses we need to add in the order most important first.
+         * IPv6 addresses we need to add in the reverse order with least
+         *   important first. Kernel will interpret the last address as most
+         *   important.
+         *
+         * @known_addresses is always in the order most-important-first. */
+        i_know = IS_IPv4 ? i : (known_addresses->len - i - 1u);
+
+        known_obj = known_addresses->pdata[i_know];
+        if (!known_obj)
             continue;
 
-        nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
+        nm_assert(NMP_OBJECT_GET_TYPE(known_obj) == NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
 
-        known_address = NMP_OBJECT_CAST_IPX_ADDRESS(o);
+        known_address = NMP_OBJECT_CAST_IPX_ADDRESS(known_obj);
 
         lifetime = nmp_utils_lifetime_get(known_address->ax.timestamp,
                                           known_address->ax.lifetime,
                                           known_address->ax.preferred,
-                                          now,
+                                          &now,
                                           &preferred);
         nm_assert(lifetime > 0);
 
+        plat_obj = nm_platform_ip_address_get(self, addr_family, ifindex, known_address);
+
+        if (plat_obj && nm_g_hash_table_contains(plat_addrs_to_delete, plat_obj)) {
+            /* This address exists, but it had the wrong priority earlier. We
+             * cannot just update it, we need to remove it first. */
+            nm_platform_ip_address_delete(self,
+                                          addr_family,
+                                          ifindex,
+                                          NMP_OBJECT_CAST_IP_ADDRESS(plat_obj));
+            plat_obj = NULL;
+        }
+
+        if (plat_obj
+            && nm_platform_vtable_address.vx[IS_IPv4].address_cmp(
+                   known_address,
+                   NMP_OBJECT_CAST_IPX_ADDRESS(plat_obj),
+                   NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY)
+                   == 0) {
+            char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
+            /* The object is already added. Skip update. */
+            _LOG3T(
+                "address: skip updating IPv%c address: %s",
+                nm_utils_addr_family_to_char(addr_family),
+                nmp_object_to_string(known_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+            continue;
+        }
+
         if (IS_IPv4) {
             if (!nm_platform_ip4_address_add(
                     self,
@@ -4172,9 +4437,8 @@ next_plat:;
                     lifetime,
                     preferred,
                     IFA_F_NOPREFIXROUTE,
-                    known_address->a4.label)) {
-                /* ignore error, for unclear reasons. */
-            }
+                    known_address->a4.label))
+                success = FALSE;
         } else {
             if (!nm_platform_ip6_address_add(self,
                                              ifindex,
@@ -4184,11 +4448,11 @@ next_plat:;
                                              lifetime,
                                              preferred,
                                              IFA_F_NOPREFIXROUTE | known_address->a6.n_ifa_flags))
-                return FALSE;
+                success = FALSE;
         }
     }
 
-    return TRUE;
+    return success;
 }
 
 gboolean
@@ -4254,7 +4518,7 @@ nm_platform_ip_address_get_prune_list(NMPlatform *self,
     const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMDedupMultiHeadEntry *head_entry;
     NMPLookup                    lookup;
-    GPtrArray                   *result;
+    GPtrArray                   *result = NULL;
     CList                       *iter;
 
     nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(NM_IS_IPv4(addr_family)), ifindex);
@@ -4264,8 +4528,6 @@ nm_platform_ip_address_get_prune_list(NMPlatform *self,
     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;
 
@@ -4275,13 +4537,12 @@ nm_platform_ip_address_get_prune_list(NMPlatform *self,
                 continue;
         }
 
+        if (!result)
+            result = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nmp_object_unref);
+
         g_ptr_array_add(result, (gpointer) nmp_object_ref(obj));
     }
 
-    if (result->len == 0) {
-        g_ptr_array_unref(result);
-        return NULL;
-    }
     return result;
 }
 
@@ -4292,7 +4553,7 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
                                     NMIPRouteTableSyncMode route_table_sync)
 {
     NMPLookup                    lookup;
-    GPtrArray                   *routes_prune;
+    GPtrArray                   *routes_prune = NULL;
     const NMDedupMultiHeadEntry *head_entry;
     CList                       *iter;
     NMPlatformIP4Route           rt_local4;
@@ -4324,8 +4585,6 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
     rt_local6.plen = 0;
     rt_mcast6.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);
@@ -4463,13 +4722,14 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
             break;
         }
 
+        if (!routes_prune) {
+            routes_prune =
+                g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nm_dedup_multi_obj_unref);
+        }
+
         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;
 }
 
@@ -4506,8 +4766,8 @@ nm_platform_ip_route_sync(NMPlatform *self,
     guint                          i;
     int                            i_type;
     gboolean                       success = TRUE;
-    char                           sbuf1[sizeof(_nm_utils_to_string_buffer)];
-    char                           sbuf2[sizeof(_nm_utils_to_string_buffer)];
+    char                           sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    char                           sbuf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
 
     nm_assert(NM_IS_PLATFORM(self));
     nm_assert(ifindex > 0);
@@ -4854,7 +5114,7 @@ nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route)
 static int
 _ip_route_add(NMPlatform *self, NMPNlmFlags flags, int addr_family, gconstpointer route)
 {
-    char sbuf[sizeof(_nm_utils_to_string_buffer)];
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     int  ifindex;
 
     _CHECK_SELF(self, klass, FALSE);
@@ -4906,7 +5166,8 @@ nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformI
 gboolean
 nm_platform_object_delete(NMPlatform *self, const NMPObject *obj)
 {
-    int ifindex;
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    int  ifindex;
 
     _CHECK_SELF(self, klass, FALSE);
 
@@ -4914,7 +5175,7 @@ nm_platform_object_delete(NMPlatform *self, const NMPObject *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));
+              nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
         break;
     case NMP_OBJECT_TYPE_IP4_ROUTE:
     case NMP_OBJECT_TYPE_IP6_ROUTE:
@@ -4923,7 +5184,7 @@ nm_platform_object_delete(NMPlatform *self, const NMPObject *obj)
         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));
+               nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
         break;
     default:
         g_return_val_if_reached(FALSE);
@@ -4941,6 +5202,7 @@ nm_platform_ip_route_get(NMPlatform   *self,
                          int           oif_ifindex,
                          NMPObject   **out_route)
 {
+    char                      sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     nm_auto_nmpobj NMPObject *route = NULL;
     int                       result;
     char                      buf[NM_UTILS_INET_ADDRSTRLEN];
@@ -4977,7 +5239,7 @@ nm_platform_ip_route_get(NMPlatform   *self,
         _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));
+              nmp_object_to_string(route, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
         NM_SET_OUT(out_route, g_steal_pointer(&route));
     }
     return result;
@@ -5004,6 +5266,7 @@ _ip4_dev_route_blacklist_timeout_ms_marked(gint64 timeout_msec)
 static gboolean
 _ip4_dev_route_blacklist_check_cb(gpointer user_data)
 {
+    char               sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMPlatform        *self = user_data;
     NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
     GHashTableIter     iter;
@@ -5034,7 +5297,7 @@ again:
             continue;
 
         _LOGT("ip4-dev-route: delete %s",
-              nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+              nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
         nm_platform_object_delete(self, p_obj);
         goto again;
     }
@@ -5097,6 +5360,7 @@ _ip4_dev_route_blacklist_notify_route(NMPlatform *self, const NMPObject *obj)
 static gboolean
 _ip4_dev_route_blacklist_gc_timeout_handle(gpointer user_data)
 {
+    char               sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMPlatform        *self = user_data;
     NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE(self);
     GHashTableIter     iter;
@@ -5112,7 +5376,7 @@ _ip4_dev_route_blacklist_gc_timeout_handle(gpointer user_data)
     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));
+                  nmp_object_to_string(p_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
             g_hash_table_iter_remove(&iter);
         }
     }
@@ -5172,6 +5436,7 @@ nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self,
                                         int         ifindex,
                                         GPtrArray  *ip4_dev_route_blacklist)
 {
+    char               sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMPlatformPrivate *priv;
     GHashTableIter     iter;
     const NMPObject   *p_obj;
@@ -5227,14 +5492,17 @@ nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self,
                 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));
+                          nmp_object_to_string(p_obj,
+                                               NMP_OBJECT_TO_STRING_PUBLIC,
+                                               sbuf,
+                                               sizeof(sbuf)));
                     *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));
+                  nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
             p_timeout_ms  = g_slice_new(gint64);
             *p_timeout_ms = timeout_msec_val;
             g_hash_table_replace(priv->ip4_dev_route_blacklist_hash,
@@ -5256,12 +5524,13 @@ nm_platform_routing_rule_add(NMPlatform                  *self,
                              NMPNlmFlags                  flags,
                              const NMPlatformRoutingRule *routing_rule)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     _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));
+          nm_platform_routing_rule_to_string(routing_rule, sbuf, sizeof(sbuf)));
     return klass->routing_rule_add(self, flags, routing_rule);
 }
 
@@ -5270,13 +5539,15 @@ nm_platform_routing_rule_add(NMPlatform                  *self,
 int
 nm_platform_qdisc_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
 {
-    int ifindex = qdisc->ifindex;
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    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));
+    _LOG3D("adding or updating a qdisc: %s",
+           nm_platform_qdisc_to_string(qdisc, sbuf, sizeof(sbuf)));
     return klass->qdisc_add(self, flags, qdisc);
 }
 
@@ -5294,13 +5565,15 @@ nm_platform_qdisc_delete(NMPlatform *self, int ifindex, guint32 parent, gboolean
 int
 nm_platform_tfilter_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformTfilter *tfilter)
 {
-    int ifindex = tfilter->ifindex;
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    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));
+    _LOG3D("adding or updating a tfilter: %s",
+           nm_platform_tfilter_to_string(tfilter, sbuf, sizeof(sbuf)));
     return klass->tfilter_add(self, flags, tfilter);
 }
 
@@ -6251,7 +6524,6 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
         "%s" /* label */
         " src %s"
         "%s" /* a_acd_not_ready */
-        "%s" /* a_assume_config_once */
         "%s" /* a_force_commit */
         "",
         s_address,
@@ -6271,7 +6543,6 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
         str_label,
         nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
         address->a_acd_not_ready ? " ip4acd-not-ready" : "",
-        address->a_assume_config_once ? " assume-config-once" : "",
         address->a_force_commit ? " force-commit" : "");
     g_free(str_peer);
     return buf;
@@ -6392,7 +6663,6 @@ nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf
         buf,
         len,
         "%s/%d lft %s pref %s%s%s%s%s src %s"
-        "%s" /* a_assume_config_once */
         "%s" /* a_force_commit */
         "",
         s_address,
@@ -6404,7 +6674,6 @@ nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf
         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->a_assume_config_once ? " assume-config-once" : "",
         address->a_force_commit ? " force-commit" : "");
     g_free(str_peer);
     return buf;
@@ -6508,7 +6777,6 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
         "%s"                      /* initcwnd */
         "%s"                      /* initrwnd */
         "%s"                      /* mtu */
-        "%s"                      /* r_assume_config_once */
         "%s"                      /* r_force_commit */
         "",
         nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
@@ -6567,7 +6835,6 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
                                                        route->lock_mtu ? "lock " : "",
                                                        route->mtu)
                                       : "",
-        route->r_assume_config_once ? " assume-config-once" : "",
         route->r_force_commit ? " force-commit" : "");
     return buf;
 }
@@ -6643,7 +6910,6 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
         "%s"                      /* initrwnd */
         "%s"                      /* mtu */
         "%s"                      /* pref */
-        "%s"                      /* r_assume_config_once */
         "%s"                      /* r_force_commit */
         "",
         nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
@@ -6706,7 +6972,6 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
             " pref %s",
             nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2)))
                        : "",
-        route->r_assume_config_once ? " assume-config-once" : "",
         route->r_force_commit ? " force-commit" : "");
 
     return buf;
@@ -7746,8 +8011,8 @@ nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1,
      * 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);
+    n1 = nm_utils_ip4_address_clear_host_address(a1->address, a1->plen);
+    n2 = nm_utils_ip4_address_clear_host_address(a2->address, a2->plen);
     NM_CMP_DIRECT_MEMCMP(&n1, &n2, sizeof(guint32));
     return 0;
 }
@@ -7770,6 +8035,25 @@ _address_pretty_sort_get_prio_6(const struct in6_addr *addr)
     return 6;
 }
 
+static int
+_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b)
+{
+    guint32 lifetime_a;
+    guint32 lifetime_b;
+    guint32 preferred_a;
+    guint32 preferred_b;
+    gint32  now = 0;
+
+    lifetime_a =
+        nmp_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, &now, &preferred_a);
+    lifetime_b =
+        nmp_utils_lifetime_get(b->timestamp, b->lifetime, b->preferred, &now, &preferred_b);
+
+    NM_CMP_DIRECT(lifetime_a, lifetime_b);
+    NM_CMP_DIRECT(preferred_a, preferred_b);
+    return 0;
+}
+
 int
 nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1,
                                         const NMPlatformIP6Address *a2,
@@ -7843,71 +8127,122 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState
                         NM_HASH_COMBINE_BOOLS(guint8,
                                               obj->use_ip4_broadcast_address,
                                               obj->a_acd_not_ready,
-                                              obj->a_assume_config_once,
                                               obj->a_force_commit));
     nm_hash_update_strarr(h, obj->label);
 }
 
 int
-nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
+nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a,
+                            const NMPlatformIP4Address *b,
+                            NMPlatformIPAddressCmpType  cmp_type)
 {
     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, a_acd_not_ready);
-    NM_CMP_FIELD_UNSAFE(a, b, a_assume_config_once);
-    NM_CMP_FIELD_UNSAFE(a, b, a_force_commit);
-    return 0;
+    NM_CMP_FIELD(a, b, address);
+
+    switch (cmp_type) {
+    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID:
+        /* 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(a->peer_address, b->peer_address, a->plen);
+        return 0;
+    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY:
+    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL:
+        NM_CMP_FIELD(a, b, peer_address);
+        NM_CMP_FIELD_STR(a, b, label);
+        if (cmp_type == NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY) {
+            NM_CMP_RETURN(_address_cmp_expiry((const NMPlatformIPAddress *) a,
+                                              (const NMPlatformIPAddress *) b));
+
+            /* Most flags are set by kernel. We only compare the ones that
+             * NetworkManager actively sets.
+             *
+             * NM actively only sets IFA_F_NOPREFIXROUTE (and IFA_F_MANAGETEMPADDR for IPv6),
+             * where nm_platform_ip_address_sync() always sets IFA_F_NOPREFIXROUTE.
+             * There are thus no flags to compare for IPv4. */
+
+            NM_CMP_DIRECT(nm_platform_ip4_broadcast_address_from_addr(a),
+                          nm_platform_ip4_broadcast_address_from_addr(b));
+        } else {
+            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(a, b, addr_source);
+            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_UNSAFE(a, b, a_acd_not_ready);
+            NM_CMP_FIELD_UNSAFE(a, b, a_force_commit);
+        }
+        return 0;
+    }
+    return nm_assert_unreachable_val(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->a_assume_config_once, obj->a_force_commit));
+    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->a_force_commit));
 }
 
 int
-nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
+nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a,
+                            const NMPlatformIP6Address *b,
+                            NMPlatformIPAddressCmpType  cmp_type)
 {
     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, a_assume_config_once);
-    NM_CMP_FIELD_UNSAFE(a, b, a_force_commit);
-    return 0;
+    NM_CMP_FIELD_IN6ADDR(a, b, address);
+
+    switch (cmp_type) {
+    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID:
+        /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
+        return 0;
+    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY:
+    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL:
+        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));
+        if (cmp_type == NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY) {
+            NM_CMP_RETURN(_address_cmp_expiry((const NMPlatformIPAddress *) a,
+                                              (const NMPlatformIPAddress *) b));
+
+            /* Most flags are set by kernel. We only compare the ones that
+             * NetworkManager actively sets.
+             *
+             * NM actively only sets IFA_F_NOPREFIXROUTE and IFA_F_MANAGETEMPADDR,
+             * where nm_platform_ip_address_sync() always sets IFA_F_NOPREFIXROUTE.
+             * We thus only care about IFA_F_MANAGETEMPADDR. */
+            NM_CMP_DIRECT(a->n_ifa_flags & IFA_F_MANAGETEMPADDR,
+                          b->n_ifa_flags & IFA_F_MANAGETEMPADDR);
+        } else {
+            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(a, b, addr_source);
+            NM_CMP_FIELD_UNSAFE(a, b, a_force_commit);
+        }
+        return 0;
+    }
+    return nm_assert_unreachable_val(0);
 }
 
 void
@@ -8007,7 +8342,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
                             obj->initrwnd,
                             obj->mtu,
                             obj->r_rtm_flags,
-                            NM_HASH_COMBINE_BOOLS(guint16,
+                            NM_HASH_COMBINE_BOOLS(guint8,
                                                   obj->metric_any,
                                                   obj->table_any,
                                                   obj->lock_window,
@@ -8015,7 +8350,6 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
                                                   obj->lock_initcwnd,
                                                   obj->lock_initrwnd,
                                                   obj->lock_mtu,
-                                                  obj->r_assume_config_once,
                                                   obj->r_force_commit));
         break;
     }
@@ -8106,10 +8440,8 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
         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_FULL) {
-            NM_CMP_FIELD_UNSAFE(a, b, r_assume_config_once);
+        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
             NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
-        }
         break;
     }
     return 0;
@@ -8202,7 +8534,6 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
                                                   obj->lock_initcwnd,
                                                   obj->lock_initrwnd,
                                                   obj->lock_mtu,
-                                                  obj->r_assume_config_once,
                                                   obj->r_force_commit),
                             obj->window,
                             obj->cwnd,
@@ -8286,10 +8617,8 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
             NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref));
         else
             NM_CMP_FIELD(a, b, rt_pref);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) {
-            NM_CMP_FIELD_UNSAFE(a, b, r_assume_config_once);
+        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
             NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
-        }
         break;
     }
     return 0;
@@ -8651,9 +8980,11 @@ log_link(NMPlatform                *self,
          NMPlatformSignalChangeType change_type,
          gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: link %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_link_to_string(device, NULL, 0));
+           nm_platform_link_to_string(device, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8664,9 +8995,11 @@ log_ip4_address(NMPlatform                *self,
                 NMPlatformSignalChangeType change_type,
                 gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: address 4 %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip4_address_to_string(address, NULL, 0));
+           nm_platform_ip4_address_to_string(address, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8677,9 +9010,11 @@ log_ip6_address(NMPlatform                *self,
                 NMPlatformSignalChangeType change_type,
                 gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: address 6 %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip6_address_to_string(address, NULL, 0));
+           nm_platform_ip6_address_to_string(address, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8690,9 +9025,11 @@ log_ip4_route(NMPlatform                *self,
               NMPlatformSignalChangeType change_type,
               gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: route   4 %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip4_route_to_string(route, NULL, 0));
+           nm_platform_ip4_route_to_string(route, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8703,9 +9040,11 @@ log_ip6_route(NMPlatform                *self,
               NMPlatformSignalChangeType change_type,
               gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: route   6 %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_ip6_route_to_string(route, NULL, 0));
+           nm_platform_ip6_route_to_string(route, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8716,10 +9055,12 @@ log_routing_rule(NMPlatform                *self,
                  NMPlatformSignalChangeType change_type,
                  gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     /* 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));
+           nm_platform_routing_rule_to_string(routing_rule, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8730,9 +9071,11 @@ log_qdisc(NMPlatform                *self,
           NMPlatformSignalChangeType change_type,
           gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: qdisc %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_qdisc_to_string(qdisc, NULL, 0));
+           nm_platform_qdisc_to_string(qdisc, sbuf, sizeof(sbuf)));
 }
 
 static void
@@ -8743,9 +9086,11 @@ log_tfilter(NMPlatform                *self,
             NMPlatformSignalChangeType change_type,
             gpointer                   user_data)
 {
+    char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
     _LOG3D("signal: tfilter %7s: %s",
            nm_platform_signal_change_type_to_string(change_type),
-           nm_platform_tfilter_to_string(tfilter, NULL, 0));
+           nm_platform_tfilter_to_string(tfilter, sbuf, sizeof(sbuf)));
 }
 
 /*****************************************************************************/
@@ -8756,6 +9101,7 @@ nm_platform_cache_update_emit_signal(NMPlatform      *self,
                                      const NMPObject *obj_old,
                                      const NMPObject *obj_new)
 {
+    char             sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     gboolean         visible_new;
     gboolean         visible_old;
     const NMPObject *o;
@@ -8818,7 +9164,7 @@ nm_platform_cache_update_emit_signal(NMPlatform      *self,
     _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_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
 
     nmp_object_ref(o);
     g_signal_emit(self,
@@ -8863,6 +9209,37 @@ nm_platform_netns_push(NMPlatform *self, NMPNetns **netns)
 
 /*****************************************************************************/
 
+const _NMPlatformVTableAddressUnion nm_platform_vtable_address = {
+    .v4 =
+        {
+            .is_ip4         = TRUE,
+            .obj_type       = NMP_OBJECT_TYPE_IP4_ADDRESS,
+            .addr_family    = AF_INET,
+            .sizeof_address = sizeof(NMPlatformIP4Address),
+            .address_cmp =
+                (int (*)(const NMPlatformIPXAddress *a,
+                         const NMPlatformIPXAddress *b,
+                         NMPlatformIPAddressCmpType  cmp_type)) nm_platform_ip4_address_cmp,
+            .address_to_string = (const char *(*) (const NMPlatformIPXAddress *address,
+                                                   char                       *buf,
+                                                   gsize len)) nm_platform_ip4_address_to_string,
+        },
+    .v6 =
+        {
+            .is_ip4         = FALSE,
+            .obj_type       = NMP_OBJECT_TYPE_IP6_ADDRESS,
+            .addr_family    = AF_INET6,
+            .sizeof_address = sizeof(NMPlatformIP6Address),
+            .address_cmp =
+                (int (*)(const NMPlatformIPXAddress *a,
+                         const NMPlatformIPXAddress *b,
+                         NMPlatformIPAddressCmpType  cmp_type)) nm_platform_ip6_address_cmp,
+            .address_to_string = (const char *(*) (const NMPlatformIPXAddress *address,
+                                                   char                       *buf,
+                                                   gsize len)) nm_platform_ip6_address_to_string,
+        },
+};
+
 const _NMPlatformVTableRouteUnion nm_platform_vtable_route = {
     .v4 =
         {
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 6f5cd524..246d6ff6 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -95,6 +95,14 @@ typedef enum {
 } NMPNlmFlags;
 
 typedef enum {
+    NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID,
+
+    NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY,
+
+    NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL,
+} NMPlatformIPAddressCmpType;
+
+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
@@ -259,17 +267,17 @@ struct _NMPlatformLink {
     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,
+typedef enum {
+    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);
@@ -323,8 +331,6 @@ typedef enum {
     /* Meta flags not honored by NMPlatform (netlink code). Instead, they can be
      * used by the upper layers which use NMPlatformIPRoute to track addresses that
      * should be configured. */             \
-    /* Whether the address is should be configured once during assume. */                    \
-    bool a_assume_config_once : 1;                                                           \
     bool a_force_commit : 1;                                                                 \
                                                                                              \
     guint8 plen;                                                                             \
@@ -468,12 +474,9 @@ typedef union {
      * 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;                                                                   \
-                                                                                          \
     /* Meta flags not honored by NMPlatform (netlink code). Instead, they can be
      * used by the upper layers which use NMPlatformIPRoute to track routes that
      * should be configured. */          \
-    /* Whether the route is should be configured once during assume. */                   \
-    bool r_assume_config_once : 1;                                                        \
     /* Whether the route should be committed even if it was removed externally. */        \
     bool r_force_commit : 1;                                                              \
                                                                                           \
@@ -776,10 +779,31 @@ typedef struct {
 #undef __NMPlatformObjWithIfindex_COMMON
 
 typedef struct {
-    gboolean      is_ip4;
+    bool          is_ip4;
+    NMPObjectType obj_type;
+    gint8         addr_family;
+    guint8        sizeof_address;
+    int (*address_cmp)(const NMPlatformIPXAddress *a,
+                       const NMPlatformIPXAddress *b,
+                       NMPlatformIPAddressCmpType  cmp_type);
+    const char *(*address_to_string)(const NMPlatformIPXAddress *address, char *buf, gsize len);
+} NMPlatformVTableAddress;
+
+typedef union {
+    struct {
+        NMPlatformVTableAddress v6;
+        NMPlatformVTableAddress v4;
+    };
+    NMPlatformVTableAddress vx[2];
+} _NMPlatformVTableAddressUnion;
+
+extern const _NMPlatformVTableAddressUnion nm_platform_vtable_address;
+
+typedef struct {
+    bool          is_ip4;
+    gint8         addr_family;
+    guint8        sizeof_route;
     NMPObjectType obj_type;
-    int           addr_family;
-    gsize         sizeof_route;
     int (*route_cmp)(const NMPlatformIPXRoute *a,
                      const NMPlatformIPXRoute *b,
                      NMPlatformIPRouteCmpType  cmp_type);
@@ -1017,6 +1041,14 @@ typedef void (*NMPlatformAsyncCallback)(GError *error, gpointer user_data);
 
 /*****************************************************************************/
 
+typedef struct _NMPlatformCsmeConnInfo {
+    guint8      ssid[32];
+    guint32     channel;
+    NMEtherAddr addr;
+    guint8      sta_cipher;
+    guint8      auth_mode;
+} NMPlatformCsmeConnInfo;
+
 typedef enum {
     NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_L3MDEV,
     NM_PLATFORM_KERNEL_SUPPORT_TYPE_FRA_UID_RANGE,
@@ -1105,6 +1137,9 @@ typedef struct {
                     guint32                mtu,
                     gconstpointer          extra_data,
                     const NMPlatformLink **out_link);
+
+    int (*link_change)(NMPlatform *self, NMLinkType type, int ifindex, gconstpointer extra_data);
+
     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);
@@ -1204,6 +1239,10 @@ typedef struct {
     gboolean (*wifi_set_wake_on_wlan)(NMPlatform                  *self,
                                       int                          ifindex,
                                       _NMSettingWirelessWakeOnWLan wowl);
+    gboolean (*wifi_get_csme_conn_info)(NMPlatform             *self,
+                                        int                     ifindex,
+                                        NMPlatformCsmeConnInfo *out_conn_info);
+    gboolean (*wifi_get_device_from_csme)(NMPlatform *self, int ifindex);
 
     guint32 (*mesh_get_channel)(NMPlatform *self, int ifindex);
     gboolean (*mesh_set_channel)(NMPlatform *self, int ifindex, guint32 channel);
@@ -1411,7 +1450,11 @@ _nm_platform_link_get_inet6_addr_gen_mode(const NMPlatformLink *pllink)
 static inline gboolean
 nm_platform_route_type_is_nodev(guint8 type)
 {
-    return NM_IN_SET(type, 6 /* RTN_BLACKHOLE */, 7 /* RTN_UNREACHABLE */, 8 /* RTN_PROHIBIT */);
+    return NM_IN_SET(type,
+                     6 /* RTN_BLACKHOLE */,
+                     7 /* RTN_UNREACHABLE */,
+                     8 /* RTN_PROHIBIT */,
+                     9 /* RTN_THROW */);
 }
 
 /**
@@ -1570,6 +1613,9 @@ int nm_platform_link_add(NMPlatform            *self,
                          gconstpointer          extra_data,
                          const NMPlatformLink **out_link);
 
+int
+nm_platform_link_change(NMPlatform *self, NMLinkType type, int ifindex, gconstpointer extra_data);
+
 static inline int
 nm_platform_link_veth_add(NMPlatform            *self,
                           const char            *name,
@@ -1606,6 +1652,12 @@ nm_platform_link_bridge_add(NMPlatform                *self,
 }
 
 static inline int
+nm_platform_link_bridge_change(NMPlatform *self, int ifindex, const NMPlatformLnkBridge *props)
+{
+    return nm_platform_link_change(self, NM_LINK_TYPE_BRIDGE, ifindex, props);
+}
+
+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);
@@ -2024,6 +2076,10 @@ void nm_platform_wifi_indicate_addressing_running(NMPlatform *self, int ifindex,
 _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);
+gboolean nm_platform_wifi_get_csme_conn_info(NMPlatform             *self,
+                                             int                     ifindex,
+                                             NMPlatformCsmeConnInfo *out_conn_info);
+gboolean nm_platform_wifi_get_device_from_csme(NMPlatform *self, int ifindex);
 
 guint32  nm_platform_mesh_get_channel(NMPlatform *self, int ifindex);
 gboolean nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel);
@@ -2038,6 +2094,11 @@ gboolean nm_platform_wpan_set_channel(NMPlatform *self, int ifindex, guint8 page
 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 NMPObject *nm_platform_ip_address_get(NMPlatform                                 *self,
+                                            int                                         addr_family,
+                                            int                                         ifindex,
+                                            gconstpointer /* (NMPlatformIPAddress *) */ needle);
+
 const NMPlatformIP4Address *nm_platform_ip4_address_get(NMPlatform *self,
                                                         int         ifindex,
                                                         in_addr_t   address,
@@ -2097,6 +2158,29 @@ gboolean nm_platform_ip4_address_delete(NMPlatform *self,
 gboolean
 nm_platform_ip6_address_delete(NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
 
+static inline gboolean
+nm_platform_ip_address_delete(NMPlatform                                       *self,
+                              int                                               addr_family,
+                              int                                               ifindex,
+                              gconstpointer /* (const NMPlatformIPAddress *) */ addr)
+{
+    if (NM_IS_IPv4(addr_family)) {
+        const NMPlatformIP4Address *a = addr;
+
+        if (ifindex <= 0)
+            ifindex = a->ifindex;
+
+        return nm_platform_ip4_address_delete(self, ifindex, a->address, a->plen, a->peer_address);
+    } else {
+        const NMPlatformIP6Address *a = addr;
+
+        if (ifindex <= 0)
+            ifindex = a->ifindex;
+
+        return nm_platform_ip6_address_delete(self, ifindex, a->address, a->plen);
+    }
+}
+
 gboolean nm_platform_ip_address_sync(NMPlatform *self,
                                      int         addr_family,
                                      int         ifindex,
@@ -2288,8 +2372,24 @@ int nm_platform_lnk_vlan_cmp(const NMPlatformLnkVlan *a, const NMPlatformLnkVlan
 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_cmp(const NMPlatformIP4Address *a,
+                                const NMPlatformIP4Address *b,
+                                NMPlatformIPAddressCmpType  cmp_type);
+int nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a,
+                                const NMPlatformIP6Address *b,
+                                NMPlatformIPAddressCmpType  cmp_type);
+
+static inline int
+nm_platform_ip4_address_cmp_full(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
+{
+    return nm_platform_ip4_address_cmp(a, b, NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL);
+}
+
+static inline int
+nm_platform_ip6_address_cmp_full(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
+{
+    return nm_platform_ip6_address_cmp(a, b, NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL);
+}
 
 int nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1,
                                             const NMPlatformIP4Address *a2);
diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h
index a80fd4d3..48631688 100644
--- a/src/libnm-platform/nmp-base.h
+++ b/src/libnm-platform/nmp-base.h
@@ -110,7 +110,7 @@ typedef struct _NMPlatformIP6Route       NMPlatformIP6Route;
 typedef struct _NMPlatformLink           NMPlatformLink;
 typedef struct _NMPObject                NMPObject;
 
-typedef enum {
+typedef enum _nm_packed {
     NMP_OBJECT_TYPE_UNKNOWN,
     NMP_OBJECT_TYPE_LINK,
 
diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c
index d518e6e5..6c177b4c 100644
--- a/src/libnm-platform/nmp-object.c
+++ b/src/libnm-platform/nmp-object.c
@@ -799,7 +799,7 @@ nmp_object_new_link(int ifindex)
 
 /*****************************************************************************/
 
-static void
+static NMPObject *
 _nmp_object_stackinit_from_class(NMPObject *obj, const NMPClass *klass)
 {
     nm_assert(obj);
@@ -812,25 +812,13 @@ _nmp_object_stackinit_from_class(NMPObject *obj, const NMPClass *klass)
                 ._ref_count = NM_OBJ_REF_COUNT_STACKINIT,
             },
     };
+    return obj;
 }
 
 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;
+    return _nmp_object_stackinit_from_class(obj, nmp_class_from_type(obj_type));
 }
 
 const NMPObject *
@@ -901,7 +889,7 @@ nmp_object_to_string(const NMPObject      *obj,
                      gsize                 buf_size)
 {
     const NMPClass *klass;
-    char            buf2[sizeof(_nm_utils_to_string_buffer)];
+    char            buf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
 
     if (!nm_utils_to_string_buffer_init_null(obj, &buf, &buf_size))
         return buf;
@@ -992,7 +980,7 @@ _vt_cmd_obj_to_string_lnk_vlan(const NMPObject      *obj,
                                gsize                 buf_size)
 {
     const NMPClass *klass;
-    char            buf2[sizeof(_nm_utils_to_string_buffer)];
+    char            buf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
     char           *b;
     gsize           l;
 
@@ -1056,7 +1044,7 @@ _vt_cmd_obj_to_string_lnk_wireguard(const NMPObject      *obj,
                                     gsize                 buf_size)
 {
     const NMPClass *klass;
-    char            buf2[sizeof(_nm_utils_to_string_buffer)];
+    char            buf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
     char           *b;
     guint           i;
 
@@ -1523,20 +1511,21 @@ nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2)
 
 _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);
-});
+static int
+_vt_cmd_plobj_id_cmp_ip4_address(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
+{
+    return nm_platform_ip4_address_cmp((const NMPlatformIP4Address *) obj1,
+                                       (const NMPlatformIP4Address *) obj2,
+                                       NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID);
+}
 
-_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);
-});
+static int
+_vt_cmd_plobj_id_cmp_ip6_address(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
+{
+    return nm_platform_ip6_address_cmp((const NMPlatformIP6Address *) obj1,
+                                       (const NMPlatformIP6Address *) obj2,
+                                       NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID);
+}
 
 _vt_cmd_plobj_id_cmp(qdisc, NMPlatformQdisc, {
     NM_CMP_FIELD(obj1, obj2, ifindex);
@@ -1551,24 +1540,24 @@ _vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, {
 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,
+    return nm_platform_ip4_route_cmp((const NMPlatformIP4Route *) obj1,
+                                     (const 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,
+    return nm_platform_ip6_route_cmp((const NMPlatformIP6Route *) obj1,
+                                     (const 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,
+    return nm_platform_routing_rule_cmp((const NMPlatformRoutingRule *) obj1,
+                                        (const NMPlatformRoutingRule *) obj2,
                                         NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID);
 }
 
@@ -3158,28 +3147,29 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
             .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,
+            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_ip4_address_cmp_full,
+        },
+    [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_full,
         },
-    [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(),
diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h
index 784dcc2d..d6b88ba2 100644
--- a/src/libnm-platform/nmp-object.h
+++ b/src/libnm-platform/nmp-object.h
@@ -91,17 +91,17 @@ typedef struct _NMPWireGuardPeer {
 
 /*****************************************************************************/
 
-typedef enum { /*< skip >*/
-               NMP_OBJECT_TO_STRING_ID,
-               NMP_OBJECT_TO_STRING_PUBLIC,
-               NMP_OBJECT_TO_STRING_ALL,
+typedef enum {
+    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,
+typedef enum {
+    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.
@@ -1098,21 +1098,6 @@ nm_platform_lookup_object_by_addr_family(NMPlatform   *platform,
 /*****************************************************************************/
 
 static inline gboolean
-nmp_object_get_assume_config_once(const NMPObject *obj)
-{
-    switch (NMP_OBJECT_GET_TYPE(obj)) {
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-        return NMP_OBJECT_CAST_IP_ADDRESS(obj)->a_assume_config_once;
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        return NMP_OBJECT_CAST_IP_ROUTE(obj)->r_assume_config_once;
-    default:
-        return nm_assert_unreachable_val(FALSE);
-    }
-}
-
-static inline gboolean
 nmp_object_get_force_commit(const NMPObject *obj)
 {
     switch (NMP_OBJECT_GET_TYPE(obj)) {
diff --git a/src/libnm-platform/nmp-route-manager.c b/src/libnm-platform/nmp-route-manager.c
index c31c9806..842301b1 100644
--- a/src/libnm-platform/nmp-route-manager.c
+++ b/src/libnm-platform/nmp-route-manager.c
@@ -397,14 +397,17 @@ nmp_route_manager_track(NMPRouteManager *self,
     _track_data_assert(track_data, TRUE);
 
     if (changed) {
-        _LOGD("track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] %s \"%s\"",
-              NM_HASH_OBFUSCATE_PTR(track_data->user_tag),
-              (track_data->track_priority_val == 0
-                   ? ""
-                   : (track_data->track_priority_present ? "+" : "-")),
-              (guint) track_data->track_priority_val,
-              NMP_OBJECT_GET_CLASS(track_data->obj)->obj_type_name,
-              nmp_object_to_string(track_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+        char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
+        _LOGD(
+            "track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] %s \"%s\"",
+            NM_HASH_OBFUSCATE_PTR(track_data->user_tag),
+            (track_data->track_priority_val == 0
+                 ? ""
+                 : (track_data->track_priority_present ? "+" : "-")),
+            (guint) track_data->track_priority_val,
+            NMP_OBJECT_GET_CLASS(track_data->obj)->obj_type_name,
+            nmp_object_to_string(track_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
     }
 
     return changed || changed_untrack;
@@ -416,6 +419,7 @@ _track_data_untrack(NMPRouteManager *self,
                     gboolean         remove_user_tag_data,
                     gboolean         make_owned_by_us)
 {
+    char          sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     TrackObjData *obj_data;
 
     nm_assert(NMP_IS_ROUTE_MANAGER(self));
@@ -426,7 +430,7 @@ _track_data_untrack(NMPRouteManager *self,
     _LOGD("untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] %s \"%s\"",
           NM_HASH_OBFUSCATE_PTR(track_data->user_tag),
           NMP_OBJECT_GET_CLASS(track_data->obj)->obj_type_name,
-          nmp_object_to_string(track_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+          nmp_object_to_string(track_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
 
 #if NM_MORE_ASSERTS
     {
@@ -552,6 +556,7 @@ nmp_route_manager_untrack_all(NMPRouteManager *self,
 void
 nmp_route_manager_sync(NMPRouteManager *self, NMPObjectType obj_type, gboolean keep_deleted)
 {
+    char                         sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     const NMDedupMultiHeadEntry *pl_head_entry;
     NMDedupMultiIter             pl_iter;
     const NMPObject             *plobj;
@@ -608,7 +613,7 @@ nmp_route_manager_sync(NMPRouteManager *self, NMPObjectType obj_type, gboolean k
             if (keep_deleted) {
                 _LOGD("forget/leak object added by us: %s \"%s\"",
                       NMP_OBJECT_GET_CLASS(plobj)->obj_type_name,
-                      nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+                      nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
                 continue;
             }
 
diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c
index d4de0dd5..9ac69bde 100644
--- a/src/libnm-platform/tests/test-nm-platform.c
+++ b/src/libnm-platform/tests/test-nm-platform.c
@@ -131,11 +131,13 @@ test_nmp_link_mode_all_advertised_modes_bits(void)
 
     for (i = 0; i < (int) G_N_ELEMENTS(_nmp_link_mode_all_advertised_modes); i++) {
         if (flags[i] != _nmp_link_mode_all_advertised_modes[i]) {
+            NM_PRAGMA_WARNING_DISABLE_DANGLING_POINTER
             g_error("_nmp_link_mode_all_advertised_modes[%d] should be 0x%0x but is 0x%0x "
                     "(according to the bits in _nmp_link_mode_all_advertised_modes_bits)",
                     i,
                     flags[i],
                     _nmp_link_mode_all_advertised_modes[i]);
+            NM_PRAGMA_WARNING_REENABLE
         }
     }
 }
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
index 3906384b..475b8a39 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
@@ -15,6 +15,8 @@
 #include <linux/nl80211.h>
 #include <linux/if.h>
 
+#include "linux-headers/nl80211-vnd-intel.h"
+
 #include "libnm-log-core/nm-logging.h"
 #include "libnm-platform/nm-netlink.h"
 #include "nm-wifi-utils-private.h"
@@ -384,8 +386,10 @@ wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs)
     int                 i;
     int                 j;
 
-    for (i = 0; i < self->num_freqs; i++) {
-        for (j = 0; freqs[j] != 0; j++) {
+    /* It's important to check the values in the order of @freqs, because
+     * that array might be sorted to contain preferred frequencies first. */
+    for (j = 0; freqs[j] != 0; j++) {
+        for (i = 0; i < self->num_freqs; i++) {
             if (self->freqs[i] == freqs[j])
                 return freqs[j];
         }
@@ -597,7 +601,7 @@ nl80211_wiphy_info_handler(struct nl_msg *msg, void *arg)
     struct nlattr              *nl_freq;
     int                         rem_freq;
     int                         rem_band;
-    int                         freq_idx;
+    guint                       num_alloc;
 
 #ifdef NL80211_FREQUENCY_ATTR_NO_IR
     G_STATIC_ASSERT_EXPR(NL80211_FREQUENCY_ATTR_PASSIVE_SCAN == NL80211_FREQUENCY_ATTR_NO_IR
@@ -651,8 +655,10 @@ nl80211_wiphy_info_handler(struct nl_msg *msg, void *arg)
         }
     }
 
-    /* Find number of supported frequencies */
+    /* Read supported frequencies */
+    num_alloc       = 32;
     info->num_freqs = 0;
+    info->freqs     = g_new(guint32, num_alloc);
 
     nla_for_each_nested (nl_band, tb[NL80211_ATTR_WIPHY_BANDS], rem_band) {
         if (nla_parse_nested_arr(tb_band, nl_band, NULL) < 0)
@@ -665,38 +671,28 @@ nl80211_wiphy_info_handler(struct nl_msg *msg, void *arg)
             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])
+            if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED])
                 continue;
 
-            info->freqs[freq_idx] = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]);
+            if (info->num_freqs >= num_alloc) {
+                num_alloc *= 2;
+                info->freqs = g_renew(guint32, info->freqs, num_alloc);
+            }
 
+            info->freqs[info->num_freqs] = 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)
+            if (info->freqs[info->num_freqs] > 2400 && info->freqs[info->num_freqs] < 2500)
                 info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_2GHZ;
-            if (info->freqs[freq_idx] > 4900 && info->freqs[freq_idx] < 6000)
+            if (info->freqs[info->num_freqs] > 4900 && info->freqs[info->num_freqs] < 6000)
                 info->caps |= _NM_WIFI_DEVICE_CAP_FREQ_5GHZ;
 
-            freq_idx++;
+            info->num_freqs++;
         }
     }
 
+    info->freqs = g_renew(guint32, info->freqs, info->num_freqs);
+
     /* Read security/encryption support */
     if (tb[NL80211_ATTR_CIPHER_SUITES]) {
         guint32 *ciphers = nla_data(tb[NL80211_ATTR_CIPHER_SUITES]);
@@ -815,6 +811,111 @@ nla_put_failure:
     g_return_val_if_reached(FALSE);
 }
 
+struct nl80211_csme_conn_info {
+    NMWifiUtilsNl80211     *self;
+    NMPlatformCsmeConnInfo *conn_info;
+};
+
+static int
+nl80211_csme_conn_event_handler(struct nl_msg *msg, void *arg)
+{
+    struct nl80211_csme_conn_info *info          = arg;
+    NMPlatformCsmeConnInfo        *out_conn_info = info->conn_info;
+    NMWifiUtilsNl80211            *self          = info->self;
+    struct genlmsghdr             *gnlh          = (void *) nlmsg_data(nlmsg_hdr(msg));
+    struct nlattr                 *tb[NL80211_ATTR_MAX + 1];
+    struct nlattr                 *data;
+    struct nlattr                 *attrs[NUM_IWL_MVM_VENDOR_ATTR];
+    int                            err;
+
+    static const struct nla_policy iwl_vendor_policy[NUM_IWL_MVM_VENDOR_ATTR] = {
+        [IWL_MVM_VENDOR_ATTR_AUTH_MODE]   = {.type = NLA_U32},
+        [IWL_MVM_VENDOR_ATTR_SSID]        = {.type = NLA_UNSPEC, .maxlen = NM_IW_ESSID_MAX_SIZE},
+        [IWL_MVM_VENDOR_ATTR_STA_CIPHER]  = {.type = NLA_U32},
+        [IWL_MVM_VENDOR_ATTR_CHANNEL_NUM] = {.type = NLA_U8},
+        [IWL_MVM_VENDOR_ATTR_ADDR] = {.type = NLA_UNSPEC, .minlen = ETH_ALEN, .maxlen = ETH_ALEN},
+    };
+
+    nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
+    data = tb[NL80211_ATTR_VENDOR_DATA];
+
+    *out_conn_info = (NMPlatformCsmeConnInfo){};
+
+    err = nla_parse_nested(attrs, MAX_IWL_MVM_VENDOR_ATTR, data, iwl_vendor_policy);
+    if (err) {
+        _LOGD("IWL_MVM_VENDOR_CMD_GET_CSME_CONN_INFO Failed to parse CSME connection info: %s",
+              nm_strerror(err));
+        return -EINVAL;
+    }
+
+    if (attrs[IWL_MVM_VENDOR_ATTR_AUTH_MODE])
+        out_conn_info->auth_mode = nla_get_u8(attrs[IWL_MVM_VENDOR_ATTR_AUTH_MODE]);
+
+    if (attrs[IWL_MVM_VENDOR_ATTR_SSID])
+        memcpy(out_conn_info->ssid,
+               nla_data(attrs[IWL_MVM_VENDOR_ATTR_SSID]),
+               nla_len(attrs[IWL_MVM_VENDOR_ATTR_SSID]));
+
+    if (attrs[IWL_MVM_VENDOR_ATTR_STA_CIPHER])
+        out_conn_info->sta_cipher = nla_get_u8(attrs[IWL_MVM_VENDOR_ATTR_STA_CIPHER]);
+
+    if (attrs[IWL_MVM_VENDOR_ATTR_CHANNEL_NUM])
+        out_conn_info->channel = nla_get_u8(attrs[IWL_MVM_VENDOR_ATTR_CHANNEL_NUM]);
+
+    if (attrs[IWL_MVM_VENDOR_ATTR_ADDR])
+        memcpy(&out_conn_info->addr,
+               nla_data(attrs[IWL_MVM_VENDOR_ATTR_ADDR]),
+               sizeof(out_conn_info->addr));
+
+    return NL_SKIP;
+}
+
+static gboolean
+wifi_nl80211_intel_vnd_get_csme_conn_info(NMWifiUtils *data, NMPlatformCsmeConnInfo *out_conn_info)
+{
+    NMWifiUtilsNl80211           *self = (NMWifiUtilsNl80211 *) data;
+    nm_auto_nlmsg struct nl_msg  *msg  = NULL;
+    int                           err;
+    struct nl80211_csme_conn_info conn_info = {
+        .self      = self,
+        .conn_info = out_conn_info,
+    };
+
+    msg = nl80211_alloc_msg(self, NL80211_CMD_VENDOR, 0);
+    NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_ID, INTEL_OUI);
+    NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_SUBCMD, IWL_MVM_VENDOR_CMD_GET_CSME_CONN_INFO);
+
+    err = nl80211_send_and_recv(self, msg, nl80211_csme_conn_event_handler, &conn_info);
+    if (err < 0)
+        _LOGD("IWL_MVM_VENDOR_CMD_GET_CSME_CONN_INFO request failed: %s", nm_strerror(err));
+
+    return err >= 0;
+
+nla_put_failure:
+    g_return_val_if_reached(FALSE);
+}
+
+static gboolean
+wifi_nl80211_intel_vnd_get_device_from_csme(NMWifiUtils *data)
+{
+    NMWifiUtilsNl80211          *self = (NMWifiUtilsNl80211 *) data;
+    nm_auto_nlmsg struct nl_msg *msg  = NULL;
+    int                          err;
+
+    msg = nl80211_alloc_msg(self, NL80211_CMD_VENDOR, 0);
+    NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_ID, INTEL_OUI);
+    NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_SUBCMD, IWL_MVM_VENDOR_CMD_HOST_GET_OWNERSHIP);
+
+    err = nl80211_send_and_recv(self, msg, NULL, NULL);
+    if (err < 0)
+        _LOGD("IWL_MVM_VENDOR_CMD_HOST_GET_OWNERSHIP request failed: %s", nm_strerror(err));
+
+    return err >= 0;
+
+nla_put_failure:
+    g_return_val_if_reached(FALSE);
+}
+
 static void
 nm_wifi_utils_nl80211_init(NMWifiUtilsNl80211 *self)
 {}
@@ -839,6 +940,8 @@ nm_wifi_utils_nl80211_class_init(NMWifiUtilsNl80211Class *klass)
     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;
+    wifi_utils_class->get_csme_conn_info          = wifi_nl80211_intel_vnd_get_csme_conn_info;
+    wifi_utils_class->get_device_from_csme        = wifi_nl80211_intel_vnd_get_device_from_csme;
 }
 
 NMWifiUtils *
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-private.h b/src/libnm-platform/wifi/nm-wifi-utils-private.h
index 7461b65e..7fe157f2 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-private.h
+++ b/src/libnm-platform/wifi/nm-wifi-utils-private.h
@@ -7,6 +7,7 @@
 #define __WIFI_UTILS_PRIVATE_H__
 
 #include "nm-wifi-utils.h"
+#include "libnm-platform/nm-platform.h"
 
 typedef struct {
     GObjectClass parent;
@@ -53,6 +54,10 @@ typedef struct {
     gboolean (*set_mesh_ssid)(NMWifiUtils *data, const guint8 *ssid, gsize len);
 
     gboolean (*indicate_addressing_running)(NMWifiUtils *data, gboolean running);
+
+    gboolean (*get_csme_conn_info)(NMWifiUtils *data, NMPlatformCsmeConnInfo *out_conn_info);
+
+    gboolean (*get_device_from_csme)(NMWifiUtils *data);
 } NMWifiUtilsClass;
 
 struct NMWifiUtils {
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-wext.c b/src/libnm-platform/wifi/nm-wifi-utils-wext.c
index 8d0e6ed0..678d71fe 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-wext.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-wext.c
@@ -255,8 +255,10 @@ wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs)
     guint            i;
     guint            j;
 
-    for (i = 0; i < wext->num_freqs; i++) {
-        for (j = 0; freqs[j] != 0; j++) {
+    /* It's important to check the values in the order of @freqs, because
+     * that array might be sorted to contain preferred frequencies first. */
+    for (j = 0; freqs[j] != 0; j++) {
+        for (i = 0; i < wext->num_freqs; i++) {
             if (wext->freqs[i] == freqs[j])
                 return freqs[j];
         }
diff --git a/src/libnm-platform/wifi/nm-wifi-utils.c b/src/libnm-platform/wifi/nm-wifi-utils.c
index 8bdd65c2..08a8ec4f 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils.c
@@ -60,6 +60,7 @@ _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);
 }
 
@@ -118,6 +119,7 @@ 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);
 }
 
@@ -126,6 +128,7 @@ 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);
 }
 
@@ -154,6 +157,28 @@ nm_wifi_utils_is_wifi(int dirfd, const char *ifname)
     return FALSE;
 }
 
+gboolean
+nm_wifi_utils_get_csme_conn_info(NMWifiUtils *data, NMPlatformCsmeConnInfo *out_conn_info)
+{
+    NMWifiUtilsClass *klass;
+
+    g_return_val_if_fail(data != NULL, FALSE);
+
+    klass = NM_WIFI_UTILS_GET_CLASS(data);
+    return klass->get_csme_conn_info ? klass->get_csme_conn_info(data, out_conn_info) : FALSE;
+}
+
+gboolean
+nm_wifi_utils_get_device_from_csme(NMWifiUtils *data)
+{
+    NMWifiUtilsClass *klass;
+
+    g_return_val_if_fail(data != NULL, FALSE);
+
+    klass = NM_WIFI_UTILS_GET_CLASS(data);
+    return klass->get_device_from_csme ? klass->get_device_from_csme(data) : FALSE;
+}
+
 /* OLPC Mesh-only functions */
 
 guint32
diff --git a/src/libnm-platform/wifi/nm-wifi-utils.h b/src/libnm-platform/wifi/nm-wifi-utils.h
index be33b90c..0d30c1a1 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils.h
+++ b/src/libnm-platform/wifi/nm-wifi-utils.h
@@ -63,6 +63,12 @@ _NMSettingWirelessWakeOnWLan nm_wifi_utils_get_wake_on_wlan(NMWifiUtils *data);
 
 gboolean nm_wifi_utils_set_wake_on_wlan(NMWifiUtils *data, _NMSettingWirelessWakeOnWLan wowl);
 
+struct _NMPlatformCsmeConnInfo;
+gboolean nm_wifi_utils_get_csme_conn_info(NMWifiUtils                    *data,
+                                          struct _NMPlatformCsmeConnInfo *out_conn_info);
+
+gboolean nm_wifi_utils_get_device_from_csme(NMWifiUtils *data);
+
 /* OLPC Mesh-only functions */
 guint32 nm_wifi_utils_get_mesh_channel(NMWifiUtils *data);