dns: make [global-dns] to overwrite configs from connections

According to the documentation, settings from [global-dns] (searches and
options) are always merged with those from connections. However this was
not happening if no [global-dns-domain-*] exists, in which case
connections were ignored. This happened because in the past both global
sections must de defined or undefined. When this was changed to allow
defining only [global-dns], allowing it in the function that generates
the resolv.conf file was forgotten. Fix that now.

Anyway, merging these configs doesn't make much sense. The searches and
options defined in connections probably make sense only for the nameservers
defined in that same connection.

Because of this, make the following change: if global nameservers are
defined, use searches and options from [global-dns] only, because those
defined in connections may not make sense for the global nameservers. If
[global-dns] is missing, assume an empty [global-dns] section.

Also, if no global nameservers are defined, but [global-dns] is, make
that it overwrites the searches and options defined in connections. This
is not ideal, but none of the alternatives is better and at least this
is easy to remember.

So, the resulting rules from above are:
- If [global-dns] is defined, it always overwrite searches and options
  from connections.
- If [global-dns-domain-*] is defined, it always overwrite nameservers
  from connections. It overwrites searches and options too.

Fixes: 1f0d1d78d2 ('dns-manager: always apply options from [global-dns]')
Fixes: f57a848da5 ('man: update documentation about global DNS configuration')
This commit is contained in:
Íñigo Huguet 2025-08-25 08:58:21 +02:00
parent 294131a2a4
commit 1cba0a3cca
6 changed files with 94 additions and 40 deletions

View file

@ -1540,8 +1540,12 @@ managed=1
<refsect1>
<title><literal>global-dns</literal> section</title>
<para>This section specifies DNS settings that are applied
globally, in addition to connection-specific ones.</para>
<para>This section specifies DNS settings that are applied globally. They
override the equivalent options defined in individual connections, making
them to be ignored. If a [global-dns-domain-*] section is defined, but this
section isn't, an empty [global-dns] section is assumed, thus overwriting
connection specific configurations too.
</para>
<para>
<variablelist>
<varlistentry>
@ -1601,6 +1605,10 @@ managed=1
default domain "*". When the global DNS domains are valid, the
name servers and domains defined globally override the ones from
active connections.
If any global DNS domain is defined but a [global-dns] section isn't,
an empty [global-dns] section is assumed, thus overwriting its
connection specific configurations too.
</para>
<para>
<variablelist>

View file

@ -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);
}
}
}

View file

@ -50,9 +50,10 @@ struct _NMGlobalDnsConfig {
char **options;
GHashTable *domains;
const char **domain_list;
gboolean internal;
char *cert_authority;
NMDnsResolveMode resolve_mode;
gboolean internal;
gboolean has_global_dns;
};
/*****************************************************************************/
@ -941,6 +942,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->has_global_dns;
}
const char *const *
nm_global_dns_config_get_searches(const NMGlobalDnsConfig *dns_config)
{
@ -1386,6 +1395,12 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
return NULL;
}
/* Defining [global-dns-domain-*] implies defining [global-dns] too (maybe empty) */
if (default_found)
dns_config->has_global_dns = TRUE;
else
dns_config->has_global_dns = g_key_file_has_group(keyfile, group);
dns_config->internal = internal;
global_dns_config_seal_domains(dns_config);
return dns_config;

View file

@ -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);

View file

@ -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);

View file

@ -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__ */