summary refs log tree commit diff
path: root/src/core/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c25
-rw-r--r--src/core/devices/nm-device-bond.c209
-rw-r--r--src/core/devices/nm-device-bridge.c148
-rw-r--r--src/core/devices/nm-device-veth.c15
-rw-r--r--src/core/devices/nm-device-vrf.c54
-rw-r--r--src/core/devices/nm-device-wireguard.c2
-rw-r--r--src/core/devices/nm-device.c582
-rw-r--r--src/core/devices/nm-device.h19
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c18
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c120
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c3
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c11
-rw-r--r--src/core/devices/team/nm-device-team.c170
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c30
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c34
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c2
16 files changed, 1022 insertions, 420 deletions
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index 39b5c4ec..a8258123 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -262,6 +262,7 @@ complete_connection(NMDevice            *device,
     const char         *ctype;
     gboolean            is_dun = FALSE;
     gboolean            is_pan = FALSE;
+    gboolean            is_nap = FALSE;
     NMSettingGsm       *s_gsm;
     NMSettingCdma      *s_cdma;
     NMSettingSerial    *s_serial;
@@ -281,6 +282,8 @@ complete_connection(NMDevice            *device,
             is_dun = TRUE;
         else if (!strcmp(ctype, NM_SETTING_BLUETOOTH_TYPE_PANU))
             is_pan = TRUE;
+        else if (!strcmp(ctype, NM_SETTING_BLUETOOTH_TYPE_NAP))
+            is_nap = TRUE;
     } else {
         if (s_gsm || s_cdma)
             is_dun = TRUE;
@@ -288,7 +291,27 @@ complete_connection(NMDevice            *device,
             is_pan = TRUE;
     }
 
-    if (is_pan) {
+    if (is_nap) {
+        /* Make sure the device supports NAP */
+        if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("NAP requested, but Bluetooth device does not support NAP"));
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_BLUETOOTH_SETTING_NAME,
+                           NM_SETTING_BLUETOOTH_TYPE);
+            return FALSE;
+        }
+
+        g_object_set(G_OBJECT(s_bt),
+                     NM_SETTING_BLUETOOTH_TYPE,
+                     NM_SETTING_BLUETOOTH_TYPE_NAP,
+                     NULL);
+
+        fallback_prefix = _("NAP connection");
+    } else if (is_pan) {
         /* Make sure the device supports PAN */
         if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) {
             g_set_error_literal(error,
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 16896d57..41473abe 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -9,6 +9,8 @@
 
 #include <stdlib.h>
 #include <net/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
@@ -16,6 +18,7 @@
 #include "nm-device-factory.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
+#include "nm-manager.h"
 #include "nm-setting-bond-port.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceBond
@@ -312,7 +315,7 @@ set_bond_attr_or_default(NMDevice *device, NMSettingBond *s_bond, const char *op
     NMDeviceBond *self = NM_DEVICE_BOND(device);
     const char   *value;
 
-    value = nm_setting_bond_get_option_or_default(s_bond, opt);
+    value = nm_setting_bond_get_option_normalized(s_bond, opt);
     if (!value) {
         if (_LOGT_ENABLED(LOGD_BOND) && nm_setting_bond_get_option_by_name(s_bond, opt))
             _LOGT(LOGD_BOND, "bond option '%s' not set as it conflicts with other options", opt);
@@ -346,54 +349,147 @@ set_bond_arp_ip_targets(NMDevice *device, NMSettingBond *s_bond)
     set_arp_targets(
         device,
         cur_arp_ip_target,
-        nm_setting_bond_get_option_or_default(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET));
+        nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET));
 }
 
-static gboolean
-apply_bonding_config(NMDeviceBond *self)
+static guint8
+_bond_arp_ip_target_to_platform(const char *value, in_addr_t out[static NM_BOND_MAX_ARP_TARGETS])
 {
-    NMDevice      *device = NM_DEVICE(self);
-    NMSettingBond *s_bond;
-    NMBondMode     mode;
-    const char    *mode_str;
-    gs_free char  *device_bond_mode = NULL;
+    gs_free const char **ip = NULL;
+    in_addr_t            in_a;
+    int                  i;
+    int                  added = 0;
 
-    s_bond = nm_device_get_applied_setting(device, NM_TYPE_SETTING_BOND);
-    g_return_val_if_fail(s_bond, FALSE);
+    ip = nm_utils_bond_option_arp_ip_targets_split(value);
 
-    mode_str = nm_setting_bond_get_option_or_default(s_bond, NM_SETTING_BOND_OPTION_MODE);
-    mode     = _nm_setting_bond_mode_from_string(mode_str);
-    g_return_val_if_fail(mode != NM_BOND_MODE_UNKNOWN, FALSE);
+    if (!ip)
+        return added;
 
-    /* Set mode first, as some other options (e.g. arp_interval) are valid
-     * only for certain modes.
-     */
-    device_bond_mode = nm_platform_sysctl_master_get_option(nm_device_get_platform(device),
-                                                            nm_device_get_ifindex(device),
-                                                            NM_SETTING_BOND_OPTION_MODE);
-    /* Need to release all slaves before we can change bond mode */
-    if (!nm_streq0(device_bond_mode, mode_str))
-        nm_device_master_release_slaves_all(device);
+    for (i = 0; ip[i]; i++) {
+        if (added > NM_BOND_MAX_ARP_TARGETS - 1)
+            break;
+        if (!nm_utils_parse_inaddr_bin(AF_INET, ip[i], NULL, &in_a))
+            nm_assert_not_reached(); /* verify() already validated the IP addresses */
+
+        out[added++] = in_a;
+    }
+    return added;
+}
 
-    set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_MODE);
+static int
+_setting_bond_primary_opt_as_ifindex(NMSettingBond *s_bond)
+{
+    const char *primary_str;
+    int         ifindex = 0;
 
-    set_bond_arp_ip_targets(device, s_bond);
+    primary_str = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
 
-    set_bond_attrs_or_default(device, s_bond, NM_MAKE_STRV(OPTIONS_APPLY_SUBSET));
-    return TRUE;
+    if (primary_str != NULL)
+        ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, primary_str);
+
+    return ifindex;
+}
+
+static void
+_platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *props)
+{
+    const char *opt_value;
+
+    *props = (NMPlatformLnkBond){
+        .mode = _nm_setting_bond_mode_from_string(
+            nm_setting_bond_get_option_normalized(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)),
+    };
+
+    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 =
+            _bond_arp_ip_target_to_platform(opt_value, props->arp_ip_target);
+
+    props->miimon_has           = !props->arp_interval && !props->arp_validate;
+    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          = props->lp_interval != 1;
+    props->tlb_dynamic_lb_has   = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB);
 }
 
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceBond    *self = NM_DEVICE_BOND(device);
-    NMActStageReturn ret  = NM_ACT_STAGE_RETURN_SUCCESS;
+    NMActStageReturn  ret = NM_ACT_STAGE_RETURN_SUCCESS;
+    NMConnection     *connection;
+    NMSettingBond    *s_bond;
+    NMPlatformLnkBond props;
+    int               r;
+    int               ifindex = nm_device_get_ifindex(device);
+
+    connection = nm_device_get_applied_connection(device);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+    s_bond = nm_connection_get_setting_bond(connection);
+    g_return_val_if_fail(s_bond, NM_ACT_STAGE_RETURN_FAILURE);
+
+    _platform_lnk_bond_init_from_setting(s_bond, &props);
 
     /* Interface must be down to set bond options */
     nm_device_take_down(device, TRUE);
-    if (!apply_bonding_config(self))
+    r = nm_platform_link_bond_change(nm_device_get_platform(device), ifindex, &props);
+    if (r < 0) {
         ret = NM_ACT_STAGE_RETURN_FAILURE;
-    else {
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+    } else {
         if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE))
             ret = NM_ACT_STAGE_RETURN_FAILURE;
     }
@@ -424,8 +520,14 @@ commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_
                                          queue_id_str);
 }
 
-static gboolean
-enslave_slave(NMDevice *device, NMDevice *port, NMConnection *connection, gboolean configure)
+static NMTernary
+attach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            NMConnection              *connection,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceBond      *self = NM_DEVICE_BOND(device);
     NMSettingBondPort *s_port;
@@ -442,7 +544,7 @@ enslave_slave(NMDevice *device, NMDevice *port, NMConnection *connection, gboole
         nm_device_bring_up(port, TRUE, NULL);
 
         if (!success) {
-            _LOGI(LOGD_BOND, "assigning bond port %s: failed", nm_device_get_ip_iface(port));
+            _LOGI(LOGD_BOND, "attaching bond port %s: failed", nm_device_get_ip_iface(port));
             return FALSE;
         }
 
@@ -450,15 +552,15 @@ enslave_slave(NMDevice *device, NMDevice *port, NMConnection *connection, gboole
 
         commit_port_options(device, port, s_port);
 
-        _LOGI(LOGD_BOND, "assigned bond port %s", nm_device_get_ip_iface(port));
+        _LOGI(LOGD_BOND, "attached bond port %s", nm_device_get_ip_iface(port));
     } else
-        _LOGI(LOGD_BOND, "bond port %s was assigned", nm_device_get_ip_iface(port));
+        _LOGI(LOGD_BOND, "bond port %s was attached", nm_device_get_ip_iface(port));
 
     return TRUE;
 }
 
 static void
-release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 {
     NMDeviceBond *self = NM_DEVICE_BOND(device);
     gboolean      success;
@@ -472,10 +574,10 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
             configure = FALSE;
     }
 
-    ifindex_slave = nm_device_get_ip_ifindex(slave);
+    ifindex_slave = nm_device_get_ip_ifindex(port);
 
     if (ifindex_slave <= 0)
-        _LOGD(LOGD_BOND, "bond slave %s is already released", nm_device_get_ip_iface(slave));
+        _LOGD(LOGD_BOND, "bond port %s is already detached", nm_device_get_ip_iface(port));
 
     if (configure) {
         NMConnection   *applied;
@@ -490,9 +592,9 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
                                                ifindex_slave);
 
             if (success) {
-                _LOGI(LOGD_BOND, "released bond slave %s", nm_device_get_ip_iface(slave));
+                _LOGI(LOGD_BOND, "detached bond port %s", nm_device_get_ip_iface(port));
             } else {
-                _LOGW(LOGD_BOND, "failed to release bond slave %s", nm_device_get_ip_iface(slave));
+                _LOGW(LOGD_BOND, "failed to detach bond port %s", nm_device_get_ip_iface(port));
             }
         }
 
@@ -512,12 +614,12 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
          * other state is noticed by the now-released slave.
          */
         if (ifindex_slave > 0) {
-            if (!nm_device_bring_up(slave, TRUE, NULL))
-                _LOGW(LOGD_BOND, "released bond slave could not be brought up.");
+            if (!nm_device_bring_up(port, TRUE, NULL))
+                _LOGW(LOGD_BOND, "detached bond port could not be brought up.");
         }
     } else {
         if (ifindex_slave > 0) {
-            _LOGI(LOGD_BOND, "bond slave %s was released", nm_device_get_ip_iface(slave));
+            _LOGI(LOGD_BOND, "bond port %s was detached", nm_device_get_ip_iface(port));
         }
     }
 }
@@ -529,12 +631,19 @@ create_and_realize(NMDevice              *device,
                    const NMPlatformLink **out_plink,
                    GError               **error)
 {
-    const char *iface = nm_device_get_iface(device);
-    int         r;
+    const char       *iface = nm_device_get_iface(device);
+    NMSettingBond    *s_bond;
+    NMPlatformLnkBond props;
+    int               r;
 
     g_assert(iface);
 
-    r = nm_platform_link_bond_add(nm_device_get_platform(device), iface, out_plink);
+    s_bond = nm_connection_get_setting_bond(connection);
+    nm_assert(s_bond);
+
+    _platform_lnk_bond_init_from_setting(s_bond, &props);
+
+    r = nm_platform_link_bond_add(nm_device_get_platform(device), iface, &props, out_plink);
     if (r < 0) {
         g_set_error(error,
                     NM_DEVICE_ERROR,
@@ -612,7 +721,7 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
     s_bond = nm_connection_get_setting_bond(con_new);
     g_return_if_fail(s_bond);
 
-    value = nm_setting_bond_get_option_or_default(s_bond, NM_SETTING_BOND_OPTION_MODE);
+    value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_MODE);
     mode  = _nm_setting_bond_mode_from_string(value);
     g_return_if_fail(mode != NM_BOND_MODE_UNKNOWN);
 
@@ -663,8 +772,8 @@ nm_device_bond_class_init(NMDeviceBondClass *klass)
     device_class->create_and_realize = create_and_realize;
     device_class->act_stage1_prepare = act_stage1_prepare;
     device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
-    device_class->enslave_slave      = enslave_slave;
-    device_class->release_slave      = release_slave;
+    device_class->attach_port        = attach_port;
+    device_class->detach_port        = detach_port;
     device_class->can_reapply_change = can_reapply_change;
     device_class->reapply_connection = reapply_connection;
 }
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index 9bf7ac01..31cf361e 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -836,30 +836,33 @@ _platform_lnk_bridge_init_from_setting(NMSettingBridge *s_bridge, NMPlatformLnkB
     to_sysfs_group_address_sys(nm_setting_bridge_get_group_address(s_bridge), &props->group_addr);
 }
 
-static NMActStageReturn
-act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
+static gboolean
+link_config(NMDevice *device, NMConnection *connection)
 {
-    NMConnection       *connection;
+    int                 ifindex = nm_device_get_ifindex(device);
     NMSettingBridge    *s_bridge;
     NMPlatformLnkBridge props;
-    int                 r;
-    int                 ifindex = nm_device_get_ifindex(device);
-
-    connection = nm_device_get_applied_connection(device);
-    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
 
     s_bridge = nm_connection_get_setting_bridge(connection);
-    g_return_val_if_fail(s_bridge, NM_ACT_STAGE_RETURN_FAILURE);
+    g_return_val_if_fail(s_bridge, FALSE);
 
     _platform_lnk_bridge_init_from_setting(s_bridge, &props);
 
-    r = nm_platform_link_bridge_change(nm_device_get_platform(device), ifindex, &props);
-    if (r < 0) {
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
+    if (nm_platform_link_bridge_change(nm_device_get_platform(device), ifindex, &props) < 0)
+        return FALSE;
+
+    return bridge_set_vlan_options(device, s_bridge);
+}
+
+static NMActStageReturn
+act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+    NMConnection *connection;
+
+    connection = nm_device_get_applied_connection(device);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-    if (!bridge_set_vlan_options(device, s_bridge)) {
+    if (!link_config(device, connection)) {
         NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
         return NM_ACT_STAGE_RETURN_FAILURE;
     }
@@ -974,8 +977,14 @@ deactivate(NMDevice *device)
     }
 }
 
-static gboolean
-enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+static NMTernary
+attach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            NMConnection              *connection,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceBridge      *self = NM_DEVICE_BRIDGE(device);
     NMConnection        *master_connection;
@@ -985,7 +994,7 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
     if (configure) {
         if (!nm_platform_link_enslave(nm_device_get_platform(device),
                                       nm_device_get_ip_ifindex(device),
-                                      nm_device_get_ip_ifindex(slave)))
+                                      nm_device_get_ip_ifindex(port)))
             return FALSE;
 
         master_connection = nm_device_get_applied_connection(device);
@@ -1010,25 +1019,25 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
              * (except for the default one) and so there's no need to flush. */
 
             if (plat_vlans
-                && !nm_platform_link_set_bridge_vlans(nm_device_get_platform(slave),
-                                                      nm_device_get_ifindex(slave),
+                && !nm_platform_link_set_bridge_vlans(nm_device_get_platform(port),
+                                                      nm_device_get_ifindex(port),
                                                       TRUE,
                                                       plat_vlans))
                 return FALSE;
         }
 
-        commit_slave_options(slave, s_port);
+        commit_slave_options(port, s_port);
 
-        _LOGI(LOGD_BRIDGE, "attached bridge port %s", nm_device_get_ip_iface(slave));
+        _LOGI(LOGD_BRIDGE, "attached bridge port %s", nm_device_get_ip_iface(port));
     } else {
-        _LOGI(LOGD_BRIDGE, "bridge port %s was attached", nm_device_get_ip_iface(slave));
+        _LOGI(LOGD_BRIDGE, "bridge port %s was attached", nm_device_get_ip_iface(port));
     }
 
     return TRUE;
 }
 
 static void
-release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 {
     NMDeviceBridge *self = NM_DEVICE_BRIDGE(device);
     gboolean        success;
@@ -1041,10 +1050,10 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
             configure = FALSE;
     }
 
-    ifindex_slave = nm_device_get_ip_ifindex(slave);
+    ifindex_slave = nm_device_get_ip_ifindex(port);
 
     if (ifindex_slave <= 0) {
-        _LOGD(LOGD_TEAM, "bond slave %s is already released", nm_device_get_ip_iface(slave));
+        _LOGD(LOGD_TEAM, "bridge port %s is already detached", nm_device_get_ip_iface(port));
         return;
     }
 
@@ -1054,12 +1063,12 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
                                            ifindex_slave);
 
         if (success) {
-            _LOGI(LOGD_BRIDGE, "detached bridge port %s", nm_device_get_ip_iface(slave));
+            _LOGI(LOGD_BRIDGE, "detached bridge port %s", nm_device_get_ip_iface(port));
         } else {
-            _LOGW(LOGD_BRIDGE, "failed to detach bridge port %s", nm_device_get_ip_iface(slave));
+            _LOGW(LOGD_BRIDGE, "failed to detach bridge port %s", nm_device_get_ip_iface(port));
         }
     } else {
-        _LOGI(LOGD_BRIDGE, "bridge port %s was detached", nm_device_get_ip_iface(slave));
+        _LOGI(LOGD_BRIDGE, "bridge port %s was detached", nm_device_get_ip_iface(port));
     }
 }
 
@@ -1085,10 +1094,6 @@ create_and_realize(NMDevice              *device,
     s_bridge = nm_connection_get_setting_bridge(connection);
     nm_assert(s_bridge);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    if (s_wired)
-        mtu = nm_setting_wired_get_mtu(s_wired);
-
     hwaddr = nm_setting_bridge_get_mac_address(s_bridge);
     if (!hwaddr
         && nm_device_hw_addr_get_cloned(device, connection, FALSE, &hwaddr_cloned, NULL, NULL)) {
@@ -1113,6 +1118,11 @@ create_and_realize(NMDevice              *device,
 
     _platform_lnk_bridge_init_from_setting(s_bridge, &props);
 
+    s_wired = nm_connection_get_setting_wired(connection);
+    nm_assert(s_wired);
+
+    mtu = nm_setting_wired_get_mtu(s_wired);
+
     /* If mtu != 0, we set the MTU of the new bridge at creation time. However, kernel will still
      * automatically adjust the MTU of the bridge based on the minimum of the slave's MTU.
      * We don't want this automatism as the user asked for a fixed MTU.
@@ -1142,6 +1152,72 @@ create_and_realize(NMDevice              *device,
 
 /*****************************************************************************/
 
+static gboolean
+can_reapply_change(NMDevice   *device,
+                   const char *setting_name,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
+                   GHashTable *diffs,
+                   GError    **error)
+{
+    /* Delegate changes to other settings to parent class */
+    if (!nm_streq(setting_name, NM_SETTING_BRIDGE_SETTING_NAME)) {
+        return NM_DEVICE_CLASS(nm_device_bridge_parent_class)
+            ->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
+    }
+
+    return nm_device_hash_check_invalid_keys(diffs,
+                                             NM_SETTING_BRIDGE_SETTING_NAME,
+                                             error,
+                                             NM_SETTING_BRIDGE_STP,
+                                             NM_SETTING_BRIDGE_PRIORITY,
+                                             NM_SETTING_BRIDGE_FORWARD_DELAY,
+                                             NM_SETTING_BRIDGE_HELLO_TIME,
+                                             NM_SETTING_BRIDGE_MAX_AGE,
+                                             NM_SETTING_BRIDGE_AGEING_TIME,
+                                             NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,
+                                             NM_SETTING_BRIDGE_MULTICAST_HASH_MAX,
+                                             NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT,
+                                             NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL,
+                                             NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL,
+                                             NM_SETTING_BRIDGE_MULTICAST_SNOOPING,
+                                             NM_SETTING_BRIDGE_MULTICAST_ROUTER,
+                                             NM_SETTING_BRIDGE_MULTICAST_QUERIER,
+                                             NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL,
+                                             NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL,
+                                             NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL,
+                                             NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR,
+                                             NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT,
+                                             NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL,
+                                             NM_SETTING_BRIDGE_GROUP_ADDRESS,
+                                             NM_SETTING_BRIDGE_VLAN_PROTOCOL,
+                                             NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,
+                                             NM_SETTING_BRIDGE_VLAN_FILTERING,
+                                             NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,
+                                             NM_SETTING_BRIDGE_VLANS);
+}
+
+static void
+reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new)
+{
+    NMDeviceBridge  *self = NM_DEVICE_BRIDGE(device);
+    NMSettingBridge *s_bridge;
+
+    NM_DEVICE_CLASS(nm_device_bridge_parent_class)->reapply_connection(device, con_old, con_new);
+
+    _LOGD(LOGD_BRIDGE, "reapplying bridge settings");
+    s_bridge = nm_connection_get_setting_bridge(con_new);
+    g_return_if_fail(s_bridge);
+
+    /* Make sure bridge_set_vlan_options() called by link_config()
+     * sets vlan_filtering and default_pvid anew. */
+    self->vlan_configured = FALSE;
+
+    link_config(device, con_new);
+}
+
+/*****************************************************************************/
+
 static void
 nm_device_bridge_init(NMDeviceBridge *self)
 {
@@ -1183,9 +1259,11 @@ nm_device_bridge_class_init(NMDeviceBridgeClass *klass)
     device_class->act_stage1_prepare                     = act_stage1_prepare;
     device_class->act_stage2_config                      = act_stage2_config;
     device_class->deactivate                             = deactivate;
-    device_class->enslave_slave                          = enslave_slave;
-    device_class->release_slave                          = release_slave;
+    device_class->attach_port                            = attach_port;
+    device_class->detach_port                            = detach_port;
     device_class->get_configured_mtu                     = nm_device_get_configured_mtu_for_wired;
+    device_class->can_reapply_change                     = can_reapply_change;
+    device_class->reapply_connection                     = reapply_connection;
 }
 
 /*****************************************************************************/
diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index 8c95a293..63dfd8bb 100644
--- a/src/core/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -82,6 +82,8 @@ create_and_realize(NMDevice              *device,
                    GError               **error)
 {
     const char    *iface = nm_device_get_iface(device);
+    const char    *peer;
+    NMDevice      *peer_device;
     NMSettingVeth *s_veth;
     int            r;
 
@@ -96,10 +98,15 @@ create_and_realize(NMDevice              *device,
         return FALSE;
     }
 
-    r = nm_platform_link_veth_add(nm_device_get_platform(device),
-                                  iface,
-                                  nm_setting_veth_get_peer(s_veth),
-                                  out_plink);
+    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))
+            return TRUE;
+    }
+
+    r = nm_platform_link_veth_add(nm_device_get_platform(device), iface, peer, out_plink);
     if (r < 0) {
         g_set_error(error,
                     NM_DEVICE_ERROR,
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index ae80e1d4..2aef0e3d 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -206,38 +206,44 @@ update_connection(NMDevice *device, NMConnection *connection)
         g_object_set(G_OBJECT(s_vrf), NM_SETTING_VRF_TABLE, priv->props.table, NULL);
 }
 
-static gboolean
-enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+static NMTernary
+attach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            NMConnection              *connection,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
-    NMDeviceVrf *self        = NM_DEVICE_VRF(device);
-    gboolean     success     = TRUE;
-    const char  *slave_iface = nm_device_get_ip_iface(slave);
+    NMDeviceVrf *self       = NM_DEVICE_VRF(device);
+    gboolean     success    = TRUE;
+    const char  *port_iface = nm_device_get_ip_iface(port);
 
-    nm_device_master_check_slave_physical_port(device, slave, LOGD_DEVICE);
+    nm_device_master_check_slave_physical_port(device, port, LOGD_DEVICE);
 
     if (configure) {
-        nm_device_take_down(slave, TRUE);
+        nm_device_take_down(port, TRUE);
         success = nm_platform_link_enslave(nm_device_get_platform(device),
                                            nm_device_get_ip_ifindex(device),
-                                           nm_device_get_ip_ifindex(slave));
-        nm_device_bring_up(slave, TRUE, NULL);
+                                           nm_device_get_ip_ifindex(port));
+        nm_device_bring_up(port, TRUE, NULL);
 
         if (!success)
             return FALSE;
 
-        _LOGI(LOGD_DEVICE, "enslaved VRF slave %s", slave_iface);
+        _LOGI(LOGD_DEVICE, "attached VRF port %s", port_iface);
     } else
-        _LOGI(LOGD_BOND, "VRF slave %s was enslaved", slave_iface);
+        _LOGI(LOGD_BOND, "VRF port %s was attached", port_iface);
 
     return TRUE;
 }
 
 static void
-release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 {
     NMDeviceVrf *self = NM_DEVICE_VRF(device);
     gboolean     success;
-    int          ifindex_slave;
+    int          ifindex_port;
     int          ifindex;
 
     if (configure) {
@@ -246,26 +252,26 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
             configure = FALSE;
     }
 
-    ifindex_slave = nm_device_get_ip_ifindex(slave);
+    ifindex_port = nm_device_get_ip_ifindex(port);
 
-    if (ifindex_slave <= 0)
-        _LOGD(LOGD_DEVICE, "VRF slave %s is already released", nm_device_get_ip_iface(slave));
+    if (ifindex_port <= 0)
+        _LOGD(LOGD_DEVICE, "VRF port %s is already detached", nm_device_get_ip_iface(port));
 
     if (configure) {
-        if (ifindex_slave > 0) {
+        if (ifindex_port > 0) {
             success = nm_platform_link_release(nm_device_get_platform(device),
                                                nm_device_get_ip_ifindex(device),
-                                               ifindex_slave);
+                                               ifindex_port);
 
             if (success) {
-                _LOGI(LOGD_DEVICE, "released VRF slave %s", nm_device_get_ip_iface(slave));
+                _LOGI(LOGD_DEVICE, "detached VRF port %s", nm_device_get_ip_iface(port));
             } else {
-                _LOGW(LOGD_DEVICE, "failed to release VRF slave %s", nm_device_get_ip_iface(slave));
+                _LOGW(LOGD_DEVICE, "failed to detach VRF port %s", nm_device_get_ip_iface(port));
             }
         }
     } else {
-        if (ifindex_slave > 0) {
-            _LOGI(LOGD_DEVICE, "VRF slave %s was released", nm_device_get_ip_iface(slave));
+        if (ifindex_port > 0) {
+            _LOGI(LOGD_DEVICE, "VRF port %s was detached", nm_device_get_ip_iface(port));
         }
     }
 }
@@ -316,8 +322,8 @@ nm_device_vrf_class_init(NMDeviceVrfClass *klass)
     device_class->is_master                        = TRUE;
     device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_VRF);
 
-    device_class->enslave_slave               = enslave_slave;
-    device_class->release_slave               = release_slave;
+    device_class->attach_port                 = attach_port;
+    device_class->detach_port                 = detach_port;
     device_class->link_changed                = link_changed;
     device_class->unrealize_notify            = unrealize_notify;
     device_class->create_and_realize          = create_and_realize;
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index bdb96cb2..179c2a22 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -18,7 +18,7 @@
 #include "nm-device-private.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-object.h"
-#include "libnm-platform/nmp-route-manager.h"
+#include "libnm-platform/nmp-global-tracker.h"
 #include "nm-device-factory.h"
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 6b854517..c51da9ac 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -41,7 +41,7 @@
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nm-platform-utils.h"
 #include "libnm-platform/nmp-object.h"
-#include "libnm-platform/nmp-route-manager.h"
+#include "libnm-platform/nmp-global-tracker.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 
@@ -120,11 +120,12 @@ typedef enum _nm_packed {
 } AddrMethodState;
 
 typedef struct {
-    CList     lst_slave;
-    NMDevice *slave;
-    gulong    watch_id;
-    bool      slave_is_enslaved;
-    bool      configure;
+    CList         lst_slave;
+    NMDevice     *slave;
+    GCancellable *cancellable;
+    gulong        watch_id;
+    bool          slave_is_enslaved;
+    bool          configure;
 } SlaveInfo;
 
 typedef struct {
@@ -392,6 +393,8 @@ typedef struct _NMDevicePrivate {
     guint device_link_changed_id;
     guint device_ip_link_changed_id;
 
+    GSource *delay_activation_source;
+
     NMDeviceState       state;
     NMDeviceStateReason state_reason;
     struct {
@@ -604,6 +607,7 @@ typedef struct _NMDevicePrivate {
             const NMDeviceIPState state;
             NMDeviceIPState       state_;
         };
+        gulong dnsmgr_update_pending_signal_id;
     } ip_data;
 
     union {
@@ -1394,6 +1398,56 @@ _prop_get_connection_dns_over_tls(NMDevice *self)
                                                        NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT);
 }
 
+static NMMptcpFlags
+_prop_get_connection_mptcp_flags(NMDevice *self)
+{
+    NMConnection *connection;
+    NMMptcpFlags  mptcp_flags = NM_MPTCP_FLAGS_NONE;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), NM_MPTCP_FLAGS_DISABLED);
+
+    connection = nm_device_get_applied_connection(self);
+    if (connection) {
+        mptcp_flags =
+            nm_setting_connection_get_mptcp_flags(nm_connection_get_setting_connection(connection));
+        if (mptcp_flags != NM_MPTCP_FLAGS_NONE)
+            mptcp_flags = nm_mptcp_flags_normalize(mptcp_flags);
+    }
+
+    if (mptcp_flags == NM_MPTCP_FLAGS_NONE) {
+        guint64 v;
+
+        v = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                        NM_CON_DEFAULT("connection.mptcp-flags"),
+                                                        self,
+                                                        0,
+                                                        G_MAXINT64,
+                                                        NM_MPTCP_FLAGS_NONE);
+        /* We filter out all invalid settings and accept it. Somewhat intentionally, we don't do a
+         * strict parsing of the value to support forward compatibility. */
+        if (v != NM_MPTCP_FLAGS_NONE)
+            mptcp_flags = nm_mptcp_flags_normalize(v);
+    }
+
+    if (mptcp_flags == NM_MPTCP_FLAGS_NONE) {
+        gint32 v;
+
+        v = nm_platform_sysctl_get_int32(nm_device_get_platform(self),
+                                         NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/mptcp/enabled"),
+                                         -1);
+        if (v > 0) {
+            /* if MPTCP is enabled via the sysctl, we use the default. */
+            mptcp_flags = _NM_MPTCP_FLAGS_DEFAULT;
+        } else
+            mptcp_flags = NM_MPTCP_FLAGS_DISABLED;
+    }
+
+    nm_assert(mptcp_flags != NM_MPTCP_FLAGS_NONE
+              && mptcp_flags == nm_mptcp_flags_normalize(mptcp_flags));
+
+    return mptcp_flags;
+}
+
 static guint32
 _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 {
@@ -1508,6 +1562,53 @@ _prop_get_connection_lldp(NMDevice *self)
     return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
 }
 
+static NMSettingIP4LinkLocal
+_prop_get_ipv4_link_local(NMDevice *self)
+{
+    NMSettingIP4Config   *s_ip4;
+    NMSettingIP4LinkLocal link_local;
+
+    s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
+    if (!s_ip4)
+        return NM_SETTING_IP4_LL_DISABLED;
+
+    link_local = nm_setting_ip4_config_get_link_local(s_ip4);
+
+    if (link_local == NM_SETTING_IP4_LL_DEFAULT) {
+        /* For connections without a ipv4.link-local property configured the global configuration
+           might defines the default value for ipv4.link-local. */
+        link_local = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                                 NM_CON_DEFAULT("ipv4.link-local"),
+                                                                 self,
+                                                                 NM_SETTING_IP4_LL_AUTO,
+                                                                 NM_SETTING_IP4_LL_ENABLED,
+                                                                 NM_SETTING_IP4_LL_DEFAULT);
+        if (link_local == NM_SETTING_IP4_LL_DEFAULT) {
+            /* If there is no global configuration for ipv4.link-local assume auto */
+            link_local = NM_SETTING_IP4_LL_AUTO;
+        } else if (link_local == NM_SETTING_IP4_LL_ENABLED
+                   && nm_streq(nm_setting_ip_config_get_method((NMSettingIPConfig *) s_ip4),
+                               NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
+            /* ipv4.method=disabled has higher priority than the global ipv4.link-local=enabled */
+            link_local = NM_SETTING_IP4_LL_DISABLED;
+        } else if (link_local == NM_SETTING_IP4_LL_DISABLED
+                   && nm_streq(nm_setting_ip_config_get_method((NMSettingIPConfig *) s_ip4),
+                               NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+            /* ipv4.method=link-local has higher priority than the global ipv4.link-local=disabled */
+            link_local = NM_SETTING_IP4_LL_ENABLED;
+        }
+    }
+
+    if (link_local == NM_SETTING_IP4_LL_AUTO) {
+        link_local = nm_streq(nm_setting_ip_config_get_method((NMSettingIPConfig *) s_ip4),
+                              NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)
+                         ? NM_SETTING_IP4_LL_ENABLED
+                         : NM_SETTING_IP4_LL_DISABLED;
+    }
+
+    return link_local;
+}
+
 static guint32
 _prop_get_ipv4_dad_timeout(NMDevice *self)
 {
@@ -2118,6 +2219,50 @@ _prop_get_ipv6_ip6_privacy(NMDevice *self)
     return _ip6_privacy_clamp(ip6_privacy);
 }
 
+static NMSettingIP6ConfigAddrGenMode
+_prop_get_ipv6_addr_gen_mode(NMDevice *self)
+{
+    NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+    NMSettingIP6Config           *s_ip6;
+    gint64                        c;
+
+    g_return_val_if_fail(self, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY);
+
+    s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
+    if (s_ip6) {
+        addr_gen_mode = nm_setting_ip6_config_get_addr_gen_mode(s_ip6);
+        if (NM_IN_SET(addr_gen_mode,
+                      NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+                      NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY))
+            return addr_gen_mode;
+    } else
+        addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT;
+
+    nm_assert(NM_IN_SET(addr_gen_mode,
+                        NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64,
+                        NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT));
+
+    c = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                    NM_CON_DEFAULT("ipv6.addr-gen-mode"),
+                                                    self,
+                                                    NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+                                                    NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT,
+                                                    -1);
+    if (c != -1)
+        addr_gen_mode = c;
+
+    if (addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT)
+        addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY;
+    else if (addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64)
+        addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
+
+    nm_assert(NM_IN_SET(addr_gen_mode,
+                        NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+                        NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY));
+
+    return addr_gen_mode;
+}
+
 static const char *
 _prop_get_x_cloned_mac_address(NMDevice *self, NMConnection *connection, gboolean is_wifi)
 {
@@ -2678,6 +2823,7 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co
     nm_l3_config_data_set_llmnr(l3cd, _prop_get_connection_llmnr(self));
     nm_l3_config_data_set_dns_over_tls(l3cd, _prop_get_connection_dns_over_tls(self));
     nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self));
+    nm_l3_config_data_set_mptcp_flags(l3cd, _prop_get_connection_mptcp_flags(self));
     return l3cd;
 }
 
@@ -2930,6 +3076,13 @@ _add_capabilities(NMDevice *self, NMDeviceCapabilities capabilities)
 /*****************************************************************************/
 
 static void
+_dev_ip_state_dnsmgr_update_pending_changed(NMDnsManager *dnsmgr, GParamSpec *pspec, NMDevice *self)
+{
+    _dev_ip_state_check(self, AF_INET);
+    _dev_ip_state_check(self, AF_INET6);
+}
+
+static void
 _dev_ip_state_req_timeout_cancel(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
@@ -3321,6 +3474,27 @@ got_ip_state:
             combinedip_state = priv->ip_data.state;
     }
 
+    if (combinedip_state == NM_DEVICE_IP_STATE_READY
+        && priv->ip_data.state <= NM_DEVICE_IP_STATE_PENDING
+        && nm_dns_manager_get_update_pending(nm_manager_get_dns_manager(priv->manager))) {
+        /* We would be ready, but a DNS update is pending. That prevents us from getting fully ready. */
+        if (priv->ip_data.dnsmgr_update_pending_signal_id == 0) {
+            priv->ip_data.dnsmgr_update_pending_signal_id =
+                g_signal_connect(nm_manager_get_dns_manager(priv->manager),
+                                 "notify::" NM_DNS_MANAGER_UPDATE_PENDING,
+                                 G_CALLBACK(_dev_ip_state_dnsmgr_update_pending_changed),
+                                 self);
+            _LOGT_ip(AF_UNSPEC,
+                     "check-state: (combined) state: wait for DNS before becoming ready");
+        }
+        combinedip_state = NM_DEVICE_IP_STATE_PENDING;
+    }
+    if (combinedip_state != NM_DEVICE_IP_STATE_PENDING
+        && priv->ip_data.dnsmgr_update_pending_signal_id != 0) {
+        nm_clear_g_signal_handler(nm_manager_get_dns_manager(priv->manager),
+                                  &priv->ip_data.dnsmgr_update_pending_signal_id);
+    }
+
     _LOGT_ip(AF_UNSPEC,
              "check-state: (combined) state %s => %s",
              nm_device_ip_state_to_string(priv->ip_data.state),
@@ -5896,43 +6070,16 @@ find_slave_info(NMDevice *self, NMDevice *slave)
     return NULL;
 }
 
-/**
- * nm_device_master_enslave_slave:
- * @self: the master device
- * @slave: the slave device to enslave
- * @connection: (allow-none): the slave device's connection
- *
- * If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
- * etc) then this function enslaves @slave.
- *
- * Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave
- *  other devices.
- */
-static gboolean
-nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *connection)
+static void
+attach_port_done(NMDevice *self, NMDevice *slave, gboolean success)
 {
     SlaveInfo *info;
-    gboolean   success = FALSE;
-    gboolean   configure;
-
-    g_return_val_if_fail(self != NULL, FALSE);
-    g_return_val_if_fail(slave != NULL, FALSE);
-    g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE);
 
     info = find_slave_info(self, slave);
     if (!info)
-        return FALSE;
+        return;
 
-    if (info->slave_is_enslaved)
-        success = TRUE;
-    else {
-        configure = (info->configure && connection != NULL);
-        if (configure)
-            g_return_val_if_fail(nm_device_get_state(slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
-
-        success = NM_DEVICE_GET_CLASS(self)->enslave_slave(self, slave, connection, configure);
-        info->slave_is_enslaved = success;
-    }
+    info->slave_is_enslaved = success;
 
     nm_device_slave_notify_enslave(info->slave, success);
 
@@ -5952,8 +6099,71 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co
      */
     if (success)
         nm_device_activate_schedule_stage3_ip_config(self, FALSE);
+}
 
-    return success;
+static void
+attach_port_cb(NMDevice *self, GError *error, gpointer user_data)
+{
+    NMDevice  *slave = user_data;
+    SlaveInfo *info;
+
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    info = find_slave_info(self, slave);
+    if (!info)
+        return;
+
+    nm_clear_g_cancellable(&info->cancellable);
+    attach_port_done(self, slave, !error);
+}
+
+/**
+ * nm_device_master_enslave_slave:
+ * @self: the master device
+ * @slave: the slave device to enslave
+ * @connection: (allow-none): the slave device's connection
+ *
+ * If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
+ * etc) then this function enslaves @slave.
+ */
+static void
+nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *connection)
+{
+    SlaveInfo *info;
+    NMTernary  success;
+    gboolean   configure;
+
+    g_return_if_fail(self);
+    g_return_if_fail(slave);
+    g_return_if_fail(NM_DEVICE_GET_CLASS(self)->attach_port);
+
+    info = find_slave_info(self, slave);
+    if (!info)
+        return;
+
+    if (info->slave_is_enslaved)
+        success = TRUE;
+    else {
+        configure = (info->configure && connection != NULL);
+        if (configure)
+            g_return_if_fail(nm_device_get_state(slave) >= NM_DEVICE_STATE_DISCONNECTED);
+
+        nm_clear_g_cancellable(&info->cancellable);
+        info->cancellable = g_cancellable_new();
+        success           = NM_DEVICE_GET_CLASS(self)->attach_port(self,
+                                                         slave,
+                                                         connection,
+                                                         configure,
+                                                         info->cancellable,
+                                                         attach_port_cb,
+                                                         slave);
+
+        if (success == NM_TERNARY_DEFAULT)
+            return;
+    }
+
+    attach_port_done(self, slave, success);
 }
 
 /**
@@ -5986,7 +6196,7 @@ nm_device_master_release_slave(NMDevice           *self,
                         RELEASE_SLAVE_TYPE_NO_CONFIG,
                         RELEASE_SLAVE_TYPE_CONFIG,
                         RELEASE_SLAVE_TYPE_CONFIG_FORCE));
-    g_return_if_fail(NM_DEVICE_GET_CLASS(self)->release_slave != NULL);
+    g_return_if_fail(NM_DEVICE_GET_CLASS(self)->detach_port != NULL);
 
     info = find_slave_info(self, slave);
 
@@ -6007,13 +6217,14 @@ nm_device_master_release_slave(NMDevice           *self,
 
     g_return_if_fail(self == slave_priv->master);
     nm_assert(slave == info->slave);
+    nm_clear_g_cancellable(&info->cancellable);
 
     /* first, let subclasses handle the release ... */
     if (info->slave_is_enslaved || nm_device_sys_iface_state_is_external(slave)
         || release_type >= RELEASE_SLAVE_TYPE_CONFIG_FORCE)
-        NM_DEVICE_GET_CLASS(self)->release_slave(self,
-                                                 slave,
-                                                 release_type >= RELEASE_SLAVE_TYPE_CONFIG);
+        NM_DEVICE_GET_CLASS(self)->detach_port(self,
+                                               slave,
+                                               release_type >= RELEASE_SLAVE_TYPE_CONFIG);
 
     /* raise notifications about the release, including clearing is_enslaved. */
     nm_device_slave_notify_release(slave, reason);
@@ -6344,7 +6555,7 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
                                        NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
     }
 
-    if (master && NM_DEVICE_GET_CLASS(master)->enslave_slave) {
+    if (master && NM_DEVICE_GET_CLASS(master)->attach_port) {
         nm_device_master_add_slave(master, self, FALSE);
         goto out;
     }
@@ -7540,8 +7751,9 @@ slave_state_changed(NMDevice           *slave,
     }
 
     if (release) {
-        configure = priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED
-                    && nm_device_sys_iface_state_get(slave) != NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
+        configure = (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED
+                     && nm_device_sys_iface_state_get(slave) != NM_DEVICE_SYS_IFACE_STATE_EXTERNAL)
+                    || nm_device_sys_iface_state_get(slave) == NM_DEVICE_SYS_IFACE_STATE_MANAGED;
 
         nm_device_master_release_slave(self,
                                        slave,
@@ -7577,7 +7789,7 @@ nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure)
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
     g_return_val_if_fail(NM_IS_DEVICE(slave), FALSE);
-    g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE);
+    g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->attach_port, FALSE);
 
     priv       = NM_DEVICE_GET_PRIVATE(self);
     slave_priv = NM_DEVICE_GET_PRIVATE(slave);
@@ -7806,6 +8018,9 @@ nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
 
     g_return_if_fail(priv->master);
 
+    if (!priv->is_enslaved)
+        return;
+
     if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state <= NM_DEVICE_STATE_ACTIVATED) {
         switch (nm_device_state_reason_check(reason)) {
         case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
@@ -7835,14 +8050,12 @@ nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
     } else
         _LOGI(LOGD_DEVICE, "released from master device %s", nm_device_get_iface(priv->master));
 
-    if (priv->is_enslaved) {
-        priv->is_enslaved = FALSE;
+    priv->is_enslaved = FALSE;
 
-        _notify(self, PROP_MASTER);
+    _notify(self, PROP_MASTER);
 
-        nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->master)->ports_variant, g_variant_unref);
-        nm_gobject_notify_together(priv->master, PROP_PORTS, PROP_SLAVES);
-    }
+    nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->master)->ports_variant, g_variant_unref);
+    nm_gobject_notify_together(priv->master, PROP_PORTS, PROP_SLAVES);
 }
 
 /**
@@ -8161,13 +8374,13 @@ device_has_config(NMDevice *self)
 
     head_entry = nm_platform_lookup(
         nm_device_get_platform(self),
-        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, pllink->ifindex));
+        nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, pllink->ifindex));
     if (head_entry)
         return TRUE;
 
     head_entry = nm_platform_lookup(
         nm_device_get_platform(self),
-        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, pllink->ifindex));
+        nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, pllink->ifindex));
     if (head_entry)
         return TRUE;
 
@@ -9329,13 +9542,13 @@ lldp_setup(NMDevice *self, NMTernary enabled)
 static void
 _routing_rules_sync(NMDevice *self, NMTernary set_mode)
 {
-    NMDevicePrivate *priv               = NM_DEVICE_GET_PRIVATE(self);
-    NMPRouteManager *route_manager      = nm_netns_get_route_manager(nm_device_get_netns(self));
-    NMDeviceClass   *klass              = NM_DEVICE_GET_CLASS(self);
-    gboolean         untrack_only_dirty = FALSE;
-    gboolean         keep_deleted_rules;
-    gpointer         user_tag_1;
-    gpointer         user_tag_2;
+    NMDevicePrivate  *priv               = NM_DEVICE_GET_PRIVATE(self);
+    NMPGlobalTracker *global_tracker     = nm_netns_get_global_tracker(nm_device_get_netns(self));
+    NMDeviceClass    *klass              = NM_DEVICE_GET_CLASS(self);
+    gboolean          untrack_only_dirty = FALSE;
+    gboolean          keep_deleted_rules;
+    gpointer          user_tag_1;
+    gpointer          user_tag_2;
 
     /* take two arbitrary user-tag pointers that belong to @self. */
     user_tag_1 = &priv->v4_route_table;
@@ -9367,13 +9580,13 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
                 nm_ip_routing_rule_to_platform(rule, &plrule);
 
                 /* We track this rule, but we also make it explicitly not weakly-tracked
-                 * (meaning to untrack NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at
+                 * (meaning to untrack NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG at
                  * the same time). */
-                nmp_route_manager_track_rule(route_manager,
-                                             &plrule,
-                                             10,
-                                             user_tag_1,
-                                             NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+                nmp_global_tracker_track_rule(global_tracker,
+                                              &plrule,
+                                              10,
+                                              user_tag_1,
+                                              NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG);
             }
         }
 
@@ -9383,25 +9596,25 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
             extra_rules = klass->get_extra_rules(self);
             if (extra_rules) {
                 for (i = 0; i < extra_rules->len; i++) {
-                    nmp_route_manager_track_rule(
-                        route_manager,
+                    nmp_global_tracker_track_rule(
+                        global_tracker,
                         NMP_OBJECT_CAST_ROUTING_RULE(extra_rules->pdata[i]),
                         10,
                         user_tag_2,
-                        NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+                        NMP_GLOBAL_TRACKER_EXTERN_WEAKLY_TRACKED_USER_TAG);
                 }
             }
         }
     }
 
-    nmp_route_manager_untrack_all(route_manager, user_tag_1, !untrack_only_dirty, TRUE);
+    nmp_global_tracker_untrack_all(global_tracker, user_tag_1, !untrack_only_dirty, TRUE);
     if (klass->get_extra_rules)
-        nmp_route_manager_untrack_all(route_manager, user_tag_2, !untrack_only_dirty, TRUE);
+        nmp_global_tracker_untrack_all(global_tracker, user_tag_2, !untrack_only_dirty, TRUE);
 
     keep_deleted_rules = FALSE;
     if (set_mode == NM_TERNARY_DEFAULT) {
         /* when exiting NM, we leave the device up and the rules configured.
-         * We just call nmp_route_manager_sync() to forget about the synced rules,
+         * We just call nmp_global_tracker_sync() to forget about the synced rules,
          * but we don't actually delete them.
          *
          * FIXME: that is a problem after restart of NetworkManager, because these
@@ -9415,7 +9628,7 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
          * file and track them after restart again. */
         keep_deleted_rules = TRUE;
     }
-    nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, keep_deleted_rules);
+    nmp_global_tracker_sync(global_tracker, NMP_OBJECT_TYPE_ROUTING_RULE, keep_deleted_rules);
 }
 
 static gboolean
@@ -10045,6 +10258,7 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d
                                             FALSE);
 
         if (notify_data->lease_update.accepted) {
+            nm_manager_write_device_state(priv->manager, self, NULL);
             if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_READY) {
                 _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
                 nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4),
@@ -10190,7 +10404,11 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             .vendor_class_identifier = vendor_class_identifier,
             .use_fqdn                = hostname_is_fqdn,
             .reject_servers          = reject_servers,
-            .v4.request_broadcast    = request_broadcast,
+            .v4 =
+                {
+                    .request_broadcast = request_broadcast,
+                    .acd_timeout_msec  = _prop_get_ipv4_dad_timeout(self),
+                },
         };
 
         priv->ipdhcp_data_4.client =
@@ -10205,22 +10423,25 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
 
         config = (NMDhcpClientConfig){
-            .addr_family        = AF_INET6,
-            .l3cfg              = nm_device_get_l3cfg(self),
-            .iface              = nm_device_get_ip_iface(self),
-            .uuid               = nm_connection_get_uuid(connection),
-            .send_hostname      = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
-            .hostname           = nm_setting_ip_config_get_dhcp_hostname(s_ip),
-            .hostname_flags     = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
-            .client_id          = duid,
-            .mud_url            = _prop_get_connection_mud_url(self, s_con),
-            .timeout            = no_lease_timeout_sec,
-            .anycast_address    = _device_get_dhcp_anycast_address(self),
-            .v6.enforce_duid    = enforce_duid,
-            .v6.iaid            = iaid,
-            .v6.iaid_explicit   = iaid_explicit,
-            .v6.info_only       = (priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF),
-            .v6.needed_prefixes = priv->ipdhcp_data_6.v6.needed_prefixes,
+            .addr_family     = AF_INET6,
+            .l3cfg           = nm_device_get_l3cfg(self),
+            .iface           = nm_device_get_ip_iface(self),
+            .uuid            = nm_connection_get_uuid(connection),
+            .send_hostname   = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
+            .hostname        = nm_setting_ip_config_get_dhcp_hostname(s_ip),
+            .hostname_flags  = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
+            .client_id       = duid,
+            .mud_url         = _prop_get_connection_mud_url(self, s_con),
+            .timeout         = no_lease_timeout_sec,
+            .anycast_address = _device_get_dhcp_anycast_address(self),
+            .v6 =
+                {
+                    .enforce_duid  = enforce_duid,
+                    .iaid          = iaid,
+                    .iaid_explicit = iaid_explicit,
+                    .info_only     = (priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF),
+                    .needed_prefixes = priv->ipdhcp_data_6.v6.needed_prefixes,
+                },
         };
 
         priv->ipdhcp_data_6.client =
@@ -10635,7 +10856,6 @@ _dev_ipll6_start(NMDevice *self)
 {
     NMDevicePrivate       *priv = NM_DEVICE_GET_PRIVATE(self);
     NMConnection          *connection;
-    NMSettingIP6Config    *s_ip6 = NULL;
     gboolean               assume;
     const char            *ifname;
     NML3IPv6LLState        llstate;
@@ -10656,14 +10876,10 @@ _dev_ipll6_start(NMDevice *self)
     }
 
     connection = nm_device_get_applied_connection(self);
-    if (connection)
-        s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
 
     assume = nm_device_sys_iface_state_is_external_or_assume(self);
 
-    if (s_ip6
-        && nm_setting_ip6_config_get_addr_gen_mode(s_ip6)
-               == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
+    if (_prop_get_ipv6_addr_gen_mode(self) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
         NMUtilsStableType stable_type;
         const char       *stable_id;
 
@@ -10866,9 +11082,10 @@ _commit_mtu(NMDevice *self)
 {
     NMDevicePrivate      *priv   = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceMtuSource     source = NM_DEVICE_MTU_SOURCE_NONE;
+    NMSettingIPConfig    *s_ip6;
     const NML3ConfigData *l3cd;
     guint32               ip6_mtu_orig;
-    guint32               ip6_mtu;
+    guint32               ip6_mtu = 0;
     guint32               mtu_desired_orig;
     guint32               mtu_desired;
     guint32               mtu_plat;
@@ -10953,10 +11170,9 @@ _commit_mtu(NMDevice *self)
         }
     }
 
-    if (mtu_desired && mtu_desired < 1280) {
-        NMSettingIPConfig *s_ip6;
+    s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
 
-        s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
+    if (mtu_desired && mtu_desired < 1280) {
         if (s_ip6
             && !NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip6),
                              NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -10971,7 +11187,12 @@ _commit_mtu(NMDevice *self)
         }
     }
 
-    ip6_mtu = priv->ip6_mtu;
+    if (s_ip6)
+        ip6_mtu = nm_setting_ip6_config_get_mtu(NM_SETTING_IP6_CONFIG(s_ip6));
+
+    if (!ip6_mtu)
+        ip6_mtu = priv->ip6_mtu;
+
     if (!ip6_mtu && priv->mtu_source == NM_DEVICE_MTU_SOURCE_NONE) {
         /* initially, if the IPv6 MTU is not specified, grow it as large as the
          * link MTU @mtu_desired. Only exception is, if @mtu_desired is so small
@@ -11353,7 +11574,7 @@ _dev_ipac6_start(NMDevice *self)
             .ifname                       = nm_device_get_ip_iface(self),
             .stable_type                  = stable_type,
             .network_id                   = stable_id,
-            .addr_gen_mode                = nm_setting_ip6_config_get_addr_gen_mode(s_ip),
+            .addr_gen_mode                = _prop_get_ipv6_addr_gen_mode(self),
             .node_type                    = node_type,
             .max_addresses                = max_addresses,
             .router_solicitations         = router_solicitations,
@@ -11640,11 +11861,14 @@ activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family, const
         goto out_devip;
 
     if (IS_IPv4) {
+        if (_prop_get_ipv4_link_local(self) == NM_SETTING_IP4_LL_ENABLED)
+            _dev_ipll4_start(self);
+
         if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
             _dev_ipdhcpx_start(self, AF_INET);
-        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
-            _dev_ipll4_start(self);
-        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+            /* pass */
+        } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
             _dev_ipshared4_start(self);
         else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
             priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
@@ -12344,7 +12568,8 @@ delete_on_deactivate_check_and_schedule(NMDevice *self)
 static void
 _cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean from_reapply)
 {
-    const int IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
 
     _dev_ipsharedx_cleanup(self, addr_family);
 
@@ -12360,6 +12585,9 @@ _cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gbool
 
     _dev_ipmanual_cleanup(self);
 
+    nm_clear_g_signal_handler(nm_manager_get_dns_manager(priv->manager),
+                              &priv->ip_data.dnsmgr_update_pending_signal_id);
+
     _dev_ip_state_cleanup(self, AF_UNSPEC, from_reapply);
     _dev_ip_state_cleanup(self, addr_family, from_reapply);
 }
@@ -12479,12 +12707,15 @@ can_reapply_change(NMDevice   *self,
                                                  NM_SETTING_CONNECTION_STABLE_ID,
                                                  NM_SETTING_CONNECTION_AUTOCONNECT,
                                                  NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
+                                                 NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
                                                  NM_SETTING_CONNECTION_ZONE,
                                                  NM_SETTING_CONNECTION_METERED,
                                                  NM_SETTING_CONNECTION_LLDP,
                                                  NM_SETTING_CONNECTION_MDNS,
                                                  NM_SETTING_CONNECTION_LLMNR,
-                                                 NM_SETTING_CONNECTION_DNS_OVER_TLS);
+                                                 NM_SETTING_CONNECTION_DNS_OVER_TLS,
+                                                 NM_SETTING_CONNECTION_MPTCP_FLAGS,
+                                                 NM_SETTING_CONNECTION_WAIT_ACTIVATION_DELAY);
     }
 
     if (NM_IN_STRSET(setting_name,
@@ -12700,7 +12931,8 @@ check_and_reapply_connection(NMDevice     *self,
                 NM_SETTING_CONNECTION_LLDP,
                 NM_SETTING_CONNECTION_MDNS,
                 NM_SETTING_CONNECTION_LLMNR,
-                NM_SETTING_CONNECTION_DNS_OVER_TLS)) {
+                NM_SETTING_CONNECTION_DNS_OVER_TLS,
+                NM_SETTING_CONNECTION_MPTCP_FLAGS)) {
             priv->ip_data_4.do_reapply = TRUE;
             priv->ip_data_6.do_reapply = TRUE;
         }
@@ -13361,29 +13593,71 @@ _dispatcher_cleanup(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
+    nm_clear_g_source_inst(&priv->delay_activation_source);
+
     if (!priv->dispatcher.call_id)
         return FALSE;
 
     nm_dispatcher_call_cancel(g_steal_pointer(&priv->dispatcher.call_id));
     priv->dispatcher.post_state        = NM_DEVICE_STATE_UNKNOWN;
     priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
+
     return TRUE;
 }
 
 static void
-_dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_data)
+_queue_dispatcher_post_state(NMDevice *self)
 {
-    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    g_return_if_fail(call_id == priv->dispatcher.call_id);
-
-    priv->dispatcher.call_id = NULL;
     nm_device_queue_state(self, priv->dispatcher.post_state, priv->dispatcher.post_state_reason);
     priv->dispatcher.post_state        = NM_DEVICE_STATE_UNKNOWN;
     priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
 }
 
+static gboolean
+_wait_activation_delay_timeout(gpointer user_data)
+{
+    NMDevice        *self = user_data;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    nm_clear_g_source_inst(&priv->delay_activation_source);
+
+    _LOGD(LOGD_DEVICE, "finished waiting on activation delay");
+    _queue_dispatcher_post_state(self);
+
+    return G_SOURCE_REMOVE;
+}
+
+static void
+_dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_data)
+{
+    NMDevice            *self = NM_DEVICE(user_data);
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection        *conn;
+    NMSettingConnection *s_conn;
+    gint32               delay_timeout;
+
+    g_return_if_fail(call_id == priv->dispatcher.call_id);
+    nm_assert(!priv->delay_activation_source);
+
+    priv->dispatcher.call_id = NULL;
+    conn                     = nm_device_get_applied_connection(self);
+    if (conn) {
+        s_conn = nm_connection_get_setting_connection(conn);
+        if (s_conn) {
+            delay_timeout = nm_setting_connection_get_wait_activation_delay(s_conn);
+            if (delay_timeout > 0) {
+                priv->delay_activation_source =
+                    nm_g_timeout_add_source(delay_timeout, _wait_activation_delay_timeout, self);
+                return;
+            }
+        }
+    }
+
+    _queue_dispatcher_post_state(self);
+}
+
 /*****************************************************************************/
 
 static void
@@ -13437,14 +13711,14 @@ spawn_ping(NMDevice *self)
     gs_free char         *str_timeout = NULL;
     gs_free char         *tmp_str     = NULL;
     const char           *args[]      = {priv->gw_ping.binary,
-                          "-I",
-                          nm_device_get_ip_iface(self),
-                          "-c",
-                          "1",
-                          "-w",
-                          NULL,
-                          priv->gw_ping.address,
-                          NULL};
+                                         "-I",
+                                         nm_device_get_ip_iface(self),
+                                         "-c",
+                                         "1",
+                                         "-w",
+                                         NULL,
+                                         priv->gw_ping.address,
+                                         NULL};
     gs_free_error GError *error       = NULL;
     gboolean              ret;
 
@@ -13665,10 +13939,38 @@ _get_carrier_wait_ms(NMDevice *self)
                                                   CARRIER_WAIT_TIME_MS);
 }
 
+/*
+ * Devices that support carrier detect must be IFF_UP to report carrier
+ * changes; so after setting the device IFF_UP we must suppress startup
+ * complete (via a pending action) until either the carrier turns on, or
+ * a timeout is reached.
+ */
+static void
+carrier_detect_wait(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    gint64           now_ms, until_ms;
+
+    if (!nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT))
+        return;
+
+    /* we start a grace period of 5 seconds during which we will schedule
+     * a pending action whenever we have no carrier.
+     *
+     * If during that time carrier goes away, we declare the interface
+     * as not ready. */
+    nm_clear_g_source(&priv->carrier_wait_id);
+    if (!priv->carrier)
+        nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
+
+    now_ms   = nm_utils_get_monotonic_timestamp_msec();
+    until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms);
+    priv->carrier_wait_id = g_timeout_add(until_ms - now_ms, carrier_wait_timeout, self);
+}
+
 gboolean
 nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
 {
-    NMDevicePrivate     *priv         = NM_DEVICE_GET_PRIVATE(self);
     gboolean             device_is_up = FALSE;
     NMDeviceCapabilities capabilities;
     int                  ifindex;
@@ -13724,27 +14026,7 @@ nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
         capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self);
     _add_capabilities(self, capabilities);
 
-    /* Devices that support carrier detect must be IFF_UP to report carrier
-     * changes; so after setting the device IFF_UP we must suppress startup
-     * complete (via a pending action) until either the carrier turns on, or
-     * a timeout is reached.
-     */
-    if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT)) {
-        gint64 now_ms, until_ms;
-
-        /* we start a grace period of 5 seconds during which we will schedule
-         * a pending action whenever we have no carrier.
-         *
-         * If during that time carrier goes away, we declare the interface
-         * as not ready. */
-        nm_clear_g_source(&priv->carrier_wait_id);
-        if (!priv->carrier)
-            nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
-
-        now_ms   = nm_utils_get_monotonic_timestamp_msec();
-        until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms);
-        priv->carrier_wait_id = g_timeout_add(until_ms - now_ms, carrier_wait_timeout, self);
-    }
+    carrier_detect_wait(self);
 
     /* Can only get HW address of some devices when they are up */
     nm_device_update_hw_address(self);
@@ -14997,6 +15279,9 @@ _cancel_activation(NMDevice *self)
     _dispatcher_cleanup(self);
     ip_check_gw_ping_cleanup(self);
 
+    _dev_ip_state_cleanup(self, AF_INET, FALSE);
+    _dev_ip_state_cleanup(self, AF_INET6, FALSE);
+
     /* Break the activation chain */
     activation_source_clear(self);
 }
@@ -15500,6 +15785,13 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
                 if (!nm_device_bring_up(self, TRUE, &no_firmware) && no_firmware)
                     _LOGW(LOGD_PLATFORM, "firmware may be missing.");
                 nm_device_set_firmware_missing(self, no_firmware ? TRUE : FALSE);
+            } else {
+                /* We didn't bring the device up and we have little idea
+                 * when was it brought up. Play it safe and assume it could
+                 * have been brought up very recently and it might one of
+                 * those who take time to detect carrier.
+                 */
+                carrier_detect_wait(self);
             }
 
             /* Ensure the device gets deactivated in response to stuff like
@@ -16804,7 +17096,7 @@ get_address_for_hostname_dns_lookup(NMDevice *self, int addr_family)
      *   opposed to an address that is configured? */
     head_entry = nm_platform_lookup(
         nm_device_get_platform(self),
-        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex));
+        nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex));
 
     if (head_entry) {
         c_list_for_each_entry (iter, &head_entry->lst_entries_head, lst_entries) {
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index d59aecb2..382c8660 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -163,6 +163,7 @@ typedef enum {
 } NMDeviceCheckDevAvailableFlags;
 
 typedef void (*NMDeviceDeactivateCallback)(NMDevice *self, GError *error, gpointer user_data);
+typedef void (*NMDeviceAttachPortCallback)(NMDevice *self, GError *error, gpointer user_data);
 
 typedef struct _NMDeviceClass {
     NMDBusObjectClass parent;
@@ -373,12 +374,18 @@ typedef struct _NMDeviceClass {
                                                NMConnection *connection,
                                                GError      **error);
 
-    gboolean (*enslave_slave)(NMDevice     *self,
-                              NMDevice     *slave,
-                              NMConnection *connection,
-                              gboolean      configure);
-
-    void (*release_slave)(NMDevice *self, NMDevice *slave, gboolean configure);
+    /* Attachs a port asynchronously. Returns TRUE/FALSE on immediate
+     * success/error; in such cases, the callback is not invoked. If the
+     * action couldn't be completed immediately, DEFAULT is returned and
+     * the callback will always be invoked asynchronously. */
+    NMTernary (*attach_port)(NMDevice                  *self,
+                             NMDevice                  *port,
+                             NMConnection              *connection,
+                             gboolean                   configure,
+                             GCancellable              *cancellable,
+                             NMDeviceAttachPortCallback callback,
+                             gpointer                   user_data);
+    void (*detach_port)(NMDevice *self, NMDevice *port, gboolean configure);
 
     void (*parent_changed_notify)(NMDevice *self,
                                   int       old_ifindex,
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index ea77dd18..048afb02 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -78,20 +78,26 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
     nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
-static gboolean
-enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+static NMTernary
+attach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            NMConnection              *connection,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     if (!configure)
         return TRUE;
 
-    if (!NM_IS_DEVICE_OVS_PORT(slave))
+    if (!NM_IS_DEVICE_OVS_PORT(port))
         return FALSE;
 
     return TRUE;
 }
 
 static void
-release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 {}
 
 void
@@ -159,8 +165,8 @@ nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
     device_class->get_generic_capabilities            = get_generic_capabilities;
     device_class->act_stage3_ip_config                = act_stage3_ip_config;
     device_class->ready_for_ip_config                 = ready_for_ip_config;
-    device_class->enslave_slave                       = enslave_slave;
-    device_class->release_slave                       = release_slave;
+    device_class->attach_port                         = attach_port;
+    device_class->detach_port                         = detach_port;
     device_class->can_reapply_change_ovs_external_ids = TRUE;
     device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
 }
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index 6ba52f40..5510e39f 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -72,20 +72,42 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
     nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
+typedef struct {
+    NMDevice                  *device;
+    NMDevice                  *port;
+    GCancellable              *cancellable;
+    NMDeviceAttachPortCallback callback;
+    gpointer                   callback_user_data;
+} AttachPortData;
+
 static void
 add_iface_cb(GError *error, gpointer user_data)
 {
-    NMDevice *slave = user_data;
-
-    if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
-        nm_log_warn(LOGD_DEVICE,
-                    "device %s could not be added to a ovs port: %s",
-                    nm_device_get_iface(slave),
-                    error->message);
-        nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+    AttachPortData       *data = user_data;
+    NMDeviceOvsPort      *self;
+    gs_free_error GError *local = NULL;
+
+    if (g_cancellable_is_cancelled(data->cancellable)) {
+        local = nm_utils_error_new_cancelled(FALSE, NULL);
+        error = local;
+    } else if (error && !nm_utils_error_is_cancelled_or_disposing(error)) {
+        self = NM_DEVICE_OVS_PORT(data->device);
+        _LOGW(LOGD_DEVICE,
+              "device %s could not be added to a ovs port: %s",
+              nm_device_get_iface(data->port),
+              error->message);
+        nm_device_state_changed(data->port,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_OVSDB_FAILED);
     }
 
-    g_object_unref(slave);
+    data->callback(data->device, error, data->callback_user_data);
+
+    g_object_unref(data->device);
+    g_object_unref(data->port);
+    nm_clear_g_cancellable(&data->cancellable);
+
+    nm_g_slice_free(data);
 }
 
 static gboolean
@@ -115,14 +137,21 @@ set_mtu_cb(GError *error, gpointer user_data)
     g_object_unref(self);
 }
 
-static gboolean
-enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+static NMTernary
+attach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            NMConnection              *connection,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceOvsPort    *self      = NM_DEVICE_OVS_PORT(device);
     NMActiveConnection *ac_port   = NULL;
     NMActiveConnection *ac_bridge = NULL;
     NMDevice           *bridge_device;
     NMSettingWired     *s_wired;
+    AttachPortData     *data;
 
     if (!configure)
         return TRUE;
@@ -131,42 +160,49 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
     ac_bridge = nm_active_connection_get_master(ac_port);
     if (!ac_bridge) {
         _LOGW(LOGD_DEVICE,
-              "can't enslave %s: bridge active-connection not found",
-              nm_device_get_iface(slave));
+              "can't attach %s: bridge active-connection not found",
+              nm_device_get_iface(port));
         return FALSE;
     }
 
     bridge_device = nm_active_connection_get_device(ac_bridge);
     if (!bridge_device) {
-        _LOGW(LOGD_DEVICE, "can't enslave %s: bridge device not found", nm_device_get_iface(slave));
+        _LOGW(LOGD_DEVICE, "can't attach %s: bridge device not found", nm_device_get_iface(port));
         return FALSE;
     }
 
+    data  = g_slice_new(AttachPortData);
+    *data = (AttachPortData){
+        .device             = g_object_ref(device),
+        .port               = g_object_ref(port),
+        .cancellable        = g_object_ref(cancellable),
+        .callback           = callback,
+        .callback_user_data = user_data,
+    };
+
     nm_ovsdb_add_interface(nm_ovsdb_get(),
                            nm_active_connection_get_applied_connection(ac_bridge),
                            nm_device_get_applied_connection(device),
-                           nm_device_get_applied_connection(slave),
+                           nm_device_get_applied_connection(port),
                            bridge_device,
-                           slave,
+                           port,
                            add_iface_cb,
-                           g_object_ref(slave));
+                           data);
 
     /* DPDK ports does not have a link after the devbind, so the MTU must be
      * set on ovsdb after adding the interface. */
-    if (NM_IS_DEVICE_OVS_INTERFACE(slave) && _ovs_interface_is_dpdk(slave)) {
-        s_wired = nm_device_get_applied_setting(slave, NM_TYPE_SETTING_WIRED);
-
-        if (!s_wired || !nm_setting_wired_get_mtu(s_wired))
-            return TRUE;
-
-        nm_ovsdb_set_interface_mtu(nm_ovsdb_get(),
-                                   nm_device_get_ip_iface(slave),
-                                   nm_setting_wired_get_mtu(s_wired),
-                                   set_mtu_cb,
-                                   g_object_ref(slave));
+    if (NM_IS_DEVICE_OVS_INTERFACE(port) && _ovs_interface_is_dpdk(port)) {
+        s_wired = nm_device_get_applied_setting(port, NM_TYPE_SETTING_WIRED);
+        if (s_wired && nm_setting_wired_get_mtu(s_wired)) {
+            nm_ovsdb_set_interface_mtu(nm_ovsdb_get(),
+                                       nm_device_get_ip_iface(port),
+                                       nm_setting_wired_get_mtu(s_wired),
+                                       set_mtu_cb,
+                                       g_object_ref(port));
+        }
     }
 
-    return TRUE;
+    return NM_TERNARY_DEFAULT;
 }
 
 static void
@@ -186,31 +222,31 @@ del_iface_cb(GError *error, gpointer user_data)
 }
 
 static void
-release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 {
-    NMDeviceOvsPort *self              = NM_DEVICE_OVS_PORT(device);
-    bool             slave_not_managed = !NM_IN_SET(nm_device_sys_iface_state_get(slave),
-                                        NM_DEVICE_SYS_IFACE_STATE_MANAGED,
-                                        NM_DEVICE_SYS_IFACE_STATE_ASSUME);
+    NMDeviceOvsPort *self             = NM_DEVICE_OVS_PORT(device);
+    bool             port_not_managed = !NM_IN_SET(nm_device_sys_iface_state_get(port),
+                                       NM_DEVICE_SYS_IFACE_STATE_MANAGED,
+                                       NM_DEVICE_SYS_IFACE_STATE_ASSUME);
 
-    _LOGI(LOGD_DEVICE, "releasing ovs interface %s", nm_device_get_ip_iface(slave));
+    _LOGI(LOGD_DEVICE, "detaching ovs interface %s", nm_device_get_ip_iface(port));
 
     /* Even if the an interface's device has gone away (e.g. externally
      * removed and thus we're called with configure=FALSE), we still need
      * to make sure its OVSDB entry is gone.
      */
-    if (configure || slave_not_managed) {
+    if (configure || port_not_managed) {
         nm_ovsdb_del_interface(nm_ovsdb_get(),
-                               nm_device_get_iface(slave),
+                               nm_device_get_iface(port),
                                del_iface_cb,
-                               g_object_ref(slave));
+                               g_object_ref(port));
     }
 
     if (configure) {
         /* Open VSwitch is going to delete this one. We must ignore what happens
          * next with the interface. */
-        if (NM_IS_DEVICE_OVS_INTERFACE(slave))
-            nm_device_update_from_platform_link(slave, NULL);
+        if (NM_IS_DEVICE_OVS_INTERFACE(port))
+            nm_device_update_from_platform_link(port, NULL);
     }
 }
 
@@ -245,8 +281,8 @@ nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
     device_class->get_generic_capabilities            = get_generic_capabilities;
     device_class->act_stage3_ip_config                = act_stage3_ip_config;
     device_class->ready_for_ip_config                 = ready_for_ip_config;
-    device_class->enslave_slave                       = enslave_slave;
-    device_class->release_slave                       = release_slave;
+    device_class->attach_port                         = attach_port;
+    device_class->detach_port                         = detach_port;
     device_class->can_reapply_change_ovs_external_ids = TRUE;
     device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
 }
diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
index 5aaa5c01..ff2c7858 100644
--- a/src/core/devices/ovs/nm-ovs-factory.c
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -178,7 +178,8 @@ ovsdb_device_removed(NMOvsdb         *ovsdb,
     device_state = nm_device_get_state(device);
 
     if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_device_get_act_request(device)
-        && device_state < NM_DEVICE_STATE_DEACTIVATING) {
+        && (device_state > NM_DEVICE_STATE_DISCONNECTED
+            && device_state < NM_DEVICE_STATE_DEACTIVATING)) {
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_DEACTIVATING,
                                 NM_DEVICE_STATE_REASON_REMOVED);
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 44e16cb7..e7c96852 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -376,6 +376,9 @@ ovsdb_call_method(NMOvsdb                  *self,
     NMOvsdbPrivate  *priv = NM_OVSDB_GET_PRIVATE(self);
     OvsdbMethodCall *call;
 
+    /* FIXME(shutdown): this function should accept a cancellable to
+     * interrupt the operation. */
+
     /* Ensure we're not unsynchronized before we queue the method call. */
     ovsdb_try_connect(self);
 
@@ -1550,7 +1553,7 @@ _external_ids_to_string(const GArray *arr)
     if (!arr)
         return g_strdup("empty");
 
-    nm_str_buf_init(&strbuf, NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
+    strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
     nm_str_buf_append(&strbuf, "[");
     for (i = 0; i < arr->len; i++) {
         const NMUtilsNamedValue *n = &g_array_index(arr, NMUtilsNamedValue, i);
@@ -1584,7 +1587,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
     json_t         *items;
     json_t         *external_ids;
     json_error_t    json_error = {
-        0,
+           0,
     };
     void       *iter;
     const char *name;
@@ -1985,7 +1988,7 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
 {
     NMOvsdbPrivate *priv       = NM_OVSDB_GET_PRIVATE(self);
     json_error_t    json_error = {
-        0,
+           0,
     };
     json_t     *json_id = NULL;
     json_int_t  id      = (json_int_t) -1;
@@ -2127,7 +2130,7 @@ ovsdb_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
     gssize          size;
     json_t         *msg;
     json_error_t    json_error = {
-        0,
+           0,
     };
 
     size = g_input_stream_read_finish(stream, res, &error);
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index b67c7100..e6d34266 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -65,6 +65,50 @@ static gboolean teamd_start(NMDeviceTeam *self);
 
 /*****************************************************************************/
 
+static struct teamdctl *
+_tdc_connect_new(NMDeviceTeam *self, const char *iface, GError **error)
+{
+    NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
+    struct teamdctl     *tdc;
+    const char          *cli_type;
+    int                  r;
+
+    tdc = teamdctl_alloc();
+    if (!tdc) {
+        nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "failure to allocate teamdctl structure");
+        g_return_val_if_reached(NULL);
+    }
+
+    if (priv->teamd_dbus_watch)
+        cli_type = "dbus";
+    else if (priv->usock_monitor)
+        cli_type = "usock";
+    else
+        cli_type = NULL;
+
+again:
+    r = teamdctl_connect(tdc, iface, NULL, cli_type);
+    if (r != 0) {
+        _LOGD(LOGD_TEAM,
+              "failure to connect to teamdctl%s%s, err=%d",
+              NM_PRINT_FMT_QUOTED2(cli_type, " with cli_type=", cli_type, ""),
+              r);
+        if (cli_type) {
+            /* How odd. Let's retry with any CLI type. */
+            cli_type = NULL;
+            goto again;
+        }
+        teamdctl_free(tdc);
+        nm_utils_error_set(error,
+                           NM_UTILS_ERROR_UNKNOWN,
+                           "failure to connect to teamd (err=%d)",
+                           r);
+        return NULL;
+    }
+
+    return tdc;
+}
+
 static NMDeviceCapabilities
 get_generic_capabilities(NMDevice *device)
 {
@@ -96,21 +140,16 @@ complete_connection(NMDevice            *device,
 static gboolean
 ensure_teamd_connection(NMDevice *device)
 {
-    NMDeviceTeam        *self = NM_DEVICE_TEAM(device);
-    NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    int                  err;
+    NMDeviceTeam         *self  = NM_DEVICE_TEAM(device);
+    NMDeviceTeamPrivate  *priv  = NM_DEVICE_TEAM_GET_PRIVATE(self);
+    gs_free_error GError *error = NULL;
 
     if (priv->tdc)
         return TRUE;
 
-    priv->tdc = teamdctl_alloc();
-    g_assert(priv->tdc);
-    err = teamdctl_connect(priv->tdc, nm_device_get_iface(device), NULL, NULL);
-    if (err != 0) {
-        _LOGE(LOGD_TEAM, "failed to connect to teamd (err=%d)", err);
-        teamdctl_free(priv->tdc);
-        priv->tdc = NULL;
-    }
+    priv->tdc = _tdc_connect_new(self, nm_device_get_iface(device), &error);
+    if (!priv->tdc)
+        _LOGE(LOGD_TEAM, "failed to connect to teamd: %s", error->message);
 
     return !!priv->tdc;
 }
@@ -183,42 +222,31 @@ update_connection(NMDevice *device, NMConnection *connection)
 /*****************************************************************************/
 
 static gboolean
-master_update_slave_connection(NMDevice     *self,
+master_update_slave_connection(NMDevice     *device,
                                NMDevice     *slave,
                                NMConnection *connection,
                                GError      **error)
 {
-    NMSettingTeamPort *s_port;
-    char              *port_config = NULL;
-    int                err         = 0;
-    struct teamdctl   *tdc;
-    const char        *team_port_config = NULL;
-    const char        *iface            = nm_device_get_iface(self);
-    const char        *iface_slave      = nm_device_get_iface(slave);
-
-    tdc = teamdctl_alloc();
+    NMDeviceTeam         *self = NM_DEVICE_TEAM(device);
+    NMSettingTeamPort    *s_port;
+    char                 *port_config   = NULL;
+    gs_free_error GError *connect_error = NULL;
+    int                   err           = 0;
+    struct teamdctl      *tdc;
+    const char           *team_port_config = NULL;
+    const char           *iface            = nm_device_get_iface(device);
+    const char           *iface_slave      = nm_device_get_iface(slave);
+
+    tdc = _tdc_connect_new(self, iface, &connect_error);
     if (!tdc) {
         g_set_error(error,
                     NM_DEVICE_ERROR,
                     NM_DEVICE_ERROR_FAILED,
                     "update slave connection for slave '%s' failed to connect to teamd for master "
-                    "%s (out of memory?)",
-                    iface_slave,
-                    iface);
-        g_return_val_if_reached(FALSE);
-    }
-
-    err = teamdctl_connect(tdc, iface, NULL, NULL);
-    if (err) {
-        teamdctl_free(tdc);
-        g_set_error(error,
-                    NM_DEVICE_ERROR,
-                    NM_DEVICE_ERROR_FAILED,
-                    "update slave connection for slave '%s' failed to connect to teamd for master "
-                    "%s (err=%d)",
+                    "%s (%s)",
                     iface_slave,
                     iface,
-                    err);
+                    connect_error->message);
         return FALSE;
     }
 
@@ -790,19 +818,25 @@ deactivate(NMDevice *device)
     teamd_cleanup(self, TRUE);
 }
 
-static gboolean
-enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+static NMTernary
+attach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            NMConnection              *connection,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
-    NMDeviceTeam        *self        = NM_DEVICE_TEAM(device);
-    NMDeviceTeamPrivate *priv        = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    gboolean             success     = TRUE;
-    const char          *slave_iface = nm_device_get_ip_iface(slave);
+    NMDeviceTeam        *self       = NM_DEVICE_TEAM(device);
+    NMDeviceTeamPrivate *priv       = NM_DEVICE_TEAM_GET_PRIVATE(self);
+    gboolean             success    = TRUE;
+    const char          *port_iface = nm_device_get_ip_iface(port);
     NMSettingTeamPort   *s_team_port;
 
-    nm_device_master_check_slave_physical_port(device, slave, LOGD_TEAM);
+    nm_device_master_check_slave_physical_port(device, port, LOGD_TEAM);
 
     if (configure) {
-        nm_device_take_down(slave, TRUE);
+        nm_device_take_down(port, TRUE);
 
         s_team_port = nm_connection_get_setting_team_port(connection);
         if (s_team_port) {
@@ -811,19 +845,19 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
             if (config) {
                 if (!priv->tdc) {
                     _LOGW(LOGD_TEAM,
-                          "enslaved team port %s config not changed, not connected to teamd",
-                          slave_iface);
+                          "attached team port %s config not changed, not connected to teamd",
+                          port_iface);
                 } else {
                     gs_free char *sanitized_config = NULL;
                     int           err;
 
                     sanitized_config = g_strdup(config);
                     g_strdelimit(sanitized_config, "\r\n", ' ');
-                    err = teamdctl_port_config_update_raw(priv->tdc, slave_iface, sanitized_config);
+                    err = teamdctl_port_config_update_raw(priv->tdc, port_iface, sanitized_config);
                     if (err != 0) {
                         _LOGE(LOGD_TEAM,
                               "failed to update config for port %s (err=%d)",
-                              slave_iface,
+                              port_iface,
                               err);
                         return FALSE;
                     }
@@ -832,8 +866,8 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
         }
         success = nm_platform_link_enslave(nm_device_get_platform(device),
                                            nm_device_get_ip_ifindex(device),
-                                           nm_device_get_ip_ifindex(slave));
-        nm_device_bring_up(slave, TRUE, NULL);
+                                           nm_device_get_ip_ifindex(port));
+        nm_device_bring_up(port, TRUE, NULL);
 
         if (!success)
             return FALSE;
@@ -841,21 +875,21 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
         nm_clear_g_source(&priv->teamd_read_timeout);
         priv->teamd_read_timeout = g_timeout_add_seconds(5, teamd_read_timeout_cb, self);
 
-        _LOGI(LOGD_TEAM, "enslaved team port %s", slave_iface);
+        _LOGI(LOGD_TEAM, "attached team port %s", port_iface);
     } else
-        _LOGI(LOGD_TEAM, "team port %s was enslaved", slave_iface);
+        _LOGI(LOGD_TEAM, "team port %s was attached", port_iface);
 
     return TRUE;
 }
 
 static void
-release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
+detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 {
     NMDeviceTeam        *self = NM_DEVICE_TEAM(device);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
     gboolean             do_release, success;
     NMSettingTeamPort   *s_port;
-    int                  ifindex_slave;
+    int                  ifindex_port;
     int                  ifindex;
 
     do_release = configure;
@@ -865,39 +899,39 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
             do_release = FALSE;
     }
 
-    ifindex_slave = nm_device_get_ip_ifindex(slave);
+    ifindex_port = nm_device_get_ip_ifindex(port);
 
-    if (ifindex_slave <= 0) {
-        _LOGD(LOGD_TEAM, "team port %s is already released", nm_device_get_ip_iface(slave));
+    if (ifindex_port <= 0) {
+        _LOGD(LOGD_TEAM, "team port %s is already detached", nm_device_get_ip_iface(port));
     } else if (do_release) {
         success = nm_platform_link_release(nm_device_get_platform(device),
                                            nm_device_get_ip_ifindex(device),
-                                           ifindex_slave);
+                                           ifindex_port);
         if (success)
-            _LOGI(LOGD_TEAM, "released team port %s", nm_device_get_ip_iface(slave));
+            _LOGI(LOGD_TEAM, "detached team port %s", nm_device_get_ip_iface(port));
         else
-            _LOGW(LOGD_TEAM, "failed to release team port %s", nm_device_get_ip_iface(slave));
+            _LOGW(LOGD_TEAM, "failed to detach team port %s", nm_device_get_ip_iface(port));
 
         /* Kernel team code "closes" the port when releasing it, (which clears
          * IFF_UP), so we must bring it back up here to ensure carrier changes and
          * other state is noticed by the now-released port.
          */
-        if (!nm_device_bring_up(slave, TRUE, NULL)) {
+        if (!nm_device_bring_up(port, TRUE, NULL)) {
             _LOGW(LOGD_TEAM,
-                  "released team port %s could not be brought up",
-                  nm_device_get_ip_iface(slave));
+                  "detached team port %s could not be brought up",
+                  nm_device_get_ip_iface(port));
         }
 
         nm_clear_g_source(&priv->teamd_read_timeout);
         priv->teamd_read_timeout = g_timeout_add_seconds(5, teamd_read_timeout_cb, self);
     } else
-        _LOGI(LOGD_TEAM, "team port %s was released", nm_device_get_ip_iface(slave));
+        _LOGI(LOGD_TEAM, "team port %s was detached", nm_device_get_ip_iface(port));
 
     /* Delete any port configuration we previously set */
     if (configure && priv->tdc
-        && (s_port = nm_device_get_applied_setting(slave, NM_TYPE_SETTING_TEAM_PORT))
+        && (s_port = nm_device_get_applied_setting(port, NM_TYPE_SETTING_TEAM_PORT))
         && (nm_setting_team_port_get_config(s_port)))
-        teamdctl_port_config_update_raw(priv->tdc, nm_device_get_ip_iface(slave), "{}");
+        teamdctl_port_config_update_raw(priv->tdc, nm_device_get_ip_iface(port), "{}");
 }
 
 static gboolean
@@ -1064,8 +1098,8 @@ nm_device_team_class_init(NMDeviceTeamClass *klass)
     device_class->act_stage1_prepare                             = act_stage1_prepare;
     device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
     device_class->deactivate         = deactivate;
-    device_class->enslave_slave      = enslave_slave;
-    device_class->release_slave      = release_slave;
+    device_class->attach_port        = attach_port;
+    device_class->detach_port        = detach_port;
 
     obj_properties[PROP_CONFIG] = g_param_spec_string(NM_DEVICE_TEAM_CONFIG,
                                                       "",
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 04608d14..39c68d77 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -3180,21 +3180,21 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
         gsize         ssid_len;
         const guint8 *ssid_data;
         const guint8  random_seed[16] = {0x9a,
-                                        0xdc,
-                                        0x86,
-                                        0x9a,
-                                        0xa8,
-                                        0xa2,
-                                        0x07,
-                                        0x97,
-                                        0xbe,
-                                        0x6d,
-                                        0xe6,
-                                        0x99,
-                                        0x9f,
-                                        0xa8,
-                                        0x09,
-                                        0x2b};
+                                         0xdc,
+                                         0x86,
+                                         0x9a,
+                                         0xa8,
+                                         0xa2,
+                                         0x07,
+                                         0x97,
+                                         0xbe,
+                                         0x6d,
+                                         0xe6,
+                                         0x99,
+                                         0x9f,
+                                         0xa8,
+                                         0x09,
+                                         0x2b};
 
         /* Calculate a stable "random" number based on the SSID. */
         ssid      = nm_setting_wireless_get_ssid(s_wifi);
diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index 69d365e3..a52696ea 100644
--- a/src/core/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -412,8 +412,8 @@ test_ap_wpa_psk_connection_base(const char   *key_mgmt,
     const char   *ssid        = "blahblah";
     const char   *bssid       = "01:02:03:04:05:06";
     const KeyData exp_wifi[]  = {{NM_SETTING_WIRELESS_SSID, ssid, 0},
-                                {NM_SETTING_WIRELESS_MODE, "infrastructure", 0},
-                                {NULL}};
+                                 {NM_SETTING_WIRELESS_MODE, "infrastructure", 0},
+                                 {NULL}};
     const KeyData both_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0},
                                  {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0},
                                  {NM_SETTING_WIRELESS_SECURITY_PSK, "asdfasdfasdfasdfasdfafs", 0},
@@ -541,8 +541,8 @@ test_ap_wpa_eap_connection_base(const char *key_mgmt,
     const char   *bssid       = "01:02:03:04:05:06";
     const KeyData src_empty[] = {{NULL}};
     const KeyData src_wsec[]  = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0},
-                                {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0},
-                                {NULL}};
+                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0},
+                                 {NULL}};
     gboolean      success;
     GError       *error = NULL;
 
@@ -778,12 +778,12 @@ test_priv_ap_leap_connection_1(gconstpointer add_wifi)
     const char   *bssid         = "01:02:03:04:05:06";
     const char   *leap_username = "Bill Smith";
     const KeyData src_wsec[]    = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
-                                {NULL}};
+                                   {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
+                                   {NULL}};
     const KeyData exp_wsec[]    = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
-                                {NULL}};
+                                   {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0},
+                                   {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
+                                   {NULL}};
     gboolean      success;
     GError       *error = NULL;
 
@@ -862,15 +862,15 @@ test_priv_ap_dynamic_wep_1(void)
     const char   *ssid         = "blahblah";
     const char   *bssid        = "01:02:03:04:05:06";
     const KeyData src_wsec[]   = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
-                                {NULL}};
+                                  {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
+                                  {NULL}};
     const KeyData both_8021x[] = {{NM_SETTING_802_1X_EAP, "peap", 0},
                                   {NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0},
                                   {NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0},
                                   {NULL}};
     const KeyData exp_wsec[]   = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
-                                {NULL}};
+                                  {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
+                                  {NULL}};
     gboolean      success;
     GError       *error = NULL;
 
@@ -917,8 +917,8 @@ test_priv_ap_dynamic_wep_2(void)
                                   {NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0},
                                   {NULL}};
     const KeyData exp_wsec[]   = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
-                                {NULL}};
+                                  {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
+                                  {NULL}};
     gboolean      success;
     GError       *error = NULL;
 
@@ -1130,8 +1130,8 @@ test_wpa_ap_leap_connection_1(gconstpointer data)
     const char   *bssid         = "01:02:03:04:05:06";
     const char   *leap_username = "Bill Smith";
     const KeyData src_wsec[]    = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
-                                {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
-                                {NULL}};
+                                   {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
+                                   {NULL}};
     gboolean      success;
     GError       *error = NULL;
 
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index 0db30046..c003880e 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -805,7 +805,7 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("invalid 'Netmask': %s", s ?: "");
         goto out;
     }
-    address.plen = nm_utils_ip4_netmask_to_prefix(address_network);
+    address.plen = _nm_utils_ip4_netmask_to_prefix(address_network);
 
     _LOGI("Address: %s", nm_platform_ip4_address_to_string(&address, sbuf, sizeof(sbuf)));
     nm_l3_config_data_add_address_4(priv->l3cd_4, &address);