diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index 0363dd863d..e3edf2d9e0 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -332,20 +332,24 @@ no-auto-default=* systemd-resolved: NetworkManager will push the DNS configuration to systemd-resolved + dnsconfd: NetworkManager will + push the DNS configuration to dnsconfd and by default set ipvX.routed-dns to + yes, so servers are contacted through right interfaces. + none: NetworkManager will not modify resolv.conf. This implies rc-manager unmanaged - Note that the plugins dnsmasq and systemd-resolved - are caching local nameservers. + Note that the plugins dnsmasq,systemd-resolved + and dnsconfd are caching local nameservers. Hence, when NetworkManager writes &nmrundir;/resolv.conf and /etc/resolv.conf (according to rc-manager setting below), the name server there will be localhost only. NetworkManager also writes a file &nmrundir;/no-stub-resolv.conf that contains the original name servers pushed to the DNS plugin. - When using dnsmasq and systemd-resolved, - per-connection added dns servers will always be queried using + When using dnsmasq,systemd-resolved and + dnsconfd per-connection added dns servers will always be queried using the device the connection has been activated on. diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 45170d31ba..e310a9c680 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -1361,6 +1361,8 @@ _prop_get_ipvx_routed_dns(NMDevice *self, int addr_family) NMSettingIPConfig *s_ip; NMSettingIPConfigRoutedDns val; int IS_IPv4; + const char *dns_mode; + NMSettingIPConfigRoutedDns fallback_value = NM_SETTING_IP_CONFIG_ROUTED_DNS_NO; g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_IP_CONFIG_ROUTED_DNS_NO); IS_IPv4 = NM_IS_IPv4(addr_family); @@ -1375,13 +1377,18 @@ _prop_get_ipvx_routed_dns(NMDevice *self, int addr_family) if (val != NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT) return val; + dns_mode = nm_config_data_get_dns_mode(nm_config_get_data(nm_config_get())); + if (nm_streq0(dns_mode, "dnsconfd")) { + fallback_value = NM_SETTING_IP_CONFIG_ROUTED_DNS_YES; + } + return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA, IS_IPv4 ? NM_CON_DEFAULT("ipv4.routed-dns") : NM_CON_DEFAULT("ipv6.routed-dns"), self, NM_SETTING_IP_CONFIG_ROUTED_DNS_NO, NM_SETTING_IP_CONFIG_ROUTED_DNS_YES, - NM_SETTING_IP_CONFIG_ROUTED_DNS_NO); + fallback_value); } static NMSettingConnectionMdns diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c new file mode 100644 index 0000000000..77dba38364 --- /dev/null +++ b/src/core/dns/nm-dns-dnsconfd.c @@ -0,0 +1,632 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-dns-dnsconfd.h" + +#include "libnm-glib-aux/nm-dbus-aux.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "libnm-platform/nm-platform.h" +#include "nm-utils.h" +#include "nm-dbus-manager.h" +#include "NetworkManagerUtils.h" +#include "nm-l3-config-data.h" +#include "nm-manager.h" +#include "devices/nm-device.h" +#include "nm-active-connection.h" +#include "nm-l3cfg.h" + +typedef struct { + GDBusConnection *dbus_connection; + GCancellable *update_cancellable; + char *name_owner; + guint name_owner_changed_id; + GCancellable *name_owner_cancellable; + GVariant *latest_update_args; +} NMDnsDnsconfdPrivate; + +struct _NMDnsDnsconfd { + NMDnsPlugin parent; + NMDnsDnsconfdPrivate _priv; +}; + +struct _NMDnsDnsconfdClass { + NMDnsPluginClass parent; +}; + +G_DEFINE_TYPE(NMDnsDnsconfd, nm_dns_dnsconfd, NM_TYPE_DNS_PLUGIN) + +#define NM_DNS_DNSCONFD_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDnsDnsconfd, NM_IS_DNS_DNSCONFD, NMDnsPlugin) + +#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "dnsconfd", __VA_ARGS__) + +#define DNSCONFD_DBUS_SERVICE "com.redhat.dnsconfd" + +typedef enum { CONNECTION_FAIL, CONNECTION_SUCCESS, CONNECTION_WAIT } ConnectionState; + +/*****************************************************************************/ + +static void +dnsconfd_update_done(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + NMDnsDnsconfd *self; + NMDnsDnsconfdPrivate *priv; + gs_free_error GError *error = NULL; + gs_unref_variant GVariant *response = NULL; + gboolean all_ok = FALSE; + char *dnsconfd_message; + + response = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error); + + if (nm_utils_error_is_cancelled(error)) + return; + + self = user_data; + priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->update_cancellable); + + if (!response) + _LOGW("dnsconfd update failed: %s", error->message); + + /* By using &s we will get pointer to char data contained + * in variant and thus no freing of dnsconfd_message is required */ + g_variant_get(response, "(b&s)", &all_ok, &dnsconfd_message); + if (all_ok) { + _LOGT("dnsconfd update successful"); + } else { + _LOGW("dnsconfd update failed: %s", dnsconfd_message); + } + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); +} + +static gboolean +is_default_interface_explicit(const CList *ip_data_lst_head) +{ + guint n_domains; + const char *const *strv_domains; + NMDnsConfigIPData *ip_data; + gboolean is_routing; + + /* if there is ~. specified in either searches or domains then default interface is explicit + * AFAIK it should not be passible to pass "." through DHCP and thus we will check only + * searches */ + + c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) { + strv_domains = + nm_l3_config_data_get_searches(ip_data->l3cd, ip_data->addr_family, &n_domains); + for (int i = 0; i < n_domains; i++) { + if (nm_streq(nm_utils_parse_dns_domain(strv_domains[i], &is_routing), ".")) { + return TRUE; + } + } + } + return FALSE; +} + +static void +gather_interface_domains(NMDnsConfigIPData *ip_data, + gboolean is_default_explicit, + const char ***routing_domains, + const char ***search_domains) +{ + guint n_domains; + const char *const *strv_domains; + gboolean is_routing; + const char *cur_domain; + GPtrArray *routing_ptr_array = g_ptr_array_sized_new(5); + GPtrArray *search_ptr_array = g_ptr_array_sized_new(5); + + /* searches have higher priority than domains (dynamically retrieved) */ + strv_domains = nm_l3_config_data_get_searches(ip_data->l3cd, ip_data->addr_family, &n_domains); + if (!n_domains) { + strv_domains = + nm_l3_config_data_get_domains(ip_data->l3cd, ip_data->addr_family, &n_domains); + } + + for (int i = 0; i < n_domains; i++) { + cur_domain = nm_utils_parse_dns_domain(strv_domains[i], &is_routing); + g_ptr_array_add(routing_ptr_array, (char *) cur_domain); + if (!is_routing) { + g_ptr_array_add(search_ptr_array, (char *) cur_domain); + } + } + + /* if there has been specified search like ~. then we will not be adding . and respect + * users wishes */ + if (!is_default_explicit + && nm_l3_config_data_get_best_default_route(ip_data->l3cd, ip_data->addr_family)) { + g_ptr_array_add(routing_ptr_array, "."); + } + g_ptr_array_add(routing_ptr_array, NULL); + g_ptr_array_add(search_ptr_array, NULL); + + /* when array would be empty we will simply return NULL */ + *routing_domains = + (const char **) g_ptr_array_free(routing_ptr_array, (routing_ptr_array->len == 1)); + *search_domains = + (const char **) g_ptr_array_free(search_ptr_array, (search_ptr_array->len == 1)); +} + +static void +get_networks(NMDnsConfigIPData *ip_data, char ***networks) +{ + NMDedupMultiIter ipconf_iter; + const NMPObject *obj; + char s_address[INET6_ADDRSTRLEN]; + /* +4 because INET6_ADDRSTRLEN already contains byte for end of string and we need 4 bytes + * to store max 3 characters of mask and slash (/128 for example) */ + char network_buffer[INET6_ADDRSTRLEN + 4]; + const NMPlatformIPRoute *route; + GPtrArray *ptr_array = g_ptr_array_sized_new(5); + guint is_ipv4 = NM_IS_IPv4(ip_data->addr_family); + + nm_l3_config_data_iter_obj_for_each (&ipconf_iter, + ip_data->l3cd, + &obj, + NMP_OBJECT_TYPE_IP_ROUTE(is_ipv4)) { + 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; + } + inet_ntop(ip_data->addr_family, route->network_ptr, s_address, INET6_ADDRSTRLEN); + sprintf(network_buffer, "%s/%u", s_address, route->plen); + g_ptr_array_add(ptr_array, g_strdup(network_buffer)); + } + + g_ptr_array_add(ptr_array, NULL); + /* if array would be empty then return NULL */ + *networks = (char **) g_ptr_array_free(ptr_array, ptr_array->len == 1); +} + +static void +server_builder_append_interface_info(GVariantBuilder *argument_builder, + const char *interface, + char **networks, + const char *connection_id, + const char *connection_uuid, + const char *dbus_path) +{ + if (connection_id) { + g_variant_builder_add(argument_builder, + "{sv}", + "connection-id", + g_variant_new("s", connection_id)); + } + if (connection_uuid) { + g_variant_builder_add(argument_builder, + "{sv}", + "connection-uuid", + g_variant_new("s", connection_uuid)); + } + if (dbus_path) { + g_variant_builder_add(argument_builder, + "{sv}", + "connection-object", + g_variant_new("s", dbus_path)); + } + if (interface) { + g_variant_builder_add(argument_builder, "{sv}", "interface", g_variant_new("s", interface)); + } + if (networks) { + g_variant_builder_add(argument_builder, + "{sv}", + "networks", + g_variant_new_strv((const char *const *) networks, -1)); + } + g_variant_builder_close(argument_builder); +} + +static gboolean +server_builder_append_base(GVariantBuilder *argument_builder, + int address_family, + const char *address_string, + const char *const *routing_domains, + const char *const *search_domains, + const char *ca) +{ + NMDnsServer dns_server; + gsize addr_size; + + if (!nm_dns_uri_parse(address_family, address_string, &dns_server)) + return FALSE; + addr_size = nm_utils_addr_family_to_size(dns_server.addr_family); + + g_variant_builder_open(argument_builder, G_VARIANT_TYPE("a{sv}")); + + /* no freeing needed in this section as builder takes ownership of all data */ + + g_variant_builder_add(argument_builder, + "{sv}", + "address", + nm_g_variant_new_ay((gconstpointer) &dns_server.addr, addr_size)); + if (dns_server.scheme == NM_DNS_URI_SCHEME_TLS) + g_variant_builder_add(argument_builder, "{sv}", "protocol", g_variant_new("s", "dns+tls")); + if (dns_server.servername) + g_variant_builder_add(argument_builder, + "{sv}", + "name", + g_variant_new("s", dns_server.servername)); + if (routing_domains) { + g_variant_builder_add(argument_builder, + "{sv}", + "routing_domains", + g_variant_new_strv(routing_domains, -1)); + } + if (search_domains) { + g_variant_builder_add(argument_builder, + "{sv}", + "search_domains", + g_variant_new_strv(search_domains, -1)); + } + if (ca) { + g_variant_builder_add(argument_builder, "{sv}", "ca", g_variant_new("s", ca)); + } + return TRUE; +} + +static void +parse_global_config(const NMGlobalDnsConfig *global_config, + GVariantBuilder *argument_builder, + guint *resolve_mode, + const char **ca) +{ + NMGlobalDnsDomain *domain; + const char *const *servers; + const char *name; + const char *routing_domains[2] = {0}; + const char *const *searches = nm_global_dns_config_get_searches(global_config); + /* ca can be specified only in global config, but if it is, then we must set it for + * all servers the same, because we do not support multiple certification authorities + * (backend limitation) */ + *ca = nm_global_dns_config_get_certification_authority(global_config); + *resolve_mode = nm_global_dns_config_get_resolve_mode(global_config); + + for (int i = 0; i < nm_global_dns_config_get_num_domains(global_config); i++) { + domain = nm_global_dns_config_get_domain(global_config, i); + servers = nm_global_dns_domain_get_servers(domain); + if (!servers) { + continue; + } + name = nm_global_dns_domain_get_name(domain); + routing_domains[0] = nm_streq(name, "*") ? "." : name; + + for (int j = 0; servers[j]; j++) { + if (server_builder_append_base(argument_builder, + AF_UNSPEC, + servers[j], + routing_domains, + searches, + *ca)) { + g_variant_builder_close(argument_builder); + } + } + } +} + +static void +send_dnsconfd_update(NMDnsDnsconfd *self) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + /* it is safe to clear cancellable here even if it is not initialized, + * as g_object_new initializes private part with zeros and nm_clear_g_cancellable + * checks whether the variable != NULL */ + nm_clear_g_cancellable(&priv->update_cancellable); + priv->update_cancellable = g_cancellable_new(); + + g_dbus_connection_call(priv->dbus_connection, + priv->name_owner, + "/com/redhat/dnsconfd", + "com.redhat.dnsconfd.Manager", + "Update", + priv->latest_update_args, + NULL, + G_DBUS_CALL_FLAGS_NONE, + 20000, + priv->update_cancellable, + dnsconfd_update_done, + self); + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); +} + +static void +name_owner_changed(NMDnsDnsconfd *self, const char *name_owner) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + name_owner = nm_str_not_empty(name_owner); + + if (nm_streq0(priv->name_owner, name_owner)) + return; + + g_free(priv->name_owner); + priv->name_owner = g_strdup(name_owner); + + if (!name_owner) { + _LOGD("D-Bus name for dnsconfd disappeared"); + return; + } + + _LOGT("D-Bus name for dnsconfd got owner %s", name_owner); + send_dnsconfd_update(self); + + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); +} + +static void +name_owner_changed_cb(GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + NMDnsDnsconfd *self = user_data; + const char *new_owner; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) + return; + + g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner); + + name_owner_changed(self, new_owner); +} + +static void +get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) +{ + if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + name_owner_changed(user_data, name_owner); +} + +static ConnectionState +ensure_all_connected(NMDnsDnsconfd *self) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + if (!priv->dbus_connection) { + priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); + if (!priv->dbus_connection) { + return CONNECTION_FAIL; + } + } + + if (priv->name_owner) { + return CONNECTION_SUCCESS; + } + + if (!priv->name_owner_changed_id) { + priv->name_owner_changed_id = + nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, + DNSCONFD_DBUS_SERVICE, + name_owner_changed_cb, + self, + NULL); + } + + if (!priv->name_owner_cancellable) { + nm_clear_g_cancellable(&priv->name_owner_cancellable); + priv->name_owner_cancellable = g_cancellable_new(); + + nm_dbus_connection_call_get_name_owner(priv->dbus_connection, + DNSCONFD_DBUS_SERVICE, + -1, + priv->name_owner_cancellable, + get_name_owner_cb, + self); + } + + return CONNECTION_WAIT; +} + +static void +parse_all_interface_config(GVariantBuilder *argument_builder, + const CList *ip_data_lst_head, + const char *ca) +{ + NMDnsConfigIPData *ip_data; + const char *const *dns_server_strings; + guint nameserver_count; + const char *ifname; + NMDevice *device; + NMActiveConnection *active_connection; + NMSettingsConnection *settings_connection; + NMActRequest *act_request; + const char *connection_id; + const char *connection_uuid; + const char *dbus_path; + gboolean explicit_default = is_default_interface_explicit(ip_data_lst_head); + + c_list_for_each_entry (ip_data, ip_data_lst_head, ip_data_lst) { + /* no need to free insides of routing and search domains, as they point to data + * owned elsewhere on the other hand networks are created by us and thus we need to also + * free the data */ + gs_free const char **routing_domains = NULL; + gs_free const char **search_domains = NULL; + gs_strfreev char **networks = NULL; + dns_server_strings = nm_l3_config_data_get_nameservers(ip_data->l3cd, + ip_data->addr_family, + &nameserver_count); + if (!nameserver_count) + continue; + ifname = nm_platform_link_get_name(NM_PLATFORM_GET, ip_data->data->ifindex); + device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, ip_data->data->ifindex); + act_request = nm_device_get_act_request(device); + active_connection = NM_ACTIVE_CONNECTION(act_request); + + /* presume that when we have server of this interface then the interface has to have + * an active connection */ + nm_assert(active_connection); + + settings_connection = nm_active_connection_get_settings_connection(active_connection); + connection_id = nm_settings_connection_get_id(settings_connection); + connection_uuid = nm_settings_connection_get_uuid(settings_connection); + dbus_path = nm_dbus_object_get_path_still_exported(NM_DBUS_OBJECT(act_request)); + + /* dbus_path also should be set */ + nm_assert(dbus_path && dbus_path[0] != 0); + + gather_interface_domains(ip_data, explicit_default, &routing_domains, &search_domains); + get_networks(ip_data, &networks); + + for (int i = 0; i < nameserver_count; i++) { + if (server_builder_append_base(argument_builder, + ip_data->addr_family, + dns_server_strings[i], + routing_domains, + search_domains, + ca)) { + server_builder_append_interface_info(argument_builder, + ifname, + networks, + connection_id, + connection_uuid, + dbus_path); + } + } + } +} + +static gboolean +update(NMDnsPlugin *plugin, + const NMGlobalDnsConfig *global_config, + const CList *ip_data_lst_head, + const char *hostdomain, + GError **error) +{ + NMDnsDnsconfd *self = NM_DNS_DNSCONFD(plugin); + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + GVariantBuilder argument_builder; + GVariant *args; + char *debug_string; + + ConnectionState all_connected; + const char *ca = NULL; + guint resolve_mode = 0; + + 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) { + _LOGT("parsing global configuration"); + parse_global_config(global_config, &argument_builder, &resolve_mode, &ca); + } + _LOGT("parsing configuration of interfaces"); + parse_all_interface_config(&argument_builder, ip_data_lst_head, ca); + + g_variant_builder_close(&argument_builder); + g_variant_builder_add(&argument_builder, "u", resolve_mode); + + args = g_variant_builder_end(&argument_builder); + if (nm_logging_get_level(LOGD_DNS) <= LOGL_TRACE) { + /* knowing how the update looks will be immensely helpful during debugging */ + debug_string = g_variant_print(args, TRUE); + _LOGT("arguments variant is composed like: %s", debug_string); + g_free(debug_string); + } + + nm_clear_pointer(&priv->latest_update_args, g_variant_unref); + priv->latest_update_args = g_variant_ref_sink(args); + + all_connected = ensure_all_connected(self); + + if (all_connected == CONNECTION_FAIL) { + nm_utils_error_set(error, + NM_UTILS_ERROR_UNKNOWN, + "no D-Bus connection available to talk to dnsconfd"); + /* not connected to dbus, can do nothing here */ + return FALSE; + } else if (all_connected == CONNECTION_WAIT) { + /* we do not have name owner yet, and have to wait */ + return TRUE; + } + + send_dnsconfd_update(self); + + return TRUE; +} + +static void +stop(NMDnsPlugin *plugin) +{ + NMDnsDnsconfd *self = NM_DNS_DNSCONFD(plugin); + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + nm_clear_g_cancellable(&priv->update_cancellable); + nm_clear_g_cancellable(&priv->name_owner_cancellable); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); +} + +static gboolean +_update_pending_detect(NMDnsDnsconfd *self) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + if (priv->update_cancellable) { + /* update in progress */ + return TRUE; + } + + return FALSE; +} + +static gboolean +get_update_pending(NMDnsPlugin *plugin) +{ + NMDnsDnsconfd *self = NM_DNS_DNSCONFD(plugin); + return _update_pending_detect(self); +} + +static void +nm_dns_dnsconfd_init(NMDnsDnsconfd *self) +{} + +NMDnsPlugin * +nm_dns_dnsconfd_new(void) +{ + return g_object_new(NM_TYPE_DNS_DNSCONFD, NULL); +} + +static void +dispose(GObject *object) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(NM_DNS_DNSCONFD(object)); + + _LOGT("disposing of Dnsconfd plugin"); + + stop(NM_DNS_PLUGIN(object)); + if (priv->name_owner) { + g_free(priv->name_owner); + } + if (priv->latest_update_args) { + g_variant_unref(priv->latest_update_args); + } + + G_OBJECT_CLASS(nm_dns_dnsconfd_parent_class)->dispose(object); + + g_clear_object(&priv->dbus_connection); +} + +static void +nm_dns_dnsconfd_class_init(NMDnsDnsconfdClass *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 = "dnsconfd"; + plugin_class->is_caching = TRUE; + plugin_class->stop = stop; + plugin_class->update = update; + plugin_class->get_update_pending = get_update_pending; +} diff --git a/src/core/dns/nm-dns-dnsconfd.h b/src/core/dns/nm-dns-dnsconfd.h new file mode 100644 index 0000000000..1f71b5076c --- /dev/null +++ b/src/core/dns/nm-dns-dnsconfd.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#ifndef __NETWORKMANAGER_DNS_DNSCONFD_H__ +#define __NETWORKMANAGER_DNS_DNSCONFD_H__ + +#include "nm-dns-plugin.h" +#include "nm-dns-manager.h" + +#define NM_TYPE_DNS_DNSCONFD (nm_dns_dnsconfd_get_type()) +#define NM_DNS_DNSCONFD(obj) \ + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_DNSCONFD, NMDnsDnsconfd)) +#define NM_DNS_DNSCONFD_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DNS_DNSCONFD, NMDnsDnsconfdClass)) +#define NM_IS_DNS_DNSCONFD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DNS_DNSCONFD)) +#define NM_IS_DNS_DNSCONFD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DNS_DNSCONFD)) +#define NM_DNS_DNSCONFD_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DNS_DNSCONFD, NMDnsDnsconfdClass)) + +typedef struct _NMDnsDnsconfd NMDnsDnsconfd; +typedef struct _NMDnsDnsconfdClass NMDnsDnsconfdClass; + +GType nm_dns_dnsconfd_get_type(void); + +NMDnsPlugin *nm_dns_dnsconfd_new(void); + +#endif /* __NETWORKMANAGER_DNS_DNSCONFD_H__ */ diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index 704d030bbe..d47590dc24 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -32,6 +32,7 @@ #include "nm-config.h" #include "nm-dbus-object.h" #include "nm-dns-dnsmasq.h" +#include "nm-dns-dnsconfd.h" #include "nm-dns-plugin.h" #include "nm-dns-systemd-resolved.h" #include "nm-ip-config.h" @@ -2525,6 +2526,12 @@ again: priv->plugin = nm_dns_dnsmasq_new(); plugin_changed = TRUE; } + } else if (nm_streq0(mode, "dnsconfd")) { + if (force_reload_plugin || !NM_IS_DNS_DNSCONFD(priv->plugin)) { + _clear_plugin(self); + priv->plugin = nm_dns_dnsconfd_new(); + plugin_changed = TRUE; + } } else { if (!NM_IN_STRSET(mode, "none", "default")) { if (mode) { @@ -2541,7 +2548,7 @@ again: if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_AUTO) { rc_manager_was_auto = TRUE; - if (nm_streq(mode, "systemd-resolved")) + if (nm_streq(mode, "systemd-resolved") || nm_streq(mode, "dnsconfd")) rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED; else if (HAS_RESOLVCONF && g_file_test(RESOLVCONF_PATH, G_FILE_TEST_IS_EXECUTABLE)) { /* We detect /sbin/resolvconf only at this stage. That means, if you install diff --git a/src/core/meson.build b/src/core/meson.build index 3167b80ce8..6cf891ee7e 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -123,6 +123,7 @@ libNetworkManager = static_library( 'dhcp/nm-dhcp-dhcpcd.c', 'dhcp/nm-dhcp-listener.c', 'dns/nm-dns-dnsmasq.c', + 'dns/nm-dns-dnsconfd.c', 'dns/nm-dns-manager.c', 'dns/nm-dns-plugin.c', 'dns/nm-dns-systemd-resolved.c', diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index e7c2ec5b8b..3762819358 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -3899,8 +3899,6 @@ out_ip4_address: /*****************************************************************************/ -#define DNS_ROUTES_FWMARK_TABLE_PRIO 20053 - static gboolean _l3cfg_routed_dns_equal(GPtrArray *routes_old, GPtrArray *routes_new) { @@ -3945,7 +3943,7 @@ _l3cfg_routed_dns_get_existing_routes(NML3Cfg *self, int addr_family) const NMPObject *obj = c_list_entry(iter, NMDedupMultiEntry, lst_entries)->obj; if (nm_platform_route_table_uncoerce(obj->ipx_route.rx.table_coerced, FALSE) - != DNS_ROUTES_FWMARK_TABLE_PRIO) + != NM_DNS_ROUTES_FWMARK_TABLE_PRIO) continue; if (!routes) @@ -4000,7 +3998,7 @@ _l3cfg_routed_dns_apply(NML3Cfg *self, const NML3ConfigData *l3cd) r = nm_platform_ip_route_get(self->priv.platform, addr_family, &dns.addr, - DNS_ROUTES_FWMARK_TABLE_PRIO, + NM_DNS_ROUTES_FWMARK_TABLE_PRIO, self->priv.ifindex, &obj); if (r < 0) { @@ -4013,14 +4011,15 @@ _l3cfg_routed_dns_apply(NML3Cfg *self, const NML3ConfigData *l3cd) if (IS_IPv4) { route_new.r4 = (NMPlatformIP4Route) { - .ifindex = self->priv.ifindex, - .network = dns.addr.addr4, - .plen = 32, - .table_any = FALSE, - .metric_any = TRUE, - .table_coerced = nm_platform_route_table_coerce(DNS_ROUTES_FWMARK_TABLE_PRIO), - .gateway = route->r4.gateway, - .rt_source = NM_IP_CONFIG_SOURCE_USER, + .ifindex = self->priv.ifindex, + .network = dns.addr.addr4, + .plen = 32, + .table_any = FALSE, + .metric_any = TRUE, + .table_coerced = + nm_platform_route_table_coerce(NM_DNS_ROUTES_FWMARK_TABLE_PRIO), + .gateway = route->r4.gateway, + .rt_source = NM_IP_CONFIG_SOURCE_USER, }; nm_platform_ip_route_normalize(addr_family, &route_new.rx); @@ -4033,14 +4032,15 @@ _l3cfg_routed_dns_apply(NML3Cfg *self, const NML3ConfigData *l3cd) g_ptr_array_add(new_routes, obj_new); } else { route_new.r6 = (NMPlatformIP6Route) { - .ifindex = self->priv.ifindex, - .network = dns.addr.addr6, - .plen = 128, - .table_any = FALSE, - .metric_any = TRUE, - .table_coerced = nm_platform_route_table_coerce(DNS_ROUTES_FWMARK_TABLE_PRIO), - .gateway = route->r6.gateway, - .rt_source = NM_IP_CONFIG_SOURCE_USER, + .ifindex = self->priv.ifindex, + .network = dns.addr.addr6, + .plen = 128, + .table_any = FALSE, + .metric_any = TRUE, + .table_coerced = + nm_platform_route_table_coerce(NM_DNS_ROUTES_FWMARK_TABLE_PRIO), + .gateway = route->r6.gateway, + .rt_source = NM_IP_CONFIG_SOURCE_USER, }; nm_platform_ip_route_normalize(addr_family, &route_new.rx); @@ -4065,9 +4065,9 @@ _l3cfg_routed_dns_apply(NML3Cfg *self, const NML3ConfigData *l3cd) rule = ((NMPlatformRoutingRule) { .addr_family = addr_family, .flags = FIB_RULE_INVERT, - .priority = DNS_ROUTES_FWMARK_TABLE_PRIO, - .table = DNS_ROUTES_FWMARK_TABLE_PRIO, - .fwmark = DNS_ROUTES_FWMARK_TABLE_PRIO, + .priority = NM_DNS_ROUTES_FWMARK_TABLE_PRIO, + .table = NM_DNS_ROUTES_FWMARK_TABLE_PRIO, + .fwmark = NM_DNS_ROUTES_FWMARK_TABLE_PRIO, .fwmask = 0xffffffff, .action = FR_ACT_TO_TBL, .protocol = RTPROT_STATIC, diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 8581f6c45e..5f0721da3c 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -24,6 +24,8 @@ #define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify" +#define NM_DNS_ROUTES_FWMARK_TABLE_PRIO 20053 + typedef enum _nm_packed { _NM_L3_ACD_DEFEND_TYPE_NONE, NM_L3_ACD_DEFEND_TYPE_NEVER,