diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c index a1800f35ce..0aa8b6487c 100644 --- a/src/core/dns/nm-dns-dnsconfd.c +++ b/src/core/dns/nm-dns-dnsconfd.c @@ -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; } diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c index 37c839c22b..f965967fb9 100644 --- a/src/core/dns/nm-dns-dnsmasq.c +++ b/src/core/dns/nm-dns-dnsmasq.c @@ -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; } diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index ae3b29e690..4ce466e1ce 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -20,10 +20,6 @@ #include -#if WITH_LIBPSL -#include -#endif - #include "libnm-core-intern/nm-core-internal.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-glib-aux/nm-io-utils.h" @@ -34,6 +30,7 @@ #include "nm-dbus-object.h" #include "nm-dns-dnsmasq.h" #include "nm-dns-dnsconfd.h" +#include "nm-dns-resolv_conf.h" #include "nm-dns-plugin.h" #include "nm-dns-systemd-resolved.h" #include "nm-ip-config.h" @@ -43,6 +40,8 @@ #define HASH_LEN NM_UTILS_CHECKSUM_LENGTH_SHA1 +#define UPDATE_PENDING_UNBLOCK_TIMEOUT_MSEC 5000 + #ifndef RESOLVCONF_PATH #define RESOLVCONF_PATH "/sbin/resolvconf" #define HAS_RESOLVCONF 0 @@ -57,23 +56,6 @@ #define HAS_NETCONFIG 1 #endif -#define UPDATE_PENDING_UNBLOCK_TIMEOUT_MSEC 5000 - -/*****************************************************************************/ - -typedef enum { SR_SUCCESS, SR_NOTFOUND, SR_ERROR } SpawnResult; - -typedef struct { - GPtrArray *nameservers; - GPtrArray *searches; - GPtrArray *options; - const char *nis_domain; - GPtrArray *nis_servers; - NMTernary has_trust_ad; -} NMResolvConfData; - -/*****************************************************************************/ - enum { CONFIG_CHANGED, @@ -111,16 +93,26 @@ typedef struct { bool update_pending : 1; + bool caching_was_successful : 1; + + bool custom_plugin_update_to_do : 1; + bool sd_resolved_backup_plugin_update_to_do : 1; + bool resolvconf_plugin_update_to_do : 1; + char *hostdomain; 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; - NMDnsPlugin *plugin; + /* Custom plugin is plugin handling configuration of + * any advanced DNS resolution service */ + NMDnsPlugin *custom_plugin; + /* sd_resolved has a special place and it is running even when + * we have different plugin in place, to handle NM's own requests */ + NMDnsPlugin *sd_resolved_backup_plugin; + NMDnsPlugin *resolvconf_plugin; + /* hash computed from all the partial hashes*/ + guint8 full_hash[HASH_LEN]; gulong update_changed_signal_id_sd; gulong update_changed_signal_id; @@ -177,41 +169,11 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER /*****************************************************************************/ - -static 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"), ); - -static NM_UTILS_LOOKUP_STR_DEFINE( - _config_type_to_string, - NMDnsIPConfigType, - NM_UTILS_LOOKUP_DEFAULT_WARN(""), - NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"), - NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"), - NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, "best"), - NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_VPN, "vpn"), ); - -/*****************************************************************************/ - static gboolean _update_pending_detect(NMDnsManager *self) { NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); - - if (priv->plugin && nm_dns_plugin_get_update_pending(priv->plugin)) - return TRUE; - if (priv->sd_resolve_plugin && nm_dns_plugin_get_update_pending(priv->sd_resolve_plugin)) - return TRUE; - return FALSE; + return priv->custom_plugin ? nm_dns_plugin_get_update_pending(priv->custom_plugin) : FALSE; } static gboolean @@ -451,20 +413,18 @@ NMDnsPlugin * nm_dns_manager_get_systemd_resolved(NMDnsManager *self) { NMDnsManagerPrivate *priv; - NMDnsPlugin *plugin = NULL; + NMDnsPlugin *resolved_plugin; g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE); - priv = NM_DNS_MANAGER_GET_PRIVATE(self); + priv = NM_DNS_MANAGER_GET_PRIVATE(self); + resolved_plugin = priv->sd_resolved_backup_plugin; - if (priv->sd_resolve_plugin) { - nm_assert(!NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)); - plugin = priv->sd_resolve_plugin; - } else if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) - plugin = priv->plugin; - - if (plugin && nm_dns_systemd_resolved_is_running(NM_DNS_SYSTEMD_RESOLVED(plugin))) - return plugin; + if (resolved_plugin + && nm_dns_systemd_resolved_is_running(NM_DNS_SYSTEMD_RESOLVED(resolved_plugin))) { + nm_assert(NM_IS_DNS_SYSTEMD_RESOLVED(resolved_plugin)); + return resolved_plugin; + } return NULL; } @@ -472,924 +432,82 @@ nm_dns_manager_get_systemd_resolved(NMDnsManager *self) /*****************************************************************************/ static void -add_string_item(GPtrArray *array, const char *str, gboolean dup) +compute_full_hash(NMDnsManager *self, guint8 buffer[static HASH_LEN]) { - int i; - - g_return_if_fail(array != NULL); - g_return_if_fail(str != NULL); - - /* Check for dupes before adding */ - for (i = 0; i < array->len; i++) { - const char *candidate = g_ptr_array_index(array, i); - - if (candidate && !strcmp(candidate, str)) - return; - } - - /* No dupes, add the new item */ - g_ptr_array_add(array, dup ? g_strdup(str) : (gpointer) str); -} - -static void -add_dns_option_item(GPtrArray *array, const char *str) -{ - if (_nm_utils_dns_option_find_idx((const char *const *) array->pdata, array->len, str) < 0) - g_ptr_array_add(array, g_strdup(str)); -} - -static void -add_dns_domains(GPtrArray *array, - int addr_family, - const NML3ConfigData *l3cd, - gboolean include_routing, - gboolean dup) -{ - const char *const *domains; - const char *const *searches; - guint num_domains; - guint num_searches; - guint i; - const char *str; - - domains = nm_l3_config_data_get_domains(l3cd, addr_family, &num_domains); - searches = nm_l3_config_data_get_searches(l3cd, addr_family, &num_searches); - - for (i = 0; i < num_searches; i++) { - str = searches[i]; - if (!include_routing && domain_is_routing(str)) - continue; - if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE)) - continue; - add_string_item(array, str, dup); - } - if (num_domains > 1 || num_searches == 0) { - for (i = 0; i < num_domains; i++) { - str = domains[i]; - if (!include_routing && domain_is_routing(str)) - continue; - if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE)) - continue; - add_string_item(array, str, dup); - } - } -} - -static void -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; - guint num_nameservers; - guint num; - guint i; - const char *const *strarr; - - nm_assert(ifindex == nm_l3_config_data_get_ifindex(l3cd)); - - strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers); - for (i = 0; i < num_nameservers; i++) { - NMIPAddr a; - - if (!nm_dns_uri_parse_plain(addr_family, strarr[i], NULL, &a)) - continue; - - if (addr_family == AF_INET) - nm_inet_ntop(addr_family, &a, buf); - else if (IN6_IS_ADDR_V4MAPPED(&a)) - nm_inet4_ntop(a.addr6.s6_addr32[3], buf); - else { - nm_inet6_ntop(&a.addr6, buf); - if (IN6_IS_ADDR_LINKLOCAL(&a)) { - const char *ifname; - - ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex); - if (ifname) { - g_strlcat(buf, "%", sizeof(buf)); - g_strlcat(buf, ifname, sizeof(buf)); - } - } - } - - add_string_item(rc->nameservers, buf, 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]; - - if (nm_streq(option, NM_SETTING_DNS_OPTION_TRUST_AD)) { - has_trust_ad = TRUE; - continue; - } - 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 (addr_family == AF_INET) { - const in_addr_t *nis_servers; - const char *nis_domain; - - nis_servers = nm_l3_config_data_get_nis_servers(l3cd, &num); - for (i = 0; i < num; i++) - add_string_item(rc->nis_servers, nm_inet4_ntop(nis_servers[i], buf), TRUE); - - if ((nis_domain = nm_l3_config_data_get_nis_domain(l3cd))) { - /* FIXME: handle multiple domains */ - if (!rc->nis_domain) - rc->nis_domain = nis_domain; - } - } -} - -static GPid -run_netconfig(NMDnsManager *self, GError **error, int *stdin_fd) -{ - char *argv[5]; - gs_free char *tmp = NULL; - GPid pid = -1; - - argv[0] = NETCONFIG_PATH; - argv[1] = "modify"; - argv[2] = "--service"; - argv[3] = "NetworkManager"; - argv[4] = NULL; - - _LOGD("spawning '%s'", (tmp = g_strjoinv(" ", argv))); - - if (!g_spawn_async_with_pipes(NULL, - argv, - NULL, - G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD, - NULL, - NULL, - &pid, - stdin_fd, - NULL, - NULL, - error)) - return -1; - - return pid; -} - -static void -netconfig_construct_str(NMDnsManager *self, GString *str, const char *key, const char *value) -{ - if (value) { - _LOGD("writing to netconfig: %s='%s'", key, value); - g_string_append_printf(str, "%s='%s'\n", key, value); - } -} - -static void -netconfig_construct_strv(NMDnsManager *self, - GString *str, - const char *key, - const char *const *values) -{ - if (values) { - gs_free char *value = NULL; - - value = g_strjoinv(" ", (char **) values); - netconfig_construct_str(self, str, key, value); - } -} - -static SpawnResult -dispatch_netconfig(NMDnsManager *self, - const char *const *searches, - const char *const *nameservers, - const char *nis_domain, - const char *const *nis_servers, - GError **error) -{ - GPid pid; - int fd; - int errsv; - int status; - gssize l; - nm_auto_free_gstring GString *str = NULL; - - pid = run_netconfig(self, error, &fd); - if (pid <= 0) - return SR_NOTFOUND; - - str = g_string_new(""); - - /* NM is writing already-merged DNS information to netconfig, so it - * does not apply to a specific network interface. - */ - netconfig_construct_str(self, str, "INTERFACE", "NetworkManager"); - netconfig_construct_strv(self, str, "DNSSEARCH", searches); - netconfig_construct_strv(self, str, "DNSSERVERS", nameservers); - netconfig_construct_str(self, str, "NISDOMAIN", nis_domain); - netconfig_construct_strv(self, str, "NISSERVERS", nis_servers); - -again: - l = write(fd, str->str, str->len); - if (l == -1) { - if (errno == EINTR) - goto again; - } - - nm_close(fd); - - /* FIXME: don't write to netconfig synchronously. */ - - /* Wait until the process exits */ - if (!nm_utils_kill_child_sync(pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Error waiting for netconfig to exit: %s", - nm_strerror_native(errsv)); - return SR_ERROR; - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) { - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Error calling netconfig: %s %d", - WIFEXITED(status) ? "exited with status" - : (WIFSIGNALED(status) ? "exited with signal" - : "exited with unknown reason"), - WIFEXITED(status) ? WEXITSTATUS(status) - : (WIFSIGNALED(status) ? WTERMSIG(status) : status)); - return SR_ERROR; - } - return SR_SUCCESS; -} - -static char * -create_resolv_conf(const char *const *searches, - const char *const *nameservers, - const char *const *options) -{ - GString *str; - gsize i; - - str = g_string_new_len(NULL, 245); - - g_string_append(str, "# Generated by NetworkManager\n"); - - if (searches && searches[0]) { - gsize search_base_idx; - - g_string_append(str, "search"); - search_base_idx = str->len; - - for (i = 0; searches[i]; i++) { - const char *s = searches[i]; - gsize l = strlen(s); - - if (l == 0 || NM_STRCHAR_ANY(s, ch, NM_IN_SET(ch, ' ', '\t', '\n'))) { - /* there should be no such characters in the search entry. Also, - * because glibc parser would treat them as line/word separator. - * - * Skip the value silently. */ - continue; - } - - if (search_base_idx > 0) { - if (str->len - search_base_idx + 1 + l > 254) { - /* this entry crosses the 256 character boundary. Older glibc versions - * would truncate the entry at this point. - * - * Fill the line with spaces to cross the 256 char boundary and continue - * afterwards. This way, the truncation happens between two search entries. */ - while (str->len - search_base_idx < 257) - g_string_append_c(str, ' '); - search_base_idx = 0; - } - } - - g_string_append_c(str, ' '); - g_string_append_len(str, s, l); - } - g_string_append_c(str, '\n'); - } - - if (nameservers && nameservers[0]) { - for (i = 0; nameservers[i]; i++) { - if (i == 3) { - g_string_append( - str, - "# NOTE: the libc resolver may not support more than 3 nameservers.\n"); - g_string_append(str, "# The nameservers listed below may not be recognized.\n"); - } - g_string_append(str, "nameserver "); - g_string_append(str, nameservers[i]); - g_string_append_c(str, '\n'); - } - } - - if (options && options[0]) { - g_string_append(str, "options"); - for (i = 0; options[i]; i++) { - g_string_append_c(str, ' '); - g_string_append(str, options[i]); - } - g_string_append_c(str, '\n'); - } - - return g_string_free(str, FALSE); -} - -char * -nmtst_dns_create_resolv_conf(const char *const *searches, - const char *const *nameservers, - const char *const *options) -{ - return create_resolv_conf(searches, nameservers, options); -} - -static gboolean -write_resolv_conf_contents(FILE *f, const char *content, GError **error) -{ - int errsv; - - if (fprintf(f, "%s", content) < 0) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not write " _PATH_RESCONF ": %s", - nm_strerror_native(errsv)); - errno = errsv; - return FALSE; - } - - return TRUE; -} - -static gboolean -write_resolv_conf(FILE *f, - const char *const *searches, - const char *const *nameservers, - const char *const *options, - GError **error) -{ - gs_free char *content = NULL; - - content = create_resolv_conf(searches, nameservers, options); - return write_resolv_conf_contents(f, content, error); -} - -static SpawnResult -dispatch_resolvconf(NMDnsManager *self, - char **searches, - char **nameservers, - char **options, - GError **error) -{ - gs_free char *cmd = NULL; - FILE *f; - gboolean success = FALSE; - int errsv; - int err; - char *argv[] = {RESOLVCONF_PATH, "-d", "NetworkManager", NULL}; - int status; - - if (!g_file_test(RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) { - g_set_error_literal(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - RESOLVCONF_PATH " is not executable"); - return SR_NOTFOUND; - } - - if (!searches && !nameservers) { - _LOGI("Removing DNS information from %s", RESOLVCONF_PATH); - - if (!g_spawn_sync("/", argv, NULL, 0, NULL, NULL, NULL, NULL, &status, error)) - return SR_ERROR; - - if (status != 0) { - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "%s returned error code", - RESOLVCONF_PATH); - return SR_ERROR; - } - - return SR_SUCCESS; - } - - _LOGI("Writing DNS information to %s", RESOLVCONF_PATH); - - /* FIXME: don't write to resolvconf synchronously. */ - - cmd = g_strconcat(RESOLVCONF_PATH, " -a ", "NetworkManager", NULL); - if ((f = popen(cmd, "w")) == NULL) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not write to %s: %s", - RESOLVCONF_PATH, - nm_strerror_native(errsv)); - return SR_ERROR; - } - - success = write_resolv_conf(f, - NM_CAST_STRV_CC(searches), - NM_CAST_STRV_CC(nameservers), - NM_CAST_STRV_CC(options), - error); - err = pclose(f); - if (err < 0) { - errsv = errno; - g_clear_error(error); - g_set_error(error, - G_IO_ERROR, - g_io_error_from_errno(errsv), - "Failed to close pipe to resolvconf: %d", - errsv); - return SR_ERROR; - } else if (err > 0) { - _LOGW("resolvconf failed with status %d", err); - g_clear_error(error); - g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "resolvconf failed with status %d", err); - return SR_ERROR; - } - - return success ? SR_SUCCESS : SR_ERROR; -} - -static const char * -_read_link_cached(const char *path, gboolean *is_cached, char **cached) -{ - nm_assert(is_cached); - nm_assert(cached); - - if (*is_cached) - return *cached; - - nm_assert(!*cached); - *is_cached = TRUE; - return (*cached = g_file_read_link(path, NULL)); -} - -#define MY_RESOLV_CONF NMRUNDIR "/resolv.conf" -#define MY_RESOLV_CONF_TMP MY_RESOLV_CONF ".tmp" -#define RESOLV_CONF_TMP "/etc/.resolv.conf.NetworkManager" - -#define NO_STUB_RESOLV_CONF NMRUNDIR "/no-stub-resolv.conf" -#define NO_STUB_RESOLV_CONF_TMP NMRUNDIR "/no-stub-resolv.conf.tmp" - -static void -update_resolv_conf_no_stub(NMDnsManager *self, - const char *const *searches, - const char *const *nameservers, - const char *const *options) -{ - gs_free char *content = NULL; - GError *local = NULL; - - content = create_resolv_conf(searches, nameservers, options); - - if (!nm_utils_file_set_contents(NO_STUB_RESOLV_CONF, - content, - -1, - 0644, - NULL, - NO_STUB_RESOLV_CONF_TMP, - NULL, - &local)) { - _LOGD("update-resolv-no-stub: failure to write file: %s", local->message); - g_error_free(local); - return; - } - - _LOGT("update-resolv-no-stub: '%s' successfully written", NO_STUB_RESOLV_CONF); -} - -static SpawnResult -update_resolv_conf(NMDnsManager *self, - const char *const *searches, - const char *const *nameservers, - const char *const *options, - GError **error, - NMDnsManagerResolvConfManager rc_manager) -{ - FILE *f; - gboolean success; - gs_free char *content = NULL; - SpawnResult write_file_result = SR_SUCCESS; - int errsv; - gboolean resconf_link_cached = FALSE; - gs_free char *resconf_link = NULL; - - content = create_resolv_conf(searches, nameservers, options); - - if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE - || (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK - && !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link))) { - gs_free char *rc_path_syml = NULL; - nm_auto_free char *rc_path_real = NULL; - const char *rc_path = _PATH_RESCONF; - GError *local = NULL; - - if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) { - rc_path_real = realpath(_PATH_RESCONF, NULL); - if (rc_path_real) - rc_path = rc_path_real; - else { - /* realpath did not resolve a path-name. That either means, - * _PATH_RESCONF: - * - does not exist - * - is a plain file - * - is a dangling symlink - * - * Handle the case, where it is a dangling symlink... */ - rc_path_syml = nm_utils_read_link_absolute(_PATH_RESCONF, NULL); - if (rc_path_syml) - rc_path = rc_path_syml; - } - } - - /* we first write to /etc/resolv.conf directly. If that fails, - * we still continue to write to runstatedir but remember the - * error. */ - if (!g_file_set_contents(rc_path, content, -1, &local)) { - _LOGT("update-resolv-conf: write to %s failed (rc-manager=%s, %s)", - rc_path, - _rc_manager_to_string(rc_manager), - local->message); - g_propagate_error(error, local); - /* clear @error, so that we don't try reset it. This is the error - * we want to propagate to the caller. */ - error = NULL; - write_file_result = SR_ERROR; - } else { - _LOGT("update-resolv-conf: write to %s succeeded (rc-manager=%s)", - rc_path, - _rc_manager_to_string(rc_manager)); - } - } - - if ((f = fopen(MY_RESOLV_CONF_TMP, "we")) == NULL) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not open %s: %s", - MY_RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - _LOGT("update-resolv-conf: open temporary file %s failed (%s)", - MY_RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - return SR_ERROR; - } - - success = write_resolv_conf_contents(f, content, error); - if (!success) { - errsv = errno; - _LOGT("update-resolv-conf: write temporary file %s failed (%s)", - MY_RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - } - - if (fclose(f) < 0) { - if (success) { - errsv = errno; - /* only set an error here if write_resolv_conf() was successful, - * since its error is more important. - */ - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not close %s: %s", - MY_RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - _LOGT("update-resolv-conf: close temporary file %s failed (%s)", - MY_RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - } - return SR_ERROR; - } else if (!success) - return SR_ERROR; - - if (rename(MY_RESOLV_CONF_TMP, MY_RESOLV_CONF) < 0) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not replace %s: %s", - MY_RESOLV_CONF, - nm_strerror_native(errsv)); - _LOGT("update-resolv-conf: failed to rename temporary file %s to %s (%s)", - MY_RESOLV_CONF_TMP, - MY_RESOLV_CONF, - nm_strerror_native(errsv)); - return SR_ERROR; - } - - if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) { - _LOGT("update-resolv-conf: write internal file %s succeeded (rc-manager=%s)", - MY_RESOLV_CONF, - _rc_manager_to_string(rc_manager)); - return write_file_result; - } - - if (rc_manager != NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK - || !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link)) { - _LOGT("update-resolv-conf: write internal file %s succeeded", MY_RESOLV_CONF); - return write_file_result; - } - - if (!nm_streq0(_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link), - MY_RESOLV_CONF)) { - _LOGT("update-resolv-conf: write internal file %s succeeded (don't touch symlink %s " - "linking to %s)", - MY_RESOLV_CONF, - _PATH_RESCONF, - _read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link)); - return write_file_result; - } - - /* By this point, /etc/resolv.conf exists and is a symlink to our internal - * resolv.conf. We update the symlink so that applications get an inotify - * notification. - */ - if (unlink(RESOLV_CONF_TMP) != 0 && ((errsv = errno) != ENOENT)) { - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not unlink %s: %s", - RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - _LOGT("update-resolv-conf: write internal file %s succeeded " - "but cannot delete temporary file %s: %s", - MY_RESOLV_CONF, - RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - return SR_ERROR; - } - - if (symlink(MY_RESOLV_CONF, RESOLV_CONF_TMP) == -1) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not create symlink %s pointing to %s: %s", - RESOLV_CONF_TMP, - MY_RESOLV_CONF, - nm_strerror_native(errsv)); - _LOGT("update-resolv-conf: write internal file %s succeeded " - "but failed to symlink %s: %s", - MY_RESOLV_CONF, - RESOLV_CONF_TMP, - nm_strerror_native(errsv)); - return SR_ERROR; - } - - if (rename(RESOLV_CONF_TMP, _PATH_RESCONF) == -1) { - errsv = errno; - g_set_error(error, - NM_MANAGER_ERROR, - NM_MANAGER_ERROR_FAILED, - "Could not rename %s to %s: %s", - RESOLV_CONF_TMP, - _PATH_RESCONF, - nm_strerror_native(errsv)); - _LOGT("update-resolv-conf: write internal file %s succeeded " - "but failed to rename temporary symlink %s to %s: %s", - MY_RESOLV_CONF, - RESOLV_CONF_TMP, - _PATH_RESCONF, - nm_strerror_native(errsv)); - return SR_ERROR; - } - - _LOGT("update-resolv-conf: write internal file %s succeeded and update symlink %s", - MY_RESOLV_CONF, - _PATH_RESCONF); - 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; - gboolean has_global_dns_section = FALSE; + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + nm_auto_free_checksum GChecksum *sum = NULL; 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); - has_global_dns_section = nm_global_dns_has_global_dns_section(global); - } + /* presence of these two is not certain but resolvconf is always used */ + if (priv->custom_plugin) + g_checksum_update(sum, nm_dns_plugin_get_hash(priv->custom_plugin), HASH_LEN); + if (priv->sd_resolved_backup_plugin) + g_checksum_update(sum, nm_dns_plugin_get_hash(priv->sd_resolved_backup_plugin), HASH_LEN); - if (!global || !nm_global_dns_config_lookup_domain(global, "*")) { - 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_l3_config_data_hash_dns(ip_data->l3cd, - sum, - ip_data->addr_family, - ip_data->ip_config_type, - has_global_dns_section); - } - } + g_checksum_update(sum, nm_dns_plugin_get_hash(priv->resolvconf_plugin), HASH_LEN); nm_utils_checksum_get_digest_len(sum, buffer, HASH_LEN); } static gboolean -merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) +compute_hash(NMDnsManager *self, NMDnsPlugin *plugin, GChecksum *global_sum) { - NMGlobalDnsDomain *default_domain; - const char *const *searches; - const char *const *options; - const char *const *servers; - guint i; + nm_auto_free_checksum GChecksum *sum = NULL; + NMDnsConfigIPData *ip_data; + guint8 new[HASH_LEN]; - /* Global config must be processed before connections' config */ - nm_assert(rc->nameservers->len == 0); + sum = global_sum ? g_checksum_copy(global_sum) : g_checksum_new(G_CHECKSUM_SHA1); - if (!global_conf) - return FALSE; + nm_assert(HASH_LEN == g_checksum_type_get_length(G_CHECKSUM_SHA1)); - searches = nm_global_dns_config_get_searches(global_conf); - if (searches) { - for (i = 0; searches[i]; i++) { - if (domain_is_routing(searches[i])) - continue; - if (!domain_is_valid(searches[i], FALSE, TRUE)) - continue; - add_string_item(rc->searches, searches[i], TRUE); - } - } - - options = nm_global_dns_config_get_options(global_conf); - if (options) { - for (i = 0; options[i]; i++) - add_string_item(rc->options, options[i], TRUE); - } - - default_domain = nm_global_dns_config_lookup_domain(global_conf, "*"); - if (!default_domain) - return TRUE; - - servers = nm_global_dns_domain_get_servers(default_domain); - if (!servers) - return TRUE; - - for (i = 0; servers[i]; i++) { - char addrstr[NM_INET_ADDRSTRLEN]; - - /* TODO: support IPv6 link-local addresses with scope id */ - if (!nm_dns_uri_parse_plain(AF_UNSPEC, servers[i], addrstr, NULL)) - continue; - - add_string_item(rc->nameservers, addrstr, TRUE); - } - - return TRUE; -} - -static const char * -get_nameserver_list(int addr_family, const NML3ConfigData *l3cd, NMStrBuf *tmp_strbuf) -{ - guint num; - guint i; - const char *const *strarr; - - nm_str_buf_reset(tmp_strbuf); - - strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num); - for (i = 0; i < num; i++) { - if (i > 0) - nm_str_buf_append_c(tmp_strbuf, ' '); - nm_str_buf_append(tmp_strbuf, strarr[i]); - } - - nm_str_buf_maybe_expand(tmp_strbuf, 1, FALSE); - return nm_str_buf_get_str(tmp_strbuf); -} - -static char ** -_ptrarray_to_strv(GPtrArray *parray) -{ - if (parray->len > 0) - g_ptr_array_add(parray, NULL); - return (char **) g_ptr_array_free(parray, parray->len == 0); -} - -static void -_collect_resolv_conf_data(NMDnsManager *self, - NMGlobalDnsConfig *global_config, - char ***out_searches, - char ***out_options, - char ***out_nameservers, - char ***out_nis_servers, - const char **out_nis_domain) -{ - NMDnsManagerPrivate *priv; - NMResolvConfData rc = { - .nameservers = g_ptr_array_new(), - .searches = g_ptr_array_new(), - .options = g_ptr_array_new(), - .nis_domain = NULL, - .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) { - 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; - const NMDnsConfigIPData *ip_data; - const CList *head; - gboolean is_first = TRUE; - - head = _mgr_get_ip_data_lst_head(self); - c_list_for_each_entry (ip_data, head, ip_data_lst) { - gboolean skip = FALSE; - int dns_priority; - - _ASSERT_dns_config_ip_data(ip_data); - - if (!nm_l3_config_data_get_dns_priority(ip_data->l3cd, - ip_data->addr_family, - &dns_priority)) - nm_assert_not_reached(); - - if (is_first) { - is_first = FALSE; - first_prio = dns_priority; - } else if (first_prio < 0 && first_prio != dns_priority) - skip = TRUE; - - _LOGT("config: %8d %-7s v%c %-5d %s: %s", - dns_priority, - _config_type_to_string(ip_data->ip_config_type), - nm_utils_addr_family_to_char(ip_data->addr_family), - ip_data->data->ifindex, - skip ? "" : "", - get_nameserver_list(ip_data->addr_family, ip_data->l3cd, &tmp_strbuf)); - - 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, + c_list_for_each_entry (ip_data, _mgr_get_ip_data_lst_head(self), ip_data_lst) { + nm_dns_plugin_checksum(plugin, ip_data->l3cd, - has_global_dns_section); - } - } + sum, + ip_data->addr_family, + ip_data->ip_config_type); } - if (priv->hostdomain) - add_string_item(rc.searches, priv->hostdomain, TRUE); + nm_utils_checksum_get_digest_len(sum, new, HASH_LEN); - if (rc.has_trust_ad == NM_TERNARY_TRUE) - g_ptr_array_add(rc.options, g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD)); + if (memcmp(new, nm_dns_plugin_get_hash(plugin), sizeof(new)) != 0) { + nm_dns_plugin_set_hash(plugin, new); + return TRUE; + } + return FALSE; +} - *out_searches = _ptrarray_to_strv(rc.searches); - *out_options = _ptrarray_to_strv(rc.options); - *out_nameservers = _ptrarray_to_strv(rc.nameservers); - *out_nis_servers = _ptrarray_to_strv(rc.nis_servers); - *out_nis_domain = rc.nis_domain; +static gboolean +update_plugin_hashes(NMDnsManager *self, gboolean set_update_flags) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + nm_auto_free_checksum GChecksum *global_sum = NULL; + const NMGlobalDnsConfig *global = + nm_config_data_get_global_dns_config(nm_config_get_data(priv->config)); + + if (global) { + global_sum = g_checksum_new(G_CHECKSUM_SHA1); + nm_global_dns_config_update_checksum(global, global_sum); + } + + /* presence of these two is not certain but resolvconf is always used */ + if (priv->custom_plugin && compute_hash(self, priv->custom_plugin, global_sum)) + priv->custom_plugin_update_to_do = priv->custom_plugin_update_to_do || set_update_flags; + + if (priv->sd_resolved_backup_plugin + && compute_hash(self, priv->sd_resolved_backup_plugin, global_sum)) + priv->sd_resolved_backup_plugin_update_to_do = + priv->sd_resolved_backup_plugin_update_to_do || set_update_flags; + + if (compute_hash(self, priv->resolvconf_plugin, global_sum)) { + priv->resolvconf_plugin_update_to_do = + priv->resolvconf_plugin_update_to_do || set_update_flags; + return TRUE; + } + + return priv->custom_plugin_update_to_do || priv->sd_resolved_backup_plugin_update_to_do; } /*****************************************************************************/ @@ -1738,23 +856,37 @@ _mgr_configs_data_clear(NMDnsManager *self) /*****************************************************************************/ +static void +update_custom_plugin(NMDnsManager *self, + gboolean no_caching, + NMDnsUpdateData *update_data, + GError **local_error) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + const char *plugin_name = nm_dns_plugin_get_name(priv->custom_plugin); + + priv->custom_plugin_update_to_do = FALSE; + if (nm_dns_plugin_is_caching(priv->custom_plugin) && !no_caching) { + _LOGD("update-dns: updating plugin %s", plugin_name); + if (!nm_dns_plugin_update(priv->custom_plugin, update_data, local_error)) { + _LOGW("update-dns: plugin %s update failed: %s", plugin_name, (*local_error)->message); + } else { + update_data->caching_successful = nm_dns_plugin_is_caching(priv->custom_plugin); + priv->caching_was_successful = update_data->caching_successful; + } + } else { + _LOGD("update-dns: plugin %s ignored (caching disabled)", plugin_name); + } +} + static gboolean update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError **error) { - NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); - const char *nis_domain = NULL; - gs_strfreev char **searches = NULL; - gs_strfreev char **options = NULL; - gs_strfreev char **nameservers = NULL; - gs_strfreev char **nis_servers = NULL; - gboolean caching = FALSE; - gboolean do_update = TRUE; - gboolean resolv_conf_updated = FALSE; - SpawnResult result = SR_SUCCESS; + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); NMConfigData *data; NMGlobalDnsConfig *global_config; - gs_free_error GError *local_error = NULL; - GError **const p_local_error = error ? &local_error : NULL; + gs_free_error GError *local_error = NULL; + NMDnsUpdateData update_data; nm_assert(!error || !*error); @@ -1767,70 +899,47 @@ update_dns(NMDnsManager *self, gboolean no_caching, gboolean force_emit, GError nm_clear_g_source(&priv->plugin_ratelimit.timer); - if (NM_IN_SET(priv->rc_manager, - NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED, - NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE)) { - do_update = FALSE; - _LOGD("update-dns: not updating resolv.conf"); - } else { - priv->dns_touched = TRUE; - _LOGD("update-dns: updating resolv.conf"); - } - 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); + _mgr_configs_data_construct(self); - _collect_resolv_conf_data(self, - global_config, - &searches, - &options, - &nameservers, - &nis_servers, - &nis_domain); + update_data = (NMDnsUpdateData) { + .ip_data_lst_head = _mgr_get_ip_data_lst_head(self), + .caching_successful = FALSE, + .resolved_used = FALSE, + .rc_manager = priv->rc_manager, + .hostdomain = priv->hostdomain, + .global_config = global_config, + .resolver_depends_on_nm = FALSE, + }; - if (priv->plugin || priv->sd_resolve_plugin) - _mgr_configs_data_construct(self); + if (priv->custom_plugin) { + if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->custom_plugin)) { + update_data.resolved_used = TRUE; + } else if (NM_IS_DNS_DNSMASQ(priv->custom_plugin)) { + update_data.resolver_depends_on_nm = TRUE; + } - if (priv->sd_resolve_plugin) { - nm_dns_plugin_update(priv->sd_resolve_plugin, - global_config, - _mgr_get_ip_data_lst_head(self), - priv->hostdomain, - NULL); + if (priv->custom_plugin_update_to_do) { + update_custom_plugin(self, no_caching, &update_data, &local_error); + } else if (nm_dns_plugin_is_caching(priv->custom_plugin) && !no_caching) { + /* If caching was succesfull before and we have no update, we have to + * consider it still being successful */ + update_data.caching_successful = priv->caching_was_successful; + } } - /* Let any plugins do their thing first */ - if (priv->plugin) { - NMDnsPlugin *plugin = priv->plugin; - const char *plugin_name = nm_dns_plugin_get_name(plugin); - gs_free_error GError *plugin_error = NULL; + if (priv->sd_resolved_backup_plugin_update_to_do && priv->sd_resolved_backup_plugin) { + nm_dns_plugin_update(priv->sd_resolved_backup_plugin, &update_data, NULL); + priv->sd_resolved_backup_plugin_update_to_do = FALSE; + } - if (nm_dns_plugin_is_caching(plugin)) { - if (no_caching) { - _LOGD("update-dns: plugin %s ignored (caching disabled)", plugin_name); - goto plugin_skip; - } - caching = TRUE; + if (priv->resolvconf_plugin_update_to_do) { + priv->resolvconf_plugin_update_to_do = FALSE; + if (!nm_dns_plugin_update(priv->resolvconf_plugin, &update_data, &local_error)) { + _LOGW("update-dns: plugin resolvconf update failed: %s", local_error->message); } - - _LOGD("update-dns: updating plugin %s", plugin_name); - if (!nm_dns_plugin_update(plugin, - global_config, - _mgr_get_ip_data_lst_head(self), - priv->hostdomain, - &plugin_error)) { - _LOGW("update-dns: plugin %s update failed: %s", plugin_name, plugin_error->message); - - /* If the plugin failed to update, we shouldn't write out a local - * caching DNS configuration to resolv.conf. - */ - caching = FALSE; - } - -plugin_skip:; } /* Clear the generated search list as it points to @@ -1838,130 +947,15 @@ plugin_skip:; * guarantee they stay alive. */ _mgr_configs_data_clear(self); - update_resolv_conf_no_stub(self, - NM_CAST_STRV_CC(searches), - NM_CAST_STRV_CC(nameservers), - NM_CAST_STRV_CC(options)); - - /* If caching was successful, we only send 127.0.0.1 to /etc/resolv.conf - * to ensure that the glibc resolver doesn't try to round-robin nameservers, - * but only uses the local caching nameserver. - */ - if (caching) { - const char *lladdr = "127.0.0.1"; - gboolean need_edns0; - gboolean need_trust; - - if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) { - /* systemd-resolved uses a different link-local address */ - lladdr = "127.0.0.53"; - } - - g_strfreev(nameservers); - nameservers = g_new0(char *, 2); - nameservers[0] = g_strdup(lladdr); - - need_edns0 = !nm_strv_contains(options, -1, NM_SETTING_DNS_OPTION_EDNS0) - && !nm_strv_contains(options, -1, NM_SETTING_DNS_OPTION_INTERNAL_NO_ADD_EDNS0); - need_trust = - !nm_strv_contains(options, -1, NM_SETTING_DNS_OPTION_TRUST_AD) - && !nm_strv_contains(options, -1, NM_SETTING_DNS_OPTION_INTERNAL_NO_ADD_TRUST_AD); - - if (need_edns0 || need_trust) { - gsize len; - - len = NM_PTRARRAY_LEN(options); - options = g_realloc(options, sizeof(char *) * (len + 3u)); - if (need_edns0) - options[len++] = g_strdup(NM_SETTING_DNS_OPTION_EDNS0); - if (need_trust) - options[len++] = g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD); - options[len] = NULL; - } - } - - if (options) { - guint i; - guint j; - - /* Skip internal options, those starting with '_' */ - for (i = 0, j = 0; options[i]; i++) { - if (options[i][0] == '_') { - g_free(options[i]); - continue; - } - if (i != j) - options[j] = options[i]; - j++; - } - options[j] = NULL; - } - - if (do_update) { - switch (priv->rc_manager) { - case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: - case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: - result = update_resolv_conf(self, - NM_CAST_STRV_CC(searches), - NM_CAST_STRV_CC(nameservers), - NM_CAST_STRV_CC(options), - p_local_error, - priv->rc_manager); - resolv_conf_updated = TRUE; - /* If we have ended with no nameservers avoid updating again resolv.conf - * on stop, as some external changes may be applied to it in the meanwhile */ - if (!nameservers && !options) - priv->dns_touched = FALSE; - break; - case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: - result = dispatch_resolvconf(self, searches, nameservers, options, p_local_error); - break; - case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: - result = dispatch_netconfig(self, - (const char *const *) searches, - (const char *const *) nameservers, - nis_domain, - (const char *const *) nis_servers, - p_local_error); - break; - default: - nm_assert_not_reached(); - } - - if (result == SR_NOTFOUND) { - _LOGD("update-dns: program not available, writing to resolv.conf"); - g_clear_error(&local_error); - result = update_resolv_conf(self, - NM_CAST_STRV_CC(searches), - NM_CAST_STRV_CC(nameservers), - NM_CAST_STRV_CC(options), - p_local_error, - NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK); - resolv_conf_updated = TRUE; - } - } - - /* Unless we've already done it, update private resolv.conf in NMRUNDIR - * ignoring any errors */ - if (!resolv_conf_updated) { - update_resolv_conf(self, - NM_CAST_STRV_CC(searches), - NM_CAST_STRV_CC(nameservers), - NM_CAST_STRV_CC(options), - NULL, - NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED); - } - /* signal that DNS resolution configs were changed */ - if ((caching || force_emit) && result == SR_SUCCESS) + if (update_data.caching_successful || force_emit) g_signal_emit(self, signals[CONFIG_CHANGED], 0); nm_clear_pointer(&priv->config_variant, g_variant_unref); _notify(self, PROP_CONFIGURATION); - if (result != SR_SUCCESS) { - if (error) - g_propagate_error(error, g_steal_pointer(&local_error)); + if (local_error) { + g_propagate_error(error, g_steal_pointer(&local_error)); return FALSE; } @@ -2154,6 +1148,8 @@ done: if (!priv->updates_queue) { gs_free_error GError *error = NULL; + update_plugin_hashes(self, TRUE); + compute_full_hash(self, priv->full_hash); if (!update_dns(self, FALSE, FALSE, &error)) _LOGW("could not commit DNS changes: %s", error->message); @@ -2189,9 +1185,9 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s * example, it is likely that the user *does* want "local" * or "localdomain" as a search domain. */ - if (domain_is_valid(domain, TRUE, FALSE)) { + if (nm_domain_is_valid(domain, TRUE, FALSE)) { /* pass */ - } else if (domain_is_valid(hostname, TRUE, FALSE)) { + } else if (nm_domain_is_valid(hostname, TRUE, FALSE)) { domain = hostname; } @@ -2211,6 +1207,9 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s if (!priv->updates_queue) { gs_free_error GError *error = NULL; + update_plugin_hashes(self, TRUE); + compute_full_hash(self, priv->full_hash); + if (!update_dns(self, FALSE, FALSE, &error)) _LOGW("could not commit DNS changes: %s", error->message); } @@ -2225,10 +1224,6 @@ 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); @@ -2239,15 +1234,16 @@ 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; + priv->config_changed = update_plugin_hashes(self, TRUE); + + compute_full_hash(self, priv->full_hash); + _LOGD("(%s): DNS configuration %s", func, priv->config_changed ? "changed" : "did not change"); priv->updates_queue--; @@ -2274,20 +1270,6 @@ nm_dns_manager_stop(NMDnsManager *self) _LOGT("stopping..."); - /* If we're quitting, leave a valid resolv.conf in place, not one - * pointing to 127.0.0.1 if dnsmasq was active. But if we haven't - * done any DNS updates yet, there's no reason to touch resolv.conf - * on shutdown. - */ - if (priv->dns_touched && priv->plugin && NM_IS_DNS_DNSMASQ(priv->plugin)) { - gs_free_error GError *error = NULL; - - if (!update_dns(self, TRUE, FALSE, &error)) - _LOGW("could not commit DNS changes on shutdown: %s", error->message); - - priv->dns_touched = FALSE; - } - priv->is_stopped = TRUE; } @@ -2301,12 +1283,13 @@ _clear_plugin(NMDnsManager *self) priv->plugin_ratelimit.ts = 0; nm_clear_g_source(&priv->plugin_ratelimit.timer); - if (priv->plugin) { - nm_clear_g_signal_handler(priv->plugin, &priv->update_changed_signal_id); - nm_dns_plugin_stop(priv->plugin); - g_clear_object(&priv->plugin); + if (priv->custom_plugin) { + nm_clear_g_signal_handler(priv->custom_plugin, &priv->update_changed_signal_id); + nm_dns_plugin_stop(priv->custom_plugin); + g_clear_object(&priv->custom_plugin); return TRUE; } + return FALSE; } @@ -2315,12 +1298,25 @@ _clear_sd_resolved_plugin(NMDnsManager *self) { NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); - if (priv->sd_resolve_plugin) { - nm_clear_g_signal_handler(priv->sd_resolve_plugin, &priv->update_changed_signal_id_sd); - nm_dns_plugin_stop(priv->sd_resolve_plugin); - g_clear_object(&priv->sd_resolve_plugin); + if (priv->sd_resolved_backup_plugin) { + nm_clear_g_signal_handler(priv->sd_resolved_backup_plugin, + &priv->update_changed_signal_id_sd); + nm_dns_plugin_stop(priv->sd_resolved_backup_plugin); + g_clear_object(&priv->sd_resolved_backup_plugin); return TRUE; } + + return FALSE; +} + +static gboolean +_clear_resolvconf_plugin(NMDnsManager *self) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self); + + nm_dns_plugin_stop(priv->resolvconf_plugin); + g_clear_object(&priv->resolvconf_plugin); + return FALSE; } @@ -2496,25 +1492,37 @@ again: rc_manager = _check_resconf_immutable(rc_manager); + /* This means we are initializing for the first time and there is no + * resolvconf plugin yet, thus initialize it */ + if (!priv->resolvconf_plugin) { + priv->resolvconf_plugin = nm_dns_resolvconf_new(); + } + if ((!mode && _resolvconf_resolved_managed()) || nm_streq0(mode, "systemd-resolved")) { - if (force_reload_plugin || !NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) { + if (force_reload_plugin || !NM_IS_DNS_SYSTEMD_RESOLVED(priv->custom_plugin)) { _clear_plugin(self); - priv->plugin = nm_dns_systemd_resolved_new(); - plugin_changed = TRUE; + priv->custom_plugin = nm_dns_systemd_resolved_new(); + /* If we were using sd_resolved as backup plugin, the hash of config + * is already in place and the new() call got us reference to that + * already existing instance, but we did not require that update to + * be succesfull, thus force sending the update again to find out + * whether resolved resolution is truly in place */ + priv->custom_plugin_update_to_do = priv->sd_resolved_backup_plugin ? TRUE : FALSE; + plugin_changed = TRUE; } mode = "systemd-resolved"; systemd_resolved = FALSE; } else if (nm_streq0(mode, "dnsmasq")) { - if (force_reload_plugin || !NM_IS_DNS_DNSMASQ(priv->plugin)) { + if (force_reload_plugin || !NM_IS_DNS_DNSMASQ(priv->custom_plugin)) { _clear_plugin(self); - priv->plugin = nm_dns_dnsmasq_new(); - plugin_changed = TRUE; + priv->custom_plugin = nm_dns_dnsmasq_new(); + plugin_changed = TRUE; } } else if (nm_streq0(mode, "dnsconfd")) { - if (force_reload_plugin || !NM_IS_DNS_DNSCONFD(priv->plugin)) { + if (force_reload_plugin || !NM_IS_DNS_DNSCONFD(priv->custom_plugin)) { _clear_plugin(self); - priv->plugin = nm_dns_dnsconfd_new(); - plugin_changed = TRUE; + priv->custom_plugin = nm_dns_dnsconfd_new(); + plugin_changed = TRUE; } } else { if (!NM_IN_STRSET(mode, "none", "default")) { @@ -2530,6 +1538,12 @@ again: plugin_changed = TRUE; } + if (plugin_changed) { + /* We need to force regeneration of resolvconf in case switch to local + * caching service would be needed */ + priv->resolvconf_plugin_update_to_do = TRUE; + } + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO) { rc_manager_was_auto = TRUE; if (nm_streq(mode, "systemd-resolved") || nm_streq(mode, "dnsconfd")) @@ -2556,9 +1570,9 @@ again: /* The systemd-resolved plugin is special. We typically always want to keep * systemd-resolved up to date even if the configured plugin is different. */ if (systemd_resolved) { - if (!priv->sd_resolve_plugin) { - priv->sd_resolve_plugin = nm_dns_systemd_resolved_new(); - systemd_resolved_changed = TRUE; + if (!priv->sd_resolved_backup_plugin) { + priv->sd_resolved_backup_plugin = nm_dns_systemd_resolved_new(); + systemd_resolved_changed = TRUE; } } else if (_clear_sd_resolved_plugin(self)) systemd_resolved_changed = TRUE; @@ -2584,29 +1598,29 @@ again: (systemd_resolved ? ",systemd-resolved" : ""), _rc_manager_to_string(rc_manager), rc_manager_was_auto ? " (auto)" : "", - NM_PRINT_FMT_QUOTED(priv->plugin, + NM_PRINT_FMT_QUOTED(priv->custom_plugin, ", plugin=", - nm_dns_plugin_get_name(priv->plugin), + nm_dns_plugin_get_name(priv->custom_plugin), "", "")); } - if (plugin_changed && priv->plugin && priv->update_changed_signal_id == 0) { - priv->update_changed_signal_id = g_signal_connect(priv->plugin, + if (plugin_changed && priv->custom_plugin && priv->update_changed_signal_id == 0) { + priv->update_changed_signal_id = g_signal_connect(priv->custom_plugin, NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED, G_CALLBACK(_update_pending_changed_cb), self); } - if (systemd_resolved_changed && priv->sd_resolve_plugin + if (systemd_resolved_changed && priv->sd_resolved_backup_plugin && priv->update_changed_signal_id_sd == 0) { - priv->update_changed_signal_id_sd = g_signal_connect(priv->sd_resolve_plugin, + priv->update_changed_signal_id_sd = g_signal_connect(priv->sd_resolved_backup_plugin, NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED, G_CALLBACK(_update_pending_changed_cb), self); } - if (!NM_IS_DNS_DNSMASQ(priv->plugin)) + if (!NM_IS_DNS_DNSMASQ(priv->custom_plugin)) nm_dnsmasq_kill_external(); _update_pending_maybe_changed(self); @@ -2643,6 +1657,19 @@ config_changed_cb(NMConfig *config, gs_free_error GError *error = NULL; priv->config_changed = TRUE; + + if (NM_FLAGS_ANY(changes, NM_CONFIG_CHANGE_CAUSE_SIGUSR1)) { + /* We need to force regeneration of plugin config as SIGUSR1 is meant to do that */ + priv->resolvconf_plugin_update_to_do = TRUE; + if (priv->custom_plugin) + priv->custom_plugin_update_to_do = TRUE; + if (priv->sd_resolved_backup_plugin) + priv->sd_resolved_backup_plugin_update_to_do = TRUE; + } + + update_plugin_hashes(self, TRUE); + compute_full_hash(self, priv->full_hash); + if (!update_dns(self, FALSE, TRUE, &error)) _LOGW("could not commit DNS changes: %s", error->message); } @@ -2832,13 +1859,17 @@ nm_dns_manager_init(NMDnsManager *self) nm_pint_equal, (GDestroyNotify) _dns_config_data_free, NULL); + init_resolv_conf_mode(self, TRUE); + + /* We init plugin hashes with defaults that exist but won't set update flags + * so there are not any unnecessary writes to resolvconf on startup etc. */ + update_plugin_hashes(self, FALSE); + compute_full_hash(self, priv->full_hash); - 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), self); - init_resolv_conf_mode(self, TRUE); } static void @@ -2858,6 +1889,7 @@ dispose(GObject *object) _clear_sd_resolved_plugin(self); _clear_plugin(self); + _clear_resolvconf_plugin(self); nm_clear_g_source_inst(&priv->update_pending_unblock); diff --git a/src/core/dns/nm-dns-manager.h b/src/core/dns/nm-dns-manager.h index 9b8c2972b4..2c6937e065 100644 --- a/src/core/dns/nm-dns-manager.h +++ b/src/core/dns/nm-dns-manager.h @@ -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); diff --git a/src/core/dns/nm-dns-plugin.c b/src/core/dns/nm-dns-plugin.c index 41a0dbc138..ad439b22bf 100644 --- a/src/core/dns/nm-dns-plugin.c +++ b/src/core/dns/nm-dns-plugin.c @@ -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) { diff --git a/src/core/dns/nm-dns-plugin.h b/src/core/dns/nm-dns-plugin.h index 6a30718912..207be22fb6 100644 --- a/src/core/dns/nm-dns-plugin.h +++ b/src/core/dns/nm-dns-plugin.h @@ -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); diff --git a/src/core/dns/nm-dns-resolv_conf.c b/src/core/dns/nm-dns-resolv_conf.c new file mode 100644 index 0000000000..970080fc40 --- /dev/null +++ b/src/core/dns/nm-dns-resolv_conf.c @@ -0,0 +1,1179 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include + +#include "libnm-core-intern/nm-core-internal.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "nm-dns-resolv_conf.h" +#include "NetworkManagerUtils.h" +#include "nm-l3-config-data.h" + +#ifndef RESOLVCONF_PATH +#define RESOLVCONF_PATH "/sbin/resolvconf" +#define HAS_RESOLVCONF 0 +#else +#define HAS_RESOLVCONF 1 +#endif + +#ifndef NETCONFIG_PATH +#define NETCONFIG_PATH "/sbin/netconfig" +#define HAS_NETCONFIG 0 +#else +#define HAS_NETCONFIG 1 +#endif + +/*****************************************************************************/ + +typedef enum { SR_SUCCESS, SR_NOTFOUND, SR_ERROR } SpawnResult; + +typedef struct { + GPtrArray *nameservers; + GPtrArray *searches; + GPtrArray *options; + char *nis_domain; + GPtrArray *nis_servers; + NMTernary has_trust_ad; + gboolean skip_connection_searches; + gboolean skip_connection_options; +} NMResolvConfDataInProgress; + +typedef struct { + char **nameservers; + char **searches; + char **options; + char *nis_domain; + char **nis_servers; + NMTernary has_trust_ad; +} NMResolvConfData; + +static NM_UTILS_LOOKUP_STR_DEFINE( + _config_type_to_string, + NMDnsIPConfigType, + NM_UTILS_LOOKUP_DEFAULT_WARN(""), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE, "best"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DNS_IP_CONFIG_TYPE_VPN, "vpn"), ); + +/*****************************************************************************/ + +typedef struct { + NMResolvConfData last_update; + gboolean dns_touched; + gboolean resolver_depends_on_nm; + gboolean caching_successful; +} NMDnsResolvconfPrivate; + +struct _NMDnsResolvconf { + NMDnsPlugin parent; + NMDnsResolvconfPrivate _priv; +}; + +struct _NMDnsResolvconfClass { + NMDnsPluginClass parent; +}; + +G_DEFINE_TYPE(NMDnsResolvconf, nm_dns_resolvconf, NM_TYPE_DNS_PLUGIN) + +#define NM_DNS_RESOLVCONF_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDnsResolvconf, NM_IS_DNS_RESOLVCONF, NMDnsPlugin) + +#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "resolvconf", __VA_ARGS__) + +/*****************************************************************************/ + +static GPid +run_netconfig(GError **error, int *stdin_fd) +{ + char *argv[5]; + gs_free char *tmp = NULL; + GPid pid = -1; + + argv[0] = NETCONFIG_PATH; + argv[1] = "modify"; + argv[2] = "--service"; + argv[3] = "NetworkManager"; + argv[4] = NULL; + + _LOGD("spawning '%s'", (tmp = g_strjoinv(" ", argv))); + + if (!g_spawn_async_with_pipes(NULL, + argv, + NULL, + G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &pid, + stdin_fd, + NULL, + NULL, + error)) + return -1; + + return pid; +} + +static void +netconfig_construct_str(GString *str, const char *key, const char *value) +{ + if (value) { + _LOGD("writing to netconfig: %s='%s'", key, value); + g_string_append_printf(str, "%s='%s'\n", key, value); + } +} + +static void +netconfig_construct_strv(GString *str, const char *key, const char *const *values) +{ + if (values) { + gs_free char *value = NULL; + + value = g_strjoinv(" ", (char **) values); + netconfig_construct_str(str, key, value); + } +} + +static SpawnResult +dispatch_netconfig(const char *const *searches, + const char *const *nameservers, + const char *nis_domain, + const char *const *nis_servers, + GError **error) +{ + GPid pid; + int fd; + int errsv; + int status; + gssize l; + nm_auto_free_gstring GString *str = NULL; + + pid = run_netconfig(error, &fd); + if (pid <= 0) + return SR_NOTFOUND; + + str = g_string_new(""); + + /* NM is writing already-merged DNS information to netconfig, so it + * does not apply to a specific network interface. + */ + netconfig_construct_str(str, "INTERFACE", "NetworkManager"); + netconfig_construct_strv(str, "DNSSEARCH", searches); + netconfig_construct_strv(str, "DNSSERVERS", nameservers); + netconfig_construct_str(str, "NISDOMAIN", nis_domain); + netconfig_construct_strv(str, "NISSERVERS", nis_servers); + +again: + l = write(fd, str->str, str->len); + if (l == -1) { + if (errno == EINTR) + goto again; + } + + nm_close(fd); + + /* FIXME: don't write to netconfig synchronously. */ + + /* Wait until the process exits */ + if (!nm_utils_kill_child_sync(pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Error waiting for netconfig to exit: %s", + nm_strerror_native(errsv)); + return SR_ERROR; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Error calling netconfig: %s %d", + WIFEXITED(status) ? "exited with status" + : (WIFSIGNALED(status) ? "exited with signal" + : "exited with unknown reason"), + WIFEXITED(status) ? WEXITSTATUS(status) + : (WIFSIGNALED(status) ? WTERMSIG(status) : status)); + return SR_ERROR; + } + return SR_SUCCESS; +} + +static char * +create_resolv_conf(const char *const *searches, + const char *const *nameservers, + const char *const *options) +{ + GString *str; + gsize i; + + str = g_string_new_len(NULL, 245); + + g_string_append(str, "# Generated by NetworkManager\n"); + + if (searches && searches[0]) { + gsize search_base_idx; + + g_string_append(str, "search"); + search_base_idx = str->len; + + for (i = 0; searches[i]; i++) { + const char *s = searches[i]; + gsize l = strlen(s); + + if (l == 0 || NM_STRCHAR_ANY(s, ch, NM_IN_SET(ch, ' ', '\t', '\n'))) { + /* there should be no such characters in the search entry. Also, + * because glibc parser would treat them as line/word separator. + * + * Skip the value silently. */ + continue; + } + + if (search_base_idx > 0) { + if (str->len - search_base_idx + 1 + l > 254) { + /* this entry crosses the 256 character boundary. Older glibc versions + * would truncate the entry at this point. + * + * Fill the line with spaces to cross the 256 char boundary and continue + * afterwards. This way, the truncation happens between two search entries. */ + while (str->len - search_base_idx < 257) + g_string_append_c(str, ' '); + search_base_idx = 0; + } + } + + g_string_append_c(str, ' '); + g_string_append_len(str, s, l); + } + g_string_append_c(str, '\n'); + } + + if (nameservers && nameservers[0]) { + for (i = 0; nameservers[i]; i++) { + if (i == 3) { + g_string_append( + str, + "# NOTE: the libc resolver may not support more than 3 nameservers.\n"); + g_string_append(str, "# The nameservers listed below may not be recognized.\n"); + } + g_string_append(str, "nameserver "); + g_string_append(str, nameservers[i]); + g_string_append_c(str, '\n'); + } + } + + if (options && options[0]) { + g_string_append(str, "options"); + for (i = 0; options[i]; i++) { + /* omit internal options, starting with '_' */ + if (options[i][0] != '_') { + g_string_append_c(str, ' '); + g_string_append(str, options[i]); + } + } + g_string_append_c(str, '\n'); + } + + return g_string_free(str, FALSE); +} + +static void +add_dns_option_item(GPtrArray *array, const char *str) +{ + if (_nm_utils_dns_option_find_idx((const char *const *) array->pdata, array->len, str) < 0) + g_ptr_array_add(array, g_strdup(str)); +} + +char * +nmtst_dns_create_resolv_conf(const char *const *searches, + const char *const *nameservers, + const char *const *options) +{ + return create_resolv_conf(searches, nameservers, options); +} + +static gboolean +write_resolv_conf_contents(FILE *f, const char *content, GError **error) +{ + int errsv; + + if (fprintf(f, "%s", content) < 0) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not write " _PATH_RESCONF ": %s", + nm_strerror_native(errsv)); + errno = errsv; + return FALSE; + } + + return TRUE; +} + +static gboolean +write_resolv_conf(FILE *f, + const char *const *searches, + const char *const *nameservers, + const char *const *options, + GError **error) +{ + gs_free char *content = NULL; + + content = create_resolv_conf(searches, nameservers, options); + return write_resolv_conf_contents(f, content, error); +} + +static SpawnResult +dispatch_resolvconf(char **searches, char **nameservers, char **options, GError **error) +{ + gs_free char *cmd = NULL; + FILE *f; + gboolean success = FALSE; + int errsv; + int err; + char *argv[] = {RESOLVCONF_PATH, "-d", "NetworkManager", NULL}; + int status; + + if (!g_file_test(RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) { + g_set_error_literal(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + RESOLVCONF_PATH " is not executable"); + return SR_NOTFOUND; + } + + if (!searches && !nameservers) { + _LOGI("Removing DNS information from %s", RESOLVCONF_PATH); + + if (!g_spawn_sync("/", argv, NULL, 0, NULL, NULL, NULL, NULL, &status, error)) + return SR_ERROR; + + if (status != 0) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "%s returned error code", + RESOLVCONF_PATH); + return SR_ERROR; + } + + return SR_SUCCESS; + } + + _LOGI("Writing DNS information to %s", RESOLVCONF_PATH); + + /* FIXME: don't write to resolvconf synchronously. */ + + cmd = g_strconcat(RESOLVCONF_PATH, " -a ", "NetworkManager", NULL); + if ((f = popen(cmd, "w")) == NULL) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not write to %s: %s", + RESOLVCONF_PATH, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + success = write_resolv_conf(f, + NM_CAST_STRV_CC(searches), + NM_CAST_STRV_CC(nameservers), + NM_CAST_STRV_CC(options), + error); + err = pclose(f); + if (err < 0) { + errsv = errno; + g_clear_error(error); + g_set_error(error, + G_IO_ERROR, + g_io_error_from_errno(errsv), + "Failed to close pipe to resolvconf: %d", + errsv); + return SR_ERROR; + } else if (err > 0) { + _LOGW("resolvconf failed with status %d", err); + g_clear_error(error); + g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "resolvconf failed with status %d", err); + return SR_ERROR; + } + + return success ? SR_SUCCESS : SR_ERROR; +} + +static const char * +_read_link_cached(const char *path, gboolean *is_cached, char **cached) +{ + nm_assert(is_cached); + nm_assert(cached); + + if (*is_cached) + return *cached; + + nm_assert(!*cached); + *is_cached = TRUE; + return (*cached = g_file_read_link(path, NULL)); +} + +#define MY_RESOLV_CONF NMRUNDIR "/resolv.conf" +#define MY_RESOLV_CONF_TMP MY_RESOLV_CONF ".tmp" +#define RESOLV_CONF_TMP "/etc/.resolv.conf.NetworkManager" + +#define NO_STUB_RESOLV_CONF NMRUNDIR "/no-stub-resolv.conf" + +static void +_update_resolv_conf_no_stub(const char *const *searches, + const char *const *nameservers, + const char *const *options) +{ + gs_free char *content = NULL; + GError *local = NULL; + + content = create_resolv_conf(searches, nameservers, options); + + if (!g_file_set_contents(NO_STUB_RESOLV_CONF, content, -1, &local)) { + _LOGD("update-resolv-no-stub: failure to write file: %s", local->message); + g_error_free(local); + return; + } + + _LOGT("update-resolv-no-stub: '%s' successfully written", NO_STUB_RESOLV_CONF); +} + +static SpawnResult +update_resolv_conf(const char *const *searches, + const char *const *nameservers, + const char *const *options, + GError **error, + NMDnsManagerResolvConfManager rc_manager) +{ + FILE *f; + gboolean success; + gs_free char *content = NULL; + SpawnResult write_file_result = SR_SUCCESS; + int errsv; + gboolean resconf_link_cached = FALSE; + gs_free char *resconf_link = NULL; + + content = create_resolv_conf(searches, nameservers, options); + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE + || (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK + && !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link))) { + gs_free char *rc_path_syml = NULL; + nm_auto_free char *rc_path_real = NULL; + const char *rc_path = _PATH_RESCONF; + GError *local = NULL; + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) { + rc_path_real = realpath(_PATH_RESCONF, NULL); + if (rc_path_real) + rc_path = rc_path_real; + else { + /* realpath did not resolve a path-name. That either means, + * _PATH_RESCONF: + * - does not exist + * - is a plain file + * - is a dangling symlink + * + * Handle the case, where it is a dangling symlink... */ + rc_path_syml = nm_utils_read_link_absolute(_PATH_RESCONF, NULL); + if (rc_path_syml) + rc_path = rc_path_syml; + } + } + + /* we first write to /etc/resolv.conf directly. If that fails, + * we still continue to write to runstatedir but remember the + * error. */ + if (!g_file_set_contents(rc_path, content, -1, &local)) { + _LOGT("update-resolv-conf: write to %s failed (rc-manager=%s, %s)", + rc_path, + _rc_manager_to_string(rc_manager), + local->message); + g_propagate_error(error, local); + /* clear @error, so that we don't try reset it. This is the error + * we want to propagate to the caller. */ + error = NULL; + write_file_result = SR_ERROR; + } else { + _LOGT("update-resolv-conf: write to %s succeeded (rc-manager=%s)", + rc_path, + _rc_manager_to_string(rc_manager)); + } + } + + if ((f = fopen(MY_RESOLV_CONF_TMP, "we")) == NULL) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not open %s: %s", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: open temporary file %s failed (%s)", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + success = write_resolv_conf_contents(f, content, error); + if (!success) { + errsv = errno; + _LOGT("update-resolv-conf: write temporary file %s failed (%s)", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + } + + if (fclose(f) < 0) { + if (success) { + errsv = errno; + /* only set an error here if write_resolv_conf() was successful, + * since its error is more important. + */ + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not close %s: %s", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: close temporary file %s failed (%s)", + MY_RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + } + return SR_ERROR; + } else if (!success) + return SR_ERROR; + + if (rename(MY_RESOLV_CONF_TMP, MY_RESOLV_CONF) < 0) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not replace %s: %s", + MY_RESOLV_CONF, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: failed to rename temporary file %s to %s (%s)", + MY_RESOLV_CONF_TMP, + MY_RESOLV_CONF, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) { + _LOGT("update-resolv-conf: write internal file %s succeeded (rc-manager=%s)", + MY_RESOLV_CONF, + _rc_manager_to_string(rc_manager)); + return write_file_result; + } + + if (rc_manager != NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK + || !_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link)) { + _LOGT("update-resolv-conf: write internal file %s succeeded", MY_RESOLV_CONF); + return write_file_result; + } + + if (!nm_streq0(_read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link), + MY_RESOLV_CONF)) { + _LOGT("update-resolv-conf: write internal file %s succeeded (don't touch symlink %s " + "linking to %s)", + MY_RESOLV_CONF, + _PATH_RESCONF, + _read_link_cached(_PATH_RESCONF, &resconf_link_cached, &resconf_link)); + return write_file_result; + } + + /* By this point, /etc/resolv.conf exists and is a symlink to our internal + * resolv.conf. We update the symlink so that applications get an inotify + * notification. + */ + if (unlink(RESOLV_CONF_TMP) != 0 && ((errsv = errno) != ENOENT)) { + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not unlink %s: %s", + RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: write internal file %s succeeded " + "but cannot delete temporary file %s: %s", + MY_RESOLV_CONF, + RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + if (symlink(MY_RESOLV_CONF, RESOLV_CONF_TMP) == -1) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not create symlink %s pointing to %s: %s", + RESOLV_CONF_TMP, + MY_RESOLV_CONF, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: write internal file %s succeeded " + "but failed to symlink %s: %s", + MY_RESOLV_CONF, + RESOLV_CONF_TMP, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + if (rename(RESOLV_CONF_TMP, _PATH_RESCONF) == -1) { + errsv = errno; + g_set_error(error, + NM_MANAGER_ERROR, + NM_MANAGER_ERROR_FAILED, + "Could not rename %s to %s: %s", + RESOLV_CONF_TMP, + _PATH_RESCONF, + nm_strerror_native(errsv)); + _LOGT("update-resolv-conf: write internal file %s succeeded " + "but failed to rename temporary symlink %s to %s: %s", + MY_RESOLV_CONF, + RESOLV_CONF_TMP, + _PATH_RESCONF, + nm_strerror_native(errsv)); + return SR_ERROR; + } + + _LOGT("update-resolv-conf: write internal file %s succeeded and update symlink %s", + MY_RESOLV_CONF, + _PATH_RESCONF); + return write_file_result; +} + +static gboolean +merge_global_dns_config(NMResolvConfDataInProgress *rc, NMGlobalDnsConfig *global_conf) +{ + NMGlobalDnsDomain *default_domain; + const char *const *searches; + const char *const *options; + const char *const *servers; + guint i; + + if (!global_conf) + return FALSE; + + searches = nm_global_dns_config_get_searches(global_conf); + if (searches) { + rc->skip_connection_searches = TRUE; + for (i = 0; searches[i]; i++) { + if (nm_domain_is_routing(searches[i])) + continue; + if (!nm_domain_is_valid(searches[i], FALSE, TRUE)) + continue; + nm_utils_g_ptr_array_add_string_item(rc->searches, searches[i], TRUE); + } + } + + options = nm_global_dns_config_get_options(global_conf); + if (options) { + rc->skip_connection_options = TRUE; + for (i = 0; options[i]; i++) + nm_utils_g_ptr_array_add_string_item(rc->options, options[i], TRUE); + } + + default_domain = nm_global_dns_config_lookup_domain(global_conf, "*"); + if (!default_domain) + return TRUE; + + servers = nm_global_dns_domain_get_servers(default_domain); + if (!servers) + return TRUE; + + for (i = 0; servers[i]; i++) { + char addrstr[NM_INET_ADDRSTRLEN]; + + /* TODO: support IPv6 link-local addresses with scope id */ + if (!nm_dns_uri_parse_plain(AF_UNSPEC, servers[i], addrstr, NULL)) + continue; + + nm_utils_g_ptr_array_add_string_item(rc->nameservers, addrstr, TRUE); + } + + return TRUE; +} + +static const char * +get_nameserver_list(int addr_family, const NML3ConfigData *l3cd, NMStrBuf *tmp_strbuf) +{ + guint num; + guint i; + const char *const *strarr; + + nm_str_buf_reset(tmp_strbuf); + + strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num); + for (i = 0; i < num; i++) { + if (i > 0) + nm_str_buf_append_c(tmp_strbuf, ' '); + nm_str_buf_append(tmp_strbuf, strarr[i]); + } + + nm_str_buf_maybe_expand(tmp_strbuf, 1, FALSE); + return nm_str_buf_get_str(tmp_strbuf); +} + +static char ** +_ptrarray_to_strv(GPtrArray *parray) +{ + if (parray->len > 0) + g_ptr_array_add(parray, NULL); + return (char **) g_ptr_array_free(parray, parray->len == 0); +} + +static void +merge_one_l3cd(NMResolvConfDataInProgress *rc, + int addr_family, + int ifindex, + const NML3ConfigData *l3cd) +{ + char buf[NM_INET_ADDRSTRLEN + 50]; + gboolean has_trust_ad; + guint num_nameservers; + guint num; + guint i; + const char *const *strarr; + + nm_assert(ifindex == nm_l3_config_data_get_ifindex(l3cd)); + + strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num_nameservers); + for (i = 0; i < num_nameservers; i++) { + NMIPAddr a; + + if (!nm_dns_uri_parse_plain(addr_family, strarr[i], NULL, &a)) + continue; + + if (addr_family == AF_INET) + nm_inet_ntop(addr_family, &a, buf); + else if (IN6_IS_ADDR_V4MAPPED(&a)) + nm_inet4_ntop(a.addr6.s6_addr32[3], buf); + else { + nm_inet6_ntop(&a.addr6, buf); + if (IN6_IS_ADDR_LINKLOCAL(&a)) { + const char *ifname; + + ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex); + if (ifname) { + g_strlcat(buf, "%", sizeof(buf)); + g_strlcat(buf, ifname, sizeof(buf)); + } + } + } + + nm_utils_g_ptr_array_add_string_item(rc->nameservers, buf, TRUE); + } + + if (!rc->skip_connection_searches) { + nm_l3_config_data_get_dns_domains(rc->searches, addr_family, l3cd, FALSE, TRUE); + } + + has_trust_ad = FALSE; + if (!rc->skip_connection_options) { + 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; + } + 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 (addr_family == AF_INET) { + const in_addr_t *nis_servers; + const char *nis_domain; + + nis_servers = nm_l3_config_data_get_nis_servers(l3cd, &num); + for (i = 0; i < num; i++) + nm_utils_g_ptr_array_add_string_item(rc->nis_servers, + nm_inet4_ntop(nis_servers[i], buf), + TRUE); + + if ((nis_domain = nm_l3_config_data_get_nis_domain(l3cd))) { + /* FIXME: handle multiple domains */ + if (!rc->nis_domain) + rc->nis_domain = g_strdup(nis_domain); + } + } +} + +static void +_clear_resolv_conf_data(NMDnsResolvconfPrivate *priv) +{ + g_strfreev(priv->last_update.nameservers); + g_strfreev(priv->last_update.nis_servers); + g_strfreev(priv->last_update.options); + g_strfreev(priv->last_update.searches); + + g_free(priv->last_update.nis_domain); + priv->last_update = (NMResolvConfData) { + .nameservers = NULL, + .searches = NULL, + .options = NULL, + .nis_domain = NULL, + .nis_servers = NULL, + .has_trust_ad = NM_TERNARY_DEFAULT, + }; + priv->resolver_depends_on_nm = FALSE; + priv->caching_successful = FALSE; +} + +static void +_finalize_resolv_conf_data(NMDnsResolvconfPrivate *priv, NMResolvConfDataInProgress in_progress) +{ + priv->last_update.nameservers = _ptrarray_to_strv(in_progress.nameservers); + priv->last_update.nis_servers = _ptrarray_to_strv(in_progress.nis_servers); + priv->last_update.options = _ptrarray_to_strv(in_progress.options); + priv->last_update.searches = _ptrarray_to_strv(in_progress.searches); + priv->last_update.has_trust_ad = in_progress.has_trust_ad; + priv->last_update.nis_domain = in_progress.nis_domain; +} + +static NMResolvConfDataInProgress +_collect_resolv_conf_data(const CList *ip_data_lst_head, + NMGlobalDnsConfig *global_config, + const char *hostdomain) +{ + NMResolvConfDataInProgress data_in_progress = {.nameservers = g_ptr_array_sized_new(5), + .nis_servers = g_ptr_array_sized_new(5), + .options = g_ptr_array_sized_new(5), + .searches = g_ptr_array_sized_new(5), + .has_trust_ad = NM_TERNARY_DEFAULT}; + + if (global_config) + merge_global_dns_config(&data_in_progress, global_config); + + 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; + const NMDnsConfigIPData *ip_data; + gboolean is_first = TRUE; + + c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) { + gboolean skip = FALSE; + int dns_priority; + + if (!nm_l3_config_data_get_dns_priority(ip_data->l3cd, + ip_data->addr_family, + &dns_priority)) + nm_assert_not_reached(); + + if (is_first) { + is_first = FALSE; + first_prio = dns_priority; + } else if (first_prio < 0 && first_prio != dns_priority) + skip = TRUE; + + _LOGT("config: %8d %-7s v%c %-5d %s: %s", + dns_priority, + _config_type_to_string(ip_data->ip_config_type), + nm_utils_addr_family_to_char(ip_data->addr_family), + ip_data->data->ifindex, + skip ? "" : "", + get_nameserver_list(ip_data->addr_family, ip_data->l3cd, &tmp_strbuf)); + + if (!skip) + merge_one_l3cd(&data_in_progress, + ip_data->addr_family, + ip_data->data->ifindex, + ip_data->l3cd); + } + } + + if (hostdomain) + nm_utils_g_ptr_array_add_string_item(data_in_progress.searches, hostdomain, TRUE); + + if (data_in_progress.has_trust_ad == NM_TERNARY_TRUE) + g_ptr_array_add(data_in_progress.options, g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD)); + + return data_in_progress; +} + +static gboolean +update(NMDnsPlugin *plugin, NMDnsUpdateData *update_data, GError **error) +{ + NMResolvConfDataInProgress data_in_progress; + NMDnsResolvconf *self = NM_DNS_RESOLVCONF(plugin); + NMDnsResolvconfPrivate *priv = NM_DNS_RESOLVCONF_GET_PRIVATE(self); + char *substitute_nameservers[2] = {0}; + gs_strfreev char **substitute_options = NULL; + char **final_nameservers = NULL; + char **final_options = NULL; + gboolean resolv_conf_updated = FALSE; + SpawnResult result = SR_SUCCESS; + + _clear_resolv_conf_data(priv); + + data_in_progress = _collect_resolv_conf_data(update_data->ip_data_lst_head, + update_data->global_config, + update_data->hostdomain); + + _finalize_resolv_conf_data(priv, data_in_progress); + + _update_resolv_conf_no_stub(NM_CAST_STRV_CC(priv->last_update.searches), + NM_CAST_STRV_CC(priv->last_update.nameservers), + NM_CAST_STRV_CC(priv->last_update.options)); + + if (NM_IN_SET(update_data->rc_manager, + NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED, + NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE)) { + _LOGD("update-dns: not updating resolv.conf"); + return TRUE; + } else { + _LOGD("update-dns: updating resolv.conf"); + } + + priv->caching_successful = update_data->caching_successful; + priv->resolver_depends_on_nm = update_data->resolver_depends_on_nm; + /* If caching was successful, we only send 127.0.0.1 to /etc/resolv.conf + * to ensure that the glibc resolver doesn't try to round-robin nameservers, + * but only uses the local caching nameserver. + */ + if (update_data->caching_successful) { + gboolean need_edns0; + gboolean need_trust; + + if (update_data->resolved_used) { + /* systemd-resolved uses 127.0.0.53 link-local address */ + substitute_nameservers[0] = "127.0.0.53"; + } else { + substitute_nameservers[0] = "127.0.0.1"; + } + + need_edns0 = !nm_strv_contains(priv->last_update.options, -1, NM_SETTING_DNS_OPTION_EDNS0) + && !nm_strv_contains(priv->last_update.options, + -1, + NM_SETTING_DNS_OPTION_INTERNAL_NO_ADD_EDNS0); + need_trust = + !nm_strv_contains(priv->last_update.options, -1, NM_SETTING_DNS_OPTION_TRUST_AD) + && !nm_strv_contains(priv->last_update.options, + -1, + NM_SETTING_DNS_OPTION_INTERNAL_NO_ADD_TRUST_AD); + + if (need_edns0 || need_trust) { + gsize len; + + len = NM_PTRARRAY_LEN(priv->last_update.options); + substitute_options = g_malloc(sizeof(char *) * (len + 3u)); + if (need_edns0) + substitute_options[len++] = g_strdup(NM_SETTING_DNS_OPTION_EDNS0); + if (need_trust) + substitute_options[len++] = g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD); + substitute_options[len] = NULL; + } + } + + final_nameservers = + (substitute_nameservers[0] ? substitute_nameservers : priv->last_update.nameservers); + final_options = (substitute_options ? substitute_options : priv->last_update.options); + + switch (update_data->rc_manager) { + case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK: + case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: + result = update_resolv_conf(NM_CAST_STRV_CC(priv->last_update.searches), + NM_CAST_STRV_CC(final_nameservers), + NM_CAST_STRV_CC(final_options), + error, + update_data->rc_manager); + resolv_conf_updated = TRUE; + break; + case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: + result = dispatch_resolvconf(priv->last_update.searches, + final_nameservers, + final_options, + error); + break; + case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: + result = dispatch_netconfig((const char *const *) priv->last_update.searches, + (const char *const *) final_nameservers, + priv->last_update.nis_domain, + (const char *const *) final_options, + error); + break; + default: + nm_assert_not_reached(); + } + + if (result == SR_NOTFOUND) { + _LOGD("update-dns: program not available, writing to resolv.conf"); + g_clear_error(error); + result = update_resolv_conf(NM_CAST_STRV_CC(priv->last_update.searches), + NM_CAST_STRV_CC(final_nameservers), + NM_CAST_STRV_CC(final_options), + error, + NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK); + resolv_conf_updated = TRUE; + } + + /* Unless we've already done it, update private resolv.conf in NMRUNDIR + * ignoring any errors */ + if (!resolv_conf_updated) { + update_resolv_conf(NM_CAST_STRV_CC(priv->last_update.searches), + NM_CAST_STRV_CC(final_nameservers), + NM_CAST_STRV_CC(final_options), + NULL, + NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED); + } else { + priv->dns_touched = TRUE; + } + + return result == SR_SUCCESS; +} + +static void +resolvconf_checksum(const NML3ConfigData *l3cd, + GChecksum *sum, + int addr_family, + NMDnsIPConfigType dns_ip_config_type) +{ + guint i; + const char *const *strarr; + const in_addr_t *wins; + guint num_elements; + gboolean empty = TRUE; + int prio = 0; + + 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])); + empty = FALSE; + } + + 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); + empty = FALSE; + } + } + + /* Resolvconf without servers serves to no purpose */ + if (empty) { + return; + } + + 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])); + } + + strarr = nm_l3_config_data_get_dns_options(l3cd, addr_family, &num_elements); + for (i = 0; i < num_elements; i++) { + g_checksum_update(sum, (const guint8 *) strarr[i], strlen(strarr[i])); + } + + 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_resolvconf_init(NMDnsResolvconf *self) +{ + NMDnsResolvconfPrivate *priv = NM_DNS_RESOLVCONF_GET_PRIVATE(self); + priv->last_update.has_trust_ad = NM_TERNARY_DEFAULT; +} + +NMDnsPlugin * +nm_dns_resolvconf_new(void) +{ + return g_object_new(NM_TYPE_DNS_RESOLVCONF, NULL); +} + +static void +stop(NMDnsPlugin *plugin) +{ + NMDnsResolvconf *self = NM_DNS_RESOLVCONF(plugin); + NMDnsResolvconfPrivate *priv = NM_DNS_RESOLVCONF_GET_PRIVATE(self); + + /* If we're quitting, leave a valid resolv.conf in place, not one + * pointing to 127.0.0.1 if dnsmasq was active. But if we haven't + * done any DNS updates yet, there's no reason to touch resolv.conf + * on shutdown. + */ + if (priv->dns_touched && priv->caching_successful && priv->resolver_depends_on_nm) { + gs_free_error GError *error = NULL; + + update_resolv_conf(NM_CAST_STRV_CC(priv->last_update.searches), + NM_CAST_STRV_CC(priv->last_update.nameservers), + NM_CAST_STRV_CC(priv->last_update.options), + &error, + NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE); + + if (error) + _LOGW("could not commit DNS changes on shutdown: %s", error->message); + + priv->dns_touched = FALSE; + } +} + +static void +dispose(GObject *object) +{ + NMDnsResolvconfPrivate *priv = NM_DNS_RESOLVCONF_GET_PRIVATE(NM_DNS_RESOLVCONF(object)); + + _LOGT("disposing of Resolvconf plugin"); + + stop(NM_DNS_PLUGIN(object)); + + g_strfreev(priv->last_update.nameservers); + g_strfreev(priv->last_update.nis_servers); + g_strfreev(priv->last_update.options); + g_strfreev(priv->last_update.searches); + g_free(priv->last_update.nis_domain); + + G_OBJECT_CLASS(nm_dns_resolvconf_parent_class)->dispose(object); +} + +static inline gboolean +get_update_pending(NMDnsPlugin *plugin) +{ + return FALSE; +} + +static void +nm_dns_resolvconf_class_init(NMDnsResolvconfClass *dns_class) +{ + NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS(dns_class); + GObjectClass *object_class = G_OBJECT_CLASS(dns_class); + + object_class->dispose = dispose; + + plugin_class->plugin_name = "resolvconf"; + plugin_class->is_caching = FALSE; + plugin_class->stop = stop; + plugin_class->update = update; + plugin_class->get_update_pending = get_update_pending; + plugin_class->checksum = resolvconf_checksum; +} diff --git a/src/core/dns/nm-dns-resolv_conf.h b/src/core/dns/nm-dns-resolv_conf.h new file mode 100644 index 0000000000..0f48b4be24 --- /dev/null +++ b/src/core/dns/nm-dns-resolv_conf.h @@ -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__ */ diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c index 30d4e446b8..ef57415f26 100644 --- a/src/core/dns/nm-dns-systemd-resolved.c +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -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; } diff --git a/src/core/meson.build b/src/core/meson.build index 4a3dfd8e11..54681ef921 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -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',