summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/devices/nm-device-bond.c95
-rw-r--r--src/core/devices/nm-device-private.h5
-rw-r--r--src/core/devices/nm-device-veth.c4
-rw-r--r--src/core/devices/nm-device.c96
-rw-r--r--src/core/devices/nm-device.h5
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c155
-rw-r--r--src/core/dhcp/nm-dhcp-client.c175
-rw-r--r--src/core/dhcp/nm-dhcp-helper.c19
-rw-r--r--src/core/dns/nm-dns-manager.c9
-rw-r--r--src/core/nm-config-data.c57
-rw-r--r--src/core/nm-firewall-utils.c125
-rw-r--r--src/core/nm-firewall-utils.h13
-rw-r--r--src/core/nm-policy.c143
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c58
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ipoib9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c74
-rw-r--r--src/libnm-core-impl/nm-setting-bond-port.c8
-rw-r--r--src/libnm-core-impl/nm-setting-bond.c71
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c3
-rw-r--r--src/libnm-core-impl/tests/test-setting.c61
-rw-r--r--src/libnm-core-intern/nm-core-internal.h5
-rw-r--r--src/libnm-core-public/nm-version-macros.h2
-rw-r--r--src/libnm-glib-aux/nm-keyfile-aux.c2
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c33
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h8
-rw-r--r--src/libnm-glib-aux/nm-str-buf.h9
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c10
-rw-r--r--src/libnm-platform/nm-linux-platform.c81
-rw-r--r--src/libnm-platform/nm-netlink.c36
-rw-r--r--src/libnm-platform/nm-netlink.h6
-rw-r--r--src/libnm-platform/nm-platform.c14
-rw-r--r--src/libnm-platform/nm-platform.h3
-rw-r--r--src/libnm-platform/nmp-global-tracker.c20
-rw-r--r--src/libnm-platform/tests/test-nm-platform.c5
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-nl80211.c2
-rw-r--r--src/libnm-platform/wpan/nm-wpan-utils.c2
-rw-r--r--src/nm-initrd-generator/nm-initrd-generator.h3
-rw-r--r--src/nm-initrd-generator/nmi-cmdline-reader.c6
-rw-r--r--src/nm-initrd-generator/nmi-dt-reader.c2
-rw-r--r--src/nm-initrd-generator/nmi-ibft-reader.c2
40 files changed, 1024 insertions, 412 deletions
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 44bb316d..3e083de4 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -395,61 +395,57 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
 {
     const char *opt_value;
 
+#define _v_fcn(fcn, s_bond, opt) (fcn(nm_setting_bond_get_option_normalized((s_bond), (opt))))
+#define _v_u8(s_bond, opt)       _nm_setting_bond_opt_value_as_u8((s_bond), (opt))
+#define _v_u16(s_bond, opt)      _nm_setting_bond_opt_value_as_u16((s_bond), (opt))
+#define _v_u32(s_bond, opt)      _nm_setting_bond_opt_value_as_u32((s_bond), (opt))
+#define _v_intbool(s_bond, opt)  _nm_setting_bond_opt_value_as_intbool((s_bond), (opt))
+
     *props = (NMPlatformLnkBond){
-        .mode = _nm_setting_bond_mode_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_MODE)),
+        .mode      = _v_fcn(_nm_setting_bond_mode_from_string, s_bond, NM_SETTING_BOND_OPTION_MODE),
         .primary   = _setting_bond_primary_opt_as_ifindex(s_bond),
-        .miimon    = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
-        .updelay   = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_UPDELAY),
-        .downdelay = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY),
-        .arp_interval =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL),
-        .resend_igmp =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP),
-        .min_links = _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS),
-        .lp_interval =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL),
-        .packets_per_port =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE),
-        .peer_notif_delay =
-            _nm_setting_bond_opt_value_as_u32(s_bond, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY),
-        .arp_all_targets = _nm_setting_bond_arp_all_targets_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS)),
-        .arp_validate = _nm_setting_bond_arp_validate_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE)),
-        .ad_actor_sys_prio =
-            _nm_setting_bond_opt_value_as_u16(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO),
-        .ad_user_port_key =
-            _nm_setting_bond_opt_value_as_u16(s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY),
-        .primary_reselect = _nm_setting_bond_primary_reselect_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT)),
-        .fail_over_mac = _nm_setting_bond_fail_over_mac_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC)),
-        .xmit_hash_policy = _nm_setting_bond_xmit_hash_policy_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY)),
-        .num_grat_arp =
-            _nm_setting_bond_opt_value_as_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP),
-        .all_ports_active =
-            _nm_setting_bond_opt_value_as_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE),
-        .lacp_rate = _nm_setting_bond_lacp_rate_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_LACP_RATE)),
-        .ad_select = _nm_setting_bond_ad_select_from_string(
-            nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_AD_SELECT)),
+        .miimon    = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
+        .updelay   = _v_u32(s_bond, NM_SETTING_BOND_OPTION_UPDELAY),
+        .downdelay = _v_u32(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY),
+        .arp_interval      = _v_u32(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL),
+        .resend_igmp       = _v_u32(s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP),
+        .min_links         = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS),
+        .lp_interval       = _v_u32(s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL),
+        .packets_per_port  = _v_u32(s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE),
+        .peer_notif_delay  = _v_u32(s_bond, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY),
+        .arp_all_targets   = _v_fcn(_nm_setting_bond_arp_all_targets_from_string,
+                                  s_bond,
+                                  NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS),
+        .arp_validate      = _v_fcn(_nm_setting_bond_arp_validate_from_string,
+                               s_bond,
+                               NM_SETTING_BOND_OPTION_ARP_VALIDATE),
+        .ad_actor_sys_prio = _v_u16(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO),
+        .ad_user_port_key  = _v_u16(s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY),
+        .primary_reselect  = _v_fcn(_nm_setting_bond_primary_reselect_from_string,
+                                   s_bond,
+                                   NM_SETTING_BOND_OPTION_PRIMARY_RESELECT),
+        .fail_over_mac     = _v_fcn(_nm_setting_bond_fail_over_mac_from_string,
+                                s_bond,
+                                NM_SETTING_BOND_OPTION_FAIL_OVER_MAC),
+        .xmit_hash_policy  = _v_fcn(_nm_setting_bond_xmit_hash_policy_from_string,
+                                   s_bond,
+                                   NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY),
+        .num_grat_arp      = _v_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP),
+        .all_ports_active  = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE),
+        .lacp_rate         = _v_fcn(_nm_setting_bond_lacp_rate_from_string,
+                            s_bond,
+                            NM_SETTING_BOND_OPTION_LACP_RATE),
+        .ad_select         = _v_fcn(_nm_setting_bond_ad_select_from_string,
+                            s_bond,
+                            NM_SETTING_BOND_OPTION_AD_SELECT),
+        .use_carrier       = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER),
+        .tlb_dynamic_lb    = _v_intbool(s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB),
     };
 
     nm_ether_addr_from_string(
         &props->ad_actor_system,
         nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM));
 
-    opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER);
-    if (opt_value != NULL)
-        props->use_carrier = _nm_utils_ascii_str_to_bool(opt_value, FALSE);
-
-    opt_value =
-        nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB);
-    if (opt_value != NULL)
-        props->tlb_dynamic_lb = _nm_utils_ascii_str_to_bool(opt_value, FALSE);
-
     opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
     if (opt_value != NULL)
         props->arp_ip_targets_num =
@@ -459,7 +455,6 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
     props->updelay_has          = props->miimon_has && props->miimon;
     props->downdelay_has        = props->miimon_has && props->miimon;
     props->peer_notif_delay_has = (props->miimon || props->arp_interval) && props->peer_notif_delay;
-    props->arp_all_targets_has  = props->arp_interval && props->arp_all_targets;
     props->resend_igmp_has      = props->resend_igmp != 1;
     props->lp_interval_has      = props->lp_interval != 1;
     props->tlb_dynamic_lb_has   = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB);
@@ -493,6 +488,10 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE))
             ret = NM_ACT_STAGE_RETURN_FAILURE;
     }
+
+    /* This is a workaround because netlink do not support ifname as primary */
+    set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
+
     nm_device_bring_up(device, TRUE, NULL);
 
     return ret;
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index b54aed6a..31424d5c 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -89,7 +89,10 @@ nm_device_devip_set_state(NMDevice             *self,
     nm_assert(NM_IS_DEVICE(self));
     nm_assert_addr_family_or_unspec(addr_family);
     nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd));
-    nm_assert(NM_IN_SET(ip_state, NM_DEVICE_IP_STATE_PENDING, NM_DEVICE_IP_STATE_READY));
+    nm_assert(NM_IN_SET(ip_state,
+                        NM_DEVICE_IP_STATE_NONE,
+                        NM_DEVICE_IP_STATE_PENDING,
+                        NM_DEVICE_IP_STATE_READY));
 
     nm_device_devip_set_state_full(self, addr_family, ip_state, l3cd, NM_DEVICE_STATE_REASON_NONE);
 }
diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index 63dfd8bb..c3482e78 100644
--- a/src/core/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -101,8 +101,8 @@ create_and_realize(NMDevice              *device,
     peer        = nm_setting_veth_get_peer(s_veth);
     peer_device = nm_manager_get_device(NM_MANAGER_GET, peer, NM_DEVICE_TYPE_VETH);
     if (peer_device) {
-        /* The veth device and its peer already exist. No need to create it again. */
-        if (nm_streq0(nm_device_get_iface(nm_device_parent_get_device(peer_device)), iface))
+        if (nm_device_parent_get_device(peer_device))
+            /* The veth device and its peer already exist. No need to create it again. */
             return TRUE;
     }
 
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index d63b902b..6be9bc7c 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -96,6 +96,8 @@
 #define NM_DEVICE_AUTH_RETRIES_INFINITY -2
 #define NM_DEVICE_AUTH_RETRIES_DEFAULT  3
 
+#define AUTOCONNECT_RESET_RETRIES_TIMER 300
+
 /*****************************************************************************/
 
 typedef void (*ActivationHandleFunc)(NMDevice *self);
@@ -761,6 +763,9 @@ typedef struct _NMDevicePrivate {
 
     GVariant *ports_variant; /* Array of port devices D-Bus path */
     char     *prop_ip_iface; /* IP interface D-Bus property */
+
+    int    autoconnect_retries;
+    gint32 autoconnect_retries_blocked_until;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -778,7 +783,9 @@ static void _dev_l3_cfg_commit_type_reset(NMDevice *self);
 
 static gboolean nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure);
 static void     nm_device_slave_notify_enslave(NMDevice *self, gboolean success);
-static void     nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason);
+static void     nm_device_slave_notify_release(NMDevice           *self,
+                                               NMDeviceStateReason reason,
+                                               ReleaseSlaveType    release_type);
 
 static void _dev_ipll6_start(NMDevice *self);
 
@@ -6236,7 +6243,7 @@ nm_device_master_release_slave(NMDevice           *self,
                                                release_type >= RELEASE_SLAVE_TYPE_CONFIG);
 
     /* raise notifications about the release, including clearing is_enslaved. */
-    nm_device_slave_notify_release(slave, reason);
+    nm_device_slave_notify_release(slave, reason, release_type);
 
     /* keep both alive until the end of the function.
      * Transfers ownership from slave_priv->master.  */
@@ -6536,12 +6543,16 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
 
     g_return_if_fail(plink);
 
-    if (plink->master <= 0)
-        goto out;
-
-    master                  = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master);
-    plink_master            = nm_platform_link_get(nm_device_get_platform(self), plink->master);
-    plink_master_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink_master));
+    if (plink->master > 0) {
+        master                  = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master);
+        plink_master            = nm_platform_link_get(nm_device_get_platform(self), plink->master);
+        plink_master_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink_master));
+    } else {
+        if (priv->master_ifindex == 0)
+            goto out;
+        master       = NULL;
+        plink_master = NULL;
+    }
 
     if (master == NULL && plink_master
         && NM_IN_STRSET(plink_master->name, "ovs-system", "ovs-netdev")
@@ -8024,7 +8035,9 @@ nm_device_slave_notify_enslave(NMDevice *self, gboolean success)
  * Notifies a slave that it has been released, and why.
  */
 static void
-nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
+nm_device_slave_notify_release(NMDevice           *self,
+                               NMDeviceStateReason reason,
+                               ReleaseSlaveType    release_type)
 {
     NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
     NMConnection    *connection = nm_device_get_applied_connection(self);
@@ -8032,7 +8045,7 @@ nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
 
     g_return_if_fail(priv->master);
 
-    if (!priv->is_enslaved)
+    if (!priv->is_enslaved && release_type == RELEASE_SLAVE_TYPE_NO_CONFIG)
         return;
 
     if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state <= NM_DEVICE_STATE_ACTIVATED) {
@@ -8956,7 +8969,7 @@ nm_device_emit_recheck_assume(gpointer user_data)
     priv = NM_DEVICE_GET_PRIVATE(self);
 
     priv->recheck_assume_id = 0;
-    if (!nm_device_get_act_request(self))
+    if (!priv->queued_act_request && !nm_device_get_act_request(self))
         g_signal_emit(self, signals[RECHECK_ASSUME], 0);
 
     return G_SOURCE_REMOVE;
@@ -9954,6 +9967,7 @@ nm_device_devip_set_state_full(NMDevice             *self,
 
     nm_assert_addr_family_or_unspec(addr_family);
     nm_assert(NM_IN_SET(ip_state,
+                        NM_DEVICE_IP_STATE_NONE,
                         NM_DEVICE_IP_STATE_PENDING,
                         NM_DEVICE_IP_STATE_READY,
                         NM_DEVICE_IP_STATE_FAILED));
@@ -9961,7 +9975,7 @@ nm_device_devip_set_state_full(NMDevice             *self,
 
     nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED)
               == (failed_reason == NM_DEVICE_STATE_REASON_NONE));
-    nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED) || !l3cd);
+    nm_assert(NM_IN_SET(ip_state, NM_DEVICE_IP_STATE_PENDING, NM_DEVICE_IP_STATE_READY) || !l3cd);
 
     p = _dev_ipdev_data(self, addr_family);
 
@@ -12206,7 +12220,7 @@ _dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
         }
 
         if (priv->ipshared_data_4.v4.firewall_config) {
-            nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, FALSE);
+            nm_firewall_config_apply_sync(priv->ipshared_data_4.v4.firewall_config, FALSE);
             nm_clear_pointer(&priv->ipshared_data_4.v4.firewall_config, nm_firewall_config_free);
         }
 
@@ -12353,8 +12367,8 @@ _dev_ipshared4_start(NMDevice *self)
         goto out_fail;
 
     priv->ipshared_data_4.v4.firewall_config =
-        nm_firewall_config_new(ip_iface, ip4_addr.address, ip4_addr.plen);
-    nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, TRUE);
+        nm_firewall_config_new_shared(ip_iface, ip4_addr.address, ip4_addr.plen);
+    nm_firewall_config_apply_sync(priv->ipshared_data_4.v4.firewall_config, TRUE);
 
     priv->ipshared_data_4.v4.l3cd = nm_l3_config_data_ref(l3cd);
     _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, l3cd, FALSE);
@@ -16857,6 +16871,49 @@ nm_device_get_initial_hw_address(NMDevice *self)
     return NM_DEVICE_GET_PRIVATE(self)->hw_addr_initial;
 }
 
+void
+nm_device_set_autoconnect_retries(NMDevice *self, int tries)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (priv->autoconnect_retries != tries) {
+        _LOGT(LOGD_DEVICE, "autoconnect: retries set %d", tries);
+        priv->autoconnect_retries = tries;
+    }
+
+    if (tries)
+        priv->autoconnect_retries_blocked_until = 0; /* we are not blocked anymore */
+    else
+        priv->autoconnect_retries_blocked_until =
+            nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER;
+}
+
+int
+nm_device_get_autoconnect_retries(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    return priv->autoconnect_retries;
+}
+
+gint32
+nm_device_autoconnect_retries_blocked_until(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    return priv->autoconnect_retries_blocked_until;
+}
+
+void
+nm_device_autoconnect_retries_reset(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    /* default value, we will sync. with connection value when needed */
+    priv->autoconnect_retries               = -2;
+    priv->autoconnect_retries_blocked_until = 0;
+}
+
 /**
  * nm_device_spec_match_list:
  * @self: an #NMDevice
@@ -17161,6 +17218,13 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     /* If the device is not supposed to have addresses,
      * return an immediate empty result.*/
     if (!nm_device_get_applied_connection(self)) {
+        nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
+        NM_SET_OUT(out_wait, FALSE);
+        return NULL;
+    }
+
+    if (!priv->carrier) {
+        nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
         NM_SET_OUT(out_wait, FALSE);
         return NULL;
     }
@@ -17601,6 +17665,8 @@ nm_device_init(NMDevice *self)
     priv->sys_iface_state_      = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
 
     priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
+
+    priv->autoconnect_retries = -2;
 }
 
 static GObject *
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index de850e68..fea46bb7 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -464,6 +464,11 @@ const char *nm_device_get_permanent_hw_address_full(NMDevice *self,
                                                     gboolean *out_is_fake);
 const char *nm_device_get_initial_hw_address(NMDevice *dev);
 
+void   nm_device_set_autoconnect_retries(NMDevice *self, int tries);
+int    nm_device_get_autoconnect_retries(NMDevice *self);
+gint32 nm_device_autoconnect_retries_blocked_until(NMDevice *self);
+void   nm_device_autoconnect_retries_reset(NMDevice *self);
+
 NMDhcpConfig *nm_device_get_dhcp_config(NMDevice *dev, int addr_family);
 
 NML3Cfg *nm_device_get_l3cfg(NMDevice *self);
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index e7c96852..d3e858a1 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -18,6 +18,7 @@
 #include "nm-manager.h"
 #include "nm-setting-ovs-external-ids.h"
 #include "nm-priv-helper-call.h"
+#include "libnm-platform/nm-platform.h"
 
 /*****************************************************************************/
 
@@ -120,6 +121,7 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
+    NMPlatform        *platform;
     GSocketConnection *conn;
     GCancellable      *conn_cancellable;
     char               buf[4096]; /* Input buffer */
@@ -135,8 +137,14 @@ typedef struct {
     GHashTable *bridges;    /* bridge uuid => OpenvswitchBridge */
     char       *db_uuid;
     guint       num_failures;
-    guint       num_pending_deletions;
     bool        ready : 1;
+    struct {
+        GPtrArray *interfaces;      /* Interface names we are waiting to go away */
+        GSource   *timeout_source;  /* After all deletions complete, wait this
+                                    * timeout for interfaces to disappear */
+        gulong     link_changed_id; /* Platform link-changed signal handle */
+        guint      num_pending_del; /* Number of ovsdb deletions pending */
+    } cleanup;
 } NMOvsdbPrivate;
 
 struct _NMOvsdb {
@@ -161,6 +169,7 @@ static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposin
 static void ovsdb_read(NMOvsdb *self);
 static void ovsdb_write(NMOvsdb *self);
 static void ovsdb_next_command(NMOvsdb *self);
+static void cleanup_check_ready(NMOvsdb *self);
 
 /*****************************************************************************/
 
@@ -2283,21 +2292,114 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 }
 
 static void
-_check_ready(NMOvsdb *self)
+cleanup_emit_ready(NMOvsdb *self, const char *reason)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    _LOGT("cleanup: ready (%s)", reason);
+
+    nm_clear_pointer(&priv->cleanup.interfaces, g_ptr_array_unref);
+    nm_clear_g_source_inst(&priv->cleanup.timeout_source);
+    nm_clear_g_signal_handler(priv->platform, &priv->cleanup.link_changed_id);
+
+    priv->ready = TRUE;
+    g_signal_emit(self, signals[READY], 0);
+    nm_manager_unblock_failed_ovs_interfaces(nm_manager_get());
+}
+
+static gboolean
+cleanup_timeout(NMOvsdb *self)
+{
+    cleanup_emit_ready(self, "timeout");
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+cleanup_link_cb(NMPlatform     *platform,
+                int             obj_type_i,
+                int             ifindex,
+                NMPlatformLink *plink,
+                int             change_type_i,
+                gpointer        user_data)
+{
+    const NMPlatformSignalChangeType change_type = change_type_i;
+
+    if (change_type != NM_PLATFORM_SIGNAL_REMOVED)
+        return;
+
+    cleanup_check_ready(user_data);
+}
+
+static void
+cleanup_check_ready(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+    guint           i    = 0;
 
     nm_assert(!priv->ready);
 
-    if (priv->num_pending_deletions == 0) {
-        priv->ready = TRUE;
-        g_signal_emit(self, signals[READY], 0);
-        nm_manager_unblock_failed_ovs_interfaces(nm_manager_get());
+    if (priv->cleanup.num_pending_del > 0)
+        return;
+
+    /* After we have deleted an interface from ovsdb, the link will stay
+     * in platform until ovs-vswitch removes it. To avoid race conditions,
+     * we need to wait until the link goes away; otherwise, after adding the
+     * interface again, these race conditions can happen:
+     * 1) we see the link in platform, and proceed with activation. But after
+     *    that, ovs-vswitchd reads the updates from ovsdb-server and deletes/recreates
+     *    the link.
+     * 2) ovs-vswitch combines the delete/insert of the interface to a no-op. NM sees
+     *    the link staying in platform, but doesn't know whether the link is ready
+     *    or we are again in case 1)
+     * In other words, it's necessary to wait that the link goes away before inserting
+     * the interface again.
+     */
+    while (i < nm_g_ptr_array_len(priv->cleanup.interfaces)) {
+        const char                  *ifname;
+        const NMDedupMultiHeadEntry *pl_links_head_entry;
+        NMDedupMultiIter             pliter;
+        const NMPlatformLink        *link;
+        gboolean                     found = FALSE;
+
+        ifname              = priv->cleanup.interfaces->pdata[i];
+        pl_links_head_entry = nm_platform_lookup_link_by_ifname(priv->platform, ifname);
+        nmp_cache_iter_for_each_link (&pliter, pl_links_head_entry, &link) {
+            if (link->type == NM_LINK_TYPE_OPENVSWITCH
+                && nmp_object_is_visible(NMP_OBJECT_UP_CAST(link))) {
+                found = TRUE;
+                break;
+            }
+        }
+
+        if (!found) {
+            g_ptr_array_remove_index_fast(priv->cleanup.interfaces, i);
+            continue;
+        }
+        i++;
     }
+
+    if (nm_g_ptr_array_len(priv->cleanup.interfaces) == 0) {
+        cleanup_emit_ready(self, "all interfaces deleted");
+        return;
+    }
+
+    _LOGT("cleanup: still waiting for %d interfaces", priv->cleanup.interfaces->len);
+
+    if (priv->cleanup.timeout_source) {
+        /* We already registered the timeout/change-callback */
+        return;
+    }
+
+    priv->cleanup.timeout_source =
+        nm_g_timeout_add_seconds_source(6, G_SOURCE_FUNC(cleanup_timeout), self);
+    priv->cleanup.link_changed_id = g_signal_connect(priv->platform,
+                                                     NM_PLATFORM_SIGNAL_LINK_CHANGED,
+                                                     G_CALLBACK(cleanup_link_cb),
+                                                     self);
 }
 
 static void
-_del_initial_iface_cb(GError *error, gpointer user_data)
+cleanup_del_iface_cb(GError *error, gpointer user_data)
 {
     NMOvsdb        *self;
     gs_free char   *ifname = NULL;
@@ -2309,18 +2411,18 @@ _del_initial_iface_cb(GError *error, gpointer user_data)
         return;
 
     priv = NM_OVSDB_GET_PRIVATE(self);
-    nm_assert(priv->num_pending_deletions > 0);
-    priv->num_pending_deletions--;
+    nm_assert(priv->cleanup.num_pending_del > 0);
+    priv->cleanup.num_pending_del--;
 
-    _LOGD("delete initial interface '%s': %s %s%s%s, pending %u",
+    _LOGD("cleanup: deleted interface '%s': %s %s%s%s, pending %u",
           ifname,
           error ? "error" : "success",
           error ? "(" : "",
           error ? error->message : "",
           error ? ")" : "",
-          priv->num_pending_deletions);
+          priv->cleanup.num_pending_del);
 
-    _check_ready(self);
+    cleanup_check_ready(self);
 }
 
 static void
@@ -2331,7 +2433,7 @@ ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
     NMUtilsUserData            *data;
     GHashTableIter              iter;
 
-    if (priv->ready || priv->num_pending_deletions != 0)
+    if (priv->ready || priv->cleanup.num_pending_del > 0 || priv->cleanup.interfaces)
         return;
 
     /* Delete OVS interfaces added by NM. Bridges and ports and
@@ -2339,17 +2441,22 @@ ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
      * when no interface is present. */
     g_hash_table_iter_init(&iter, self->_priv.interfaces);
     while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &interface)) {
-        if (interface->connection_uuid) {
-            priv->num_pending_deletions++;
-            _LOGD("deleting initial interface '%s' (pending: %u)",
-                  interface->name,
-                  priv->num_pending_deletions);
-            data = nm_utils_user_data_pack(self, g_strdup(interface->name));
-            nm_ovsdb_del_interface(self, interface->name, _del_initial_iface_cb, data);
+        if (!interface->connection_uuid) {
+            /* not created by NM, ignore */
+            continue;
         }
+
+        if (!priv->cleanup.interfaces)
+            priv->cleanup.interfaces = g_ptr_array_new_with_free_func(g_free);
+        g_ptr_array_add(priv->cleanup.interfaces, g_strdup(interface->name));
+
+        _LOGD("cleanup: deleting interface '%s'", interface->name);
+        priv->cleanup.num_pending_del++;
+        data = nm_utils_user_data_pack(self, g_strdup(interface->name));
+        nm_ovsdb_del_interface(self, interface->name, cleanup_del_iface_cb, data);
     }
 
-    _check_ready(self);
+    cleanup_check_ready(self);
 }
 
 static void
@@ -2622,8 +2729,9 @@ nm_ovsdb_init(NMOvsdb *self)
 
     c_list_init(&priv->calls_lst_head);
 
-    priv->input  = g_string_new(NULL);
-    priv->output = g_string_new(NULL);
+    priv->platform = g_object_ref(NM_PLATFORM_GET);
+    priv->input    = g_string_new(NULL);
+    priv->output   = g_string_new(NULL);
     priv->bridges =
         g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL);
     priv->ports =
@@ -2653,6 +2761,7 @@ dispose(GObject *object)
         priv->output = NULL;
     }
 
+    g_clear_object(&priv->platform);
     nm_clear_pointer(&priv->bridges, g_hash_table_destroy);
     nm_clear_pointer(&priv->ports, g_hash_table_destroy);
     nm_clear_pointer(&priv->interfaces, g_hash_table_destroy);
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index 77cfeecf..1329b953 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -115,9 +115,6 @@ typedef struct _NMDhcpClientPrivate {
                 in_addr_t    addr;
                 NMOptionBool state;
             } acd;
-            struct {
-                GDBusMethodInvocation *invocation;
-            } bound;
         } v4;
         struct {
             GSource *lladdr_timeout_source;
@@ -125,6 +122,8 @@ typedef struct _NMDhcpClientPrivate {
         } v6;
     };
 
+    GDBusMethodInvocation *invocation;
+
     struct {
         gulong id;
         bool   wait_dhcp_commit : 1;
@@ -909,13 +908,10 @@ _accept(NMDhcpClient *self, const NML3ConfigData *l3cd, GError **error)
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    if (!NM_IS_IPv4(priv->config.addr_family))
+    if (!priv->invocation)
         return TRUE;
 
-    if (!priv->v4.bound.invocation)
-        return TRUE;
-
-    g_dbus_method_invocation_return_value(g_steal_pointer(&priv->v4.bound.invocation), NULL);
+    g_dbus_method_invocation_return_value(g_steal_pointer(&priv->invocation), NULL);
     return TRUE;
 }
 
@@ -939,20 +935,17 @@ decline(NMDhcpClient *self, const NML3ConfigData *l3cd, const char *error_messag
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    if (!NM_IS_IPv4(priv->config.addr_family))
-        return TRUE;
-
-    if (!priv->v4.bound.invocation) {
+    if (!priv->invocation) {
         nm_utils_error_set(error,
                            NM_UTILS_ERROR_UNKNOWN,
                            "calling decline in unexpected script state");
         return FALSE;
     }
-
-    g_dbus_method_invocation_return_error(g_steal_pointer(&priv->v4.bound.invocation),
+    g_dbus_method_invocation_return_error(g_steal_pointer(&priv->invocation),
                                           NM_DEVICE_ERROR,
                                           NM_DEVICE_ERROR_FAILED,
-                                          "acd failed");
+                                          NM_IS_IPv4(priv->config.addr_family) ? "ACD failed"
+                                                                               : "DAD failed");
     return TRUE;
 }
 
@@ -1043,8 +1036,11 @@ ipv6_lladdr_find(NMDhcpClient *self)
     return NULL;
 }
 
-static const NMPlatformIP6Address *
-ipv6_tentative_addr_find(NMDhcpClient *self)
+static void
+ipv6_tentative_addr_check(NMDhcpClient                *self,
+                          GPtrArray                  **tentative,
+                          GPtrArray                  **missing,
+                          const NMPlatformIP6Address **valid)
 {
     NMDhcpClientPrivate        *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
     NMDedupMultiIter            iter;
@@ -1062,16 +1058,26 @@ ipv6_tentative_addr_find(NMDhcpClient *self)
                                                                     NMP_CACHE_ID_TYPE_OBJECT_TYPE,
                                                                     &needle));
         if (!pladdr) {
-            /* Address was removed from platform */
+            /* address removed: we assume that's because DAD failed */
+            if (missing) {
+                if (!*missing)
+                    *missing = g_ptr_array_new();
+                g_ptr_array_add(*missing, (gpointer) addr);
+            }
             continue;
         }
 
         if (NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_TENTATIVE)
-            && !NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_OPTIMISTIC))
-            return pladdr;
-    }
+            && !NM_FLAGS_HAS(pladdr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
+            if (tentative) {
+                if (!*tentative)
+                    *tentative = g_ptr_array_new();
+                g_ptr_array_add(*tentative, (gpointer) addr);
+            }
+        }
 
-    return NULL;
+        NM_SET_OUT(valid, addr);
+    }
 }
 
 static void
@@ -1108,21 +1114,61 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp
 
     if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE
         && priv->l3cfg_notify.wait_ipv6_dad) {
-        const NMPlatformIP6Address *tentative;
+        gs_unref_ptrarray GPtrArray *tentative = NULL;
+        gs_unref_ptrarray GPtrArray *missing   = NULL;
+        const NMPlatformIP6Address  *valid     = NULL;
+        char                         str[NM_UTILS_TO_STRING_BUFFER_SIZE];
+        guint                        i;
+        gs_free_error GError        *error = NULL;
+
+        ipv6_tentative_addr_check(self, &tentative, &missing, &valid);
+        if (tentative) {
+            for (i = 0; i < tentative->len; i++) {
+                _LOGD("still waiting DAD for address: %s",
+                      nm_platform_ip6_address_to_string(tentative->pdata[i], str, sizeof(str)));
+            }
+        } else {
+            /* done */
 
-        tentative = ipv6_tentative_addr_find(self);
-        if (!tentative) {
-            _LOGD("addresses in the lease completed DAD");
             priv->l3cfg_notify.wait_ipv6_dad = FALSE;
             nm_clear_g_source_inst(&priv->v6.dad_timeout_source);
             l3_cfg_notify_check_connected(self);
-            _emit_notify(
-                self,
-                &((NMDhcpClientNotifyData){.notify_type  = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
-                                           .lease_update = {
-                                               .l3cd     = priv->l3cd_curr,
-                                               .accepted = TRUE,
-                                           }}));
+
+            if (missing) {
+                for (i = 0; i < missing->len; i++) {
+                    _LOGE("DAD failed for address: %s",
+                          nm_platform_ip6_address_to_string(missing->pdata[i], str, sizeof(str)));
+                }
+            }
+
+            if (valid) {
+                /* at least one non-duplicate address */
+                _LOGD("addresses in the lease completed DAD: accept the lease");
+
+                if (_dhcp_client_accept(self, priv->l3cd_curr, &error)) {
+                    _emit_notify(self,
+                                 &((NMDhcpClientNotifyData){
+                                     .notify_type  = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
+                                     .lease_update = {
+                                         .l3cd     = priv->l3cd_curr,
+                                         .accepted = TRUE,
+                                     }}));
+                } else {
+                    gs_free char *reason =
+                        g_strdup_printf("error accepting lease: %s", error->message);
+
+                    _LOGD("accept failed: %s", error->message);
+                    _emit_notify(self,
+                                 &((NMDhcpClientNotifyData){
+                                     .notify_type         = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
+                                     .it_looks_bad.reason = reason,
+                                 }));
+                }
+            } else {
+                _LOGD("decline the lease");
+                if (!_dhcp_client_decline(self, priv->l3cd_curr, "DAD failed", &error))
+                    _LOGD("decline failed: %s", error->message);
+            }
         }
     }
 
@@ -1155,20 +1201,23 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp
                                                     address4->peer_address))
                 goto wait_dhcp_commit_done;
         } else {
-            const NMPlatformIP6Address *address6 = (const NMPlatformIP6Address *) lease_address;
-            const NMPlatformIP6Address *tentative;
-            char                        str[NM_UTILS_TO_STRING_BUFFER_SIZE];
+            const NMPlatformIP6Address  *address6  = (const NMPlatformIP6Address *) lease_address;
+            gs_unref_ptrarray GPtrArray *tentative = NULL;
+            char                         str[NM_UTILS_TO_STRING_BUFFER_SIZE];
+            guint                        i;
 
             if (!nm_l3_config_data_lookup_address_6(committed_l3cd, &address6->address))
                 goto wait_dhcp_commit_done;
 
-            tentative = ipv6_tentative_addr_find(self);
+            ipv6_tentative_addr_check(self, &tentative, NULL, NULL);
             if (tentative) {
                 priv->l3cfg_notify.wait_ipv6_dad = TRUE;
                 priv->v6.dad_timeout_source =
                     nm_g_timeout_add_seconds_source(30, ipv6_dad_timeout, self);
-                _LOGD("wait DAD for address %s",
-                      nm_platform_ip6_address_to_string(tentative, str, sizeof(str)));
+                for (i = 0; i < tentative->len; i++) {
+                    _LOGD("wait DAD for address %s",
+                          nm_platform_ip6_address_to_string(tentative->pdata[i], str, sizeof(str)));
+                }
             } else {
                 priv->l3cfg_notify.wait_ipv6_dad = FALSE;
                 nm_clear_g_source_inst(&priv->v6.dad_timeout_source);
@@ -1179,22 +1228,22 @@ l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDhcp
 
         l3_cfg_notify_check_connected(self);
 
-        _LOGD("accept lease");
+        if (priv->config.addr_family == AF_INET || !priv->l3cfg_notify.wait_ipv6_dad) {
+            _LOGD("accept lease");
 
-        if (!_dhcp_client_accept(self, priv->l3cd_curr, &error)) {
-            gs_free char *reason = g_strdup_printf("error accepting lease: %s", error->message);
+            if (!_dhcp_client_accept(self, priv->l3cd_curr, &error)) {
+                gs_free char *reason = g_strdup_printf("error accepting lease: %s", error->message);
 
-            _LOGD("accept failed: %s", error->message);
+                _LOGD("accept failed: %s", error->message);
 
-            _emit_notify(self,
-                         &((NMDhcpClientNotifyData){
-                             .notify_type         = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
-                             .it_looks_bad.reason = reason,
-                         }));
-            goto wait_dhcp_commit_done;
-        }
+                _emit_notify(self,
+                             &((NMDhcpClientNotifyData){
+                                 .notify_type         = NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
+                                 .it_looks_bad.reason = reason,
+                             }));
+                goto wait_dhcp_commit_done;
+            }
 
-        if (priv->config.addr_family == AF_INET || !priv->l3cfg_notify.wait_ipv6_dad) {
             _emit_notify(
                 self,
                 &((NMDhcpClientNotifyData){.notify_type  = NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE,
@@ -1368,8 +1417,8 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
 
     priv->is_stopped = TRUE;
 
-    if (NM_IS_IPv4(priv->config.addr_family) && priv->v4.bound.invocation) {
-        g_dbus_method_invocation_return_error(g_steal_pointer(&priv->v4.bound.invocation),
+    if (priv->invocation) {
+        g_dbus_method_invocation_return_error(g_steal_pointer(&priv->invocation),
                                               NM_DEVICE_ERROR,
                                               NM_DEVICE_ERROR_FAILED,
                                               "dhcp stopping");
@@ -1528,7 +1577,6 @@ nm_dhcp_client_handle_event(gpointer               unused,
     NMPlatformIP6Address                    prefix = {
                            0,
     };
-    int IS_IPv4;
 
     g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
     g_return_val_if_fail(iface != NULL, FALSE);
@@ -1625,16 +1673,13 @@ nm_dhcp_client_handle_event(gpointer               unused,
         client_event_type = NM_DHCP_CLIENT_EVENT_TYPE_FAIL;
     }
 
-    IS_IPv4 = NM_IS_IPv4(priv->config.addr_family);
-
-    if (IS_IPv4 && priv->v4.bound.invocation)
-        g_dbus_method_invocation_return_value(g_steal_pointer(&priv->v4.bound.invocation), NULL);
+    if (priv->invocation)
+        g_dbus_method_invocation_return_value(g_steal_pointer(&priv->invocation), NULL);
 
-    if (IS_IPv4
-        && NM_IN_SET(client_event_type,
-                     NM_DHCP_CLIENT_EVENT_TYPE_BOUND,
-                     NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED))
-        priv->v4.bound.invocation = g_steal_pointer(&invocation);
+    if (NM_IN_SET(client_event_type,
+                  NM_DHCP_CLIENT_EVENT_TYPE_BOUND,
+                  NM_DHCP_CLIENT_EVENT_TYPE_EXTENDED))
+        priv->invocation = g_steal_pointer(&invocation);
 
     _nm_dhcp_client_notify(self, client_event_type, l3cd);
 
@@ -1785,10 +1830,6 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
          * explicitly initialize the respective union member. */
         if (NM_IS_IPv4(priv->config.addr_family)) {
             priv->v4 = (typeof(priv->v4)){
-                .bound =
-                    {
-                        .invocation = NULL,
-                    },
                 .acd =
                     {
                         .addr                = INADDR_ANY,
diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c
index 5a17f4e8..78db617c 100644
--- a/src/core/dhcp/nm-dhcp-helper.c
+++ b/src/core/dhcp/nm-dhcp-helper.c
@@ -114,6 +114,8 @@ main(int argc, char *argv[])
     gint64                           time_start;
     gint64                           time_end;
     gint64                           remaining_time;
+    gboolean                         IS_IPv4;
+    const char                      *reason;
 
     /* Connecting to the unix socket can fail with EAGAIN if there are too
      * many pending connections and the server can't accept them in time
@@ -124,7 +126,19 @@ main(int argc, char *argv[])
     time_end   = time_start + (5000 * 1000L);
     try_count  = 0;
 
-    _LOGi("nm-dhcp-helper: event called");
+    reason = getenv("reason");
+
+    _LOGi("nm-dhcp-helper: event called: %s", reason);
+
+    IS_IPv4 = !NM_IN_STRSET(reason,
+                            "PREINIT6",
+                            "BOUND6",
+                            "RENEW6",
+                            "REBIND6",
+                            "DEPREF6",
+                            "EXPIRE6",
+                            "RELEASE6",
+                            "STOP6");
 
 do_connect:
     try_count++;
@@ -244,5 +258,6 @@ out:
     }
 
     _LOGi("success: %s", success ? "YES" : "NO");
-    return success ? EXIT_SUCCESS : EXIT_FAILURE;
+    /* The error code to send a decline depends on the address family */
+    return success ? EXIT_SUCCESS : (IS_IPv4 ? 1 : 3);
 }
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index 1e54452a..a2fead3f 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -2039,8 +2039,15 @@ nm_dns_manager_set_ip_config(NMDnsManager         *self,
 
     if (!ip_data) {
         ip_data = _dns_config_ip_data_new(data, addr_family, source_tag, l3cd, ip_config_type);
-        if (!any_removed)
+        priv->ip_data_lst_need_sort = TRUE;
+        if (!any_removed) {
+            /* `any_removed` tracks whether we deleted any ip_data. If that happened,
+             * we already compared the old and new l3cds and set `changed` accordingly.
+             * Here we only need to set `changed` if we are adding a new ip_data without
+             * removing the old one.
+             */
             changed = TRUE;
+        }
     } else {
         ip_data->ip_config_type = ip_config_type;
         changed                 = TRUE;
diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c
index 0f512e38..753e3fb5 100644
--- a/src/core/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -585,7 +585,7 @@ _merge_keyfiles(GKeyFile *keyfile_user, GKeyFile *keyfile_intern)
         if (!keys)
             continue;
 
-        is_intern = g_str_has_prefix(group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+        is_intern = NM_STR_HAS_PREFIX(group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
         if (!is_intern
             && g_key_file_has_key(keyfile_intern,
                                   group,
@@ -634,9 +634,11 @@ _nm_config_data_log_sort(const char **pa, const char **pb, gpointer dummy)
     const char *a = *pa;
     const char *b = *pb;
 
+    nm_assert(a && b && !nm_streq(a, b));
+
     /* we sort intern groups to the end. */
-    a_is_intern = g_str_has_prefix(a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
-    b_is_intern = g_str_has_prefix(b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+    a_is_intern = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+    b_is_intern = NM_STR_HAS_PREFIX(b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
 
     if (a_is_intern && b_is_intern)
         return 0;
@@ -646,8 +648,8 @@ _nm_config_data_log_sort(const char **pa, const char **pb, gpointer dummy)
         return -1;
 
     /* we sort connection groups before intern groups (to the end). */
-    a_is_connection = a && g_str_has_prefix(a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
-    b_is_connection = b && g_str_has_prefix(b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
+    a_is_connection = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
+    b_is_connection = NM_STR_HAS_PREFIX(b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
 
     if (a_is_connection && b_is_connection) {
         /* if both are connection groups, we want the explicit [connection] group first. */
@@ -668,8 +670,8 @@ _nm_config_data_log_sort(const char **pa, const char **pb, gpointer dummy)
         return -1;
 
     /* we sort device groups before connection groups (to the end). */
-    a_is_device = a && g_str_has_prefix(a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
-    b_is_device = b && g_str_has_prefix(b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+    a_is_device = NM_STR_HAS_PREFIX(a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+    b_is_device = NM_STR_HAS_PREFIX(b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
 
     if (a_is_device && b_is_device) {
         /* if both are device groups, we want the explicit [device] group first. */
@@ -770,27 +772,48 @@ nm_config_data_log(const NMConfigData  *self,
     groups_full = g_ptr_array_sized_new(ngroups + 5);
 
     if (ngroups) {
+        /* g_key_file_get_groups() can return duplicates ( :( ), but the
+         * keyfile that we constructed should not have any. Assert for that. */
+        nm_assert(!nm_strv_has_duplicate((const char *const *) groups, ngroups, FALSE));
+
         g_ptr_array_set_size(groups_full, ngroups);
         memcpy(groups_full->pdata, groups, sizeof(groups[0]) * ngroups);
-        g_ptr_array_sort_with_data(groups_full, (GCompareDataFunc) _nm_config_data_log_sort, NULL);
     }
 
     if (print_default) {
         for (g = 0; g < G_N_ELEMENTS(default_values); g++) {
             const char *group = default_values[g].group;
-            gssize      idx;
+            guint       g2;
 
-            idx = nm_utils_array_find_binary_search((gconstpointer *) groups_full->pdata,
-                                                    sizeof(char *),
-                                                    groups_full->len,
-                                                    &group,
-                                                    (GCompareDataFunc) _nm_config_data_log_sort,
-                                                    NULL);
-            if (idx < 0)
-                g_ptr_array_insert(groups_full, (~idx), (gpointer) group);
+            if (g > 0) {
+                if (nm_streq(group, default_values[g - 1].group)) {
+                    /* Repeated values. We already added this one. Skip */
+                    continue;
+                }
+                if (NM_MORE_ASSERT_ONCE(20)) {
+                    /* We require that the default values are grouped by their "group".
+                     * That is, all default values for a certain "group" are close to
+                     * each other in the list. Assert for that. */
+                    for (g2 = g + 1; g2 < groups_full->len; g2++) {
+                        nm_assert(!nm_streq(default_values[g - 1].group, default_values[g2].group));
+                    }
+                }
+            }
+
+            for (g2 = 0; g2 < groups_full->len; g2++) {
+                if (nm_streq(group, groups_full->pdata[g2]))
+                    goto next;
+            }
+
+            g_ptr_array_add(groups_full, (gpointer) group);
+
+next:
+            (void) 0;
         }
     }
 
+    g_ptr_array_sort_with_data(groups_full, (GCompareDataFunc) _nm_config_data_log_sort, NULL);
+
     if (!stream)
         _LOG(stream, prefix, "config-data[%p]: %u groups", self, groups_full->len);
 
diff --git a/src/core/nm-firewall-utils.c b/src/core/nm-firewall-utils.c
index 1311f503..e482ab96 100644
--- a/src/core/nm-firewall-utils.c
+++ b/src/core/nm-firewall-utils.c
@@ -111,6 +111,8 @@ _share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const c
     return nm_str_buf_finalize(&strbuf, NULL);
 }
 
+/*****************************************************************************/
+
 static gboolean
 _share_iptables_call_v(const char *const *argv)
 {
@@ -171,7 +173,7 @@ _share_iptables_chain_add(const char *table, const char *chain)
 }
 
 static void
-_share_iptables_set_masquerade(gboolean add, const char *ip_iface, in_addr_t addr, guint8 plen)
+_share_iptables_set_masquerade_sync(gboolean up, const char *ip_iface, in_addr_t addr, guint8 plen)
 {
     char          str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
     gs_free char *comment_name = NULL;
@@ -182,7 +184,7 @@ _share_iptables_set_masquerade(gboolean add, const char *ip_iface, in_addr_t add
     _share_iptables_call("" IPTABLES_PATH "",
                          "--table",
                          "nat",
-                         add ? "--insert" : "--delete",
+                         up ? "--insert" : "--delete",
                          "POSTROUTING",
                          "--source",
                          str_subnet,
@@ -309,8 +311,8 @@ _share_iptables_set_shared_chains_delete(const char *chain_input, const char *ch
     _share_iptables_chain_delete("filter", chain_forward);
 }
 
-_nm_unused static void
-_share_iptables_set_shared(gboolean add, const char *ip_iface, in_addr_t addr, guint plen)
+static void
+_share_iptables_set_shared_sync(gboolean up, const char *ip_iface, in_addr_t addr, guint plen)
 {
     gs_free char *comment_name  = NULL;
     gs_free char *chain_input   = NULL;
@@ -320,13 +322,13 @@ _share_iptables_set_shared(gboolean add, const char *ip_iface, in_addr_t addr, g
     chain_input   = _share_iptables_get_name(TRUE, "nm-sh-in", ip_iface);
     chain_forward = _share_iptables_get_name(TRUE, "nm-sh-fw", ip_iface);
 
-    if (add)
+    if (up)
         _share_iptables_set_shared_chains_add(chain_input, chain_forward, ip_iface, addr, plen);
 
     _share_iptables_call("" IPTABLES_PATH "",
                          "--table",
                          "filter",
-                         add ? "--insert" : "--delete",
+                         up ? "--insert" : "--delete",
                          "INPUT",
                          "--in-interface",
                          ip_iface,
@@ -340,7 +342,7 @@ _share_iptables_set_shared(gboolean add, const char *ip_iface, in_addr_t addr, g
     _share_iptables_call("" IPTABLES_PATH "",
                          "--table",
                          "filter",
-                         add ? "--insert" : "--delete",
+                         up ? "--insert" : "--delete",
                          "FORWARD",
                          "--jump",
                          chain_forward,
@@ -349,7 +351,7 @@ _share_iptables_set_shared(gboolean add, const char *ip_iface, in_addr_t addr, g
                          "--comment",
                          comment_name);
 
-    if (!add)
+    if (!up)
         _share_iptables_set_shared_chains_delete(chain_input, chain_forward);
 }
 
@@ -428,14 +430,21 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
     } else if (g_subprocess_get_successful(call_data->subprocess)) {
         nm_log_dbg(LOGD_SHARING, "firewall: nft[%s]: command successful", call_data->identifier);
     } else {
+        char          buf[NM_UTILS_GET_PROCESS_EXIT_STATUS_BUF_LEN];
         gs_free char *ss_stdout    = NULL;
         gs_free char *ss_stderr    = NULL;
         gboolean      print_stdout = (stdout_buf && g_bytes_get_size(stdout_buf) > 0);
         gboolean      print_stderr = (stderr_buf && g_bytes_get_size(stderr_buf) > 0);
+        int           status;
+
+        status = g_subprocess_get_status(call_data->subprocess);
+
+        nm_utils_get_process_exit_status_desc_buf(status, buf, sizeof(buf));
 
         nm_log_warn(LOGD_SHARING,
-                    "firewall: nft[%s]: command failed:%s%s%s%s%s%s%s",
+                    "firewall: nft[%s]: command %s:%s%s%s%s%s%s%s",
                     call_data->identifier,
+                    buf,
                     print_stdout || print_stderr ? "" : " unknown reason",
                     NM_PRINT_FMT_QUOTED(
                         print_stdout,
@@ -453,6 +462,8 @@ _fw_nft_call_communicate_cb(GObject *source, GAsyncResult *result, gpointer user
                                                            &ss_stderr),
                         "\")",
                         ""));
+
+        nm_utils_error_set(&error, NM_UTILS_ERROR_COMMAND_FAILED, "nft command %s", buf);
     }
 
     _fw_nft_call_data_free(call_data, g_steal_pointer(&error));
@@ -486,23 +497,31 @@ _fw_nft_call_timeout_cb(gpointer user_data)
     return G_SOURCE_CONTINUE;
 }
 
-static void
-_fw_nft_call(GBytes             *stdin_buf,
-             GCancellable       *cancellable,
-             GAsyncReadyCallback callback,
-             gpointer            callback_user_data)
+void
+nm_firewall_nft_call(GBytes             *stdin_buf,
+                     GCancellable       *cancellable,
+                     GAsyncReadyCallback callback,
+                     gpointer            callback_user_data)
 {
     gs_unref_object GSubprocessLauncher *subprocess_launcher = NULL;
     gs_free_error GError                *error               = NULL;
     FwNftCallData                       *call_data;
+    gs_free char                        *ss1 = NULL;
 
     call_data  = g_slice_new(FwNftCallData);
     *call_data = (FwNftCallData){
-        .task       = nm_g_task_new(NULL, cancellable, _fw_nft_call, callback, callback_user_data),
-        .subprocess = NULL,
+        .task =
+            nm_g_task_new(NULL, cancellable, nm_firewall_nft_call, callback, callback_user_data),
+        .subprocess     = NULL,
         .timeout_source = NULL,
     };
 
+    nm_log_trace(LOGD_SHARING,
+                 "firewall: nft: call command: [ '%s' ]",
+                 nm_utils_buf_utf8safe_escape_bytes(stdin_buf,
+                                                    NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+                                                    &ss1));
+
     if (cancellable) {
         call_data->cancellable_id = g_cancellable_connect(cancellable,
                                                           G_CALLBACK(_fw_nft_call_cancelled_cb),
@@ -554,10 +573,10 @@ _fw_nft_call(GBytes             *stdin_buf,
                            g_task_get_context(call_data->task));
 }
 
-static gboolean
-_fw_nft_call_finish(GAsyncResult *result, GError **error)
+gboolean
+nm_firewall_nft_call_finish(GAsyncResult *result, GError **error)
 {
-    g_return_val_if_fail(nm_g_task_is_valid(result, NULL, _fw_nft_call), FALSE);
+    g_return_val_if_fail(nm_g_task_is_valid(result, NULL, nm_firewall_nft_call), FALSE);
 
     return g_task_propagate_boolean(G_TASK(result), error);
 }
@@ -575,7 +594,7 @@ _fw_nft_call_sync_done(GObject *source, GAsyncResult *result, gpointer user_data
 {
     FwNftCallSyncData *data = user_data;
 
-    data->success = _fw_nft_call_finish(result, data->error);
+    data->success = nm_firewall_nft_call_finish(result, data->error);
     g_main_loop_quit(data->loop);
 }
 
@@ -590,7 +609,7 @@ _fw_nft_call_sync(GBytes *stdin_buf, GError **error)
                               .error = error,
     };
 
-    _fw_nft_call(stdin_buf, NULL, _fw_nft_call_sync_done, &data);
+    nm_firewall_nft_call(stdin_buf, NULL, _fw_nft_call_sync_done, &data);
 
     g_main_loop_run(main_loop);
     return data.success;
@@ -598,25 +617,30 @@ _fw_nft_call_sync(GBytes *stdin_buf, GError **error)
 
 /*****************************************************************************/
 
+#define _append(p_strbuf, fmt, ...) nm_str_buf_append_printf((p_strbuf), "" fmt "\n", ##__VA_ARGS__)
+
 static void
-_fw_nft_set(gboolean add, const char *ip_iface, in_addr_t addr, guint8 plen)
+_fw_nft_append_cmd_table(NMStrBuf *strbuf, const char *family, const char *table_name, gboolean up)
+{
+    /* Either delete the table, or create/flush it. */
+    _append(strbuf, "add table %s %s", family, table_name);
+    _append(strbuf, "%s table %s %s", up ? "flush" : "delete", family, table_name);
+}
+
+static GBytes *
+_fw_nft_set_shared_construct(gboolean up, const char *ip_iface, in_addr_t addr, guint8 plen)
 {
     nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
-    gs_unref_bytes GBytes   *stdin_buf  = NULL;
     gs_free char            *table_name = NULL;
-    gs_free char            *ss1        = NULL;
     char                     str_subnet[_SHARE_IPTABLES_SUBNET_TO_STR_LEN];
 
     table_name = _share_iptables_get_name(FALSE, "nm-shared", ip_iface);
 
     _share_iptables_subnet_to_str(str_subnet, addr, plen);
 
-#define _append(p_strbuf, fmt, ...) nm_str_buf_append_printf((p_strbuf), "" fmt "\n", ##__VA_ARGS__)
-
-    _append(&strbuf, "add table ip %s", table_name);
-    _append(&strbuf, "%s table ip %s", add ? "flush" : "delete", table_name);
+    _fw_nft_append_cmd_table(&strbuf, "ip", table_name, up);
 
-    if (add) {
+    if (up) {
         _append(&strbuf,
                 "add chain ip %s nat_postrouting {"
                 " type nat hook postrouting priority 100; policy accept; "
@@ -631,16 +655,15 @@ _fw_nft_set(gboolean add, const char *ip_iface, in_addr_t addr, guint8 plen)
         /* This filter_input chain serves no real purpose, because "accept" only stops
          * evaluation of the current rule. It cannot fully accept the packet. Since
          * this chain has no other rules, it is useless in this form.
+         *
+         * _append(&strbuf,
+         *         "add chain ip %s filter_input {"
+         *         " type filter hook input priority 0; policy accept; "
+         *         "};",
+         *         table_name);
+         * _append(&strbuf, "add rule ip %s filter_input tcp dport { 67, 53 } accept;", table_name);
+         * _append(&strbuf, "add rule ip %s filter_input udp dport { 67, 53 } accept;", table_name);
          */
-        /*
-        _append(&strbuf,
-                "add chain ip %s filter_input {"
-                " type filter hook input priority 0; policy accept; "
-                "};",
-                table_name);
-        _append(&strbuf, "add rule ip %s filter_input tcp dport { 67, 53 } accept;", table_name);
-        _append(&strbuf, "add rule ip %s filter_input udp dport { 67, 53 } accept;", table_name);
-        */
 
         _append(&strbuf,
                 "add chain ip %s filter_forward {"
@@ -673,14 +696,7 @@ _fw_nft_set(gboolean add, const char *ip_iface, in_addr_t addr, guint8 plen)
                 ip_iface);
     }
 
-    nm_log_trace(LOGD_SHARING,
-                 "firewall: nft command: [ %s ]",
-                 nm_utils_str_utf8safe_escape(nm_str_buf_get_str(&strbuf),
-                                              NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
-                                              &ss1));
-
-    stdin_buf = nm_str_buf_finalize_to_gbytes(&strbuf);
-    _fw_nft_call_sync(stdin_buf, NULL);
+    return nm_str_buf_finalize_to_gbytes(&strbuf);
 }
 
 /*****************************************************************************/
@@ -692,7 +708,7 @@ struct _NMFirewallConfig {
 };
 
 NMFirewallConfig *
-nm_firewall_config_new(const char *ip_iface, in_addr_t addr, guint8 plen)
+nm_firewall_config_new_shared(const char *ip_iface, in_addr_t addr, guint8 plen)
 {
     NMFirewallConfig *self;
 
@@ -719,17 +735,24 @@ nm_firewall_config_free(NMFirewallConfig *self)
     nm_g_slice_free(self);
 }
 
+/*****************************************************************************/
+
 void
-nm_firewall_config_apply(NMFirewallConfig *self, gboolean shared)
+nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up)
 {
     switch (nm_firewall_utils_get_backend()) {
     case NM_FIREWALL_BACKEND_IPTABLES:
-        _share_iptables_set_masquerade(shared, self->ip_iface, self->addr, self->plen);
-        _share_iptables_set_shared(shared, self->ip_iface, self->addr, self->plen);
+        _share_iptables_set_masquerade_sync(up, self->ip_iface, self->addr, self->plen);
+        _share_iptables_set_shared_sync(up, self->ip_iface, self->addr, self->plen);
         break;
     case NM_FIREWALL_BACKEND_NFTABLES:
-        _fw_nft_set(shared, self->ip_iface, self->addr, self->plen);
+    {
+        gs_unref_bytes GBytes *stdin_buf = NULL;
+
+        stdin_buf = _fw_nft_set_shared_construct(up, self->ip_iface, self->addr, self->plen);
+        _fw_nft_call_sync(stdin_buf, NULL);
         break;
+    }
     case NM_FIREWALL_BACKEND_NONE:
         break;
     default:
diff --git a/src/core/nm-firewall-utils.h b/src/core/nm-firewall-utils.h
index 3d6c8a69..9d883fea 100644
--- a/src/core/nm-firewall-utils.h
+++ b/src/core/nm-firewall-utils.h
@@ -20,10 +20,19 @@ NMFirewallBackend nm_firewall_utils_get_backend(void);
 
 typedef struct _NMFirewallConfig NMFirewallConfig;
 
-NMFirewallConfig *nm_firewall_config_new(const char *ip_iface, in_addr_t addr, guint8 plen);
+NMFirewallConfig *nm_firewall_config_new_shared(const char *ip_iface, in_addr_t addr, guint8 plen);
 
 void nm_firewall_config_free(NMFirewallConfig *self);
 
-void nm_firewall_config_apply(NMFirewallConfig *self, gboolean shared);
+void nm_firewall_config_apply_sync(NMFirewallConfig *self, gboolean up);
+
+/*****************************************************************************/
+
+void nm_firewall_nft_call(GBytes             *stdin_buf,
+                          GCancellable       *cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer            callback_user_data);
+
+gboolean nm_firewall_nft_call_finish(GAsyncResult *result, GError **error);
 
 #endif /* __NM_FIREWALL_UTILS_H__ */
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index 16b0211f..518fc596 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -797,6 +797,20 @@ device_dns_lookup_done(NMDevice *device, gpointer user_data)
 }
 
 static void
+device_carrier_changed(NMDevice *device, GParamSpec *pspec, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy        *self = _PRIV_TO_SELF(priv);
+    gs_free char    *msg  = NULL;
+
+    if (nm_device_has_carrier(device)) {
+        g_signal_handlers_disconnect_by_func(device, device_carrier_changed, priv);
+        msg = g_strdup_printf("device '%s' got carrier", nm_device_get_iface(device));
+        update_system_hostname(self, msg);
+    }
+}
+
+static void
 update_system_hostname(NMPolicy *self, const char *msg)
 {
     NMPolicyPrivate       *priv = NM_POLICY_GET_PRIVATE(self);
@@ -880,6 +894,7 @@ update_system_hostname(NMPolicy *self, const char *msg)
         info        = &g_array_index(infos, DeviceHostnameInfo, i);
         addr_family = info->IS_IPv4 ? AF_INET : AF_INET6;
         g_signal_handlers_disconnect_by_func(info->device, device_dns_lookup_done, self);
+        g_signal_handlers_disconnect_by_func(info->device, device_carrier_changed, priv);
 
         if (info->from_dhcp) {
             dhcp_config = nm_device_get_dhcp_config(info->device, addr_family);
@@ -905,10 +920,18 @@ update_system_hostname(NMPolicy *self, const char *msg)
 
         if (priv->hostname_mode != NM_POLICY_HOSTNAME_MODE_DHCP) {
             if (info->from_dns) {
-                const char *result;
-                gboolean    wait = FALSE;
+                const char *result = NULL;
+                gboolean    wait   = FALSE;
 
-                result = nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait);
+                if (nm_device_has_carrier(info->device)) {
+                    result =
+                        nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait);
+                } else {
+                    g_signal_connect(info->device,
+                                     "notify::" NM_DEVICE_CARRIER,
+                                     G_CALLBACK(device_carrier_changed),
+                                     priv);
+                }
                 if (result) {
                     _set_hostname(self, result, "from address lookup");
                     return;
@@ -1556,6 +1579,7 @@ nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
     NMPolicyPrivate             *priv        = NM_POLICY_GET_PRIVATE(self);
     NMSettingsConnection *const *connections = NULL;
     guint                        i;
+    gboolean                     multiconnect = FALSE;
 
     _LOGT(LOGD_DEVICE, "unblocking failed OVS interfaces");
 
@@ -1563,6 +1587,13 @@ nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
     for (i = 0; connections[i]; i++) {
         NMSettingsConnection *sett_conn  = connections[i];
         NMConnection         *connection = nm_settings_connection_get_connection(sett_conn);
+        NMSettingConnection  *s_con      = nm_connection_get_setting_connection(connection);
+
+        /* Check if any connection is connect multiple */
+        if (s_con
+            && nm_setting_connection_get_multi_connect(s_con)
+                   == NM_CONNECTION_MULTI_CONNECT_MULTIPLE)
+            multiconnect = TRUE;
 
         if (nm_connection_get_setting_ovs_interface(connection)) {
             nm_settings_connection_autoconnect_retries_reset(sett_conn);
@@ -1572,6 +1603,18 @@ nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
                 FALSE);
         }
     }
+
+    /* There is, at least, one multiconnect connection with an OVS setting.
+     * Let's check the OVS devices retries */
+    if (multiconnect) {
+        NMDevice      *device;
+        GHashTableIter h_iter;
+        g_hash_table_iter_init(&h_iter, priv->devices);
+        while (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) {
+            if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE)
+                nm_device_autoconnect_retries_reset(device);
+        }
+    }
 }
 
 static gboolean
@@ -1583,7 +1626,8 @@ reset_autoconnect_all(
     NMPolicyPrivate             *priv        = NM_POLICY_GET_PRIVATE(self);
     NMSettingsConnection *const *connections = NULL;
     guint                        i;
-    gboolean                     changed = FALSE;
+    gboolean                     changed      = FALSE;
+    gboolean                     multiconnect = FALSE;
 
     _LOGD(LOGD_DEVICE,
           "re-enabling autoconnect for all connections%s%s%s",
@@ -1594,6 +1638,8 @@ reset_autoconnect_all(
     connections = nm_settings_get_connections(priv->settings, NULL);
     for (i = 0; connections[i]; i++) {
         NMSettingsConnection *sett_conn = connections[i];
+        NMSettingConnection  *s_con =
+            nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn));
 
         if (device
             && !nm_device_check_connection_compatible(
@@ -1602,6 +1648,11 @@ reset_autoconnect_all(
                 NULL))
             continue;
 
+        if (s_con
+            && nm_setting_connection_get_multi_connect(s_con)
+                   == NM_CONNECTION_MULTI_CONNECT_MULTIPLE)
+            multiconnect = TRUE;
+
         if (only_no_secrets) {
             /* we only reset the no-secrets blocked flag. */
             if (nm_settings_connection_autoconnect_blocked_reason_set(
@@ -1630,6 +1681,20 @@ reset_autoconnect_all(
             }
         }
     }
+
+    /* There is, at least, one multiconnect connection. Let's check the devices retries */
+    if (multiconnect) {
+        NMDevice      *device_iter;
+        GHashTableIter h_iter;
+        g_hash_table_iter_init(&h_iter, priv->devices);
+        while (g_hash_table_iter_next(&h_iter, (gpointer *) &device_iter, NULL)) {
+            if (nm_device_get_autoconnect_retries(device_iter) != -2) {
+                nm_device_autoconnect_retries_reset(device_iter);
+                changed = TRUE;
+            }
+        }
+    }
+
     return changed;
 }
 
@@ -1687,7 +1752,8 @@ reset_connections_retries(gpointer user_data)
     NMSettingsConnection *const *connections = NULL;
     guint                        i;
     gint32                       con_stamp, min_stamp, now;
-    gboolean                     changed = FALSE;
+    gboolean                     changed      = FALSE;
+    gboolean                     multiconnect = FALSE;
 
     priv->reset_retries_id = 0;
 
@@ -1696,6 +1762,14 @@ reset_connections_retries(gpointer user_data)
     connections = nm_settings_get_connections(priv->settings, NULL);
     for (i = 0; connections[i]; i++) {
         NMSettingsConnection *connection = connections[i];
+        NMSettingConnection  *s_con =
+            nm_connection_get_setting_connection(nm_settings_connection_get_connection(connection));
+
+        /* Check if any connection is connect multiple */
+        if (s_con
+            && nm_setting_connection_get_multi_connect(s_con)
+                   == NM_CONNECTION_MULTI_CONNECT_MULTIPLE)
+            multiconnect = TRUE;
 
         con_stamp = nm_settings_connection_autoconnect_retries_blocked_until(connection);
         if (con_stamp == 0)
@@ -1708,6 +1782,25 @@ reset_connections_retries(gpointer user_data)
             min_stamp = con_stamp;
     }
 
+    /* There is, at least, one multiconnect connection. Let's check the devices retries */
+    if (multiconnect) {
+        NMDevice      *device;
+        GHashTableIter h_iter;
+        g_hash_table_iter_init(&h_iter, priv->devices);
+        while (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) {
+            con_stamp = nm_device_autoconnect_retries_blocked_until(device);
+
+            /* default value in device is -2, which means, we do not care */
+            if (con_stamp <= 0)
+                continue;
+            if (con_stamp <= now) {
+                nm_device_autoconnect_retries_reset(device);
+                changed = TRUE;
+            } else if (min_stamp == 0 || min_stamp > con_stamp)
+                min_stamp = con_stamp;
+        }
+    }
+
     /* Schedule the handler again if there are some stamps left */
     if (min_stamp != 0)
         priv->reset_retries_id =
@@ -1721,21 +1814,31 @@ reset_connections_retries(gpointer user_data)
 }
 
 static void
-_connection_autoconnect_retries_set(NMPolicy *self, NMSettingsConnection *connection, int tries)
+_connection_autoconnect_retries_set(NMPolicy             *self,
+                                    NMSettingsConnection *connection,
+                                    NMDevice             *device,
+                                    int                   tries)
 {
-    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    NMPolicyPrivate     *priv = NM_POLICY_GET_PRIVATE(self);
+    NMSettingConnection *s_con;
+    gint32               retry_time;
 
     nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
     nm_assert(tries >= 0);
 
-    nm_settings_connection_autoconnect_retries_set(connection, tries);
+    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(connection));
+    if (s_con
+        && nm_setting_connection_get_multi_connect(s_con) == NM_CONNECTION_MULTI_CONNECT_MULTIPLE) {
+        nm_device_set_autoconnect_retries(device, tries);
+        retry_time = nm_device_autoconnect_retries_blocked_until(device);
+    } else {
+        nm_settings_connection_autoconnect_retries_set(connection, tries);
+        retry_time = nm_settings_connection_autoconnect_retries_blocked_until(connection);
+    }
 
     if (tries == 0) {
         /* Schedule a handler to reset retries count */
         if (!priv->reset_retries_id) {
-            gint32 retry_time =
-                nm_settings_connection_autoconnect_retries_blocked_until(connection);
-
             g_warn_if_fail(retry_time != 0);
             priv->reset_retries_id =
                 g_timeout_add_seconds(MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()),
@@ -1997,13 +2100,25 @@ device_state_changed(NMDevice           *device,
             }
 
             if (!blocked) {
-                tries = nm_settings_connection_autoconnect_retries_get(sett_conn);
+                s_con = nm_connection_get_setting_connection(
+                    nm_settings_connection_get_connection(sett_conn));
+                if (s_con
+                    && nm_setting_connection_get_multi_connect(s_con)
+                           == NM_CONNECTION_MULTI_CONNECT_MULTIPLE) {
+                    if (nm_device_get_autoconnect_retries(device) == -2)
+                        nm_device_set_autoconnect_retries(
+                            device,
+                            nm_settings_connection_autoconnect_retries_get(sett_conn));
+
+                    tries = nm_device_get_autoconnect_retries(device);
+                } else
+                    tries = nm_settings_connection_autoconnect_retries_get(sett_conn);
                 if (tries > 0) {
                     _LOGD(LOGD_DEVICE,
                           "connection '%s' failed to autoconnect; %d tries left",
                           nm_settings_connection_get_id(sett_conn),
                           tries - 1);
-                    _connection_autoconnect_retries_set(self, sett_conn, tries - 1);
+                    _connection_autoconnect_retries_set(self, sett_conn, device, tries - 1);
                 } else if (tries != 0) {
                     _LOGD(LOGD_DEVICE,
                           "connection '%s' failed to autoconnect; infinite tries left",
@@ -2893,7 +3008,7 @@ dispose(GObject *object)
     }
 
     g_hash_table_iter_init(&h_iter, priv->devices);
-    if (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) {
+    while (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) {
         g_hash_table_iter_remove(&h_iter);
         devices_list_unregister(self, device);
     }
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 6452d72a..4d8e7bd6 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
@@ -5355,59 +5355,41 @@ wired_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
 static gboolean
 parse_infiniband_p_key(shvarFile *ifcfg, int *out_p_key, char **out_parent, GError **error)
 {
-    char    *device = NULL, *physdev = NULL, *pkey_id = NULL;
-    char    *ifname = NULL;
-    int      id;
-    gboolean ret = FALSE;
-
-    device = svGetValueStr_cp(ifcfg, "DEVICE");
-    if (!device) {
-        PARSE_WARNING("InfiniBand connection specified PKEY but not DEVICE");
-        goto done;
-    }
+    gs_free char *physdev = NULL;
+    gs_free char *pkey_id = NULL;
+    int           id;
 
     physdev = svGetValueStr_cp(ifcfg, "PHYSDEV");
     if (!physdev) {
-        PARSE_WARNING("InfiniBand connection specified PKEY but not PHYSDEV");
-        goto done;
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "infiniband connection specified PKEY but not PHYSDEV");
+        return FALSE;
     }
 
     pkey_id = svGetValueStr_cp(ifcfg, "PKEY_ID");
     if (!pkey_id) {
-        PARSE_WARNING("InfiniBand connection specified PKEY but not PKEY_ID");
-        goto done;
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "infiniband connection specified PKEY but not PKEY_ID");
+        return FALSE;
     }
 
     id = _nm_utils_ascii_str_to_int64(pkey_id, 0, 0, 0xFFFF, -1);
     if (id == -1) {
-        PARSE_WARNING("invalid InfiniBand PKEY_ID '%s'", pkey_id);
-        goto done;
-    }
-    id = (id | 0x8000);
-
-    ifname = g_strdup_printf("%s.%04x", physdev, (unsigned) id);
-    if (strcmp(device, ifname) != 0) {
-        PARSE_WARNING("InfiniBand DEVICE (%s) does not match PHYSDEV+PKEY_ID (%s)", device, ifname);
-        goto done;
-    }
-
-    *out_p_key  = id;
-    *out_parent = g_strdup(physdev);
-    ret         = TRUE;
-
-done:
-    g_free(device);
-    g_free(physdev);
-    g_free(pkey_id);
-    g_free(ifname);
-
-    if (!ret) {
         g_set_error(error,
                     NM_SETTINGS_ERROR,
                     NM_SETTINGS_ERROR_INVALID_CONNECTION,
-                    "Failed to create InfiniBand setting");
+                    "invalid infiniband PKEY_ID '%s'",
+                    pkey_id);
+        return FALSE;
     }
-    return ret;
+
+    *out_p_key  = id;
+    *out_parent = g_steal_pointer(&physdev);
+    return TRUE;
 }
 
 static NMSetting *
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ipoib b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ipoib
new file mode 100644
index 00000000..b45da5f7
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ipoib
@@ -0,0 +1,9 @@
+TYPE=InfiniBand
+PKEY=yes
+PKEY_ID=12
+PHYSDEV=ib0
+CONNECTED_MODE=yes
+IPADDR=192.168.2.2
+NETMASK=255.255.255.0
+GATEWAY=192.168.2.1
+NAME=ib012
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 9fdae4bd..f9fd7fd5 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
@@ -8211,8 +8211,35 @@ test_read_infiniband(void)
 }
 
 static void
-test_write_infiniband(void)
+test_read_ipoib(void)
 {
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingInfiniband          *s_infiniband;
+    char                         *unmanaged = NULL;
+    const char                   *transport_mode;
+    int                           pkey;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-ipoib",
+                                       NULL,
+                                       TYPE_INFINIBAND,
+                                       &unmanaged);
+    g_assert(!unmanaged);
+
+    s_infiniband = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_INFINIBAND);
+
+    pkey = nm_setting_infiniband_get_p_key(s_infiniband);
+    g_assert(pkey);
+    g_assert_cmpint(pkey, ==, 12);
+
+    transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband);
+    g_assert(transport_mode);
+    g_assert_cmpstr(transport_mode, ==, "connected");
+}
+
+static void
+test_write_infiniband(gconstpointer test_data)
+{
+    const int                     TEST_IDX   = GPOINTER_TO_INT(test_data);
     nmtst_auto_unlinkfile char   *testfile   = NULL;
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
@@ -8223,14 +8250,15 @@ test_write_infiniband(void)
     const char  *mac = "80:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22";
     guint32      mtu = 65520;
     NMIPAddress *addr;
-    GError      *error = NULL;
+    GError      *error          = NULL;
+    const char  *interface_name = NULL;
 
     connection = nm_simple_connection_new();
 
     s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
-                 "Test Write InfiniBand",
+                 "Test Write Infiniband",
                  NM_SETTING_CONNECTION_UUID,
                  nm_uuid_generate_random_str_a(),
                  NM_SETTING_CONNECTION_AUTOCONNECT,
@@ -8239,15 +8267,28 @@ test_write_infiniband(void)
                  NM_SETTING_INFINIBAND_SETTING_NAME,
                  NULL);
 
+    if (NM_IN_SET(TEST_IDX, 1, 3))
+        interface_name = "ib0.000c";
+
+    g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL);
+
     s_infiniband = _nm_connection_new_setting(connection, NM_TYPE_SETTING_INFINIBAND);
-    g_object_set(s_infiniband,
-                 NM_SETTING_INFINIBAND_MAC_ADDRESS,
-                 mac,
-                 NM_SETTING_INFINIBAND_MTU,
-                 mtu,
-                 NM_SETTING_INFINIBAND_TRANSPORT_MODE,
-                 "connected",
-                 NULL);
+    g_object_set(s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", NULL);
+    if (NM_IN_SET(TEST_IDX, 1, 2)) {
+        g_object_set(s_infiniband,
+                     NM_SETTING_INFINIBAND_MAC_ADDRESS,
+                     mac,
+                     NM_SETTING_INFINIBAND_MTU,
+                     mtu,
+                     NULL);
+    } else {
+        g_object_set(s_infiniband,
+                     NM_SETTING_INFINIBAND_P_KEY,
+                     12,
+                     NM_SETTING_INFINIBAND_PARENT,
+                     "ib0",
+                     NULL);
+    }
 
     s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
@@ -8267,6 +8308,9 @@ test_write_infiniband(void)
     s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
+    if (nmtst_get_rand_bool())
+        nmtst_connection_normalize(connection);
+
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
@@ -8274,6 +8318,8 @@ test_write_infiniband(void)
     reread = _connection_from_file(testfile, NULL, TYPE_INFINIBAND, NULL);
 
     nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+
+    g_assert_cmpstr(interface_name, ==, nm_connection_get_interface_name(reread));
 }
 
 static void
@@ -10446,6 +10492,7 @@ main(int argc, char **argv)
     g_test_add_func(TPATH "wifi/read/wep-no-keys", test_read_wifi_wep_no_keys);
     g_test_add_func(TPATH "wifi/read/wep-agent-keys", test_read_wifi_wep_agent_keys);
     g_test_add_func(TPATH "infiniband/read", test_read_infiniband);
+    g_test_add_func(TPATH "ipoib/read", test_read_ipoib);
     g_test_add_func(TPATH "vlan/read", test_read_vlan_interface);
     g_test_add_func(TPATH "vlan/read-flags-1", test_read_vlan_flags_1);
     g_test_add_func(TPATH "vlan/read-flags-2", test_read_vlan_flags_2);
@@ -10582,7 +10629,10 @@ main(int argc, char **argv)
     g_test_add_func(TPATH "permissions/read", test_read_permissions);
     g_test_add_func(TPATH "permissions/write", test_write_permissions);
     g_test_add_func(TPATH "wifi/write-wep-agent-keys", test_write_wifi_wep_agent_keys);
-    g_test_add_func(TPATH "infiniband/write", test_write_infiniband);
+    g_test_add_data_func(TPATH "infiniband/write/1", GINT_TO_POINTER(1), test_write_infiniband);
+    g_test_add_data_func(TPATH "infiniband/write/2", GINT_TO_POINTER(2), test_write_infiniband);
+    g_test_add_data_func(TPATH "infiniband/write/3", GINT_TO_POINTER(3), test_write_infiniband);
+    g_test_add_data_func(TPATH "infiniband/write/4", GINT_TO_POINTER(4), test_write_infiniband);
     g_test_add_func(TPATH "vlan/write", test_write_vlan);
     g_test_add_func(TPATH "vlan/write-flags", test_write_vlan_flags);
     g_test_add_func(TPATH "vlan/write-only-vlanid", test_write_vlan_only_vlanid);
diff --git a/src/libnm-core-impl/nm-setting-bond-port.c b/src/libnm-core-impl/nm-setting-bond-port.c
index 9b41a74f..7ea82a76 100644
--- a/src/libnm-core-impl/nm-setting-bond-port.c
+++ b/src/libnm-core-impl/nm-setting-bond-port.c
@@ -85,13 +85,15 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
 
         slave_type = nm_setting_connection_get_slave_type(s_con);
-        if (!nm_streq0(slave_type, NM_SETTING_BOND_SETTING_NAME)) {
+        if (slave_type && !nm_streq(slave_type, NM_SETTING_BOND_SETTING_NAME)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                        _("A connection with a '%s' setting must have the slave-type set to '%s'"),
+                        _("A connection with a '%s' setting must have the slave-type set to '%s'. "
+                          "Instead it is '%s'"),
                         NM_SETTING_BOND_PORT_SETTING_NAME,
-                        NM_SETTING_BOND_SETTING_NAME);
+                        NM_SETTING_BOND_SETTING_NAME,
+                        slave_type);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
diff --git a/src/libnm-core-impl/nm-setting-bond.c b/src/libnm-core-impl/nm-setting-bond.c
index cdfc7641..18b6fefb 100644
--- a/src/libnm-core-impl/nm-setting-bond.c
+++ b/src/libnm-core-impl/nm-setting-bond.c
@@ -764,37 +764,64 @@ _nm_setting_bond_get_option_type(NMSettingBond *setting, const char *name)
     return option_meta->opt_type;
 }
 
-guint32
-_nm_setting_bond_opt_value_as_u32(NMSettingBond *s_bond, const char *opt)
+#define _opt_value_as_u64(s_bond, opt, v_max)                                            \
+    ({                                                                                   \
+        const OptionMeta *_meta;                                                         \
+        NMSettingBond    *_s_bond = (s_bond);                                            \
+        const char       *_opt    = (opt);                                               \
+        const guint64     _v_max  = (v_max);                                             \
+        const char       *_s;                                                            \
+        guint64           _val;                                                          \
+                                                                                         \
+        nm_assert(NM_IS_SETTING_BOND(_s_bond));                                          \
+        nm_assert(_opt);                                                                 \
+                                                                                         \
+        _meta = _get_option_meta(_opt);                                                  \
+                                                                                         \
+        nm_assert(_meta);                                                                \
+        nm_assert(_meta->opt_type == NM_BOND_OPTION_TYPE_INT);                           \
+        nm_assert(_meta->min < _meta->max);                                              \
+        nm_assert(_meta->max <= _v_max);                                                 \
+        nm_assert(_meta->val);                                                           \
+                                                                                         \
+        _s = nm_setting_bond_get_option_normalized(_s_bond, _opt);                       \
+        if (_s) {                                                                        \
+            _val = _nm_utils_ascii_str_to_uint64(_s, 10, _meta->min, _meta->max, 0);     \
+            /* Note that _s is only a valid integer, if the profile verifies. We require
+             * that the caller only calls these functions on valid profile. */ \
+            nm_assert(errno == 0);                                                       \
+        } else {                                                                         \
+            _val  = 0;                                                                   \
+            errno = EINVAL;                                                              \
+        }                                                                                \
+                                                                                         \
+        _val;                                                                            \
+    })
+
+guint8
+_nm_setting_bond_opt_value_as_u8(NMSettingBond *s_bond, const char *opt)
 {
-    nm_assert(_get_option_meta(opt)->opt_type == NM_BOND_OPTION_TYPE_INT);
-    return _nm_utils_ascii_str_to_uint64(nm_setting_bond_get_option_normalized(s_bond, opt),
-                                         10,
-                                         0,
-                                         G_MAXUINT32,
-                                         0);
+    return _opt_value_as_u64(s_bond, opt, G_MAXUINT8);
 }
 
 guint16
 _nm_setting_bond_opt_value_as_u16(NMSettingBond *s_bond, const char *opt)
 {
-    nm_assert(_get_option_meta(opt)->opt_type == NM_BOND_OPTION_TYPE_INT);
-    return _nm_utils_ascii_str_to_uint64(nm_setting_bond_get_option_normalized(s_bond, opt),
-                                         10,
-                                         0,
-                                         G_MAXUINT16,
-                                         0);
+    return _opt_value_as_u64(s_bond, opt, G_MAXUINT16);
 }
 
-guint8
-_nm_setting_bond_opt_value_as_u8(NMSettingBond *s_bond, const char *opt)
+guint32
+_nm_setting_bond_opt_value_as_u32(NMSettingBond *s_bond, const char *opt)
+{
+    return _opt_value_as_u64(s_bond, opt, G_MAXUINT32);
+}
+
+bool
+_nm_setting_bond_opt_value_as_intbool(NMSettingBond *s_bond, const char *opt)
 {
-    nm_assert(_get_option_meta(opt)->opt_type == NM_BOND_OPTION_TYPE_INT);
-    return _nm_utils_ascii_str_to_uint64(nm_setting_bond_get_option_normalized(s_bond, opt),
-                                         10,
-                                         0,
-                                         G_MAXUINT8,
-                                         0);
+    /* This does not parse the value as a boolean string, instead, it requires
+     * that it's a number, either "0" or "1". */
+    return _opt_value_as_u64(s_bond, opt, 1);
 }
 
 /*****************************************************************************/
diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c
index 8b593b97..94794d1e 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -832,8 +832,7 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
                                              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT,
                                              NM_SETTING_PARAM_NONE,
                                              NMSettingIP6ConfigPrivate,
-                                             addr_gen_mode,
-                                             .to_dbus_including_default = TRUE);
+                                             addr_gen_mode);
 
     /**
      * NMSettingIP6Config:token:
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index 5bdae716..a4e3932a 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -5068,6 +5068,65 @@ test_6lowpan_1(void)
 
 /*****************************************************************************/
 
+static void
+test_bond_meta(void)
+{
+    gs_unref_object NMConnection *con = NULL;
+    NMSettingBond                *set;
+    char                          sbuf[200];
+
+    create_bond_connection(&con, &set);
+
+    g_assert_cmpstr(nm_setting_bond_get_option_normalized(set, NM_SETTING_BOND_OPTION_MODE),
+                    ==,
+                    "balance-rr");
+
+#define _A(_nm_setting_bond_opt_value_as_xxx, set, opt, value, errsv)                  \
+    G_STMT_START                                                                       \
+    {                                                                                  \
+        g_assert_cmpint(_nm_setting_bond_opt_value_as_xxx((set), (opt)), ==, (value)); \
+        g_assert_cmpint(errno, ==, (errsv));                                           \
+    }                                                                                  \
+    G_STMT_END
+
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_MIIMON, 100, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_UPDELAY, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_DOWNDELAY, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_ARP_INTERVAL, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_RESEND_IGMP, 1, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_MIN_LINKS, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_LP_INTERVAL, 1, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, 1, 0);
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, 0, EINVAL);
+    _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, 0, EINVAL);
+    _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, 1, 0);
+    _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_intbool, set, NM_SETTING_BOND_OPTION_USE_CARRIER, 1, 0);
+    _A(_nm_setting_bond_opt_value_as_intbool,
+       set,
+       NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,
+       0,
+       EINVAL);
+
+    nm_setting_bond_add_option(set, NM_SETTING_BOND_OPTION_ARP_INTERVAL, "5");
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_ARP_INTERVAL, 5, 0);
+
+    nm_setting_bond_add_option(set,
+                               NM_SETTING_BOND_OPTION_ARP_INTERVAL,
+                               nm_sprintf_buf(sbuf, "%d", G_MAXINT));
+    _A(_nm_setting_bond_opt_value_as_u32, set, NM_SETTING_BOND_OPTION_ARP_INTERVAL, G_MAXINT, 0);
+
+    nm_setting_bond_add_option(set, NM_SETTING_BOND_OPTION_MODE, "802.3ad");
+    _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, 65535, 0);
+    _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, 0, 0);
+
+    nm_setting_bond_add_option(set, NM_SETTING_BOND_OPTION_MODE, "balance-tlb");
+    _A(_nm_setting_bond_opt_value_as_intbool, set, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, 1, 0);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -5185,5 +5244,7 @@ main(int argc, char **argv)
 
     g_test_add_func("/libnm/test_setting_metadata", test_setting_metadata);
 
+    g_test_add_func("/libnm/test_bond_meta", test_bond_meta);
+
     return g_test_run();
 }
diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h
index ee4cc25b..4e1bab47 100644
--- a/src/libnm-core-intern/nm-core-internal.h
+++ b/src/libnm-core-intern/nm-core-internal.h
@@ -517,9 +517,10 @@ NMConnectionMultiConnect _nm_connection_get_multi_connect(NMConnection *connecti
 
 gboolean _nm_setting_bond_option_supported(const char *option, NMBondMode mode);
 
-guint32 _nm_setting_bond_opt_value_as_u32(NMSettingBond *s_bond, const char *opt);
-guint16 _nm_setting_bond_opt_value_as_u16(NMSettingBond *s_bond, const char *opt);
 guint8  _nm_setting_bond_opt_value_as_u8(NMSettingBond *s_bond, const char *opt);
+guint16 _nm_setting_bond_opt_value_as_u16(NMSettingBond *s_bond, const char *opt);
+guint32 _nm_setting_bond_opt_value_as_u32(NMSettingBond *s_bond, const char *opt);
+bool    _nm_setting_bond_opt_value_as_intbool(NMSettingBond *s_bond, const char *opt);
 
 /*****************************************************************************/
 
diff --git a/src/libnm-core-public/nm-version-macros.h b/src/libnm-core-public/nm-version-macros.h
index acf84ddd..edd872e8 100644
--- a/src/libnm-core-public/nm-version-macros.h
+++ b/src/libnm-core-public/nm-version-macros.h
@@ -30,7 +30,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (0)
+#define NM_MICRO_VERSION (2)
 
 /**
  * NM_CHECK_VERSION:
diff --git a/src/libnm-glib-aux/nm-keyfile-aux.c b/src/libnm-glib-aux/nm-keyfile-aux.c
index 1cae28b4..20a4690f 100644
--- a/src/libnm-glib-aux/nm-keyfile-aux.c
+++ b/src/libnm-glib-aux/nm-keyfile-aux.c
@@ -442,7 +442,7 @@ nm_key_file_db_prune(NMKeyFileDB *self,
     _LOGD("prune keyfile of old entries: \"%s\"", self->filename);
 
     if (!self->groups_pruned) {
-        /* When we prune the first time, we swap the GKeyfile instance.
+        /* When we prune the first time, we swap the GKeyFile instance.
          * The instance loaded from disk might have unrelated groups and
          * comments. Let's get rid of them by creating a new instance.
          *
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index 624f9a3e..d0885477 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -6621,19 +6621,38 @@ nm_crypto_md5_hash(const guint8 *salt,
 
 /*****************************************************************************/
 
-char *
-nm_utils_get_process_exit_status_desc(int status)
+const char *
+nm_utils_get_process_exit_status_desc_buf(int status, char *buf, gsize buf_len)
 {
+    const char *buf0 = buf;
+
+    nm_assert(buf_len == 0 || buf);
+
+    /* This should give a partial sentence, it it can be combined with
+     * prinft("command XYZ %s.\n", desc) */
+
     if (WIFEXITED(status))
-        return g_strdup_printf("exited with status %d", WEXITSTATUS(status));
+        nm_strbuf_append(&buf, &buf_len, "exited with status %d", WEXITSTATUS(status));
     else if (WIFSIGNALED(status))
-        return g_strdup_printf("killed by signal %d", WTERMSIG(status));
+        nm_strbuf_append(&buf, &buf_len, "killed by signal %d", WTERMSIG(status));
     else if (WIFSTOPPED(status))
-        return g_strdup_printf("stopped by signal %d", WSTOPSIG(status));
+        nm_strbuf_append(&buf, &buf_len, "stopped by signal %d", WSTOPSIG(status));
     else if (WIFCONTINUED(status))
-        return g_strdup("resumed by SIGCONT)");
+        nm_strbuf_append(&buf, &buf_len, "resumed by SIGCONT");
     else
-        return g_strdup_printf("exited with unknown status 0x%x", status);
+        nm_strbuf_append(&buf, &buf_len, "exited with unknown status 0x%x", status);
+
+    return buf0;
+}
+
+char *
+nm_utils_get_process_exit_status_desc(int status)
+{
+    char buf[NM_UTILS_GET_PROCESS_EXIT_STATUS_BUF_LEN];
+
+    nm_utils_get_process_exit_status_desc_buf(status, buf, sizeof(buf));
+
+    return g_strdup(buf);
 }
 
 /*****************************************************************************/
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index 3d8eaced..fe7c59f3 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -809,6 +809,8 @@ gssize _nm_strv_find_first(const char *const *list, gssize len, const char *need
 #define nm_strv_find_first(list, len, needle) \
     _nm_strv_find_first(NM_CAST_STRV_CC(list), (len), (needle))
 
+#define nm_strv_contains(list, len, needle) (nm_strv_find_first((list), (len), (needle)) >= 0)
+
 gboolean nm_strv_has_duplicate(const char *const *list, gssize len, gboolean is_sorted);
 
 const char **nm_strv_cleanup_const(const char **strv, gboolean skip_empty, gboolean skip_repeated);
@@ -1318,6 +1320,8 @@ typedef enum {
     NM_UTILS_ERROR_INVALID_ARGUMENT,    /*< nick=InvalidArgument >*/
     NM_UTILS_ERROR_NOT_READY,           /*< nick=NotReady >*/
 
+    NM_UTILS_ERROR_COMMAND_FAILED, /*< nick=CommandFailed >*/
+
     NM_UTILS_ERROR_AMBIGUOUS, /*< nick=Ambiguous >*/
 
     /* the following codes have a special meaning and are exactly used for
@@ -3403,6 +3407,10 @@ void nm_crypto_md5_hash(const guint8 *salt,
 
 /*****************************************************************************/
 
+#define NM_UTILS_GET_PROCESS_EXIT_STATUS_BUF_LEN 41
+
+const char *nm_utils_get_process_exit_status_desc_buf(int status, char *buf, gsize buf_len);
+
 char *nm_utils_get_process_exit_status_desc(int status);
 
 gboolean nm_utils_validate_hostname(const char *hostname);
diff --git a/src/libnm-glib-aux/nm-str-buf.h b/src/libnm-glib-aux/nm-str-buf.h
index 652bc96b..e8b51208 100644
--- a/src/libnm-glib-aux/nm-str-buf.h
+++ b/src/libnm-glib-aux/nm-str-buf.h
@@ -78,14 +78,14 @@ NM_STR_BUF_INIT(gsize allocated, gboolean do_bzero_mem)
             ? (size)                                                                        \
             : 0,                                                                            \
         FALSE,                                                                              \
-        (do_bzero_mem));
+        (do_bzero_mem))
 
 #define NM_STR_BUF_INIT_ARR(arr, do_bzero_mem)                                                    \
     NM_STR_BUF_INIT_FULL((arr),                                                                   \
                          0,                                                                       \
                          NM_STATIC_ASSERT_EXPR_1(sizeof(arr) > sizeof(char *)) ? sizeof(arr) : 0, \
                          FALSE,                                                                   \
-                         (do_bzero_mem));
+                         (do_bzero_mem))
 
 void _nm_str_buf_ensure_size(NMStrBuf *strbuf, gsize new_size, gboolean reserve_exact);
 
@@ -530,7 +530,10 @@ nm_str_buf_finalize(NMStrBuf *strbuf, gsize *out_len)
         char *str = g_steal_pointer(&strbuf->_priv_str);
         char *result;
 
-        result = g_strndup(str, strbuf->_priv_len);
+        result = g_new(char, strbuf->_priv_len + 1u);
+        memcpy(result, str, strbuf->_priv_len);
+        result[strbuf->_priv_len] = '\0';
+
         if (strbuf->_priv_do_bzero_mem)
             nm_explicit_bzero(str, strbuf->_priv_len);
         return result;
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 7d36d64a..cfe70b58 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -970,6 +970,16 @@ test_nm_str_buf(void)
         } else
             g_assert(stack_buf != nm_str_buf_get_str(&strbuf));
     }
+
+    {
+        nm_auto_str_buf NMStrBuf s1  = NM_STR_BUF_INIT_A(10, nmtst_get_rand_bool());
+        gs_free char            *str = NULL;
+        gsize                    l;
+
+        nm_str_buf_append_len(&s1, "a\0b", 3);
+        str = nm_str_buf_finalize(&s1, &l);
+        g_assert_cmpmem(str, l + 1, "a\0b", 4);
+    }
 }
 
 /*****************************************************************************/
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index cd685fe2..eb7c671b 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -1589,11 +1589,8 @@ _parse_lnk_bond(const char *kind, struct nlattr *info_data)
 
     if (tb[IFLA_BOND_MODE])
         props->mode = nla_get_u8(tb[IFLA_BOND_MODE]);
-    if (tb[IFLA_BOND_PRIMARY]) {
-        props->primary = nla_get_u32(tb[IFLA_BOND_PRIMARY]);
-    } else if (tb[IFLA_BOND_ACTIVE_SLAVE]) {
-        props->primary = nla_get_u32(tb[IFLA_BOND_ACTIVE_SLAVE]);
-    }
+    if (tb[IFLA_BOND_PRIMARY])
+        props->primary = NM_CLAMP((int) nla_get_u32(tb[IFLA_BOND_PRIMARY]), 0, G_MAXINT);
     if (tb[IFLA_BOND_MIIMON]) {
         props->miimon     = nla_get_u32(tb[IFLA_BOND_MIIMON]);
         props->miimon_has = TRUE;
@@ -1631,12 +1628,8 @@ _parse_lnk_bond(const char *kind, struct nlattr *info_data)
     }
     if (tb[IFLA_BOND_ARP_VALIDATE])
         props->arp_validate = nla_get_u32(tb[IFLA_BOND_ARP_VALIDATE]);
-    if (tb[IFLA_BOND_ARP_ALL_TARGETS]) {
-        props->arp_all_targets     = nla_get_u32(tb[IFLA_BOND_ARP_ALL_TARGETS]);
-        props->arp_all_targets_has = TRUE;
-    } else {
-        props->arp_all_targets_has = FALSE;
-    }
+    if (tb[IFLA_BOND_ARP_ALL_TARGETS])
+        props->arp_all_targets = nla_get_u32(tb[IFLA_BOND_ARP_ALL_TARGETS]);
     if (tb[IFLA_BOND_PRIMARY_RESELECT])
         props->primary_reselect = nla_get_u8(tb[IFLA_BOND_PRIMARY_RESELECT]);
     if (tb[IFLA_BOND_FAIL_OVER_MAC])
@@ -2677,7 +2670,7 @@ _wireguard_read_info(NMPlatform     *platform /* used only as logging context */
           ifindex,
           wireguard_family_id);
 
-    msg = nlmsg_alloc();
+    msg = nlmsg_alloc(0);
 
     if (!genlmsg_put(msg,
                      NL_AUTO_PORT,
@@ -2886,7 +2879,7 @@ _wireguard_create_change_nlmsgs(NMPlatform                               *platfo
 
 again:
 
-    msg = nlmsg_alloc();
+    msg = nlmsg_alloc(0);
     if (!genlmsg_put(msg,
                      NL_AUTO_PORT,
                      NL_AUTO_SEQ,
@@ -4521,7 +4514,7 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
             nla_nest_end(msg, targets);
         }
 
-        if (props->arp_all_targets_has)
+        if (props->arp_all_targets)
             NLA_PUT_U32(msg, IFLA_BOND_ARP_ALL_TARGETS, props->arp_all_targets);
         if (props->arp_interval)
             NLA_PUT_U32(msg, IFLA_BOND_ARP_INTERVAL, props->arp_interval);
@@ -4539,7 +4532,7 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
             NLA_PUT_U32(msg, IFLA_BOND_PACKETS_PER_SLAVE, props->packets_per_port);
         if (props->peer_notif_delay_has)
             NLA_PUT_U32(msg, IFLA_BOND_PEER_NOTIF_DELAY, props->peer_notif_delay);
-        if (props->primary)
+        if (props->primary > 0)
             NLA_PUT_U32(msg, IFLA_BOND_PRIMARY, props->primary);
         if (props->resend_igmp_has)
             NLA_PUT_U32(msg, IFLA_BOND_RESEND_IGMP, props->resend_igmp);
@@ -4972,7 +4965,8 @@ _nl_msg_new_link_full(uint16_t    nlmsg_type,
                       const char *ifname,
                       guint8      family,
                       unsigned    flags_mask,
-                      unsigned    flags_set)
+                      unsigned    flags_set,
+                      size_t      len)
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
     const struct ifinfomsg       ifi = {
@@ -4984,7 +4978,7 @@ _nl_msg_new_link_full(uint16_t    nlmsg_type,
 
     nm_assert(NM_IN_SET(nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK, RTM_SETLINK));
 
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
+    msg = nlmsg_alloc_new(len ? nlmsg_total_size(NLMSG_HDRLEN + len) : 0, nlmsg_type, nlmsg_flags);
 
     if (nlmsg_append_struct(msg, &ifi) < 0)
         goto nla_put_failure;
@@ -5001,7 +4995,7 @@ nla_put_failure:
 static struct nl_msg *
 _nl_msg_new_link(uint16_t nlmsg_type, uint16_t nlmsg_flags, int ifindex, const char *ifname)
 {
-    return _nl_msg_new_link_full(nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0);
+    return _nl_msg_new_link_full(nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0, 0);
 }
 
 /* Copied and modified from libnl3's build_addr_msg(). */
@@ -5033,7 +5027,7 @@ _nl_msg_new_address(uint16_t      nlmsg_type,
     nm_assert(NM_IN_SET(family, AF_INET, AF_INET6));
     nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWADDR, RTM_DELADDR));
 
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
+    msg = nlmsg_alloc_new(0, nlmsg_type, nlmsg_flags);
 
     addr_len = family == AF_INET ? sizeof(in_addr_t) : sizeof(struct in6_addr);
 
@@ -5140,7 +5134,7 @@ _nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *ob
         NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
     nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
 
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
+    msg = nlmsg_alloc_new(0, nlmsg_type, nlmsg_flags);
 
     if (nlmsg_append_struct(msg, &rtmsg) < 0)
         goto nla_put_failure;
@@ -5232,7 +5226,7 @@ _nl_msg_new_routing_rule(uint16_t                     nlmsg_type,
     const guint8 addr_size           = nm_utils_addr_family_to_size(routing_rule->addr_family);
     guint32      table;
 
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
+    msg = nlmsg_alloc_new(0, nlmsg_type, nlmsg_flags);
 
     table = routing_rule->table;
 
@@ -5351,7 +5345,7 @@ _nl_msg_new_qdisc(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPlatformQdi
                   .tcm_info    = qdisc->info,
     };
 
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
+    msg = nlmsg_alloc_new(0, nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
 
     if (nlmsg_append_struct(msg, &tcm) < 0)
         goto nla_put_failure;
@@ -5439,7 +5433,7 @@ _nl_msg_new_tfilter(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPlatformT
                   .tcm_info    = tfilter->info,
     };
 
-    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
+    msg = nlmsg_alloc_new(0, nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
 
     if (nlmsg_append_struct(msg, &tcm) < 0)
         goto nla_put_failure;
@@ -7234,7 +7228,7 @@ _nl_msg_new_dump_rtnl(NMPObjectType obj_type, int preferred_addr_family)
     nm_assert(klass);
     nm_assert(klass->rtm_gettype > 0);
 
-    nlmsg = nlmsg_alloc_simple(klass->rtm_gettype, NLM_F_DUMP);
+    nlmsg = nlmsg_alloc_new(0, klass->rtm_gettype, NLM_F_DUMP);
 
     if (klass->addr_family != AF_UNSPEC) {
         /* if the class specifies a particular address family, then it is preferred. */
@@ -7279,7 +7273,7 @@ _nl_msg_new_dump_genl_families(void)
 {
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-    nlmsg = nlmsg_alloc_size(nlmsg_total_size(GENL_HDRLEN));
+    nlmsg = nlmsg_alloc(nlmsg_total_size(GENL_HDRLEN));
 
     if (!genlmsg_put(nlmsg,
                      NL_AUTO_PORT,
@@ -8095,7 +8089,8 @@ link_change_flags(NMPlatform *platform, int ifindex, unsigned flags_mask, unsign
           nm_platform_link_flags2str(flags_set, s_flags, sizeof(s_flags)),
           nm_platform_link_flags2str(flags_mask, s_flags2, sizeof(s_flags2)));
 
-    nlmsg = _nl_msg_new_link_full(RTM_NEWLINK, 0, ifindex, NULL, AF_UNSPEC, flags_mask, flags_set);
+    nlmsg =
+        _nl_msg_new_link_full(RTM_NEWLINK, 0, ifindex, NULL, AF_UNSPEC, flags_mask, flags_set, 0);
     if (!nlmsg)
         return -NME_UNSPEC;
     return do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
@@ -8410,16 +8405,23 @@ link_set_sriov_vfs(NMPlatform *platform, int ifindex, const NMPlatformVF *const
 {
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
     struct nlattr               *list, *info, *vlan_list;
-    guint                        i;
+    guint                        i      = 0;
+    guint                        num    = 0;
+    size_t                       buflen = 0;
 
-    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
+    while (vfs[num])
+        num++;
+
+    /* A single IFLA_VF_INFO shouldn't take more than 200 bytes. */
+    buflen = (num + 1) * 200;
+    nlmsg  = _nl_msg_new_link_full(RTM_NEWLINK, 0, ifindex, NULL, AF_UNSPEC, 0, 0, buflen);
     if (!nlmsg)
         g_return_val_if_reached(-NME_BUG);
 
     if (!(list = nla_nest_start(nlmsg, IFLA_VFINFO_LIST)))
         goto nla_put_failure;
 
-    for (i = 0; vfs[i]; i++) {
+    for (; vfs[i]; i++) {
         const NMPlatformVF *vf = vfs[i];
 
         if (!(info = nla_nest_start(nlmsg, IFLA_VF_INFO)))
@@ -8492,6 +8494,11 @@ link_set_sriov_vfs(NMPlatform *platform, int ifindex, const NMPlatformVF *const
 
     return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
 nla_put_failure:
+    _LOGE("error building SR-IOV VFs netlink message: used %u/%zu bytes for %u/%u VFs",
+          nlmsg_hdr(nlmsg)->nlmsg_len,
+          buflen,
+          i,
+          num);
     g_return_val_if_reached(FALSE);
 }
 
@@ -8506,8 +8513,14 @@ link_set_bridge_vlans(NMPlatform                        *platform,
     struct bridge_vlan_info      vinfo = {};
     guint                        i;
 
-    nlmsg =
-        _nl_msg_new_link_full(vlans ? RTM_SETLINK : RTM_DELLINK, 0, ifindex, NULL, AF_BRIDGE, 0, 0);
+    nlmsg = _nl_msg_new_link_full(vlans ? RTM_SETLINK : RTM_DELLINK,
+                                  0,
+                                  ifindex,
+                                  NULL,
+                                  AF_BRIDGE,
+                                  0,
+                                  0,
+                                  0);
     if (!nlmsg)
         g_return_val_if_reached(-NME_BUG);
 
@@ -9535,7 +9548,7 @@ tc_delete(NMPlatform *platform,
         log_tag = "do-delete-tc";
     }
 
-    msg = nlmsg_alloc_simple(nlmsg_type, NMP_NLM_FLAG_F_ECHO);
+    msg = nlmsg_alloc_new(0, nlmsg_type, NMP_NLM_FLAG_F_ECHO);
 
     if (nlmsg_append_struct(msg, &tcm) < 0)
         goto nla_put_failure;
@@ -10259,7 +10272,7 @@ mptcp_addr_update(NMPlatform *platform, NMOptionBool add, const NMPlatformMptcpA
           cmd_str,
           nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)));
 
-    nlmsg = nlmsg_alloc_size(nlmsg_total_size(GENL_HDRLEN) + 200);
+    nlmsg = nlmsg_alloc(nlmsg_total_size(GENL_HDRLEN) + 200);
 
     if (!genlmsg_put(nlmsg,
                      NL_AUTO_PORT,
@@ -10350,7 +10363,7 @@ mptcp_addrs_dump(NMPlatform *platform)
         return NULL;
     }
 
-    nlmsg = nlmsg_alloc_size(nlmsg_total_size(GENL_HDRLEN));
+    nlmsg = nlmsg_alloc(nlmsg_total_size(GENL_HDRLEN));
 
     if (!genlmsg_put(nlmsg,
                      NL_AUTO_PORT,
diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c
index 36841d56..e08eee5e 100644
--- a/src/libnm-platform/nm-netlink.c
+++ b/src/libnm-platform/nm-netlink.c
@@ -364,11 +364,22 @@ nla_reserve(struct nl_msg *msg, int attrtype, int attrlen)
 
 /*****************************************************************************/
 
+/**
+ * Allocate a new netlink message.
+ *
+ * Allocates a new netlink message without any further payload. If @len is zero,
+ * the maximum payload size is set to the size of one memory page.
+ *
+ * @return Newly allocated netlink message or NULL.
+ */
 struct nl_msg *
-nlmsg_alloc_size(size_t len)
+nlmsg_alloc(size_t len)
 {
     struct nl_msg *nm;
 
+    if (len == 0)
+        len = nm_utils_getpagesize();
+
     if (len < sizeof(struct nlmsghdr))
         len = sizeof(struct nlmsghdr);
     else if (len > UINT32_MAX)
@@ -384,38 +395,23 @@ nlmsg_alloc_size(size_t len)
     return nm;
 }
 
-/**
- * Allocate a new netlink message with the default maximum payload size.
- *
- * Allocates a new netlink message without any further payload. The
- * maximum payload size defaults to PAGESIZE or as otherwise specified
- * with nlmsg_set_default_size().
- *
- * @return Newly allocated netlink message or NULL.
- */
-struct nl_msg *
-nlmsg_alloc(void)
-{
-    return nlmsg_alloc_size(nm_utils_getpagesize());
-}
-
 struct nl_msg *
 nlmsg_alloc_convert(struct nlmsghdr *hdr)
 {
     struct nl_msg *nm;
 
-    nm = nlmsg_alloc_size(NLMSG_ALIGN(hdr->nlmsg_len));
+    nm = nlmsg_alloc(NLMSG_ALIGN(hdr->nlmsg_len));
     memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
     return nm;
 }
 
 struct nl_msg *
-nlmsg_alloc_simple(uint16_t nlmsgtype, uint16_t flags)
+nlmsg_alloc_new(size_t size, uint16_t nlmsgtype, uint16_t flags)
 {
     struct nl_msg *nm;
     struct nlmsghdr *new;
 
-    nm               = nlmsg_alloc();
+    nm               = nlmsg_alloc(size);
     new              = nm->nm_nlh;
     new->nlmsg_type  = nlmsgtype;
     new->nlmsg_flags = flags;
@@ -928,7 +924,7 @@ genl_ctrl_resolve(struct nl_sock *sk, const char *name)
                              .valid_arg = &response_data,
     };
 
-    msg = nlmsg_alloc();
+    msg = nlmsg_alloc(0);
 
     if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY, 1))
         return -ENOMEM;
diff --git a/src/libnm-platform/nm-netlink.h b/src/libnm-platform/nm-netlink.h
index 44ed70af..634be2b4 100644
--- a/src/libnm-platform/nm-netlink.h
+++ b/src/libnm-platform/nm-netlink.h
@@ -393,13 +393,11 @@ nla_parse_nested(struct nlattr           *tb[],
 
 /*****************************************************************************/
 
-struct nl_msg *nlmsg_alloc(void);
-
-struct nl_msg *nlmsg_alloc_size(size_t max);
+struct nl_msg *nlmsg_alloc(size_t len);
 
 struct nl_msg *nlmsg_alloc_convert(struct nlmsghdr *hdr);
 
-struct nl_msg *nlmsg_alloc_simple(uint16_t nlmsgtype, uint16_t flags);
+struct nl_msg *nlmsg_alloc_new(size_t size, uint16_t nlmsgtype, uint16_t flags);
 
 void *nlmsg_reserve(struct nl_msg *n, uint32_t len, uint32_t pad);
 
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index df177485..7d82083c 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -6082,7 +6082,6 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
     char sbuf_updelay[30];
     char sbuf_downdelay[30];
     char sbuf_peer_notif_delay[60];
-    char sbuf_arp_all_targets[30];
     char sbuf_resend_igmp[30];
     char sbuf_lp_interval[30];
     char sbuf_tlb_dynamic_lb[30];
@@ -6096,7 +6095,7 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
         &len,
         "bond"
         " mode %u"
-        " primary %u"
+        " primary %d"
         "%s" /* miimon */
         "%s" /* updelay */
         "%s" /* downdelay */
@@ -6106,7 +6105,7 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
         "%s" /* lp_interval */
         " packets_per_port %u"
         "%s" /* peer_notif_delay */
-        "%s" /* arp_all_targets */
+        " arp_all_targets %u"
         " arp_validate %u"
         " ad_actor_sys_prio %u"
         " ad_user_port_key %u"
@@ -6156,12 +6155,7 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
                              !lnk->peer_notif_delay_has ? "?" : "",
                              lnk->peer_notif_delay)
             : "",
-        lnk->arp_all_targets_has || lnk->arp_all_targets != 0
-            ? nm_sprintf_buf(sbuf_arp_all_targets,
-                             " arp_all_targets%s %u",
-                             !lnk->arp_all_targets_has ? "?" : "",
-                             lnk->arp_all_targets)
-            : "",
+        lnk->arp_all_targets,
         lnk->arp_validate,
         lnk->ad_actor_sys_prio,
         lnk->ad_user_port_key,
@@ -8043,7 +8037,6 @@ nm_platform_lnk_bond_hash_update(const NMPlatformLnkBond *obj, NMHashState *h)
                         obj->primary_reselect,
                         obj->xmit_hash_policy,
                         NM_HASH_COMBINE_BOOLS(guint16,
-                                              obj->arp_all_targets_has,
                                               obj->downdelay_has,
                                               obj->lp_interval_has,
                                               obj->miimon_has,
@@ -8089,7 +8082,6 @@ nm_platform_lnk_bond_cmp(const NMPlatformLnkBond *a, const NMPlatformLnkBond *b)
     NM_CMP_FIELD(a, b, mode);
     NM_CMP_FIELD(a, b, primary_reselect);
     NM_CMP_FIELD(a, b, xmit_hash_policy);
-    NM_CMP_FIELD_BOOL(a, b, arp_all_targets_has);
     NM_CMP_FIELD_BOOL(a, b, downdelay_has);
     NM_CMP_FIELD_BOOL(a, b, lp_interval_has);
     NM_CMP_FIELD_BOOL(a, b, miimon_has);
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 90ffbed0..7792bbf5 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -895,6 +895,7 @@ extern const NMPlatformLnkBridge nm_platform_lnk_bridge_default;
 #define NM_BOND_MAX_ARP_TARGETS 16
 
 typedef struct {
+    int         primary;
     in_addr_t   arp_ip_target[NM_BOND_MAX_ARP_TARGETS];
     guint32     arp_all_targets;
     guint32     arp_interval;
@@ -905,7 +906,6 @@ typedef struct {
     guint32     min_links;
     guint32     packets_per_port;
     guint32     peer_notif_delay;
-    guint32     primary;
     guint32     resend_igmp;
     guint32     updelay;
     guint16     ad_actor_sys_prio;
@@ -920,7 +920,6 @@ typedef struct {
     guint8      mode;
     guint8      primary_reselect;
     guint8      xmit_hash_policy;
-    bool        arp_all_targets_has : 1;
     bool        downdelay_has : 1;
     bool        lp_interval_has : 1;
     bool        miimon_has : 1;
diff --git a/src/libnm-platform/nmp-global-tracker.c b/src/libnm-platform/nmp-global-tracker.c
index ea4da284..12869cb2 100644
--- a/src/libnm-platform/nmp-global-tracker.c
+++ b/src/libnm-platform/nmp-global-tracker.c
@@ -198,7 +198,7 @@ _track_data_hash(gconstpointer data)
     _track_data_assert(track_data, FALSE);
 
     nm_hash_init(&h, 269297543u);
-    nmp_object_id_hash_update(track_data->obj, &h);
+    nmp_object_hash_update(track_data->obj, &h);
     nm_hash_update_val(&h, track_data->user_tag);
     return nm_hash_complete(&h);
 }
@@ -213,7 +213,7 @@ _track_data_equal(gconstpointer data_a, gconstpointer data_b)
     _track_data_assert(track_data_b, FALSE);
 
     return track_data_a->user_tag == track_data_b->user_tag
-           && nmp_object_id_equal(track_data_a->obj, track_data_b->obj);
+           && nmp_object_equal(track_data_a->obj, track_data_b->obj);
 }
 
 static void
@@ -253,6 +253,22 @@ _track_obj_data_get_best_data(TrackObjData *obj_data)
         td_best = track_data;
     }
 
+    if (!td_best)
+        return NULL;
+
+    /* Always copy the object from the best TrackData to the TrackObjData. It is
+     * a bit odd that this getter modifies TrackObjData. However, it gives the
+     * nice property that after calling _track_obj_data_get_best_data() you can
+     * use obj_data->obj (and get the same as td_best->obj).
+     *
+     * This is actually important, because the previous obj_data->obj will have
+     * the same ID, but it might have minor differences to td_best->obj.
+     *
+     * Note that at this point obj_data->obj also might be an object that is no longer
+     * tracked. Updating the reference will ensure that we don't have such old references
+     * around and update to use the most appropriate one. */
+    nmp_object_ref_set(&obj_data->obj, td_best->obj);
+
     return td_best;
 }
 
diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c
index c351f014..cd54df92 100644
--- a/src/libnm-platform/tests/test-nm-platform.c
+++ b/src/libnm-platform/tests/test-nm-platform.c
@@ -27,10 +27,9 @@ test_use_symbols(void)
         (void (*)(void)) nlmsg_hdr,
         (void (*)(void)) nlmsg_reserve,
         (void (*)(void)) nla_reserve,
-        (void (*)(void)) nlmsg_alloc_size,
-        (void (*)(void)) nlmsg_alloc,
         (void (*)(void)) nlmsg_alloc_convert,
-        (void (*)(void)) nlmsg_alloc_simple,
+        (void (*)(void)) nlmsg_alloc_new,
+        (void (*)(void)) nlmsg_alloc,
         (void (*)(void)) nlmsg_free,
         (void (*)(void)) nlmsg_append,
         (void (*)(void)) nlmsg_parse,
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
index 37edd928..c7ee0473 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
@@ -86,7 +86,7 @@ _nl80211_alloc_msg(guint16 genl_family_id, int ifindex, int phy, uint8_t cmd, ui
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
 
-    msg = nlmsg_alloc();
+    msg = nlmsg_alloc(0);
     genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, genl_family_id, 0, flags, cmd, 0);
     NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
     if (phy != -1)
diff --git a/src/libnm-platform/wpan/nm-wpan-utils.c b/src/libnm-platform/wpan/nm-wpan-utils.c
index 675efe5a..03c9581f 100644
--- a/src/libnm-platform/wpan/nm-wpan-utils.c
+++ b/src/libnm-platform/wpan/nm-wpan-utils.c
@@ -79,7 +79,7 @@ _nl802154_alloc_msg(guint16 genl_family_id, int ifindex, uint8_t cmd, uint16_t f
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
 
-    msg = nlmsg_alloc();
+    msg = nlmsg_alloc(0);
     if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, genl_family_id, 0, flags, cmd, 0))
         goto nla_put_failure;
     NLA_PUT_U32(msg, NL802154_ATTR_IFINDEX, ifindex);
diff --git a/src/nm-initrd-generator/nm-initrd-generator.h b/src/nm-initrd-generator/nm-initrd-generator.h
index 4f33ac65..0761c9bb 100644
--- a/src/nm-initrd-generator/nm-initrd-generator.h
+++ b/src/nm-initrd-generator/nm-initrd-generator.h
@@ -12,6 +12,9 @@
 #define NMI_WAIT_DEVICE_TIMEOUT_MSEC 60000
 #define NMI_IP_REQUIRED_TIMEOUT_MSEC 20000
 
+#define NMI_AUTOCONNECT_PRIORITY_CMDLINE  -100
+#define NMI_AUTOCONNECT_PRIORITY_FIRMWARE -200
+
 static inline int
 get_ip_address_family(const char *str, gboolean with_prefix)
 {
diff --git a/src/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c
index 34b19e45..36bfdb0b 100644
--- a/src/nm-initrd-generator/nmi-cmdline-reader.c
+++ b/src/nm-initrd-generator/nmi-cmdline-reader.c
@@ -99,6 +99,7 @@ reader_create_connection(Reader                  *reader,
                          const char              *ifname,
                          const char              *mac,
                          const char              *type_name,
+                         int                      autoconnect_priority,
                          NMConnectionMultiConnect multi_connect)
 {
     NMConnection *connection;
@@ -155,6 +156,8 @@ reader_create_connection(Reader                  *reader,
                  multi_connect,
                  NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES,
                  1,
+                 NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
+                 autoconnect_priority,
                  NULL);
 
     if (nm_streq0(type_name, NM_SETTING_INFINIBAND_SETTING_NAME)) {
@@ -189,6 +192,7 @@ reader_get_default_connection(Reader *reader)
                                        NULL,
                                        NULL,
                                        NM_SETTING_WIRED_SETTING_NAME,
+                                       NMI_AUTOCONNECT_PRIORITY_CMDLINE,
                                        NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
         nm_connection_add_setting(con, nm_setting_wired_new());
         reader->default_connection = con;
@@ -264,6 +268,7 @@ reader_get_connection(Reader     *reader,
                                               ifname,
                                               mac,
                                               type_name,
+                                              NMI_AUTOCONNECT_PRIORITY_CMDLINE,
                                               NM_CONNECTION_MULTI_CONNECT_SINGLE);
     }
     setting = (NMSetting *) nm_connection_get_setting_connection(connection);
@@ -1526,6 +1531,7 @@ nmi_cmdline_reader_parse(const char        *etc_connections_dir,
                                                   NULL,
                                                   bootif,
                                                   NM_SETTING_WIRED_SETTING_NAME,
+                                                  NMI_AUTOCONNECT_PRIORITY_FIRMWARE,
                                                   NM_CONNECTION_MULTI_CONNECT_SINGLE);
         } else {
             g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, bootif, NULL);
diff --git a/src/nm-initrd-generator/nmi-dt-reader.c b/src/nm-initrd-generator/nmi-dt-reader.c
index 554f5c4d..99fe25a2 100644
--- a/src/nm-initrd-generator/nmi-dt-reader.c
+++ b/src/nm-initrd-generator/nmi-dt-reader.c
@@ -248,6 +248,8 @@ nmi_dt_reader_parse(const char *sysfs_dir)
                                            NM_SETTING_WIRED_SETTING_NAME,
                                            NM_SETTING_CONNECTION_ID,
                                            "OpenFirmware Connection",
+                                           NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
+                                           NMI_AUTOCONNECT_PRIORITY_FIRMWARE,
                                            NULL));
 
     s_ip4 = nm_setting_ip4_config_new();
diff --git a/src/nm-initrd-generator/nmi-ibft-reader.c b/src/nm-initrd-generator/nmi-ibft-reader.c
index 5e4b1bb3..3f30e531 100644
--- a/src/nm-initrd-generator/nmi-ibft-reader.c
+++ b/src/nm-initrd-generator/nmi-ibft-reader.c
@@ -328,6 +328,8 @@ connection_setting_add(GHashTable   *nic,
                  id,
                  NM_SETTING_CONNECTION_INTERFACE_NAME,
                  NULL,
+                 NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
+                 NMI_AUTOCONNECT_PRIORITY_FIRMWARE,
                  NULL);
 
     g_free(uuid);