summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2025-09-12 19:47:07 +0200
committerMichael Biebl <biebl@debian.org>2025-09-12 19:47:07 +0200
commitbc5f10851bffd6af1c2855635be7a28b6dba3195 (patch)
treed47255895378efcdc0a32c2ddeed055f76935654 /src/core
parent7e9091a5960f67a84787c03153324915220e4816 (diff)
New upstream version 1.54.1 upstream/1.54.1
Diffstat (limited to 'src/core')
-rw-r--r--src/core/devices/nm-device-bond.c3
-rw-r--r--src/core/devices/nm-device.c25
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c51
-rw-r--r--src/core/dns/nm-dns-manager.c78
-rw-r--r--src/core/nm-config-data.c38
-rw-r--r--src/core/nm-config-data.h1
-rw-r--r--src/core/nm-config.c14
-rw-r--r--src/core/nm-l3-config-data.c23
-rw-r--r--src/core/nm-l3-config-data.h3
-rw-r--r--src/core/nm-manager.c13
-rw-r--r--src/core/tests/config/global-dns-empty.conf3
-rw-r--r--src/core/tests/config/global-dns-not-set.conf5
-rw-r--r--src/core/tests/config/test-config.c22
13 files changed, 197 insertions, 82 deletions
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 673d2361..39e68e96 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -52,8 +52,7 @@
         NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \
         NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_USE_CARRIER,            \
         NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,      \
-        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX,    \
-        NM_SETTING_BOND_OPTION_LACP_ACTIVE
+        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX
 
 #define OPTIONS_REAPPLY_FULL                                     \
     OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index f6057e52..79b1076d 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -9164,6 +9164,10 @@ is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
+    /* unrealized software devices are always available, hardware devices never */
+    if (!nm_device_is_real(self))
+        return nm_device_is_software(self);
+
     if (priv->carrier || priv->ignore_carrier)
         return TRUE;
 
@@ -14696,13 +14700,15 @@ impl_device_delete(NMDBusObject                      *obj,
                            NULL);
 }
 
-static void
+static gboolean
 _device_activate(NMDevice *self, NMActRequest *req)
 {
     NMConnection *connection;
 
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(NM_IS_ACT_REQUEST(req));
+    /* Returns TRUE on success, FALSE if the activation request could not be started */
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
+    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), FALSE);
     nm_assert(nm_device_is_real(self));
 
     /* Ensure the activation request is still valid; the controller may have
@@ -14710,7 +14716,7 @@ _device_activate(NMDevice *self, NMActRequest *req)
      */
     if (nm_active_connection_get_state(NM_ACTIVE_CONNECTION(req))
         >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)
-        return;
+        return FALSE;
 
     if (!nm_device_get_managed(self, FALSE)) {
         /* It's unclear why the device would be unmanaged at this point.
@@ -14721,7 +14727,7 @@ _device_activate(NMDevice *self, NMActRequest *req)
         nm_active_connection_set_state_fail((NMActiveConnection *) req,
                                             NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
                                             NULL);
-        return;
+        return FALSE;
     }
 
     connection = nm_act_request_get_applied_connection(req);
@@ -14737,6 +14743,8 @@ _device_activate(NMDevice *self, NMActRequest *req)
     act_request_set(self, req);
 
     nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
+
+    return TRUE;
 }
 
 static void
@@ -14756,7 +14764,9 @@ _carrier_wait_check_queued_act_request(NMDevice *self)
 
         _LOGD(LOGD_DEVICE, "Activate queued activation request as we now have carrier");
         queued_req = g_steal_pointer(&priv->queued_act_request);
-        _device_activate(self, queued_req);
+        if (!_device_activate(self, queued_req)) {
+            delete_on_deactivate_check_and_schedule(self);
+        }
     }
 }
 
@@ -17534,7 +17544,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
             gs_unref_object NMActRequest *queued_req = NULL;
 
             queued_req = g_steal_pointer(&priv->queued_act_request);
-            _device_activate(self, queued_req);
+            if (!_device_activate(self, queued_req))
+                delete_on_deactivate_check_and_schedule(self);
         }
         break;
     case NM_DEVICE_STATE_ACTIVATED:
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 06eee142..b890b110 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -196,7 +196,8 @@ static void periodic_update(NMDeviceWifi *self);
 static void ap_add_remove(NMDeviceWifi *self,
                           gboolean      is_adding,
                           NMWifiAP     *ap,
-                          gboolean      recheck_available_connections);
+                          gboolean      recheck_available_connections,
+                          gboolean      recheck_auto_activate);
 
 static void _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset);
 
@@ -714,7 +715,10 @@ update_seen_bssids_cache(NMDeviceWifi *self, NMWifiAP *ap)
 }
 
 static void
-set_current_ap(NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
+set_current_ap(NMDeviceWifi *self,
+               NMWifiAP     *new_ap,
+               gboolean      recheck_available_connections,
+               gboolean      recheck_auto_activate)
 {
     NMDeviceWifiPrivate *priv;
     NMWifiAP            *old_ap;
@@ -741,7 +745,11 @@ set_current_ap(NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available_
         /* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */
         if (NM_IN_SET(mode, _NM_802_11_MODE_ADHOC, _NM_802_11_MODE_AP)
             || nm_wifi_ap_get_fake(old_ap))
-            ap_add_remove(self, FALSE, old_ap, recheck_available_connections);
+            ap_add_remove(self,
+                          FALSE,
+                          old_ap,
+                          recheck_available_connections,
+                          recheck_auto_activate);
         g_object_unref(old_ap);
     }
 
@@ -814,7 +822,8 @@ static void
 ap_add_remove(NMDeviceWifi *self,
               gboolean      is_adding, /* or else removing */
               NMWifiAP     *ap,
-              gboolean      recheck_available_connections)
+              gboolean      recheck_available_connections,
+              gboolean      recheck_auto_activate)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
@@ -845,13 +854,14 @@ ap_add_remove(NMDeviceWifi *self,
         nm_dbus_object_clear_and_unexport(&ap);
     }
 
-    nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
+    if (recheck_auto_activate)
+        nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
     if (recheck_available_connections)
         nm_device_recheck_available_connections(NM_DEVICE(self));
 }
 
 static void
-remove_all_aps(NMDeviceWifi *self)
+remove_all_aps(NMDeviceWifi *self, gboolean disposing)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMWifiAP            *ap;
@@ -859,12 +869,13 @@ remove_all_aps(NMDeviceWifi *self)
     if (c_list_is_empty(&priv->aps_lst_head))
         return;
 
-    set_current_ap(self, NULL, FALSE);
+    set_current_ap(self, NULL, FALSE, !disposing);
 
     while ((ap = c_list_first_entry(&priv->aps_lst_head, NMWifiAP, aps_lst)))
-        ap_add_remove(self, FALSE, ap, FALSE);
+        ap_add_remove(self, FALSE, ap, FALSE, !disposing);
 
-    nm_device_recheck_available_connections(NM_DEVICE(self));
+    if (!disposing)
+        nm_device_recheck_available_connections(NM_DEVICE(self));
 }
 
 static gboolean
@@ -951,7 +962,7 @@ deactivate(NMDevice *device)
 
     priv->rate = 0;
 
-    set_current_ap(self, NULL, TRUE);
+    set_current_ap(self, NULL, TRUE, TRUE);
 
     if (!wake_on_wlan_restore(self))
         _LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot unconfigure WoWLAN.");
@@ -2000,7 +2011,7 @@ supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
             if (nm_wifi_ap_set_fake(found_ap, TRUE))
                 _ap_dump(self, LOGL_DEBUG, found_ap, "updated", 0);
         } else {
-            ap_add_remove(self, FALSE, found_ap, TRUE);
+            ap_add_remove(self, FALSE, found_ap, TRUE, TRUE);
             schedule_ap_list_dump(self);
         }
         return;
@@ -2043,7 +2054,7 @@ supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
             }
         }
 
-        ap_add_remove(self, TRUE, ap, TRUE);
+        ap_add_remove(self, TRUE, ap, TRUE, TRUE);
     }
 
     /* Update the current AP if the supplicant notified a current BSS change
@@ -2268,7 +2279,7 @@ link_timeout_cb(gpointer user_data)
     if (nm_device_get_state(device) != NM_DEVICE_STATE_ACTIVATED)
         return FALSE;
 
-    set_current_ap(self, NULL, TRUE);
+    set_current_ap(self, NULL, TRUE, TRUE);
 
     nm_device_state_changed(device,
                             NM_DEVICE_STATE_FAILED,
@@ -2684,7 +2695,7 @@ supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
             }
         }
 
-        set_current_ap(self, new_ap, TRUE);
+        set_current_ap(self, new_ap, TRUE, TRUE);
 
         req = nm_device_get_act_request(NM_DEVICE(self));
         if (req) {
@@ -3118,7 +3129,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         priv->mode = _NM_802_11_MODE_AP;
 
         /* Scanning not done in AP mode; clear the scan list */
-        remove_all_aps(self);
+        remove_all_aps(self, FALSE);
     } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0)
         priv->mode = _NM_802_11_MODE_MESH;
     _notify(self, PROP_MODE);
@@ -3155,14 +3166,14 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             nm_wifi_ap_set_address(ap_fake, nm_device_get_hw_address(device));
 
         g_object_freeze_notify(G_OBJECT(self));
-        ap_add_remove(self, TRUE, ap_fake, TRUE);
+        ap_add_remove(self, TRUE, ap_fake, TRUE, TRUE);
         g_object_thaw_notify(G_OBJECT(self));
         ap = ap_fake;
     }
 
     _scan_notify_allowed(self, NM_TERNARY_DEFAULT);
 
-    set_current_ap(self, ap, FALSE);
+    set_current_ap(self, ap, FALSE, TRUE);
     nm_active_connection_set_specific_object(NM_ACTIVE_CONNECTION(req),
                                              nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)));
     return NM_ACT_STAGE_RETURN_SUCCESS;
@@ -3528,7 +3539,7 @@ device_state_changed(NMDevice           *device,
 
         cleanup_association_attempt(self, TRUE);
         cleanup_supplicant_failures(self);
-        remove_all_aps(self);
+        remove_all_aps(self, FALSE);
     }
 
     switch (new_state) {
@@ -3566,7 +3577,7 @@ device_state_changed(NMDevice           *device,
     }
 
     if (clear_aps)
-        remove_all_aps(self);
+        remove_all_aps(self, FALSE);
 
     _scan_notify_allowed(self, NM_TERNARY_DEFAULT);
 }
@@ -3808,7 +3819,7 @@ dispose(GObject *object)
 
     g_clear_object(&priv->sup_mgr);
 
-    remove_all_aps(self);
+    remove_all_aps(self, TRUE);
 
     if (priv->p2p_device) {
         /* Destroy the P2P device. */
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index d47590dc..57e73226 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -586,7 +586,11 @@ add_dns_domains(GPtrArray            *array,
 }
 
 static void
-merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3ConfigData *l3cd)
+merge_one_l3cd(NMResolvConfData     *rc,
+               int                   addr_family,
+               int                   ifindex,
+               const NML3ConfigData *l3cd,
+               gboolean              ignore_searches_and_options)
 {
     char               buf[NM_INET_ADDRSTRLEN + 50];
     gboolean           has_trust_ad;
@@ -624,30 +628,32 @@ merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3Con
         add_string_item(rc->nameservers, buf, TRUE);
     }
 
-    add_dns_domains(rc->searches, addr_family, l3cd, FALSE, TRUE);
+    if (!ignore_searches_and_options) {
+        add_dns_domains(rc->searches, addr_family, l3cd, FALSE, TRUE);
 
-    has_trust_ad = FALSE;
-    strarr       = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num);
-    for (i = 0; i < num; i++) {
-        const char *option = strarr[i];
+        has_trust_ad = FALSE;
+        strarr       = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num);
+        for (i = 0; i < num; i++) {
+            const char *option = strarr[i];
 
-        if (nm_streq(option, NM_SETTING_DNS_OPTION_TRUST_AD)) {
-            has_trust_ad = TRUE;
-            continue;
+            if (nm_streq(option, NM_SETTING_DNS_OPTION_TRUST_AD)) {
+                has_trust_ad = TRUE;
+                continue;
+            }
+            add_dns_option_item(rc->options, option);
         }
-        add_dns_option_item(rc->options, option);
-    }
 
-    if (num_nameservers == 0) {
-        /* If the @l3cd contributes no DNS servers, ignore whether trust-ad is set or unset
-         * for this @l3cd. */
-    } else if (has_trust_ad) {
-        /* We only set has_trust_ad to TRUE, if all IP configs agree (or don't contribute).
-         * Once set to FALSE, it doesn't get reset. */
-        if (rc->has_trust_ad == NM_TERNARY_DEFAULT)
-            rc->has_trust_ad = NM_TERNARY_TRUE;
-    } else
-        rc->has_trust_ad = NM_TERNARY_FALSE;
+        if (num_nameservers == 0) {
+            /* If the @l3cd contributes no DNS servers, ignore whether trust-ad is set or unset
+             * for this @l3cd. */
+        } else if (has_trust_ad) {
+            /* We only set has_trust_ad to TRUE, if all IP configs agree (or don't contribute).
+             * Once set to FALSE, it doesn't get reset. */
+            if (rc->has_trust_ad == NM_TERNARY_DEFAULT)
+                rc->has_trust_ad = NM_TERNARY_TRUE;
+        } else
+            rc->has_trust_ad = NM_TERNARY_FALSE;
+    }
 
     if (addr_family == AF_INET) {
         const in_addr_t *nis_servers;
@@ -1231,12 +1237,15 @@ compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[
 {
     nm_auto_free_checksum GChecksum *sum = NULL;
     NMDnsConfigIPData               *ip_data;
+    gboolean                         has_global_dns_section = FALSE;
 
     sum = g_checksum_new(G_CHECKSUM_SHA1);
     nm_assert(HASH_LEN == g_checksum_type_get_length(G_CHECKSUM_SHA1));
 
-    if (global)
+    if (global) {
         nm_global_dns_config_update_checksum(global, sum);
+        has_global_dns_section = nm_global_dns_has_global_dns_section(global);
+    }
 
     if (!global || !nm_global_dns_config_lookup_domain(global, "*")) {
         const CList *head;
@@ -1248,7 +1257,8 @@ compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[
             nm_l3_config_data_hash_dns(ip_data->l3cd,
                                        sum,
                                        ip_data->addr_family,
-                                       ip_data->ip_config_type);
+                                       ip_data->ip_config_type,
+                                       has_global_dns_section);
         }
     }
 
@@ -1264,6 +1274,9 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
     const char *const *servers;
     guint              i;
 
+    /* Global config must be processed before connections' config */
+    nm_assert(rc->nameservers->len == 0);
+
     if (!global_conf)
         return FALSE;
 
@@ -1351,12 +1364,17 @@ _collect_resolv_conf_data(NMDnsManager      *self,
             .nis_servers  = g_ptr_array_new(),
             .has_trust_ad = NM_TERNARY_DEFAULT,
     };
+    gboolean has_global_dns_section = FALSE;
 
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    if (global_config)
+    if (global_config) {
         merge_global_dns_config(&rc, global_config);
+        has_global_dns_section = nm_global_dns_has_global_dns_section(global_config);
+    }
 
+    /* If global nameservers are defined, no DNS configs are used from connections at all,
+     * including searches and options. */
     if (!global_config || !nm_global_dns_config_lookup_domain(global_config, "*")) {
         nm_auto_str_buf NMStrBuf tmp_strbuf = NM_STR_BUF_INIT(0, FALSE);
         int                      first_prio = 0;
@@ -1390,8 +1408,16 @@ _collect_resolv_conf_data(NMDnsManager      *self,
                   skip ? "<SKIP>" : "",
                   get_nameserver_list(ip_data->addr_family, ip_data->l3cd, &tmp_strbuf));
 
-            if (!skip)
-                merge_one_l3cd(&rc, ip_data->addr_family, ip_data->data->ifindex, ip_data->l3cd);
+            if (!skip) {
+                /* Merge the configs from connections. However, if there was a [global-dns]
+                 * it overwrites searches and options from the connections, thus we only
+                 * merge the nameservers. */
+                merge_one_l3cd(&rc,
+                               ip_data->addr_family,
+                               ip_data->data->ifindex,
+                               ip_data->l3cd,
+                               has_global_dns_section);
+            }
         }
     }
 
diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c
index 5a84a2c8..461fd8ed 100644
--- a/src/core/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -50,9 +50,9 @@ struct _NMGlobalDnsConfig {
     char           **options;
     GHashTable      *domains;
     const char     **domain_list;
-    gboolean         internal;
     char            *cert_authority;
     NMDnsResolveMode resolve_mode;
+    gboolean         internal;
 };
 
 /*****************************************************************************/
@@ -941,6 +941,14 @@ next:
 
 /*****************************************************************************/
 
+gboolean
+nm_global_dns_has_global_dns_section(const NMGlobalDnsConfig *dns_config)
+{
+    g_return_val_if_fail(dns_config, FALSE);
+
+    return dns_config->searches != NULL || dns_config->options != NULL;
+}
+
 const char *const *
 nm_global_dns_config_get_searches(const NMGlobalDnsConfig *dns_config)
 {
@@ -1236,6 +1244,7 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
     gs_free char      *cert_authority = NULL;
     gs_free char      *resolve_mode   = NULL;
     NMDnsResolveMode   parsed_resolve_mode;
+    gboolean           has_global_dns_section;
 
     if (internal) {
         group         = NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS;
@@ -1386,6 +1395,22 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
         return NULL;
     }
 
+    /* Defining [global-dns-domain-*] implies defining [global-dns] too (maybe empty) */
+    if (default_found)
+        has_global_dns_section = TRUE;
+    else
+        has_global_dns_section = g_key_file_has_group(keyfile, group);
+
+    /* If there exist a [global-dns] section, always initialize "searches" and "options" so
+     * they appear in D-Bus. Clients can use this to know if it's defined, so they can know
+     * if DNS configs from connections are relevant or not. */
+    if (has_global_dns_section) {
+        if (!dns_config->searches)
+            dns_config->searches = nm_strv_empty_new();
+        if (!dns_config->options)
+            dns_config->options = nm_strv_empty_new();
+    }
+
     dns_config->internal = internal;
     global_dns_config_seal_domains(dns_config);
     return dns_config;
@@ -1606,17 +1631,6 @@ nm_global_dns_config_from_dbus(const GValue *value, GError **error)
         g_variant_unref(val);
     }
 
-    /* An empty value is valid and clears the internal configuration */
-    if (!nm_global_dns_config_is_empty(dns_config)
-        && !nm_global_dns_config_lookup_domain(dns_config, "*")) {
-        g_set_error_literal(error,
-                            NM_MANAGER_ERROR,
-                            NM_MANAGER_ERROR_FAILED,
-                            "Global DNS configuration is missing the default domain");
-        nm_global_dns_config_free(dns_config);
-        return NULL;
-    }
-
     global_dns_config_seal_domains(dns_config);
     return dns_config;
 }
diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h
index d764a7c4..6666ccf6 100644
--- a/src/core/nm-config-data.h
+++ b/src/core/nm-config-data.h
@@ -274,6 +274,7 @@ gboolean nm_config_data_is_intern_atomic_group(const NMConfigData *self, const c
 
 GKeyFile *nm_config_data_clone_keyfile_intern(const NMConfigData *self);
 
+gboolean           nm_global_dns_has_global_dns_section(const NMGlobalDnsConfig *dns_config);
 const char *const *nm_global_dns_config_get_searches(const NMGlobalDnsConfig *dns_config);
 const char *const *nm_global_dns_config_get_options(const NMGlobalDnsConfig *dns_config);
 const char *nm_global_dns_config_get_certification_authority(const NMGlobalDnsConfig *dns_config);
diff --git a/src/core/nm-config.c b/src/core/nm-config.c
index a55d2d13..d1f2bbed 100644
--- a/src/core/nm-config.c
+++ b/src/core/nm-config.c
@@ -18,6 +18,7 @@
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-core-intern/nm-keyfile-internal.h"
 #include "libnm-core-intern/nm-keyfile-utils.h"
+#include "libnm-glib-aux/nm-keyfile-aux.h"
 
 #define DEFAULT_CONFIG_MAIN_FILE     NMCONFDIR "/NetworkManager.conf"
 #define DEFAULT_CONFIG_DIR           NMCONFDIR "/conf.d"
@@ -1046,6 +1047,10 @@ read_config(GKeyFile   *keyfile,
             /* internal groups cannot be set by user configuration. */
             continue;
         }
+
+        if (!g_key_file_has_group(keyfile, group))
+            nm_key_file_add_group(keyfile, group);
+
         keys = g_key_file_get_keys(kf, group, &nkeys, NULL);
         if (!keys)
             continue;
@@ -1639,6 +1644,12 @@ intern_config_read(const char        *filename,
                                  "");
         }
 
+        if (!g_key_file_has_group(keyfile_intern, group)) {
+            nm_key_file_add_group(keyfile_intern, group);
+            if (is_intern)
+                has_intern = TRUE;
+        }
+
         for (k = 0; keys[k]; k++) {
             gs_free char *value_set = NULL;
             const char   *key       = keys[k];
@@ -1823,6 +1834,9 @@ intern_config_write(const char        *filename,
             }
         }
 
+        if (!g_key_file_has_group(keyfile, group))
+            nm_key_file_add_group(keyfile, group);
+
         for (k = 0; keys[k]; k++) {
             const char   *key       = keys[k];
             gs_free char *value_set = NULL;
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 20623145..666aa8a3 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -3139,7 +3139,8 @@ void
 nm_l3_config_data_hash_dns(const NML3ConfigData *l3cd,
                            GChecksum            *sum,
                            int                   addr_family,
-                           NMDnsIPConfigType     dns_ip_config_type)
+                           NMDnsIPConfigType     dns_ip_config_type,
+                           gboolean              ignore_searches_and_options)
 {
     guint              i;
     int                val;
@@ -3178,16 +3179,18 @@ nm_l3_config_data_hash_dns(const NML3ConfigData *l3cd,
         empty = FALSE;
     }
 
-    searches = nm_l3_config_data_get_searches(l3cd, addr_family, &num_searches);
-    for (i = 0; i < num_searches; i++) {
-        g_checksum_update(sum, (const guint8 *) searches[i], strlen(searches[i]));
-        empty = FALSE;
-    }
+    if (!ignore_searches_and_options) {
+        searches = nm_l3_config_data_get_searches(l3cd, addr_family, &num_searches);
+        for (i = 0; i < num_searches; i++) {
+            g_checksum_update(sum, (const guint8 *) searches[i], strlen(searches[i]));
+            empty = FALSE;
+        }
 
-    options = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num_options);
-    for (i = 0; i < num_options; i++) {
-        g_checksum_update(sum, (const guint8 *) options[i], strlen(options[i]));
-        empty = FALSE;
+        options = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num_options);
+        for (i = 0; i < num_options; i++) {
+            g_checksum_update(sum, (const guint8 *) options[i], strlen(options[i]));
+            empty = FALSE;
+        }
     }
 
     val = nm_l3_config_data_get_mdns(l3cd);
diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h
index bcb6af67..265e126d 100644
--- a/src/core/nm-l3-config-data.h
+++ b/src/core/nm-l3-config-data.h
@@ -615,6 +615,7 @@ nmtst_l3_config_data_get_best_gateway(const NML3ConfigData *self, int addr_famil
 void nm_l3_config_data_hash_dns(const NML3ConfigData *l3cd,
                                 GChecksum            *sum,
                                 int                   addr_family,
-                                NMDnsIPConfigType     dns_ip_config_type);
+                                NMDnsIPConfigType     dns_ip_config_type,
+                                gboolean              ignore_searches_and_options);
 
 #endif /* __NM_L3_CONFIG_DATA_H__ */
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index fce37071..9dc7bc01 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -5718,6 +5718,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
     GError                  *local = NULL;
     NMConnectionMultiConnect multi_connect;
     const char              *parent_spec;
+    gboolean                 did_realize = FALSE;
 
     g_return_val_if_fail(NM_IS_MANAGER(self), FALSE);
     g_return_val_if_fail(NM_IS_ACTIVE_CONNECTION(active), FALSE);
@@ -5892,6 +5893,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
                                nm_device_get_iface(device));
                 return FALSE;
             }
+            did_realize = TRUE;
         }
     }
 
@@ -5923,7 +5925,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
                         "The controller connection '%s' is not compatible with '%s'",
                         nm_settings_connection_get_id(controller_connection),
                         nm_settings_connection_get_id(sett_conn));
-            return FALSE;
+            goto err_unrealize;
         }
 
         if (!controller_ac) {
@@ -5946,7 +5948,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
                                    "Controller connection '%s' can't be activated: ",
                                    nm_settings_connection_get_id(controller_connection));
                 }
-                return FALSE;
+                goto err_unrealize;
             }
         }
 
@@ -6040,7 +6042,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
                                 NM_MANAGER_ERROR,
                                 NM_MANAGER_ERROR_DEPENDENCY_FAILED,
                                 "Activation failed because the device is unmanaged");
-            return FALSE;
+            goto err_unrealize;
         }
     }
 
@@ -6048,6 +6050,11 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
     active_connection_add(self, active);
     nm_device_queue_activation(device, NM_ACT_REQUEST(active));
     return TRUE;
+
+err_unrealize:
+    if (did_realize)
+        nm_device_unrealize(device, TRUE, NULL);
+    return FALSE;
 }
 
 static gboolean
diff --git a/src/core/tests/config/global-dns-empty.conf b/src/core/tests/config/global-dns-empty.conf
new file mode 100644
index 00000000..fba823bf
--- /dev/null
+++ b/src/core/tests/config/global-dns-empty.conf
@@ -0,0 +1,3 @@
+# Good configuration, an empty global-dns section must be valid
+
+[global-dns]
diff --git a/src/core/tests/config/global-dns-not-set.conf b/src/core/tests/config/global-dns-not-set.conf
new file mode 100644
index 00000000..133bd4ed
--- /dev/null
+++ b/src/core/tests/config/global-dns-not-set.conf
@@ -0,0 +1,5 @@
+# Good configuration, an empty [global-dns] must be implicitly assumed because a domain is defined
+
+[global-dns-domain-*]
+servers=4.5.6.7
+options=myoption1
diff --git a/src/core/tests/config/test-config.c b/src/core/tests/config/test-config.c
index 2980cda7..78fd1057 100644
--- a/src/core/tests/config/test-config.c
+++ b/src/core/tests/config/test-config.c
@@ -387,7 +387,27 @@ test_config_global_dns(void)
     g_assert(dns);
     g_object_unref(config);
 
-    /* Check that a file with a domain domain, but without a default one gives a NULL configuration */
+    /* Check that a file with an empty global-dns section gives a good configuration.
+     * Check also that searches and options are not NULL, as this is how we expose to
+     * D-Bus that global-dns is defined. */
+    config =
+        setup_config(NULL, TEST_DIR "/global-dns-empty.conf", "", NULL, "/no/such/dir", "", NULL);
+    dns = nm_config_data_get_global_dns_config(nm_config_get_data_orig(config));
+    g_assert(dns);
+    g_assert(nm_global_dns_config_get_searches(dns));
+    g_assert(nm_global_dns_config_get_options(dns));
+    g_object_unref(config);
+
+    /* Check that a file with a domain, but no global-dns, assumes an implicit empty global-dns */
+    config =
+        setup_config(NULL, TEST_DIR "/global-dns-not-set.conf", "", NULL, "/no/such/dir", "", NULL);
+    dns = nm_config_data_get_global_dns_config(nm_config_get_data_orig(config));
+    g_assert(dns);
+    g_assert(nm_global_dns_config_get_searches(dns));
+    g_assert(nm_global_dns_config_get_options(dns));
+    g_object_unref(config);
+
+    /* Check that a file with a domain, but without a default one, gives a NULL configuration */
     config =
         setup_config(NULL, TEST_DIR "/global-dns-invalid.conf", "", NULL, "/no/such/dir", "", NULL);
     dns = nm_config_data_get_global_dns_config(nm_config_get_data_orig(config));