dns: Rework DNS checksums and isolate resolvconf operations

Now all the plugins for configuration of DNS resolvers have
their own implementation of checksums. This is required,
because the plugins are paying different amount of attention
to different parameters of connections. The previous common
implementation for example ignored new routes and thus
did not trigger sending update with forwarding of reverse
DNS queries to those servers.

Additionally to make dns-manager more clear, operations with
resolv.conf have been moved to their separate plugin
nm-dns-resolv_conf
This commit is contained in:
Tomas Korbar 2026-03-23 13:28:13 +01:00
parent 91db5d5f90
commit 3c01edf937
10 changed files with 1757 additions and 1259 deletions

View file

@ -808,6 +808,77 @@ dispose(GObject *object)
g_clear_object(&priv->dbus_connection);
}
static void
dnsconfd_checksum(const NML3ConfigData *l3cd,
GChecksum *sum,
int addr_family,
NMDnsIPConfigType dns_ip_config_type)
{
guint i;
NMDedupMultiIter ipconf_iter;
const NMPObject *obj;
const NMPlatformIPRoute *route;
const char *const *strarr;
const in_addr_t *wins;
guint element_num;
int prio = 0;
gboolean empty = TRUE;
g_return_if_fail(l3cd);
g_return_if_fail(sum);
strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (gpointer) strarr[i], strlen(strarr[i]));
empty = FALSE;
}
if (addr_family == AF_INET) {
wins = nm_l3_config_data_get_wins(l3cd, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (guint8 *) &wins[i], 4);
empty = FALSE;
}
}
/* Without servers, the sum should be zero, as Dnsconfd API is oriented
* around servers */
if (empty) {
_LOGT("no servers, thus checksum is zero");
return;
}
strarr = nm_l3_config_data_get_searches(l3cd, addr_family, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i]));
}
/* If we've got searches, then we do not care about domains, because
* searches have higher priority */
if (!element_num) {
strarr = nm_l3_config_data_get_domains(l3cd, addr_family, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i]));
}
}
nm_l3_config_data_iter_obj_for_each (&ipconf_iter,
l3cd,
&obj,
NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))) {
route = NMP_OBJECT_CAST_IP_ROUTE(obj);
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)
|| route->table_coerced == NM_DNS_ROUTES_FWMARK_TABLE_PRIO) {
continue;
}
g_checksum_update(sum, (const guint8 *) route, sizeof(*route));
}
g_checksum_update(sum, (const guint8 *) &dns_ip_config_type, sizeof(dns_ip_config_type));
nm_l3_config_data_get_dns_priority(l3cd, addr_family, &prio);
g_checksum_update(sum, (const guint8 *) &prio, sizeof(prio));
}
static void
nm_dns_dnsconfd_class_init(NMDnsDnsconfdClass *dns_class)
{
@ -821,4 +892,5 @@ nm_dns_dnsconfd_class_init(NMDnsDnsconfdClass *dns_class)
plugin_class->stop = stop;
plugin_class->update = update;
plugin_class->get_update_pending = get_update_pending;
plugin_class->checksum = dnsconfd_checksum;
}

View file

@ -22,6 +22,7 @@
#include "nm-dbus-manager.h"
#include "NetworkManagerUtils.h"
#include "nm-l3-config-data.h"
#include "nm-l3cfg.h"
#define PIDFILE NMRUNDIR "/dnsmasq.pid"
#define CONFDIR NMCONFDIR "/dnsmasq.d"
@ -1228,6 +1229,76 @@ update(NMDnsPlugin *plugin, NMDnsUpdateData *update_data, GError **error)
return TRUE;
}
static void
dnsmasq_checksum(const NML3ConfigData *l3cd,
GChecksum *sum,
int addr_family,
NMDnsIPConfigType dns_ip_config_type)
{
guint i;
NMDedupMultiIter ipconf_iter;
const NMPObject *obj;
const NMPlatformIPRoute *route;
const char *const *strarr;
const in_addr_t *wins;
guint element_num;
int prio = 0;
gboolean empty = TRUE;
g_return_if_fail(l3cd);
g_return_if_fail(sum);
strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (gpointer) strarr[i], strlen(strarr[i]));
empty = FALSE;
}
if (addr_family == AF_INET) {
wins = nm_l3_config_data_get_wins(l3cd, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (guint8 *) &wins[i], 4);
empty = FALSE;
}
}
/* Without servers, the sum should be zero, as Dnsmasq plugin depends on servers */
if (empty) {
return;
}
strarr = nm_l3_config_data_get_searches(l3cd, addr_family, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i]));
}
/* If we've got searches, then we do not care about domains, because
* searches have higher priority */
if (!element_num) {
strarr = nm_l3_config_data_get_domains(l3cd, addr_family, &element_num);
for (i = 0; i < element_num; i++) {
g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i]));
}
}
/* Dnsmasq consumes reverse dns domains, thus it depends on routes */
nm_l3_config_data_iter_obj_for_each (&ipconf_iter,
l3cd,
&obj,
NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))) {
route = NMP_OBJECT_CAST_IP_ROUTE(obj);
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)
|| route->table_coerced == NM_DNS_ROUTES_FWMARK_TABLE_PRIO) {
continue;
}
g_checksum_update(sum, (const guint8 *) route, sizeof(*route));
}
g_checksum_update(sum, (const guint8 *) &dns_ip_config_type, sizeof(dns_ip_config_type));
nm_l3_config_data_get_dns_priority(l3cd, addr_family, &prio);
g_checksum_update(sum, (const guint8 *) &prio, sizeof(prio));
}
/*****************************************************************************/
static void
@ -1291,4 +1362,5 @@ nm_dns_dnsmasq_class_init(NMDnsDnsmasqClass *dns_class)
plugin_class->stop = stop;
plugin_class->update = update;
plugin_class->get_update_pending = get_update_pending;
plugin_class->checksum = dnsmasq_checksum;
}

File diff suppressed because it is too large Load diff

View file

@ -98,44 +98,6 @@ gboolean nm_dns_manager_set_ip_config(NMDnsManager *self,
void nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean skip_update);
/**
* NMDnsManagerResolvConfManager
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN: unspecified rc-manager.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED: do not touch /etc/resolv.conf
* (but still write the internal copy -- unless it is symlinked by
* /etc/resolv.conf)
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO: if /etc/resolv.conf is marked
* as an immutable file, use "unmanaged" and don't touch /etc/resolv.conf.
* Otherwise, if "systemd-resolved" is enabled (or detected), configure systemd-resolved via D-Bus
* and don't touch /etc/resolv.conf.
* Otherwise, if "resolvconf" application is found, use it.
* As last resort, fallback to "symlink" which writes to /etc/resolv.conf
* if (and only if) the file is missing or not a symlink.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE: similar to "unmanaged",
* but indicates that resolv.conf cannot be modified.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: NM writes /etc/resolv.conf
* if the file is missing or not a symlink. An existing symlink is
* left untouched.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: Write to /etc/resolv.conf directly.
* If it is a file, write it as file, otherwise follow symlinks.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: NM is managing resolv.conf
through resolvconf
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: NM is managing resolv.conf
through netconfig
*
* NMDnsManager's management of resolv.conf
*/
typedef enum {
NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN,
NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO,
NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,
NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE,
NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK,
NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE,
NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF,
NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,
} NMDnsManagerResolvConfManager;
void nm_dns_manager_stop(NMDnsManager *self);
NMDnsPlugin *nm_dns_manager_get_systemd_resolved(NMDnsManager *self);

View file

@ -62,22 +62,37 @@ G_DEFINE_ABSTRACT_TYPE(NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT)
} \
G_STMT_END
NM_UTILS_LOOKUP_STR_DEFINE(
_rc_manager_to_string,
NMDnsManagerResolvConfManager,
NM_UTILS_LOOKUP_DEFAULT_WARN(NULL),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO, "auto"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN, "unknown"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED, "unmanaged"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE, "immutable"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK, "symlink"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE, "file"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF, "resolvconf"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG, "netconfig"), );
/*****************************************************************************/
gboolean
nm_dns_plugin_update(NMDnsPlugin *self,
const NMGlobalDnsConfig *global_config,
const CList *ip_config_lst_head,
const char *hostdomain,
GError **error)
nm_dns_plugin_update(NMDnsPlugin *self, NMDnsUpdateData *update_data, GError **error)
{
g_return_val_if_fail(NM_DNS_PLUGIN_GET_CLASS(self)->update != NULL, FALSE);
return NM_DNS_PLUGIN_GET_CLASS(self)->update(self,
global_config,
ip_config_lst_head,
hostdomain,
error);
return NM_DNS_PLUGIN_GET_CLASS(self)->update(self, update_data, error);
}
void
nm_dns_plugin_checksum(NMDnsPlugin *self,
const NML3ConfigData *l3cd,
GChecksum *sum,
int addr_family,
NMDnsIPConfigType dns_ip_config_type)
{
NM_DNS_PLUGIN_GET_CLASS(self)->checksum(l3cd, sum, addr_family, dns_ip_config_type);
}
gboolean
@ -98,6 +113,27 @@ nm_dns_plugin_get_name(NMDnsPlugin *self)
return klass->plugin_name;
}
const guint8 *
nm_dns_plugin_get_hash(NMDnsPlugin *self)
{
NMDnsPluginClass *klass;
g_return_val_if_fail(NM_IS_DNS_PLUGIN(self), NULL);
klass = NM_DNS_PLUGIN_GET_CLASS(self);
return klass->hash;
}
void
nm_dns_plugin_set_hash(NMDnsPlugin *self, guint8 *hash)
{
NMDnsPluginClass *klass;
klass = NM_DNS_PLUGIN_GET_CLASS(self);
memcpy(klass->hash, hash, NM_UTILS_CHECKSUM_LENGTH_SHA1);
}
void
nm_dns_plugin_stop(NMDnsPlugin *self)
{

View file

@ -20,6 +20,47 @@
(G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass))
#define NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED "update-pending-changed"
/**
* NMDnsManagerResolvConfManager
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN: unspecified rc-manager.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED: do not touch /etc/resolv.conf
* (but still write the internal copy -- unless it is symlinked by
* /etc/resolv.conf)
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO: if /etc/resolv.conf is marked
* as an immutable file, use "unmanaged" and don't touch /etc/resolv.conf.
* Otherwise, if "systemd-resolved" is enabled (or detected), configure systemd-resolved via D-Bus
* and don't touch /etc/resolv.conf.
* Otherwise, if "resolvconf" application is found, use it.
* As last resort, fallback to "symlink" which writes to /etc/resolv.conf
* if (and only if) the file is missing or not a symlink.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE: similar to "unmanaged",
* but indicates that resolv.conf cannot be modified.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: NM writes /etc/resolv.conf
* if the file is missing or not a symlink. An existing symlink is
* left untouched.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: Write to /etc/resolv.conf directly.
* If it is a file, write it as file, otherwise follow symlinks.
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: NM is managing resolv.conf
through resolvconf
* @NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: NM is managing resolv.conf
through netconfig
*
* NMDnsManager's management of resolv.conf
*/
typedef enum {
NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN,
NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO,
NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,
NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE,
NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK,
NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE,
NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF,
NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,
} NMDnsManagerResolvConfManager;
const char *_rc_manager_to_string(NMDnsManagerResolvConfManager val);
typedef struct {
const CList *ip_data_lst_head;
gboolean caching_successful;
@ -45,11 +86,7 @@ typedef struct {
* 'global_config' is the optional global DNS
* configuration.
*/
gboolean (*update)(NMDnsPlugin *self,
const NMGlobalDnsConfig *global_config,
const CList *ip_config_lst_head,
const char *hostdomain,
GError **error);
gboolean (*update)(NMDnsPlugin *plugin, NMDnsUpdateData *update_data, GError **error);
void (*stop)(NMDnsPlugin *self);
@ -63,6 +100,15 @@ typedef struct {
*/
bool is_caching : 1;
guint8 hash[NM_UTILS_CHECKSUM_LENGTH_SHA1]; /* SHA1 hash of current plugin config */
/* Each way to set up resolution can be sensitive to different
* options or data, this function ensures that update is done
* only when relevant data change */
void (*checksum)(const NML3ConfigData *l3cd,
GChecksum *sum,
int addr_family,
NMDnsIPConfigType dns_ip_config_type);
} NMDnsPluginClass;
GType nm_dns_plugin_get_type(void);
@ -71,11 +117,17 @@ gboolean nm_dns_plugin_is_caching(NMDnsPlugin *self);
const char *nm_dns_plugin_get_name(NMDnsPlugin *self);
gboolean nm_dns_plugin_update(NMDnsPlugin *self,
const NMGlobalDnsConfig *global_config,
const CList *ip_config_lst_head,
const char *hostname,
GError **error);
const guint8 *nm_dns_plugin_get_hash(NMDnsPlugin *self);
void nm_dns_plugin_set_hash(NMDnsPlugin *self, guint8 *hash);
gboolean nm_dns_plugin_update(NMDnsPlugin *self, NMDnsUpdateData *update_data, GError **error);
void nm_dns_plugin_checksum(NMDnsPlugin *self,
const NML3ConfigData *l3cd,
GChecksum *sum,
int addr_family,
NMDnsIPConfigType dns_ip_config_type);
void nm_dns_plugin_stop(NMDnsPlugin *self);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2025 Red Hat, Inc.
*/
#ifndef __NETWORKMANAGER_DNS_RESOLVCONF_H__
#define __NETWORKMANAGER_DNS_RESOLVCONF_H__
#include "nm-dns-plugin.h"
#include "nm-dns-manager.h"
#define NM_TYPE_DNS_RESOLVCONF (nm_dns_resolvconf_get_type())
#define NM_DNS_RESOLVCONF(obj) \
(_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_RESOLVCONF, NMDnsResolvconf))
#define NM_DNS_RESOLVCONF_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_RESOLVCONF, NMDnsResolvconfClass))
#define NM_IS_DNS_RESOLVCONF(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_RESOLVCONF))
#define NM_IS_DNS_RESOLVCONF_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_RESOLVCONF))
#define NM_DNS_RESOLVCONF_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_RESOLVCONF, NMDnsResolvconfClass))
typedef struct _NMDnsResolvconf NMDnsResolvconf;
typedef struct _NMDnsResolvconfClass NMDnsResolvconfClass;
GType nm_dns_resolvconf_get_type(void);
NMDnsPlugin *nm_dns_resolvconf_new(void);
#endif /* __NETWORKMANAGER_DNS_RESOLVCONF_H__ */

View file

@ -1237,6 +1237,68 @@ nm_dns_systemd_resolved_resolve_cancel(NMDnsSystemdResolvedResolveHandle *handle
_resolve_complete_error(handle, error);
}
static void
resolved_checksum(const NML3ConfigData *l3cd,
GChecksum *sum,
int addr_family,
NMDnsIPConfigType dns_ip_config_type)
{
guint i;
int val;
const char *const *strarr;
const in_addr_t *wins;
guint num_elements;
g_return_if_fail(l3cd);
g_return_if_fail(sum);
strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_elements);
for (i = 0; i < num_elements; i++) {
g_checksum_update(sum, (gpointer) strarr[i], strlen(strarr[i]));
}
if (addr_family == AF_INET) {
wins = nm_l3_config_data_get_wins(l3cd, &num_elements);
for (i = 0; i < num_elements; i++) {
g_checksum_update(sum, (guint8 *) &wins[i], 4);
}
}
strarr = nm_l3_config_data_get_domains(l3cd, addr_family, &num_elements);
for (i = 0; i < num_elements; i++) {
g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i]));
}
strarr = nm_l3_config_data_get_searches(l3cd, addr_family, &num_elements);
for (i = 0; i < num_elements; i++) {
g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i]));
}
val = nm_l3_config_data_get_mdns(l3cd);
if (val != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
}
val = nm_l3_config_data_get_llmnr(l3cd);
if (val != NM_SETTING_CONNECTION_LLMNR_DEFAULT) {
g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
}
val = nm_l3_config_data_get_dns_over_tls(l3cd);
if (val != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT) {
g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
}
/* Priority has to be included everytime, because even if mdns/llmnr is
* left to default value, it could be enabled by default and priority
* would decide which interface should be used for mdns/llmnr */
val = 0;
g_checksum_update(sum, (const guint8 *) &dns_ip_config_type, sizeof(dns_ip_config_type));
nm_l3_config_data_get_dns_priority(l3cd, addr_family, &val);
g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
}
/*****************************************************************************/
static void
@ -1350,4 +1412,5 @@ nm_dns_systemd_resolved_class_init(NMDnsSystemdResolvedClass *dns_class)
plugin_class->stop = stop;
plugin_class->update = update;
plugin_class->get_update_pending = get_update_pending;
plugin_class->checksum = resolved_checksum;
}

View file

@ -134,6 +134,7 @@ libNetworkManager = static_library(
'dhcp/nm-dhcp-listener.c',
'dns/nm-dns-dnsmasq.c',
'dns/nm-dns-dnsconfd.c',
'dns/nm-dns-resolv_conf.c',
'dns/nm-dns-manager.c',
'dns/nm-dns-plugin.c',
'dns/nm-dns-systemd-resolved.c',