From bef448a93ec9bfdba7be66d2f0921f33f70a5c2f Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 14 Apr 2025 12:40:38 +0200 Subject: [PATCH 1/8] dns: Add cert-authority and resolve-mode to global dns config hash --- src/core/nm-config-data.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c index dd0822adf4..ca2a15eaa2 100644 --- a/src/core/nm-config-data.c +++ b/src/core/nm-config-data.c @@ -401,7 +401,7 @@ nm_config_data_get_ignore_carrier_for_port(const NMConfigData *self, if (!nm_utils_ifname_valid_kernel(controller, NULL)) goto out_default; - match_data = (NMMatchSpecDeviceData) { + match_data = (NMMatchSpecDeviceData){ .interface_name = controller, .device_type = port_type, }; @@ -1119,7 +1119,8 @@ nm_global_dns_config_update_checksum(const NMGlobalDnsConfig *dns_config, GCheck v8 = NM_HASH_COMBINE_BOOLS(guint8, !dns_config->searches, !dns_config->options, - !dns_config->domain_list); + !dns_config->domain_list, + !dns_config->cert_authority); g_checksum_update(sum, (guchar *) &v8, 1); if (dns_config->searches) { @@ -1134,6 +1135,12 @@ nm_global_dns_config_update_checksum(const NMGlobalDnsConfig *dns_config, GCheck (guchar *) dns_config->options[i], strlen(dns_config->options[i]) + 1); } + if (dns_config->cert_authority) { + g_checksum_update(sum, + (guchar *) dns_config->cert_authority, + strlen(dns_config->cert_authority) + 1); + } + g_checksum_update(sum, (guchar *) &dns_config->resolve_mode, sizeof(dns_config->resolve_mode)); if (dns_config->domain_list) { for (i = 0; dns_config->domain_list[i]; i++) { @@ -2005,7 +2012,7 @@ _match_section_info_init(MatchSectionInfo *connection_info, if (!value) continue; - vals[j++] = (NMUtilsNamedValue) { + vals[j++] = (NMUtilsNamedValue){ .name = g_steal_pointer(&key), .value_str = value, }; From ca389ce017dc86ccdf4e38406bf074350f6a0698 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 23 Mar 2026 12:51:46 +0100 Subject: [PATCH 2/8] dns: Introduce nm_l3_config_data_get_dns_domains function nm_l3_config_data_get_dns_domains function gets both domains and searches of NML3ConfigData structure --- src/core/dns/nm-dns-manager.c | 6 +++++- src/core/nm-l3-config-data.c | 37 +++++++++++++++++++++++++++++++++++ src/core/nm-l3-config-data.h | 6 ++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index ec33c46427..59ef4a7de5 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -2799,7 +2799,11 @@ _get_config_variant(NMDnsManager *self) else g_ptr_array_set_size(array_domains, 0); - add_dns_domains(array_domains, ip_data->addr_family, ip_data->l3cd, TRUE, FALSE); + nm_l3_config_data_get_dns_domains(array_domains, + ip_data->addr_family, + ip_data->l3cd, + TRUE, + FALSE); if (array_domains->len) { g_variant_builder_add(&entry_builder, "{sv}", diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 9e9066c3ed..610240957c 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -219,6 +219,43 @@ _garray_inaddr_clone(const GArray *src, int addr_family) return dst; } +void +nm_l3_config_data_get_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 && nm_domain_is_routing(str)) + continue; + if (!nm_domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE)) + continue; + nm_utils_g_ptr_array_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 && nm_domain_is_routing(str)) + continue; + if (!nm_domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE)) + continue; + nm_utils_g_ptr_array_add_string_item(array, str, dup); + } + } +} + static void _garray_inaddr_merge(GArray **p_dst, const GArray *src, int addr_family) { diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index b76e11f9b1..5311b24f4f 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -608,6 +608,12 @@ void nm_l3_config_data_set_allow_routes_without_address(NML3ConfigData *self, gboolean nm_l3_config_data_get_routed_dns(const NML3ConfigData *self, int addr_family); void nm_l3_config_data_set_routed_dns(NML3ConfigData *self, int addr_family, gboolean value); +void nm_l3_config_data_get_dns_domains(GPtrArray *array, + int addr_family, + const NML3ConfigData *l3cd, + gboolean include_routing, + gboolean dup); + NMProxyConfigMethod nm_l3_config_data_get_proxy_method(const NML3ConfigData *self); gboolean nm_l3_config_data_set_proxy_method(NML3ConfigData *self, NMProxyConfigMethod value); From acbd5781e75619fb28149b3e8460428196abb518 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 23 Mar 2026 13:07:07 +0100 Subject: [PATCH 3/8] dns: Move domain_is_routing and domain_is_valid functions domain_is_routing and domain_is_valid functions could be used outside of nm-dns-manager thus move them to core utils. --- src/core/dns/nm-dns-manager.c | 51 --------------------------------- src/core/nm-core-utils.c | 54 +++++++++++++++++++++++++++++++++++ src/core/nm-core-utils.h | 6 ++++ 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index 59ef4a7de5..ae3b29e690 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -177,57 +177,6 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER /*****************************************************************************/ -static gboolean -domain_is_valid(const char *domain, - gboolean reject_public_suffix, - gboolean assume_any_tld_is_public) -{ - if (*domain == '\0') - return FALSE; - - if (reject_public_suffix) { - int is_pub; - -#if !WITH_LIBPSL - /* Without libpsl, we cannot detect that the domain is a public suffix, we assume - * the domain is not and valid. */ - is_pub = FALSE; -#elif defined(PSL_TYPE_NO_STAR_RULE) - /* - * If we use PSL_TYPE_ANY, any TLD (top-level domain, i.e., domain - * with no dots) is considered *public* by the PSL library even if - * it is *not* on the official suffix list. This is the implicit - * behavior of the older API function psl_is_public_suffix(). - * To inhibit that and only deem TLDs explicitly listed in the PSL - * as public, we need to turn off the "prevailing star rule" with - * PSL_TYPE_NO_STAR_RULE. - * For documentation on psl_is_public_suffix2(), see: - * https://rockdaboot.github.io/libpsl/libpsl-Public-Suffix-List-functions.html#psl-is-public-suffix2 - * For more on the public suffix format, including wildcards: - * https://github.com/publicsuffix/list/wiki/Format#format - */ - is_pub = - psl_is_public_suffix2(psl_builtin(), - domain, - assume_any_tld_is_public ? PSL_TYPE_ANY : PSL_TYPE_NO_STAR_RULE); -#else - is_pub = psl_is_public_suffix(psl_builtin(), domain); -#endif - - if (is_pub) - return FALSE; - } - - return TRUE; -} - -static gboolean -domain_is_routing(const char *domain) -{ - return domain[0] == '~'; -} - -/*****************************************************************************/ static NM_UTILS_LOOKUP_STR_DEFINE( _rc_manager_to_string, diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index ee05bedd01..91d2466099 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -26,6 +26,10 @@ #include #include +#if WITH_LIBPSL +#include +#endif + #include "libnm-glib-aux/nm-uuid.h" #include "libnm-platform/nmp-base.h" #include "libnm-std-aux/unaligned.h" @@ -4455,6 +4459,56 @@ nm_utils_parse_dns_domain(const char *domain, gboolean *is_routing) return domain; } +inline gboolean +nm_domain_is_routing(const char *domain) +{ + return domain[0] == '~'; +} + +gboolean +nm_domain_is_valid(const char *domain, + gboolean reject_public_suffix, + gboolean assume_any_tld_is_public) +{ + if (*domain == '\0') + return FALSE; + + if (reject_public_suffix) { + int is_pub; + +#if !WITH_LIBPSL + /* Without libpsl, we cannot detect that the domain is a public suffix, we assume + * the domain is not and valid. */ + is_pub = FALSE; +#elif defined(PSL_TYPE_NO_STAR_RULE) + /* + * If we use PSL_TYPE_ANY, any TLD (top-level domain, i.e., domain + * with no dots) is considered *public* by the PSL library even if + * it is *not* on the official suffix list. This is the implicit + * behavior of the older API function psl_is_public_suffix(). + * To inhibit that and only deem TLDs explicitly listed in the PSL + * as public, we need to turn off the "prevailing star rule" with + * PSL_TYPE_NO_STAR_RULE. + * For documentation on psl_is_public_suffix2(), see: + * https://rockdaboot.github.io/libpsl/libpsl-Public-Suffix-List-functions.html#psl-is-public-suffix2 + * For more on the public suffix format, including wildcards: + * https://github.com/publicsuffix/list/wiki/Format#format + */ + is_pub = + psl_is_public_suffix2(psl_builtin(), + domain, + assume_any_tld_is_public ? PSL_TYPE_ANY : PSL_TYPE_NO_STAR_RULE); +#else + is_pub = psl_is_public_suffix(psl_builtin(), domain); +#endif + + if (is_pub) + return FALSE; + } + + return TRUE; +} + /*****************************************************************************/ static guint32 diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index a1892b9993..44fd5766a6 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -457,6 +457,12 @@ const char *nm_activation_type_to_string(NMActivationType activation_type); const char *nm_utils_parse_dns_domain(const char *domain, gboolean *is_routing); +gboolean nm_domain_is_routing(const char *domain); + +gboolean nm_domain_is_valid(const char *domain, + gboolean reject_public_suffix, + gboolean assume_any_tld_is_public); + /*****************************************************************************/ void nm_wifi_utils_parse_ies(const guint8 *bytes, From 0b04d9ad83dfdf5ba177608b6a4ab041bc079a30 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 23 Mar 2026 13:13:13 +0100 Subject: [PATCH 4/8] Add nm_utils_g_ptr_array_add_string_item convenience function --- src/core/nm-core-utils.c | 20 ++++++++++++++++++++ src/core/nm-core-utils.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 91d2466099..0b38b241d2 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -4161,6 +4161,26 @@ nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings) g_value_take_boxed(value, strv); } +void +nm_utils_g_ptr_array_add_string_item(GPtrArray *array, const char *str, gboolean dup) +{ + 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 && nm_streq(candidate, str)) + return; + } + + /* No dupes, add the new item */ + g_ptr_array_add(array, dup ? g_strdup(str) : (gpointer) str); +} + /*****************************************************************************/ const char * diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index 44fd5766a6..f087e965ec 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -423,6 +423,8 @@ void _nm_utils_set_testing(NMUtilsTestFlags flags); void nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings); +void nm_utils_g_ptr_array_add_string_item(GPtrArray *array, const char *str, gboolean dup); + /*****************************************************************************/ const char *nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size); From 8e7c19d2379104332f09062c56cbbc77fc375ff7 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 23 Mar 2026 13:20:47 +0100 Subject: [PATCH 5/8] dns: Introduce NMDnsUpdateData structure NMDnsUpdateData structure holds information required for DNS plugins to change their behavior based on connection update. --- src/core/dns/nm-dns-dnsconfd.c | 12 ++++-------- src/core/dns/nm-dns-dnsmasq.c | 12 +++++------- src/core/dns/nm-dns-plugin.h | 9 +++++++++ src/core/dns/nm-dns-systemd-resolved.c | 8 ++------ 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c index 5f32f3faec..a1800f35ce 100644 --- a/src/core/dns/nm-dns-dnsconfd.c +++ b/src/core/dns/nm-dns-dnsconfd.c @@ -679,11 +679,7 @@ parse_all_interface_config(GVariantBuilder *argument_builder, } static gboolean -update(NMDnsPlugin *plugin, - const NMGlobalDnsConfig *global_config, - const CList *ip_data_lst_head, - const char *hostdomain, - GError **error) +update(NMDnsPlugin *plugin, NMDnsUpdateData *update_data, GError **error) { NMDnsDnsconfd *self = NM_DNS_DNSCONFD(plugin); NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); @@ -698,12 +694,12 @@ update(NMDnsPlugin *plugin, g_variant_builder_init(&argument_builder, G_VARIANT_TYPE("(aa{sv}u)")); g_variant_builder_open(&argument_builder, G_VARIANT_TYPE("aa{sv}")); - if (global_config) { + if (update_data->global_config) { _LOGT("parsing global configuration"); - parse_global_config(global_config, &argument_builder, &resolve_mode, &ca); + parse_global_config(update_data->global_config, &argument_builder, &resolve_mode, &ca); } _LOGT("parsing configuration of interfaces"); - parse_all_interface_config(&argument_builder, ip_data_lst_head, ca); + parse_all_interface_config(&argument_builder, update_data->ip_data_lst_head, ca); g_variant_builder_close(&argument_builder); g_variant_builder_add(&argument_builder, "u", resolve_mode); diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c index cc0a878843..37c839c22b 100644 --- a/src/core/dns/nm-dns-dnsmasq.c +++ b/src/core/dns/nm-dns-dnsmasq.c @@ -1207,11 +1207,7 @@ start_dnsmasq(NMDnsDnsmasq *self, gboolean force_start, GError **error) } static gboolean -update(NMDnsPlugin *plugin, - const NMGlobalDnsConfig *global_config, - const CList *ip_data_lst_head, - const char *hostdomain, - GError **error) +update(NMDnsPlugin *plugin, NMDnsUpdateData *update_data, GError **error) { NMDnsDnsmasq *self = NM_DNS_DNSMASQ(plugin); NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE(self); @@ -1220,8 +1216,10 @@ update(NMDnsPlugin *plugin, return FALSE; nm_clear_pointer(&priv->set_server_ex_args, g_variant_unref); - priv->set_server_ex_args = - g_variant_ref_sink(create_update_args(self, global_config, ip_data_lst_head, hostdomain)); + priv->set_server_ex_args = g_variant_ref_sink(create_update_args(self, + update_data->global_config, + update_data->ip_data_lst_head, + update_data->hostdomain)); priv->set_server_ex_args_dirty = TRUE; send_dnsmasq_update(self); diff --git a/src/core/dns/nm-dns-plugin.h b/src/core/dns/nm-dns-plugin.h index 364f15cedf..6a30718912 100644 --- a/src/core/dns/nm-dns-plugin.h +++ b/src/core/dns/nm-dns-plugin.h @@ -20,6 +20,15 @@ (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass)) #define NM_DNS_PLUGIN_UPDATE_PENDING_CHANGED "update-pending-changed" +typedef struct { + const CList *ip_data_lst_head; + gboolean caching_successful; + gboolean resolved_used; + gboolean resolver_depends_on_nm; + NMDnsManagerResolvConfManager rc_manager; + const char *hostdomain; + NMGlobalDnsConfig *global_config; +} NMDnsUpdateData; struct _NMDnsPluginPrivate; diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c index 172bd0bc6b..30d4e446b8 100644 --- a/src/core/dns/nm-dns-systemd-resolved.c +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -801,11 +801,7 @@ start_resolve: } static gboolean -update(NMDnsPlugin *plugin, - const NMGlobalDnsConfig *global_config, - const CList *ip_data_lst_head, - const char *hostdomain, - GError **error) +update(NMDnsPlugin *plugin, NMDnsUpdateData *update_data, GError **error) { NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin); NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); @@ -826,7 +822,7 @@ update(NMDnsPlugin *plugin, interfaces = g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) _interface_config_free); - c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) { + c_list_for_each_entry (ip_data, update_data->ip_data_lst_head, ip_data_lst) { InterfaceConfig *ic = NULL; int ifindex = ip_data->data->ifindex; From 91db5d5f90ccbd1814a3e19df0adf5f7c7837760 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 23 Mar 2026 13:22:23 +0100 Subject: [PATCH 6/8] Fix formatting in nm-config-data --- src/core/nm-config-data.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c index ca2a15eaa2..733d5001d8 100644 --- a/src/core/nm-config-data.c +++ b/src/core/nm-config-data.c @@ -401,7 +401,7 @@ nm_config_data_get_ignore_carrier_for_port(const NMConfigData *self, if (!nm_utils_ifname_valid_kernel(controller, NULL)) goto out_default; - match_data = (NMMatchSpecDeviceData){ + match_data = (NMMatchSpecDeviceData) { .interface_name = controller, .device_type = port_type, }; @@ -2012,7 +2012,7 @@ _match_section_info_init(MatchSectionInfo *connection_info, if (!value) continue; - vals[j++] = (NMUtilsNamedValue){ + vals[j++] = (NMUtilsNamedValue) { .name = g_steal_pointer(&key), .value_str = value, }; From 3c01edf937e81b1943b0f857d6caba9f3e908ecd Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 23 Mar 2026 13:28:13 +0100 Subject: [PATCH 7/8] dns: Rework DNS checksums and isolate resolvconf operations Now all the plugins for configuration of DNS resolvers have their own implementation of checksums. This is required, because the plugins are paying different amount of attention to different parameters of connections. The previous common implementation for example ignored new routes and thus did not trigger sending update with forwarding of reverse DNS queries to those servers. Additionally to make dns-manager more clear, operations with resolv.conf have been moved to their separate plugin nm-dns-resolv_conf --- src/core/dns/nm-dns-dnsconfd.c | 72 ++ src/core/dns/nm-dns-dnsmasq.c | 72 ++ src/core/dns/nm-dns-manager.c | 1434 ++++-------------------- src/core/dns/nm-dns-manager.h | 38 - src/core/dns/nm-dns-plugin.c | 56 +- src/core/dns/nm-dns-plugin.h | 72 +- src/core/dns/nm-dns-resolv_conf.c | 1179 +++++++++++++++++++ src/core/dns/nm-dns-resolv_conf.h | 29 + src/core/dns/nm-dns-systemd-resolved.c | 63 ++ src/core/meson.build | 1 + 10 files changed, 1757 insertions(+), 1259 deletions(-) create mode 100644 src/core/dns/nm-dns-resolv_conf.c create mode 100644 src/core/dns/nm-dns-resolv_conf.h 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', From a1fae0286ac152ced1188a25383adaf31cef8e99 Mon Sep 17 00:00:00 2001 From: Tomas Korbar Date: Mon, 1 Sep 2025 11:03:31 +0200 Subject: [PATCH 8/8] dns: Clear update_pending flag of dns-mgr on update timeout Even if the first dns update times out, allow the next one 5 seconds to be finished again. --- src/core/dns/nm-dns-manager.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index 4ce466e1ce..05d1fac12b 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -191,6 +191,9 @@ _update_pending_unblock_cb(gpointer user_data) _LOGW( "update-pending changed: DNS plugin did not become ready again. Assume something is wrong"); + /* If previous update left this field as TRUE and we have to clear it, so the next + * update is again allowed the 5 seconds timeout */ + priv->update_pending = FALSE; _notify(self, PROP_UPDATE_PENDING); return G_SOURCE_CONTINUE; } @@ -232,7 +235,6 @@ nm_dns_manager_get_update_pending(NMDnsManager *self) g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE); priv = NM_DNS_MANAGER_GET_PRIVATE(self); - nm_assert(priv->update_pending == _update_pending_detect(self)); nm_assert(priv->update_pending || !priv->update_pending_unblock); /* update-pending can only be TRUE for a certain time (before we assume