summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-12-06 21:57:59 +0100
committerMichael Biebl <biebl@debian.org>2020-12-06 21:57:59 +0100
commit65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (patch)
tree180827692f002e5f1dad6a0fa8ca489e6bb6438f /src
parentf2ddac4cbc895837ddcc55015fae112f9859cd0a (diff)
New upstream version 1.28.0 upstream/1.28.0
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device-bridge.c14
-rw-r--r--src/devices/nm-device.c16
-rw-r--r--src/devices/nm-device.h11
-rw-r--r--src/devices/ovs/nm-ovsdb.c50
-rw-r--r--src/devices/wifi/nm-device-wifi.c10
-rw-r--r--src/devices/wifi/nm-iwd-manager.c5
-rw-r--r--src/devices/wifi/nm-wifi-ap.c18
-rw-r--r--src/devices/wwan/nm-modem-broadband.c7
-rw-r--r--src/dns/nm-dns-dnsmasq.c15
-rw-r--r--src/dns/nm-dns-manager.c238
-rw-r--r--src/dns/nm-dns-manager.h26
-rw-r--r--src/dns/nm-dns-systemd-resolved.c234
-rw-r--r--src/initrd/nm-initrd-generator.h24
-rw-r--r--src/initrd/nmi-cmdline-reader.c121
-rw-r--r--src/initrd/nmi-ibft-reader.c4
-rw-r--r--src/initrd/tests/test-cmdline-reader.c413
-rw-r--r--src/nm-config.c1
-rw-r--r--src/nm-config.h4
-rw-r--r--src/nm-l3cfg.c4
-rw-r--r--src/nm-manager.c12
-rw-r--r--src/nm-policy.c5
-rw-r--r--src/platform/nm-fake-platform.c19
-rw-r--r--src/platform/nm-linux-platform.c4
-rw-r--r--src/platform/nm-platform.c8
-rw-r--r--src/platform/nm-platform.h48
-rw-r--r--src/platform/tests/test-common.c2
-rw-r--r--src/platform/tests/test-link.c3
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build1
28 files changed, 1081 insertions, 236 deletions
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 3c27b349..48dcec1b 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -1027,6 +1027,7 @@ create_and_realize(NMDevice *             device,
                    const NMPlatformLink **out_plink,
                    GError **              error)
 {
+    NMSettingWired *    s_wired;
     NMSettingBridge *   s_bridge;
     const char *        iface = nm_device_get_iface(device);
     const char *        hwaddr;
@@ -1034,12 +1035,17 @@ create_and_realize(NMDevice *             device,
     guint8              mac_address[NM_UTILS_HWADDR_LEN_MAX];
     NMPlatformLnkBridge props;
     int                 r;
+    guint32             mtu = 0;
 
     nm_assert(iface);
 
     s_bridge = nm_connection_get_setting_bridge(connection);
     nm_assert(s_bridge);
 
+    s_wired = nm_connection_get_setting_wired(connection);
+    if (s_wired)
+        mtu = nm_setting_wired_get_mtu(s_wired);
+
     hwaddr = nm_setting_bridge_get_mac_address(s_bridge);
     if (!hwaddr
         && nm_device_hw_addr_get_cloned(device, connection, FALSE, &hwaddr_cloned, NULL, NULL)) {
@@ -1097,10 +1103,17 @@ create_and_realize(NMDevice *             device,
 
     to_sysfs_group_address_sys(nm_setting_bridge_get_group_address(s_bridge), &props.group_addr);
 
+    /* If mtu != 0, we set the MTU of the new bridge at creation time. However, kernel will still
+     * automatically adjust the MTU of the bridge based on the minimum of the slave's MTU.
+     * We don't want this automatism as the user asked for a fixed MTU.
+     *
+     * To workaround this behavior of kernel, we will later toggle the MTU twice. See
+     * NMDeviceClass.mtu_force_set. */
     r = nm_platform_link_bridge_add(nm_device_get_platform(device),
                                     iface,
                                     hwaddr ? mac_address : NULL,
                                     hwaddr ? ETH_ALEN : 0,
+                                    mtu,
                                     &props,
                                     out_plink);
     if (r < 0) {
@@ -1152,6 +1165,7 @@ nm_device_bridge_class_init(NMDeviceBridgeClass *klass)
     device_class->link_types                = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_BRIDGE);
 
     device_class->is_master                   = TRUE;
+    device_class->mtu_force_set               = TRUE;
     device_class->get_generic_capabilities    = get_generic_capabilities;
     device_class->check_connection_compatible = check_connection_compatible;
     device_class->check_connection_available  = check_connection_available;
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 2dab5075..0be05f23 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -659,6 +659,8 @@ typedef struct _NMDevicePrivate {
         guint64 tx_bytes;
         guint64 rx_bytes;
     } stats;
+
+    bool mtu_force_set_done : 1;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -10433,6 +10435,18 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
         }
     }
 
+    if (mtu_desired && NM_DEVICE_GET_CLASS(self)->mtu_force_set && !priv->mtu_force_set_done) {
+        priv->mtu_force_set_done = TRUE;
+
+        if (mtu_desired == mtu_plat) {
+            mtu_plat--;
+            if (NM_DEVICE_GET_CLASS(self)->set_platform_mtu(self, mtu_desired - 1)) {
+                _LOGD(LOGD_DEVICE, "mtu: force-set MTU to %u", mtu_desired - 1);
+            } else
+                _LOGW(LOGD_DEVICE, "mtu: failure to force-set MTU to %u", mtu_desired - 1);
+        }
+    }
+
     _LOGT(LOGD_DEVICE,
           "mtu: device-mtu: %u%s, ipv6-mtu: %u%s, ifindex: %d",
           (guint) mtu_desired,
@@ -15773,6 +15787,8 @@ _cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
 
     priv->linklocal6_dad_counter = 0;
 
+    priv->mtu_force_set_done = FALSE;
+
     /* Clean up IP configs; this does not actually deconfigure the
      * interface; the caller must flush routes and addresses explicitly.
      */
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 13782417..3eae9318 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -232,6 +232,17 @@ typedef struct _NMDeviceClass {
      * type (NMDeviceClass), not the actual device instance. */
     bool is_master : 1;
 
+    /* Force setting the MTU actually means first setting the MTU
+     * to (desired_MTU-1) and then setting the desired_MTU
+     * so that kernel actually applies the MTU, otherwise
+     * kernel will ignore the request if the link's MTU is the
+     * same as the desired one.
+     *
+     * This is just a workaround made for bridges (ATM) that employ
+     * a auto-MTU adjust mechanism if no MTU is manually set.
+     */
+    bool mtu_force_set : 1;
+
     void (*state_changed)(NMDevice *          device,
                           NMDeviceState       new_state,
                           NMDeviceState       old_state,
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 9826c1b7..27ef789f 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -707,7 +707,7 @@ _add_interface(NMOvsdb *     self,
     nm_auto_decref_json json_t *interfaces     = NULL;
     nm_auto_decref_json json_t *new_interfaces = NULL;
     gboolean                    has_interface  = FALSE;
-    gboolean                    interface_is_internal;
+    gboolean                    interface_is_local;
     gs_free char *              bridge_cloned_mac    = NULL;
     gs_free char *              interface_cloned_mac = NULL;
     GError *                    error                = NULL;
@@ -721,10 +721,10 @@ _add_interface(NMOvsdb *     self,
     new_ports      = json_array();
     new_interfaces = json_array();
 
-    bridge_name           = nm_connection_get_interface_name(bridge);
-    port_name             = nm_connection_get_interface_name(port);
-    interface_name        = nm_connection_get_interface_name(interface);
-    interface_is_internal = nm_streq0(bridge_name, interface_name);
+    bridge_name        = nm_connection_get_interface_name(bridge);
+    port_name          = nm_connection_get_interface_name(port);
+    interface_name     = nm_connection_get_interface_name(interface);
+    interface_is_local = nm_streq0(bridge_name, interface_name);
 
     /* Determine cloned MAC addresses */
     if (!nm_device_hw_addr_get_cloned(bridge_device,
@@ -733,7 +733,7 @@ _add_interface(NMOvsdb *     self,
                                       &bridge_cloned_mac,
                                       NULL,
                                       &error)) {
-        _LOGW("Cannot determine cloned mac for OVS %s '%s': %s",
+        _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s",
               "bridge",
               bridge_name,
               error->message);
@@ -746,18 +746,34 @@ _add_interface(NMOvsdb *     self,
                                       &interface_cloned_mac,
                                       NULL,
                                       &error)) {
-        _LOGW("Cannot determine cloned mac for OVS %s '%s': %s",
+        _LOGW("Cannot determine cloned MAC for OVS %s '%s': %s",
               "interface",
               interface_name,
               error->message);
         g_clear_error(&error);
     }
 
-    if (interface_is_internal && !bridge_cloned_mac && interface_cloned_mac) {
-        _LOGT("'%s' is a local ovs-interface, the MAC will be set on ovs-bridge '%s'",
-              interface_name,
-              bridge_name);
-        bridge_cloned_mac = g_steal_pointer(&interface_cloned_mac);
+    /* For local interfaces, ovs complains if it finds a
+     * MAC address in the Interface table because it only takes
+     * the MAC from the Bridge table.
+     * Set any cloned MAC present in a local interface connection
+     * into the Bridge table, unless conflicting with the bridge MAC. */
+    if (interface_is_local && interface_cloned_mac) {
+        if (bridge_cloned_mac && !nm_streq(interface_cloned_mac, bridge_cloned_mac)) {
+            _LOGW("Cloned MAC '%s' of local ovs-interface '%s' conflicts with MAC '%s' of bridge "
+                  "'%s'",
+                  interface_cloned_mac,
+                  interface_name,
+                  bridge_cloned_mac,
+                  bridge_name);
+            nm_clear_g_free(&interface_cloned_mac);
+        } else {
+            nm_clear_g_free(&bridge_cloned_mac);
+            bridge_cloned_mac = g_steal_pointer(&interface_cloned_mac);
+            _LOGT("'%s' is a local ovs-interface, the MAC will be set on ovs-bridge '%s'",
+                  interface_name,
+                  bridge_name);
+        }
     }
 
     g_hash_table_iter_init(&iter, priv->bridges);
@@ -823,7 +839,7 @@ _add_interface(NMOvsdb *     self,
             g_return_if_fail(ovs_bridge);
             _expect_bridge_ports(params, ovs_bridge->name, ports);
             _set_bridge_ports(params, bridge_name, new_ports);
-            if (bridge_cloned_mac && interface_is_internal)
+            if (bridge_cloned_mac && interface_is_local)
                 _set_bridge_mac(params, bridge_name, bridge_cloned_mac);
         }
 
@@ -1175,8 +1191,12 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
     }
 
     if (ovs) {
-        iter          = json_object_iter(ovs);
-        priv->db_uuid = iter ? g_strdup(json_object_iter_key(iter)) : NULL;
+        const char *s;
+
+        iter = json_object_iter(ovs);
+        s    = json_object_iter_key(iter);
+        if (s)
+            nm_utils_strdup_reset(&priv->db_uuid, s);
     }
 
     /* Interfaces */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 55062644..980916dd 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -1390,11 +1390,11 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
          * a new one.*/
         priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC;
 
-        generate_mac_address_mask =
-            nm_config_data_get_device_config(NM_CONFIG_GET_DATA,
-                                             "wifi.scan-generate-mac-address-mask",
-                                             device,
-                                             NULL);
+        generate_mac_address_mask = nm_config_data_get_device_config(
+            NM_CONFIG_GET_DATA,
+            NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
+            device,
+            NULL);
 
         priv->scan_last_request_started_at_msec = G_MININT64;
         priv->scan_periodic_next_msec           = 0;
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 6a562014..c7e99e0b 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -673,15 +673,16 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
     gboolean             mapped;
     KnownNetworkData *   data;
     KnownNetworkId       id;
+    gs_free char *       ssid_str = NULL;
 
     id.security = nm_wifi_connection_get_iwd_security(conn, &mapped);
     if (!mapped)
         return;
 
     s_wireless = nm_connection_get_setting_wireless(conn);
-    id.name    = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wireless));
+    ssid_str   = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wireless));
+    id.name    = ssid_str;
     data       = g_hash_table_lookup(priv->known_networks, &id);
-    g_free((char *) id.name);
     if (!data)
         return;
 
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index fe26421d..55438a53 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -113,22 +113,26 @@ nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid)
 
     g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE);
 
-    if (ssid) {
-        l = g_bytes_get_size(ssid);
-        if (l == 0 || l > 32)
-            g_return_val_if_reached(FALSE);
+    if (!ssid) {
+        /* we don't clear the SSID, once we have it. We can only update
+         * it by a better value. */
+        return FALSE;
     }
 
+    l = g_bytes_get_size(ssid);
+    if (l == 0 || l > 32)
+        g_return_val_if_reached(FALSE);
+
     priv = NM_WIFI_AP_GET_PRIVATE(ap);
 
     if (ssid == priv->ssid)
         return FALSE;
-    if (ssid && priv->ssid && g_bytes_equal(ssid, priv->ssid))
+    if (priv->ssid && g_bytes_equal(ssid, priv->ssid))
         return FALSE;
 
+    g_bytes_ref(ssid);
     nm_clear_pointer(&priv->ssid, g_bytes_unref);
-    if (ssid)
-        priv->ssid = g_bytes_ref(ssid);
+    priv->ssid = ssid;
 
     _notify(ap, PROP_SSID);
     return TRUE;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 633878ce..7b3a13a3 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -39,8 +39,11 @@ MODEM_CAPS_3GPP(MMModemCapability caps)
 
 #define MODEM_CAPS_3GPP2(caps) (caps & (MM_MODEM_CAPABILITY_CDMA_EVDO))
 
-/* Maximum time to keep the DBus call waiting for a connection result */
-#define MODEM_CONNECT_TIMEOUT_SECS 120
+/* Maximum time to keep the DBus call waiting for a connection result.
+ * This value is greater than the default timeout in ModemManager (180s since
+ * 1.16), so that whenever possible the timeout happens first there instead of
+ * in NetworkManager. */
+#define MODEM_CONNECT_TIMEOUT_SECS 200
 
 /*****************************************************************************/
 
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index 451e4846..97f1dfab 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -820,11 +820,18 @@ add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsIPConfigD
     for (i = 0; i < num; i++) {
         addr = nm_ip_config_get_nameserver(ip_config, i);
         ip_addr_to_string(addr_family, addr, iface, ip_addr_to_string_buf);
-        for (j = 0; ip_data->domains.search[j]; j++) {
-            domain = nm_utils_parse_dns_domain(ip_data->domains.search[j], NULL);
-            add_dnsmasq_nameserver(self, servers, ip_addr_to_string_buf, domain[0] ? domain : NULL);
-        }
 
+        if (!ip_data->domains.has_default_route_explicit && ip_data->domains.has_default_route)
+            add_dnsmasq_nameserver(self, servers, ip_addr_to_string_buf, NULL);
+        if (ip_data->domains.search) {
+            for (j = 0; ip_data->domains.search[j]; j++) {
+                domain = nm_utils_parse_dns_domain(ip_data->domains.search[j], NULL);
+                add_dnsmasq_nameserver(self,
+                                       servers,
+                                       ip_addr_to_string_buf,
+                                       domain[0] ? domain : NULL);
+            }
+        }
         if (ip_data->domains.reverse) {
             for (j = 0; ip_data->domains.reverse[j]; j++) {
                 add_dnsmasq_nameserver(self,
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 635e94f6..3fc9a396 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -222,6 +222,26 @@ _ASSERT_ip_config_data(const NMDnsIPConfigData *ip_data)
     nm_assert(NM_IS_IP_CONFIG(ip_data->ip_config));
     nm_assert(c_list_contains(&ip_data->data->data_lst_head, &ip_data->data_lst));
     nm_assert(ip_data->data->ifindex == nm_ip_config_get_ifindex(ip_data->ip_config));
+#if NM_MORE_ASSERTS > 5
+    {
+        gboolean has_default = FALSE;
+        gsize    i;
+
+        for (i = 0; ip_data->domains.search && ip_data->domains.search; i++) {
+            const char *d = ip_data->domains.search[i];
+
+            d = nm_utils_parse_dns_domain(d, NULL);
+            nm_assert(d);
+            if (d[0] == '\0')
+                has_default = TRUE;
+        }
+        nm_assert(has_default == ip_data->domains.has_default_route_explicit);
+        if (ip_data->domains.has_default_route_explicit)
+            nm_assert(ip_data->domains.has_default_route_exclusive);
+        if (ip_data->domains.has_default_route_exclusive)
+            nm_assert(ip_data->domains.has_default_route);
+    }
+#endif
 }
 
 static NMDnsIPConfigData *
@@ -233,10 +253,12 @@ _ip_config_data_new(NMDnsConfigData *data, NMIPConfig *ip_config, NMDnsIPConfigT
     nm_assert(NM_IS_IP_CONFIG(ip_config));
     nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
-    ip_data                 = g_slice_new0(NMDnsIPConfigData);
-    ip_data->data           = data;
-    ip_data->ip_config      = g_object_ref(ip_config);
-    ip_data->ip_config_type = ip_config_type;
+    ip_data  = g_slice_new(NMDnsIPConfigData);
+    *ip_data = (NMDnsIPConfigData){
+        .data           = data,
+        .ip_config      = g_object_ref(ip_config),
+        .ip_config_type = ip_config_type,
+    };
     c_list_link_tail(&data->data_lst_head, &ip_data->data_lst);
     c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_config_lst_head,
                      &ip_data->ip_config_lst);
@@ -267,7 +289,7 @@ _ip_config_data_free(NMDnsIPConfigData *ip_data)
                                          ip_data);
 
     g_object_unref(ip_data->ip_config);
-    g_slice_free(NMDnsIPConfigData, ip_data);
+    nm_g_slice_free(ip_data);
 }
 
 static NMDnsIPConfigData *
@@ -292,7 +314,7 @@ _config_data_free(NMDnsConfigData *data)
     _ASSERT_config_data(data);
 
     nm_assert(c_list_is_empty(&data->data_lst_head));
-    g_slice_free(NMDnsConfigData, data);
+    nm_g_slice_free(data);
 }
 
 static int
@@ -1284,6 +1306,19 @@ get_ip_rdns_domains(NMIPConfig *ip_config)
     return _nm_utils_strv_cleanup(strv, FALSE, FALSE, TRUE);
 }
 
+static gboolean
+domain_ht_get_priority(GHashTable *ht, const char *domain, int *out_priority)
+{
+    gpointer ptr;
+
+    if (!ht || !g_hash_table_lookup_extended(ht, domain, NULL, &ptr)) {
+        *out_priority = 0;
+        return FALSE;
+    }
+    *out_priority = GPOINTER_TO_INT(ptr);
+    return TRUE;
+}
+
 /* Check if the domain is shadowed by a parent domain with more negative priority */
 static gboolean
 domain_is_shadowed(GHashTable * ht,
@@ -1300,21 +1335,25 @@ domain_is_shadowed(GHashTable * ht,
 
     nm_assert(!g_hash_table_contains(ht, domain));
 
-    parent_priority = GPOINTER_TO_INT(g_hash_table_lookup(ht, ""));
-    if (parent_priority < 0 && parent_priority < priority) {
-        *out_parent          = "";
-        *out_parent_priority = parent_priority;
-        return TRUE;
+    if (domain_ht_get_priority(ht, "", &parent_priority)) {
+        nm_assert(parent_priority <= priority);
+        if (parent_priority < 0 && parent_priority < priority) {
+            *out_parent          = "";
+            *out_parent_priority = parent_priority;
+            return TRUE;
+        }
     }
 
     parent = strchr(domain, '.');
     while (parent && parent[1]) {
         parent++;
-        parent_priority = GPOINTER_TO_INT(g_hash_table_lookup(ht, parent));
-        if (parent_priority < 0 && parent_priority < priority) {
-            *out_parent          = parent;
-            *out_parent_priority = parent_priority;
-            return TRUE;
+        if (domain_ht_get_priority(ht, parent, &parent_priority)) {
+            nm_assert(parent_priority <= priority);
+            if (parent_priority < 0 && parent_priority < priority) {
+                *out_parent          = parent;
+                *out_parent_priority = parent_priority;
+                return TRUE;
+            }
         }
         parent = strchr(parent, '.');
     }
@@ -1329,8 +1368,22 @@ rebuild_domain_lists(NMDnsManager *self)
     gs_unref_hashtable GHashTable *ht               = NULL;
     gs_unref_hashtable GHashTable *wildcard_entries = NULL;
     CList *                        head;
+    int                            prev_priority = G_MININT;
 
     head = _ip_config_lst_head(self);
+
+#if NM_MORE_ASSERTS
+    /* we call clear_domain_lists() at the end of update. We
+     * don't expect any domain settings here. */
+    c_list_for_each_entry (ip_data, head, ip_config_lst) {
+        nm_assert(!ip_data->domains.search);
+        nm_assert(!ip_data->domains.reverse);
+        nm_assert(!ip_data->domains.has_default_route_explicit);
+        nm_assert(!ip_data->domains.has_default_route_exclusive);
+        nm_assert(!ip_data->domains.has_default_route);
+    }
+#endif
+
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         NMIPConfig *ip_config    = ip_data->ip_config;
         gboolean    add_wildcard = FALSE;
@@ -1368,8 +1421,11 @@ rebuild_domain_lists(NMDnsManager *self)
         guint        n_domains;
         guint        num_dom1;
         guint        num_dom2;
-        guint        cap_dom;
+        guint        n_domains_allocated;
         guint        i;
+        gboolean     has_default_route_maybe    = FALSE;
+        gboolean     has_default_route_explicit = FALSE;
+        gboolean     has_default_route_auto     = FALSE;
 
         if (!nm_ip_config_get_num_nameservers(ip_config))
             continue;
@@ -1378,15 +1434,10 @@ rebuild_domain_lists(NMDnsManager *self)
         n_domains  = nm_ip_config_get_num_domains(ip_config);
 
         priority = nm_ip_config_get_dns_priority(ip_config);
-        nm_assert(priority != 0);
-
-        cap_dom = 2u + NM_MAX(n_domains, n_searches);
-
-        g_free(ip_data->domains.search);
-        domains                 = g_new(const char *, cap_dom);
-        ip_data->domains.search = domains;
 
-        num_dom1 = 0;
+        nm_assert(priority != 0);
+        nm_assert(prev_priority <= priority);
+        prev_priority = priority;
 
         /* Add wildcard lookup domain to connections with the default route.
          * If there is no default route, add the wildcard domain to all non-VPN
@@ -1398,12 +1449,17 @@ rebuild_domain_lists(NMDnsManager *self)
              * whether it is suitable for certain operations (like having an automatically
              * added "~" domain). */
             if (g_hash_table_contains(wildcard_entries, ip_data))
-                domains[num_dom1++] = "~";
+                has_default_route_maybe = TRUE;
         } else {
             if (ip_data->ip_config_type != NM_DNS_IP_CONFIG_TYPE_VPN)
-                domains[num_dom1++] = "~";
+                has_default_route_maybe = TRUE;
         }
 
+        n_domains_allocated = (n_searches > 0 ? n_searches : n_domains) + 1u;
+        domains             = g_new(const char *, n_domains_allocated);
+
+        num_dom1 = 0;
+
         /* searches are preferred over domains */
         if (n_searches > 0) {
             for (i = 0; i < n_searches; i++)
@@ -1413,32 +1469,55 @@ rebuild_domain_lists(NMDnsManager *self)
                 domains[num_dom1++] = nm_ip_config_get_domain(ip_config, i);
         }
 
-        nm_assert(num_dom1 < cap_dom);
+        nm_assert(num_dom1 < n_domains_allocated);
 
         num_dom2 = 0;
-        for (i = 0; i < num_dom1; i++) {
+        for (i = 0; TRUE; i++) {
+            const char *domain_full;
             const char *domain_clean;
             const char *parent;
             int         old_priority;
             int         parent_priority;
-
-            domain_clean = nm_utils_parse_dns_domain(domains[i], NULL);
+            gboolean    check_default_route;
+
+            if (i < num_dom1) {
+                check_default_route = FALSE;
+                domain_full         = domains[i];
+                domain_clean        = nm_utils_parse_dns_domain(domains[i], NULL);
+            } else if (i == num_dom1) {
+                if (!has_default_route_maybe)
+                    continue;
+                if (has_default_route_explicit)
+                    continue;
+                check_default_route = TRUE;
+                domain_full         = "~";
+                domain_clean        = "";
+            } else
+                break;
 
             /* Remove domains with lower priority */
-            old_priority = GPOINTER_TO_INT(nm_g_hash_table_lookup(ht, domain_clean));
-            if (old_priority != 0) {
+            if (domain_ht_get_priority(ht, domain_clean, &old_priority)) {
+                nm_assert(old_priority <= priority);
                 if (old_priority < priority) {
-                    _LOGT(
-                        "plugin: drop domain '%s' (i=%d, p=%d) because it already exists with p=%d",
-                        domains[i],
-                        ip_data->data->ifindex,
-                        priority,
-                        old_priority);
+                    _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) because it already exists "
+                          "with p=%d",
+                          NM_PRINT_FMT_QUOTED(!check_default_route,
+                                              "'",
+                                              domain_full,
+                                              "'",
+                                              "<auto-default>"),
+                          ip_data->data->ifindex,
+                          priority,
+                          old_priority);
                     continue;
                 }
             } else if (domain_is_shadowed(ht, domain_clean, priority, &parent, &parent_priority)) {
-                _LOGT("plugin: drop domain '%s' (i=%d, p=%d) shadowed by '%s' (p=%d)",
-                      domains[i],
+                _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) shadowed by '%s' (p=%d)",
+                      NM_PRINT_FMT_QUOTED(!check_default_route,
+                                          "'",
+                                          domain_full,
+                                          "'",
+                                          "<auto-default>"),
                       ip_data->data->ifindex,
                       priority,
                       parent,
@@ -1446,20 +1525,55 @@ rebuild_domain_lists(NMDnsManager *self)
                 continue;
             }
 
-            _LOGT("plugin: add domain '%s' (i=%d, p=%d)",
-                  domains[i],
-                  ip_data->data->ifindex,
-                  priority);
+            _LOGT(
+                "plugin: add domain %s%s%s (i=%d, p=%d)",
+                NM_PRINT_FMT_QUOTED(!check_default_route, "'", domain_full, "'", "<auto-default>"),
+                ip_data->data->ifindex,
+                priority);
+
             if (!ht)
                 ht = g_hash_table_new(nm_str_hash, g_str_equal);
             g_hash_table_insert(ht, (gpointer) domain_clean, GINT_TO_POINTER(priority));
-            domains[num_dom2++] = domains[i];
+
+            if (check_default_route)
+                has_default_route_auto = TRUE;
+            else {
+                nm_assert(num_dom2 <= num_dom1);
+                nm_assert(num_dom2 < n_domains_allocated);
+                domains[num_dom2++] = domain_full;
+                if (domain_clean[0] == '\0')
+                    has_default_route_explicit = TRUE;
+            }
         }
-        nm_assert(num_dom2 < cap_dom);
+        nm_assert(num_dom2 < n_domains_allocated);
         domains[num_dom2] = NULL;
 
-        g_strfreev(ip_data->domains.reverse);
-        ip_data->domains.reverse = get_ip_rdns_domains(ip_config);
+        nm_assert(!ip_data->domains.search);
+        nm_assert(!ip_data->domains.reverse);
+        ip_data->domains.search                     = domains;
+        ip_data->domains.reverse                    = get_ip_rdns_domains(ip_config);
+        ip_data->domains.has_default_route_explicit = has_default_route_explicit;
+        ip_data->domains.has_default_route_exclusive =
+            has_default_route_explicit || (priority < 0 && has_default_route_auto);
+        ip_data->domains.has_default_route =
+            ip_data->domains.has_default_route_exclusive || has_default_route_auto;
+
+        {
+            gs_free char *str1 = NULL;
+            gs_free char *str2 = NULL;
+
+            _LOGT("plugin: settings: ifindex=%d, priority=%d, default-route=%d%s, search=%s, "
+                  "reverse=%s",
+                  ip_data->data->ifindex,
+                  priority,
+                  ip_data->domains.has_default_route,
+                  ip_data->domains.has_default_route_explicit
+                      ? " (explicit)"
+                      : (ip_data->domains.has_default_route_exclusive ? " (exclusive)" : ""),
+                  (str1 = g_strjoinv(",", (char **) ip_data->domains.search)),
+                  (ip_data->domains.reverse ? (str2 = g_strjoinv(",", ip_data->domains.reverse))
+                                            : ""));
+        }
     }
 }
 
@@ -1473,6 +1587,9 @@ clear_domain_lists(NMDnsManager *self)
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         nm_clear_g_free(&ip_data->domains.search);
         nm_clear_pointer(&ip_data->domains.reverse, g_strfreev);
+        ip_data->domains.has_default_route_explicit  = FALSE;
+        ip_data->domains.has_default_route_exclusive = FALSE;
+        ip_data->domains.has_default_route           = FALSE;
     }
 }
 
@@ -1697,7 +1814,7 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
 
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    data = g_hash_table_lookup(priv->configs, GINT_TO_POINTER(ifindex));
+    data = g_hash_table_lookup(priv->configs, &ifindex);
     if (!data)
         ip_data = NULL;
     else
@@ -1713,7 +1830,7 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
         /* deleting a config doesn't invalidate the configs' sort order. */
         _ip_config_data_free(ip_data);
         if (c_list_is_empty(&data->data_lst_head))
-            g_hash_table_remove(priv->configs, GINT_TO_POINTER(ifindex));
+            g_hash_table_remove(priv->configs, &ifindex);
         goto changed;
     }
 
@@ -1723,12 +1840,14 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
     }
 
     if (!data) {
-        data          = g_slice_new0(NMDnsConfigData);
-        data->ifindex = ifindex;
-        data->self    = self;
-        c_list_init(&data->data_lst_head);
+        data  = g_slice_new(NMDnsConfigData);
+        *data = (NMDnsConfigData){
+            .ifindex       = ifindex,
+            .self          = self,
+            .data_lst_head = C_LIST_INIT(data->data_lst_head),
+        };
         _ASSERT_config_data(data);
-        g_hash_table_insert(priv->configs, GINT_TO_POINTER(ifindex), data);
+        g_hash_table_add(priv->configs, data);
     }
 
     if (!ip_data)
@@ -2381,8 +2500,11 @@ nm_dns_manager_init(NMDnsManager *self)
 
     priv->config = g_object_ref(nm_config_get());
 
-    priv->configs =
-        g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _config_data_free);
+    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMDnsConfigData, ifindex) == 0);
+    priv->configs = g_hash_table_new_full(nm_pint_hash,
+                                          nm_pint_equals,
+                                          (GDestroyNotify) _config_data_free,
+                                          NULL);
 
     /* Set the initial hash */
     compute_hash(self, NULL, NM_DNS_MANAGER_GET_PRIVATE(self)->hash);
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index f91d1556..fb2f36d2 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -22,7 +22,7 @@ typedef enum {
 
 enum {
     NM_DNS_PRIORITY_DEFAULT_NORMAL = 100,
-    NM_DNS_PRIORITY_DEFAULT_VPN    = -50,
+    NM_DNS_PRIORITY_DEFAULT_VPN    = 50,
 };
 
 struct _NMDnsConfigData;
@@ -37,13 +37,35 @@ typedef struct {
     struct {
         const char **search;
         char **      reverse;
+
+        /* Whether "search" explicitly contains a default route "~"
+         * or "". It is redundant information, but for faster lookup. */
+        bool has_default_route_explicit : 1;
+
+        /* Whether an explicit "~" search domain should be added.
+         * For systemd-resolved, this configured an explicit wildcard
+         * search domain, and should be used for profiles with negative
+         * DNS priority.
+         *
+         * If "has_default_route_explicit", this is always TRUE and implied.
+         *
+         * With systemd-resolved, if TRUE we will set a "." search domain.
+         */
+        bool has_default_route_exclusive : 1;
+
+        /* Whether the device should be used for any domains "~".
+         *
+         * If "has_default_route_exclusive", this is always TRUE and implied.
+         *
+         * With systemd-resolved, this is the value for SetLinkDefaultRoute(). */
+        bool has_default_route : 1;
     } domains;
 } NMDnsIPConfigData;
 
 typedef struct _NMDnsConfigData {
+    int                   ifindex;
     struct _NMDnsManager *self;
     CList                 data_lst_head;
-    int                   ifindex;
 } NMDnsConfigData;
 
 #define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type())
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 24c0ddb5..fe2e78af 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -34,6 +34,9 @@
 #define SYSTEMD_RESOLVED_MANAGER_IFACE "org.freedesktop.resolve1.Manager"
 #define SYSTEMD_RESOLVED_DBUS_PATH     "/org/freedesktop/resolve1"
 
+/* define a variable, so that we can compare the operation with pointer equality. */
+static const char *const DBUS_OP_SET_LINK_DEFAULT_ROUTE = "SetLinkDefaultRoute";
+
 /*****************************************************************************/
 
 typedef struct {
@@ -42,15 +45,18 @@ typedef struct {
 } InterfaceConfig;
 
 typedef struct {
-    CList       request_queue_lst;
-    const char *operation;
-    GVariant *  argument;
+    CList                 request_queue_lst;
+    const char *          operation;
+    GVariant *            argument;
+    NMDnsSystemdResolved *self;
+    int                   ifindex;
 } RequestItem;
 
 /*****************************************************************************/
 
 typedef struct {
     GDBusConnection *dbus_connection;
+    GHashTable *     dirty_interfaces;
     GCancellable *   cancellable;
     CList            request_queue_lst_head;
     guint            name_owner_changed_id;
@@ -58,6 +64,8 @@ typedef struct {
     bool             try_start_blocked : 1;
     bool             dbus_has_owner : 1;
     bool             dbus_initied : 1;
+    bool             request_queue_to_send : 1;
+    NMTernary        has_link_default_route : 3;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -87,18 +95,26 @@ _request_item_free(RequestItem *request_item)
 {
     c_list_unlink_stale(&request_item->request_queue_lst);
     g_variant_unref(request_item->argument);
-    g_slice_free(RequestItem, request_item);
+    nm_g_slice_free(request_item);
 }
 
 static void
-_request_item_append(CList *request_queue_lst_head, const char *operation, GVariant *argument)
+_request_item_append(NMDnsSystemdResolved *self,
+                     const char *          operation,
+                     int                   ifindex,
+                     GVariant *            argument)
 {
-    RequestItem *request_item;
+    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    RequestItem *                request_item;
 
-    request_item            = g_slice_new(RequestItem);
-    request_item->operation = operation;
-    request_item->argument  = g_variant_ref_sink(argument);
-    c_list_link_tail(request_queue_lst_head, &request_item->request_queue_lst);
+    request_item  = g_slice_new(RequestItem);
+    *request_item = (RequestItem){
+        .operation = operation,
+        .argument  = g_variant_ref_sink(argument),
+        .self      = self,
+        .ifindex   = ifindex,
+    };
+    c_list_link_tail(&priv->request_queue_lst_head, &request_item->request_queue_lst);
 }
 
 /*****************************************************************************/
@@ -115,43 +131,69 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
 {
     gs_unref_variant GVariant *v       = NULL;
     gs_free_error GError *       error = NULL;
-    NMDnsSystemdResolved *       self  = (NMDnsSystemdResolved *) user_data;
+    NMDnsSystemdResolved *       self;
     NMDnsSystemdResolvedPrivate *priv;
+    RequestItem *                request_item;
+    NMLogLevel                   log_level;
 
     v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error);
-    if (!v && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    if (nm_utils_error_is_cancelled(error))
         return;
 
-    priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
+    request_item = user_data;
+    self         = request_item->self;
+    priv         = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
-    if (!v) {
-        if (!priv->send_updates_warn_ratelimited) {
-            priv->send_updates_warn_ratelimited = TRUE;
-            _LOGW("send-updates failed to update systemd-resolved: %s", error->message);
-        } else
-            _LOGD("send-updates failed: %s", error->message);
-    } else
+    if (v) {
+        if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+            && priv->has_link_default_route == NM_TERNARY_DEFAULT) {
+            priv->has_link_default_route = NM_TERNARY_TRUE;
+            _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported");
+        }
         priv->send_updates_warn_ratelimited = FALSE;
+        return;
+    }
+
+    if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+        && nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+        if (priv->has_link_default_route == NM_TERNARY_DEFAULT) {
+            priv->has_link_default_route = NM_TERNARY_FALSE;
+            _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported");
+        }
+        return;
+    }
+
+    log_level = LOGL_DEBUG;
+    if (!priv->send_updates_warn_ratelimited) {
+        priv->send_updates_warn_ratelimited = TRUE;
+        log_level                           = LOGL_WARN;
+    }
+    _NMLOG(log_level,
+           "send-updates %s@%d failed: %s",
+           request_item->operation,
+           request_item->ifindex,
+           error->message);
 }
 
-static void
+static gboolean
 update_add_ip_config(NMDnsSystemdResolved *self,
                      GVariantBuilder *     dns,
                      GVariantBuilder *     domains,
                      NMDnsIPConfigData *   data)
 {
-    int          addr_family;
-    gsize        addr_size;
-    guint        i, n;
-    gboolean     is_routing;
-    const char **iter;
-    const char * domain;
+    int         addr_family;
+    gsize       addr_size;
+    guint       i, n;
+    gboolean    is_routing;
+    const char *domain;
+    gboolean    has_config = FALSE;
 
     addr_family = nm_ip_config_get_addr_family(data->ip_config);
     addr_size   = nm_utils_addr_family_to_size(addr_family);
 
-    if (!data->domains.search || !data->domains.search[0])
-        return;
+    if ((!data->domains.search || !data->domains.search[0])
+        && !data->domains.has_default_route_exclusive && !data->domains.has_default_route)
+        return FALSE;
 
     n = nm_ip_config_get_num_nameservers(data->ip_config);
     for (i = 0; i < n; i++) {
@@ -164,12 +206,22 @@ update_add_ip_config(NMDnsSystemdResolved *self,
                                       addr_size,
                                       1));
         g_variant_builder_close(dns);
+        has_config = TRUE;
     }
 
-    for (iter = data->domains.search; *iter; iter++) {
-        domain = nm_utils_parse_dns_domain(*iter, &is_routing);
-        g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing);
+    if (!data->domains.has_default_route_explicit && data->domains.has_default_route_exclusive) {
+        g_variant_builder_add(domains, "(sb)", ".", TRUE);
+        has_config = TRUE;
     }
+    if (data->domains.search) {
+        for (i = 0; data->domains.search[i]; i++) {
+            domain = nm_utils_parse_dns_domain(data->domains.search[i], &is_routing);
+            g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing);
+            has_config = TRUE;
+        }
+    }
+
+    return has_config;
 }
 
 static void
@@ -183,15 +235,17 @@ free_pending_updates(NMDnsSystemdResolved *self)
         _request_item_free(request_item);
 }
 
-static void
+static gboolean
 prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
 {
-    NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
-    GVariantBuilder              dns, domains;
-    NMCListElem *                elem;
-    NMSettingConnectionMdns      mdns     = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-    NMSettingConnectionLlmnr     llmnr    = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
-    const char *                 mdns_arg = NULL, *llmnr_arg = NULL;
+    GVariantBuilder          dns;
+    GVariantBuilder          domains;
+    NMCListElem *            elem;
+    NMSettingConnectionMdns  mdns     = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+    NMSettingConnectionLlmnr llmnr    = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+    const char *             mdns_arg = NULL, *llmnr_arg = NULL;
+    gboolean                 has_config        = FALSE;
+    gboolean                 has_default_route = FALSE;
 
     g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))"));
     g_variant_builder_add(&dns, "i", ic->ifindex);
@@ -205,7 +259,10 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
         NMDnsIPConfigData *data      = elem->data;
         NMIPConfig *       ip_config = data->ip_config;
 
-        update_add_ip_config(self, &dns, &domains, data);
+        has_config |= update_add_ip_config(self, &dns, &domains, data);
+
+        if (data->domains.has_default_route)
+            has_default_route = TRUE;
 
         if (NM_IS_IP4_CONFIG(ip_config)) {
             mdns  = NM_MAX(mdns, nm_ip4_config_mdns_get(NM_IP4_CONFIG(ip_config)));
@@ -248,16 +305,25 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
     }
     nm_assert(llmnr_arg);
 
-    _request_item_append(&priv->request_queue_lst_head, "SetLinkDNS", g_variant_builder_end(&dns));
-    _request_item_append(&priv->request_queue_lst_head,
-                         "SetLinkDomains",
-                         g_variant_builder_end(&domains));
-    _request_item_append(&priv->request_queue_lst_head,
+    if (!nm_str_is_empty(mdns_arg) || !nm_str_is_empty(llmnr_arg))
+        has_config = TRUE;
+
+    _request_item_append(self, "SetLinkDomains", ic->ifindex, g_variant_builder_end(&domains));
+    _request_item_append(self,
+                         DBUS_OP_SET_LINK_DEFAULT_ROUTE,
+                         ic->ifindex,
+                         g_variant_new("(ib)", ic->ifindex, has_default_route));
+    _request_item_append(self,
                          "SetLinkMulticastDNS",
+                         ic->ifindex,
                          g_variant_new("(is)", ic->ifindex, mdns_arg ?: ""));
-    _request_item_append(&priv->request_queue_lst_head,
+    _request_item_append(self,
                          "SetLinkLLMNR",
+                         ic->ifindex,
                          g_variant_new("(is)", ic->ifindex, llmnr_arg ?: ""));
+    _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns));
+
+    return has_config;
 }
 
 static void
@@ -266,7 +332,7 @@ send_updates(NMDnsSystemdResolved *self)
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     RequestItem *                request_item;
 
-    if (c_list_is_empty(&priv->request_queue_lst_head)) {
+    if (!priv->request_queue_to_send) {
         /* nothing to do. */
         return;
     }
@@ -296,15 +362,30 @@ send_updates(NMDnsSystemdResolved *self)
         return;
     }
 
-    _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head));
-
     nm_clear_g_cancellable(&priv->cancellable);
 
+    if (c_list_is_empty(&priv->request_queue_lst_head)) {
+        _LOGT("send-updates: no requests to send");
+        priv->request_queue_to_send = FALSE;
+        return;
+    }
+
+    _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head));
+
     priv->cancellable = g_cancellable_new();
 
-    while (
-        (request_item =
-             c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst))) {
+    priv->request_queue_to_send = FALSE;
+
+    c_list_for_each_entry (request_item, &priv->request_queue_lst_head, request_queue_lst) {
+        if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+            && priv->has_link_default_route == NM_TERNARY_FALSE) {
+            /* The "SetLinkDefaultRoute" API is only supported since v240.
+             * We detected that it is not supported, and skip the call. There
+             * is no special workaround, because in this case we rely on systemd-resolved
+             * to do the right thing automatically. */
+            continue;
+        }
+
         /* Above we explicitly call "StartServiceByName" trying to avoid D-Bus activating systmd-resolved
          * multiple times. There is still a race, were we might hit this line although actually
          * the service just quit this very moment. In that case, we would try to D-Bus activate the
@@ -324,8 +405,7 @@ send_updates(NMDnsSystemdResolved *self)
                                -1,
                                priv->cancellable,
                                call_done,
-                               self);
-        _request_item_free(request_item);
+                               request_item);
     }
 }
 
@@ -336,19 +416,22 @@ update(NMDnsPlugin *            plugin,
        const char *             hostname,
        GError **                error)
 {
-    NMDnsSystemdResolved *self                = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolved *       self         = NM_DNS_SYSTEMD_RESOLVED(plugin);
+    NMDnsSystemdResolvedPrivate *priv         = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
     gs_unref_hashtable GHashTable *interfaces = NULL;
     gs_free gpointer * interfaces_keys        = NULL;
     guint              interfaces_len;
-    guint              i;
+    int                ifindex;
+    gpointer           pointer;
     NMDnsIPConfigData *ip_data;
+    GHashTableIter     iter;
+    guint              i;
 
     interfaces =
         g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _interface_config_free);
 
     c_list_for_each_entry (ip_data, ip_config_lst_head, ip_config_lst) {
         InterfaceConfig *ic = NULL;
-        int              ifindex;
 
         ifindex = ip_data->data->ifindex;
         nm_assert(ifindex == nm_ip_config_get_ifindex(ip_data->ip_config));
@@ -371,11 +454,33 @@ update(NMDnsPlugin *            plugin,
     for (i = 0; i < interfaces_len; i++) {
         InterfaceConfig *ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(interfaces_keys[i]));
 
-        prepare_one_interface(self, ic);
+        if (prepare_one_interface(self, ic))
+            g_hash_table_add(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex));
+        else
+            g_hash_table_remove(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex));
     }
 
-    send_updates(self);
+    /* If we previously configured an ifindex with non-empty values in
+     * resolved, and the current update doesn't contain that interface,
+     * reset the resolved configuration for that ifindex. */
+    g_hash_table_iter_init(&iter, priv->dirty_interfaces);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &pointer, NULL)) {
+        ifindex = GPOINTER_TO_INT(pointer);
+        if (!g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) {
+            InterfaceConfig ic;
+
+            _LOGT("clear previously configured ifindex %d", ifindex);
+            ic = (InterfaceConfig){
+                .ifindex          = ifindex,
+                .configs_lst_head = C_LIST_INIT(ic.configs_lst_head),
+            };
+            prepare_one_interface(self, &ic);
+            g_hash_table_iter_remove(&iter);
+        }
+    }
 
+    priv->request_queue_to_send = TRUE;
+    send_updates(self);
     return TRUE;
 }
 
@@ -394,8 +499,11 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
         _LOGT("D-Bus name for systemd-resolved has owner %s", owner);
 
     priv->dbus_has_owner = !!owner;
-    if (owner)
-        priv->try_start_blocked = FALSE;
+    if (owner) {
+        priv->try_start_blocked     = FALSE;
+        priv->request_queue_to_send = TRUE;
+    } else
+        priv->has_link_default_route = NM_TERNARY_DEFAULT;
 
     send_updates(self);
 }
@@ -468,7 +576,10 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
 {
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
+    priv->has_link_default_route = NM_TERNARY_DEFAULT;
+
     c_list_init(&priv->request_queue_lst_head);
+    priv->dirty_interfaces = g_hash_table_new(nm_direct_hash, NULL);
 
     priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET);
     if (!priv->dbus_connection) {
@@ -510,6 +621,7 @@ dispose(GObject *object)
     nm_clear_g_cancellable(&priv->cancellable);
 
     g_clear_object(&priv->dbus_connection);
+    nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_unref);
 
     G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object);
 }
diff --git a/src/initrd/nm-initrd-generator.h b/src/initrd/nm-initrd-generator.h
index cac01cb8..69c24b1b 100644
--- a/src/initrd/nm-initrd-generator.h
+++ b/src/initrd/nm-initrd-generator.h
@@ -11,17 +11,23 @@
 
 #define NMI_WAIT_DEVICE_TIMEOUT_MS 60000
 
-static inline gboolean
-guess_ip_address_family(const char *str)
+static inline int
+get_ip_address_family(const char *str, gboolean with_prefix)
 {
-    if (str == NULL)
-        return AF_UNSPEC;
-    else if (strchr(str, '.'))
-        return AF_INET;
-    else if (strchr(str, ':'))
-        return AF_INET6;
-    else
+    int addr_family;
+
+    if (!str)
         return AF_UNSPEC;
+
+    if (with_prefix) {
+        if (nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, str, &addr_family, NULL, NULL))
+            return addr_family;
+    } else {
+        if (nm_utils_parse_inaddr_bin(AF_UNSPEC, str, &addr_family, NULL))
+            return addr_family;
+    }
+
+    return AF_UNSPEC;
 }
 
 GHashTable *nmi_ibft_read(const char *sysfs_dir);
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index 5e610e15..1c1b43eb 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -24,6 +24,8 @@
 typedef struct {
     GHashTable *  hash;
     GPtrArray *   array;
+    GPtrArray *   vlan_parents;
+    GHashTable *  explicit_ip_connections;
     NMConnection *bootdev_connection; /* connection for bootdev=$ifname */
     NMConnection *default_connection; /* connection not bound to any ifname */
     char *        hostname;
@@ -41,8 +43,11 @@ reader_new(void)
 
     reader  = g_slice_new(Reader);
     *reader = (Reader){
-        .hash  = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref),
-        .array = g_ptr_array_new(),
+        .hash = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref),
+        .explicit_ip_connections =
+            g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL),
+        .vlan_parents = g_ptr_array_new_with_free_func(g_free),
+        .array        = g_ptr_array_new(),
     };
 
     return reader;
@@ -54,6 +59,8 @@ reader_destroy(Reader *reader, gboolean free_hash)
     gs_unref_hashtable GHashTable *hash = NULL;
 
     g_ptr_array_unref(reader->array);
+    g_ptr_array_unref(reader->vlan_parents);
+    g_hash_table_unref(reader->explicit_ip_connections);
     hash = g_steal_pointer(&reader->hash);
     nm_clear_g_free(&reader->hostname);
     nm_clear_g_free(&reader->dhcp4_vci);
@@ -383,6 +390,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
     NMSettingIPConfig *s_ip4 = NULL, *s_ip6 = NULL;
     gs_unref_hashtable GHashTable *ibft = NULL;
     const char *                   tmp;
+    const char *                   tmp2;
     const char *                   kind             = NULL;
     const char *                   client_ip        = NULL;
     const char *                   peer             = NULL;
@@ -411,17 +419,37 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
         /* ip={dhcp|on|any|dhcp6|auto6|ibft} */
         kind = tmp;
     } else {
-        client_ip_family = guess_ip_address_family(tmp);
-        if (client_ip_family != AF_UNSPEC) {
-            /* <client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>: */
-            client_ip       = tmp;
-            peer            = get_word(&argument, ':');
+        tmp2 = get_word(&argument, ':');
+        if (NM_IN_STRSET(tmp2,
+                         "none",
+                         "off",
+                         "dhcp",
+                         "on"
+                         "any",
+                         "dhcp6",
+                         "auto",
+                         "auto6",
+                         "ibft")) {
+            /* <ifname>:{none|off|dhcp|on|any|dhcp6|auto|auto6|ibft} */
+            iface_spec = tmp;
+            kind       = tmp2;
+        } else {
+            /* <client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<kind> */
+            client_ip = tmp;
+            if (client_ip) {
+                client_ip_family = get_ip_address_family(client_ip, TRUE);
+                if (client_ip_family == AF_UNSPEC) {
+                    _LOGW(LOGD_CORE, "Invalid IP address '%s'.", client_ip);
+                    return;
+                }
+            }
+
+            peer            = tmp2;
             gateway_ip      = get_word(&argument, ':');
             netmask         = get_word(&argument, ':');
             client_hostname = get_word(&argument, ':');
             iface_spec      = get_word(&argument, ':');
-        } else {
-            iface_spec = tmp;
+            kind            = get_word(&argument, ':');
         }
 
         if (client_hostname && !nm_sd_hostname_is_valid(client_hostname, FALSE))
@@ -432,16 +460,12 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
             reader->hostname = g_strdup(client_hostname);
         }
 
-        /* <ifname>:{none|off|dhcp|on|any|dhcp6|auto6|ibft} */
-
-        kind = get_word(&argument, ':');
-
         tmp                = get_word(&argument, ':');
-        dns_addr_family[0] = guess_ip_address_family(tmp);
+        dns_addr_family[0] = get_ip_address_family(tmp, FALSE);
         if (dns_addr_family[0] != AF_UNSPEC) {
             dns[0]             = tmp;
             dns[1]             = get_word(&argument, ':');
-            dns_addr_family[1] = guess_ip_address_family(dns[1]);
+            dns_addr_family[1] = get_ip_address_family(dns[1], FALSE);
             if (*argument)
                 _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument);
         } else {
@@ -461,6 +485,8 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
     else
         connection = reader_get_default_connection(reader);
 
+    g_hash_table_add(reader->explicit_ip_connections, g_object_ref(connection));
+
     s_ip4 = nm_connection_get_setting_ip4_config(connection);
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
 
@@ -506,9 +532,8 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
                 _LOGW(LOGD_CORE, "Invalid address '%s': %s", client_ip, error->message);
                 g_clear_error(&error);
             }
-        } else {
-            _LOGW(LOGD_CORE, "Unrecognized address: %s", client_ip);
-        }
+        } else
+            nm_assert_not_reached();
 
         if (address) {
             switch (client_ip_family) {
@@ -531,7 +556,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
                 nm_setting_ip_config_add_address(s_ip6, address);
                 break;
             default:
-                _LOGW(LOGD_CORE, "Unknown address family: %s", client_ip);
+                nm_assert_not_reached();
                 break;
             }
             nm_ip_address_unref(address);
@@ -543,7 +568,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
         if (nm_setting_ip_config_get_num_addresses(s_ip6) == 0) {
             g_object_set(s_ip6,
                          NM_SETTING_IP_CONFIG_METHOD,
-                         NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                         NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
                          NULL);
         }
         if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) {
@@ -618,22 +643,16 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
         _LOGW(LOGD_CORE, "Ignoring peer: %s (not implemented)\n", peer);
 
     if (gateway_ip && *gateway_ip) {
-        int addr_family = guess_ip_address_family(gateway_ip);
-
-        if (nm_utils_ipaddr_is_valid(addr_family, gateway_ip)) {
-            switch (addr_family) {
-            case AF_INET:
-                g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL);
-                break;
-            case AF_INET6:
-                g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL);
-                break;
-            default:
-                _LOGW(LOGD_CORE, "Unknown address family: %s", gateway_ip);
-                break;
-            }
-        } else {
+        switch (get_ip_address_family(gateway_ip, FALSE)) {
+        case AF_INET:
+            g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL);
+            break;
+        case AF_INET6:
+            g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL);
+            break;
+        default:
             _LOGW(LOGD_CORE, "Invalid gateway: %s", gateway_ip);
+            break;
         }
     }
 
@@ -842,6 +861,9 @@ reader_parse_vlan(Reader *reader, char *argument)
 
     if (argument && *argument)
         _LOGW(LOGD_CORE, "Ignoring extra: '%s'.", argument);
+
+    if (!nm_strv_ptrarray_contains(reader->vlan_parents, phy))
+        g_ptr_array_add(reader->vlan_parents, g_strdup(phy));
 }
 
 static void
@@ -941,7 +963,7 @@ reader_add_nameservers(Reader *reader, GPtrArray *nameservers)
 
     for (i = 0; i < nameservers->len; i++) {
         ns          = nameservers->pdata[i];
-        addr_family = guess_ip_address_family(ns);
+        addr_family = get_ip_address_family(ns, FALSE);
         if (addr_family == AF_UNSPEC) {
             _LOGW(LOGD_CORE, "Unknown address family: %s", ns);
             continue;
@@ -1085,6 +1107,33 @@ nmi_cmdline_reader_parse(const char *sysfs_dir, const char *const *argv, char **
         }
     }
 
+    for (i = 0; i < reader->vlan_parents->len; i++) {
+        NMConnection *     connection;
+        NMSettingIPConfig *s_ip;
+
+        /* Disable IP configuration for parent connections of VLANs,
+         * unless those interfaces were explicitly configured otherwise. */
+
+        connection = reader_get_connection(reader, reader->vlan_parents->pdata[i], NULL, TRUE);
+        if (!g_hash_table_contains(reader->explicit_ip_connections, connection)) {
+            s_ip = nm_connection_get_setting_ip4_config(connection);
+            if (s_ip) {
+                g_object_set(s_ip,
+                             NM_SETTING_IP_CONFIG_METHOD,
+                             NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+                             NULL);
+            }
+
+            s_ip = nm_connection_get_setting_ip6_config(connection);
+            if (s_ip) {
+                g_object_set(s_ip,
+                             NM_SETTING_IP_CONFIG_METHOD,
+                             NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                             NULL);
+            }
+        }
+    }
+
     if (ignore_bootif)
         nm_clear_g_free(&bootif_val);
     if (bootif_val) {
diff --git a/src/initrd/nmi-ibft-reader.c b/src/initrd/nmi-ibft-reader.c
index 80b2e5f9..b7ce4671 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/initrd/nmi-ibft-reader.c
@@ -165,9 +165,9 @@ ip_setting_add_from_block(GHashTable *nic, NMConnection *connection, GError **er
                      NULL);
     }
 
-    family = guess_ip_address_family(s_ipaddr);
+    family = get_ip_address_family(s_ipaddr, FALSE);
     if (family == AF_UNSPEC)
-        family = guess_ip_address_family(s_gateway);
+        family = get_ip_address_family(s_gateway, FALSE);
 
     switch (family) {
     case AF_INET:
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index c3511332..14a83c08 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -80,6 +80,116 @@ test_auto(void)
 }
 
 static void
+test_dhcp_with_hostname(void)
+{
+    gs_unref_hashtable GHashTable *connections = NULL;
+    const char *const *            ARGV        = NM_MAKE_STRV("ip=::::host1::dhcp");
+    NMConnection *                 connection;
+    NMSettingConnection *          s_con;
+    NMSettingWired *               s_wired;
+    NMSettingIPConfig *            s_ip4;
+    NMSettingIPConfig *            s_ip6;
+    gs_free char *                 hostname = NULL;
+
+    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+    g_assert(connections);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
+    g_assert_cmpstr(hostname, ==, "host1");
+
+    connection = g_hash_table_lookup(connections, "default_connection");
+    g_assert(connection);
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    g_assert(!nm_connection_get_setting_vlan(connection));
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Wired Connection");
+    g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+    g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con),
+                    ==,
+                    NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
+    g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1);
+
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert(!nm_setting_wired_get_mac_address(s_wired));
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+}
+
+static void
+test_dhcp_with_mtu(void)
+{
+    const char *const *ARGV0  = NM_MAKE_STRV("ip=:dhcp:1499");
+    const char *const *ARGV1  = NM_MAKE_STRV("ip=::::::dhcp:1499");
+    const char *const *ARGV[] = {ARGV0, ARGV1};
+    guint              i;
+
+    for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
+        gs_unref_hashtable GHashTable *connections = NULL;
+        NMConnection *                 connection;
+        NMSettingConnection *          s_con;
+        NMSettingWired *               s_wired;
+        NMSettingIPConfig *            s_ip4;
+        NMSettingIPConfig *            s_ip6;
+        gs_free char *                 hostname = NULL;
+
+        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
+        g_assert(connections);
+        g_assert_cmpint(g_hash_table_size(connections), ==, 1);
+        g_assert_cmpstr(hostname, ==, NULL);
+
+        connection = g_hash_table_lookup(connections, "default_connection");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+
+        s_con = nm_connection_get_setting_connection(connection);
+        g_assert(s_con);
+        g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                        ==,
+                        NM_SETTING_WIRED_SETTING_NAME);
+        g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Wired Connection");
+        g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
+        g_assert_cmpint(nm_setting_connection_get_multi_connect(s_con),
+                        ==,
+                        NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
+        g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1);
+
+        g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+        s_wired = nm_connection_get_setting_wired(connection);
+        g_assert(s_wired);
+        g_assert(!nm_setting_wired_get_mac_address(s_wired));
+        g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1499);
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    }
+}
+
+static void
 test_if_auto_with_mtu(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
@@ -235,7 +345,9 @@ test_if_ip4_manual(void)
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     g_assert(s_ip6);
-    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
     g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
 
     connection = g_hash_table_lookup(connections, "eth4");
@@ -305,6 +417,52 @@ test_if_ip6_manual(void)
 }
 
 static void
+test_if_off(void)
+{
+    gs_unref_hashtable GHashTable *connections = NULL;
+    const char *const *            ARGV        = NM_MAKE_STRV("ip=off",
+                                           "ip=ens3:off",
+                                           "ip=10.0.0.8:::::ens4:off",
+                                           "ip=[2001:DB8::8]:::::ens5:off");
+    NMConnection *                 connection;
+    NMSettingIPConfig *            s_ip4;
+    NMSettingIPConfig *            s_ip6;
+    gs_free char *                 hostname = NULL;
+    struct {
+        const char name[32];
+        const char ipv4_method[32];
+        const char ipv6_method[32];
+
+    } conn_expected[] = {
+        {"default_connection",
+         NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+         NM_SETTING_IP6_CONFIG_METHOD_DISABLED},
+        {"ens3", NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_DISABLED},
+        {"ens4", NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NM_SETTING_IP6_CONFIG_METHOD_DISABLED},
+        {"ens5", NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_MANUAL},
+    };
+
+    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+    g_assert(connections);
+    g_assert_cmpint(g_hash_table_size(connections), ==, G_N_ELEMENTS(conn_expected));
+    g_assert_cmpstr(hostname, ==, NULL);
+
+    for (int i = 0; i < G_N_ELEMENTS(conn_expected); ++i) {
+        connection = g_hash_table_lookup(connections, conn_expected[i].name);
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, conn_expected[i].ipv4_method);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, conn_expected[i].ipv6_method);
+    }
+}
+
+static void
 test_if_mac_ifname(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
@@ -353,8 +511,9 @@ static void
 test_multiple_merge(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *ARGV = NM_MAKE_STRV("ip=192.0.2.2:::::eth0", "ip=[2001:db8::2]:::56::eth0");
-    NMConnection *     connection;
+    const char *const *            ARGV =
+        NM_MAKE_STRV("ip=192.0.2.2/16:::::eth0", "ip=[2001:db8::2]:::56::eth0");
+    NMConnection *       connection;
     NMSettingConnection *s_con;
     NMSettingWired *     s_wired;
     NMSettingIPConfig *  s_ip4;
@@ -389,6 +548,7 @@ test_multiple_merge(void)
     ip_addr = nm_setting_ip_config_get_address(s_ip4, 0);
     g_assert(ip_addr);
     g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "192.0.2.2");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 16);
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     g_assert(s_ip6);
@@ -454,7 +614,7 @@ test_bootdev(void)
 
     connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
     g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 2);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 3);
     g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "ens3");
@@ -483,6 +643,18 @@ test_bootdev(void)
                     NM_SETTING_VLAN_SETTING_NAME);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "vlan2");
     g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "vlan2");
+
+    connection = g_hash_table_lookup(connections, "ens5");
+    g_assert(connection);
+    nmtst_assert_connection_verifies_without_normalization(connection);
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_WIRED_SETTING_NAME);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "ens5");
+    g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "ens5");
 }
 
 static void
@@ -729,7 +901,9 @@ test_bond_ip(void)
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     g_assert(s_ip6);
-    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
     g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6));
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0);
     g_assert(!nm_setting_ip_config_get_gateway(s_ip6));
@@ -1168,6 +1342,229 @@ test_team(void)
 }
 
 static void
+test_vlan(void)
+{
+    const char *const *ARGV0  = NM_MAKE_STRV("ip=eth0.100:dhcp", "vlan=eth0.100:eth0");
+    const char *const *ARGV1  = NM_MAKE_STRV("vlan=eth0.100:eth0", "ip=eth0.100:dhcp");
+    const char *const *ARGV[] = {ARGV0, ARGV1};
+    guint              i;
+
+    for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
+        gs_unref_hashtable GHashTable *connections = NULL;
+        NMConnection *                 connection;
+        NMSettingIPConfig *            s_ip4;
+        NMSettingIPConfig *            s_ip6;
+        NMSettingVlan *                s_vlan;
+        gs_free char *                 hostname = NULL;
+
+        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
+        g_assert(connections);
+        g_assert_cmpint(g_hash_table_size(connections), ==, 2);
+        g_assert_cmpstr(hostname, ==, NULL);
+
+        /* VLAN eth0.100 */
+        connection = g_hash_table_lookup(connections, "eth0.100");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                        ==,
+                        NM_SETTING_VLAN_SETTING_NAME);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0.100");
+
+        s_vlan = nm_connection_get_setting_vlan(connection);
+        g_assert(s_vlan);
+        g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
+        g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 100);
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+
+        /* Ethernet eth0 */
+        connection = g_hash_table_lookup(connections, "eth0");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                        ==,
+                        NM_SETTING_WIRED_SETTING_NAME);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+    }
+}
+
+static void
+test_vlan_with_dhcp_on_parent(void)
+{
+    const char *const *ARGV0  = NM_MAKE_STRV("vlan=eth0.100:eth0", "ip=eth0:dhcp");
+    const char *const *ARGV1  = NM_MAKE_STRV("ip=eth0:dhcp", "vlan=eth0.100:eth0");
+    const char *const *ARGV[] = {ARGV0, ARGV1};
+    guint              i;
+
+    for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
+        gs_unref_hashtable GHashTable *connections = NULL;
+        NMConnection *                 connection;
+        NMSettingIPConfig *            s_ip4;
+        NMSettingIPConfig *            s_ip6;
+        NMSettingVlan *                s_vlan;
+        gs_free char *                 hostname = NULL;
+
+        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
+        g_assert(connections);
+        g_assert_cmpint(g_hash_table_size(connections), ==, 2);
+        g_assert_cmpstr(hostname, ==, NULL);
+
+        /* VLAN eth0.100 */
+        connection = g_hash_table_lookup(connections, "eth0.100");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                        ==,
+                        NM_SETTING_VLAN_SETTING_NAME);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0.100");
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+
+        s_vlan = nm_connection_get_setting_vlan(connection);
+        g_assert(s_vlan);
+        g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
+        g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 100);
+
+        /* Ethernet eth0 */
+        connection = g_hash_table_lookup(connections, "eth0");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                        ==,
+                        NM_SETTING_WIRED_SETTING_NAME);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    }
+}
+
+static void
+test_vlan_over_bond(void)
+{
+    const char *const *ARGV0  = NM_MAKE_STRV("ip=1.2.3.4:::24::vlan1:none",
+                                            "bond=bond2:ens3,ens4:mode=active-backup",
+                                            "vlan=vlan1:bond2");
+    const char *const *ARGV1  = NM_MAKE_STRV("vlan=vlan1:bond2",
+                                            "ip=1.2.3.4:::24::vlan1:none",
+                                            "bond=bond2:ens3,ens4:mode=active-backup");
+    const char *const *ARGV2  = NM_MAKE_STRV("bond=bond2:ens3,ens4:mode=active-backup",
+                                            "ip=1.2.3.4:::24::vlan1:none",
+                                            "vlan=vlan1:bond2");
+    const char *const *ARGV[] = {ARGV0, ARGV1, ARGV2};
+    guint              i;
+
+    for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
+        gs_unref_hashtable GHashTable *connections = NULL;
+        NMConnection *                 connection;
+        NMSettingIPConfig *            s_ip4;
+        NMSettingIPConfig *            s_ip6;
+        NMSettingVlan *                s_vlan;
+        gs_free char *                 hostname = NULL;
+
+        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
+        g_assert(connections);
+        g_assert_cmpint(g_hash_table_size(connections), ==, 4);
+        g_assert_cmpstr(hostname, ==, NULL);
+
+        /* VLAN vlan1 */
+        connection = g_hash_table_lookup(connections, "vlan1");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                        ==,
+                        NM_SETTING_VLAN_SETTING_NAME);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "vlan1");
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+
+        s_vlan = nm_connection_get_setting_vlan(connection);
+        g_assert(s_vlan);
+        g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "bond2");
+        g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 1);
+
+        /* Bond bond2 */
+        connection = g_hash_table_lookup(connections, "bond2");
+        g_assert(connection);
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_connection_type(connection),
+                        ==,
+                        NM_SETTING_BOND_SETTING_NAME);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "bond2");
+
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+
+        /* Ethernet ens3 and ens4 */
+        connection = g_hash_table_lookup(connections, "ens3");
+        g_assert(connection);
+        connection = g_hash_table_lookup(connections, "ens4");
+        g_assert(connection);
+    }
+}
+
+static void
 test_ibft_ip_dev(void)
 {
     const char *const *ARGV                    = NM_MAKE_STRV("ip=eth0:ibft");
@@ -1784,12 +2181,15 @@ main(int argc, char **argv)
     nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT");
 
     g_test_add_func("/initrd/cmdline/auto", test_auto);
+    g_test_add_func("/initrd/cmdline/dhcp_with_hostname", test_dhcp_with_hostname);
+    g_test_add_func("/initrd/cmdline/dhcp_with_mtu", test_dhcp_with_mtu);
     g_test_add_func("/initrd/cmdline/if_auto_with_mtu", test_if_auto_with_mtu);
     g_test_add_func("/initrd/cmdline/if_dhcp6", test_if_dhcp6);
     g_test_add_func("/initrd/cmdline/if_auto_with_mtu_and_mac", test_if_auto_with_mtu_and_mac);
     g_test_add_func("/initrd/cmdline/if_ip4_manual", test_if_ip4_manual);
     g_test_add_func("/initrd/cmdline/if_ip6_manual", test_if_ip6_manual);
     g_test_add_func("/initrd/cmdline/if_mac_ifname", test_if_mac_ifname);
+    g_test_add_func("/initrd/cmdline/if_off", test_if_off);
     g_test_add_func("/initrd/cmdline/multiple/merge", test_multiple_merge);
     g_test_add_func("/initrd/cmdline/multiple/bootdev", test_multiple_bootdev);
     g_test_add_func("/initrd/cmdline/nameserver", test_nameserver);
@@ -1799,6 +2199,9 @@ main(int argc, char **argv)
     g_test_add_func("/initrd/cmdline/bond/ip", test_bond_ip);
     g_test_add_func("/initrd/cmdline/bond/default", test_bond_default);
     g_test_add_func("/initrd/cmdline/team", test_team);
+    g_test_add_func("/initrd/cmdline/vlan", test_vlan);
+    g_test_add_func("/initrd/cmdline/vlan/dhcp-on-parent", test_vlan_with_dhcp_on_parent);
+    g_test_add_func("/initrd/cmdline/vlan/over-bond", test_vlan_over_bond);
     g_test_add_func("/initrd/cmdline/bridge", test_bridge);
     g_test_add_func("/initrd/cmdline/bridge/default", test_bridge_default);
     g_test_add_func("/initrd/cmdline/bridge/ip", test_bridge_ip);
diff --git a/src/nm-config.c b/src/nm-config.c
index 1d751343..8f46661d 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -884,6 +884,7 @@ static const ConfigGroup config_groups[] = {
                              NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
                              NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
                              NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
+                             NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
                              NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
                              NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ),
     },
diff --git a/src/nm-config.h b/src/nm-config.h
index 698482e3..9f866f29 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -85,7 +85,9 @@
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS              "sriov-num-vfs"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND               "wifi.backend"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS "wifi.scan-rand-mac-address"
-#define NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT       "carrier-wait-timeout"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK \
+    "wifi.scan-generate-mac-address-mask"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT "carrier-wait-timeout"
 
 #define NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE "match-device"
 #define NM_CONFIG_KEYFILE_KEY_STOP_MATCH   "stop-match"
diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c
index 36aaf0fd..7b1e6f22 100644
--- a/src/nm-l3cfg.c
+++ b/src/nm-l3cfg.c
@@ -605,8 +605,8 @@ _load_link(NML3Cfg *self, gboolean initial)
     gboolean                        nacd_changed;
     gboolean                        nacd_new_valid;
     gboolean                        nacd_old_valid;
-    const guint8 *                  nacd_old_addr;
-    const guint8 *                  nacd_new_addr;
+    const guint8 *                  nacd_old_addr = NULL;
+    const guint8 *                  nacd_new_addr = NULL;
     gboolean                        nacd_link_now_up;
     AcdData *                       acd_data;
 
diff --git a/src/nm-manager.c b/src/nm-manager.c
index a04ca0e6..46f81c7c 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -2445,7 +2445,8 @@ _device_auth_done_fail_on_idle(gpointer user_data, GCancellable *cancellable)
     NMManagerDeviceAuthRequestFunc callback;
     gpointer                       callback_user_data;
 
-    nm_utils_user_data_unpack(&self,
+    nm_utils_user_data_unpack(user_data,
+                              &self,
                               &device,
                               &context,
                               &subject,
@@ -2858,15 +2859,20 @@ recheck_assume_connection(NMManager *self, NMDevice *device)
             activation_type_assume = TRUE;
 
             if (generated) {
+                gs_unref_object NMConnection *con2 = NULL;
+
+                con2 = nm_simple_connection_new_clone(
+                    nm_settings_connection_get_connection(sett_conn));
+
                 /* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */
-                nm_connection_add_setting(nm_settings_connection_get_connection(sett_conn),
+                nm_connection_add_setting(con2,
                                           g_object_new(NM_TYPE_SETTING_IP4_CONFIG,
                                                        NM_SETTING_IP_CONFIG_METHOD,
                                                        NM_SETTING_IP4_CONFIG_METHOD_AUTO,
                                                        NULL));
 
                 nm_settings_connection_update(sett_conn,
-                                              NULL,
+                                              con2,
                                               NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
                                               NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
                                               NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 60f5fb02..038b89cf 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -143,6 +143,11 @@ _dns_manager_set_ip_config(NMDnsManager *    dns_manager,
                            NMDnsIPConfigType ip_config_type,
                            NMDevice *        device)
 {
+    if (device && nm_device_sys_iface_state_is_external(device)) {
+        nm_dns_manager_set_ip_config(dns_manager, ip_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
+        return;
+    }
+
     if (NM_IN_SET(ip_config_type, NM_DNS_IP_CONFIG_TYPE_DEFAULT, NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE)
         && device
         && nm_device_get_route_metric_default(nm_device_get_device_type(device))
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 94c3f0e8..5bb0b426 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -225,7 +225,8 @@ link_add_pre(NMPlatform *platform,
              const char *name,
              NMLinkType  type,
              const void *address,
-             size_t      address_len)
+             size_t      address_len,
+             guint32     mtu)
 {
     NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE(platform);
     NMFakePlatformLink *   device;
@@ -251,6 +252,7 @@ link_add_pre(NMPlatform *platform,
     link->ifindex     = name ? ifindex : 0;
     link->type        = type;
     link->kind        = g_intern_string(nm_link_type_to_string(type));
+    link->mtu         = mtu;
     link->initialized = TRUE;
     if (name)
         strcpy(link->name, name);
@@ -285,6 +287,7 @@ link_add(NMPlatform *           platform,
          int                    parent,
          const void *           address,
          size_t                 address_len,
+         guint32                mtu,
          gconstpointer          extra_data,
          const NMPlatformLink **out_link)
 {
@@ -300,7 +303,7 @@ link_add(NMPlatform *           platform,
     NMPObject *                     dev_obj;
     NMPObject *                     dev_lnk = NULL;
 
-    device = link_add_pre(platform, name, type, address, address_len);
+    device = link_add_pre(platform, name, type, address, address_len, mtu);
 
     g_assert(device);
 
@@ -326,7 +329,7 @@ link_add(NMPlatform *           platform,
     case NM_LINK_TYPE_VETH:
         veth_peer = extra_data;
         g_assert(veth_peer);
-        device_veth = link_add_pre(platform, veth_peer, type, NULL, 0);
+        device_veth = link_add_pre(platform, veth_peer, type, NULL, 0, 0);
         break;
     case NM_LINK_TYPE_VLAN:
     {
@@ -401,7 +404,7 @@ link_add_one(NMPlatform *platform,
     NMPCacheOpsType                 cache_op;
     int                             ifindex;
 
-    device = link_add_pre(platform, name, NM_LINK_TYPE_VLAN, NULL, 0);
+    device = link_add_pre(platform, name, NM_LINK_TYPE_VLAN, NULL, 0, 0);
 
     ifindex = NMP_OBJECT_CAST_LINK(device->obj)->ifindex;
 
@@ -1313,10 +1316,10 @@ nm_fake_platform_setup(void)
 
     nm_platform_setup(platform);
 
-    link_add(platform, NM_LINK_TYPE_LOOPBACK, "lo", 0, NULL, 0, NULL, NULL);
-    link_add(platform, NM_LINK_TYPE_ETHERNET, "eth0", 0, NULL, 0, NULL, NULL);
-    link_add(platform, NM_LINK_TYPE_ETHERNET, "eth1", 0, NULL, 0, NULL, NULL);
-    link_add(platform, NM_LINK_TYPE_ETHERNET, "eth2", 0, NULL, 0, NULL, NULL);
+    link_add(platform, NM_LINK_TYPE_LOOPBACK, "lo", 0, NULL, 0, 0, NULL, NULL);
+    link_add(platform, NM_LINK_TYPE_ETHERNET, "eth0", 0, NULL, 0, 0, NULL, NULL);
+    link_add(platform, NM_LINK_TYPE_ETHERNET, "eth1", 0, NULL, 0, 0, NULL, NULL);
+    link_add(platform, NM_LINK_TYPE_ETHERNET, "eth2", 0, NULL, 0, 0, NULL, NULL);
 }
 
 static void
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 3fa70bbc..b377c85e 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -7341,6 +7341,7 @@ link_add(NMPlatform *           platform,
          int                    parent,
          const void *           address,
          size_t                 address_len,
+         guint32                mtu,
          gconstpointer          extra_data,
          const NMPlatformLink **out_link)
 {
@@ -7368,6 +7369,9 @@ link_add(NMPlatform *           platform,
     if (address && address_len)
         NLA_PUT(nlmsg, IFLA_ADDRESS, address_len, address);
 
+    if (mtu)
+        NLA_PUT_U32(nlmsg, IFLA_MTU, mtu);
+
     if (!_nl_msg_new_link_set_linkinfo(nlmsg, type, extra_data))
         return -NME_UNSPEC;
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index d1ff6fbc..7969206c 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -1230,11 +1230,13 @@ nm_platform_link_add(NMPlatform *           self,
                      int                    parent,
                      const void *           address,
                      size_t                 address_len,
+                     guint32                mtu,
                      gconstpointer          extra_data,
                      const NMPlatformLink **out_link)
 {
     int  r;
     char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
+    char mtu_buf[16];
     char parent_buf[64];
     char buf[512];
 
@@ -1254,6 +1256,7 @@ nm_platform_link_add(NMPlatform *           self,
            "\"%s\"" /* name */
            "%s%s"   /* parent */
            "%s%s"   /* address */
+           "%s%s"   /* mtu */
            "%s"     /* extra_data */
            "",
            nm_link_type_to_string(type),
@@ -1263,6 +1266,8 @@ nm_platform_link_add(NMPlatform *           self,
            address ? ", address: " : "",
            address ? _nm_utils_hwaddr_ntoa(address, address_len, FALSE, addr_buf, sizeof(addr_buf))
                    : "",
+           mtu ? ", mtu: " : "",
+           mtu ? nm_sprintf_buf(mtu_buf, "%u", mtu) : "",
            ({
                char *buf_p   = buf;
                gsize buf_len = sizeof(buf);
@@ -1345,7 +1350,8 @@ nm_platform_link_add(NMPlatform *           self,
                buf;
            }));
 
-    return klass->link_add(self, type, name, parent, address, address_len, extra_data, out_link);
+    return klass
+        ->link_add(self, type, name, parent, address, address_len, mtu, extra_data, out_link);
 }
 
 /**
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index c7b8017a..a1314000 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -1085,6 +1085,7 @@ typedef struct {
                     int                    parent,
                     const void *           address,
                     size_t                 address_len,
+                    guint32                mtu,
                     gconstpointer          extra_data,
                     const NMPlatformLink **out_link);
     gboolean (*link_delete)(NMPlatform *self, int ifindex);
@@ -1532,6 +1533,7 @@ int nm_platform_link_add(NMPlatform *           self,
                          int                    parent,
                          const void *           address,
                          size_t                 address_len,
+                         guint32                mtu,
                          gconstpointer          extra_data,
                          const NMPlatformLink **out_link);
 
@@ -1541,13 +1543,13 @@ nm_platform_link_veth_add(NMPlatform *           self,
                           const char *           peer,
                           const NMPlatformLink **out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_VETH, name, 0, NULL, 0, peer, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_VETH, name, 0, NULL, 0, 0, peer, out_link);
 }
 
 static inline int
 nm_platform_link_dummy_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_DUMMY, name, 0, NULL, 0, NULL, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_DUMMY, name, 0, NULL, 0, 0, NULL, out_link);
 }
 
 static inline int
@@ -1555,6 +1557,7 @@ nm_platform_link_bridge_add(NMPlatform *               self,
                             const char *               name,
                             const void *               address,
                             size_t                     address_len,
+                            guint32                    mtu,
                             const NMPlatformLnkBridge *props,
                             const NMPlatformLink **    out_link)
 {
@@ -1564,6 +1567,7 @@ nm_platform_link_bridge_add(NMPlatform *               self,
                                 0,
                                 address,
                                 address_len,
+                                mtu,
                                 props,
                                 out_link);
 }
@@ -1571,19 +1575,19 @@ nm_platform_link_bridge_add(NMPlatform *               self,
 static inline int
 nm_platform_link_bond_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_BOND, name, 0, NULL, 0, NULL, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_BOND, name, 0, NULL, 0, 0, NULL, out_link);
 }
 
 static inline int
 nm_platform_link_team_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_TEAM, name, 0, NULL, 0, NULL, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_TEAM, name, 0, NULL, 0, 0, NULL, out_link);
 }
 
 static inline int
 nm_platform_link_wireguard_add(NMPlatform *self, const char *name, const NMPlatformLink **out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_WIREGUARD, name, 0, NULL, 0, NULL, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_WIREGUARD, name, 0, NULL, 0, 0, NULL, out_link);
 }
 
 static inline int
@@ -1602,6 +1606,7 @@ nm_platform_link_gre_add(NMPlatform *            self,
                                 0,
                                 address,
                                 address_len,
+                                0,
                                 props,
                                 out_link);
 }
@@ -1612,7 +1617,7 @@ nm_platform_link_sit_add(NMPlatform *            self,
                          const NMPlatformLnkSit *props,
                          const NMPlatformLink ** out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_SIT, name, 0, NULL, 0, props, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_SIT, name, 0, NULL, 0, 0, props, out_link);
 }
 
 static inline int
@@ -1632,6 +1637,7 @@ nm_platform_link_vlan_add(NMPlatform *           self,
                                 parent,
                                 NULL,
                                 0,
+                                0,
                                 &((NMPlatformLnkVlan){
                                     .id    = vlanid,
                                     .flags = vlanflags,
@@ -1645,7 +1651,7 @@ nm_platform_link_vrf_add(NMPlatform *            self,
                          const NMPlatformLnkVrf *props,
                          const NMPlatformLink ** out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_VRF, name, 0, NULL, 0, props, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_VRF, name, 0, NULL, 0, 0, props, out_link);
 }
 
 static inline int
@@ -1654,7 +1660,7 @@ nm_platform_link_vxlan_add(NMPlatform *              self,
                            const NMPlatformLnkVxlan *props,
                            const NMPlatformLink **   out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, props, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, 0, props, out_link);
 }
 
 static inline int
@@ -1663,7 +1669,15 @@ nm_platform_link_6lowpan_add(NMPlatform *           self,
                              int                    parent,
                              const NMPlatformLink **out_link)
 {
-    return nm_platform_link_add(self, NM_LINK_TYPE_6LOWPAN, name, parent, NULL, 0, NULL, out_link);
+    return nm_platform_link_add(self,
+                                NM_LINK_TYPE_6LOWPAN,
+                                name,
+                                parent,
+                                NULL,
+                                0,
+                                0,
+                                NULL,
+                                out_link);
 }
 
 static inline int
@@ -1675,7 +1689,7 @@ nm_platform_link_ip6tnl_add(NMPlatform *               self,
     g_return_val_if_fail(props, -NME_BUG);
     g_return_val_if_fail(!props->is_gre, -NME_BUG);
 
-    return nm_platform_link_add(self, NM_LINK_TYPE_IP6TNL, name, 0, NULL, 0, props, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_IP6TNL, name, 0, NULL, 0, 0, props, out_link);
 }
 
 static inline int
@@ -1695,6 +1709,7 @@ nm_platform_link_ip6gre_add(NMPlatform *               self,
                                 0,
                                 address,
                                 address_len,
+                                0,
                                 props,
                                 out_link);
 }
@@ -1707,7 +1722,7 @@ nm_platform_link_ipip_add(NMPlatform *             self,
 {
     g_return_val_if_fail(props, -NME_BUG);
 
-    return nm_platform_link_add(self, NM_LINK_TYPE_IPIP, name, 0, NULL, 0, props, out_link);
+    return nm_platform_link_add(self, NM_LINK_TYPE_IPIP, name, 0, NULL, 0, 0, props, out_link);
 }
 
 static inline int
@@ -1720,7 +1735,15 @@ nm_platform_link_macsec_add(NMPlatform *               self,
     g_return_val_if_fail(props, -NME_BUG);
     g_return_val_if_fail(parent > 0, -NME_BUG);
 
-    return nm_platform_link_add(self, NM_LINK_TYPE_MACSEC, name, parent, NULL, 0, props, out_link);
+    return nm_platform_link_add(self,
+                                NM_LINK_TYPE_MACSEC,
+                                name,
+                                parent,
+                                NULL,
+                                0,
+                                0,
+                                props,
+                                out_link);
 }
 
 static inline int
@@ -1739,6 +1762,7 @@ nm_platform_link_macvlan_add(NMPlatform *                self,
                                 parent,
                                 NULL,
                                 0,
+                                0,
                                 props,
                                 out_link);
 }
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index e305357a..fb430046 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1534,7 +1534,7 @@ nmtstp_link_bridge_add(NMPlatform *               platform,
     }
 
     if (!pllink) {
-        r = nm_platform_link_bridge_add(platform, name, NULL, 0, lnk, &pllink);
+        r = nm_platform_link_bridge_add(platform, name, NULL, 0, 0, lnk, &pllink);
     }
 
     _assert_pllink(platform, r == 0, pllink, name, NM_LINK_TYPE_BRIDGE);
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 829abcc9..5fd67ec4 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -101,6 +101,7 @@ software_add(NMLinkType link_type, const char *name)
                                                                 name,
                                                                 NULL,
                                                                 0,
+                                                                0,
                                                                 &nm_platform_lnk_bridge_default,
                                                                 NULL));
     case NM_LINK_TYPE_BOND:
@@ -131,6 +132,7 @@ software_add(NMLinkType link_type, const char *name)
                                                              PARENT_NAME,
                                                              NULL,
                                                              0,
+                                                             0,
                                                              &nm_platform_lnk_bridge_default,
                                                              NULL)))
             accept_signal(parent_added);
@@ -581,6 +583,7 @@ test_bridge_addr(void)
                                                               DEVICE_NAME,
                                                               addr,
                                                               sizeof(addr),
+                                                              0,
                                                               &nm_platform_lnk_bridge_default,
                                                               &plink)));
     g_assert(plink);
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index e193ff96..10b93d0a 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -18,6 +18,7 @@ libnmdbus_ifcfg_rh = static_library(
   name,
   sources: dbus_sources,
   dependencies: glib_dep,
+  c_args: introspection_extra_cflags,
 )
 
 core_sources = files(