about summary refs log tree commit diff
path: root/src/core/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-01-25 09:46:18 +0100
committerMichael Biebl <biebl@debian.org>2024-01-25 09:46:18 +0100
commit70e18d99b8e3e77bb37e218d7ac582130156f8ef (patch)
treed40c587e6d3f0e094ff558e415f1bb9803643214 /src/core/devices
parentd4d8b2b91f7ba000d97a8b2aab48c85000c11314 (diff)
New upstream version 1.45.90 upstream/1.45.90
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c6
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c6
-rw-r--r--src/core/devices/nm-device-6lowpan.c6
-rw-r--r--src/core/devices/nm-device-bond.c25
-rw-r--r--src/core/devices/nm-device-bridge.c195
-rw-r--r--src/core/devices/nm-device-dummy.c8
-rw-r--r--src/core/devices/nm-device-ethernet.c42
-rw-r--r--src/core/devices/nm-device-factory.c1
-rw-r--r--src/core/devices/nm-device-generic.c6
-rw-r--r--src/core/devices/nm-device-hsr.c287
-rw-r--r--src/core/devices/nm-device-hsr.h31
-rw-r--r--src/core/devices/nm-device-infiniband.c12
-rw-r--r--src/core/devices/nm-device-tun.c8
-rw-r--r--src/core/devices/nm-device-vlan.c12
-rw-r--r--src/core/devices/nm-device-vxlan.c6
-rw-r--r--src/core/devices/nm-device-wireguard.c4
-rw-r--r--src/core/devices/nm-device-wpan.c8
-rw-r--r--src/core/devices/nm-device.c535
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c6
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c340
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c6
-rw-r--r--src/core/devices/team/nm-device-team.c22
-rw-r--r--src/core/devices/wifi/nm-device-iwd-p2p.c6
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c6
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c89
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c35
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c9
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c41
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.h3
-rw-r--r--src/core/devices/wifi/nm-wifi-common.c11
30 files changed, 1333 insertions, 439 deletions
diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index 89bc84d8..ba605077 100644
--- a/src/core/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -683,7 +683,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_adsl = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_ADSL,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index 8b13e97d..ce110aa0 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -1306,7 +1306,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_bluetooth = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_BLUETOOTH,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Name", "s", NM_DEVICE_BT_NAME),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("BtCapabilities",
                                                            "u",
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index f3386ddb..78ec634c 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -212,7 +212,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_6lowpan = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_6LOWPAN,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT), ), ),
 };
 
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 10fe8092..a3467930 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -248,9 +248,9 @@ controller_update_port_connection(NMDevice     *self,
                      NULL);
 
     g_object_set(nm_connection_get_setting_connection(connection),
-                 NM_SETTING_CONNECTION_MASTER,
+                 NM_SETTING_CONNECTION_CONTROLLER,
                  nm_connection_get_uuid(applied_connection),
-                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_CONNECTION_PORT_TYPE,
                  NM_SETTING_BOND_SETTING_NAME,
                  NULL);
     return TRUE;
@@ -292,7 +292,7 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new
                 }
             }
 
-            if (nm_strv_find_first(new_strv, i, s) < 0)
+            if (!nm_strv_contains(new_strv, i, s))
                 new_strv[j++] = s;
         }
         new_strv[j] = NULL;
@@ -683,6 +683,7 @@ commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_
                                 .prio     = prio_has ? prio : 0,
                                 .prio_has = prio_has,
                             }),
+                            NULL,
                             0);
 }
 
@@ -928,9 +929,21 @@ static const NMDBusInterfaceInfoExtended interface_info_device_bond = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_BOND,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Slaves",
+                "ao",
+                NM_DEVICE_SLAVES,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index 9a45dbf3..193b655c 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -421,22 +421,6 @@ static const Option master_options[] = {
         0,
     }};
 
-static const Option slave_options[] = {
-    OPTION(NM_SETTING_BRIDGE_PORT_PRIORITY,
-           "priority",
-           OPTION_TYPE_INT(NM_BRIDGE_PORT_PRIORITY_MIN,
-                           NM_BRIDGE_PORT_PRIORITY_MAX,
-                           NM_BRIDGE_PORT_PRIORITY_DEF),
-           .default_if_zero = TRUE, ),
-    OPTION(NM_SETTING_BRIDGE_PORT_PATH_COST,
-           "path_cost",
-           OPTION_TYPE_INT(NM_BRIDGE_PORT_PATH_COST_MIN,
-                           NM_BRIDGE_PORT_PATH_COST_MAX,
-                           NM_BRIDGE_PORT_PATH_COST_DEF),
-           .default_if_zero = TRUE, ),
-    OPTION(NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode", OPTION_TYPE_BOOL(FALSE), ),
-    {0}};
-
 static const NMPlatformBridgeVlan **
 setting_vlans_to_platform(GPtrArray *array)
 {
@@ -473,90 +457,26 @@ setting_vlans_to_platform(GPtrArray *array)
 static void
 commit_port_options(NMDevice *device, NMSettingBridgePort *setting)
 {
-    const Option              *option;
-    NMSetting                 *s;
-    gs_unref_object NMSetting *s_clear = NULL;
-    int                        ifindex = nm_device_get_ifindex(device);
-
-    if (setting)
-        s = NM_SETTING(setting);
-    else
-        s = s_clear = nm_setting_bridge_port_new();
-
-    for (option = slave_options; option->name; option++) {
-        nm_auto_unset_gvalue GValue val = G_VALUE_INIT;
-        GParamSpec                 *pspec;
-        const char                 *value;
-        char                        value_buf[100];
-
-        pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(s), option->name);
-        nm_assert(pspec);
-
-        g_value_init(&val, G_PARAM_SPEC_VALUE_TYPE(pspec));
-        g_object_get_property((GObject *) s, option->name, &val);
-
-        if (option->to_sysfs) {
-            value = option->to_sysfs(&val);
-            goto out;
-        }
-
-        switch (pspec->value_type) {
-        case G_TYPE_BOOLEAN:
-            value = g_value_get_boolean(&val) ? "1" : "0";
-            break;
-        case G_TYPE_UINT64:
-        case G_TYPE_UINT:
-        {
-            guint64 uval;
-
-            if (pspec->value_type == G_TYPE_UINT64)
-                uval = g_value_get_uint64(&val);
-            else
-                uval = (guint) g_value_get_uint(&val);
-
-            /* zero means "unspecified" for some NM properties but isn't in the
-             * allowed kernel range, so reset the property to the default value.
-             */
-            if (option->default_if_zero && uval == 0) {
-                if (pspec->value_type == G_TYPE_UINT64)
-                    uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(pspec);
-                else
-                    uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT(pspec);
-            }
-
-            /* Linux kernel bridge interfaces use 'centiseconds' for time-based values.
-             * In reality it's not centiseconds, but depends on HZ and USER_HZ, which
-             * is almost always works out to be a multiplier of 100, so we can assume
-             * centiseconds.  See clock_t_to_jiffies().
-             */
-            if (option->user_hz_compensate)
-                uval *= 100;
-
-            if (pspec->value_type == G_TYPE_UINT64)
-                nm_sprintf_buf(value_buf, "%" G_GUINT64_FORMAT, uval);
-            else
-                nm_sprintf_buf(value_buf, "%u", (guint) uval);
-
-            value = value_buf;
-        } break;
-        case G_TYPE_STRING:
-            value = g_value_get_string(&val);
-            break;
-        default:
-            nm_assert_not_reached();
-            value = NULL;
-            break;
-        }
-
-out:
-        if (!value)
-            return;
-
-        nm_platform_sysctl_slave_set_option(nm_device_get_platform(device),
-                                            ifindex,
-                                            option->sysname,
-                                            value);
-    }
+    guint32 path_cost, priority;
+
+    path_cost = nm_setting_bridge_port_get_path_cost(setting);
+    if (path_cost == 0)
+        path_cost = NM_BRIDGE_PORT_PATH_COST_DEF;
+
+    priority = nm_setting_bridge_port_get_priority(setting);
+    if (priority == 0)
+        priority = NM_BRIDGE_PORT_PRIORITY_DEF;
+
+    nm_platform_link_change(nm_device_get_platform(device),
+                            nm_device_get_ifindex(device),
+                            NULL,
+                            NULL,
+                            &((NMPlatformLinkBridgePort){
+                                .path_cost = path_cost,
+                                .priority  = priority,
+                                .hairpin   = nm_setting_bridge_port_get_hairpin_mode(setting),
+                            }),
+                            0);
 }
 
 static void
@@ -662,50 +582,37 @@ master_update_slave_connection(NMDevice     *device,
                                NMConnection *connection,
                                GError      **error)
 {
-    NMDeviceBridge      *self = NM_DEVICE_BRIDGE(device);
-    NMSettingConnection *s_con;
-    NMSettingBridgePort *s_port;
-    int                  ifindex_slave      = nm_device_get_ifindex(slave);
-    NMConnection        *applied_connection = nm_device_get_applied_connection(device);
-
-    const Option *option;
+    NMSettingConnection  *s_con;
+    NMSettingBridgePort  *s_port;
+    int                   ifindex_slave      = nm_device_get_ifindex(slave);
+    NMConnection         *applied_connection = nm_device_get_applied_connection(device);
+    const NMPlatformLink *pllink;
 
     g_return_val_if_fail(ifindex_slave > 0, FALSE);
 
     s_con  = nm_connection_get_setting_connection(connection);
     s_port = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BRIDGE_PORT);
-
-    for (option = slave_options; option->name; option++) {
-        gs_free char *str = nm_platform_sysctl_slave_get_option(nm_device_get_platform(device),
-                                                                ifindex_slave,
-                                                                option->sysname);
-        uint          value;
-
-        if (str) {
-            /* See comments in set_sysfs_uint() about centiseconds. */
-            if (option->user_hz_compensate) {
-                value = _nm_utils_ascii_str_to_int64(str,
-                                                     10,
-                                                     option->nm_min * 100,
-                                                     option->nm_max * 100,
-                                                     option->nm_default * 100);
-                value /= 100;
-            } else {
-                value = _nm_utils_ascii_str_to_int64(str,
-                                                     10,
-                                                     option->nm_min,
-                                                     option->nm_max,
-                                                     option->nm_default);
-            }
-            g_object_set(s_port, option->name, value, NULL);
-        } else
-            _LOGW(LOGD_BRIDGE, "failed to read bridge port setting '%s'", option->sysname);
+    pllink = nm_platform_link_get(nm_device_get_platform(slave), ifindex_slave);
+
+    if (pllink && pllink->port_kind == NM_PORT_KIND_BRIDGE) {
+        g_object_set(s_port,
+                     NM_SETTING_BRIDGE_PORT_PATH_COST,
+                     pllink->port_data.bridge.path_cost,
+                     NULL);
+        g_object_set(s_port,
+                     NM_SETTING_BRIDGE_PORT_PRIORITY,
+                     pllink->port_data.bridge.priority,
+                     NULL);
+        g_object_set(s_port,
+                     NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,
+                     pllink->port_data.bridge.hairpin,
+                     NULL);
     }
 
     g_object_set(s_con,
-                 NM_SETTING_CONNECTION_MASTER,
+                 NM_SETTING_CONNECTION_CONTROLLER,
                  nm_connection_get_uuid(applied_connection),
-                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_CONNECTION_PORT_TYPE,
                  NM_SETTING_BRIDGE_SETTING_NAME,
                  NULL);
     return TRUE;
@@ -1234,9 +1141,21 @@ static const NMDBusInterfaceInfoExtended interface_info_device_bridge = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_BRIDGE,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Slaves",
+                "ao",
+                NM_DEVICE_SLAVES,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c
index 36a0ee6f..b7c4106a 100644
--- a/src/core/devices/nm-device-dummy.c
+++ b/src/core/devices/nm-device-dummy.c
@@ -107,9 +107,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_dummy = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_DUMMY,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress",
-                                                           "s",
-                                                           NM_DEVICE_HW_ADDRESS), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index aedacc24..0f7f9d65 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -1431,7 +1431,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
                 mtu = nm_setting_ppp_get_mtu(s_ppp);
                 mru = nm_setting_ppp_get_mru(s_ppp);
-                mxu = MAX(mru, mtu);
+                mxu = NM_MAX(mru, mtu);
                 if (mxu) {
                     _LOGD(LOGD_PPP,
                           "set MTU to %u (PPP interface MRU %u, MTU %u)",
@@ -1711,10 +1711,8 @@ new_default_connection(NMDevice *self)
     NMSettingsConnection *const   *connections;
     NMSetting                     *setting;
     gs_unref_hashtable GHashTable *existing_ids = NULL;
-    struct udev_device            *dev;
     const char                    *perm_hw_addr;
     const char                    *iface;
-    const char                    *uprop   = "0";
     gs_free char                  *defname = NULL;
     gs_free char                  *uuid    = NULL;
     guint                          i, n_connections;
@@ -1760,30 +1758,6 @@ new_default_connection(NMDevice *self)
                  iface,
                  NULL);
 
-    /* Check if we should create a Link-Local only connection */
-    dev = nm_platform_link_get_udev_device(nm_device_get_platform(NM_DEVICE(self)),
-                                           nm_device_get_ip_ifindex(self));
-    if (dev)
-        uprop = udev_device_get_property_value(dev, "NM_AUTO_DEFAULT_LINK_LOCAL_ONLY");
-
-    if (_nm_utils_ascii_str_to_bool(uprop, FALSE)) {
-        setting = nm_setting_ip4_config_new();
-        g_object_set(setting,
-                     NM_SETTING_IP_CONFIG_METHOD,
-                     NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
-                     NULL);
-        nm_connection_add_setting(connection, setting);
-
-        setting = nm_setting_ip6_config_new();
-        g_object_set(setting,
-                     NM_SETTING_IP_CONFIG_METHOD,
-                     NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
-                     NM_SETTING_IP_CONFIG_MAY_FAIL,
-                     TRUE,
-                     NULL);
-        nm_connection_add_setting(connection, setting);
-    }
-
     return connection;
 }
 
@@ -2043,14 +2017,20 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wired = {
         NM_DBUS_INTERFACE_DEVICE_WIRED,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PermHwAddress",
-                                                           "s",
-                                                           NM_DEVICE_PERM_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "PermHwAddress",
+                "s",
+                NM_DEVICE_PERM_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Speed", "u", NM_DEVICE_ETHERNET_SPEED),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("S390Subchannels",
                                                            "as",
                                                            NM_DEVICE_ETHERNET_S390_SUBCHANNELS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c
index 2f0ae2e8..c97fbb57 100644
--- a/src/core/devices/nm-device-factory.c
+++ b/src/core/devices/nm-device-factory.c
@@ -396,6 +396,7 @@ nm_device_factory_manager_load_factories(NMDeviceFactoryManagerFactoryFunc callb
     _ADD_INTERNAL(nm_bridge_device_factory_get_type);
     _ADD_INTERNAL(nm_dummy_device_factory_get_type);
     _ADD_INTERNAL(nm_ethernet_device_factory_get_type);
+    _ADD_INTERNAL(nm_hsr_device_factory_get_type);
     _ADD_INTERNAL(nm_infiniband_device_factory_get_type);
     _ADD_INTERNAL(nm_ip_tunnel_device_factory_get_type);
     _ADD_INTERNAL(nm_loopback_device_factory_get_type);
diff --git a/src/core/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c
index c0dcf0de..ead671d4 100644
--- a/src/core/devices/nm-device-generic.c
+++ b/src/core/devices/nm-device-generic.c
@@ -168,7 +168,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_generic = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_GENERIC,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
                 "TypeDescription",
                 "s",
diff --git a/src/core/devices/nm-device-hsr.c b/src/core/devices/nm-device-hsr.c
new file mode 100644
index 00000000..8156fa77
--- /dev/null
+++ b/src/core/devices/nm-device-hsr.c
@@ -0,0 +1,287 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-manager.h"
+#include "nm-device-hsr.h"
+
+#include <linux/if_ether.h>
+
+#include "libnm-core-intern/nm-core-internal.h"
+#include "nm-act-request.h"
+#include "nm-device-private.h"
+#include "nm-setting-hsr.h"
+#include "libnm-platform/nm-platform.h"
+#include "nm-device-factory.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceHsr
+#include "nm-device-logging.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceHsr,
+                             PROP_PORT1,
+                             PROP_PORT2,
+                             PROP_SUPERVISION_ADDRESS,
+                             PROP_MULTICAST_SPEC,
+                             PROP_PRP, );
+
+typedef struct {
+    NMPlatformLnkHsr props;
+} NMDeviceHsrPrivate;
+
+struct _NMDeviceHsr {
+    NMDevice           parent;
+    NMDeviceHsrPrivate _priv;
+};
+
+struct _NMDeviceHsrClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceHsr, nm_device_hsr, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_HSR_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDeviceHsr, NM_IS_DEVICE_HSR, NMDevice)
+
+/*****************************************************************************/
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *dev)
+{
+    return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+update_properties(NMDevice *device)
+{
+    NMDeviceHsr            *self;
+    NMDeviceHsrPrivate     *priv;
+    const NMPlatformLink   *plink;
+    const NMPlatformLnkHsr *props;
+    int                     ifindex;
+
+    g_return_if_fail(NM_IS_DEVICE_HSR(device));
+    self = NM_DEVICE_HSR(device);
+    priv = NM_DEVICE_HSR_GET_PRIVATE(self);
+
+    ifindex = nm_device_get_ifindex(device);
+    g_return_if_fail(ifindex > 0);
+    props = nm_platform_link_get_lnk_hsr(nm_device_get_platform(device), ifindex, &plink);
+
+    if (!props) {
+        _LOGW(LOGD_PLATFORM, "could not get HSR properties");
+        return;
+    }
+
+    g_object_freeze_notify((GObject *) device);
+
+#define CHECK_PROPERTY_CHANGED(field, prop)      \
+    G_STMT_START                                 \
+    {                                            \
+        if (priv->props.field != props->field) { \
+            priv->props.field = props->field;    \
+            _notify(self, prop);                 \
+        }                                        \
+    }                                            \
+    G_STMT_END
+
+    CHECK_PROPERTY_CHANGED(port1, PROP_PORT1);
+    CHECK_PROPERTY_CHANGED(port2, PROP_PORT2);
+    CHECK_PROPERTY_CHANGED(multicast_spec, PROP_MULTICAST_SPEC);
+    CHECK_PROPERTY_CHANGED(prp, PROP_PRP);
+
+    if (!nm_ether_addr_equal(&priv->props.supervision_address, &props->supervision_address))
+        _notify(self, PROP_SUPERVISION_ADDRESS);
+
+    g_object_thaw_notify((GObject *) device);
+}
+
+static void
+link_changed(NMDevice *device, const NMPlatformLink *pllink)
+{
+    NM_DEVICE_CLASS(nm_device_hsr_parent_class)->link_changed(device, pllink);
+    update_properties(device);
+}
+
+static gboolean
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
+                   const NMPlatformLink **out_plink,
+                   GError               **error)
+{
+    const char      *iface = nm_device_get_iface(device);
+    NMSettingHsr    *s_hsr;
+    NMPlatformLnkHsr lnk = {};
+    int              r;
+
+    s_hsr = _nm_connection_get_setting(connection, NM_TYPE_SETTING_HSR);
+    nm_assert(s_hsr);
+
+    if (nm_setting_hsr_get_port1(s_hsr) != NULL)
+        lnk.port1 = nm_platform_link_get_ifindex(NM_PLATFORM_GET, nm_setting_hsr_get_port1(s_hsr));
+    if (nm_setting_hsr_get_port2(s_hsr) != NULL)
+        lnk.port2 = nm_platform_link_get_ifindex(NM_PLATFORM_GET, nm_setting_hsr_get_port2(s_hsr));
+    lnk.multicast_spec = nm_setting_hsr_get_multicast_spec(s_hsr);
+    lnk.prp            = nm_setting_hsr_get_prp(s_hsr);
+    r = nm_platform_link_hsr_add(nm_device_get_platform(device), iface, &lnk, out_plink);
+    if (r < 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_CREATION_FAILED,
+                    "Failed to create HSR interface '%s' for '%s': %s",
+                    iface,
+                    nm_connection_get_id(connection),
+                    nm_strerror(r));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMDeviceHsr        *self = NM_DEVICE_HSR(object);
+    NMDeviceHsrPrivate *priv = NM_DEVICE_HSR_GET_PRIVATE(self);
+    NMDevice           *port;
+
+    switch (prop_id) {
+    case PROP_PORT1:
+        port = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, priv->props.port1);
+        nm_dbus_utils_g_value_set_object_path(value, port);
+        break;
+    case PROP_PORT2:
+        port = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, priv->props.port2);
+        nm_dbus_utils_g_value_set_object_path(value, port);
+        break;
+    case PROP_SUPERVISION_ADDRESS:
+        g_value_take_string(value, nm_ether_addr_to_string_dup(&priv->props.supervision_address));
+        break;
+    case PROP_MULTICAST_SPEC:
+        g_value_set_uchar(value, priv->props.multicast_spec);
+        break;
+    case PROP_PRP:
+        g_value_set_boolean(value, priv->props.prp);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nm_device_hsr_init(NMDeviceHsr *self)
+{}
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_device_hsr = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_HSR,
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Port1", "o", NM_DEVICE_HSR_PORT1),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Port2", "o", NM_DEVICE_HSR_PORT2),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("SupervisionAddress",
+                                                           "s",
+                                                           NM_DEVICE_HSR_SUPERVISION_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("MulticastSpec",
+                                                           "y",
+                                                           NM_DEVICE_HSR_MULTICAST_SPEC),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Prp", "b", NM_DEVICE_HSR_PRP), ), ),
+};
+
+static void
+nm_device_hsr_class_init(NMDeviceHsrClass *klass)
+{
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
+
+    object_class->get_property = get_property;
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_hsr);
+
+    device_class->connection_type_supported        = NM_SETTING_HSR_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_HSR_SETTING_NAME;
+    device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_HSR);
+
+    device_class->link_changed             = link_changed;
+    device_class->create_and_realize       = create_and_realize;
+    device_class->get_generic_capabilities = get_generic_capabilities;
+
+    obj_properties[PROP_PORT1] = g_param_spec_string(NM_DEVICE_HSR_PORT1,
+                                                     "",
+                                                     "",
+                                                     NULL,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_PORT2] = g_param_spec_string(NM_DEVICE_HSR_PORT2,
+                                                     "",
+                                                     "",
+                                                     NULL,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_SUPERVISION_ADDRESS] =
+        g_param_spec_string(NM_DEVICE_HSR_SUPERVISION_ADDRESS,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_MULTICAST_SPEC] =
+        g_param_spec_uchar(NM_DEVICE_HSR_MULTICAST_SPEC,
+                           "",
+                           "",
+                           0,
+                           G_MAXUINT8,
+                           0,
+                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_PRP] = g_param_spec_boolean(NM_DEVICE_HSR_PRP,
+                                                    "",
+                                                    "",
+                                                    FALSE,
+                                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_HSR_DEVICE_FACTORY (nm_hsr_device_factory_get_type())
+#define NM_HSR_DEVICE_FACTORY(obj) \
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_HSR_DEVICE_FACTORY, NMHsrDeviceFactory))
+
+static NMDevice *
+create_device(NMDeviceFactory      *factory,
+              const char           *iface,
+              const NMPlatformLink *plink,
+              NMConnection         *connection,
+              gboolean             *out_ignore)
+{
+    return g_object_new(NM_TYPE_DEVICE_HSR,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "hsr",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_HSR,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_HSR,
+                        NULL);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL(
+    HSR,
+    Hsr,
+    hsr,
+    NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_HSR)
+        NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_HSR_SETTING_NAME),
+    factory_class->create_device = create_device;)
diff --git a/src/core/devices/nm-device-hsr.h b/src/core/devices/nm-device-hsr.h
new file mode 100644
index 00000000..1da90358
--- /dev/null
+++ b/src/core/devices/nm-device-hsr.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_HSR_H__
+#define __NETWORKMANAGER_DEVICE_HSR_H__
+
+#include "nm-device.h"
+
+#define NM_TYPE_DEVICE_HSR (nm_device_hsr_get_type())
+#define NM_DEVICE_HSR(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_HSR, NMDeviceHsr))
+#define NM_DEVICE_HSR_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_HSR, NMDeviceHsrClass))
+#define NM_IS_DEVICE_HSR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_HSR))
+#define NM_IS_DEVICE_HSR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_HSR))
+#define NM_DEVICE_HSR_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_HSR, NMDeviceHsrClass))
+
+#define NM_DEVICE_HSR_PORT1               "port1"
+#define NM_DEVICE_HSR_PORT2               "port2"
+#define NM_DEVICE_HSR_SUPERVISION_ADDRESS "supervision-address"
+#define NM_DEVICE_HSR_MULTICAST_SPEC      "multicast-spec"
+#define NM_DEVICE_HSR_PRP                 "prp"
+
+typedef struct _NMDeviceHsr      NMDeviceHsr;
+typedef struct _NMDeviceHsrClass NMDeviceHsrClass;
+
+GType nm_device_hsr_get_type(void);
+
+#endif /* __NETWORKMANAGER_DEVICE_HSR_H__ */
diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index 6ce61d0b..c974696c 100644
--- a/src/core/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -362,8 +362,16 @@ static const NMDBusInterfaceInfoExtended interface_info_device_infiniband = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_INFINIBAND,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index 430e62aa..28b03cec 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -452,9 +452,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_tun = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("MultiQueue",
                                                            "b",
                                                            NM_DEVICE_TUN_MULTI_QUEUE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress",
-                                                           "s",
-                                                           NM_DEVICE_HW_ADDRESS), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index 7849e724..cc649ec9 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -525,8 +525,16 @@ static const NMDBusInterfaceInfoExtended interface_info_device_vlan = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_VLAN,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VlanId", "u", NM_DEVICE_VLAN_ID), ), ),
 };
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index 061ee3f2..6a23d51e 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -576,7 +576,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_vxlan = {
         NM_DBUS_INTERFACE_DEVICE_VXLAN,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Id", "u", NM_DEVICE_VXLAN_ID),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Group", "s", NM_DEVICE_VXLAN_GROUP),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Local", "s", NM_DEVICE_VXLAN_LOCAL),
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index db02033d..dd40d11e 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -598,7 +598,7 @@ _peers_add(NMDeviceWireGuard *self, NMWireGuardPeer *peer)
     };
 
     c_list_link_tail(&priv->lst_peers_head, &peer_data->lst_peers);
-    if (!nm_g_hash_table_add(priv->peers, peer_data))
+    if (!g_hash_table_add(priv->peers, peer_data))
         nm_assert_not_reached();
     return peer_data;
 }
@@ -720,7 +720,7 @@ _peers_retry_in_msec(PeerData *peer_data, gboolean after_failure)
         return RETRY_IN_MSEC_MAX;
 
     /* double the retry-time, starting with one second. */
-    return NM_MIN(RETRY_IN_MSEC_MAX, (1u << peer_data->ep_resolv.resolv_fail_count) * 500);
+    return NM_MIN(RETRY_IN_MSEC_MAX, (1l << peer_data->ep_resolv.resolv_fail_count) * 500);
 }
 
 static void
diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 282eea87..7885355d 100644
--- a/src/core/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -197,9 +197,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wpan = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_WPAN,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress",
-                                                           "s",
-                                                           NM_DEVICE_HW_ADDRESS), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index d1212560..16f8e1f2 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -22,6 +22,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/if_ether.h>
 #include <linux/if_infiniband.h>
+#include <libudev.h>
 
 #include "libnm-std-aux/unaligned.h"
 #include "libnm-glib-aux/nm-uuid.h"
@@ -303,6 +304,8 @@ typedef struct {
     NMEthtoolCoalesceState *coalesce;
     NMEthtoolRingState     *ring;
     NMEthtoolPauseState    *pause;
+    NMEthtoolChannelsState *channels;
+    NMEthtoolEEEState      *eee;
 } EthtoolState;
 
 typedef enum {
@@ -1030,12 +1033,14 @@ _prop_get_connection_stable_id(NMDevice          *self,
         gs_free char        *generated = NULL;
         NMUtilsStableType    stable_type;
         NMSettingConnection *s_con;
+        NMSettingWireless   *s_wifi;
         gboolean             hwaddr_is_fake;
         const char          *hwaddr;
         const char          *stable_id;
         const char          *uuid;
 
-        s_con = nm_connection_get_setting_connection(connection);
+        s_con  = nm_connection_get_setting_connection(connection);
+        s_wifi = nm_connection_get_setting_wireless(connection);
 
         stable_id = nm_setting_connection_get_stable_id(s_con);
 
@@ -1058,6 +1063,7 @@ _prop_get_connection_stable_id(NMDevice          *self,
                                                !hwaddr_is_fake ? hwaddr : NULL,
                                                nm_utils_boot_id_str(),
                                                uuid,
+                                               s_wifi ? nm_setting_wireless_get_ssid(s_wifi) : NULL,
                                                &generated);
 
         /* current_stable_id_type is a bitfield! */
@@ -1517,7 +1523,7 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 
     if (route_table == 0u && connection
         && (s_con = nm_connection_get_setting_connection(connection))
-        && (nm_streq0(nm_setting_connection_get_slave_type(s_con), NM_SETTING_VRF_SETTING_NAME)
+        && (nm_streq0(nm_setting_connection_get_port_type(s_con), NM_SETTING_VRF_SETTING_NAME)
             && priv->master && nm_device_get_device_type(priv->master) == NM_DEVICE_TYPE_VRF)) {
         const NMPlatformLnkVrf *lnk;
 
@@ -1655,7 +1661,7 @@ _prop_get_ipv4_dad_timeout(NMDevice *self)
                                                        self,
                                                        0,
                                                        NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
-                                                       0);
+                                                       200);
 }
 
 static guint32
@@ -2017,7 +2023,10 @@ _prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con)
 }
 
 static GBytes *
-_prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *hwaddr)
+_prop_get_ipv4_dhcp_client_id(NMDevice     *self,
+                              NMConnection *connection,
+                              GBytes       *hwaddr,
+                              gboolean     *out_send_client_id)
 {
     NMSettingIPConfig *s_ip4;
     const char        *client_id;
@@ -2030,6 +2039,8 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
     GBytes            *result;
     gs_free char      *logstr1 = NULL;
 
+    NM_SET_OUT(out_send_client_id, TRUE);
+
     s_ip4     = nm_connection_get_setting_ip4_config(connection);
     client_id = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4));
 
@@ -2049,6 +2060,12 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
         return NULL;
     }
 
+    if (nm_streq(client_id, "none")) {
+        _LOGD(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4, "ipv4.dhcp-client-id: set to \"none\"");
+        NM_SET_OUT(out_send_client_id, FALSE);
+        return NULL;
+    }
+
     if (nm_streq(client_id, "mac")) {
         if (!hwaddr) {
             fail_reason = "missing link-layer address";
@@ -2335,7 +2352,7 @@ _prop_get_x_cloned_mac_address(NMDevice *self, NMConnection *connection, gboolea
                 if (v == NM_SETTING_MAC_RANDOMIZATION_ALWAYS)
                     addr = NM_CLONED_MAC_RANDOM;
             }
-        } else if (NM_CLONED_MAC_IS_SPECIAL(a) || nm_utils_hwaddr_valid(a, ETH_ALEN))
+        } else if (NM_CLONED_MAC_IS_SPECIAL(a, is_wifi) || nm_utils_hwaddr_valid(a, ETH_ALEN))
             addr = a;
     }
 
@@ -2374,6 +2391,8 @@ _ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *etht
     gs_free NMEthtoolFeatureStates *features = NULL;
 
     features = g_steal_pointer(&ethtool_state->features);
+    if (!features)
+        return;
 
     if (!nm_platform_ethtool_set_features(platform,
                                           ethtool_state->ifindex,
@@ -2393,8 +2412,7 @@ _ethtool_features_set(NMDevice         *self,
 {
     gs_free NMEthtoolFeatureStates *features = NULL;
 
-    if (ethtool_state->features)
-        _ethtool_features_reset(self, platform, ethtool_state);
+    _ethtool_features_reset(self, platform, ethtool_state);
 
     if (nm_setting_ethtool_init_features(s_ethtool, ethtool_state->requested) == 0)
         return;
@@ -2592,6 +2610,104 @@ _ethtool_ring_set(NMDevice         *self,
 }
 
 static void
+_ethtool_channels_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
+{
+    gs_free NMEthtoolChannelsState *channels = NULL;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(ethtool_state);
+
+    channels = g_steal_pointer(&ethtool_state->channels);
+    if (!channels)
+        return;
+
+    if (!nm_platform_ethtool_set_channels(platform, ethtool_state->ifindex, channels))
+        _LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more channels settings");
+    else
+        _LOGD(LOGD_DEVICE, "ethtool: channels settings successfully reset");
+}
+
+static void
+_ethtool_channels_set(NMDevice         *self,
+                      NMPlatform       *platform,
+                      EthtoolState     *ethtool_state,
+                      NMSettingEthtool *s_ethtool)
+{
+    NMEthtoolChannelsState channels_old;
+    NMEthtoolChannelsState channels_new;
+    GHashTable            *hash;
+    GHashTableIter         iter;
+    const char            *name;
+    GVariant              *variant;
+    gboolean               has_old = FALSE;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
+    nm_assert(ethtool_state);
+    nm_assert(!ethtool_state->channels);
+
+    hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
+    if (!hash)
+        return;
+
+    g_hash_table_iter_init(&iter, hash);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
+        NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
+        guint32     u32;
+
+        if (!nm_ethtool_id_is_channels(ethtool_id))
+            continue;
+
+        nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
+
+        if (!has_old) {
+            if (!nm_platform_ethtool_get_link_channels(platform,
+                                                       ethtool_state->ifindex,
+                                                       &channels_old)) {
+                _LOGW(LOGD_DEVICE,
+                      "ethtool: failure setting channels options (cannot read existing setting)");
+                return;
+            }
+            has_old      = TRUE;
+            channels_new = channels_old;
+        }
+
+        u32 = g_variant_get_uint32(variant);
+
+        switch (ethtool_id) {
+        case NM_ETHTOOL_ID_CHANNELS_RX:
+            channels_new.rx = u32;
+            break;
+        case NM_ETHTOOL_ID_CHANNELS_TX:
+            channels_new.tx = u32;
+            break;
+        case NM_ETHTOOL_ID_CHANNELS_OTHER:
+            channels_new.other = u32;
+            break;
+        case NM_ETHTOOL_ID_CHANNELS_COMBINED:
+            channels_new.combined = u32;
+            break;
+        default:
+            nm_assert_not_reached();
+        }
+    }
+
+    if (!has_old)
+        return;
+
+    ethtool_state->channels = nm_memdup(&channels_old, sizeof(channels_old));
+
+    if (!nm_platform_ethtool_set_channels(platform, ethtool_state->ifindex, &channels_new)) {
+        _LOGW(LOGD_DEVICE, "ethtool: failure setting channels settings");
+        return;
+    }
+
+    _LOGD(LOGD_DEVICE, "ethtool: channels settings successfully set");
+}
+
+static void
 _ethtool_pause_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
 {
     gs_free NMEthtoolPauseState *pause = NULL;
@@ -2611,6 +2727,25 @@ _ethtool_pause_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool
 }
 
 static void
+_ethtool_eee_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
+{
+    gs_free NMEthtoolEEEState *eee = NULL;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(ethtool_state);
+
+    eee = g_steal_pointer(&ethtool_state->eee);
+    if (!eee)
+        return;
+
+    if (!nm_platform_ethtool_set_eee(platform, ethtool_state->ifindex, eee))
+        _LOGW(LOGD_DEVICE, "ethtool: failure resetting eee settings");
+    else
+        _LOGD(LOGD_DEVICE, "ethtool: eee settings successfully reset");
+}
+
+static void
 _ethtool_pause_set(NMDevice         *self,
                    NMPlatform       *platform,
                    EthtoolState     *ethtool_state,
@@ -2699,6 +2834,73 @@ _ethtool_pause_set(NMDevice         *self,
 }
 
 static void
+_ethtool_eee_set(NMDevice         *self,
+                 NMPlatform       *platform,
+                 EthtoolState     *ethtool_state,
+                 NMSettingEthtool *s_ethtool)
+{
+    NMEthtoolEEEState eee_old;
+    NMEthtoolEEEState eee_new;
+    GHashTable       *hash;
+    GHashTableIter    iter;
+    const char       *name;
+    GVariant         *variant;
+    gboolean          has_old = FALSE;
+    NMTernary         eee     = NM_TERNARY_DEFAULT;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
+    nm_assert(ethtool_state);
+    nm_assert(!ethtool_state->eee);
+
+    hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
+    if (!hash)
+        return;
+
+    g_hash_table_iter_init(&iter, hash);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
+        NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
+
+        if (!nm_ethtool_id_is_eee(ethtool_id))
+            continue;
+
+        nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN));
+
+        if (!has_old) {
+            if (!nm_platform_ethtool_get_link_eee(platform, ethtool_state->ifindex, &eee_old)) {
+                _LOGW(LOGD_DEVICE,
+                      "ethtool: failure setting eee options (cannot read "
+                      "existing setting)");
+                return;
+            }
+            has_old = TRUE;
+        }
+
+        if (ethtool_id == NM_ETHTOOL_ID_EEE_ENABLED)
+            eee = g_variant_get_boolean(variant);
+        else
+            nm_assert_not_reached();
+    }
+
+    if (!has_old)
+        return;
+
+    eee_new = eee_old;
+    if (eee != NM_TERNARY_DEFAULT)
+        eee_new.enabled = !!eee;
+
+    ethtool_state->eee = nm_memdup(&eee_old, sizeof(eee_old));
+
+    if (!nm_platform_ethtool_set_eee(platform, ethtool_state->ifindex, &eee_new)) {
+        _LOGW(LOGD_DEVICE, "ethtool: failure setting eee settings");
+        return;
+    }
+
+    _LOGD(LOGD_DEVICE, "ethtool: eee settings successfully set");
+}
+
+static void
 _ethtool_state_reset(NMDevice *self)
 {
     NMPlatform           *platform      = nm_device_get_platform(self);
@@ -2708,14 +2910,12 @@ _ethtool_state_reset(NMDevice *self)
     if (!ethtool_state)
         return;
 
-    if (ethtool_state->features)
-        _ethtool_features_reset(self, platform, ethtool_state);
-    if (ethtool_state->coalesce)
-        _ethtool_coalesce_reset(self, platform, ethtool_state);
-    if (ethtool_state->ring)
-        _ethtool_ring_reset(self, platform, ethtool_state);
-    if (ethtool_state->pause)
-        _ethtool_pause_reset(self, platform, ethtool_state);
+    _ethtool_features_reset(self, platform, ethtool_state);
+    _ethtool_coalesce_reset(self, platform, ethtool_state);
+    _ethtool_ring_reset(self, platform, ethtool_state);
+    _ethtool_pause_reset(self, platform, ethtool_state);
+    _ethtool_channels_reset(self, platform, ethtool_state);
+    _ethtool_eee_reset(self, platform, ethtool_state);
 }
 
 static void
@@ -2750,9 +2950,11 @@ _ethtool_state_set(NMDevice *self)
     _ethtool_coalesce_set(self, platform, ethtool_state, s_ethtool);
     _ethtool_ring_set(self, platform, ethtool_state, s_ethtool);
     _ethtool_pause_set(self, platform, ethtool_state, s_ethtool);
+    _ethtool_channels_set(self, platform, ethtool_state, s_ethtool);
+    _ethtool_eee_set(self, platform, ethtool_state, s_ethtool);
 
     if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring
-        || ethtool_state->pause)
+        || ethtool_state->pause || ethtool_state->channels || ethtool_state->eee)
         priv->ethtool_state = g_steal_pointer(&ethtool_state);
 }
 
@@ -2863,7 +3065,7 @@ nm_device_link_properties_set(NMDevice *self, gboolean reapply)
     _RESET(NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS, gso_max_segments);
     _RESET(NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE, gro_max_size);
 
-    if (nm_platform_link_change(platform, ifindex, &props, NULL, flags)) {
+    if (nm_platform_link_change(platform, ifindex, &props, NULL, NULL, flags)) {
         _LOGD(LOGD_DEVICE, "link properties successfully set");
     } else {
         _LOGW(LOGD_DEVICE, "failure setting link properties");
@@ -2891,6 +3093,7 @@ link_properties_reset(NMDevice *self)
                                 ifindex,
                                 &priv->link_props_state.props,
                                 NULL,
+                                NULL,
                                 priv->link_props_state.flags)) {
         _LOGD(LOGD_DEVICE, "link properties successfully reset");
     } else {
@@ -4228,6 +4431,19 @@ update_external_connection(NMDevice *self)
 }
 
 static void
+_dev_ipv6_log_conflicts(NMDevice *self, GArray *conflicts)
+{
+    guint i;
+    char  sbuf[NM_INET_ADDRSTRLEN];
+
+    for (i = 0; i < conflicts->len; i++) {
+        const struct in6_addr *addr = &nm_g_array_index(conflicts, const struct in6_addr, i);
+
+        _LOGI(LOGD_DEVICE, "Conflict detected for IPv6 address: %s", nm_inet6_ntop(addr, sbuf));
+    }
+}
+
+static void
 _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
@@ -4250,7 +4466,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
         char                   buf_addr[NM_INET_ADDRSTRLEN];
 
         if (addr_info->state == NM_L3_ACD_ADDR_STATE_USED) {
-            _LOGI(LOGD_DEVICE,
+            _LOGW(LOGD_DEVICE,
                   "IP address %s cannot be configured because it is already in use in the "
                   "network by host %s",
                   nm_inet4_ntop(addr_info->addr, buf_addr),
@@ -4284,7 +4500,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
         if (priv->ipshared_data_4.state == NM_DEVICE_IP_STATE_PENDING
             && !priv->ipshared_data_4.v4.dnsmasq_manager && priv->ipshared_data_4.v4.l3cd) {
             _dev_ipshared4_spawn_dnsmasq(self);
-            nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
         }
         _dev_ip_state_check_async(self, AF_UNSPEC);
         _dev_ipmanual_check_ready(self);
@@ -4322,6 +4537,10 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
                                          NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY,
                                          &conflicts);
             if (conflicts) {
+                if (_NMLOG_ENABLED(LOGL_INFO, LOGD_DEVICE)) {
+                    _dev_ipv6_log_conflicts(self, conflicts);
+                }
+
                 /* nm_ndisc_dad_failed() will emit a new "NDisc:config-received"
                  * signal; _dev_ipac6_ndisc_config_changed() will be called
                  * synchronously to update the current state and schedule a commit. */
@@ -5231,6 +5450,8 @@ nm_device_get_route_metric_default(NMDeviceType device_type)
         return 200;
     case NM_DEVICE_TYPE_WIMAX:
         return 250;
+    case NM_DEVICE_TYPE_HSR:
+        return 275;
     case NM_DEVICE_TYPE_BOND:
         return 300;
     case NM_DEVICE_TYPE_TEAM:
@@ -5715,7 +5936,7 @@ out:
     return FALSE;
 }
 
-#define CONCHECK_P_PROBE_INTERVAL 1
+#define CONCHECK_P_PROBE_INTERVAL 1u
 
 static void
 concheck_periodic_schedule_set(NMDevice *self, int addr_family, ConcheckScheduleMode mode)
@@ -5887,7 +6108,7 @@ concheck_update_interval(NMDevice *self, int addr_family, gboolean check_now)
 
     new_interval = nm_connectivity_get_interval(concheck_get_mgr(self));
 
-    new_interval = NM_MIN(new_interval, 7 * 24 * 3600);
+    new_interval = NM_MIN(new_interval, 7u * 24u * 3600u);
 
     if (new_interval != priv->concheck_x[IS_IPv4].p_max_interval) {
         _LOGT(LOGD_CONCHECK,
@@ -7133,7 +7354,7 @@ device_ip_link_changed(gpointer user_data)
     ip_iface = pllink->name;
 
     if (!ip_iface[0])
-        return FALSE;
+        return G_SOURCE_REMOVE;
 
     if (!nm_streq(priv->ip_iface, ip_iface)) {
         _LOGI(LOGD_DEVICE,
@@ -7989,6 +8210,40 @@ nm_device_owns_iface(NMDevice *self, const char *iface)
     return FALSE;
 }
 
+static void
+apply_udev_auto_default_configs(NMDevice *self, NMConnection *connection)
+{
+    struct udev_device *dev;
+    const char         *uprop;
+    NMSetting          *setting;
+
+    dev = nm_platform_link_get_udev_device(nm_device_get_platform(NM_DEVICE(self)),
+                                           nm_device_get_ip_ifindex(self));
+    if (!dev)
+        return;
+
+    uprop = udev_device_get_property_value(dev, "NM_AUTO_DEFAULT_LINK_LOCAL_ONLY");
+    uprop = uprop ?: udev_device_get_property_value(dev, "ID_NET_AUTO_LINK_LOCAL_ONLY");
+
+    if (_nm_utils_ascii_str_to_bool(uprop, FALSE)) {
+        setting = nm_setting_ip4_config_new();
+        g_object_set(setting,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
+                     NULL);
+        nm_connection_add_setting(connection, setting);
+
+        setting = nm_setting_ip6_config_new();
+        g_object_set(setting,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+                     NM_SETTING_IP_CONFIG_MAY_FAIL,
+                     TRUE,
+                     NULL);
+        nm_connection_add_setting(connection, setting);
+    }
+}
+
 NMConnection *
 nm_device_new_default_connection(NMDevice *self)
 {
@@ -8002,6 +8257,8 @@ nm_device_new_default_connection(NMDevice *self)
     if (!connection)
         return NULL;
 
+    apply_udev_auto_default_configs(self, connection);
+
     if (!nm_connection_normalize(connection, NULL, NULL, &error)) {
         _LOGD(LOGD_DEVICE, "device generated an invalid default connection: %s", error->message);
         g_error_free(error);
@@ -8323,7 +8580,7 @@ nm_device_slave_notify_release(NMDevice           *self,
             master_status = "failed";
             break;
         case NM_DEVICE_STATE_REASON_USER_REQUESTED:
-            reason        = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
+            reason        = NM_DEVICE_STATE_REASON_USER_REQUESTED;
             master_status = "deactivated by user request";
             break;
         case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED:
@@ -9184,7 +9441,7 @@ nm_device_check_slave_connection_compatible(NMDevice *self, NMConnection *slave)
 
     s_con = nm_connection_get_setting_connection(slave);
     g_assert(s_con);
-    slave_type = nm_setting_connection_get_slave_type(s_con);
+    slave_type = nm_setting_connection_get_port_type(s_con);
     if (!slave_type)
         return FALSE;
 
@@ -9746,10 +10003,16 @@ activate_stage1_device_prepare(NMDevice *self)
     master = nm_active_connection_get_master(active);
     if (master) {
         if (nm_active_connection_get_state(master) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+            NMDevice           *master_device  = nm_active_connection_get_device(master);
+            NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
+
             _LOGD(LOGD_DEVICE, "master connection is deactivating");
-            nm_device_state_changed(self,
-                                    NM_DEVICE_STATE_FAILED,
-                                    NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
+
+            if (master_device && NM_DEVICE_GET_PRIVATE(master_device)->queued_act_request) {
+                /* if the controller is going to activate again, don't block this device */
+                failure_reason = NM_DEVICE_STATE_REASON_NONE;
+            }
+            nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
             return;
         }
         /* If the master connection is ready for slaves, attach ourselves */
@@ -10363,7 +10626,6 @@ _dev_ipmanual_check_ready(NMDevice *self)
     gboolean               has_carrier;
     NML3CfgCheckReadyFlags flags;
     gboolean               ready;
-    gs_unref_array GArray *conflicts = NULL;
     int                    IS_IPv4;
 
     if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_PENDING
@@ -10393,26 +10655,41 @@ _dev_ipmanual_check_ready(NMDevice *self)
         }
     }
 
-    flags = NM_L3CFG_CHECK_READY_FLAGS_NONE;
-    if (has_carrier) {
-        flags |= NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY;
-        flags |= NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY;
-    }
+    flags = NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY;
+    flags |= NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY;
 
     for (IS_IPv4 = 0; IS_IPv4 < 2; IS_IPv4++) {
-        const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        const int              addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        gs_unref_array GArray *conflicts   = NULL;
 
         ready = nm_l3cfg_check_ready(priv->l3cfg,
                                      priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d,
                                      addr_family,
                                      flags,
                                      &conflicts);
-        if (conflicts) {
-            _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
-            _dev_ip_state_check_async(self, AF_UNSPEC);
-        } else if (ready) {
-            _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
-            _dev_ip_state_check_async(self, AF_UNSPEC);
+
+        if (_NMLOG_ENABLED(LOGL_INFO, LOGD_DEVICE) && conflicts && !IS_IPv4) {
+            _dev_ipv6_log_conflicts(self, conflicts);
+        }
+
+        if (ready) {
+            guint num_addrs = 0;
+
+            num_addrs =
+                nm_l3_config_data_get_num_addresses(priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d,
+                                                    addr_family);
+
+            if (conflicts && conflicts->len == num_addrs) {
+                _LOGD_ipmanual(addr_family, "all manual addresses failed DAD, failing");
+                _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
+                _dev_ip_state_check_async(self, AF_UNSPEC);
+            } else {
+                if (conflicts) {
+                    _LOGD_ipmanual(addr_family, "some manual addresses passed DAD, continuing");
+                }
+                _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
+                _dev_ip_state_check_async(self, AF_UNSPEC);
+            }
         }
     }
 }
@@ -10692,8 +10969,10 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         const char *const     *reject_servers;
         const char            *hostname;
         gboolean               hostname_is_fqdn;
+        gboolean               send_client_id;
+
+        client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr, &send_client_id);
 
-        client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr);
         vendor_class_identifier =
             _prop_get_ipv4_dhcp_vendor_class_identifier(self, NM_SETTING_IP4_CONFIG(s_ip));
         reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip, NULL);
@@ -10730,6 +11009,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
                 {
                     .request_broadcast = request_broadcast,
                     .acd_timeout_msec  = _prop_get_ipv4_dad_timeout(self),
+                    .send_client_id    = send_client_id,
                 },
             .previous_lease = priv->l3cds[L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4)].d,
         };
@@ -11560,13 +11840,13 @@ _commit_mtu(NMDevice *self)
     mtu_plat = nm_platform_link_get_mtu(nm_device_get_platform(self), ifindex);
 
     if (ip6_mtu) {
-        ip6_mtu = NM_MAX(1280, ip6_mtu);
+        ip6_mtu = NM_MAX(1280u, ip6_mtu);
 
         if (!mtu_desired)
             mtu_desired = mtu_plat;
 
         if (mtu_desired) {
-            mtu_desired = NM_MAX(1280, mtu_desired);
+            mtu_desired = NM_MAX(1280u, mtu_desired);
 
             if (mtu_desired < ip6_mtu)
                 ip6_mtu = mtu_desired;
@@ -11736,8 +12016,9 @@ _dev_ipac6_ndisc_config_changed(NMNDisc              *ndisc,
                                 const NML3ConfigData *l3cd,
                                 NMDevice             *self)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    gboolean         ready;
+    NMDevicePrivate       *priv = NM_DEVICE_GET_PRIVATE(self);
+    gboolean               ready;
+    gs_unref_array GArray *conflicts = NULL;
 
     /* The ndisc configuration changes when we receive a new RA or
      * when a lifetime expires; but also when DAD fails for a
@@ -11754,7 +12035,12 @@ _dev_ipac6_ndisc_config_changed(NMNDisc              *ndisc,
                                  l3cd,
                                  AF_INET6,
                                  NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY,
-                                 NULL);
+                                 &conflicts);
+
+    if (_NMLOG_ENABLED(LOGL_INFO, LOGD_DEVICE) && conflicts) {
+        _dev_ipv6_log_conflicts(self, conflicts);
+    }
+
     if (ready) {
         _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
     } else {
@@ -11856,16 +12142,8 @@ _dev_ipac6_start(NMDevice *self)
     NMUtilsIPv6IfaceId  iid;
     gboolean            is_token;
 
-    if (priv->ipac6_data.state == NM_DEVICE_IP_STATE_NONE) {
-        if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
-            _LOGI_ipac6("addrconf6: kernel does not support IPv6");
-            _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_FAILED);
-            _dev_ip_state_check_async(self, AF_INET6);
-            return;
-        }
-
+    if (priv->ipac6_data.state == NM_DEVICE_IP_STATE_NONE)
         _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_PENDING);
-    }
 
     if (NM_IN_SET(priv->ipll_data_6.state, NM_DEVICE_IP_STATE_NONE, NM_DEVICE_IP_STATE_PENDING)) {
         _dev_ipac6_grace_period_start(self, 30, TRUE);
@@ -12406,6 +12684,30 @@ activate_stage3_ip_config(NMDevice *self)
 
     ifindex = nm_device_get_ip_ifindex(self);
 
+    /* Add the interface to the specified firewall zone */
+    switch (priv->fw_state) {
+    case FIREWALL_STATE_UNMANAGED:
+        if (nm_device_sys_iface_state_is_external(self)) {
+            /* fake success */
+            priv->fw_state = FIREWALL_STATE_INITIALIZED;
+        } else if (ifindex > 0) {
+            priv->fw_state = FIREWALL_STATE_WAIT_STAGE_3;
+            fw_change_zone(self);
+            return;
+        }
+        /* no ifindex, nothing to do for now */
+        break;
+    case FIREWALL_STATE_WAIT_STAGE_3:
+        /* a firewall call for stage3 is pending. Return and wait. */
+        return;
+    default:
+        nm_assert(NM_IN_SET((FirewallState) priv->fw_state,
+                            FIREWALL_STATE_INITIALIZED,
+                            FIREWALL_STATE_WAIT_IP_CONFIG));
+        break;
+    }
+    nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
+
     ipv4_method = nm_device_get_effective_ip_config_method(self, AF_INET);
     if (nm_streq(ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
         /* "auto" usually means DHCPv4 or autoconf6, but it doesn't have to be. Subclasses
@@ -12414,8 +12716,18 @@ activate_stage3_ip_config(NMDevice *self)
     }
 
     ipv6_method = nm_device_get_effective_ip_config_method(self, AF_INET6);
-
-    if (nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+    if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
+        _NMLOG_ip((nm_device_sys_iface_state_is_external(self)
+                   || NM_IN_STRSET(ipv6_method,
+                                   NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                                   NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                                   NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
+                      ? LOGL_DEBUG
+                      : LOGL_WARN,
+                  AF_INET6,
+                  "IPv6 not supported by kernel resulting in \"ipv6.method=disabled\"");
+        ipv6_method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+    } else if (nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
         ipv6_method = klass->get_ip_method_auto(self, AF_INET6);
     }
 
@@ -12436,30 +12748,6 @@ activate_stage3_ip_config(NMDevice *self)
                         nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO));
     }
 
-    /* Add the interface to the specified firewall zone */
-    switch (priv->fw_state) {
-    case FIREWALL_STATE_UNMANAGED:
-        if (nm_device_sys_iface_state_is_external(self)) {
-            /* fake success */
-            priv->fw_state = FIREWALL_STATE_INITIALIZED;
-        } else if (ifindex > 0) {
-            priv->fw_state = FIREWALL_STATE_WAIT_STAGE_3;
-            fw_change_zone(self);
-            return;
-        }
-        /* no ifindex, nothing to do for now */
-        break;
-    case FIREWALL_STATE_WAIT_STAGE_3:
-        /* a firewall call for stage3 is pending. Return and wait. */
-        return;
-    default:
-        nm_assert(NM_IN_SET((FirewallState) priv->fw_state,
-                            FIREWALL_STATE_INITIALIZED,
-                            FIREWALL_STATE_WAIT_IP_CONFIG));
-        break;
-    }
-    nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
-
     if (priv->state < NM_DEVICE_STATE_IP_CONFIG) {
         _dev_ip_state_req_timeout_schedule(self, AF_INET);
         _dev_ip_state_req_timeout_schedule(self, AF_INET6);
@@ -12726,18 +13014,32 @@ out_fail:
 static void
 _dev_ipshared4_spawn_dnsmasq(NMDevice *self)
 {
-    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
-    const char           *ip_iface;
-    gs_free_error GError *error = NULL;
-    NMSettingConnection  *s_con;
-    gboolean              announce_android_metered;
-    NMConnection         *applied;
+    NMDevicePrivate       *priv = NM_DEVICE_GET_PRIVATE(self);
+    const char            *ip_iface;
+    gs_free_error GError  *error = NULL;
+    NMSettingConnection   *s_con;
+    gboolean               announce_android_metered;
+    NMConnection          *applied;
+    gs_unref_array GArray *conflicts = NULL;
+    gboolean               ready;
 
     nm_assert(priv->ipshared_data_4.v4.firewall_config);
     nm_assert(priv->ipshared_data_4.v4.dnsmasq_state_id == 0);
     nm_assert(!priv->ipshared_data_4.v4.dnsmasq_manager);
     nm_assert(priv->ipshared_data_4.v4.l3cd);
 
+    ready = nm_l3cfg_check_ready(priv->l3cfg,
+                                 priv->l3cds[L3_CONFIG_DATA_TYPE_SHARED_4].d,
+                                 AF_INET,
+                                 NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY,
+                                 &conflicts);
+    if (!ready) {
+        _LOGT_ipshared(AF_INET, "address not ready, wait");
+        return;
+    }
+    if (conflicts)
+        goto out_fail;
+
     ip_iface = nm_device_get_ip_iface(self);
     g_return_if_fail(ip_iface);
 
@@ -12782,9 +13084,11 @@ _dev_ipshared4_spawn_dnsmasq(NMDevice *self)
 
     _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_READY);
     _dev_ip_state_check_async(self, AF_INET);
+    nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
     return;
 
 out_fail:
+    nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
     _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
     _dev_ip_state_check_async(self, AF_INET);
 }
@@ -12993,7 +13297,7 @@ _nm_device_hash_check_invalid_keys(GHashTable        *hash,
 
         g_hash_table_iter_init(&iter, hash);
         while (g_hash_table_iter_next(&iter, (gpointer *) &k, NULL)) {
-            if (nm_strv_find_first(whitelist, -1, k) < 0) {
+            if (!nm_strv_contains(whitelist, -1, k)) {
                 first_invalid_key = k;
                 break;
             }
@@ -14952,20 +15256,23 @@ nm_device_set_unmanaged_by_user_settings(NMDevice *self, gboolean now)
 void
 nm_device_set_unmanaged_by_user_udev(NMDevice *self)
 {
-    int      ifindex;
-    gboolean platform_unmanaged = FALSE;
+    NMOptionBool platform_unmanaged;
+    int          ifindex;
 
     ifindex = self->_priv->ifindex;
 
-    if (ifindex <= 0
-        || !nm_platform_link_get_unmanaged(nm_device_get_platform(self),
-                                           ifindex,
-                                           &platform_unmanaged))
+    if (ifindex <= 0)
+        return;
+
+    platform_unmanaged = nm_platform_link_get_unmanaged(nm_device_get_platform(self), ifindex);
+    if (platform_unmanaged == NM_OPTION_BOOL_DEFAULT)
         return;
 
     nm_device_set_unmanaged_by_flags(self,
                                      NM_UNMANAGED_USER_UDEV,
-                                     platform_unmanaged,
+                                     platform_unmanaged == NM_OPTION_BOOL_TRUE
+                                         ? NM_UNMAN_FLAG_OP_SET_UNMANAGED
+                                         : NM_UNMAN_FLAG_OP_SET_MANAGED,
                                      NM_DEVICE_STATE_REASON_USER_REQUESTED);
 }
 
@@ -17128,9 +17435,12 @@ _hw_addr_get_cloned(NMDevice     *self,
 
         addr_out = g_steal_pointer(&hw_addr_generated);
         type_out = HW_ADDR_TYPE_GENERATED;
-    } else if (NM_IN_STRSET(addr, NM_CLONED_MAC_STABLE)) {
+    } else if (nm_streq(addr, NM_CLONED_MAC_STABLE)
+               || (is_wifi && nm_streq(addr, NM_CLONED_MAC_STABLE_SSID))) {
+        gs_free char     *stable_id_free = NULL;
         NMUtilsStableType stable_type;
         const char       *stable_id;
+        GBytes           *ssid = NULL;
 
         if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) {
             /* hm, we already use a generate MAC address. Most certainly, that is from the same
@@ -17138,7 +17448,28 @@ _hw_addr_get_cloned(NMDevice     *self,
             goto out_no_action;
         }
 
-        stable_id         = _prop_get_connection_stable_id(self, connection, &stable_type);
+        if (!nm_streq(addr, NM_CLONED_MAC_STABLE)) {
+            NMSettingWireless *s_wifi;
+
+            s_wifi = nm_connection_get_setting_wireless(connection);
+            if (s_wifi)
+                ssid = nm_setting_wireless_get_ssid(s_wifi);
+        }
+
+        if (G_UNLIKELY(ssid)) {
+            stable_type = nm_utils_stable_id_parse_network_ssid(ssid,
+                                                                nm_connection_get_uuid(connection),
+                                                                TRUE,
+                                                                &stable_id_free);
+            stable_id   = stable_id_free;
+        } else {
+            /* If @addr is NM_CLONED_MAC_STABLE_SSID, and this is not a Wi-Fi
+             * profile, the behavior is the same as NM_CLONED_MAC_STABLE.  Note
+             * that this really shouldn't happen, because we have a Wi-Fi
+             * profile at hand, and an SSID should be set. */
+            stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
+        }
+
         hw_addr_generated = nm_utils_hw_addr_gen_stable_eth(
             stable_type,
             stable_id,
@@ -17982,7 +18313,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         nm_assert(priv->type == NM_DEVICE_TYPE_UNKNOWN);
         priv->type = g_value_get_uint(value);
         nm_assert(priv->type > NM_DEVICE_TYPE_UNKNOWN);
-        nm_assert(priv->type <= NM_DEVICE_TYPE_LOOPBACK);
+        nm_assert(priv->type <= NM_DEVICE_TYPE_HSR);
         break;
     case PROP_LINK_TYPE:
         /* construct-only */
@@ -18331,9 +18662,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Capabilities",
                                                            "u",
                                                            NM_DEVICE_CAPABILITIES),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Address",
-                                                           "u",
-                                                           NM_DEVICE_IP4_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Ip4Address",
+                "u",
+                NM_DEVICE_IP4_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("State", "u", NM_DEVICE_STATE),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("StateReason",
                                                            "(uu)",
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index ff1917b1..3bc03ac1 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -152,7 +152,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ovs_bridge = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Slaves",
+                "ao",
+                NM_DEVICE_SLAVES,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index fd48c2fd..17eb2c2d 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -24,10 +24,21 @@
 
 typedef struct {
     NMOvsdb *ovsdb;
-    GSource *wait_link_idle_source;
-    gulong   wait_link_signal_id;
-    int      wait_link_ifindex;
-    bool     wait_link_is_waiting : 1;
+
+    struct {
+        /* The source for the idle handler to set the TUN ifindex */
+        GSource *tun_set_ifindex_idle_source;
+        /* The cloned MAC to set */
+        char *cloned_mac;
+        /* The id for the signal watching the TUN link to appear/change */
+        gulong tun_link_signal_id;
+        /* The TUN ifindex to set in the idle handler */
+        int tun_ifindex;
+        /* Whether we have determined the cloned MAC */
+        bool cloned_mac_evaluated : 1;
+        /* Whether we are waiting for the kernel link */
+        bool waiting : 1;
+    } wait_link;
 } NMDeviceOvsInterfacePrivate;
 
 struct _NMDeviceOvsInterface {
@@ -46,6 +57,13 @@ G_DEFINE_TYPE(NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
+static void _netdev_tun_link_cb(NMPlatform     *platform,
+                                int             obj_type_i,
+                                int             ifindex,
+                                NMPlatformLink *pllink,
+                                int             change_type_i,
+                                NMDevice       *device);
+
 static const char *
 get_type_description(NMDevice *device)
 {
@@ -117,35 +135,65 @@ check_connection_compatible(NMDevice     *device,
     return TRUE;
 }
 
+static gboolean
+check_waiting_for_link(NMDevice *device, const char *from)
+{
+    NMDeviceOvsInterface        *self     = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv     = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+    NMPlatform                  *platform = nm_device_get_platform(device);
+    const NMPlatformLink        *pllink;
+    int                          ip_ifindex;
+    const char                  *reason = NULL;
+
+    if (!priv->wait_link.waiting)
+        return FALSE;
+
+    nm_assert(priv->wait_link.cloned_mac_evaluated);
+    ip_ifindex = nm_device_get_ip_ifindex(device);
+
+    if (ip_ifindex <= 0) {
+        reason = "no ifindex";
+    } else if (!(pllink = nm_platform_link_get(platform, ip_ifindex))) {
+        reason = "platform link not found";
+    } else if (priv->wait_link.cloned_mac
+               && !nm_utils_hwaddr_matches(priv->wait_link.cloned_mac,
+                                           -1,
+                                           pllink->l_address.data,
+                                           pllink->l_address.len)) {
+        reason = "cloned MAC address is not set yet";
+    } else {
+        priv->wait_link.waiting = FALSE;
+    }
+
+    if (priv->wait_link.waiting)
+        _LOGT(LOGD_DEVICE, "ovs-wait-link(%s): not ready: %s", from, reason);
+
+    return priv->wait_link.waiting;
+}
+
 static void
 link_changed(NMDevice *device, const NMPlatformLink *pllink)
 {
-    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
-    if (!pllink || !priv->wait_link_is_waiting)
+    if (!pllink || !priv->wait_link.waiting)
         return;
 
-    priv->wait_link_is_waiting = FALSE;
+    if (nm_device_get_state(device) != NM_DEVICE_STATE_IP_CONFIG)
+        return;
 
-    if (nm_device_get_state(device) == NM_DEVICE_STATE_IP_CONFIG) {
-        if (!nm_device_hw_addr_set_cloned(device,
-                                          nm_device_get_applied_connection(device),
-                                          FALSE)) {
-            nm_device_devip_set_failed(device, AF_INET, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-            nm_device_devip_set_failed(device, AF_INET6, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-            return;
-        }
+    if (check_waiting_for_link(device, "link-changed"))
+        return;
 
-        nm_device_link_properties_set(device, FALSE);
-        nm_device_bring_up(device);
+    _LOGT(LOGD_CORE, "ovs-wait-link: link is ready after link changed event");
 
-        nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
-        nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
-        nm_device_activate_schedule_stage3_ip_config(device, FALSE);
-        return;
-    }
+    nm_device_link_properties_set(device, FALSE);
+    nm_device_bring_up(device);
 
-    nm_device_activate_schedule_stage2_device_config(device, FALSE);
+    nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
+    nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
+    nm_device_activate_schedule_stage3_ip_config(device, FALSE);
 }
 
 static gboolean
@@ -205,7 +253,9 @@ set_platform_mtu(NMDevice *device, guint32 mtu)
 static gboolean
 ready_for_ip_config(NMDevice *device, gboolean is_manual)
 {
-    return nm_device_get_ip_ifindex(device) > 0;
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device);
+
+    return nm_device_get_ip_ifindex(device) > 0 && !priv->wait_link.waiting;
 }
 
 static gboolean
@@ -215,10 +265,28 @@ _set_ip_ifindex_tun(gpointer user_data)
     NMDeviceOvsInterface        *self   = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv   = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
-    nm_clear_g_source_inst(&priv->wait_link_idle_source);
+    _LOGT(LOGD_CORE,
+          "ovs-wait-link: setting ip-ifindex %d from tun interface",
+          priv->wait_link.tun_ifindex);
+
+    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
+
+    nm_device_set_ip_ifindex(device, priv->wait_link.tun_ifindex);
+
+    if (check_waiting_for_link(device, "set-ip-ifindex-tun")) {
+        /* If the link is not ready, it means the MAC is not set yet. We don't have
+         * a convenient way to monitor for ip-ifindex changes other than listening
+         * for platform events again.*/
+        nm_assert(!priv->wait_link.tun_link_signal_id);
+        priv->wait_link.tun_link_signal_id = g_signal_connect(nm_device_get_platform(device),
+                                                              NM_PLATFORM_SIGNAL_LINK_CHANGED,
+                                                              G_CALLBACK(_netdev_tun_link_cb),
+                                                              self);
+        return G_SOURCE_CONTINUE;
+    }
+
+    _LOGT(LOGD_CORE, "tun link is ready");
 
-    priv->wait_link_is_waiting = FALSE;
-    nm_device_set_ip_ifindex(device, priv->wait_link_ifindex);
     nm_device_link_properties_set(device, FALSE);
 
     nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
@@ -239,75 +307,149 @@ _netdev_tun_link_cb(NMPlatform     *platform,
     const NMPlatformSignalChangeType change_type = change_type_i;
     NMDeviceOvsInterface            *self        = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate     *priv        = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+    int                              ip_ifindex;
 
-    if (change_type == NM_PLATFORM_SIGNAL_ADDED) {
-        if (pllink->type == NM_LINK_TYPE_TUN
-            && nm_streq0(pllink->name, nm_device_get_iface(device))) {
-            nm_clear_g_signal_handler(platform, &priv->wait_link_signal_id);
+    if (pllink->type != NM_LINK_TYPE_TUN || !nm_streq0(pllink->name, nm_device_get_iface(device)))
+        return;
 
-            priv->wait_link_ifindex = ifindex;
+    ip_ifindex = nm_device_get_ip_ifindex(device);
+    if (ip_ifindex > 0) {
+        /* When we have an ifindex, we are only waiting for the MAC to settle */
+        if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
+            return;
 
-            priv->wait_link_idle_source = nm_g_idle_add_source(_set_ip_ifindex_tun, device);
+        if (!check_waiting_for_link(device, "tun-link-changed")) {
+            _LOGT(LOGD_CORE, "ovs-wait-link: tun link is ready, cloned MAC is set");
+
+            nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id);
+            nm_device_link_properties_set(device, FALSE);
+
+            nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
+            nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
+            nm_device_activate_schedule_stage3_ip_config(device, FALSE);
         }
+        return;
     }
+
+    /* No ip-ifindex on the device, set it when the link appears */
+    if (change_type != NM_PLATFORM_SIGNAL_ADDED)
+        return;
+
+    _LOGT(LOGD_CORE,
+          "ovs-wait-link: found matching tun interface, schedule set-ip-ifindex(%d)",
+          ifindex);
+    nm_clear_g_signal_handler(platform, &priv->wait_link.tun_link_signal_id);
+    priv->wait_link.tun_ifindex                 = ifindex;
+    priv->wait_link.tun_set_ifindex_idle_source = nm_g_idle_add_source(_set_ip_ifindex_tun, device);
+}
+
+static gboolean
+ovs_interface_is_netdev_datapath(NMDeviceOvsInterface *self)
+{
+    NMDevice           *device       = NM_DEVICE(self);
+    NMActiveConnection *ac           = NULL;
+    NMSettingOvsBridge *s_ovs_bridge = NULL;
+
+    ac = NM_ACTIVE_CONNECTION(nm_device_get_act_request(device));
+    if (!ac)
+        return FALSE;
+
+    /* get ovs-port active-connection */
+    ac = nm_active_connection_get_master(ac);
+    if (!ac)
+        return FALSE;
+
+    /* get ovs-bridge active-connection */
+    ac = nm_active_connection_get_master(ac);
+    if (!ac)
+        return FALSE;
+
+    s_ovs_bridge =
+        nm_connection_get_setting_ovs_bridge(nm_active_connection_get_applied_connection(ac));
+    if (!s_ovs_bridge)
+        return FALSE;
+
+    return nm_streq0(nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge), "netdev");
 }
 
 static void
 act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    NMActiveConnection          *controller_act = NULL;
-    NMSettingOvsBridge          *s_ovs_bridge   = NULL;
-    NMDeviceOvsInterface        *self           = NM_DEVICE_OVS_INTERFACE(device);
-    NMDeviceOvsInterfacePrivate *priv           = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    /*
+     * When the ovs-interface device enters stage3, it becomes eligible to be attached to
+     * its controller (a ovs-port). If also the ovs-bridge is ready, an entry is created
+     * in the ovsdb in NMDeviceOvsPort->attach_port().
+     * FIXME(l3cfg): we should create the IP ifindex before stage3 start.
+     *
+     * NMDeviceOvsInterface->act_stage3_ip_config() is supposed to perform device-specific
+     * IP configuration on the device. An ovs-interface can be of different types, that
+     * require different handling:
+     *
+     *  - "patch" and "dpdk" interfaces don't have any kernel link associated and thus
+     *  NetworkManager completely skips any kind of IP configuration on them, by returning
+     *  FALSE to ->ready_for_ip_config().
+     *
+     *  - "system" interfaces represent other interface types with kernel link (for
+     *  example, ethernet, bond, etc.) that get attached to a ovs bridge. Once they are
+     *  attached, NetworkManager can start the IP configuration right away.
+     *
+     *  - "internal" interfaces are virtual interfaces created by openvswitch. Once the
+     *  entry is created in the ovsdb, the kernel will create a link for the
+     *  interface. When using the system datapath (the default), the link is of type
+     *  "openvswitch", while when using the netdev (userspace) datapath, the link is a tun
+     *  (tap) one. For both datapath types, we use this method to delay the IP
+     *  configuration until the link appears. Note that ready_for_ip_config() returns FALSE
+     *  when there is no ifindex, and so all the regular IP methods (static, auto, etc.)
+     *  can't proceed.
+     */
 
     if (!_is_internal_interface(device)) {
         nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
         return;
     }
 
-    /* When the ovs-bridge controller is using netdev datapath, the interface
-     * link created is a tun device instead of a ovs-interface. NetworkManager must
-     * detect the creation of the tun link and attach the ifindex to the
-     * ovs-interface device. */
-    controller_act = NM_ACTIVE_CONNECTION(nm_device_get_act_request(device));
-    if (controller_act && nm_device_get_ip_ifindex(device) <= 0 && priv->wait_link_signal_id == 0) {
-        controller_act = nm_active_connection_get_master(controller_act);
-        if (controller_act) {
-            controller_act = nm_active_connection_get_master(controller_act);
-            if (controller_act)
-                s_ovs_bridge = nm_connection_get_setting_ovs_bridge(
-                    nm_active_connection_get_applied_connection(controller_act));
-            if (s_ovs_bridge
-                && nm_streq0(nm_setting_ovs_bridge_get_datapath_type(s_ovs_bridge), "netdev"))
-                priv->wait_link_signal_id = g_signal_connect(nm_device_get_platform(device),
-                                                             NM_PLATFORM_SIGNAL_LINK_CHANGED,
-                                                             G_CALLBACK(_netdev_tun_link_cb),
-                                                             self);
-        }
+    /*
+    * If a ovs interface has the cloned-mac-address property set, we pass the desired MAC
+    * to ovsdb when creating the db entry, and openvswitch will eventually assign it to
+    * the interface. Note that usually the link will not have the desired MAC when it's
+    * created, and so we need to also monitor link changes to detect when the MAC is
+    * ready; only after that we can start IP configuration. Otherwise, the ARP
+    * announcements, the DHCP client-id, etc will use the wrong MAC.
+    */
+    if (!priv->wait_link.cloned_mac_evaluated) {
+        nm_assert(!priv->wait_link.cloned_mac);
+        nm_device_hw_addr_get_cloned(device,
+                                     nm_device_get_applied_connection(device),
+                                     FALSE,
+                                     &priv->wait_link.cloned_mac,
+                                     NULL,
+                                     NULL);
+        priv->wait_link.cloned_mac_evaluated = TRUE;
     }
 
-    /* FIXME(l3cfg): we should create the IP ifindex before stage3 start.
-     *
-     * For now it's here because when the ovs-interface enters stage3, then it's added to the
-     * controller (ovs-port) and the entry is create in the ovsdb. Only after that the kernel
-     * link appears.
-     *
-     * This should change. */
-    if (nm_device_get_ip_ifindex(device) <= 0) {
-        _LOGT(LOGD_DEVICE, "waiting for link to appear");
-        priv->wait_link_is_waiting = TRUE;
+    priv->wait_link.waiting = TRUE;
+    if (check_waiting_for_link(device, addr_family == AF_INET ? "stage3-ipv4" : "stage3-ipv6")) {
         nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_PENDING, NULL);
+        if (nm_device_get_ip_ifindex(device) <= 0 && priv->wait_link.tun_link_signal_id == 0
+            && ovs_interface_is_netdev_datapath(self)) {
+            priv->wait_link.tun_link_signal_id = g_signal_connect(nm_device_get_platform(device),
+                                                                  NM_PLATFORM_SIGNAL_LINK_CHANGED,
+                                                                  G_CALLBACK(_netdev_tun_link_cb),
+                                                                  self);
+        }
         return;
     }
 
-    priv->wait_link_is_waiting = FALSE;
-    nm_clear_g_source_inst(&priv->wait_link_idle_source);
-    nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link_signal_id);
+    _LOGT(LOGD_DEVICE,
+          "ovs-wait-link: link is ready, IPv%c can proceed",
+          nm_utils_addr_family_to_char(addr_family));
 
-    if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) {
-        nm_device_devip_set_failed(device, addr_family, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-        return;
-    }
+    priv->wait_link.waiting = FALSE;
+    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
+    nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
 
     nm_device_link_properties_set(device, FALSE);
     nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
@@ -325,8 +467,11 @@ deactivate(NMDevice *device)
     NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
-    priv->wait_link_is_waiting = FALSE;
-    nm_clear_g_source_inst(&priv->wait_link_idle_source);
+    priv->wait_link.waiting              = FALSE;
+    priv->wait_link.cloned_mac_evaluated = FALSE;
+    nm_clear_g_free(&priv->wait_link.cloned_mac);
+    nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
+    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
 }
 
 typedef struct {
@@ -418,13 +563,27 @@ deactivate_async(NMDevice                  *device,
 
     _LOGT(LOGD_CORE, "deactivate: start async");
 
-    /* We want to ensure that the kernel link for this device is
-     * removed upon disconnection so that it will not interfere with
-     * later activations of the same device. Unfortunately there is
-     * no synchronization mechanism with vswitchd, we only update
-     * ovsdb and wait that changes are picked up.
-     */
+    nm_clear_g_signal_handler(nm_device_get_platform(device), &priv->wait_link.tun_link_signal_id);
+    nm_clear_g_source_inst(&priv->wait_link.tun_set_ifindex_idle_source);
+    priv->wait_link.cloned_mac_evaluated = FALSE;
+    nm_clear_g_free(&priv->wait_link.cloned_mac);
 
+    /* We want to ensure that the kernel link for this device is removed upon
+     * disconnection, so that it will not interfere with later activations of the same
+     * device.
+     *
+     * To do so, we need to be very careful, because unfortunately there is no
+     * synchronization mechanism with vswitchd: we only update ovsdb, wait that changes
+     * are picked up and we see the effects on the kernel interface (appearing or going
+     * away).
+     *
+     * That means for example that if the ovs interface entered stage3 and the entry was
+     * added to the ovsdb, we expect a link to appear. If we disconnect at this point, we
+     * delete the entry from the ovsdb. Now we don't know if ovs-vswitchd will see two
+     * updates or only one. In other words, we don't know if the interface will appear and
+     * go away, or if it will not appear ever. In this situation, the solution is to wait
+     * with a timeout.
+     */
     data  = g_slice_new(DeactivateData);
     *data = (DeactivateData){
         .self               = g_object_ref(self),
@@ -433,7 +592,7 @@ deactivate_async(NMDevice                  *device,
         .callback_user_data = callback_user_data,
     };
 
-    if (!priv->wait_link_is_waiting
+    if (!priv->wait_link.waiting
         && !nm_platform_link_get_by_ifname(nm_device_get_platform(device),
                                            nm_device_get_iface(device))) {
         _LOGT(LOGD_CORE, "deactivate: link not present, proceeding");
@@ -442,20 +601,15 @@ deactivate_async(NMDevice                  *device,
         return;
     }
 
-    nm_clear_g_source_inst(&priv->wait_link_idle_source);
-
-    if (priv->wait_link_is_waiting) {
-        /* At this point we have issued an INSERT and a DELETE
-         * command for the interface to ovsdb. We don't know if
-         * vswitchd will see the two updates or only one. We
-         * must add a timeout to avoid waiting forever in case
-         * the link doesn't appear.
-         */
+    if (priv->wait_link.waiting) {
+        /* Here we have issued an INSERT and a DELETE command for the interface to ovsdb,
+         * and must wait with a timeout. */
         data->link_timeout_id = g_timeout_add(6000, deactivate_link_timeout, data);
         _LOGT(LOGD_DEVICE, "deactivate: waiting for link to disappear in 6 seconds");
     } else
         _LOGT(LOGD_DEVICE, "deactivate: waiting for link to disappear");
 
+    priv->wait_link.waiting = FALSE;
     data->cancelled_id =
         g_cancellable_connect(cancellable, G_CALLBACK(deactivate_cancelled_cb), data, NULL);
     data->link_changed_id = g_signal_connect(nm_device_get_platform(device),
@@ -506,6 +660,10 @@ dispose(GObject *object)
     NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(object);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
+    nm_assert(!priv->wait_link.waiting);
+    nm_assert(priv->wait_link.tun_link_signal_id == 0);
+    nm_assert(!priv->wait_link.tun_set_ifindex_idle_source);
+
     if (priv->ovsdb) {
         g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self);
         g_clear_object(&priv->ovsdb);
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index 5ede46e9..258c72f2 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -266,7 +266,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ovs_port = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_OVS_PORT,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Slaves",
+                "ao",
+                NM_DEVICE_SLAVES,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index 4d748362..d8c3c2c2 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -290,9 +290,9 @@ master_update_slave_connection(NMDevice     *device,
     g_free(port_config);
 
     g_object_set(nm_connection_get_setting_connection(connection),
-                 NM_SETTING_CONNECTION_MASTER,
+                 NM_SETTING_CONNECTION_CONTROLLER,
                  nm_connection_get_uuid(applied_connection),
-                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_CONNECTION_PORT_TYPE,
                  NM_SETTING_TEAM_SETTING_NAME,
                  NULL);
     return TRUE;
@@ -1090,9 +1090,21 @@ static const NMDBusInterfaceInfoExtended interface_info_device_team = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_TEAM,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Carrier",
+                "b",
+                NM_DEVICE_CARRIER,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Slaves",
+                "ao",
+                NM_DEVICE_SLAVES,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Config",
                                                            "s",
                                                            NM_DEVICE_TEAM_CONFIG), ), ),
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c
index 73fd4716..fadc6722 100644
--- a/src/core/devices/wifi/nm-device-iwd-p2p.c
+++ b/src/core/devices/wifi/nm-device-iwd-p2p.c
@@ -1144,7 +1144,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
         .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_wifi_p2p_peer_added,
                                                 &nm_signal_info_wifi_p2p_peer_removed, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peers",
                                                            "ao",
                                                            NM_DEVICE_IWD_P2P_PEERS), ), ),
diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index 436c7847..8e1e779b 100644
--- a/src/core/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -497,7 +497,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_olpc_mesh = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_OLPC_MESH,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Companion",
                                                            "o",
                                                            NM_DEVICE_OLPC_MESH_COMPANION),
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index fa8cb8fa..981811a3 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -15,6 +15,7 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-glib-aux/nm-ref-string.h"
 #include "libnm-platform/nm-platform.h"
@@ -982,23 +983,24 @@ device_state_changed(NMDevice           *device,
 }
 
 static void
-impl_device_wifi_p2p_start_find(NMDBusObject                      *obj,
-                                const NMDBusInterfaceInfoExtended *interface_info,
-                                const NMDBusMethodInfoExtended    *method_info,
-                                GDBusConnection                   *connection,
-                                const char                        *sender,
-                                GDBusMethodInvocation             *invocation,
-                                GVariant                          *parameters)
+p2p_start_find_auth_cb(NMDevice              *device,
+                       GDBusMethodInvocation *invocation,
+                       NMAuthSubject         *subject,
+                       GError                *error,
+                       gpointer               user_data)
 {
-    NMDeviceWifiP2P           *self    = NM_DEVICE_WIFI_P2P(obj);
+    NMDeviceWifiP2P           *self    = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate    *priv    = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
-    gs_unref_variant GVariant *options = NULL;
+    gs_unref_variant GVariant *options = user_data;
     const char                *opts_key;
     GVariant                  *opts_val;
     GVariantIter               iter;
     gint32                     timeout = 30;
 
-    g_variant_get(parameters, "(@a{sv})", &options);
+    if (error) {
+        g_dbus_method_invocation_return_gerror(invocation, error);
+        return;
+    }
 
     g_variant_iter_init(&iter, options);
     while (g_variant_iter_next(&iter, "{&sv}", &opts_key, &opts_val)) {
@@ -1050,17 +1052,43 @@ impl_device_wifi_p2p_start_find(NMDBusObject                      *obj,
 }
 
 static void
-impl_device_wifi_p2p_stop_find(NMDBusObject                      *obj,
-                               const NMDBusInterfaceInfoExtended *interface_info,
-                               const NMDBusMethodInfoExtended    *method_info,
-                               GDBusConnection                   *connection,
-                               const char                        *sender,
-                               GDBusMethodInvocation             *invocation,
-                               GVariant                          *parameters)
+impl_device_wifi_p2p_start_find(NMDBusObject                      *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended    *method_info,
+                                GDBusConnection                   *connection,
+                                const char                        *sender,
+                                GDBusMethodInvocation             *invocation,
+                                GVariant                          *parameters)
+{
+    gs_unref_variant GVariant *options = NULL;
+
+    g_variant_get(parameters, "(@a{sv})", &options);
+
+    nm_device_auth_request(NM_DEVICE(obj),
+                           invocation,
+                           NULL,
+                           NM_AUTH_PERMISSION_WIFI_SCAN,
+                           TRUE,
+                           NULL,
+                           p2p_start_find_auth_cb,
+                           g_steal_pointer(&options));
+}
+
+static void
+p2p_stop_find_auth_cb(NMDevice              *device,
+                      GDBusMethodInvocation *invocation,
+                      NMAuthSubject         *subject,
+                      GError                *error,
+                      gpointer               user_data)
 {
-    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(obj);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
+    if (error) {
+        g_dbus_method_invocation_return_gerror(invocation, error);
+        return;
+    }
+
     if (!priv->mgmt_iface) {
         g_dbus_method_invocation_return_error_literal(
             invocation,
@@ -1075,6 +1103,25 @@ impl_device_wifi_p2p_stop_find(NMDBusObject                      *obj,
     g_dbus_method_invocation_return_value(invocation, NULL);
 }
 
+static void
+impl_device_wifi_p2p_stop_find(NMDBusObject                      *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended    *method_info,
+                               GDBusConnection                   *connection,
+                               const char                        *sender,
+                               GDBusMethodInvocation             *invocation,
+                               GVariant                          *parameters)
+{
+    nm_device_auth_request(NM_DEVICE(obj),
+                           invocation,
+                           NULL,
+                           NM_AUTH_PERMISSION_WIFI_SCAN,
+                           TRUE,
+                           NULL,
+                           p2p_stop_find_auth_cb,
+                           NULL);
+}
+
 /*****************************************************************************/
 
 NMSupplicantInterface *
@@ -1171,7 +1218,11 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
         .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_wifi_p2p_peer_added,
                                                 &nm_signal_info_wifi_p2p_peer_removed, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "HwAddress",
+                "s",
+                NM_DEVICE_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peers",
                                                            "ao",
                                                            NM_DEVICE_WIFI_P2P_PEERS), ), ),
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 43772834..2cd41b27 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -3191,27 +3191,28 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
         GBytes       *ssid;
         gsize         ssid_len;
         const guint8 *ssid_data;
-        const guint8  random_seed[16] = {0x9a,
-                                         0xdc,
-                                         0x86,
-                                         0x9a,
-                                         0xa8,
-                                         0xa2,
-                                         0x07,
-                                         0x97,
-                                         0xbe,
-                                         0x6d,
-                                         0xe6,
-                                         0x99,
-                                         0x9f,
-                                         0xa8,
-                                         0x09,
-                                         0x2b};
 
         /* Calculate a stable "random" number based on the SSID. */
         ssid      = nm_setting_wireless_get_ssid(s_wifi);
         ssid_data = g_bytes_get_data(ssid, &ssid_len);
-        rnd       = c_siphash_hash(random_seed, ssid_data, ssid_len);
+        rnd       = c_siphash_hash(NM_HASH_SEED_16(0x9a,
+                                             0xdc,
+                                             0x86,
+                                             0x9a,
+                                             0xa8,
+                                             0xa2,
+                                             0x07,
+                                             0x97,
+                                             0xbe,
+                                             0x6d,
+                                             0xe6,
+                                             0x99,
+                                             0x9f,
+                                             0xa8,
+                                             0x09,
+                                             0x2b),
+                             ssid_data,
+                             ssid_len);
     }
 
     if (nm_streq0(band, "a")) {
diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c
index 0d4daf21..76a342e2 100644
--- a/src/core/devices/wifi/nm-iwd-manager.c
+++ b/src/core/devices/wifi/nm-iwd-manager.c
@@ -810,7 +810,7 @@ sett_conn_changed(NMSettingsConnection   *sett_conn,
             nm_log_dbg(LOGD_WIFI,
                        "iwd: profile at %s not removed: %s (%i)",
                        orig_full_path,
-                       strerror(errno),
+                       nm_strerror_native(errno),
                        errno);
 
         removed = TRUE;
@@ -1431,7 +1431,10 @@ try_delete_file:
     if (g_remove(full_path) == 0)
         _LOGD("IWD profile at %s removed", full_path);
     else if (errno != ENOENT)
-        _LOGD("IWD profile at %s not removed: %s (%i)", full_path, strerror(errno), errno);
+        _LOGD("IWD profile at %s not removed: %s (%i)",
+              full_path,
+              nm_strerror_native(errno),
+              errno);
 }
 
 static void
@@ -1899,7 +1902,7 @@ nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap)
     NM80211ApSecurityFlags sec_flags = nm_wifi_ap_get_wpa_flags(ap) | nm_wifi_ap_get_rsn_flags(ap);
 
     ssid_bytes = g_bytes_get_data(nm_wifi_ap_get_ssid(ap), &ssid_len);
-    ssid_len   = MIN(ssid_len, 32);
+    ssid_len   = NM_MIN(ssid_len, 32u);
     memcpy(name_buf, ssid_bytes, ssid_len);
     name_buf[ssid_len] = '\0';
 
diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index 825a5aa8..d4d3815e 100644
--- a/src/core/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -36,6 +36,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMWifiAP,
                              PROP_HW_ADDRESS,
                              PROP_MODE,
                              PROP_MAX_BITRATE,
+                             PROP_BANDWIDTH,
                              PROP_STRENGTH,
                              PROP_LAST_SEEN, );
 
@@ -47,6 +48,7 @@ struct _NMWifiAPPrivate {
     guint8       strength;
     guint32      freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
     guint32      max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */
+    guint32      bandwidth;   /* Bandwidth of the AP in MHz */
 
     gint64
         last_seen_msec; /* Timestamp when the AP was seen lastly (in nm_utils_get_monotonic_timestamp_*() scale).
@@ -277,6 +279,32 @@ nm_wifi_ap_get_max_bitrate(NMWifiAP *ap)
     return NM_WIFI_AP_GET_PRIVATE(ap)->max_bitrate;
 }
 
+guint32
+nm_wifi_ap_get_bandwidth(NMWifiAP *ap)
+{
+    g_return_val_if_fail(NM_IS_WIFI_AP(ap), 0);
+    g_return_val_if_fail(nm_dbus_object_is_exported(NM_DBUS_OBJECT(ap)), 0);
+
+    return NM_WIFI_AP_GET_PRIVATE(ap)->bandwidth;
+}
+
+gboolean
+nm_wifi_ap_set_bandwidth(NMWifiAP *ap, guint32 bandwidth)
+{
+    NMWifiAPPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE);
+
+    priv = NM_WIFI_AP_GET_PRIVATE(ap);
+
+    if (priv->bandwidth != bandwidth) {
+        priv->bandwidth = bandwidth;
+        _notify(ap, PROP_BANDWIDTH);
+        return TRUE;
+    }
+    return FALSE;
+}
+
 gboolean
 nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate)
 {
@@ -393,6 +421,7 @@ nm_wifi_ap_update_from_properties(NMWifiAP *ap, const NMSupplicantBssInfo *bss_i
     }
 
     changed |= nm_wifi_ap_set_max_bitrate(ap, bss_info->max_rate);
+    changed |= nm_wifi_ap_set_bandwidth(ap, bss_info->bandwidth);
 
     if (priv->metered != bss_info->metered) {
         priv->metered = bss_info->metered;
@@ -683,6 +712,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     case PROP_MAX_BITRATE:
         g_value_set_uint(value, priv->max_bitrate);
         break;
+    case PROP_BANDWIDTH:
+        g_value_set_uint(value, priv->bandwidth);
+        break;
     case PROP_STRENGTH:
         g_value_set_uchar(value, priv->strength);
         break;
@@ -873,6 +905,7 @@ static const NMDBusInterfaceInfoExtended interface_info_access_point = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("MaxBitrate",
                                                            "u",
                                                            NM_WIFI_AP_MAX_BITRATE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Bandwidth", "u", NM_WIFI_AP_BANDWIDTH),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Strength", "y", NM_WIFI_AP_STRENGTH),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LastSeen",
                                                            "i",
@@ -979,6 +1012,14 @@ nm_wifi_ap_class_init(NMWifiAPClass *ap_class)
                                                       -1,
                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
+    obj_properties[PROP_BANDWIDTH] = g_param_spec_uint(NM_WIFI_AP_BANDWIDTH,
+                                                       "",
+                                                       "",
+                                                       0,
+                                                       G_MAXUINT32,
+                                                       0,
+                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
 
diff --git a/src/core/devices/wifi/nm-wifi-ap.h b/src/core/devices/wifi/nm-wifi-ap.h
index 93dc8edf..0f229640 100644
--- a/src/core/devices/wifi/nm-wifi-ap.h
+++ b/src/core/devices/wifi/nm-wifi-ap.h
@@ -27,6 +27,7 @@
 #define NM_WIFI_AP_HW_ADDRESS  "hw-address"
 #define NM_WIFI_AP_MODE        "mode"
 #define NM_WIFI_AP_MAX_BITRATE "max-bitrate"
+#define NM_WIFI_AP_BANDWIDTH   "bandwidth"
 #define NM_WIFI_AP_STRENGTH    "strength"
 #define NM_WIFI_AP_LAST_SEEN   "last-seen"
 
@@ -78,6 +79,8 @@ guint32                nm_wifi_ap_get_freq(NMWifiAP *ap);
 gboolean               nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq);
 guint32                nm_wifi_ap_get_max_bitrate(NMWifiAP *ap);
 gboolean               nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate);
+guint32                nm_wifi_ap_get_bandwidth(NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_bandwidth(NMWifiAP *ap, guint32 bandwidth);
 gboolean               nm_wifi_ap_get_fake(const NMWifiAP *ap);
 gboolean               nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake);
 NM80211ApFlags         nm_wifi_ap_get_flags(const NMWifiAP *self);
diff --git a/src/core/devices/wifi/nm-wifi-common.c b/src/core/devices/wifi/nm-wifi-common.c
index 681d9cb5..e3639ec5 100644
--- a/src/core/devices/wifi/nm-wifi-common.c
+++ b/src/core/devices/wifi/nm-wifi-common.c
@@ -132,7 +132,8 @@ const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
                 NM_DEFINE_GDBUS_METHOD_INFO_INIT(
                     "GetAccessPoints",
                     .out_args = NM_DEFINE_GDBUS_ARG_INFOS(
-                        NM_DEFINE_GDBUS_ARG_INFO("access_points", "ao"), ), ),
+                        NM_DEFINE_GDBUS_ARG_INFO("access_points", "ao"), ),
+                    .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
                 .handle = impl_device_wifi_get_access_points, ),
             NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
                 NM_DEFINE_GDBUS_METHOD_INFO_INIT(
@@ -150,9 +151,11 @@ const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
                                                 &nm_signal_info_wireless_access_point_removed, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PermHwAddress",
-                                                           "s",
-                                                           NM_DEVICE_PERM_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "PermHwAddress",
+                "s",
+                NM_DEVICE_PERM_HW_ADDRESS,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "u", NM_DEVICE_WIFI_MODE),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Bitrate", "u", NM_DEVICE_WIFI_BITRATE),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("AccessPoints",