diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index 15072f799d..4fe7945033 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -35,6 +35,7 @@ #include "nm-dns-plugin.h" #include "nm-dns-systemd-resolved.h" #include "nm-dns-unbound.h" +#include "nm-ip-config.h" #include "nm-l3-config-data.h" #include "nm-manager.h" #include "nm-utils.h" @@ -99,6 +100,9 @@ typedef struct { char *hostname; guint updates_queue; + guint8 hash[HASH_LEN]; /* SHA1 hash of current DNS config */ + guint8 prev_hash[HASH_LEN]; /* Hash when begin_updates() was called */ + NMDnsManagerResolvConfManager rc_manager; char * mode; NMDnsPlugin * sd_resolve_plugin; @@ -1097,6 +1101,30 @@ update_resolv_conf(NMDnsManager * self, return write_file_result; } +static void +compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[static HASH_LEN]) +{ + nm_auto_free_checksum GChecksum *sum = NULL; + NMDnsConfigIPData * ip_data; + + sum = g_checksum_new(G_CHECKSUM_SHA1); + nm_assert(HASH_LEN == g_checksum_type_get_length(G_CHECKSUM_SHA1)); + + if (global) + nm_global_dns_config_update_checksum(global, sum); + else { + const CList *head; + + /* FIXME(ip-config-checksum): this relies on the fact that an IP + * configuration without DNS parameters gives a zero checksum. */ + head = _mgr_get_ip_data_lst_head(self); + c_list_for_each_entry (ip_data, head, ip_data_lst) + nm_ip_config_dns_hash(ip_data->l3cd, sum, ip_data->addr_family); + } + + nm_utils_checksum_get_digest_len(sum, buffer, HASH_LEN); +} + static gboolean merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) { @@ -1649,6 +1677,9 @@ update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError data = nm_config_get_data(priv->config); global_config = nm_config_data_get_global_dns_config(data); + /* Update hash with config we're applying */ + compute_hash(self, global_config, priv->hash); + _collect_resolv_conf_data(self, global_config, &searches, @@ -2006,6 +2037,10 @@ nm_dns_manager_begin_updates(NMDnsManager *self, const char *func) priv = NM_DNS_MANAGER_GET_PRIVATE(self); + /* Save current hash when starting a new batch */ + if (priv->updates_queue == 0) + memcpy(priv->prev_hash, priv->hash, sizeof(priv->hash)); + priv->updates_queue++; _LOGD("(%s): queueing DNS updates (%d)", func, priv->updates_queue); @@ -2016,12 +2051,15 @@ nm_dns_manager_end_updates(NMDnsManager *self, const char *func) { NMDnsManagerPrivate *priv; gs_free_error GError *error = NULL; + guint8 new[HASH_LEN]; g_return_if_fail(self != NULL); priv = NM_DNS_MANAGER_GET_PRIVATE(self); g_return_if_fail(priv->updates_queue > 0); + compute_hash(self, nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)), new); + priv->config_changed = (memcmp(new, priv->prev_hash, sizeof(new)) != 0) ? TRUE : FALSE; _LOGD("(%s): DNS configuration %s", func, priv->config_changed ? "changed" : "did not change"); priv->updates_queue--; @@ -2574,6 +2612,7 @@ nm_dns_manager_init(NMDnsManager *self) (GDestroyNotify) _dns_config_data_free, NULL); + compute_hash(self, NULL, NM_DNS_MANAGER_GET_PRIVATE(self)->hash); g_signal_connect(G_OBJECT(priv->config), NM_CONFIG_SIGNAL_CONFIG_CHANGED, G_CALLBACK(config_changed_cb), diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c index 0c4f8099de..e86e993c17 100644 --- a/src/core/nm-config-data.c +++ b/src/core/nm-config-data.c @@ -982,6 +982,61 @@ nm_global_dns_config_cmp(const NMGlobalDnsConfig *a, return 0; } +void +nm_global_dns_config_update_checksum(const NMGlobalDnsConfig *dns_config, GChecksum *sum) +{ + NMGlobalDnsDomain *domain; + guint i, j; + guint8 v8; + + g_return_if_fail(dns_config); + g_return_if_fail(sum); + + v8 = NM_HASH_COMBINE_BOOLS(guint8, + !dns_config->searches, + !dns_config->options, + !dns_config->domain_list); + g_checksum_update(sum, (guchar *) &v8, 1); + + if (dns_config->searches) { + for (i = 0; dns_config->searches[i]; i++) + g_checksum_update(sum, + (guchar *) dns_config->searches[i], + strlen(dns_config->searches[i]) + 1); + } + if (dns_config->options) { + for (i = 0; dns_config->options[i]; i++) + g_checksum_update(sum, + (guchar *) dns_config->options[i], + strlen(dns_config->options[i]) + 1); + } + + if (dns_config->domain_list) { + for (i = 0; dns_config->domain_list[i]; i++) { + domain = g_hash_table_lookup(dns_config->domains, dns_config->domain_list[i]); + nm_assert(domain); + + v8 = NM_HASH_COMBINE_BOOLS(guint8, !domain->servers, !domain->options); + g_checksum_update(sum, (guchar *) &v8, 1); + + g_checksum_update(sum, (guchar *) domain->name, strlen(domain->name) + 1); + + if (domain->servers) { + for (j = 0; domain->servers[j]; j++) + g_checksum_update(sum, + (guchar *) domain->servers[j], + strlen(domain->servers[j]) + 1); + } + if (domain->options) { + for (j = 0; domain->options[j]; j++) + g_checksum_update(sum, + (guchar *) domain->options[j], + strlen(domain->options[j]) + 1); + } + } + } +} + static void global_dns_domain_free(NMGlobalDnsDomain *domain) { diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h index 89c69e2877..1923dc49c4 100644 --- a/src/core/nm-config-data.h +++ b/src/core/nm-config-data.h @@ -267,7 +267,8 @@ gboolean nm_global_dns_config_is_empty(const NMGlobalDnsConfig *dns_co int nm_global_dns_config_cmp(const NMGlobalDnsConfig *a, const NMGlobalDnsConfig *b, gboolean check_internal); -void nm_global_dns_config_free(NMGlobalDnsConfig *dns_config); +void nm_global_dns_config_update_checksum(const NMGlobalDnsConfig *dns_config, GChecksum *sum); +void nm_global_dns_config_free(NMGlobalDnsConfig *dns_config); NMGlobalDnsConfig *nm_global_dns_config_from_dbus(const GValue *value, GError **error); void nm_global_dns_config_to_dbus(const NMGlobalDnsConfig *dns_config, GValue *value); diff --git a/src/core/nm-ip-config.c b/src/core/nm-ip-config.c index b3e599400e..2470e40e2b 100644 --- a/src/core/nm-ip-config.c +++ b/src/core/nm-ip-config.c @@ -498,6 +498,77 @@ nm_ip4_config_class_init(NMIP4ConfigClass *klass) g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST_ip4, obj_properties_ip4); } +void +nm_ip_config_dns_hash(const NML3ConfigData *l3cd, GChecksum *sum, int addr_family) +{ + guint i; + int val; + const char *const *nameservers; + const in_addr_t * wins; + const char *const *domains; + const char *const *searches; + const char *const *options; + guint num_nameservers; + guint num_wins; + guint num_domains; + guint num_searches; + guint num_options; + + g_return_if_fail(l3cd); + g_return_if_fail(sum); + + nameservers = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers); + for (i = 0; i < num_nameservers; i++) { + g_checksum_update(sum, + nm_ip_addr_from_packed_array(addr_family, nameservers, i), + nm_utils_addr_family_to_size(addr_family)); + } + + if (addr_family == AF_INET) { + wins = nm_l3_config_data_get_wins(l3cd, &num_wins); + for (i = 0; i < num_wins; i++) + g_checksum_update(sum, (guint8 *) &wins[i], 4); + } + + domains = nm_l3_config_data_get_domains(l3cd, addr_family, &num_domains); + for (i = 0; i < num_domains; i++) { + g_checksum_update(sum, (const guint8 *) domains[i], strlen(domains[i])); + } + + 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])); + } + + 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])); + } + + 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)); + + /* FIXME(ip-config-checksum): the DNS priority should be considered relevant + * and added into the checksum as well, but this can't be done right now + * because in the DNS manager we rely on the fact that an empty + * configuration (i.e. just created) has a zero checksum. This is needed to + * avoid rewriting resolv.conf when there is no change. + * + * The DNS priority initial value depends on the connection type (VPN or + * not), so it's a bit difficult to add it to checksum maintaining the + * assumption of checksum(empty)=0 + */ +} + /*****************************************************************************/ /* public */ diff --git a/src/core/nm-ip-config.h b/src/core/nm-ip-config.h index 5b2552e3a3..9bc1ff45c6 100644 --- a/src/core/nm-ip-config.h +++ b/src/core/nm-ip-config.h @@ -51,6 +51,8 @@ NMIPConfig *nm_ip_config_new(int addr_family, NML3Cfg *l3cfg); void nm_ip_config_take_and_unexport_on_idle(NMIPConfig *self_take); +void nm_ip_config_dns_hash(const NML3ConfigData *l3cd, GChecksum *sum, int addr_family); + /*****************************************************************************/ static inline NML3Cfg *