summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.c2
-rw-r--r--src/core/devices/nm-acd-manager.c11
-rw-r--r--src/core/devices/nm-device-bond.c42
-rw-r--r--src/core/devices/nm-device-ethernet.c4
-rw-r--r--src/core/devices/nm-device.c147
-rw-r--r--src/core/devices/nm-device.h35
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c10
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c15
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c8
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c1
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c10
-rw-r--r--src/core/dhcp/nm-dhcp-client.c7
-rw-r--r--src/core/dhcp/nm-dhcp-client.h2
-rw-r--r--src/core/dhcp/nm-dhcp-dhcpcd.c42
-rw-r--r--src/core/dhcp/nm-dhcp-nettools.c29
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c46
-rw-r--r--src/core/dhcp/nm-dhcp-utils.c2
-rw-r--r--src/core/dns/nm-dns-manager.c16
-rw-r--r--src/core/initrd/nmi-cmdline-reader.c29
-rw-r--r--src/core/initrd/tests/test-cmdline-reader.c67
-rw-r--r--src/core/nm-config-data.c2
-rw-r--r--src/core/nm-core-utils.c12
-rw-r--r--src/core/nm-ip4-config.c15
-rw-r--r--src/core/nm-ip6-config.c33
-rw-r--r--src/core/nm-manager.c70
-rw-r--r--src/core/nm-policy.c1
-rw-r--r--src/core/nm-types.h12
-rw-r--r--src/core/platform/nm-platform.c124
-rw-r--r--src/core/platform/tests/test-common.c5
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c40
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected15
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c80
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.c6
-rw-r--r--src/core/supplicant/nm-supplicant-config.c5
-rw-r--r--src/core/supplicant/nm-supplicant-interface.c28
-rw-r--r--src/core/supplicant/nm-supplicant-manager.c33
-rw-r--r--src/core/supplicant/nm-supplicant-types.h15
-rw-r--r--src/core/tests/test-systemd.c10
41 files changed, 711 insertions, 326 deletions
diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c
index dd998d29..459ea409 100644
--- a/src/core/devices/bluetooth/nm-bluez-manager.c
+++ b/src/core/devices/bluetooth/nm-bluez-manager.c
@@ -2879,6 +2879,8 @@ dispose(GObject *object)
     g_clear_object(&priv->dbus_connection);
 
     nm_clear_pointer(&priv->bzobjs, g_hash_table_destroy);
+    nm_clear_pointer(&priv->conn_data_heads, g_hash_table_destroy);
+    nm_clear_pointer(&priv->conn_data_elems, g_hash_table_destroy);
 }
 
 static void
diff --git a/src/core/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c
index b95f90fd..81a28797 100644
--- a/src/core/devices/nm-acd-manager.c
+++ b/src/core/devices/nm-acd-manager.c
@@ -188,6 +188,7 @@ acd_event(int fd, GIOCondition condition, gpointer data)
         char          to_string_buffer[ACD_EVENT_TO_STRING_BUF_SIZE];
         gs_free char *hwaddr_str         = NULL;
         gboolean      check_probing_done = FALSE;
+        char          buf[ETH_ALEN * 3];
 
         switch (event->event) {
         case N_ACD_EVENT_READY:
@@ -202,8 +203,9 @@ acd_event(int fd, GIOCondition condition, gpointer data)
                           nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
                           acd_error_to_string(r));
                 } else {
-                    _LOGD("announcing address %s",
-                          _nm_utils_inet4_ntop(info->address, address_str));
+                    _LOGD("announcing address %s (hw-addr %s)",
+                          _nm_utils_inet4_ntop(info->address, address_str),
+                          _nm_utils_hwaddr_ntoa(self->hwaddr, ETH_ALEN, TRUE, buf, sizeof(buf)));
                 }
             }
             check_probing_done = TRUE;
@@ -394,6 +396,7 @@ nm_acd_manager_announce_addresses(NMAcdManager *self)
     int            r;
     int            fd;
     gboolean       success = TRUE;
+    char           buf[ETH_ALEN * 3];
 
     r = acd_init(self);
     if (r) {
@@ -428,7 +431,9 @@ nm_acd_manager_announce_addresses(NMAcdManager *self)
                       acd_error_to_string(r));
                 success = FALSE;
             } else
-                _LOGD("announcing address %s", _nm_utils_inet4_ntop(info->address, sbuf));
+                _LOGD("announcing address %s (hw-addr %s)",
+                      _nm_utils_inet4_ntop(info->address, sbuf),
+                      _nm_utils_hwaddr_ntoa(self->hwaddr, ETH_ALEN, TRUE, buf, sizeof(buf)));
         }
     }
 
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index f68c080b..9c1c484f 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -34,7 +34,7 @@
         NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \
         NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,         \
         NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,       \
-        NM_SETTING_BOND_OPTION_NUM_GRAT_ARP
+        NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY
 
 #define OPTIONS_REAPPLY_SUBSET                                                            \
     NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY,                        \
@@ -46,7 +46,7 @@
         NM_SETTING_BOND_OPTION_MIN_LINKS, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE,       \
         NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, NM_SETTING_BOND_OPTION_RESEND_IGMP,      \
         NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,      \
-        NM_SETTING_BOND_OPTION_NUM_GRAT_ARP
+        NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY
 
 #define OPTIONS_REAPPLY_FULL                                     \
     OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \
@@ -109,6 +109,24 @@ _set_bond_attr(NMDevice *device, const char *attr, const char *value)
     int           ifindex = nm_device_get_ifindex(device);
     gboolean      ret;
 
+    nm_assert(attr && attr[0]);
+    nm_assert(value);
+
+    if (nm_streq(value, NM_BOND_AD_ACTOR_SYSTEM_DEFAULT)
+        && nm_streq(attr, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) {
+        gs_free char *cur_val = NULL;
+
+        /* kernel does not allow setting ad_actor_system to "00:00:00:00:00:00". We would thus
+         * log an EINVAL error. Avoid that... at least, if the value is already "00:00:00:00:00:00". */
+        cur_val =
+            nm_platform_sysctl_master_get_option(nm_device_get_platform(device), ifindex, attr);
+        if (nm_streq0(cur_val, NM_BOND_AD_ACTOR_SYSTEM_DEFAULT))
+            return TRUE;
+
+        /* OK, the current value is different, and we will proceed setting "00:00:00:00:00:00".
+         * That will fail, and we will log a warning. There is nothing else to do. */
+    }
+
     ret =
         nm_platform_sysctl_master_set_option(nm_device_get_platform(device), ifindex, attr, value);
     if (!ret)
@@ -426,9 +444,10 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
         _LOGD(LOGD_BOND, "bond slave %s is already released", nm_device_get_ip_iface(slave));
 
     if (configure) {
-        /* When the last slave is released the bond MAC will be set to a random
-         * value by kernel; remember the current one and restore it afterwards.
-         */
+        NMConnection *  applied;
+        NMSettingWired *s_wired;
+        const char *    cloned_mac;
+
         address = g_strdup(nm_device_get_hw_address(device));
 
         if (ifindex_slave > 0) {
@@ -443,9 +462,16 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
             }
         }
 
-        nm_platform_process_events(nm_device_get_platform(device));
-        if (nm_device_update_hw_address(device))
-            nm_device_hw_addr_set(device, address, "restore", FALSE);
+        if ((applied = nm_device_get_applied_connection(device))
+            && ((s_wired = nm_connection_get_setting_wired(applied)))
+            && ((cloned_mac = nm_setting_wired_get_cloned_mac_address(s_wired)))) {
+            /* When the last slave is released the bond MAC will be set to a random
+             * value by kernel; if we have set a cloned-mac-address, we need to
+             * restore it to the previous value. */
+            nm_platform_process_events(nm_device_get_platform(device));
+            if (nm_device_update_hw_address(device))
+                nm_device_hw_addr_set(device, address, "restore", FALSE);
+        }
 
         /* Kernel bonding code "closes" the slave when releasing it, (which clears
          * IFF_UP), so we must bring it back up here to ensure carrier changes and
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 44428869..59f6c613 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -730,7 +730,9 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
 
     if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
         supplicant_interface_release(self);
-        wired_auth_cond_fail(self, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        nm_device_state_changed(NM_DEVICE(self),
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
         return;
     }
 
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 040dd0b4..3874e586 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -125,7 +125,6 @@ typedef struct {
 typedef struct {
     NMDevice *device;
     guint     idle_add_id;
-    int       ifindex;
 } DeleteOnDeactivateData;
 
 typedef struct {
@@ -4703,10 +4702,6 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co
      */
     nm_device_update_hw_address(self);
 
-    /* Send ARP announcements if did not yet and have addresses. */
-    if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !priv->acd.announcing)
-        nm_device_arp_announce(self);
-
     /* Restart IP configuration if we're waiting for slaves.  Do this
      * after updating the hardware address as IP config may need the
      * new address.
@@ -5053,6 +5048,10 @@ nm_device_set_carrier(NMDevice *self, gboolean carrier)
             nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
             _carrier_wait_check_queued_act_request(self);
         }
+
+        /* Send ARP announcements if did not yet and have carrier. */
+        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !priv->acd.announcing)
+            nm_device_arp_announce(self);
     } else {
         if (priv->carrier_wait_id)
             nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
@@ -5415,11 +5414,11 @@ device_link_changed(NMDevice *self)
                 /* Ensure the assume check is queued before any queued state changes
                  * from the transition to UNAVAILABLE.
                  */
-                nm_device_queue_recheck_assume(self);
                 reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
             }
         }
 
+        nm_device_queue_recheck_assume(self);
         nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
     }
 
@@ -8362,26 +8361,23 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
 static gboolean
 tc_commit(NMDevice *self)
 {
-    NMConnection *    connection          = NULL;
     gs_unref_ptrarray GPtrArray *qdiscs   = NULL;
     gs_unref_ptrarray GPtrArray *tfilters = NULL;
-    NMSettingTCConfig *          s_tc     = NULL;
+    NMSettingTCConfig *          s_tc;
     NMPlatform *                 platform;
     int                          ip_ifindex;
 
-    platform   = nm_device_get_platform(self);
-    connection = nm_device_get_applied_connection(self);
-    if (connection)
-        s_tc = nm_connection_get_setting_tc_config(connection);
+    s_tc = nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG);
+    if (!s_tc)
+        return TRUE;
 
     ip_ifindex = nm_device_get_ip_ifindex(self);
     if (!ip_ifindex)
-        return s_tc == NULL;
+        return FALSE;
 
-    if (s_tc) {
-        qdiscs   = nm_utils_qdiscs_from_tc_setting(platform, s_tc, ip_ifindex);
-        tfilters = nm_utils_tfilters_from_tc_setting(platform, s_tc, ip_ifindex);
-    }
+    platform = nm_device_get_platform(self);
+    qdiscs   = nm_utils_qdiscs_from_tc_setting(platform, s_tc, ip_ifindex);
+    tfilters = nm_utils_tfilters_from_tc_setting(platform, s_tc, ip_ifindex);
 
     if (!nm_platform_qdisc_sync(platform, ip_ifindex, qdiscs))
         return FALSE;
@@ -9980,7 +9976,9 @@ nm_device_request_ip6_prefixes(NMDevice *self, int needed_prefixes)
         _LOGD(LOGD_IP6, "ipv6-pd: asking DHCPv6 for %d prefixes", needed_prefixes);
         nm_device_dhcp6_renew(self, FALSE);
     } else {
-        _LOGI(LOGD_IP6, "ipv6-pd: device doesn't use DHCPv6, can't request prefixes");
+        priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_OTHERCONF;
+        _LOGD(LOGD_DEVICE | LOGD_DHCP6, "ipv6-pd: starting DHCPv6 to request a prefix");
+        dhcp6_start(self, FALSE);
     }
 }
 
@@ -11679,8 +11677,6 @@ start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
 
     nm_utils_share_rules_add_all_rules(share_rules, ip_iface, ip4_addr->address, ip4_addr->plen);
 
-    nm_utils_share_rules_apply(share_rules, TRUE);
-
     nm_act_request_set_shared(req, share_rules);
 
     conn  = nm_act_request_get_applied_connection(req);
@@ -11792,7 +11788,6 @@ activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
     const char *     method;
     int              ip_ifindex;
     int              errsv;
-    gboolean         do_announce = FALSE;
 
     req = nm_device_get_act_request(self);
     g_assert(req);
@@ -11918,31 +11913,13 @@ activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
         }
     }
 
-    if (IS_IPv4) {
-        /* Send ARP announcements */
-
-        if (nm_device_is_master(self)) {
-            CList *    iter;
-            SlaveInfo *info;
-
-            /* Skip announcement if there are no device enslaved, for two reasons:
-             * 1) the master has a temporary MAC address until the first slave comes
-             * 2) announcements are going to be dropped anyway without slaves
-             */
-            do_announce = FALSE;
-
-            c_list_for_each (iter, &priv->slaves) {
-                info = c_list_entry(iter, SlaveInfo, lst_slave);
-                if (info->slave_is_enslaved) {
-                    do_announce = TRUE;
-                    break;
-                }
-            }
-        } else
-            do_announce = TRUE;
-
-        if (do_announce)
-            nm_device_arp_announce(self);
+    if (IS_IPv4 && priv->carrier) {
+        /* We send ARP announcements only when the link gets carrier,
+         * otherwise the announcements would be lost. Furthermore, for
+         * controllers having carrier implies that there is at least one
+         * port and therefore the MAC address is the correct one.
+         */
+        nm_device_arp_announce(self);
     }
 
     if (IS_IPv4) {
@@ -12141,28 +12118,19 @@ nm_device_is_nm_owned(NMDevice *self)
 static gboolean
 delete_on_deactivate_link_delete(gpointer user_data)
 {
-    DeleteOnDeactivateData *data = user_data;
-    NMDevice *              self = data->device;
+    DeleteOnDeactivateData *data        = user_data;
+    nm_auto_unref_object NMDevice *self = data->device;
+    NMDevicePrivate *              priv = NM_DEVICE_GET_PRIVATE(self);
+    gs_free_error GError *error         = NULL;
 
     _LOGD(LOGD_DEVICE,
-          "delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)",
-          data->ifindex,
+          "delete_on_deactivate: cleanup and delete virtual link (id=%u)",
           data->idle_add_id);
 
-    if (data->device) {
-        NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(data->device);
-        gs_free_error GError *error = NULL;
-
-        g_object_remove_weak_pointer(G_OBJECT(data->device), (void **) &data->device);
-        priv->delete_on_deactivate_data = NULL;
+    priv->delete_on_deactivate_data = NULL;
 
-        if (!nm_device_unrealize(data->device, TRUE, &error))
-            _LOGD(LOGD_DEVICE,
-                  "delete_on_deactivate: unrealizing %d failed (%s)",
-                  data->ifindex,
-                  error->message);
-    } else if (data->ifindex > 0)
-        nm_platform_link_delete(nm_device_get_platform(self), data->ifindex);
+    if (!nm_device_unrealize(self, TRUE, &error))
+        _LOGD(LOGD_DEVICE, "delete_on_deactivate: unrealizing failed (%s)", error->message);
 
     nm_device_emit_recheck_auto_activate(self);
 
@@ -12181,17 +12149,16 @@ delete_on_deactivate_unschedule(NMDevice *self)
         priv->delete_on_deactivate_data = NULL;
 
         g_source_remove(data->idle_add_id);
-        g_object_remove_weak_pointer(G_OBJECT(self), (void **) &data->device);
         _LOGD(LOGD_DEVICE,
-              "delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)",
-              data->ifindex,
+              "delete_on_deactivate: cancel cleanup and delete virtual link (id=%u)",
               data->idle_add_id);
+        g_object_unref(data->device);
         g_free(data);
     }
 }
 
 static void
-delete_on_deactivate_check_and_schedule(NMDevice *self, int ifindex)
+delete_on_deactivate_check_and_schedule(NMDevice *self)
 {
     NMDevicePrivate *       priv = NM_DEVICE_GET_PRIVATE(self);
     DeleteOnDeactivateData *data;
@@ -12208,16 +12175,13 @@ delete_on_deactivate_check_and_schedule(NMDevice *self, int ifindex)
         return;
     delete_on_deactivate_unschedule(self); /* always cancel and reschedule */
 
-    data = g_new(DeleteOnDeactivateData, 1);
-    g_object_add_weak_pointer(G_OBJECT(self), (void **) &data->device);
-    data->device                    = self;
-    data->ifindex                   = ifindex;
+    data                            = g_new(DeleteOnDeactivateData, 1);
+    data->device                    = g_object_ref(self);
     data->idle_add_id               = g_idle_add(delete_on_deactivate_link_delete, data);
     priv->delete_on_deactivate_data = data;
 
     _LOGD(LOGD_DEVICE,
-          "delete_on_deactivate: schedule cleanup and delete virtual link #%d (id=%u)",
-          ifindex,
+          "delete_on_deactivate: schedule cleanup and delete virtual link (id=%u)",
           data->idle_add_id);
 }
 
@@ -13478,12 +13442,13 @@ nm_device_set_ip_config(NMDevice *  self,
                         gboolean    commit,
                         GPtrArray * ip4_dev_route_blacklist)
 {
-    NMDevicePrivate *     priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int             IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMIPConfig *          old_config;
-    gboolean              has_changes = FALSE;
-    gboolean              success     = TRUE;
-    NMSettingsConnection *settings_connection;
+    NMDevicePrivate *      priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int              IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMIPConfig *           old_config;
+    gboolean               has_changes = FALSE;
+    gboolean               success     = TRUE;
+    NMSettingsConnection * settings_connection;
+    NMIPRouteTableSyncMode route_table_sync_mode;
 
     nm_assert_addr_family(addr_family);
     nm_assert(!new_config || nm_ip_config_get_addr_family(new_config) == addr_family);
@@ -13495,11 +13460,18 @@ nm_device_set_ip_config(NMDevice *  self,
                   })));
     nm_assert(IS_IPv4 || !ip4_dev_route_blacklist);
 
+    if (commit && new_config)
+        route_table_sync_mode = _get_route_table_sync_mode_stateful(self, addr_family);
+    else
+        route_table_sync_mode = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE;
+
     _LOGD(LOGD_IPX(IS_IPv4),
-          "ip%c-config: update (commit=%d, new-config=%p)",
+          "ip%c-config: update (commit=%d, new-config=" NM_HASH_OBFUSCATE_PTR_FMT
+          ", route-table-sync-mode=%d)",
           nm_utils_addr_family_to_char(addr_family),
           commit,
-          new_config);
+          NM_HASH_OBFUSCATE_PTR(new_config),
+          (int) route_table_sync_mode);
 
     /* Always commit to nm-platform to update lifetimes */
     if (commit && new_config) {
@@ -13508,7 +13480,7 @@ nm_device_set_ip_config(NMDevice *  self,
         if (IS_IPv4) {
             success = nm_ip4_config_commit(NM_IP4_CONFIG(new_config),
                                            nm_device_get_platform(self),
-                                           _get_route_table_sync_mode_stateful(self, AF_INET));
+                                           route_table_sync_mode);
             nm_platform_ip4_dev_route_blacklist_set(nm_device_get_platform(self),
                                                     nm_ip_config_get_ifindex(new_config),
                                                     ip4_dev_route_blacklist);
@@ -13517,7 +13489,7 @@ nm_device_set_ip_config(NMDevice *  self,
 
             success = nm_ip6_config_commit(NM_IP6_CONFIG(new_config),
                                            nm_device_get_platform(self),
-                                           _get_route_table_sync_mode_stateful(self, AF_INET6),
+                                           route_table_sync_mode,
                                            &temporary_not_available);
 
             if (!_rt6_temporary_not_available_set(self, temporary_not_available))
@@ -15846,7 +15818,7 @@ _cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
         /* Check if the device was deactivated, and if so, delete_link.
          * Don't call delete_link synchronously because we are currently
          * handling a state change -- which is not reentrant. */
-        delete_on_deactivate_check_and_schedule(self, nm_device_get_ip_ifindex(self));
+        delete_on_deactivate_check_and_schedule(self);
     }
 
     /* ip_iface should be cleared after flushing all routes and addresses, since
@@ -15906,9 +15878,12 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
 
             nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex);
             nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex);
-            nm_platform_tfilter_sync(platform, ifindex, NULL);
-            nm_platform_qdisc_sync(platform, ifindex, NULL);
             set_ipv6_token(self, iid, "::");
+
+            if (nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG)) {
+                nm_platform_tfilter_sync(platform, ifindex, NULL);
+                nm_platform_qdisc_sync(platform, ifindex, NULL);
+            }
         }
     }
 
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index 72777d0a..68343ef5 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -627,27 +627,28 @@ void nm_device_copy_ip6_dns_config(NMDevice *self, NMDevice *from_device);
  *   setting the NM_UNMANAGED_IS_SLAVE to %TRUE makes no sense, this flag has only
  *   meaning to set a slave device as managed if the parent is managed too.
  */
-typedef enum { /*< skip >*/
-               NM_UNMANAGED_NONE = 0,
+typedef enum {
+    NM_UNMANAGED_NONE = 0,
 
-               /* these flags are authoritative. If one of them is set,
+    /* these flags are authoritative. If one of them is set,
      * the device cannot be managed. */
-               NM_UNMANAGED_SLEEPING      = (1LL << 0),
-               NM_UNMANAGED_QUITTING      = (1LL << 1),
-               NM_UNMANAGED_PARENT        = (1LL << 2),
-               NM_UNMANAGED_BY_TYPE       = (1LL << 3),
-               NM_UNMANAGED_PLATFORM_INIT = (1LL << 4),
-               NM_UNMANAGED_USER_EXPLICIT = (1LL << 5),
-               NM_UNMANAGED_USER_SETTINGS = (1LL << 6),
-
-               /* These flags can be non-effective and be overwritten
+    NM_UNMANAGED_SLEEPING      = (1LL << 0),
+    NM_UNMANAGED_QUITTING      = (1LL << 1),
+    NM_UNMANAGED_PARENT        = (1LL << 2),
+    NM_UNMANAGED_BY_TYPE       = (1LL << 3),
+    NM_UNMANAGED_PLATFORM_INIT = (1LL << 4),
+    NM_UNMANAGED_USER_EXPLICIT = (1LL << 5),
+    NM_UNMANAGED_USER_SETTINGS = (1LL << 6),
+
+    /* These flags can be non-effective and be overwritten
      * by other flags. */
-               NM_UNMANAGED_BY_DEFAULT    = (1LL << 8),
-               NM_UNMANAGED_USER_CONF     = (1LL << 9),
-               NM_UNMANAGED_USER_UDEV     = (1LL << 10),
-               NM_UNMANAGED_EXTERNAL_DOWN = (1LL << 11),
-               NM_UNMANAGED_IS_SLAVE      = (1LL << 12),
+    NM_UNMANAGED_BY_DEFAULT    = (1LL << 7),
+    NM_UNMANAGED_USER_CONF     = (1LL << 8),
+    NM_UNMANAGED_USER_UDEV     = (1LL << 9),
+    NM_UNMANAGED_EXTERNAL_DOWN = (1LL << 10),
+    NM_UNMANAGED_IS_SLAVE      = (1LL << 11),
 
+    NM_UNMANAGED_ALL = ((1LL << 12) - 1),
 } NMUnmanagedFlags;
 
 typedef enum {
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 5d07c211..0a537f7a 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -78,6 +78,15 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static gboolean
+can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    return nm_ovsdb_is_ready(priv->ovsdb);
+}
+
+static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMSettingOvsInterface *s_ovs_iface;
@@ -427,6 +436,7 @@ nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
     device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH);
 
+    device_class->can_auto_connect                    = can_auto_connect;
     device_class->can_update_from_platform_link       = can_update_from_platform_link;
     device_class->deactivate                          = deactivate;
     device_class->deactivate_async                    = deactivate_async;
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index 95ade44b..01f2a303 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -588,10 +588,16 @@ deactivate(NMDevice *device)
             return;
     }
 
-    cleanup_association_attempt(self, TRUE);
+    cleanup_association_attempt(self, FALSE);
     priv->act_mode_switch = FALSE;
 
-    if (!priv->dbus_station_proxy)
+    /* Don't trigger any actions on the IWD side until the device is managed */
+    if (priv->iwd_autoconnect && nm_device_get_state(device) < NM_DEVICE_STATE_DISCONNECTED)
+        return;
+
+    if (priv->dbus_station_proxy)
+        send_disconnect(self);
+    else
         reset_mode(self, NULL, NULL, NULL);
 }
 
@@ -647,6 +653,11 @@ deactivate_async(NMDevice *                 device,
     cleanup_association_attempt(self, FALSE);
     priv->act_mode_switch = FALSE;
 
+    if (priv->iwd_autoconnect && nm_device_get_state(device) < NM_DEVICE_STATE_DISCONNECTED) {
+        nm_utils_invoke_on_idle(cancellable, disconnect_cb_on_idle, user_data);
+        return;
+    }
+
     if (priv->dbus_station_proxy) {
         g_dbus_proxy_call(priv->dbus_station_proxy,
                           "Disconnect",
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 042d4887..c9655eea 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -2565,6 +2565,7 @@ supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMRefString *        current_bss;
     NMWifiAP *           new_ap = NULL;
+    NMActRequest *       req;
 
     current_bss = nm_supplicant_interface_get_current_bss(iface);
     if (current_bss)
@@ -2612,6 +2613,13 @@ supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
         }
 
         set_current_ap(self, new_ap, TRUE);
+
+        req = nm_device_get_act_request(NM_DEVICE(self));
+        if (req) {
+            nm_active_connection_set_specific_object(
+                NM_ACTIVE_CONNECTION(req),
+                new_ap ? nm_dbus_object_get_path(NM_DBUS_OBJECT(new_ap)) : NULL);
+        }
     }
 }
 
diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c
index b4b019d3..68a74438 100644
--- a/src/core/devices/wifi/nm-iwd-manager.c
+++ b/src/core/devices/wifi/nm-iwd-manager.c
@@ -1119,6 +1119,7 @@ object_compare_interfaces(gconstpointer a, gconstpointer b)
         NM_IWD_KNOWN_NETWORK_INTERFACE,
         NM_IWD_NETWORK_INTERFACE,
         NM_IWD_DEVICE_INTERFACE,
+        NULL,
     };
     int   rank_a = G_N_ELEMENTS(interface_order);
     int   rank_b = G_N_ELEMENTS(interface_order);
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index ca028804..1297d964 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -369,10 +369,10 @@ static void
 connect_ready(MMModemSimple *simple_iface, GAsyncResult *res, NMModemBroadband *self)
 {
     ConnectContext *ctx;
-    GError *        error      = NULL;
-    NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
-    NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
-    MMBearer *      bearer;
+    GError *        error            = NULL;
+    NMModemIPMethod ip4_method       = NM_MODEM_IP_METHOD_UNKNOWN;
+    NMModemIPMethod ip6_method       = NM_MODEM_IP_METHOD_UNKNOWN;
+    gs_unref_object MMBearer *bearer = NULL;
 
     bearer = mm_modem_simple_connect_finish(simple_iface, res, &error);
 
@@ -386,7 +386,7 @@ connect_ready(MMModemSimple *simple_iface, GAsyncResult *res, NMModemBroadband *
     if (!ctx)
         return;
 
-    self->_priv.bearer = bearer;
+    self->_priv.bearer = g_steal_pointer(&bearer);
 
     if (!self->_priv.bearer) {
         if (g_error_matches(error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index c38c814e..3b0343fd 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -84,6 +84,11 @@ G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+/* we use pid=-1 for invalid PIDs. Ensure that pid_t can hold negative values. */
+G_STATIC_ASSERT(!(((pid_t) -1) > 0));
+
+/*****************************************************************************/
+
 pid_t
 nm_dhcp_client_get_pid(NMDhcpClient *self)
 {
@@ -749,7 +754,7 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
         _LOGI("canceled DHCP transaction");
     nm_assert(priv->pid == -1);
 
-    nm_dhcp_client_set_state(self, NM_DHCP_STATE_DONE, NULL, NULL);
+    nm_dhcp_client_set_state(self, NM_DHCP_STATE_TERMINATED, NULL, NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h
index 72ab477d..af3406cc 100644
--- a/src/core/dhcp/nm-dhcp-client.h
+++ b/src/core/dhcp/nm-dhcp-client.h
@@ -51,7 +51,7 @@ typedef enum {
     NM_DHCP_STATE_BOUND,      /* new lease */
     NM_DHCP_STATE_EXTENDED,   /* lease extended */
     NM_DHCP_STATE_TIMEOUT,    /* timed out contacting server */
-    NM_DHCP_STATE_DONE,       /* client quit or stopped */
+    NM_DHCP_STATE_DONE,       /* client reported it's stopping */
     NM_DHCP_STATE_EXPIRE,     /* lease expired or NAKed */
     NM_DHCP_STATE_FAIL,       /* failed for some reason */
     NM_DHCP_STATE_TERMINATED, /* client is no longer running */
diff --git a/src/core/dhcp/nm-dhcp-dhcpcd.c b/src/core/dhcp/nm-dhcp-dhcpcd.c
index cf9fe5c2..cdb266ed 100644
--- a/src/core/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/core/dhcp/nm-dhcp-dhcpcd.c
@@ -169,27 +169,29 @@ stop(NMDhcpClient *client, gboolean release)
     int           sig, errsv;
 
     pid = nm_dhcp_client_get_pid(client);
-    sig = release ? SIGALRM : SIGTERM;
-    _LOGD("sending %s to dhcpcd pid %d", sig == SIGALRM ? "SIGALRM" : "SIGTERM", pid);
-
-    /* dhcpcd-9.x features privilege separation.
-     * It's not our job to track all these processes so we rely on dhcpcd
-     * to always cleanup after itself.
-     * Because it also re-parents itself to PID 1, the process cannot be
-     * reaped or waited for.
-     * As such, just send the correct signal.
-     */
-    if (kill(pid, sig) == -1) {
-        errsv = errno;
-        _LOGE("failed to kill dhcpcd %d:%s", errsv, strerror(errsv));
-    }
+    if (pid > 1) {
+        sig = release ? SIGALRM : SIGTERM;
+        _LOGD("sending %s to dhcpcd pid %d", sig == SIGALRM ? "SIGALRM" : "SIGTERM", pid);
+
+        /* dhcpcd-9.x features privilege separation.
+         * It's not our job to track all these processes so we rely on dhcpcd
+         * to always cleanup after itself.
+         * Because it also re-parents itself to PID 1, the process cannot be
+         * reaped or waited for.
+         * As such, just send the correct signal.
+         */
+        if (kill(pid, sig) == -1) {
+            errsv = errno;
+            _LOGE("failed to kill dhcpcd %d:%s", errsv, strerror(errsv));
+        }
 
-    /* When this function exits NM expects the PID to be -1.
-     * This means we also need to stop watching the pid.
-     * If we need to know the exit status then we need to refactor NM
-     * to allow a non -1 to mean we're waiting to exit still.
-     */
-    nm_dhcp_client_stop_watch_child(client, pid);
+        /* When this function exits NM expects the PID to be -1.
+         * This means we also need to stop watching the pid.
+         * If we need to know the exit status then we need to refactor NM
+         * to allow a non -1 to mean we're waiting to exit still.
+         */
+        nm_dhcp_client_stop_watch_child(client, pid);
+    }
 }
 
 /*****************************************************************************/
diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c
index 116e1bdb..c8b80ea6 100644
--- a/src/core/dhcp/nm-dhcp-nettools.c
+++ b/src/core/dhcp/nm-dhcp-nettools.c
@@ -342,16 +342,16 @@ lease_parse_routes(NDhcp4ClientLease *lease,
     const guint8 *l_data;
     gsize         l_data_len;
     int           r;
+    guint         i;
 
-    r = _client_lease_query(lease,
-                            NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
-                            &l_data,
-                            &l_data_len);
-    if (r == 0) {
-        nm_str_buf_reset(sbuf);
+    for (i = 0; i < 2; i++) {
+        const guint8 option_code = (i == 0) ? NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE
+                                            : NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE;
 
-        has_classless = TRUE;
+        if (_client_lease_query(lease, option_code, &l_data, &l_data_len) != 0)
+            continue;
 
+        nm_str_buf_reset(sbuf);
         while (lease_option_consume_route(&l_data, &l_data_len, TRUE, &dest, &plen, &gateway)) {
             _nm_utils_inet4_ntop(dest, dest_str);
             _nm_utils_inet4_ntop(gateway, gateway_str);
@@ -359,6 +359,11 @@ lease_parse_routes(NDhcp4ClientLease *lease,
             nm_str_buf_append_required_delimiter(sbuf, ' ');
             nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str);
 
+            if (has_classless) {
+                /* Ignore private option if the standard one is present */
+                continue;
+            }
+
             if (plen == 0) {
                 /* if there are multiple default routes, we add them with differing
                  * metrics. */
@@ -384,10 +389,8 @@ lease_parse_routes(NDhcp4ClientLease *lease,
                 NULL);
         }
 
-        nm_dhcp_option_add_option(options,
-                                  AF_INET,
-                                  NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
-                                  nm_str_buf_get_str(sbuf));
+        has_classless = TRUE;
+        nm_dhcp_option_add_option(options, AF_INET, option_code, nm_str_buf_get_str(sbuf));
     }
 
     r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &l_data, &l_data_len);
@@ -693,8 +696,8 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
 
         v_str = nm_utils_buf_utf8safe_escape((char *) l_data, l_data_len, 0, &to_free);
 
-        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, v_str);
-        nm_ip4_config_set_nis_domain(ip4_config, v_str);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, v_str ?: "");
+        nm_ip4_config_set_nis_domain(ip4_config, v_str ?: "");
     }
 
     lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options, &sbuf);
diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index b92a9073..100807aa 100644
--- a/src/core/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -85,29 +85,29 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
     const struct in_addr *         addr_list;
     char                           addr_str[NM_UTILS_INET_ADDRSTRLEN];
     const char *                   s;
-    nm_auto_free_gstring GString *str      = NULL;
-    gs_free sd_dhcp_route **routes         = NULL;
-    const char *const *     search_domains = NULL;
-    guint16                 mtu;
-    int                     i, num;
-    const void *            data;
-    gsize                   data_len;
-    gboolean                metered                   = FALSE;
-    gboolean                has_router_from_classless = FALSE;
-    gboolean                has_classless_route       = FALSE;
-    gboolean                has_static_route          = FALSE;
-    const gint32            ts                        = nm_utils_get_monotonic_timestamp_sec();
-    gint64                  ts_time                   = time(NULL);
-    struct in_addr          a_address;
-    struct in_addr          a_netmask;
-    struct in_addr          a_next_server;
-    struct in_addr          server_id;
-    struct in_addr          broadcast;
-    const struct in_addr *  a_router;
-    guint32                 a_plen;
-    guint32                 a_lifetime;
-    guint32                 renewal;
-    guint32                 rebinding;
+    nm_auto_free_gstring GString *str           = NULL;
+    nm_auto_free sd_dhcp_route **routes         = NULL;
+    const char *const *          search_domains = NULL;
+    guint16                      mtu;
+    int                          i, num;
+    const void *                 data;
+    gsize                        data_len;
+    gboolean                     metered                   = FALSE;
+    gboolean                     has_router_from_classless = FALSE;
+    gboolean                     has_classless_route       = FALSE;
+    gboolean                     has_static_route          = FALSE;
+    const gint32                 ts                        = nm_utils_get_monotonic_timestamp_sec();
+    gint64                       ts_time                   = time(NULL);
+    struct in_addr               a_address;
+    struct in_addr               a_netmask;
+    struct in_addr               a_next_server;
+    struct in_addr               server_id;
+    struct in_addr               broadcast;
+    const struct in_addr *       a_router;
+    guint32                      a_plen;
+    guint32                      a_lifetime;
+    guint32                      renewal;
+    guint32                      rebinding;
     gs_free nm_sd_dhcp_option *private_options = NULL;
 
     nm_assert(lease != NULL);
diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c
index 646411e2..63d90641 100644
--- a/src/core/dhcp/nm-dhcp-utils.c
+++ b/src/core/dhcp/nm-dhcp-utils.c
@@ -880,7 +880,7 @@ nm_dhcp_lease_data_parse_cstr(const guint8 *data, gsize n_data, gsize *out_new_l
         n_data--;
 
     if (n_data > 0) {
-        if (memchr(data, n_data, '\0')) {
+        if (memchr(data, '\0', n_data)) {
             /* we accept trailing NUL, but none in between.
              *
              * https://tools.ietf.org/html/rfc2132#section-2
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index f318b9b8..59c8898a 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -1624,7 +1624,7 @@ _mgr_configs_data_clear(NMDnsManager *self)
 /*****************************************************************************/
 
 static gboolean
-update_dns(NMDnsManager *self, gboolean no_caching, GError **error)
+update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError **error)
 {
     NMDnsManagerPrivate *priv                = NM_DNS_MANAGER_GET_PRIVATE(self);
     const char *         nis_domain          = NULL;
@@ -1815,8 +1815,8 @@ plugin_skip:;
                            NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED);
     }
 
-    /* signal that resolv.conf was changed */
-    if (do_update && result == SR_SUCCESS)
+    /* signal that DNS resolution configs were changed */
+    if ((do_update || caching || force_emit) && result == SR_SUCCESS)
         g_signal_emit(self, signals[CONFIG_CHANGED], 0);
 
     nm_clear_pointer(&priv->config_variant, g_variant_unref);
@@ -1924,7 +1924,7 @@ changed:
     if (!priv->updates_queue) {
         gs_free_error GError *error = NULL;
 
-        if (!update_dns(self, FALSE, &error))
+        if (!update_dns(self, FALSE, FALSE, &error))
             _LOGW("could not commit DNS changes: %s", error->message);
     }
 
@@ -1965,7 +1965,7 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s
     if (!priv->updates_queue) {
         gs_free_error GError *error = NULL;
 
-        if (!update_dns(self, FALSE, &error))
+        if (!update_dns(self, FALSE, FALSE, &error))
             _LOGW("could not commit DNS changes: %s", error->message);
     }
 }
@@ -2012,7 +2012,7 @@ nm_dns_manager_end_updates(NMDnsManager *self, const char *func)
 
     /* Commit all the outstanding changes */
     _LOGD("(%s): committing DNS changes (%d)", func, priv->updates_queue);
-    if (!update_dns(self, FALSE, &error))
+    if (!update_dns(self, FALSE, FALSE, &error))
         _LOGW("could not commit DNS changes: %s", error->message);
 
     memset(priv->prev_hash, 0, sizeof(priv->prev_hash));
@@ -2038,7 +2038,7 @@ nm_dns_manager_stop(NMDnsManager *self)
     if (priv->dns_touched && priv->plugin && NM_IS_DNS_DNSMASQ(priv->plugin)) {
         gs_free_error GError *error = NULL;
 
-        if (!update_dns(self, TRUE, &error))
+        if (!update_dns(self, TRUE, FALSE, &error))
             _LOGW("could not commit DNS changes on shutdown: %s", error->message);
 
         priv->dns_touched = FALSE;
@@ -2363,7 +2363,7 @@ config_changed_cb(NMConfig *          config,
                          | NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) {
         gs_free_error GError *error = NULL;
 
-        if (!update_dns(self, FALSE, &error))
+        if (!update_dns(self, FALSE, TRUE, &error))
             _LOGW("could not commit DNS changes: %s", error->message);
     }
 }
diff --git a/src/core/initrd/nmi-cmdline-reader.c b/src/core/initrd/nmi-cmdline-reader.c
index 508ef2b2..0a24199e 100644
--- a/src/core/initrd/nmi-cmdline-reader.c
+++ b/src/core/initrd/nmi-cmdline-reader.c
@@ -393,8 +393,9 @@ reader_read_all_connections_from_fw(Reader *reader, const char *sysfs_dir)
 static void
 reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4 = NULL, *s_ip6 = NULL;
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4 = NULL, *s_ip6 = NULL;
     gs_unref_hashtable GHashTable *ibft = NULL;
     const char *                   tmp;
     const char *                   tmp2;
@@ -495,6 +496,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
 
     g_hash_table_add(reader->explicit_ip_connections, g_object_ref(connection));
 
+    s_con = nm_connection_get_setting_connection(connection);
     s_ip4 = nm_connection_get_setting_ip4_config(connection);
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
 
@@ -544,6 +546,12 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
             nm_assert_not_reached();
 
         if (address) {
+            /* We don't want to have multiple devices up with the
+             * same static address. */
+            g_object_set(s_con,
+                         NM_SETTING_CONNECTION_MULTI_CONNECT,
+                         NM_CONNECTION_MULTI_CONNECT_SINGLE,
+                         NULL);
             switch (client_ip_family) {
             case AF_INET:
                 g_object_set(s_ip4,
@@ -599,7 +607,12 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
                          NULL);
         }
     } else if (NM_IN_STRSET(kind, "auto6", "dhcp6")) {
-        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE, NULL);
+        g_object_set(s_ip6,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                     NM_SETTING_IP_CONFIG_MAY_FAIL,
+                     FALSE,
+                     NULL);
         if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) {
             g_object_set(s_ip4,
                          NM_SETTING_IP_CONFIG_METHOD,
@@ -764,6 +777,9 @@ reader_parse_master(Reader *reader, char *argument, const char *type_name, const
         mtu = get_word(&argument, ':');
     }
 
+    if (mtu)
+        connection_set(connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU, mtu);
+
     do {
         slave = get_word(&slaves, ',');
         if (slave == NULL)
@@ -777,8 +793,6 @@ reader_parse_master(Reader *reader, char *argument, const char *type_name, const
                      NM_SETTING_CONNECTION_MASTER,
                      master,
                      NULL);
-        if (mtu)
-            connection_set(connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU, mtu);
     } while (slaves && *slaves != '\0');
 
     if (argument && *argument)
@@ -916,6 +930,11 @@ reader_parse_rd_znet(Reader *reader, char *argument, gboolean net_ifnames)
     subchannels[0] = get_word(&argument, ',');
     subchannels[1] = get_word(&argument, ',');
 
+    /* Without subchannels we can't univocally match
+     * a device. */
+    if (!subchannels[0] || !subchannels[1])
+        return;
+
     if (nm_streq0(nettype, "ctc")) {
         if (net_ifnames == TRUE) {
             prefix = "sl";
diff --git a/src/core/initrd/tests/test-cmdline-reader.c b/src/core/initrd/tests/test-cmdline-reader.c
index 33fb22d3..e3a3753e 100644
--- a/src/core/initrd/tests/test-cmdline-reader.c
+++ b/src/core/initrd/tests/test-cmdline-reader.c
@@ -456,6 +456,46 @@ test_if_ip4_manual(void)
 }
 
 static void
+test_if_ip4_manual_no_dev(void)
+{
+    const char *const *  ARGV = NM_MAKE_STRV("ip=192.0.2.2::192.0.2.1:24:::");
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSettingIPConfig *  s_ip6;
+    NMIPAddress *        ip_addr;
+
+    connection = _parse_con(ARGV, "default_connection");
+    g_assert_cmpstr(nm_connection_get_id(connection), ==, "Wired Connection");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1);
+    g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con),
+                    ==,
+                    NM_CONNECTION_MULTI_CONNECT_SINGLE);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                    ==,
+                    NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+    g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    ip_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip_addr);
+    g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "192.0.2.2");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 24);
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.0.2.1");
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+}
+
+static void
 test_if_ip6_manual(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
@@ -825,13 +865,14 @@ test_bond(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
     const char *const *            ARGV        = NM_MAKE_STRV("rd.route=192.0.2.53::bong0",
-                                           "bond=bong0:eth0,eth1:mode=balance-rr",
+                                           "bond=bong0:eth0,eth1:mode=balance-rr:9000",
                                            "nameserver=203.0.113.53");
     NMConnection *                 connection;
     NMSettingConnection *          s_con;
     NMSettingIPConfig *            s_ip4;
     NMSettingIPConfig *            s_ip6;
     NMSettingBond *                s_bond;
+    NMSettingWired *               s_wired;
     NMIPRoute *                    ip_route;
     const char *                   master_uuid;
 
@@ -847,6 +888,10 @@ test_bond(void)
     master_uuid = nm_connection_get_uuid(connection);
     g_assert(master_uuid);
 
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 9000);
+
     s_ip4 = nm_connection_get_setting_ip4_config(connection);
     g_assert(s_ip4);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
@@ -1811,6 +1856,24 @@ test_rd_znet_no_ip(void)
 }
 
 static void
+test_rd_znet_malformed(void)
+{
+    const char *const *const ARGV0  = NM_MAKE_STRV("rd.znet=");
+    const char *const *const ARGV1  = NM_MAKE_STRV("rd.znet=,");
+    const char *const *const ARGV2  = NM_MAKE_STRV("rd.znet=foobar");
+    const char *const *const ARGV3  = NM_MAKE_STRV("rd.znet=qeth,0.0.0800,,,layer2=0,portno=1");
+    const char *const *const ARGV[] = {ARGV0, ARGV1, ARGV2, ARGV3};
+    guint                    i;
+
+    for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
+        gs_unref_hashtable GHashTable *connections = NULL;
+
+        connections = _parse_cons(ARGV[i]);
+        g_assert_cmpint(g_hash_table_size(connections), ==, 0);
+    }
+}
+
+static void
 test_bootif_ip(void)
 {
     const char *const *ARGV                  = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03", "ip=dhcp");
@@ -2142,6 +2205,7 @@ main(int argc, char **argv)
     g_test_add_func("/initrd/cmdline/if_dhcp6", test_if_dhcp6);
     g_test_add_func("/initrd/cmdline/if_auto_with_mtu_and_mac", test_if_auto_with_mtu_and_mac);
     g_test_add_func("/initrd/cmdline/if_ip4_manual", test_if_ip4_manual);
+    g_test_add_func("/initrd/cmdline/if_ip4_manual_no_dev", test_if_ip4_manual_no_dev);
     g_test_add_func("/initrd/cmdline/if_ip6_manual", test_if_ip6_manual);
     g_test_add_func("/initrd/cmdline/if_mac_ifname", test_if_mac_ifname);
     g_test_add_func("/initrd/cmdline/if_off", test_if_off);
@@ -2168,6 +2232,7 @@ main(int argc, char **argv)
     g_test_add_func("/initrd/cmdline/rd_znet", test_rd_znet);
     g_test_add_func("/initrd/cmdline/rd_znet/legacy", test_rd_znet_legacy);
     g_test_add_func("/initrd/cmdline/rd_znet/no_ip", test_rd_znet_no_ip);
+    g_test_add_func("/initrd/cmdline/rd_znet/empty", test_rd_znet_malformed);
     g_test_add_func("/initrd/cmdline/bootif/ip", test_bootif_ip);
     g_test_add_func("/initrd/cmdline/bootif/no_ip", test_bootif_no_ip);
     g_test_add_func("/initrd/cmdline/bootif/hwtype", test_bootif_hwtype);
diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c
index c62c6773..d457f505 100644
--- a/src/core/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -437,7 +437,7 @@ static NMAuthPolkitMode
 _config_data_get_main_auth_polkit(const NMConfigData *self, gboolean *out_invalid_config)
 {
     NMAuthPolkitMode auth_polkit_mode;
-    const char *     str;
+    gs_free char *   str = NULL;
 
     str              = nm_config_data_get_value(self,
                                    NM_CONFIG_KEYFILE_GROUP_MAIN,
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 9075c30d..a27c50dc 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -1956,7 +1956,8 @@ nm_wildcard_match_check(const char *str, const char *const *patterns, guint num_
 
         _pattern_parse(patterns[i], &p, &is_inverted, &is_mandatory);
 
-        match = (fnmatch(p, str, 0) == 0);
+        match = (fnmatch(p, str ?: "", 0) == 0);
+
         if (is_inverted)
             match = !match;
 
@@ -5065,9 +5066,12 @@ nm_wifi_utils_parse_ies(const guint8 *bytes,
         case WLAN_EID_VENDOR_SPECIFIC:
             if (len == 8 && bytes[0] == 0x00 /* OUI: Microsoft */
                 && bytes[1] == 0x50 && bytes[2] == 0xf2
-                && bytes[3] == 0x11)                     /* OUI type: Network cost */
-                NM_SET_OUT(out_metered, (bytes[7] > 1)); /* Cost level > 1 */
-            if (elem_len >= 10 && bytes[0] == 0x50       /* OUI: WiFi Alliance */
+                && bytes[3] == 0x11) /* OUI type: Network cost */
+            {
+                /* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nct/ */
+                NM_SET_OUT(out_metered, (bytes[4] > 1)); /* Cost level > 1 */
+            }
+            if (elem_len >= 10 && bytes[0] == 0x50 /* OUI: WiFi Alliance */
                 && bytes[1] == 0x6f && bytes[2] == 0x9a
                 && bytes[3] == 0x1c) /* OUI type: OWE Transition Mode */
                 NM_SET_OUT(out_owe_transition_mode, TRUE);
diff --git a/src/core/nm-ip4-config.c b/src/core/nm-ip4-config.c
index c49dfb4c..608b7fc8 100644
--- a/src/core/nm-ip4-config.c
+++ b/src/core/nm-ip4-config.c
@@ -649,21 +649,6 @@ nm_ip4_config_add_dependent_routes(NMIP4Config *self,
         if (my_addr->external)
             continue;
 
-        /* Pre-generate local route added by kernel */
-        r                   = nmp_object_new(NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
-        route               = NMP_OBJECT_CAST_IP4_ROUTE(r);
-        route->ifindex      = ifindex;
-        route->rt_source    = NM_IP_CONFIG_SOURCE_KERNEL;
-        route->network      = my_addr->address;
-        route->plen         = 32;
-        route->pref_src     = my_addr->address;
-        route->type_coerced = nm_platform_route_type_coerce(RTN_LOCAL);
-        route->scope_inv    = nm_platform_route_scope_inv(RT_SCOPE_HOST);
-        route->table_coerced =
-            nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL);
-        _add_route(self, r, NULL, NULL);
-        nm_clear_pointer(&r, nmp_object_unref);
-
         if (nm_utils_ip4_address_is_zeronet(network)) {
             /* Kernel doesn't add device-routes for destinations that
              * start with 0.x.y.z. Skip them. */
diff --git a/src/core/nm-ip6-config.c b/src/core/nm-ip6-config.c
index 1f7def34..1e36050d 100644
--- a/src/core/nm-ip6-config.c
+++ b/src/core/nm-ip6-config.c
@@ -396,23 +396,6 @@ nm_ip6_config_add_dependent_routes(NMIP6Config *self,
      *
      * For manually added IPv6 routes, add the device routes explicitly. */
 
-    /* Pre-generate multicast route */
-    {
-        nm_auto_nmpobj NMPObject *r = NULL;
-        NMPlatformIP6Route *      route;
-
-        r                         = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
-        route                     = NMP_OBJECT_CAST_IP6_ROUTE(r);
-        route->ifindex            = ifindex;
-        route->network.s6_addr[0] = 0xffu;
-        route->plen               = 8;
-        route->table_coerced      = nm_platform_route_table_coerce(RT_TABLE_LOCAL);
-        route->type_coerced       = nm_platform_route_type_coerce(RTN_UNICAST);
-        route->metric             = 256;
-
-        _add_route(self, r, NULL, NULL);
-    }
-
     nm_ip_config_iter_ip6_address_for_each (&iter, self, &my_addr) {
         NMPlatformIP6Route *route;
         gboolean            has_peer;
@@ -421,22 +404,6 @@ nm_ip6_config_add_dependent_routes(NMIP6Config *self,
         if (my_addr->external)
             continue;
 
-        {
-            nm_auto_nmpobj NMPObject *r = NULL;
-
-            /* Pre-generate local route added by kernel */
-            r                   = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
-            route               = NMP_OBJECT_CAST_IP6_ROUTE(r);
-            route->ifindex      = ifindex;
-            route->network      = my_addr->address;
-            route->plen         = 128;
-            route->type_coerced = nm_platform_route_type_coerce(RTN_LOCAL);
-            route->metric       = 0;
-            route->table_coerced =
-                nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL);
-            _add_route(self, r, NULL, NULL);
-        }
-
         if (NM_FLAGS_HAS(my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE))
             continue;
         if (my_addr->plen == 0)
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index 5a6e05a9..f44306e1 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -363,6 +363,7 @@ static NMActiveConnection *active_connection_find(NMManager *             self,
                                                   NMSettingsConnection *  sett_conn,
                                                   const char *            uuid,
                                                   NMActiveConnectionState max_state,
+                                                  gboolean                also_waiting_auth,
                                                   GPtrArray **            out_all_matching);
 
 static NMConnectivity *concheck_get_mgr(NMManager *self);
@@ -833,6 +834,7 @@ _delete_volatile_connection_do(NMManager *self, NMSettingsConnection *connection
                                connection,
                                NULL,
                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
+                               TRUE,
                                NULL))
         return;
 
@@ -978,6 +980,7 @@ active_connection_find(
     NMSettingsConnection *  sett_conn,
     const char *            uuid,
     NMActiveConnectionState max_state /* candidates in state @max_state will be found */,
+    gboolean                also_waiting_auth /* return also ACs waiting authorization */,
     GPtrArray **            out_all_matching)
 {
     NMManagerPrivate *  priv = NM_MANAGER_GET_PRIVATE(self);
@@ -1017,11 +1020,14 @@ active_connection_find(
     if (!best_ac) {
         AsyncOpData *async_op_data;
 
+        if (!also_waiting_auth)
+            return NULL;
+
         c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) {
             NMSettingsConnection *ac_conn;
 
             ac      = async_op_data->ac_auth.active;
-            ac_conn = nm_active_connection_get_settings_connection(ac);
+            ac_conn = _nm_active_connection_get_settings_connection(ac);
             if (sett_conn && sett_conn != ac_conn)
                 continue;
             if (uuid && !nm_streq0(uuid, nm_settings_connection_get_uuid(ac_conn)))
@@ -1078,6 +1084,7 @@ active_connection_find_by_connection(NMManager *             self,
                                   sett_conn,
                                   sett_conn ? NULL : nm_connection_get_uuid(connection),
                                   max_state,
+                                  FALSE,
                                   out_all_matching);
 }
 
@@ -1112,6 +1119,7 @@ _get_activatable_connections_filter(NMSettings *          settings,
                                    sett_conn,
                                    NULL,
                                    NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+                                   FALSE,
                                    NULL);
 }
 
@@ -2245,6 +2253,7 @@ connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection,
                                connection,
                                NULL,
                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
+                               FALSE,
                                NULL)) {
         /* the connection still has an active-connection. It will be purged
          * when the active connection(s) get(s) removed. */
@@ -2564,6 +2573,7 @@ new_activation_allowed_for_connection(NMManager *self, NMSettingsConnection *con
                                    connection,
                                    NULL,
                                    NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+                                   FALSE,
                                    NULL);
 }
 
@@ -2839,14 +2849,16 @@ recheck_assume_connection(NMManager *self, NMDevice *device)
     g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
 
     if (!nm_device_get_managed(device, FALSE)) {
-        nm_device_assume_state_reset(device);
+        /* If the device is only unmanaged by NM_UNMANAGED_PLATFORM_INIT,
+         * don't reset the state now but wait until it becomes managed. */
+        if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_ALL) != NM_UNMANAGED_PLATFORM_INIT)
+            nm_device_assume_state_reset(device);
         _LOG2D(LOGD_DEVICE, device, "assume: don't assume because %s", "not managed");
         return FALSE;
     }
 
     state = nm_device_get_state(device);
     if (state > NM_DEVICE_STATE_DISCONNECTED) {
-        nm_device_assume_state_reset(device);
         _LOG2D(LOGD_DEVICE,
                device,
                "assume: don't assume due to device state %s",
@@ -3161,7 +3173,10 @@ _device_realize_finish(NMManager *self, NMDevice *device, const NMPlatformLink *
     nm_device_realize_finish(device, plink);
 
     if (!nm_device_get_managed(device, FALSE)) {
-        nm_device_assume_state_reset(device);
+        /* If the device is only unmanaged by NM_UNMANAGED_PLATFORM_INIT,
+         * don't reset the state now but wait until it becomes managed. */
+        if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_ALL) != NM_UNMANAGED_PLATFORM_INIT)
+            nm_device_assume_state_reset(device);
         return;
     }
 
@@ -3510,6 +3525,45 @@ typedef struct {
 } PlatformLinkCbData;
 
 static gboolean
+_check_remove_dev_on_link_deleted(NMManager *self, NMDevice *device)
+{
+    NMManagerPrivate *           priv  = NM_MANAGER_GET_PRIVATE(self);
+    NMSettingsConnection *const *scons = NULL;
+    NMConnection *               con;
+    guint                        i;
+
+    nm_assert(nm_device_is_software(device));
+
+    /* In general, software devices stick around as unrealized
+     * until their connection is removed. However, we don't want
+     * that a NM-generated connection keeps the device alive.
+     * If there are no other compatible connections, the device
+     * should be also removed.
+     */
+
+    scons = nm_settings_get_connections(priv->settings, NULL);
+
+    for (i = 0; scons[i]; i++) {
+        con = nm_settings_connection_get_connection(scons[i]);
+        if (!nm_connection_is_virtual(con))
+            continue;
+
+        if (NM_FLAGS_HAS(nm_settings_connection_get_flags(scons[i]),
+                         NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
+            continue;
+
+        if (!nm_device_check_connection_compatible(device, con, NULL))
+            continue;
+
+        /* Found a virtual connection compatible, the device must
+         * stay around unrealized. */
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
 _platform_link_cb_idle(PlatformLinkCbData *data)
 {
     int                   ifindex = data->ifindex;
@@ -3534,13 +3588,15 @@ _platform_link_cb_idle(PlatformLinkCbData *data)
         if (device) {
             if (nm_device_is_software(device)) {
                 nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
-                /* Our software devices stick around until their connection is removed */
                 if (!nm_device_unrealize(device, FALSE, &error)) {
                     _LOG2W(LOGD_DEVICE, device, "failed to unrealize: %s", error->message);
                     g_clear_error(&error);
                     remove_device(self, device, FALSE);
                 } else {
-                    nm_device_update_from_platform_link(device, NULL);
+                    if (_check_remove_dev_on_link_deleted(self, device))
+                        remove_device(self, device, FALSE);
+                    else
+                        nm_device_update_from_platform_link(device, NULL);
                 }
             } else {
                 /* Hardware and external devices always get removed when their kernel link is gone */
@@ -4134,6 +4190,7 @@ find_master(NMManager *            self,
                                                 master_connection,
                                                 NULL,
                                                 NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
+                                                FALSE,
                                                 NULL);
     }
 
@@ -4985,6 +5042,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
                                     sett_conn,
                                     NULL,
                                     NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+                                    FALSE,
                                     &all_ac_arr);
         if (ac) {
             n_all = all_ac_arr ? all_ac_arr->len : ((guint) 1);
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index db4983f8..71aa1ece 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -652,6 +652,7 @@ device_hostname_info_compare(gconstpointer a, gconstpointer b)
 
     NM_CMP_FIELD(info1, info2, priority);
     NM_CMP_FIELD_UNSAFE(info2, info1, is_default);
+    NM_CMP_FIELD_UNSAFE(info2, info1, IS_IPv4);
 
     return 0;
 }
diff --git a/src/core/nm-types.h b/src/core/nm-types.h
index ab231459..8a32b7d2 100644
--- a/src/core/nm-types.h
+++ b/src/core/nm-types.h
@@ -245,12 +245,16 @@ typedef enum {
  *   local table (255).
  * @NM_IP_ROUTE_TABLE_SYNC_MODE_ALL: NM will sync all tables, including the
  *   local table (255).
+ * @NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE: NM will sync all tables (including
+ *   the local table). It will thereby remove all addresses, that is during
+ *   deactivation.
  */
 typedef enum {
-    NM_IP_ROUTE_TABLE_SYNC_MODE_NONE = 0,
-    NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN = 1,
-    NM_IP_ROUTE_TABLE_SYNC_MODE_FULL = 2,
-    NM_IP_ROUTE_TABLE_SYNC_MODE_ALL  = 3,
+    NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
+    NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+    NM_IP_ROUTE_TABLE_SYNC_MODE_FULL,
+    NM_IP_ROUTE_TABLE_SYNC_MODE_ALL,
+    NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE,
 } NMIPRouteTableSyncMode;
 
 /* settings */
diff --git a/src/core/platform/nm-platform.c b/src/core/platform/nm-platform.c
index 0e5f8ab5..459a3307 100644
--- a/src/core/platform/nm-platform.c
+++ b/src/core/platform/nm-platform.c
@@ -4357,34 +4357,134 @@ nm_platform_ip_route_get_prune_list(NMPlatform *           self,
     GPtrArray *                  routes_prune;
     const NMDedupMultiHeadEntry *head_entry;
     CList *                      iter;
+    NMPlatformIP4Route           rt_local4;
+    NMPlatformIP6Route           rt_local6;
+    const NMPlatformLink *       pllink;
+    const NMPlatformLnkVrf *     lnk_vrf;
+    guint32                      local_table;
 
     nm_assert(NM_IS_PLATFORM(self));
     nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
     nm_assert(NM_IN_SET(route_table_sync,
                         NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
                         NM_IP_ROUTE_TABLE_SYNC_MODE_FULL,
-                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL));
+                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL,
+                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE));
 
     nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), ifindex);
     head_entry = nm_platform_lookup(self, &lookup);
     if (!head_entry)
         return NULL;
 
+    lnk_vrf = nm_platform_link_get_lnk_vrf(self, ifindex, &pllink);
+    if (!lnk_vrf && pllink && pllink->master > 0)
+        lnk_vrf = nm_platform_link_get_lnk_vrf(self, pllink->master, NULL);
+    local_table = lnk_vrf ? lnk_vrf->table : RT_TABLE_LOCAL;
+
+    rt_local4.plen = 0;
+    rt_local6.plen = 0;
+
     routes_prune = g_ptr_array_new_full(head_entry->len, (GDestroyNotify) nm_dedup_multi_obj_unref);
 
     c_list_for_each (iter, &head_entry->lst_entries_head) {
-        const NMPObject *obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj;
+        const NMPObject *         obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj;
+        const NMPlatformIPXRoute *rt  = NMP_OBJECT_CAST_IPX_ROUTE(obj);
 
-        if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_FULL) {
-            if (nm_platform_ip_route_get_effective_table(NMP_OBJECT_CAST_IP_ROUTE(obj))
-                == RT_TABLE_LOCAL)
+        switch (route_table_sync) {
+        case NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN:
+            if (!nm_platform_route_table_is_main(nm_platform_ip_route_get_effective_table(&rt->rx)))
                 continue;
-        } else if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN) {
-            if (!nm_platform_route_table_is_main(
-                    nm_platform_ip_route_get_effective_table(NMP_OBJECT_CAST_IP_ROUTE(obj))))
+            break;
+        case NM_IP_ROUTE_TABLE_SYNC_MODE_FULL:
+            if (nm_platform_ip_route_get_effective_table(&rt->rx) == RT_TABLE_LOCAL)
                 continue;
-        } else
-            nm_assert(route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_ALL);
+            break;
+        case NM_IP_ROUTE_TABLE_SYNC_MODE_ALL:
+
+            /* FIXME: we should better handle routes that are automatically added by kernel.
+             *
+             * For now, make a good guess which are those routes and exclude them from
+             * pruning them. */
+
+            if (NM_IS_IPv4(addr_family)) {
+                /* for each IPv4 address kernel adds a route like
+                 *
+                 *  local $ADDR dev $IFACE table local proto kernel scope host src $PRIMARY_ADDR
+                 *
+                 * Check whether route could be of that kind. */
+                if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table
+                    && rt->rx.plen == 32 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL
+                    && rt->rx.metric == 0
+                    && rt->r4.scope_inv == nm_platform_route_scope_inv(RT_SCOPE_HOST)
+                    && rt->r4.gateway == INADDR_ANY) {
+                    if (rt_local4.plen == 0) {
+                        rt_local4 = (NMPlatformIP4Route){
+                            .ifindex       = ifindex,
+                            .type_coerced  = nm_platform_route_type_coerce(RTN_LOCAL),
+                            .plen          = 32,
+                            .rt_source     = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL,
+                            .metric        = 0,
+                            .table_coerced = nm_platform_route_table_coerce(local_table),
+                            .scope_inv     = nm_platform_route_scope_inv(RT_SCOPE_HOST),
+                            .gateway       = INADDR_ANY,
+                        };
+                    }
+
+                    /* the possible "network" depends on the addresses we have. We don't check that
+                     * carefully. If the other parameters match, we assume that this route is the one
+                     * generated by kernel. */
+                    rt_local4.network  = rt->r4.network;
+                    rt_local4.pref_src = rt->r4.pref_src;
+
+                    /* to be more confident about comparing the value, use our nm_platform_ip4_route_cmp()
+                     * implementation. That will also consider parameters that we leave unspecified here. */
+                    if (nm_platform_ip4_route_cmp(&rt->r4,
+                                                  &rt_local4,
+                                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+                        == 0)
+                        continue;
+                }
+            } else {
+                /* for each IPv6 address (that is no longer tentative) kernel adds a route like
+                 *
+                 *  local $ADDR dev $IFACE table local proto kernel metric 0 pref medium
+                 *
+                 * Same as for the IPv4 case. */
+                if (nm_platform_ip_route_get_effective_table(&rt->rx) == local_table
+                    && rt->rx.plen == 128 && rt->rx.rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL
+                    && rt->rx.metric == 0 && rt->r6.rt_pref == NM_ICMPV6_ROUTER_PREF_MEDIUM
+                    && IN6_IS_ADDR_UNSPECIFIED(&rt->r6.gateway)) {
+                    if (rt_local6.plen == 0) {
+                        rt_local6 = (NMPlatformIP6Route){
+                            .ifindex       = ifindex,
+                            .type_coerced  = nm_platform_route_type_coerce(RTN_LOCAL),
+                            .plen          = 128,
+                            .rt_source     = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL,
+                            .metric        = 0,
+                            .table_coerced = nm_platform_route_table_coerce(local_table),
+                            .rt_pref       = NM_ICMPV6_ROUTER_PREF_MEDIUM,
+                            .gateway       = IN6ADDR_ANY_INIT,
+                        };
+                    }
+
+                    rt_local6.network = rt->r6.network;
+
+                    if (nm_platform_ip6_route_cmp(&rt->r6,
+                                                  &rt_local6,
+                                                  NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+                        == 0)
+                        continue;
+                }
+            }
+            break;
+
+        case NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE:
+            break;
+
+        default:
+            nm_assert_not_reached();
+            break;
+        }
 
         g_ptr_array_add(routes_prune, (gpointer) nmp_object_ref(obj));
     }
@@ -4679,7 +4779,7 @@ nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex)
         routes_prune = nm_platform_ip_route_get_prune_list(self,
                                                            AF_INET,
                                                            ifindex,
-                                                           NM_IP_ROUTE_TABLE_SYNC_MODE_ALL);
+                                                           NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE);
         success &= nm_platform_ip_route_sync(self, AF_INET, ifindex, NULL, routes_prune, NULL);
     }
     if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
@@ -4688,7 +4788,7 @@ nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex)
         routes_prune = nm_platform_ip_route_get_prune_list(self,
                                                            AF_INET6,
                                                            ifindex,
-                                                           NM_IP_ROUTE_TABLE_SYNC_MODE_ALL);
+                                                           NM_IP_ROUTE_TABLE_SYNC_MODE_ALL_PRUNE);
         success &= nm_platform_ip_route_sync(self, AF_INET6, ifindex, NULL, routes_prune, NULL);
     }
     return success;
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index 4a117d59..1b977145 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -1542,7 +1542,10 @@ nmtstp_link_bridge_add(NMPlatform *               platform,
 
     ll = NMP_OBJECT_CAST_LNK_BRIDGE(NMP_OBJECT_UP_CAST(pllink)->_link.netlink.lnk);
 
-    g_assert_cmpint(lnk->forward_delay, ==, ll->forward_delay);
+    /* account for roundtrip rounding error with clock_t_to_jiffies()/jiffies_to_clock_t(). */
+    g_assert_cmpint(lnk->forward_delay, >=, ll->forward_delay - 1);
+    g_assert_cmpint(lnk->forward_delay, <=, ll->forward_delay);
+
     g_assert_cmpint(lnk->hello_time, ==, ll->hello_time);
     g_assert_cmpint(lnk->max_age, ==, ll->max_age);
     g_assert_cmpint(lnk->ageing_time, ==, ll->ageing_time);
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 209957d9..a42c4188 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -2707,7 +2707,8 @@ make_tc_setting(shvarFile *ifcfg)
     }
 
     if (nm_setting_tc_config_get_num_qdiscs(s_tc) > 0
-        || nm_setting_tc_config_get_num_tfilters(s_tc) > 0)
+        || nm_setting_tc_config_get_num_tfilters(s_tc) > 0
+        || svGetValueBoolean(ifcfg, "TC_COMMIT", FALSE))
         return NM_SETTING(s_tc);
 
     g_object_unref(s_tc);
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 8da5de47..ada1942a 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -1026,6 +1026,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("STABLE_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("STP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("SUBCHANNELS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("TC_COMMIT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("TEAM_CONFIG", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("TEAM_MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("TEAM_MASTER_UUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 36ec9225..04a1b63d 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
     NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[247];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[248];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index a968fce0..45b76456 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1137,7 +1137,7 @@ write_wired_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
     svSetValueStr(ifcfg, "CTCPROT", nm_setting_wired_get_s390_option_by_key(s_wired, "ctcprot"));
 
     num_opts = nm_setting_wired_get_num_s390_options(s_wired);
-    if (s390_subchannels && num_opts) {
+    if (num_opts > 0) {
         nm_auto_free_gstring GString *tmp = NULL;
 
         for (i = 0; i < num_opts; i++) {
@@ -2511,46 +2511,46 @@ write_sriov_setting(NMConnection *connection, shvarFile *ifcfg)
     }
 }
 
-static gboolean
-write_tc_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
+static void
+write_tc_setting(NMConnection *connection, shvarFile *ifcfg)
 {
     NMSettingTCConfig *s_tc;
-    guint              i, num, n;
+    guint              num_qdiscs;
+    guint              num_filters;
+    guint              i;
+    guint              n;
     char               tag[64];
 
     s_tc = nm_connection_get_setting_tc_config(connection);
     if (!s_tc)
-        return TRUE;
+        return;
 
-    num = nm_setting_tc_config_get_num_qdiscs(s_tc);
-    for (n = 1, i = 0; i < num; i++) {
+    num_qdiscs = nm_setting_tc_config_get_num_qdiscs(s_tc);
+    for (n = 1, i = 0; i < num_qdiscs; i++) {
         NMTCQdisc *   qdisc;
         gs_free char *str = NULL;
 
         qdisc = nm_setting_tc_config_get_qdisc(s_tc, i);
-        str   = nm_utils_tc_qdisc_to_str(qdisc, error);
-        if (!str)
-            return FALSE;
-
+        str   = nm_utils_tc_qdisc_to_str(qdisc, NULL);
+        nm_assert(str);
         svSetValueStr(ifcfg, numbered_tag(tag, "QDISC", n), str);
         n++;
     }
 
-    num = nm_setting_tc_config_get_num_tfilters(s_tc);
-    for (n = 1, i = 0; i < num; i++) {
+    num_filters = nm_setting_tc_config_get_num_tfilters(s_tc);
+    for (n = 1, i = 0; i < num_filters; i++) {
         NMTCTfilter * tfilter;
         gs_free char *str = NULL;
 
         tfilter = nm_setting_tc_config_get_tfilter(s_tc, i);
-        str     = nm_utils_tc_tfilter_to_str(tfilter, error);
-        if (!str)
-            return FALSE;
-
+        str     = nm_utils_tc_tfilter_to_str(tfilter, NULL);
+        nm_assert(str);
         svSetValueStr(ifcfg, numbered_tag(tag, "FILTER", n), str);
         n++;
     }
 
-    return TRUE;
+    if (num_qdiscs == 0 && num_filters == 0)
+        svSetValueBoolean(ifcfg, "TC_COMMIT", TRUE);
 }
 
 static void
@@ -3373,9 +3373,7 @@ do_write_construct(NMConnection *                  connection,
     write_match_setting(connection, ifcfg);
     write_hostname_setting(connection, ifcfg);
     write_sriov_setting(connection, ifcfg);
-
-    if (!write_tc_setting(connection, ifcfg, error))
-        return FALSE;
+    write_tc_setting(connection, ifcfg);
 
     route_path_is_svformat = utils_has_route_file_new_syntax(route_path);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected
new file mode 100644
index 00000000..4df768b4
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected
@@ -0,0 +1,15 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+TC_COMMIT=yes
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+GATEWAY=1.1.1.1
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write TC config"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 59127d01..9d9ed626 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -11109,6 +11109,85 @@ test_tc_read(void)
 }
 
 static void
+test_tc_write_empty(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMSettingWired *              s_wired;
+    NMSettingTCConfig *           s_tc;
+    NMIPAddress *                 addr;
+    GError *                      error = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write TC config",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_utils_uuid_generate_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "eth0",
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NULL);
+
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    nm_ip_address_unref(addr);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+
+    /* TC setting */
+    s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_tc));
+
+    nm_connection_add_setting(connection, nm_setting_proxy_new());
+
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    _writer_new_connec_exp(connection,
+                           TEST_SCRATCH_DIR,
+                           TEST_IFCFG_DIR "/ifcfg-test-tc-write-empty.cexpected",
+                           &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_BOND, NULL);
+
+    nmtst_assert_connection_equals(connection, FALSE, reread, FALSE);
+}
+
+static void
 test_tc_write(void)
 {
     nmtst_auto_unlinkfile char *testfile     = NULL;
@@ -11848,6 +11927,7 @@ main(int argc, char **argv)
 
     g_test_add_func(TPATH "tc/read", test_tc_read);
     g_test_add_func(TPATH "tc/write", test_tc_write);
+    g_test_add_func(TPATH "tc/write_empty", test_tc_write_empty);
     g_test_add_func(TPATH "utils/test_well_known_keys", test_well_known_keys);
     g_test_add_func(TPATH "utils/test_utils_has_route_file_new_syntax",
                     test_utils_has_route_file_new_syntax);
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
index f3dffdae..ca1233f5 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -137,7 +137,11 @@ nms_keyfile_nmmeta_read(const char * dirname,
                                        NMMETA_KF_GROUP_NAME_NMMETA,
                                        NMMETA_KF_KEY_NAME_NMMETA_UUID,
                                        NULL);
-        if (!nm_streq0(v_uuid, uuid))
+        if (!v_uuid)
+            return FALSE;
+        if (strncmp(v_uuid, uuid, uuid_len) != 0)
+            return FALSE;
+        if (v_uuid[uuid_len] != '\0')
             return FALSE;
 
         loaded_path      = g_key_file_get_string(kf,
diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c
index eab494b0..bc2a692e 100644
--- a/src/core/supplicant/nm-supplicant-config.c
+++ b/src/core/supplicant/nm-supplicant-config.c
@@ -841,6 +841,11 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *
             g_string_append(key_mgmt_conf, " wpa-psk-sha256");
         if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
             g_string_append(key_mgmt_conf, " ft-psk");
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SAE)) {
+            g_string_append(key_mgmt_conf, " sae");
+            if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
+                g_string_append(key_mgmt_conf, " ft-sae");
+        }
     } else if (nm_streq(key_mgmt, "wpa-eap")) {
         if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
             g_string_append(key_mgmt_conf, " wpa-eap-sha256");
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index 6d9c604f..a38ebf18 100644
--- a/src/core/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -586,7 +586,7 @@ _bss_info_properties_changed(NMSupplicantInterface *self,
         if (v_v) {
             if (g_variant_lookup(v_v, "Type", "&s", &v_s)) {
                 f = NM_802_11_AP_FLAGS_WPS;
-                if (nm_streq(v_s, "pcb"))
+                if (nm_streq(v_s, "pbc"))
                     f |= NM_802_11_AP_FLAGS_WPS_PBC;
                 else if (nm_streq(v_s, "pin"))
                     f |= NM_802_11_AP_FLAGS_WPS_PIN;
@@ -1171,19 +1171,24 @@ parse_capabilities(NMSupplicantInterface *self, GVariant *capabilities)
     const gboolean                old_prop_scan_ssid   = priv->prop_scan_ssid;
     const guint32                 old_max_scan_ssids   = priv->max_scan_ssids;
     gboolean                      have_ft              = FALSE;
+    gboolean                      have_sae             = FALSE;
     gint32                        max_scan_ssids;
     const char **                 array;
 
     nm_assert(capabilities && g_variant_is_of_type(capabilities, G_VARIANT_TYPE_VARDICT));
 
     if (g_variant_lookup(capabilities, "KeyMgmt", "^a&s", &array)) {
-        have_ft = g_strv_contains(array, "wpa-ft-psk");
+        have_ft  = g_strv_contains(array, "wpa-ft-psk");
+        have_sae = g_strv_contains(array, "sae");
         g_free(array);
     }
 
     priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET(priv->iface_capabilities,
                                                      NM_SUPPL_CAP_TYPE_FT,
                                                      have_ft ? NM_TERNARY_TRUE : NM_TERNARY_FALSE);
+    priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET(priv->iface_capabilities,
+                                                     NM_SUPPL_CAP_TYPE_SAE,
+                                                     have_sae ? NM_TERNARY_TRUE : NM_TERNARY_FALSE);
 
     if (g_variant_lookup(capabilities, "Modes", "^a&s", &array)) {
         /* Setting p2p_capable might toggle _prop_p2p_available_get(). However,
@@ -1255,6 +1260,15 @@ _starting_check_ready(NMSupplicantInterface *self)
         return;
     }
 
+    _LOGD("interface supported features:"
+          " AP%c"
+          " FT%c"
+          " SAE%c"
+          "",
+          NM_SUPPL_CAP_TO_CHAR(priv->iface_capabilities, NM_SUPPL_CAP_TYPE_AP),
+          NM_SUPPL_CAP_TO_CHAR(priv->iface_capabilities, NM_SUPPL_CAP_TYPE_FT),
+          NM_SUPPL_CAP_TO_CHAR(priv->iface_capabilities, NM_SUPPL_CAP_TYPE_SAE));
+
     set_state(self, priv->supp_state);
 }
 
@@ -1278,6 +1292,10 @@ _get_capability(NMSupplicantInterfacePrivate *priv, NMSupplCapType type)
                 value = iface_value;
         }
         break;
+    case NM_SUPPL_CAP_TYPE_SAE:
+        nm_assert(NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type) == NM_TERNARY_DEFAULT);
+        value = NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type);
+        break;
     default:
         nm_assert(NM_SUPPL_CAP_MASK_GET(priv->iface_capabilities, type) == NM_TERNARY_DEFAULT);
         value = NM_SUPPL_CAP_MASK_GET(priv->global_capabilities, type);
@@ -1305,9 +1323,13 @@ nm_supplicant_interface_get_capabilities(NMSupplicantInterface *self)
     caps = NM_SUPPL_CAP_MASK_SET(caps,
                                  NM_SUPPL_CAP_TYPE_FT,
                                  _get_capability(priv, NM_SUPPL_CAP_TYPE_FT));
+    caps = NM_SUPPL_CAP_MASK_SET(caps,
+                                 NM_SUPPL_CAP_TYPE_SAE,
+                                 _get_capability(priv, NM_SUPPL_CAP_TYPE_SAE));
 
     nm_assert(!NM_FLAGS_ANY(priv->iface_capabilities,
-                            ~(NM_SUPPL_CAP_MASK_T_AP_MASK | NM_SUPPL_CAP_MASK_T_FT_MASK)));
+                            ~(NM_SUPPL_CAP_MASK_T_AP_MASK | NM_SUPPL_CAP_MASK_T_FT_MASK
+                              | NM_SUPPL_CAP_MASK_T_SAE_MASK)));
 
 #if NM_MORE_ASSERTS > 10
     {
diff --git a/src/core/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c
index 32554187..7cf1a2bb 100644
--- a/src/core/supplicant/nm-supplicant-manager.c
+++ b/src/core/supplicant/nm-supplicant-manager.c
@@ -168,19 +168,6 @@ _caps_set(NMSupplicantManagerPrivate *priv, NMSupplCapType type, NMTernary value
     priv->capabilities = NM_SUPPL_CAP_MASK_SET(priv->capabilities, type, value);
 }
 
-static char
-_caps_to_char(NMSupplicantManagerPrivate *priv, NMSupplCapType type)
-{
-    NMTernary val;
-
-    val = NM_SUPPL_CAP_MASK_GET(priv->capabilities, type);
-    if (val == NM_TERNARY_TRUE)
-        return '+';
-    if (val == NM_TERNARY_FALSE)
-        return '-';
-    return '?';
-}
-
 /*****************************************************************************/
 
 static void
@@ -395,7 +382,7 @@ _create_iface_complete(NMSupplMgrCreateIfaceHandle *handle,
     nm_clear_g_cancellable(&handle->cancellable);
     nm_ref_string_unref(handle->name_owner);
 
-    nm_g_slice_free_fcn(handle);
+    nm_g_slice_free(handle);
 }
 
 static void
@@ -1008,15 +995,15 @@ _dbus_get_capabilities_cb(GVariant *res, GError *error, gpointer user_data)
           " FAST%c"
           " WFD%c"
           "",
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_AP),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_PMF),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_FILS),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_P2P),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_FT),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_SHA384),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_MESH),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_FAST),
-          _caps_to_char(priv, NM_SUPPL_CAP_TYPE_WFD));
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_AP),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_PMF),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FILS),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_P2P),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FT),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_SHA384),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_MESH),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FAST),
+          NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_WFD));
 
     nm_assert(g_hash_table_size(priv->supp_ifaces) == 0);
     nm_assert(c_list_is_empty(&priv->supp_lst_head));
diff --git a/src/core/supplicant/nm-supplicant-types.h b/src/core/supplicant/nm-supplicant-types.h
index adcf02db..ae2db3f6 100644
--- a/src/core/supplicant/nm-supplicant-types.h
+++ b/src/core/supplicant/nm-supplicant-types.h
@@ -41,6 +41,7 @@ typedef enum {
     NM_SUPPL_CAP_TYPE_FILS,
     NM_SUPPL_CAP_TYPE_P2P,
     NM_SUPPL_CAP_TYPE_FT,
+    NM_SUPPL_CAP_TYPE_SAE,
     NM_SUPPL_CAP_TYPE_SHA384,
     NM_SUPPL_CAP_TYPE_MESH,
     NM_SUPPL_CAP_TYPE_FAST,
@@ -71,6 +72,7 @@ typedef enum {
     _NM_SUPPL_CAP_MASK_DEFINE(MESH),
     _NM_SUPPL_CAP_MASK_DEFINE(WFD),
     _NM_SUPPL_CAP_MASK_DEFINE(FT),
+    _NM_SUPPL_CAP_MASK_DEFINE(SAE),
     _NM_SUPPL_CAP_MASK_DEFINE(SHA384),
 #undef _NM_SUPPL_CAP_MASK_DEFINE
 } NMSupplCapMask;
@@ -114,6 +116,19 @@ NM_SUPPL_CAP_MASK_GET(NMSupplCapMask features, NMSupplCapType type)
     return (NMTernary)(f - 1);
 }
 
+static inline char
+NM_SUPPL_CAP_TO_CHAR(NMSupplCapMask features, NMSupplCapType type)
+{
+    NMTernary val;
+
+    val = NM_SUPPL_CAP_MASK_GET(features, type);
+    if (val == NM_TERNARY_TRUE)
+        return '+';
+    if (val == NM_TERNARY_FALSE)
+        return '-';
+    return '?';
+}
+
 /*****************************************************************************/
 
 /**
diff --git a/src/core/tests/test-systemd.c b/src/core/tests/test-systemd.c
index 03576c13..fa510366 100644
--- a/src/core/tests/test-systemd.c
+++ b/src/core/tests/test-systemd.c
@@ -247,11 +247,11 @@ _test_unbase64mem_mem(const char *base64, const guint8 *expected_arr, gsize expe
 {
     gs_free char *expected_base64 = NULL;
     int           r;
-    gs_free guint8 *exp2_arr = NULL;
-    gs_free guint8 *exp3_arr = NULL;
-    gsize           exp2_len;
-    gsize           exp3_len;
-    gsize           i;
+    nm_auto_free guint8 *exp2_arr = NULL;
+    nm_auto_free guint8 *exp3_arr = NULL;
+    gsize                exp2_len;
+    gsize                exp3_len;
+    gsize                i;
 
     expected_base64 = g_base64_encode(expected_arr, expected_len);