about summary refs log tree commit diff
path: root/src/core/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2025-02-12 13:46:50 +0100
committerMichael Biebl <biebl@debian.org>2025-02-12 13:46:50 +0100
commit8bdf070ff046f482f6eb5e2b15ebc216f5d1e3da (patch)
treef706478d189d54c6532e8863d4b0d5ff5575af60 /src/core/devices
parent818258cf34b83fbc754633295e1052d4752d7b15 (diff)
New upstream version 1.51.90 upstream/1.51.90
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c5
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.c10
-rw-r--r--src/core/devices/bluetooth/nm-bluez5-dun.c4
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c3
-rw-r--r--src/core/devices/nm-device-6lowpan.c29
-rw-r--r--src/core/devices/nm-device-bond.c7
-rw-r--r--src/core/devices/nm-device-bridge.c33
-rw-r--r--src/core/devices/nm-device-dummy.c16
-rw-r--r--src/core/devices/nm-device-ethernet.c8
-rw-r--r--src/core/devices/nm-device-factory.c3
-rw-r--r--src/core/devices/nm-device-factory.h12
-rw-r--r--src/core/devices/nm-device-infiniband.c26
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c31
-rw-r--r--src/core/devices/nm-device-ipvlan.c467
-rw-r--r--src/core/devices/nm-device-ipvlan.h31
-rw-r--r--src/core/devices/nm-device-loopback.c16
-rw-r--r--src/core/devices/nm-device-macsec.c33
-rw-r--r--src/core/devices/nm-device-macvlan.c41
-rw-r--r--src/core/devices/nm-device-ppp.c19
-rw-r--r--src/core/devices/nm-device-private.h4
-rw-r--r--src/core/devices/nm-device-tun.c3
-rw-r--r--src/core/devices/nm-device-utils.c2
-rw-r--r--src/core/devices/nm-device-vlan.c37
-rw-r--r--src/core/devices/nm-device-vrf.c5
-rw-r--r--src/core/devices/nm-device-vxlan.c29
-rw-r--r--src/core/devices/nm-device-wireguard.c20
-rw-r--r--src/core/devices/nm-device-wpan.c3
-rw-r--r--src/core/devices/nm-device.c712
-rw-r--r--src/core/devices/nm-lldp-listener.c10
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c2
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c4
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c22
-rw-r--r--src/core/devices/team/nm-device-team.c3
-rw-r--r--src/core/devices/wifi/nm-device-iwd-p2p.c3
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c15
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c3
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c3
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c5
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c16
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c41
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c4
-rw-r--r--src/core/devices/wwan/nm-modem.c2
42 files changed, 1262 insertions, 480 deletions
diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index ba605077..a6dc6326 100644
--- a/src/core/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -117,8 +117,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("ADSL connection"),
                               NULL,
-                              NULL,
-                              FALSE); /* No IPv6 yet by default */
+                              NULL);
     return TRUE;
 }
 
@@ -494,7 +493,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             _LOGD(LOGD_ADSL, "starting PPPoA");
         }
 
-        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig){
+        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig) {
                                              .netns         = nm_device_get_netns(device),
                                              .parent_iface  = ppp_iface,
                                              .callback      = _ppp_mgr_callback,
diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c
index ac26ede8..3088cd6f 100644
--- a/src/core/devices/bluetooth/nm-bluez-manager.c
+++ b/src/core/devices/bluetooth/nm-bluez-manager.c
@@ -334,7 +334,7 @@ _bz_dbus_obj_new(NMBluezManager *self, const char *object_path)
     l = strlen(object_path) + 1;
 
     bzobj  = g_malloc(sizeof(BzDBusObj) + l);
-    *bzobj = (BzDBusObj){
+    *bzobj = (BzDBusObj) {
         .object_path                           = bzobj->_object_path_intern,
         .self                                  = self,
         .x_network_server.lst                  = C_LIST_INIT(bzobj->x_network_server.lst),
@@ -751,7 +751,7 @@ _conn_data_head_new(NMBluetoothCapabilities bt_type, const char *bdaddr)
 
     l         = strlen(bdaddr) + 1;
     cdata_hd  = g_malloc(sizeof(ConnDataHead) + l);
-    *cdata_hd = (ConnDataHead){
+    *cdata_hd = (ConnDataHead) {
         .bdaddr   = cdata_hd->bdaddr_data,
         .lst_head = C_LIST_INIT(cdata_hd->lst_head),
         .bt_type  = bt_type,
@@ -1143,7 +1143,7 @@ _network_server_vt_register_bridge(const NMBtVTableNetworkServer *vtable,
           bzobj->d_adapter.address);
 
     r_req_data  = g_slice_new(NetworkServerRegisterReqData);
-    *r_req_data = (NetworkServerRegisterReqData){
+    *r_req_data = (NetworkServerRegisterReqData) {
         .int_cancellable    = g_cancellable_new(),
         .ext_cancellable    = g_object_ref(cancellable),
         .callback           = callback,
@@ -2749,7 +2749,7 @@ nm_bluez_manager_connect(NMBluezManager         *self,
     }
 
     c_req_data  = g_slice_new(DeviceConnectReqData);
-    *c_req_data = (DeviceConnectReqData){
+    *c_req_data = (DeviceConnectReqData) {
         .int_cancellable    = g_steal_pointer(&int_cancellable),
         .ext_cancellable    = g_object_ref(cancellable),
         .callback           = callback,
@@ -2814,7 +2814,7 @@ nm_bluez_manager_init(NMBluezManager *self)
 {
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
 
-    priv->vtable_network_server = (NMBtVTableNetworkServer){
+    priv->vtable_network_server = (NMBtVTableNetworkServer) {
         .is_available      = _network_server_vt_is_available,
         .register_bridge   = _network_server_vt_register_bridge,
         .unregister_bridge = _network_server_vt_unregister_bridge,
diff --git a/src/core/devices/bluetooth/nm-bluez5-dun.c b/src/core/devices/bluetooth/nm-bluez5-dun.c
index 426bab0c..08d37c27 100644
--- a/src/core/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/core/devices/bluetooth/nm-bluez5-dun.c
@@ -683,7 +683,7 @@ nm_bluez5_dun_connect(const char                  *adapter,
     dst_l = strlen(remote) + 1;
 
     cdat  = g_slice_new(ConnectData);
-    *cdat = (ConnectData){
+    *cdat = (ConnectData) {
         .callback              = callback,
         .callback_user_data    = callback_user_data,
         .cancellable           = g_object_ref(cancellable),
@@ -691,7 +691,7 @@ nm_bluez5_dun_connect(const char                  *adapter,
     };
 
     context  = g_malloc(sizeof(NMBluez5DunContext) + src_l + dst_l);
-    *context = (NMBluez5DunContext){
+    *context = (NMBluez5DunContext) {
         .cdat                        = cdat,
         .notify_tty_hangup_cb        = notify_tty_hangup_cb,
         .notify_tty_hangup_user_data = notify_tty_hangup_user_data,
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index ce110aa0..4406bcf4 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -404,8 +404,7 @@ complete_connection(NMDevice            *device,
                               preferred,
                               fallback_prefix,
                               NULL,
-                              NULL,
-                              is_dun ? FALSE : TRUE); /* No IPv6 yet for DUN */
+                              NULL);
 
     setting_bdaddr = nm_setting_bluetooth_get_bdaddr(s_bt);
     if (setting_bdaddr) {
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index 78ec634c..3dabcb9b 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -161,8 +161,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("6LOWPAN connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN));
     if (!s_6lowpan) {
@@ -276,27 +275,10 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL);
 
     s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN));
-    g_assert(s_6lowpan);
-
-    return nm_setting_6lowpan_get_parent(s_6lowpan);
-}
-
-static char *
-get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
-{
-    NMSetting6Lowpan *s_6lowpan;
-    const char       *ifname;
-
-    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL);
-
-    s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN));
-    g_assert(s_6lowpan);
-
-    if (!parent_iface)
+    if (s_6lowpan)
+        return nm_setting_6lowpan_get_parent(s_6lowpan);
+    else
         return NULL;
-
-    ifname = nm_connection_get_interface_name(connection);
-    return g_strdup(ifname);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
@@ -306,5 +288,4 @@ NM_DEVICE_FACTORY_DEFINE_INTERNAL(
     NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_6LOWPAN)
         NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_6LOWPAN_SETTING_NAME),
     factory_class->create_device         = create_device;
-    factory_class->get_connection_parent = get_connection_parent;
-    factory_class->get_connection_iface  = get_connection_iface;);
+    factory_class->get_connection_parent = get_connection_parent;);
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 3ab17aff..53b32466 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -94,8 +94,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("Bond connection"),
                               "bond",
-                              NULL,
-                              TRUE);
+                              NULL);
 
     _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND);
 
@@ -436,7 +435,7 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
 #define _v_u32(s_bond, opt)      _nm_setting_bond_opt_value_as_u32((s_bond), (opt))
 #define _v_intbool(s_bond, opt)  _nm_setting_bond_opt_value_as_intbool((s_bond), (opt))
 
-    *props = (NMPlatformLnkBond){
+    *props = (NMPlatformLnkBond) {
         .mode      = _v_fcn(_nm_setting_bond_mode_from_string, s_bond, NM_SETTING_BOND_OPTION_MODE),
         .primary   = _setting_bond_primary_opt_as_ifindex(s_bond),
         .miimon    = _v_u32(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
@@ -681,7 +680,7 @@ commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_
     nm_platform_link_change(nm_device_get_platform(port),
                             nm_device_get_ifindex(port),
                             NULL,
-                            &((NMPlatformLinkBondPort){
+                            &((NMPlatformLinkBondPort) {
                                 .queue_id = s_port ? nm_setting_bond_port_get_queue_id(s_port)
                                                    : NM_BOND_PORT_QUEUE_ID_DEF,
                                 .prio     = prio_has ? prio : 0,
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index a237c0b7..7c34fde0 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -164,8 +164,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("Bridge connection"),
                               "bridge",
-                              NULL,
-                              TRUE);
+                              NULL);
 
     _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BRIDGE);
 
@@ -439,7 +438,7 @@ setting_vlans_to_platform(GPtrArray *array, guint *out_len)
 
         nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end);
 
-        arr[i] = (NMPlatformBridgeVlan){
+        arr[i] = (NMPlatformBridgeVlan) {
             .vid_start = vid_start,
             .vid_end   = vid_end,
             .pvid      = nm_bridge_vlan_is_pvid(vlan),
@@ -468,7 +467,7 @@ commit_port_options(NMDevice *device, NMSettingBridgePort *setting)
                             nm_device_get_ifindex(device),
                             NULL,
                             NULL,
-                            &((NMPlatformLinkBridgePort){
+                            &((NMPlatformLinkBridgePort) {
                                 .path_cost = path_cost,
                                 .priority  = priority,
                                 .hairpin   = nm_setting_bridge_port_get_hairpin_mode(setting),
@@ -660,10 +659,10 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is
         nm_platform_link_set_bridge_info(
             plat,
             ifindex,
-            &((NMPlatformLinkSetBridgeInfoData){.vlan_filtering_has    = TRUE,
-                                                .vlan_filtering_val    = FALSE,
-                                                .vlan_default_pvid_has = TRUE,
-                                                .vlan_default_pvid_val = 1}));
+            &((NMPlatformLinkSetBridgeInfoData) {.vlan_filtering_has    = TRUE,
+                                                 .vlan_filtering_val    = FALSE,
+                                                 .vlan_default_pvid_has = TRUE,
+                                                 .vlan_default_pvid_val = 1}));
         nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL, 0);
         return TRUE;
     }
@@ -690,10 +689,10 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is
         nm_platform_link_set_bridge_info(
             plat,
             ifindex,
-            &((NMPlatformLinkSetBridgeInfoData){.vlan_filtering_has    = TRUE,
-                                                .vlan_filtering_val    = FALSE,
-                                                .vlan_default_pvid_has = TRUE,
-                                                .vlan_default_pvid_val = 0}));
+            &((NMPlatformLinkSetBridgeInfoData) {.vlan_filtering_has    = TRUE,
+                                                 .vlan_filtering_val    = FALSE,
+                                                 .vlan_default_pvid_has = TRUE,
+                                                 .vlan_default_pvid_val = 0}));
 
         /* Clear all existing VLANs */
         if (!nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL, 0))
@@ -706,8 +705,8 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is
             nm_platform_link_set_bridge_info(
                 plat,
                 ifindex,
-                &((NMPlatformLinkSetBridgeInfoData){.vlan_default_pvid_has = TRUE,
-                                                    .vlan_default_pvid_val = pvid}));
+                &((NMPlatformLinkSetBridgeInfoData) {.vlan_default_pvid_has = TRUE,
+                                                     .vlan_default_pvid_val = pvid}));
         }
     }
 
@@ -721,7 +720,7 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is
 
     nm_platform_link_set_bridge_info(plat,
                                      ifindex,
-                                     &((NMPlatformLinkSetBridgeInfoData){
+                                     &((NMPlatformLinkSetBridgeInfoData) {
                                          .vlan_filtering_has = TRUE,
                                          .vlan_filtering_val = TRUE,
                                      }));
@@ -757,7 +756,7 @@ merge_bridge_vlan_default_pvid(NMPlatformBridgeVlan *vlans, guint *num_vlans, gu
          * Set the PVID flag only if the port didn't have one. */
         vlans = g_realloc_n(vlans, *num_vlans + 1, sizeof(NMPlatformBridgeVlan));
         (*num_vlans)++;
-        vlans[*num_vlans - 1] = (NMPlatformBridgeVlan){
+        vlans[*num_vlans - 1] = (NMPlatformBridgeVlan) {
             .vid_start = default_pvid,
             .vid_end   = default_pvid,
             .untagged  = TRUE,
@@ -847,7 +846,7 @@ nm_device_reapply_bridge_port_vlans(NMDevice *device)
 static void
 _platform_lnk_bridge_init_from_setting(NMSettingBridge *s_bridge, NMPlatformLnkBridge *props)
 {
-    *props = (NMPlatformLnkBridge){
+    *props = (NMPlatformLnkBridge) {
         .forward_delay = _DEFAULT_IF_ZERO(nm_setting_bridge_get_forward_delay(s_bridge) * 100u,
                                           NM_BRIDGE_FORWARD_DELAY_DEF_SYS),
         .hello_time    = _DEFAULT_IF_ZERO(nm_setting_bridge_get_hello_time(s_bridge) * 100u,
diff --git a/src/core/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c
index b7c4106a..1bc5447f 100644
--- a/src/core/devices/nm-device-dummy.c
+++ b/src/core/devices/nm-device-dummy.c
@@ -48,14 +48,14 @@ complete_connection(NMDevice            *device,
                     NMConnection *const *existing_connections,
                     GError             **error)
 {
-    nm_utils_complete_generic_with_params(nm_device_get_platform(device),
-                                          connection,
-                                          NM_SETTING_DUMMY_SETTING_NAME,
-                                          existing_connections,
-                                          NULL,
-                                          _("Dummy connection"),
-                                          NULL,
-                                          nm_device_get_ip_iface(device));
+    nm_utils_complete_generic(nm_device_get_platform(device),
+                              connection,
+                              NM_SETTING_DUMMY_SETTING_NAME,
+                              existing_connections,
+                              NULL,
+                              _("Dummy connection"),
+                              NULL,
+                              nm_device_get_ip_iface(device));
 
     _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_DUMMY);
 
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 16992524..4034fdaa 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -1451,7 +1451,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
             priv->ppp_data.ppp_mgr =
-                nm_ppp_mgr_start(&((const NMPppMgrConfig){
+                nm_ppp_mgr_start(&((const NMPppMgrConfig) {
                                      .netns         = nm_device_get_netns(device),
                                      .parent_iface  = nm_device_get_iface(device),
                                      .callback      = _ppp_mgr_callback,
@@ -1640,8 +1640,7 @@ complete_connection(NMDevice            *device,
                                   NULL,
                                   _("Veth connection"),
                                   "veth",
-                                  NULL,
-                                  TRUE);
+                                  NULL);
 
         s_veth = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VETH);
 
@@ -1698,8 +1697,7 @@ complete_connection(NMDevice            *device,
         NULL,
         s_pppoe ? _("PPPoE connection") : _("Wired connection"),
         NULL,
-        nm_setting_wired_get_mac_address(s_wired) ? NULL : nm_device_get_iface(device),
-        s_pppoe ? FALSE : TRUE); /* No IPv6 by default yet for PPPoE */
+        nm_setting_wired_get_mac_address(s_wired) ? NULL : nm_device_get_iface(device));
 
     return TRUE;
 }
diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c
index 22c8fa5a..15858362 100644
--- a/src/core/devices/nm-device-factory.c
+++ b/src/core/devices/nm-device-factory.c
@@ -147,7 +147,7 @@ nm_device_factory_get_connection_iface(NMDeviceFactory *factory,
         g_set_error(error,
                     NM_MANAGER_ERROR,
                     NM_MANAGER_ERROR_FAILED,
-                    "failed to determine interface name: error determine name for %s",
+                    "failed to determine interface name for a %s",
                     nm_connection_get_connection_type(connection));
         return NULL;
     }
@@ -415,6 +415,7 @@ nm_device_factory_manager_load_factories(NMDeviceFactoryManagerFactoryFunc callb
     _ADD_INTERNAL(nm_hsr_device_factory_get_type);
     _ADD_INTERNAL(nm_infiniband_device_factory_get_type);
     _ADD_INTERNAL(nm_ip_tunnel_device_factory_get_type);
+    _ADD_INTERNAL(nm_ipvlan_device_factory_get_type);
     _ADD_INTERNAL(nm_loopback_device_factory_get_type);
     _ADD_INTERNAL(nm_macsec_device_factory_get_type);
     _ADD_INTERNAL(nm_macvlan_device_factory_get_type);
diff --git a/src/core/devices/nm-device-factory.h b/src/core/devices/nm-device-factory.h
index 004ae9b1..d4aae75f 100644
--- a/src/core/devices/nm-device-factory.h
+++ b/src/core/devices/nm-device-factory.h
@@ -69,11 +69,15 @@ typedef struct {
     /**
      * get_connection_parent:
      * @factory: the #NMDeviceFactory
-     * @connection: the #NMConnection to return the parent name for, if supported
+     * @connection: the #NMConnection (possibly incomplete) to return the parent name for, if supported
      *
      * Given a connection, returns the parent interface name, parent connection
      * UUID, or parent device permanent hardware address for @connection.
      *
+     * Note that @connection is not necessarily a valid connection.
+     * It might be called during AddAndActivate before the connection is
+     * completed and normalized.
+     *
      * Returns: the parent interface name, parent connection UUID, parent
      *   device permanent hardware address, or %NULL
      */
@@ -82,12 +86,16 @@ typedef struct {
     /**
      * get_connection_iface:
      * @factory: the #NMDeviceFactory
-     * @connection: the #NMConnection to return the interface name for
+     * @connection: the #NMConnection (possibly incomplete) to return the interface name for
      * @parent_iface: optional parent interface name for virtual devices
      *
      * Given a connection, returns the interface name that a device activating
      * that connection would have.
      *
+     * Note that @connection is not necessarily a valid connection.
+     * It might be called during AddAndActivate before the connection is
+     * completed and normalized.
+     *
      * Returns: the interface name, or %NULL
      */
     char *(*get_connection_iface)(NMDeviceFactory *factory,
diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index c974696c..a5a82b94 100644
--- a/src/core/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -159,8 +159,7 @@ complete_connection(NMDevice            *device,
         NULL,
         _("InfiniBand connection"),
         NULL,
-        nm_setting_infiniband_get_mac_address(s_infiniband) ? NULL : nm_device_get_iface(device),
-        TRUE);
+        nm_setting_infiniband_get_mac_address(s_infiniband) ? NULL : nm_device_get_iface(device));
 
     if (!nm_setting_infiniband_get_transport_mode(s_infiniband))
         g_object_set(G_OBJECT(s_infiniband),
@@ -464,9 +463,10 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
                          NULL);
 
     s_infiniband = nm_connection_get_setting_infiniband(connection);
-    g_assert(s_infiniband);
-
-    return nm_setting_infiniband_get_parent(s_infiniband);
+    if (s_infiniband)
+        return nm_setting_infiniband_get_parent(s_infiniband);
+    else
+        return NULL;
 }
 
 static char *
@@ -477,17 +477,19 @@ get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const c
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_INFINIBAND_SETTING_NAME),
                          NULL);
 
-    s_infiniband = nm_connection_get_setting_infiniband(connection);
-    g_assert(s_infiniband);
-
     if (!parent_iface)
         return NULL;
 
-    g_return_val_if_fail(g_strcmp0(parent_iface, nm_setting_infiniband_get_parent(s_infiniband))
-                             == 0,
-                         NULL);
+    s_infiniband = nm_connection_get_setting_infiniband(connection);
+    if (s_infiniband) {
+        g_return_val_if_fail(g_strcmp0(parent_iface, nm_setting_infiniband_get_parent(s_infiniband))
+                                 == 0,
+                             NULL);
 
-    return g_strdup(nm_setting_infiniband_get_virtual_interface_name(s_infiniband));
+        return g_strdup(nm_setting_infiniband_get_virtual_interface_name(s_infiniband));
+    } else {
+        return NULL;
+    }
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
index cc62180e..2ecfe453 100644
--- a/src/core/devices/nm-device-ip-tunnel.c
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -402,8 +402,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("IP tunnel connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
     if (!s_ip_tunnel) {
@@ -1369,29 +1368,10 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
                          NULL);
 
     s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
-    g_assert(s_ip_tunnel);
-
-    return nm_setting_ip_tunnel_get_parent(s_ip_tunnel);
-}
-
-static char *
-get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
-{
-    const char        *ifname;
-    NMSettingIPTunnel *s_ip_tunnel;
-
-    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IP_TUNNEL_SETTING_NAME),
-                         NULL);
-
-    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
-    g_assert(s_ip_tunnel);
-
-    if (nm_setting_ip_tunnel_get_parent(s_ip_tunnel) && !parent_iface)
+    if (s_ip_tunnel)
+        return nm_setting_ip_tunnel_get_parent(s_ip_tunnel);
+    else
         return NULL;
-
-    ifname = nm_connection_get_interface_name(connection);
-
-    return g_strdup(ifname);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
@@ -1409,5 +1389,4 @@ NM_DEVICE_FACTORY_DEFINE_INTERNAL(
                                          NM_LINK_TYPE_VTI6)
         NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_IP_TUNNEL_SETTING_NAME),
     factory_class->create_device         = create_device;
-    factory_class->get_connection_parent = get_connection_parent;
-    factory_class->get_connection_iface  = get_connection_iface;);
+    factory_class->get_connection_parent = get_connection_parent;);
diff --git a/src/core/devices/nm-device-ipvlan.c b/src/core/devices/nm-device-ipvlan.c
new file mode 100644
index 00000000..00a1b579
--- /dev/null
+++ b/src/core/devices/nm-device-ipvlan.c
@@ -0,0 +1,467 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-ipvlan.h"
+
+#include <linux/if_link.h>
+
+#include "libnm-core-intern/nm-core-internal.h"
+#include "nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "nm-act-request.h"
+#include "nm-manager.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "libnm-platform/nm-platform.h"
+#include "nm-device-factory.h"
+#include "nm-setting-ipvlan.h"
+#include "nm-setting-wired.h"
+#include "nm-active-connection.h"
+#include "nm-utils.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceIpvlan
+#include "nm-device-logging.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIpvlan, PROP_MODE, PROP_PRIVATE, PROP_VEPA, );
+
+typedef struct {
+    NMPlatformLnkIpvlan props;
+} NMDeviceIpvlanPrivate;
+
+struct _NMDeviceIpvlan {
+    NMDevice              parent;
+    NMDeviceIpvlanPrivate _priv;
+};
+
+struct _NMDeviceIpvlanClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceIpvlan, nm_device_ipvlan, NM_TYPE_DEVICE);
+
+#define NM_DEVICE_IPVLAN_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDeviceIpvlan, NM_IS_DEVICE_IPVLAN, NMDevice)
+
+/*****************************************************************************/
+
+static int modes[][2] = {
+    {NM_SETTING_IPVLAN_MODE_L2, IPVLAN_MODE_L2},
+    {NM_SETTING_IPVLAN_MODE_L3, IPVLAN_MODE_L3},
+    {NM_SETTING_IPVLAN_MODE_L3S, IPVLAN_MODE_L3S},
+};
+
+static int
+setting_mode_to_platform(int mode)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS(modes); i++) {
+        if (modes[i][0] == mode)
+            return modes[i][1];
+    }
+
+    return -1;
+}
+
+static int
+platform_mode_to_setting(int mode)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS(modes); i++) {
+        if (modes[i][1] == mode)
+            return modes[i][0];
+    }
+
+    return 0;
+}
+
+static const char *
+platform_mode_to_string(guint mode)
+{
+    switch (mode) {
+    case IPVLAN_MODE_L2:
+        return "l2";
+    case IPVLAN_MODE_L3:
+        return "l3";
+    case IPVLAN_MODE_L3S:
+        return "l3s";
+    default:
+        return "unknown";
+    }
+}
+
+/*****************************************************************************/
+
+static void
+update_properties(NMDevice *device)
+{
+    NMDeviceIpvlan            *self   = NM_DEVICE_IPVLAN(device);
+    NMDeviceIpvlanPrivate     *priv   = NM_DEVICE_IPVLAN_GET_PRIVATE(self);
+    GObject                   *object = G_OBJECT(device);
+    const NMPlatformLnkIpvlan *props;
+    const NMPlatformLink      *plink;
+
+    props = nm_platform_link_get_lnk_ipvlan(nm_device_get_platform(device),
+                                            nm_device_get_ifindex(device),
+                                            &plink);
+
+    if (!props) {
+        _LOGW(LOGD_PLATFORM, "could not get IPVLAN properties");
+        return;
+    }
+
+    g_object_freeze_notify(object);
+
+    nm_device_parent_set_ifindex(device, plink->parent);
+
+#define CHECK_PROPERTY_CHANGED(field, prop)      \
+    G_STMT_START                                 \
+    {                                            \
+        if (priv->props.field != props->field) { \
+            priv->props.field = props->field;    \
+            _notify(self, prop);                 \
+        }                                        \
+    }                                            \
+    G_STMT_END
+
+    CHECK_PROPERTY_CHANGED(mode, PROP_MODE);
+    CHECK_PROPERTY_CHANGED(private_flag, PROP_PRIVATE);
+    CHECK_PROPERTY_CHANGED(vepa, PROP_VEPA);
+
+    g_object_thaw_notify(object);
+}
+
+static void
+link_changed(NMDevice *device, const NMPlatformLink *pllink)
+{
+    NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)->link_changed(device, pllink);
+    update_properties(device);
+}
+
+static gboolean
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
+                   const NMPlatformLink **out_plink,
+                   GError               **error)
+{
+    const char         *iface = nm_device_get_iface(device);
+    NMSettingIpvlan    *s_ipvlan;
+    NMPlatformLnkIpvlan lnk = {};
+    int                 parent_ifindex;
+    int                 r;
+
+    s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+    nm_assert(s_ipvlan);
+
+    if (!parent) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+                    "IPVLAN device cannot be created without a parent interface");
+        return FALSE;
+    }
+
+    parent_ifindex = nm_device_get_ifindex(parent);
+    if (parent_ifindex <= 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+                    "cannot retrieve ifindex of interface %s (%s)",
+                    nm_device_get_iface(parent),
+                    nm_device_get_type_desc(parent));
+        return FALSE;
+    }
+
+    if (setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan)) < 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_FAILED,
+                    "unsupported IPVLAN mode %u",
+                    nm_setting_ipvlan_get_mode(s_ipvlan));
+        return FALSE;
+    }
+    lnk.mode         = setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan));
+    lnk.private_flag = nm_setting_ipvlan_get_private(s_ipvlan);
+    lnk.vepa         = nm_setting_ipvlan_get_vepa(s_ipvlan);
+
+    r = nm_platform_link_ipvlan_add(nm_device_get_platform(device),
+                                    iface,
+                                    parent_ifindex,
+                                    &lnk,
+                                    out_plink);
+
+    if (r < 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_CREATION_FAILED,
+                    "Failed to create IPVLAN interface '%s' for '%s': %s",
+                    iface,
+                    nm_connection_get_id(connection),
+                    nm_strerror(r));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+    if (!nm_device_parent_get_device(device))
+        return FALSE;
+    return NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)->is_available(device, flags);
+}
+
+/*****************************************************************************/
+
+static gboolean
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
+{
+    NMDeviceIpvlanPrivate *priv = NM_DEVICE_IPVLAN_GET_PRIVATE(device);
+    NMSettingIpvlan       *s_ipvlan;
+    const char            *parent = NULL;
+
+    if (!NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)
+             ->check_connection_compatible(device, connection, check_properties, error))
+        return FALSE;
+
+    s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+
+    if (check_properties && nm_device_is_real(device)) {
+        if (setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan)) != priv->props.mode) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IPVLAN mode setting differs");
+            return FALSE;
+        }
+
+        if (nm_setting_ipvlan_get_private(s_ipvlan) != priv->props.private_flag) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IPVLAN private flag setting differs");
+            return FALSE;
+        }
+        if (nm_setting_ipvlan_get_vepa(s_ipvlan) != priv->props.vepa) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IPVLAN VEPA flag setting differs");
+            return FALSE;
+        }
+
+        /* Check parent interface; could be an interface name or a UUID */
+        parent = nm_setting_ipvlan_get_parent(s_ipvlan);
+        if (parent) {
+            if (!nm_device_match_parent(device, parent)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "IPVLAN parent setting differs");
+                return FALSE;
+            }
+        } else {
+            /* Parent could be a MAC address in an NMSettingWired */
+            if (!nm_device_match_parent_hwaddr(device, connection, TRUE)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "IPVLAN parent mac setting differs");
+                return FALSE;
+            }
+        }
+    }
+    return TRUE;
+}
+
+static void
+update_connection(NMDevice *device, NMConnection *connection)
+{
+    NMDeviceIpvlanPrivate *priv = NM_DEVICE_IPVLAN_GET_PRIVATE(device);
+    NMSettingIpvlan *s_ipvlan   = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_IPVLAN);
+
+    if (priv->props.mode != setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan)))
+        g_object_set(s_ipvlan,
+                     NM_SETTING_IPVLAN_MODE,
+                     platform_mode_to_setting(priv->props.mode),
+                     NULL);
+
+    if (priv->props.private_flag != nm_setting_ipvlan_get_private(s_ipvlan))
+        g_object_set(s_ipvlan, NM_SETTING_IPVLAN_PRIVATE, priv->props.private_flag, NULL);
+
+    if (priv->props.vepa != nm_setting_ipvlan_get_vepa(s_ipvlan))
+        g_object_set(s_ipvlan, NM_SETTING_IPVLAN_VEPA, priv->props.vepa, NULL);
+
+    g_object_set(
+        s_ipvlan,
+        NM_SETTING_IPVLAN_PARENT,
+        nm_device_parent_find_for_connection(device, nm_setting_ipvlan_get_parent(s_ipvlan)),
+        NULL);
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMDeviceIpvlanPrivate *priv = NM_DEVICE_IPVLAN_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MODE:
+        g_value_set_string(value, platform_mode_to_string(priv->props.mode));
+        break;
+    case PROP_PRIVATE:
+        g_value_set_boolean(value, priv->props.private_flag);
+        break;
+    case PROP_VEPA:
+        g_value_set_boolean(value, priv->props.vepa);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ipvlan_init(NMDeviceIpvlan *self)
+{}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_ipvlan = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_IPVLAN,
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "s", NM_DEVICE_IPVLAN_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Private",
+                                                           "b",
+                                                           NM_DEVICE_IPVLAN_PRIVATE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Vepa",
+                                                           "b",
+                                                           NM_DEVICE_IPVLAN_VEPA), ), ),
+};
+
+static void
+nm_device_ipvlan_class_init(NMDeviceIpvlanClass *klass)
+{
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
+
+    object_class->get_property = get_property;
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ipvlan);
+
+    device_class->connection_type_supported        = NM_SETTING_IPVLAN_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_IPVLAN_SETTING_NAME;
+    device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_IPVLAN);
+
+    device_class->check_connection_compatible = check_connection_compatible;
+    device_class->create_and_realize          = create_and_realize;
+    device_class->get_generic_capabilities    = get_generic_capabilities;
+    device_class->is_available                = is_available;
+    device_class->link_changed                = link_changed;
+    device_class->update_connection           = update_connection;
+
+    obj_properties[PROP_MODE] = g_param_spec_string(NM_DEVICE_IPVLAN_MODE,
+                                                    "",
+                                                    "",
+                                                    NULL,
+                                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_PRIVATE] = g_param_spec_boolean(NM_DEVICE_IPVLAN_PRIVATE,
+                                                        "",
+                                                        "",
+                                                        TRUE,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_VEPA] = g_param_spec_boolean(NM_DEVICE_IPVLAN_VEPA,
+                                                     "",
+                                                     "",
+                                                     TRUE,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_IPVLAN_DEVICE_FACTORY (nm_ipvlan_device_factory_get_type())
+#define NM_IPVLAN_DEVICE_FACTORY(obj) \
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IPVLAN_DEVICE_FACTORY, NMIpvlanDeviceFactory))
+
+static NMDevice *
+create_device(NMDeviceFactory      *factory,
+              const char           *iface,
+              const NMPlatformLink *plink,
+              NMConnection         *connection,
+              gboolean             *out_ignore)
+{
+    NMSettingIpvlan *s_ipvlan;
+
+    if (connection) {
+        s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+        nm_assert(s_ipvlan);
+    }
+
+    return g_object_new(NM_TYPE_DEVICE_IPVLAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Ipvlan",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_IPVLAN,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_IPVLAN,
+                        NULL);
+}
+
+static const char *
+get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
+{
+    NMSettingIpvlan *s_ipvlan;
+    NMSettingWired  *s_wired;
+    const char      *parent = NULL;
+
+    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IPVLAN_SETTING_NAME), NULL);
+
+    s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+    if (s_ipvlan) {
+        parent = nm_setting_ipvlan_get_parent(s_ipvlan);
+        if (parent)
+            return parent;
+    }
+
+    /* Try the hardware address from the IPVLAN connection's hardware setting */
+    s_wired = nm_connection_get_setting_wired(connection);
+    if (s_wired)
+        return nm_setting_wired_get_mac_address(s_wired);
+    else
+        return NULL;
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL(
+    IPVLAN,
+    Ipvlan,
+    ipvlan,
+    NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_IPVLAN)
+        NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_IPVLAN_SETTING_NAME),
+    factory_class->create_device         = create_device;
+    factory_class->get_connection_parent = get_connection_parent;);
diff --git a/src/core/devices/nm-device-ipvlan.h b/src/core/devices/nm-device-ipvlan.h
new file mode 100644
index 00000000..6a228abe
--- /dev/null
+++ b/src/core/devices/nm-device-ipvlan.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_IPVLAN_H__
+#define __NETWORKMANAGER_DEVICE_IPVLAN_H__
+
+#include "nm-device.h"
+
+#define NM_TYPE_DEVICE_IPVLAN (nm_device_ipvlan_get_type())
+#define NM_DEVICE_IPVLAN(obj) \
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IPVLAN, NMDeviceIpvlan))
+#define NM_DEVICE_IPVLAN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IPVLAN, NMDeviceIpvlanClass))
+#define NM_IS_DEVICE_IPVLAN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IPVLAN))
+#define NM_IS_DEVICE_IPVLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_IPVLAN))
+#define NM_DEVICE_IPVLAN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_IPVLAN, NMDeviceIpvlanClass))
+
+#define NM_DEVICE_IPVLAN_PARENT  "parent"
+#define NM_DEVICE_IPVLAN_MODE    "mode"
+#define NM_DEVICE_IPVLAN_PRIVATE "private"
+#define NM_DEVICE_IPVLAN_VEPA    "vepa"
+
+typedef struct _NMDeviceIpvlan      NMDeviceIpvlan;
+typedef struct _NMDeviceIpvlanClass NMDeviceIpvlanClass;
+
+GType nm_device_ipvlan_get_type(void);
+
+#endif /* __NETWORKMANAGER_DEVICE_IPVLAN_H__ */
diff --git a/src/core/devices/nm-device-loopback.c b/src/core/devices/nm-device-loopback.c
index ec72aa96..268e1fb1 100644
--- a/src/core/devices/nm-device-loopback.c
+++ b/src/core/devices/nm-device-loopback.c
@@ -59,14 +59,14 @@ complete_connection(NMDevice            *device,
                     NMConnection *const *existing_connections,
                     GError             **error)
 {
-    nm_utils_complete_generic_with_params(nm_device_get_platform(device),
-                                          connection,
-                                          NM_SETTING_LOOPBACK_SETTING_NAME,
-                                          existing_connections,
-                                          NULL,
-                                          _("Loopback connection"),
-                                          NULL,
-                                          nm_device_get_ip_iface(device));
+    nm_utils_complete_generic(nm_device_get_platform(device),
+                              connection,
+                              NM_SETTING_LOOPBACK_SETTING_NAME,
+                              existing_connections,
+                              NULL,
+                              _("Loopback connection"),
+                              NULL,
+                              nm_device_get_ip_iface(device));
 
     _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_LOOPBACK);
 
diff --git a/src/core/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c
index 32fab5be..89a06720 100644
--- a/src/core/devices/nm-device-macsec.c
+++ b/src/core/devices/nm-device-macsec.c
@@ -1022,36 +1022,18 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACSEC_SETTING_NAME), NULL);
 
     s_macsec = nm_connection_get_setting_macsec(connection);
-    g_assert(s_macsec);
-
-    parent = nm_setting_macsec_get_parent(s_macsec);
-    if (parent)
-        return parent;
+    if (s_macsec) {
+        parent = nm_setting_macsec_get_parent(s_macsec);
+        if (parent)
+            return parent;
+    }
 
     /* Try the hardware address from the MACsec connection's hardware setting */
     s_wired = nm_connection_get_setting_wired(connection);
     if (s_wired)
         return nm_setting_wired_get_mac_address(s_wired);
-
-    return NULL;
-}
-
-static char *
-get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
-{
-    NMSettingMacsec *s_macsec;
-    const char      *ifname;
-
-    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACSEC_SETTING_NAME), NULL);
-
-    s_macsec = nm_connection_get_setting_macsec(connection);
-    g_assert(s_macsec);
-
-    if (!parent_iface)
+    else
         return NULL;
-
-    ifname = nm_connection_get_interface_name(connection);
-    return g_strdup(ifname);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
@@ -1061,5 +1043,4 @@ NM_DEVICE_FACTORY_DEFINE_INTERNAL(
     NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_MACSEC)
         NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_MACSEC_SETTING_NAME),
     factory_class->create_device         = create_device;
-    factory_class->get_connection_parent = get_connection_parent;
-    factory_class->get_connection_iface  = get_connection_iface;)
+    factory_class->get_connection_parent = get_connection_parent;);
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index 8cdef0cf..9501e8f1 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -232,9 +232,8 @@ create_and_realize(NMDevice              *device,
         g_set_error(error,
                     NM_DEVICE_ERROR,
                     NM_DEVICE_ERROR_FAILED,
-                    "unsupported MACVLAN mode %u in connection %s",
-                    nm_setting_macvlan_get_mode(s_macvlan),
-                    nm_connection_get_uuid(connection));
+                    "unsupported MACVLAN mode %u",
+                    nm_setting_macvlan_get_mode(s_macvlan));
         return FALSE;
     }
     lnk.no_promisc = !nm_setting_macvlan_get_promiscuous(s_macvlan);
@@ -365,8 +364,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("MACVLAN connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_macvlan = nm_connection_get_setting_macvlan(connection);
     if (!s_macvlan) {
@@ -590,36 +588,18 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACVLAN_SETTING_NAME), NULL);
 
     s_macvlan = nm_connection_get_setting_macvlan(connection);
-    g_assert(s_macvlan);
-
-    parent = nm_setting_macvlan_get_parent(s_macvlan);
-    if (parent)
-        return parent;
+    if (s_macvlan) {
+        parent = nm_setting_macvlan_get_parent(s_macvlan);
+        if (parent)
+            return parent;
+    }
 
     /* Try the hardware address from the MACVLAN connection's hardware setting */
     s_wired = nm_connection_get_setting_wired(connection);
     if (s_wired)
         return nm_setting_wired_get_mac_address(s_wired);
-
-    return NULL;
-}
-
-static char *
-get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
-{
-    NMSettingMacvlan *s_macvlan;
-    const char       *ifname;
-
-    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACVLAN_SETTING_NAME), NULL);
-
-    s_macvlan = nm_connection_get_setting_macvlan(connection);
-    g_assert(s_macvlan);
-
-    if (!parent_iface)
+    else
         return NULL;
-
-    ifname = nm_connection_get_interface_name(connection);
-    return g_strdup(ifname);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
@@ -629,5 +609,4 @@ NM_DEVICE_FACTORY_DEFINE_INTERNAL(
     NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP)
         NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_MACVLAN_SETTING_NAME),
     factory_class->create_device         = create_device;
-    factory_class->get_connection_parent = get_connection_parent;
-    factory_class->get_connection_iface  = get_connection_iface;);
+    factory_class->get_connection_parent = get_connection_parent;);
diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c
index 27566607..f44fe2f0 100644
--- a/src/core/devices/nm-device-ppp.c
+++ b/src/core/devices/nm-device-ppp.c
@@ -188,7 +188,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         s_pppoe = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE);
         g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
-        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig){
+        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig) {
                                              .netns        = nm_device_get_netns(device),
                                              .parent_iface = nm_setting_pppoe_get_parent(s_pppoe),
                                              .callback     = _ppp_mgr_callback,
@@ -380,20 +380,10 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
     nm_assert(nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME));
 
     s_pppoe = nm_connection_get_setting_pppoe(connection);
-    nm_assert(s_pppoe);
-
-    return nm_setting_pppoe_get_parent(s_pppoe);
-}
-
-static char *
-get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
-{
-    nm_assert(nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME));
-
-    if (!parent_iface)
+    if (s_pppoe)
+        return nm_setting_pppoe_get_parent(s_pppoe);
+    else
         return NULL;
-
-    return g_strdup(nm_connection_get_interface_name(connection));
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
@@ -403,6 +393,5 @@ NM_DEVICE_FACTORY_DEFINE_INTERNAL(
     NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_PPP)
         NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_PPPOE_SETTING_NAME),
     factory_class->get_connection_parent = get_connection_parent;
-    factory_class->get_connection_iface  = get_connection_iface;
     factory_class->create_device         = create_device;
     factory_class->match_connection      = match_connection;);
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index 3bf5925c..2f73a01b 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -115,8 +115,8 @@ gboolean nm_device_sysctl_ip_conf_set(NMDevice   *self,
 
 NML3ConfigData *nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source);
 
-const NML3ConfigData *nm_device_create_l3_config_data_from_connection(NMDevice     *self,
-                                                                      NMConnection *connection);
+NML3ConfigData *nm_device_create_l3_config_data_from_connection(NMDevice     *self,
+                                                                NMConnection *connection);
 
 void nm_device_ip_method_dhcp4_start(NMDevice *self);
 
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index 28b03cec..faab86d0 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -143,8 +143,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("TUN connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_tun = nm_connection_get_setting_tun(connection);
     if (!s_tun) {
diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c
index ccb6292b..9fd7ac9d 100644
--- a/src/core/devices/nm-device-utils.c
+++ b/src/core/devices/nm-device-utils.c
@@ -355,7 +355,7 @@ nm_device_resolve_address(int                 addr_family,
     NMDnsSystemdResolved *resolved;
 
     info  = g_new(ResolveAddrInfo, 1);
-    *info = (ResolveAddrInfo){
+    *info = (ResolveAddrInfo) {
         .task = nm_g_task_new(NULL, cancellable, nm_device_resolve_address, callback, cb_data),
         .addr_family = addr_family,
         .address     = nm_ip_addr_init(addr_family, address),
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index d4630999..59a429ca 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -241,7 +241,7 @@ create_and_realize(NMDevice              *device,
     r = nm_platform_link_vlan_add(nm_device_get_platform(device),
                                   iface,
                                   parent_ifindex,
-                                  &((NMPlatformLnkVlan){
+                                  &((NMPlatformLnkVlan) {
                                       .id       = vlan_id,
                                       .flags    = nm_setting_vlan_get_flags(s_vlan),
                                       .protocol = protocol,
@@ -379,8 +379,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("VLAN connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_vlan = nm_connection_get_setting_vlan(connection);
     if (!s_vlan) {
@@ -618,43 +617,35 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VLAN_SETTING_NAME), NULL);
 
     s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
-
-    parent = nm_setting_vlan_get_parent(s_vlan);
-    if (parent)
-        return parent;
+    if (s_vlan) {
+        parent = nm_setting_vlan_get_parent(s_vlan);
+        if (parent)
+            return parent;
+    }
 
     /* Try the hardware address from the VLAN connection's hardware setting */
     s_wired = nm_connection_get_setting_wired(connection);
     if (s_wired)
         return nm_setting_wired_get_mac_address(s_wired);
-
-    return NULL;
+    else
+        return NULL;
 }
 
 static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
-    const char    *ifname;
     NMSettingVlan *s_vlan;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VLAN_SETTING_NAME), NULL);
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
-
     if (!parent_iface)
         return NULL;
 
-    ifname = nm_connection_get_interface_name(connection);
-    if (ifname)
-        return g_strdup(ifname);
-
-    /* If the connection doesn't specify the interface name for the VLAN
-     * device, we create one for it using the VLAN ID and the parent
-     * interface's name.
-     */
-    return nmp_utils_new_vlan_name(parent_iface, nm_setting_vlan_get_id(s_vlan));
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    if (s_vlan)
+        return nmp_utils_new_vlan_name(parent_iface, nm_setting_vlan_get_id(s_vlan));
+    else
+        return NULL;
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index c3d31347..7dfd6504 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -48,7 +48,7 @@ do_update_properties(NMDeviceVrf *self, const NMPlatformLnkVrf *props)
     NMPlatformLnkVrf    props_null;
 
     if (!props) {
-        props_null = (NMPlatformLnkVrf){};
+        props_null = (NMPlatformLnkVrf) {};
         props      = &props_null;
     }
 
@@ -184,8 +184,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("VRF connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF);
     if (!s_vrf) {
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index 6a23d51e..4058287c 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -384,8 +384,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("VXLAN connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_vxlan = nm_connection_get_setting_vxlan(connection);
     if (!s_vxlan) {
@@ -777,27 +776,10 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VXLAN_SETTING_NAME), NULL);
 
     s_vxlan = nm_connection_get_setting_vxlan(connection);
-    g_assert(s_vxlan);
-
-    return nm_setting_vxlan_get_parent(s_vxlan);
-}
-
-static char *
-get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
-{
-    const char     *ifname;
-    NMSettingVxlan *s_vxlan;
-
-    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VXLAN_SETTING_NAME), NULL);
-
-    s_vxlan = nm_connection_get_setting_vxlan(connection);
-    g_assert(s_vxlan);
-
-    if (nm_setting_vxlan_get_parent(s_vxlan) && !parent_iface)
+    if (s_vxlan)
+        return nm_setting_vxlan_get_parent(s_vxlan);
+    else
         return NULL;
-
-    ifname = nm_connection_get_interface_name(connection);
-    return g_strdup(ifname);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
@@ -807,5 +789,4 @@ NM_DEVICE_FACTORY_DEFINE_INTERNAL(
     NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VXLAN)
         NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_VXLAN_SETTING_NAME),
     factory_class->create_device         = create_device;
-    factory_class->get_connection_parent = get_connection_parent;
-    factory_class->get_connection_iface  = get_connection_iface;);
+    factory_class->get_connection_parent = get_connection_parent;);
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index 00a8c718..4a08192e 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -430,7 +430,7 @@ get_extra_rules(NMDevice *device)
 
         g_ptr_array_add(extra_rules,
                         nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE,
-                                       &((const NMPlatformRoutingRule){
+                                       &((const NMPlatformRoutingRule) {
                                            .priority                   = priority,
                                            .addr_family                = addr_family,
                                            .action                     = FR_ACT_TO_TBL,
@@ -440,7 +440,7 @@ get_extra_rules(NMDevice *device)
 
         g_ptr_array_add(extra_rules,
                         nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE,
-                                       &((const NMPlatformRoutingRule){
+                                       &((const NMPlatformRoutingRule) {
                                            .priority    = priority + 1u,
                                            .addr_family = addr_family,
                                            .action      = FR_ACT_TO_TBL,
@@ -588,7 +588,7 @@ _peers_add(NMDeviceWireGuard *self, NMWireGuardPeer *peer)
     nm_assert(!_peers_find(priv, peer));
 
     peer_data  = g_slice_new(PeerData);
-    *peer_data = (PeerData){
+    *peer_data = (PeerData) {
         .self = self,
         .peer = nm_wireguard_peer_ref(peer),
         .ep_resolv =
@@ -792,7 +792,7 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
             switch (g_inet_address_get_family(a)) {
             case G_SOCKET_FAMILY_IPV4:
                 nm_assert(g_inet_address_get_native_size(a) == sizeof(struct in_addr));
-                s->in = (struct sockaddr_in){
+                s->in = (struct sockaddr_in) {
                     .sin_family = AF_INET,
                     .sin_port   = htons(nm_sock_addr_endpoint_get_port(
                         _nm_wireguard_peer_get_endpoint(peer_data->peer))),
@@ -801,7 +801,7 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
                 break;
             case G_SOCKET_FAMILY_IPV6:
                 nm_assert(g_inet_address_get_native_size(a) == sizeof(struct in6_addr));
-                s->in6 = (struct sockaddr_in6){
+                s->in6 = (struct sockaddr_in6) {
                     .sin6_family   = AF_INET6,
                     .sin6_port     = htons(nm_sock_addr_endpoint_get_port(
                         _nm_wireguard_peer_get_endpoint(peer_data->peer))),
@@ -985,7 +985,7 @@ _peers_update(NMDeviceWireGuard *self,
     if (nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable))
         _peers_resolving_cnt_decrement(self);
 
-    peer_data->ep_resolv = (PeerEndpointResolveData){
+    peer_data->ep_resolv = (PeerEndpointResolveData) {
         .sockaddr          = sockaddr,
         .resolv_fail_count = 0,
         .cancellable       = NULL,
@@ -1166,7 +1166,7 @@ _peers_get_platform_list(NMDeviceWireGuardPrivate            *priv,
                     prefix = addr_family == AF_INET ? 32 : 128;
 
                 g_array_append_val(allowed_ips,
-                                   ((NMPWireGuardAllowedIP){
+                                   ((NMPWireGuardAllowedIP) {
                                        .family = addr_family,
                                        .mask   = prefix,
                                        .addr   = addrbin,
@@ -1486,7 +1486,7 @@ link_config(NMDeviceWireGuard   *self,
 
     _peers_update_all(self, s_wg, &peers_removed);
 
-    wg_lnk = (NMPlatformLnkWireGuard){};
+    wg_lnk = (NMPlatformLnkWireGuard) {};
 
     wg_change_flags = NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE;
 
@@ -1733,7 +1733,7 @@ _get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family)
             }
 
             if (addr_family == AF_INET) {
-                rt.r4 = (NMPlatformIP4Route){
+                rt.r4 = (NMPlatformIP4Route) {
                     .network       = addrbin.addr4,
                     .plen          = prefix,
                     .ifindex       = ip_ifindex,
@@ -1742,7 +1742,7 @@ _get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family)
                     .metric        = route_metric,
                 };
             } else {
-                rt.r6 = (NMPlatformIP6Route){
+                rt.r6 = (NMPlatformIP6Route) {
                     .network       = addrbin.addr6,
                     .plen          = prefix,
                     .ifindex       = ip_ifindex,
diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 7885355d..67f845c8 100644
--- a/src/core/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -53,8 +53,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("WPAN connection"),
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     s_wpan = NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN));
     if (!s_wpan) {
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 6c5a9b5a..e310a9c6 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -283,6 +283,7 @@ typedef struct {
             NML3IPv4LL             *ipv4ll;
             NML3IPv4LLRegistration *ipv4ll_registation;
             GSource                *timeout_source;
+            NMSettingIP4LinkLocal   mode;
         } v4;
         struct {
             NML3IPv6LL     *ipv6ll;
@@ -315,6 +316,7 @@ typedef struct {
     NMEthtoolPauseState    *pause;
     NMEthtoolChannelsState *channels;
     NMEthtoolEEEState      *eee;
+    uint32_t                fec_mode;
 } EthtoolState;
 
 typedef enum {
@@ -615,6 +617,9 @@ typedef struct _NMDevicePrivate {
 
     NMPacrunnerConfId *pacrunner_conf_id;
 
+    const char *ipv4_method;
+    const char *ipv6_method;
+
     struct {
         union {
             const NMDeviceIPState state;
@@ -694,16 +699,6 @@ typedef struct _NMDevicePrivate {
         bool previous_mode_has : 1;
     } addrgenmode6_data;
 
-    struct {
-        NMLogDomain log_domain;
-        guint       timeout;
-        guint       watch;
-        GPid        pid;
-        char       *binary;
-        char       *address;
-        guint       deadline;
-    } gw_ping;
-
     /* Firewall */
     FirewallState             fw_state : 4;
     NMFirewalldManager       *fw_mgr;
@@ -781,6 +776,8 @@ typedef struct _NMDevicePrivate {
 
     GVariant *ports_variant; /* Array of port devices D-Bus path */
     char     *prop_ip_iface; /* IP interface D-Bus property */
+    GList    *ping_operations;
+    GSource  *ping_timeout;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -837,6 +834,7 @@ static void _set_mtu(NMDevice *self, guint32 mtu);
 static void _commit_mtu(NMDevice *self);
 static void _cancel_activation(NMDevice *self);
 
+static void _dev_ipll4_check_fallback(NMDevice *self, const NML3ConfigData *l3cd_new);
 static void _dev_ipll4_notify_event(NMDevice *self);
 
 static void _dev_ip_state_check(NMDevice *self, int addr_family);
@@ -1357,6 +1355,42 @@ _prop_get_ipv6_ra_timeout(NMDevice *self)
                                                        0);
 }
 
+static NMSettingIPConfigRoutedDns
+_prop_get_ipvx_routed_dns(NMDevice *self, int addr_family)
+{
+    NMSettingIPConfig         *s_ip;
+    NMSettingIPConfigRoutedDns val;
+    int                        IS_IPv4;
+    const char                *dns_mode;
+    NMSettingIPConfigRoutedDns fallback_value = NM_SETTING_IP_CONFIG_ROUTED_DNS_NO;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_IP_CONFIG_ROUTED_DNS_NO);
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+
+    s_ip = nm_device_get_applied_setting(self,
+                                         IS_IPv4 ? NM_TYPE_SETTING_IP4_CONFIG
+                                                 : NM_TYPE_SETTING_IP6_CONFIG);
+    if (!s_ip)
+        return NM_SETTING_IP_CONFIG_ROUTED_DNS_NO;
+
+    val = nm_setting_ip_config_get_routed_dns(s_ip);
+    if (val != NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT)
+        return val;
+
+    dns_mode = nm_config_data_get_dns_mode(nm_config_get_data(nm_config_get()));
+    if (nm_streq0(dns_mode, "dnsconfd")) {
+        fallback_value = NM_SETTING_IP_CONFIG_ROUTED_DNS_YES;
+    }
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       IS_IPv4 ? NM_CON_DEFAULT("ipv4.routed-dns")
+                                                               : NM_CON_DEFAULT("ipv6.routed-dns"),
+                                                       self,
+                                                       NM_SETTING_IP_CONFIG_ROUTED_DNS_NO,
+                                                       NM_SETTING_IP_CONFIG_ROUTED_DNS_YES,
+                                                       fallback_value);
+}
+
 static NMSettingConnectionMdns
 _prop_get_connection_mdns(NMDevice *self)
 {
@@ -1603,6 +1637,7 @@ _prop_get_ipv4_link_local(NMDevice *self)
 {
     NMSettingIP4Config   *s_ip4;
     NMSettingIP4LinkLocal link_local;
+    const char           *method;
 
     s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
     if (!s_ip4)
@@ -1611,6 +1646,8 @@ _prop_get_ipv4_link_local(NMDevice *self)
     if (NM_IS_DEVICE_LOOPBACK(self))
         return NM_SETTING_IP4_LL_DISABLED;
 
+    method = nm_setting_ip_config_get_method((NMSettingIPConfig *) s_ip4);
+
     link_local = nm_setting_ip4_config_get_link_local(s_ip4);
 
     if (link_local == NM_SETTING_IP4_LL_DEFAULT) {
@@ -1620,31 +1657,45 @@ _prop_get_ipv4_link_local(NMDevice *self)
                                                                  NM_CON_DEFAULT("ipv4.link-local"),
                                                                  self,
                                                                  NM_SETTING_IP4_LL_AUTO,
-                                                                 NM_SETTING_IP4_LL_ENABLED,
+                                                                 NM_SETTING_IP4_LL_FALLBACK,
                                                                  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 */
+        } else if (NM_IN_SET(link_local, NM_SETTING_IP4_LL_ENABLED, NM_SETTING_IP4_LL_FALLBACK)
+                   && nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
+            /* ipv4.method=disabled has higher priority than the global
+             * ipv4.link-local=enabled / ipv4.link-local=fallback */
             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)) {
+                   && nm_streq(method, 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;
+        /* ipv4.link-local=auto means enabled for ipv4.method=link-local,
+         * and disabled for anything else */
+        if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+            link_local = NM_SETTING_IP4_LL_ENABLED;
+        } else {
+            link_local = NM_SETTING_IP4_LL_DISABLED;
+        }
+    }
+
+    if (link_local == NM_SETTING_IP4_LL_FALLBACK
+        && nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+        /* ipv4.link-local=fallback with ipv4.method=link-local will
+         * always be on anyway, simplify logic */
+        link_local = NM_SETTING_IP4_LL_ENABLED;
     }
 
+    nm_assert(NM_IN_SET(link_local,
+                        NM_SETTING_IP4_LL_DISABLED,
+                        NM_SETTING_IP4_LL_ENABLED,
+                        NM_SETTING_IP4_LL_FALLBACK));
+
     return link_local;
 }
 
@@ -1806,6 +1857,29 @@ _prop_get_ipvx_may_fail_cached(NMDevice *self, int addr_family, NMTernary *cache
     return _CACHED_BOOL(cache, _prop_get_ipvx_may_fail(self, addr_family));
 }
 
+static gboolean
+_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self)
+{
+    NMSettingIP4Config               *s_ip4;
+    NMSettingIP4DhcpIpv6OnlyPreferred ipv6_only;
+
+    s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
+    if (!s_ip4)
+        return FALSE;
+
+    ipv6_only = nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(s_ip4);
+    if (ipv6_only != NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT)
+        return ipv6_only;
+
+    return nm_config_data_get_connection_default_int64(
+        NM_CONFIG_GET_DATA,
+        NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"),
+        self,
+        NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO,
+        NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
+        NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO);
+}
+
 /**
  * _prop_get_ipvx_dhcp_iaid:
  * @self: the #NMDevice
@@ -2005,6 +2079,59 @@ _prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
         return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP6;
 }
 
+static gboolean
+_prop_get_ipvx_dhcp_send_hostname(NMDevice *self, int addr_family)
+{
+    const int          IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMSettingIPConfig *s_ip    = IS_IPv4
+                                     ? nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG)
+                                     : nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
+    gboolean           send_hostname;
+    gboolean           send_hostname_v2;
+
+    g_return_val_if_fail(s_ip, FALSE);
+
+    send_hostname    = nm_setting_ip_config_get_dhcp_send_hostname(s_ip);
+    send_hostname_v2 = nm_setting_ip_config_get_dhcp_send_hostname_v2(s_ip);
+
+    if (send_hostname_v2 == NM_TERNARY_DEFAULT) {
+        send_hostname_v2 = nm_config_data_get_connection_default_int64(
+            NM_CONFIG_GET_DATA,
+            IS_IPv4 ? NM_CON_DEFAULT("ipv4.dhcp-send-hostname")
+                    : NM_CON_DEFAULT("ipv6.dhcp-send-hostname"),
+            self,
+            NM_TERNARY_FALSE,
+            NM_TERNARY_TRUE,
+            send_hostname ? NM_TERNARY_TRUE : NM_TERNARY_FALSE);
+    }
+
+    return send_hostname_v2;
+}
+
+static gboolean
+_prop_get_connection_ip_ping_addresses_require_all(NMDevice *self, NMSettingConnection *s_con)
+{
+    NMTernary   ip_ping_addresses_require_all;
+    const char *s;
+
+    ip_ping_addresses_require_all = nm_setting_connection_get_ip_ping_addresses_require_all(s_con);
+
+    if (ip_ping_addresses_require_all != NM_TERNARY_DEFAULT) {
+        return ip_ping_addresses_require_all;
+    } else {
+        s = nm_config_data_get_connection_default(
+            NM_CONFIG_GET_DATA,
+            NM_CON_DEFAULT("connection.ip-ping-addresses-require-all"),
+            self);
+
+        if (s) {
+            return _nm_utils_ascii_str_to_bool(s, FALSE);
+        }
+    }
+
+    return FALSE;
+}
+
 static const char *
 _prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con)
 {
@@ -2564,6 +2691,19 @@ _ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *etht
 }
 
 static void
+_ethtool_fec_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
+{
+    if (ethtool_state->fec_mode) {
+        if (!nm_platform_ethtool_set_fec_mode(platform,
+                                              ethtool_state->ifindex,
+                                              ethtool_state->fec_mode))
+            _LOGW(LOGD_DEVICE, "ethtool: failure resetting FEC");
+        else
+            _LOGD(LOGD_DEVICE, "ethtool: FEC successfully reset");
+    }
+}
+
+static void
 _ethtool_features_set(NMDevice         *self,
                       NMPlatform       *platform,
                       EthtoolState     *ethtool_state,
@@ -2595,6 +2735,55 @@ _ethtool_features_set(NMDevice         *self,
 }
 
 static void
+_ethtool_fec_set(NMDevice         *self,
+                 NMPlatform       *platform,
+                 EthtoolState     *ethtool_state,
+                 NMSettingEthtool *s_ethtool)
+{
+    uint32_t       old_fec_mode;
+    uint32_t       fec_mode = NM_SETTING_ETHTOOL_FEC_MODE_NONE;
+    GHashTable    *hash;
+    GHashTableIter iter;
+    const char    *name;
+    GVariant      *variant;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
+    nm_assert(ethtool_state);
+    nm_assert(!ethtool_state->fec_mode);
+
+    hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
+    if (!hash)
+        return;
+
+    g_hash_table_iter_init(&iter, hash);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
+        NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
+
+        if (!nm_ethtool_id_is_fec(ethtool_id))
+            continue;
+
+        nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
+        fec_mode = g_variant_get_uint32(variant);
+    }
+
+    nm_platform_ethtool_get_fec_mode(platform, ethtool_state->ifindex, &old_fec_mode);
+
+    /* The NM_SETTING_ETHTOOL_FEC_MODE_NONE is query only value, hence do nothing. */
+    if (!fec_mode || fec_mode == NM_SETTING_ETHTOOL_FEC_MODE_NONE) {
+        return;
+    }
+
+    if (!nm_platform_ethtool_set_fec_mode(platform, ethtool_state->ifindex, fec_mode))
+        _LOGW(LOGD_DEVICE, "ethtool: failure setting FEC %d", fec_mode);
+    else {
+        _LOGD(LOGD_DEVICE, "ethtool: FEC %d successfully set", fec_mode);
+        ethtool_state->fec_mode = old_fec_mode;
+    }
+}
+
+static void
 _ethtool_coalesce_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
 {
     gs_free NMEthtoolCoalesceState *coalesce = NULL;
@@ -3075,6 +3264,7 @@ _ethtool_state_reset(NMDevice *self)
     _ethtool_pause_reset(self, platform, ethtool_state);
     _ethtool_channels_reset(self, platform, ethtool_state);
     _ethtool_eee_reset(self, platform, ethtool_state);
+    _ethtool_fec_reset(self, platform, ethtool_state);
 }
 
 static void
@@ -3111,9 +3301,11 @@ _ethtool_state_set(NMDevice *self)
     _ethtool_pause_set(self, platform, ethtool_state, s_ethtool);
     _ethtool_channels_set(self, platform, ethtool_state, s_ethtool);
     _ethtool_eee_set(self, platform, ethtool_state, s_ethtool);
+    _ethtool_fec_set(self, platform, ethtool_state, s_ethtool);
 
     if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring
-        || ethtool_state->pause || ethtool_state->channels || ethtool_state->eee)
+        || ethtool_state->pause || ethtool_state->channels || ethtool_state->eee
+        || ethtool_state->fec_mode != 0)
         priv->ethtool_state = g_steal_pointer(&ethtool_state);
 }
 
@@ -3124,7 +3316,7 @@ link_properties_fill_from_setting(NMDevice *self, NMPlatformLinkProps *props)
     NMSettingLink            *s_link;
     gint64                    v;
 
-    *props = (NMPlatformLinkProps){};
+    *props = (NMPlatformLinkProps) {};
 
     s_link = nm_device_get_applied_setting(self, NM_TYPE_SETTING_LINK);
     if (!s_link)
@@ -3336,7 +3528,7 @@ nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source)
     return nm_l3_config_data_new(nm_device_get_multi_index(self), ifindex, source);
 }
 
-const NML3ConfigData *
+NML3ConfigData *
 nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *connection)
 {
     NML3ConfigData *l3cd;
@@ -4610,15 +4802,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
     nm_assert(l3cfg == priv->l3cfg);
 
     switch (notify_data->notify_type) {
-    case NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED:
-        if (notify_data->l3cd_changed.commited) {
-            g_signal_emit(self,
-                          signals[L3CD_CHANGED],
-                          0,
-                          notify_data->l3cd_changed.l3cd_old,
-                          notify_data->l3cd_changed.l3cd_new);
-        }
-        return;
     case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
     {
         const NML3AcdAddrInfo *addr_info = &notify_data->acd_event.info;
@@ -4646,16 +4829,25 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
         const NML3ConfigData *l3cd;
         NMDeviceState         state = nm_device_get_state(self);
 
+        l3cd = nm_l3cfg_get_combined_l3cd(l3cfg, TRUE);
         if (state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
             /* FIXME(l3cfg): MTU handling should be moved to l3cfg. */
-            l3cd = nm_l3cfg_get_combined_l3cd(l3cfg, TRUE);
             if (l3cd)
                 priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu(l3cd);
             _commit_mtu(self);
         }
+        _dev_ipll4_check_fallback(self, l3cd);
         return;
     }
     case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
+        if (notify_data->commit.l3cd_changed) {
+            g_signal_emit(self,
+                          signals[L3CD_CHANGED],
+                          0,
+                          notify_data->commit.l3cd_old,
+                          notify_data->commit.l3cd_new);
+        }
+
         if (priv->ipshared_data_4.state == NM_DEVICE_IP_STATE_PENDING
             && !priv->ipshared_data_4.v4.dnsmasq_manager && priv->ipshared_data_4.v4.l3cd) {
             _dev_ipshared4_spawn_dnsmasq(self);
@@ -5635,6 +5827,8 @@ nm_device_get_route_metric_default(NMDeviceType device_type)
         return 400;
     case NM_DEVICE_TYPE_MACVLAN:
         return 410;
+    case NM_DEVICE_TYPE_IPVLAN:
+        return 420;
     case NM_DEVICE_TYPE_BRIDGE:
         return 425;
     case NM_DEVICE_TYPE_TUN:
@@ -7971,9 +8165,9 @@ sriov_op_queue(NMDevice                *self,
      * grace period we pull the plug and cancel it. */
 
     op  = g_slice_new(SriovOp);
-    *op = (SriovOp){
+    *op = (SriovOp) {
         .sriov_params =
-            (NMPlatformSriovParams){
+            (NMPlatformSriovParams) {
                 .num_vfs             = num_vfs,
                 .autoprobe           = autoprobe,
                 .eswitch_mode        = (_NMSriovEswitchMode) eswitch_mode,
@@ -8554,7 +8748,7 @@ port_state_changed(NMDevice           *port,
 /**
  * nm_device_controller_add_port:
  * @self: the controller device
- * @port: the port device to attach as port 
+ * @port: the port device to attach as port
  * @configure: pass %TRUE if the port should be configured by the controller, or
  * %FALSE if it is already configured outside NetworkManager
  *
@@ -10722,6 +10916,25 @@ _dev_ipll4_start(NMDevice *self)
         nm_l3_ipv4ll_register_new(priv->ipll_data_4.v4.ipv4ll, timeout_msec);
 }
 
+static void
+_dev_ipll4_check_fallback(NMDevice *self, const NML3ConfigData *l3cd_new)
+{
+    gboolean         has_non_ll;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (!l3cd_new || priv->ipll_data_4.v4.mode != NM_SETTING_IP4_LL_FALLBACK) {
+        return;
+    }
+
+    has_non_ll = nm_l3_config_data_get_flags(l3cd_new) & NM_L3_CONFIG_DAT_FLAGS_HAS_IPV4_NON_LL;
+    _LOGT_ipll(AF_INET, "%s fallback", has_non_ll ? "cleanup" : "start");
+    if (has_non_ll) {
+        _dev_ipllx_cleanup(self, AF_INET);
+    } else {
+        _dev_ipll4_start(self);
+    }
+}
+
 /*****************************************************************************/
 
 static const char *
@@ -10963,8 +11176,8 @@ _dev_ipmanual_check_ready(NMDevice *self)
 static void
 _dev_ipmanual_start(NMDevice *self)
 {
-    NMDevicePrivate                         *priv = NM_DEVICE_GET_PRIVATE(self);
-    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+    NMDevicePrivate                        *priv = NM_DEVICE_GET_PRIVATE(self);
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
 
     if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_NONE
         || priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_NONE)
@@ -10974,6 +11187,13 @@ _dev_ipmanual_start(NMDevice *self)
         l3cd =
             nm_device_create_l3_config_data_from_connection(self,
                                                             nm_device_get_applied_connection(self));
+
+        if (_prop_get_ipvx_routed_dns(self, AF_INET) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) {
+            nm_l3_config_data_set_routed_dns(l3cd, AF_INET, TRUE);
+        }
+        if (_prop_get_ipvx_routed_dns(self, AF_INET6) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) {
+            nm_l3_config_data_set_routed_dns(l3cd, AF_INET6, TRUE);
+        }
     }
 
     if (!l3cd) {
@@ -11130,6 +11350,8 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d
             const NML3ConfigData *dhcp_l3cd = priv->l3cds[L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4)].d;
 
             _LOGT_ipdhcp(addr_family, "lease lost");
+            _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_PENDING);
+            _dev_ip_state_check_async(self, addr_family);
             if (dhcp_l3cd
                 && nm_l3cfg_remove_config(
                     priv->l3cfg,
@@ -11273,7 +11495,8 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         gboolean               hostname_is_fqdn;
         gboolean               send_client_id;
         guint8                 dscp;
-        gboolean               dscp_explicit = FALSE;
+        gboolean               dscp_explicit  = FALSE;
+        gboolean               ipv6_only_pref = FALSE;
 
         client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr, &send_client_id);
         dscp      = _prop_get_ipv4_dhcp_dscp(self, &dscp_explicit);
@@ -11292,7 +11515,18 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             hostname         = nm_setting_ip_config_get_dhcp_hostname(s_ip);
         }
 
-        config = (NMDhcpClientConfig){
+        if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self)) {
+            if (nm_streq0(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+                _LOGI_ipdhcp(
+                    addr_family,
+                    "not requesting the \"IPv6-only preferred\" option because IPv6 is disabled");
+            } else {
+                _LOGD_ipdhcp(addr_family, "requesting the \"IPv6-only preferred\" option");
+                ipv6_only_pref = TRUE;
+            }
+        }
+
+        config = (NMDhcpClientConfig) {
             .addr_family             = AF_INET,
             .l3cfg                   = nm_device_get_l3cfg(self),
             .iface                   = nm_device_get_ip_iface(self),
@@ -11300,7 +11534,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             .uuid                    = nm_connection_get_uuid(connection),
             .hwaddr                  = hwaddr,
             .bcast_hwaddr            = bcast_hwaddr,
-            .send_hostname           = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
+            .send_hostname           = _prop_get_ipvx_dhcp_send_hostname(self, AF_INET),
             .hostname                = hostname,
             .hostname_flags          = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
             .client_id               = client_id,
@@ -11312,11 +11546,12 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             .reject_servers          = reject_servers,
             .v4 =
                 {
-                    .request_broadcast = request_broadcast,
-                    .acd_timeout_msec  = _prop_get_ipv4_dad_timeout(self),
-                    .send_client_id    = send_client_id,
-                    .dscp              = dscp,
-                    .dscp_explicit     = dscp_explicit,
+                    .request_broadcast   = request_broadcast,
+                    .acd_timeout_msec    = _prop_get_ipv4_dad_timeout(self),
+                    .send_client_id      = send_client_id,
+                    .dscp                = dscp,
+                    .dscp_explicit       = dscp_explicit,
+                    .ipv6_only_preferred = ipv6_only_pref,
                 },
             .previous_lease = priv->l3cds[L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4)].d,
         };
@@ -11333,13 +11568,13 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_explicit);
         duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
 
-        config = (NMDhcpClientConfig){
+        config = (NMDhcpClientConfig) {
             .addr_family     = AF_INET6,
             .l3cfg           = nm_device_get_l3cfg(self),
             .iface           = nm_device_get_ip_iface(self),
             .iface_type_log  = nm_device_get_type_desc_for_log(self),
             .uuid            = nm_connection_get_uuid(connection),
-            .send_hostname   = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
+            .send_hostname   = _prop_get_ipvx_dhcp_send_hostname(self, AF_INET6),
             .hostname        = nm_setting_ip_config_get_dhcp_hostname(s_ip),
             .hostname_flags  = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
             .client_id       = duid,
@@ -12629,6 +12864,9 @@ _dev_sysctl_save_ip6_properties(NMDevice *self)
     if (!ifname)
         return;
 
+    if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR))
+        return;
+
     for (i = 0; i < G_N_ELEMENTS(ip6_properties_to_save); i++) {
         value =
             nm_platform_sysctl_ip_conf_get(platform, AF_INET6, ifname, ip6_properties_to_save[i]);
@@ -12648,6 +12886,9 @@ _dev_sysctl_restore_ip6_properties(NMDevice *self)
     gpointer         key;
     gpointer         value;
 
+    if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR))
+        return;
+
     g_hash_table_iter_init(&iter, priv->ip6_saved_properties);
     while (g_hash_table_iter_next(&iter, &key, &value))
         nm_device_sysctl_ip_conf_set(self, AF_INET6, key, value);
@@ -12769,7 +13010,7 @@ get_ip_method_auto(NMDevice *self, int addr_family)
 }
 
 static void
-activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family, const char *method)
+activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family)
 {
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
@@ -12819,30 +13060,31 @@ 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)
+        priv->ipll_data_4.v4.mode = _prop_get_ipv4_link_local(self);
+        if (priv->ipll_data_4.v4.mode == NM_SETTING_IP4_LL_ENABLED)
             _dev_ipll4_start(self);
 
-        if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+        if (nm_streq(priv->ipv4_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)) {
+        else if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
             /* pass */
-        } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+        } else if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
             _dev_ipshared4_start(self);
-        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+        else if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
             priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
-        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+        else if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
             /* pass */
         } else
             nm_assert_not_reached();
     }
 
     if (!IS_IPv4) {
-        if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+        if (nm_streq(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
             if (!priv->ip_data_x[IS_IPv4].is_disabled) {
                 priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
                 nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
             }
-        } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+        } else if (nm_streq(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
             if (!priv->ip_data_x[IS_IPv4].is_ignore) {
                 priv->ip_data_x[IS_IPv4].is_ignore = TRUE;
                 if (priv->controller) {
@@ -12875,15 +13117,15 @@ activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family, const
         } else {
             _dev_ipll6_start(self);
 
-            if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))
+            if (NM_IN_STRSET(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))
                 _dev_ipac6_start(self);
-            else if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED))
+            else if (NM_IN_STRSET(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_SHARED))
                 _dev_ipshared6_start(self);
-            else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+            else if (nm_streq(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
                 priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
                 _dev_ipdhcpx_start(self, AF_INET6);
             } else
-                nm_assert(NM_IN_STRSET(method,
+                nm_assert(NM_IN_STRSET(priv->ipv6_method,
                                        NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
                                        NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL));
         }
@@ -12984,8 +13226,6 @@ activate_stage3_ip_config(NMDevice *self)
     NMDevicePrivate *priv  = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceClass   *klass = NM_DEVICE_GET_CLASS(self);
     int              ifindex;
-    const char      *ipv4_method;
-    const char      *ipv6_method;
 
     /* stage3 is different from stage1+2.
      *
@@ -13035,17 +13275,17 @@ activate_stage3_ip_config(NMDevice *self)
     }
     nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
 
-    ipv4_method = nm_device_get_effective_ip_config_method(self, AF_INET);
-    if (nm_streq(ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+    priv->ipv4_method = nm_device_get_effective_ip_config_method(self, AF_INET);
+    if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
         /* "auto" usually means DHCPv4 or autoconf6, but it doesn't have to be. Subclasses
          * can overwrite it. For example, you cannot run DHCPv4 on PPP/WireGuard links. */
-        ipv4_method = klass->get_ip_method_auto(self, AF_INET);
+        priv->ipv4_method = klass->get_ip_method_auto(self, AF_INET);
     }
 
-    ipv6_method = nm_device_get_effective_ip_config_method(self, AF_INET6);
+    priv->ipv6_method = nm_device_get_effective_ip_config_method(self, AF_INET6);
     if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
         _NMLOG_ip((nm_device_managed_type_is_external(self)
-                   || NM_IN_STRSET(ipv6_method,
+                   || NM_IN_STRSET(priv->ipv6_method,
                                    NM_SETTING_IP6_CONFIG_METHOD_AUTO,
                                    NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
                                    NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
@@ -13053,9 +13293,9 @@ activate_stage3_ip_config(NMDevice *self)
                       : LOGL_WARN,
                   AF_INET6,
                   "IPv6 not supported by kernel resulting in \"ipv6.method=disabled\"");
-        ipv6_method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
-    } else if (nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
-        ipv6_method = klass->get_ip_method_auto(self, AF_INET6);
+        priv->ipv6_method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+    } else if (nm_streq(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+        priv->ipv6_method = klass->get_ip_method_auto(self, AF_INET6);
     }
 
     if (priv->ip_data_4.do_reapply) {
@@ -13064,7 +13304,7 @@ activate_stage3_ip_config(NMDevice *self)
         _cleanup_ip_pre(self,
                         AF_INET,
                         CLEANUP_TYPE_KEEP_REAPPLY,
-                        nm_streq(ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO));
+                        nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO));
     }
     if (priv->ip_data_6.do_reapply) {
         _LOGD_ip(AF_INET6, "reapply...");
@@ -13072,7 +13312,7 @@ activate_stage3_ip_config(NMDevice *self)
         _cleanup_ip_pre(self,
                         AF_INET6,
                         CLEANUP_TYPE_KEEP_REAPPLY,
-                        nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO));
+                        nm_streq(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO));
     }
 
     if (priv->state < NM_DEVICE_STATE_IP_CONFIG) {
@@ -13103,7 +13343,7 @@ activate_stage3_ip_config(NMDevice *self)
     if (!nm_device_managed_type_is_external(self)
         && (!klass->ready_for_ip_config || klass->ready_for_ip_config(self, TRUE))) {
         if (priv->ipmanual_data.state_6 == NM_DEVICE_IP_STATE_NONE
-            && !NM_IN_STRSET(ipv6_method,
+            && !NM_IN_STRSET(priv->ipv6_method,
                              NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
                              NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
             /* Ensure the MTU makes sense. If it was below 1280 the kernel would not
@@ -13127,8 +13367,8 @@ activate_stage3_ip_config(NMDevice *self)
         _dev_ipmanual_start(self);
     }
 
-    activate_stage3_ip_config_for_addr_family(self, AF_INET, ipv4_method);
-    activate_stage3_ip_config_for_addr_family(self, AF_INET6, ipv6_method);
+    activate_stage3_ip_config_for_addr_family(self, AF_INET);
+    activate_stage3_ip_config_for_addr_family(self, AF_INET6);
 }
 
 void
@@ -13349,6 +13589,9 @@ _dev_ipshared4_spawn_dnsmasq(NMDevice *self)
     NMConnection          *applied;
     gs_unref_array GArray *conflicts = NULL;
     gboolean               ready;
+    NMSettingIPConfig     *s_ip4 = NULL;
+    const char            *shared_dhcp_range;
+    int                    shared_dhcp_lease_time;
 
     nm_assert(priv->ipshared_data_4.v4.firewall_config);
     nm_assert(priv->ipshared_data_4.v4.dnsmasq_state_id == 0);
@@ -13394,9 +13637,14 @@ _dev_ipshared4_spawn_dnsmasq(NMDevice *self)
         break;
     }
 
+    s_ip4                  = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
+    shared_dhcp_range      = nm_setting_ip_config_get_shared_dhcp_range(s_ip4);
+    shared_dhcp_lease_time = nm_setting_ip_config_get_shared_dhcp_lease_time(s_ip4);
     priv->ipshared_data_4.v4.dnsmasq_manager = nm_dnsmasq_manager_new(ip_iface);
     if (!nm_dnsmasq_manager_start(priv->ipshared_data_4.v4.dnsmasq_manager,
                                   priv->ipshared_data_4.v4.l3cd,
+                                  shared_dhcp_range,
+                                  shared_dhcp_lease_time,
                                   announce_android_metered,
                                   &error)) {
         _LOGW_ipshared(AF_INET, "could not start dnsmasq: %s", error->message);
@@ -13567,8 +13815,11 @@ _cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gbool
 
     _dev_ipdhcpx_cleanup(self, addr_family, !preserve_dhcp || !keep_reapply, FALSE);
 
-    if (!IS_IPv4)
+    if (IS_IPv4) {
+        priv->ipll_data_4.v4.mode = NM_SETTING_IP4_LL_DISABLED;
+    } else {
         _dev_ipac6_cleanup(self);
+    }
 
     _dev_ipllx_cleanup(self, addr_family);
 
@@ -14125,7 +14376,7 @@ impl_device_reapply(NMDBusObject                      *obj,
     }
 
     reapply_data  = g_slice_new(ReapplyData);
-    *reapply_data = (ReapplyData){
+    *reapply_data = (ReapplyData) {
         .connection    = connection,
         .version_id    = version_id,
         .reapply_flags = reapply_flags,
@@ -14708,6 +14959,37 @@ _dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_da
 
 /*****************************************************************************/
 
+typedef struct {
+    NMLogDomain log_domain;
+    NMDevice   *device;
+    gboolean    ping_addresses_require_all;
+    GSource    *watch;
+    GPid        pid;
+    char       *binary;
+    char       *address;
+    guint       deadline;
+} PingOperation;
+
+static PingOperation *
+ping_operation_new(NMDevice   *self,
+                   NMLogDomain log_domain,
+                   const char *address,
+                   const char *ping_binary,
+                   guint       ping_timeout,
+                   gboolean    ip_ping_addresses_require_all)
+{
+    PingOperation *ping_op = g_new0(PingOperation, 1);
+
+    ping_op->device                     = self;
+    ping_op->log_domain                 = log_domain;
+    ping_op->address                    = g_strdup(address);
+    ping_op->binary                     = g_strdup(ping_binary);
+    ping_op->deadline                   = ping_timeout + 10;
+    ping_op->ping_addresses_require_all = ip_ping_addresses_require_all;
+
+    return ping_op;
+}
+
 static void
 ip_check_pre_up(NMDevice *self)
 {
@@ -14730,49 +15012,50 @@ ip_check_pre_up(NMDevice *self)
 }
 
 static void
-ip_check_gw_ping_cleanup(NMDevice *self)
+cleanup_ping_operation(PingOperation *ping_op)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    nm_clear_g_source(&priv->gw_ping.watch);
-    nm_clear_g_source(&priv->gw_ping.timeout);
+    if (ping_op->watch) {
+        nm_clear_g_source_inst(&ping_op->watch);
+    }
 
-    if (priv->gw_ping.pid) {
-        nm_utils_kill_child_async(priv->gw_ping.pid,
+    if (ping_op->pid) {
+        nm_utils_kill_child_async(ping_op->pid,
                                   SIGTERM,
-                                  priv->gw_ping.log_domain,
+                                  ping_op->log_domain,
                                   "ping",
                                   1000,
                                   NULL,
                                   NULL);
-        priv->gw_ping.pid = 0;
+        ping_op->pid = 0;
     }
 
-    nm_clear_g_free(&priv->gw_ping.binary);
-    nm_clear_g_free(&priv->gw_ping.address);
+    nm_clear_g_free(&ping_op->binary);
+    nm_clear_g_free(&ping_op->address);
+
+    g_free(ping_op);
 }
 
 static gboolean
-spawn_ping(NMDevice *self)
+spawn_ping_for_operation(NMDevice *self, PingOperation *ping_op)
 {
-    NMDevicePrivate      *priv        = NM_DEVICE_GET_PRIVATE(self);
     gs_free char         *str_timeout = NULL;
     gs_free char         *tmp_str     = NULL;
-    const char           *args[]      = {priv->gw_ping.binary,
+    const char           *args[]      = {ping_op->binary,
                                          "-I",
                                          nm_device_get_ip_iface(self),
                                          "-c",
                                          "1",
                                          "-w",
                                          NULL,
-                                         priv->gw_ping.address,
+                                         ping_op->address,
                                          NULL};
     gs_free_error GError *error       = NULL;
     gboolean              ret;
 
-    args[6] = str_timeout = g_strdup_printf("%u", priv->gw_ping.deadline);
-    tmp_str               = g_strjoinv(" ", (char **) args);
-    _LOGD(priv->gw_ping.log_domain, "ping: running '%s'", tmp_str);
+    args[6] = str_timeout = g_strdup_printf("%u", ping_op->deadline);
+
+    tmp_str = g_strjoinv(" ", (char **) args);
+    _LOGD(ping_op->log_domain, "ping: running '%s'", tmp_str);
 
     ret = g_spawn_async("/",
                         (char **) args,
@@ -14780,14 +15063,13 @@ spawn_ping(NMDevice *self)
                         G_SPAWN_DO_NOT_REAP_CHILD,
                         NULL,
                         NULL,
-                        &priv->gw_ping.pid,
+                        &ping_op->pid,
                         &error);
 
-    if (!ret) {
-        _LOGW(priv->gw_ping.log_domain,
-              "ping: could not spawn %s: %s",
-              priv->gw_ping.binary,
-              error->message);
+    if (ret) {
+        ping_op->watch = nm_g_child_watch_add_source(ping_op->pid, ip_check_ping_watch_cb, ping_op);
+    } else {
+        _LOGD(ping_op->log_domain, "ping: could not spawn %s: %s", ping_op->binary, error->message);
     }
 
     return ret;
@@ -14796,16 +15078,19 @@ spawn_ping(NMDevice *self)
 static gboolean
 respawn_ping_cb(gpointer user_data)
 {
-    NMDevice        *self = NM_DEVICE(user_data);
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    PingOperation   *ping_op = (PingOperation *) user_data;
+    NMDevice        *self    = ping_op->device;
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
 
-    priv->gw_ping.watch = 0;
+    nm_clear_g_source_inst(&ping_op->watch);
 
-    if (spawn_ping(self)) {
-        priv->gw_ping.watch = g_child_watch_add(priv->gw_ping.pid, ip_check_ping_watch_cb, self);
-    } else {
-        ip_check_gw_ping_cleanup(self);
-        ip_check_pre_up(self);
+    if (!spawn_ping_for_operation(self, ping_op)) {
+        cleanup_ping_operation(ping_op);
+        priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
+
+        if (g_list_length(priv->ping_operations) == 0) {
+            ip_check_pre_up(self);
+        }
     }
 
     return FALSE;
@@ -14814,34 +15099,64 @@ respawn_ping_cb(gpointer user_data)
 static void
 ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
 {
-    NMDevice        *self       = NM_DEVICE(user_data);
-    NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
-    NMLogDomain      log_domain = priv->gw_ping.log_domain;
-    gboolean         success    = FALSE;
+    PingOperation   *ping_op = (PingOperation *) user_data;
+    NMDevice        *self    = ping_op->device;
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    gboolean         success = FALSE;
 
-    if (!priv->gw_ping.watch)
+    if (!ping_op->watch)
         return;
-    priv->gw_ping.watch = 0;
-    priv->gw_ping.pid   = 0;
+
+    nm_clear_g_source_inst(&ping_op->watch);
+    ping_op->pid = 0;
 
     if (WIFEXITED(status)) {
         if (WEXITSTATUS(status) == 0) {
-            _LOGD(log_domain, "ping: gateway ping succeeded");
+            _LOGD(ping_op->log_domain, "ping: ping succeeded on %s", ping_op->address);
             success = TRUE;
         } else {
-            _LOGW(log_domain, "ping: gateway ping failed with error code %d", WEXITSTATUS(status));
+            _LOGD(ping_op->log_domain,
+                  "ping: ping failed with error code %d on %s",
+                  WEXITSTATUS(status),
+                  ping_op->address);
         }
-    } else
-        _LOGW(log_domain, "ping: stopped unexpectedly with status %d", status);
+    } else {
+        _LOGD(ping_op->log_domain,
+              "ping: stopped unexpectedly with status %d on %s",
+              status,
+              ping_op->address);
+    }
 
     if (success) {
-        /* We've got connectivity, proceed to pre_up */
-        ip_check_gw_ping_cleanup(self);
-        ip_check_pre_up(self);
+        if (ping_op->ping_addresses_require_all) {
+            cleanup_ping_operation(ping_op);
+            priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
+            if (g_list_length(priv->ping_operations) == 0) {
+                _LOGD(ping_op->log_domain,
+                      "ping: ip-ping-addresses requires all, all ping checks on ip-ping-addresses "
+                      "succeeded");
+                if (priv->ping_timeout)
+                    nm_clear_g_source_inst(&priv->ping_timeout);
+                ip_check_pre_up(self);
+            }
+        } else {
+            nm_assert(priv->ping_operations);
+
+            g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
+            priv->ping_operations = NULL;
+
+            if (priv->ping_timeout)
+                nm_clear_g_source_inst(&priv->ping_timeout);
+
+            _LOGD(ping_op->log_domain,
+                  "ping: ip-ping-addresses requires any, one ping check on ip-ping-addresses "
+                  "succeeded");
+            ip_check_pre_up(self);
+        }
     } else {
         /* If ping exited with an error it may have returned early,
          * wait 1 second and restart it */
-        priv->gw_ping.watch = g_timeout_add_seconds(1, respawn_ping_cb, self);
+        ping_op->watch = nm_g_timeout_add_seconds_source(1, respawn_ping_cb, ping_op);
     }
 }
 
@@ -14851,39 +15166,31 @@ ip_check_ping_timeout_cb(gpointer user_data)
     NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    priv->gw_ping.timeout = 0;
+    _LOGW(LOGD_DEVICE, "ping timeout: unreachable gateway or ip-ping-addresses");
 
-    _LOGW(priv->gw_ping.log_domain, "ping: gateway ping timed out");
+    if (priv->ping_operations) {
+        g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
+        priv->ping_operations = NULL;
+    }
 
-    ip_check_gw_ping_cleanup(self);
+    if (priv->ping_timeout)
+        nm_clear_g_source_inst(&priv->ping_timeout);
     ip_check_pre_up(self);
+
     return FALSE;
 }
 
 static gboolean
-start_ping(NMDevice   *self,
-           NMLogDomain log_domain,
-           const char *binary,
-           const char *address,
-           guint       timeout)
+start_ping(NMDevice *self, PingOperation *ping_op)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    g_return_val_if_fail(priv->gw_ping.watch == 0, FALSE);
-    g_return_val_if_fail(priv->gw_ping.timeout == 0, FALSE);
-
-    priv->gw_ping.log_domain = log_domain;
-    priv->gw_ping.address    = g_strdup(address);
-    priv->gw_ping.binary     = g_strdup(binary);
-    priv->gw_ping.deadline   = timeout + 10; /* the proper termination is enforced by a timer */
-
-    if (spawn_ping(self)) {
-        priv->gw_ping.watch   = g_child_watch_add(priv->gw_ping.pid, ip_check_ping_watch_cb, self);
-        priv->gw_ping.timeout = g_timeout_add_seconds(timeout, ip_check_ping_timeout_cb, self);
+    if (spawn_ping_for_operation(self, ping_op)) {
+        priv->ping_operations = g_list_append(priv->ping_operations, ping_op);
         return TRUE;
     }
 
-    ip_check_gw_ping_cleanup(self);
+    cleanup_ping_operation(ping_op);
     return FALSE;
 }
 
@@ -14893,18 +15200,19 @@ nm_device_start_ip_check(NMDevice *self)
     NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
     NMConnection        *connection;
     NMSettingConnection *s_con;
-    guint                timeout     = 0;
-    const char          *ping_binary = NULL;
+    guint                gw_ping_timeout = 0;
+    guint                ip_ping_timeout = 0;
+    const char          *ping_binary     = NULL;
     char                 buf[NM_INET_ADDRSTRLEN];
     NMLogDomain          log_domain = LOGD_IP4;
+    gboolean             ip_ping_addresses_require_all;
+    gboolean             ping_started = FALSE;
 
     /* Shouldn't be any active ping here, since IP_CHECK happens after the
      * first IP method completes.  Any subsequently completing IP method doesn't
      * get checked.
      */
-    g_return_if_fail(!priv->gw_ping.watch);
-    g_return_if_fail(!priv->gw_ping.timeout);
-    g_return_if_fail(!priv->gw_ping.pid);
+    g_return_if_fail(priv->ping_operations == NULL);
     g_return_if_fail(priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
                      || priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY);
 
@@ -14913,13 +15221,17 @@ nm_device_start_ip_check(NMDevice *self)
 
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
-    timeout = nm_setting_connection_get_gateway_ping_timeout(s_con);
+    gw_ping_timeout               = nm_setting_connection_get_gateway_ping_timeout(s_con);
+    ip_ping_addresses_require_all = _prop_get_connection_ip_ping_addresses_require_all(self, s_con);
+    ip_ping_timeout               = nm_setting_connection_get_ip_ping_timeout(s_con);
 
     buf[0] = '\0';
-    if (timeout) {
+    if (gw_ping_timeout != 0 && ip_ping_timeout == 0) {
         const NMPObject      *gw;
         const NML3ConfigData *l3cd;
 
+        _LOGD(LOGD_DEVICE, "starting ping gateway...");
+
         l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
         if (!l3cd) {
             /* pass */
@@ -14940,11 +15252,68 @@ nm_device_start_ip_check(NMDevice *self)
         }
     }
 
-    if (buf[0])
-        start_ping(self, log_domain, ping_binary, buf, timeout);
+    if (buf[0]) {
+        PingOperation *ping_op = ping_operation_new(self,
+                                                    log_domain,
+                                                    buf,
+                                                    ping_binary,
+                                                    gw_ping_timeout,
+                                                    ip_ping_addresses_require_all);
 
-    /* If no ping was started, just advance to pre_up */
-    if (!priv->gw_ping.pid)
+        if (start_ping(self, ping_op))
+            ping_started = TRUE;
+    }
+
+    if (gw_ping_timeout == 0 && ip_ping_timeout != 0) {
+        const NML3ConfigData *l3cd;
+        guint                 i;
+        GArray            *ip_ping_addresses = _nm_setting_connection_get_ip_ping_addresses(s_con);
+        const char *const *strv = nm_strvarray_get_strv_notempty(ip_ping_addresses, NULL);
+
+        _LOGD(LOGD_DEVICE, "starting ping ip addresses...");
+
+        l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
+
+        if (l3cd) {
+            for (i = 0; strv[i]; i++) {
+                const char     *s = strv[i];
+                struct in_addr  ipv4_addr;
+                struct in6_addr ipv6_addr;
+
+                if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
+                    && inet_pton(AF_INET, (const char *) s, &ipv4_addr)) {
+                    ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL);
+                    log_domain  = LOGD_IP4;
+                } else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY
+                           && inet_pton(AF_INET6, (const char *) s, &ipv6_addr)) {
+                    ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL);
+                    log_domain  = LOGD_IP6;
+                } else
+                    continue;
+
+                if (s[0]) {
+                    PingOperation *ping_op = ping_operation_new(self,
+                                                                log_domain,
+                                                                s,
+                                                                ping_binary,
+                                                                ip_ping_timeout,
+                                                                ip_ping_addresses_require_all);
+
+                    if (start_ping(self, ping_op))
+                        ping_started = TRUE;
+                }
+            }
+        }
+    }
+
+    if (ping_started) {
+        priv->ping_timeout =
+            nm_g_timeout_add_seconds_source(gw_ping_timeout ? gw_ping_timeout : ip_ping_timeout,
+                                            ip_check_ping_timeout_cb,
+                                            self);
+    }
+    /* If no ping was started, just advance to pre_up. */
+    else
         ip_check_pre_up(self);
 }
 
@@ -16403,7 +16772,14 @@ _cancel_activation(NMDevice *self)
     }
 
     _dispatcher_cleanup(self);
-    ip_check_gw_ping_cleanup(self);
+
+    if (priv->ping_operations) {
+        g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
+        priv->ping_operations = NULL;
+    }
+
+    if (priv->ping_timeout)
+        nm_clear_g_source_inst(&priv->ping_timeout);
 
     _dev_ip_state_cleanup(self, AF_INET, FALSE);
     _dev_ip_state_cleanup(self, AF_INET6, FALSE);
@@ -16629,6 +17005,9 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
         priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
     }
 
+    priv->ipv4_method = NULL;
+    priv->ipv6_method = NULL;
+
     _cleanup_generic_post(self, reason, cleanup_type);
 }
 
@@ -16643,6 +17022,9 @@ deactivate_reset_hw_addr(NMDevice *self)
 static void
 ip6_managed_setup(NMDevice *self)
 {
+    if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR))
+        return;
+
     _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
     _dev_sysctl_set_disable_ipv6(self, FALSE);
     nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
@@ -17138,7 +17520,12 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         break;
     }
     case NM_DEVICE_STATE_SECONDARIES:
-        ip_check_gw_ping_cleanup(self);
+        if (priv->ping_operations) {
+            g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
+            priv->ping_operations = NULL;
+        }
+        if (priv->ping_timeout)
+            nm_clear_g_source_inst(&priv->ping_timeout);
         _LOGD(LOGD_DEVICE, "device entered SECONDARIES state");
         break;
     default:
@@ -18382,7 +18769,7 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     resolver = priv->hostname_resolver_x[IS_IPv4];
     if (!resolver) {
         resolver  = g_slice_new(HostnameResolver);
-        *resolver = (HostnameResolver){
+        *resolver = (HostnameResolver) {
             .device      = self,
             .addr_family = addr_family,
             .state       = RESOLVER_WAIT_ADDRESS,
@@ -18737,7 +19124,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         nm_assert(priv->type == NM_DEVICE_TYPE_UNKNOWN);
         priv->type = g_value_get_uint(value);
         nm_assert(priv->type > NM_DEVICE_TYPE_UNKNOWN);
-        nm_assert(priv->type <= NM_DEVICE_TYPE_HSR);
+        nm_assert(priv->type <= NM_DEVICE_TYPE_IPVLAN);
         break;
     case PROP_LINK_TYPE:
         /* construct-only */
@@ -19525,6 +19912,9 @@ nm_device_class_init(NMDeviceClass *klass)
                                                 G_TYPE_BOOLEAN,
                                                 0);
 
+    /* Signal "l3cd-changed" indicates that the combined layer-3 configuration
+     * on the device has changed. It is invoked after the new configuration has
+     * been committed to kernel. */
     signals[L3CD_CHANGED] = g_signal_new(NM_DEVICE_L3CD_CHANGED,
                                          G_OBJECT_CLASS_TYPE(object_class),
                                          G_SIGNAL_RUN_FIRST,
diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c
index 59c8f54c..e08b379a 100644
--- a/src/core/devices/nm-lldp-listener.c
+++ b/src/core/devices/nm-lldp-listener.c
@@ -397,7 +397,7 @@ lldp_neighbor_new(NMLldpNeighbor *neighbor_nm)
     }
 
     neigh  = g_slice_new(LldpNeighbor);
-    *neigh = (LldpNeighbor){
+    *neigh = (LldpNeighbor) {
         .neighbor_nm     = nm_lldp_neighbor_ref(neighbor_nm),
         .chassis_id_type = chassis_id_type,
         .chassis_id      = g_steal_pointer(&s_chassis_id),
@@ -522,7 +522,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
             if (len <= 6)
                 continue;
 
-                /* skip over leading TLV, OUI and subtype */
+            /* skip over leading TLV, OUI and subtype */
 #if NM_MORE_ASSERTS > 5
             {
                 guint8 check_hdr[] = {0xfe | (((len - 2) >> 8) & 0x01),
@@ -721,7 +721,7 @@ nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len)
     g_assert(raw_data);
     g_assert(raw_len > 0);
 
-    lldp_rx = nm_lldp_rx_new(&((NMLldpRXConfig){
+    lldp_rx = nm_lldp_rx_new(&((NMLldpRXConfig) {
         .ifindex       = 1,
         .neighbors_max = MAX_NEIGHBORS,
         .callback      = nmtst_lldp_event_handler,
@@ -889,7 +889,7 @@ nm_lldp_listener_new(int                  ifindex,
     g_return_val_if_fail(notify_callback, FALSE);
 
     self  = g_slice_new(NMLldpListener);
-    *self = (NMLldpListener){
+    *self = (NMLldpListener) {
         .ifindex          = ifindex,
         .notify_callback  = notify_callback,
         .notify_user_data = notify_user_data,
@@ -897,7 +897,7 @@ nm_lldp_listener_new(int                  ifindex,
 
     nm_assert(nm_g_main_context_is_thread_default(g_main_context_default()));
 
-    lldp_rx = nm_lldp_rx_new(&((NMLldpRXConfig){
+    lldp_rx = nm_lldp_rx_new(&((NMLldpRXConfig) {
         .ifindex       = ifindex,
         .neighbors_max = MAX_NEIGHBORS,
         .callback      = lldp_event_handler,
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 8aeb8718..06a1da15 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -603,7 +603,7 @@ deactivate_async(NMDevice                  *device,
      * with a timeout.
      */
     data  = g_slice_new(DeactivateData);
-    *data = (DeactivateData){
+    *data = (DeactivateData) {
         .self               = g_object_ref(self),
         .cancellable        = g_object_ref(cancellable),
         .callback           = callback,
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index 2e8ab717..7eacedb8 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -175,7 +175,7 @@ attach_port(NMDevice                  *device,
     }
 
     data  = g_slice_new(AttachPortData);
-    *data = (AttachPortData){
+    *data = (AttachPortData) {
         .device             = g_object_ref(device),
         .port               = g_object_ref(port),
         .cancellable        = g_object_ref(cancellable),
@@ -233,7 +233,7 @@ detach_port(NMDevice                  *device,
         AttachPortData *data;
 
         data  = g_slice_new(AttachPortData);
-        *data = (AttachPortData){
+        *data = (AttachPortData) {
             .device             = g_object_ref(device),
             .port               = g_object_ref(port),
             .cancellable        = nm_g_object_ref(cancellable),
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 8e32cff5..528d44d8 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -205,7 +205,7 @@ static void     cleanup_check_ready(NMOvsdb *self);
 /*****************************************************************************/
 
 #define OVSDB_METHOD_PAYLOAD_MONITOR() \
-    (&((const OvsdbMethodPayload){     \
+    (&((const OvsdbMethodPayload) {    \
         .monitor = {},                 \
     }))
 
@@ -214,7 +214,7 @@ static void     cleanup_check_ready(NMOvsdb *self);
                                            xinterface,        \
                                            xbridge_device,    \
                                            xinterface_device) \
-    (&((const OvsdbMethodPayload){                            \
+    (&((const OvsdbMethodPayload) {                           \
         .add_interface =                                      \
             {                                                 \
                 .bridge           = (xbridge),                \
@@ -226,7 +226,7 @@ static void     cleanup_check_ready(NMOvsdb *self);
     }))
 
 #define OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(xifname)               \
-    (&((const OvsdbMethodPayload){                                \
+    (&((const OvsdbMethodPayload) {                               \
         .del_interface =                                          \
             {                                                     \
                 .ifname = (char *) NM_CONSTCAST(char, (xifname)), \
@@ -234,7 +234,7 @@ static void     cleanup_check_ready(NMOvsdb *self);
     }))
 
 #define OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(xifname, xmtu)     \
-    (&((const OvsdbMethodPayload){                                \
+    (&((const OvsdbMethodPayload) {                               \
         .set_interface_mtu =                                      \
             {                                                     \
                 .ifname = (char *) NM_CONSTCAST(char, (xifname)), \
@@ -249,7 +249,7 @@ static void     cleanup_check_ready(NMOvsdb *self);
                                          xexternal_ids_new,                          \
                                          xother_config_old,                          \
                                          xother_config_new)                          \
-    (&((const OvsdbMethodPayload){                                                   \
+    (&((const OvsdbMethodPayload) {                                                  \
         .set_reapply =                                                               \
             {                                                                        \
                 .device_type      = xdevice_type,                                    \
@@ -420,7 +420,7 @@ ovsdb_call_method(NMOvsdb                  *self,
     ovsdb_try_connect(self);
 
     call  = g_slice_new(OvsdbMethodCall);
-    *call = (OvsdbMethodCall){
+    *call = (OvsdbMethodCall) {
         .self              = self,
         .call_id           = CALL_ID_UNSPEC,
         .command           = command,
@@ -1682,7 +1682,7 @@ _strdict_extract(json_t *strdict, GArray **out_array)
         }
 
         v  = nm_g_array_append_new(*out_array, NMUtilsNamedValue);
-        *v = (NMUtilsNamedValue){
+        *v = (NMUtilsNamedValue) {
             .name      = g_strdup(key),
             .value_str = g_strdup(val),
         };
@@ -1909,7 +1909,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
             gs_free char *strtmp2 = NULL;
 
             ovs_interface  = g_slice_new(OpenvswitchInterface);
-            *ovs_interface = (OpenvswitchInterface){
+            *ovs_interface = (OpenvswitchInterface) {
                 .interface_uuid  = g_strdup(key),
                 .name            = g_strdup(name),
                 .type            = g_strdup(type),
@@ -2040,7 +2040,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
             gs_free char *strtmp2 = NULL;
 
             ovs_port  = g_slice_new(OpenvswitchPort);
-            *ovs_port = (OpenvswitchPort){
+            *ovs_port = (OpenvswitchPort) {
                 .port_uuid       = g_strdup(key),
                 .name            = g_strdup(name),
                 .connection_uuid = g_strdup(connection_uuid),
@@ -2161,7 +2161,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
             gs_free char *strtmp2 = NULL;
 
             ovs_bridge  = g_slice_new(OpenvswitchBridge);
-            *ovs_bridge = (OpenvswitchBridge){
+            *ovs_bridge = (OpenvswitchBridge) {
                 .bridge_uuid     = g_strdup(key),
                 .name            = g_strdup(name),
                 .connection_uuid = g_strdup(connection_uuid),
@@ -2905,7 +2905,7 @@ ovsdb_call_new(NMOvsdbCallback callback, gpointer user_data)
     OvsdbCall *call;
 
     call  = g_slice_new(OvsdbCall);
-    *call = (OvsdbCall){
+    *call = (OvsdbCall) {
         .callback  = callback,
         .user_data = user_data,
     };
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index a4c77f7f..40779c89 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -130,8 +130,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("Team connection"),
                               "team",
-                              NULL,
-                              TRUE);
+                              NULL);
 
     _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_TEAM);
 
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c
index fadc6722..184c8ec5 100644
--- a/src/core/devices/wifi/nm-device-iwd-p2p.c
+++ b/src/core/devices/wifi/nm-device-iwd-p2p.c
@@ -301,8 +301,7 @@ complete_connection(NMDevice            *device,
                               setting_name,
                               setting_name,
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     return TRUE;
 }
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index d6e3ed08..fa6e2f9d 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -275,7 +275,7 @@ ap_from_network(NMDeviceIwd *self,
 
     ssid = g_bytes_new(name, NM_MIN(32u, strlen(name)));
 
-    bss_info = (NMSupplicantBssInfo){
+    bss_info = (NMSupplicantBssInfo) {
         .bss_path       = bss_path,
         .last_seen_msec = last_seen_msec,
         .bssid_valid    = TRUE,
@@ -1074,8 +1074,7 @@ complete_connection(NMDevice            *device,
                               ssid_utf8,
                               ssid_utf8,
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     if (hidden)
         g_object_set(s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
@@ -3602,7 +3601,7 @@ nm_device_iwd_parse_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *
             preferred_lifetime = valid_lifetime;
 
         if (addr_family == AF_INET) {
-            a.a4 = (NMPlatformIP4Address){
+            a.a4 = (NMPlatformIP4Address) {
                 .address      = addr_bin.addr4,
                 .peer_address = addr_bin.addr4,
                 .plen         = plen,
@@ -3614,7 +3613,7 @@ nm_device_iwd_parse_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *
                 .broadcast_address         = bcast_bin.addr4,
             };
         } else {
-            a.a6 = (NMPlatformIP6Address){
+            a.a6 = (NMPlatformIP6Address) {
                 .address     = addr_bin.addr6,
                 .plen        = 128,
                 .timestamp   = (valid_lifetime != NM_PLATFORM_LIFETIME_PERMANENT) ? timestamp : 0,
@@ -3703,7 +3702,7 @@ nm_device_iwd_parse_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *
         }
 
         if (addr_family == AF_INET) {
-            r.r4 = (NMPlatformIP4Route){
+            r.r4 = (NMPlatformIP4Route) {
                 .network  = dst_addr_str ? dst_addr_bin.addr4 : 0,
                 .plen     = dst_addr_str ? dst_plen : 0,
                 .gateway  = router_str ? router_bin.addr4 : 0,
@@ -3712,7 +3711,7 @@ nm_device_iwd_parse_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *
                     nm_platform_route_scope_inv(router_str ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK),
             };
         } else {
-            r.r6 = (NMPlatformIP6Route){
+            r.r6 = (NMPlatformIP6Route) {
                 .network     = dst_addr_str ? dst_addr_bin.addr6 : nm_ip_addr_zero.addr6,
                 .plen        = dst_addr_str ? dst_plen : 0,
                 .gateway     = router_str ? router_bin.addr6 : nm_ip_addr_zero.addr6,
@@ -3739,7 +3738,7 @@ nm_device_iwd_parse_netconfig(NMDeviceIwd *self, int addr_family, GVariantIter *
             if (inet_pton(addr_family, str_value, &dns_bin) != 1)
                 goto param_error;
 
-            nm_l3_config_data_add_nameserver_detail(l3cd, addr_family, &dns_bin, NULL);
+            nm_l3_config_data_add_nameserver_addr(l3cd, addr_family, &dns_bin);
             nm_l3_config_data_set_dns_priority(l3cd, addr_family, NM_DNS_PRIORITY_DEFAULT_NORMAL);
         }
     }
diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index 8e1e779b..b62dc311 100644
--- a/src/core/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -111,8 +111,7 @@ complete_connection(NMDevice            *device,
                               NULL,
                               _("Mesh"),
                               NULL,
-                              NULL,
-                              FALSE); /* No IPv6 by default */
+                              NULL);
 
     return TRUE;
 }
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index f06383b1..957a2df6 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -319,8 +319,7 @@ complete_connection(NMDevice            *device,
                               setting_name,
                               setting_name,
                               NULL,
-                              NULL,
-                              TRUE);
+                              NULL);
 
     return TRUE;
 }
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index ea65499e..06eee142 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -340,7 +340,7 @@ _scan_request_ssids_track(NMDeviceWifiPrivate *priv, const GPtrArray *ssids)
         d = g_hash_table_lookup(priv->scan_request_ssids_hash, &ssid);
         if (!d) {
             d  = g_slice_new(ScanRequestSsidData);
-            *d = (ScanRequestSsidData){
+            *d = (ScanRequestSsidData) {
                 .lst            = C_LIST_INIT(d->lst),
                 .timestamp_msec = now_msec,
                 .ssid           = g_bytes_ref(ssid),
@@ -1296,8 +1296,7 @@ complete_connection(NMDevice            *device,
         ssid_utf8,
         ssid_utf8,
         NULL,
-        nm_setting_wireless_get_mac_address(s_wifi) ? NULL : nm_device_get_iface(device),
-        TRUE);
+        nm_setting_wireless_get_mac_address(s_wifi) ? NULL : nm_device_get_iface(device));
 
     if (hidden)
         g_object_set(s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index 8a8d062f..332352ab 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -1577,19 +1577,17 @@ ip_config_to_iwd_config(int addr_family, GKeyFile *file, NMSettingIPConfig *s_ip
     if (num) {
         nm_str_buf_reset(&strbuf);
         for (i = 0; i < num; i++) {
-            char     sbuf[NM_INET_ADDRSTRLEN];
-            NMIPAddr a;
-
-            if (!nm_utils_dnsname_parse_assert(addr_family,
-                                               nm_setting_ip_config_get_dns(s_ip, i),
-                                               NULL,
-                                               &a,
-                                               NULL))
+            char addrstr[NM_INET_ADDRSTRLEN];
+
+            if (!nm_dns_uri_parse_plain(addr_family,
+                                        nm_setting_ip_config_get_dns(s_ip, i),
+                                        addrstr,
+                                        NULL))
                 continue;
 
             if (strbuf.len > 0)
                 nm_str_buf_append_c(&strbuf, ' ');
-            nm_str_buf_append(&strbuf, nm_inet_ntop(addr_family, &a, sbuf));
+            nm_str_buf_append(&strbuf, addrstr);
         }
         /* It doesn't matter whether we add the DNS under [IPv4] or [IPv6]
          * except that with method=auto the list will override the
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index c03446b9..018e5306 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -668,6 +668,8 @@ connect_context_step(NMModemBroadband *self)
             NMSettingGsm *s_gsm     = nm_connection_get_setting_gsm(ctx->connection);
             const char   *apn       = nm_setting_gsm_get_initial_eps_apn(s_gsm);
             gboolean      do_config = nm_setting_gsm_get_initial_eps_config(s_gsm);
+            const char   *username  = nm_setting_gsm_get_initial_eps_username(s_gsm);
+            const char   *password  = nm_setting_gsm_get_initial_eps_password(s_gsm);
 
             /* assume do_config is true if an APN is set */
             if (apn || do_config) {
@@ -690,9 +692,28 @@ connect_context_step(NMModemBroadband *self)
                     /* do nothing */
                     break;
                 }
-                if (apn)
-                    mm_bearer_properties_set_apn(config, apn);
+                if (apn) {
+                    MMBearerAllowedAuth allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
 
+                    mm_bearer_properties_set_apn(config, apn);
+                    mm_bearer_properties_set_user(config, username);
+                    mm_bearer_properties_set_password(config, password);
+
+                    if (nm_setting_gsm_get_initial_eps_noauth(s_gsm))
+                        allowed_auth |= MM_BEARER_ALLOWED_AUTH_NONE;
+                    if (!nm_setting_gsm_get_initial_eps_refuse_pap(s_gsm))
+                        allowed_auth |= MM_BEARER_ALLOWED_AUTH_PAP;
+                    if (!nm_setting_gsm_get_initial_eps_refuse_chap(s_gsm))
+                        allowed_auth |= MM_BEARER_ALLOWED_AUTH_CHAP;
+                    if (!nm_setting_gsm_get_initial_eps_refuse_mschap(s_gsm))
+                        allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAP;
+                    if (!nm_setting_gsm_get_initial_eps_refuse_mschapv2(s_gsm))
+                        allowed_auth |= MM_BEARER_ALLOWED_AUTH_MSCHAPV2;
+                    if (!nm_setting_gsm_get_initial_eps_refuse_eap(s_gsm))
+                        allowed_auth |= MM_BEARER_ALLOWED_AUTH_EAP;
+
+                    mm_bearer_properties_set_allowed_auth(config, allowed_auth);
+                }
                 /*
                  * Setting the initial EPS bearer settings is a no-op in
                  * ModemManager if the desired configuration is already active.
@@ -896,8 +917,7 @@ complete_connection(NMModem             *modem,
                                   NULL,
                                   _("GSM connection"),
                                   NULL,
-                                  NULL,
-                                  FALSE); /* No IPv6 yet by default */
+                                  NULL);
 
         return TRUE;
     }
@@ -917,8 +937,7 @@ complete_connection(NMModem             *modem,
                                   NULL,
                                   _("CDMA connection"),
                                   NULL,
-                                  iface,
-                                  FALSE); /* No IPv6 yet by default */
+                                  iface);
 
         return TRUE;
     }
@@ -1116,7 +1135,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
                                      ifindex,
                                      NM_IP_CONFIG_SOURCE_WWAN);
 
-        address = (NMPlatformIP4Address){
+        address = (NMPlatformIP4Address) {
             .address      = address_network,
             .peer_address = address_network,
             .plen         = mm_bearer_ip_config_get_prefix(self->_priv.ipv4_config),
@@ -1127,7 +1146,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
 
         _LOGI("  address %s", nm_platform_ip4_address_to_string(&address, sbuf, sizeof(sbuf)));
 
-        route = (NMPlatformIP4Route){
+        route = (NMPlatformIP4Route) {
             .rt_source     = NM_IP_CONFIG_SOURCE_WWAN,
             .gateway       = gw,
             .table_any     = TRUE,
@@ -1141,7 +1160,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
         dns = mm_bearer_ip_config_get_dns(self->_priv.ipv4_config);
         for (i = 0; dns && dns[i]; i++) {
             if (nm_inet_parse_bin(AF_INET, dns[i], NULL, &address_network) && address_network > 0) {
-                nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET, &address_network, NULL);
+                nm_l3_config_data_add_nameserver_addr(l3cd, AF_INET, &address_network);
                 _LOGI("  DNS %s", dns[i]);
             }
         }
@@ -1193,7 +1212,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
         do_auto = TRUE;
 
         if (address_string) {
-            address = (NMPlatformIP6Address){};
+            address = (NMPlatformIP6Address) {};
 
             if (!inet_pton(AF_INET6, address_string, &address.address)) {
                 g_set_error(&error,
@@ -1260,7 +1279,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
             struct in6_addr addr;
 
             if (inet_pton(AF_INET6, dns[i], &addr)) {
-                nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET6, &addr, NULL);
+                nm_l3_config_data_add_nameserver_addr(l3cd, AF_INET6, &addr);
                 _LOGI("  DNS %s", dns[i]);
             }
         }
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index 3e0bbd48..80f966f1 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -1256,7 +1256,7 @@ handle_settings(NMModemOfono *self, GVariant *v_dict)
         goto out;
     }
 
-    address = (NMPlatformIP4Address){
+    address = (NMPlatformIP4Address) {
         .ifindex     = ifindex,
         .address     = address_network,
         .addr_source = NM_IP_CONFIG_SOURCE_WWAN,
@@ -1315,7 +1315,7 @@ handle_settings(NMModemOfono *self, GVariant *v_dict)
             }
             any_good = TRUE;
             _LOGI("DNS: %s", array[i]);
-            nm_l3_config_data_add_nameserver_detail(priv->l3cd_4, AF_INET, &address_network, NULL);
+            nm_l3_config_data_add_nameserver_addr(priv->l3cd_4, AF_INET, &address_network);
         }
         if (!any_good) {
             _LOGW("Settings: 'DomainNameServers': none specified");
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index 23e7de4a..c4852ea2 100644
--- a/src/core/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -1064,7 +1064,7 @@ nm_modem_act_stage2_config(NMModem *self, NMDevice *device, NMDeviceStateReason
         else
             baud_override = 0;
 
-        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig){
+        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig) {
                                              .netns         = nm_device_get_netns(device),
                                              .parent_iface  = priv->data_port,
                                              .callback      = _ppp_mgr_callback,