diff options
| author | Michael Biebl <biebl@debian.org> | 2025-02-12 13:46:50 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2025-02-12 13:46:50 +0100 |
| commit | 8bdf070ff046f482f6eb5e2b15ebc216f5d1e3da (patch) | |
| tree | f706478d189d54c6532e8863d4b0d5ff5575af60 /src/core/dns | |
| parent | 818258cf34b83fbc754633295e1052d4752d7b15 (diff) | |
New upstream version 1.51.90 upstream/1.51.90
Diffstat (limited to 'src/core/dns')
| -rw-r--r-- | src/core/dns/nm-dns-dnsconfd.c | 802 | ||||
| -rw-r--r-- | src/core/dns/nm-dns-dnsconfd.h | 29 | ||||
| -rw-r--r-- | src/core/dns/nm-dns-dnsmasq.c | 19 | ||||
| -rw-r--r-- | src/core/dns/nm-dns-manager.c | 43 | ||||
| -rw-r--r-- | src/core/dns/nm-dns-systemd-resolved.c | 39 |
5 files changed, 891 insertions, 41 deletions
diff --git a/src/core/dns/nm-dns-dnsconfd.c b/src/core/dns/nm-dns-dnsconfd.c new file mode 100644 index 00000000..b356c2f9 --- /dev/null +++ b/src/core/dns/nm-dns-dnsconfd.c @@ -0,0 +1,802 @@ +/* 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 enum { + DNSCONFD_PLUGIN_IDLE = 0, + DNSCONFD_PLUGIN_WAIT_CONNECT = 1, + DNSCONFD_PLUGIN_WAIT_UPDATE_DONE = 2, + DNSCONFD_PLUGIN_WAIT_SERIAL = 3 +} DnsconfdPluginState; + +typedef struct { + GDBusConnection *dbus_connection; + GCancellable *update_cancellable; + char *name_owner; + guint name_owner_changed_id; + GCancellable *name_owner_cancellable; + GVariant *latest_update_args; + + guint awaited_configuration_serial; + guint present_configuration_serial; + guint properties_changed_id; + GCancellable *serial_cancellable; + + DnsconfdPluginState plugin_state; +} 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_serial_changed(NMDnsDnsconfd *self, guint new_serial) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + priv->present_configuration_serial = new_serial; + if (priv->plugin_state == DNSCONFD_PLUGIN_WAIT_SERIAL + && priv->awaited_configuration_serial == new_serial) { + priv->plugin_state = DNSCONFD_PLUGIN_IDLE; + /* Update finished, serials match */ + _LOGT("serials match, update finished"); + } + + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); +} + +static void +dnsconfd_properties_changed(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; + gs_unref_variant GVariant *updated_properties = NULL; + guint new_serial; + + if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sa{sv}as)"))) { + _LOGW("received properties changed signal but the type is wrong"); + return; + } + + g_variant_get(parameters, "(&s@a{sv}as)", NULL, &updated_properties, NULL); + + if (!g_variant_lookup(updated_properties, "configuration_serial", "u", &new_serial)) { + _LOGT("properties changed but they do not contain new serial"); + return; + } + _LOGT("properties changed and contain new serial %u", new_serial); + + dnsconfd_serial_changed(self, new_serial); +} + +static void +dnsconfd_serial_retrieval_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; + gs_unref_variant GVariant *new_serial_variant = NULL; + guint new_serial; + + 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->serial_cancellable); + + g_variant_get(response, "(v)", &new_serial_variant); + + g_variant_get(new_serial_variant, "u", &new_serial); + + _LOGT("serial retrieval done %u", new_serial); + + dnsconfd_serial_changed(self, new_serial); +} + +static gboolean +subscribe_serial(NMDnsDnsconfd *self) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + priv->properties_changed_id = + g_dbus_connection_signal_subscribe(priv->dbus_connection, + priv->name_owner, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + "/com/redhat/dnsconfd", + "com.redhat.dnsconfd.Manager", + G_DBUS_SIGNAL_FLAGS_NONE, + dnsconfd_properties_changed, + self, + NULL); + if (!priv->properties_changed_id) { + return FALSE; + } + + nm_clear_g_cancellable(&priv->serial_cancellable); + + g_dbus_connection_call( + priv->dbus_connection, + priv->name_owner, + "/com/redhat/dnsconfd", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new("(ss)", "com.redhat.dnsconfd.Manager", "configuration_serial"), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + priv->serial_cancellable, + dnsconfd_serial_retrieval_done, + self); + return TRUE; +} + +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; + guint awaited_serial; + 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, "(u&s)", &awaited_serial, &dnsconfd_message); + + if (!awaited_serial) { + _LOGW("dnsconfd refused update: %s", dnsconfd_message); + priv->plugin_state = DNSCONFD_PLUGIN_IDLE; + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); + return; + } + + priv->awaited_configuration_serial = awaited_serial; + _LOGT("dnsconfd accepted update, awaited serial is %u", awaited_serial); + + if (priv->awaited_configuration_serial == priv->present_configuration_serial) { + /* Serials match, update finished */ + priv->plugin_state = DNSCONFD_PLUGIN_IDLE; + _LOGT("after update serials match"); + } else { + priv->plugin_state = DNSCONFD_PLUGIN_WAIT_SERIAL; + _LOGT("after update serials don't match, waiting"); + } + + _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 any connection's active ipvX.search setting then default + * interface is explicit */ + + 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 (guint i = 0; i < n_domains; i++) { + if (nm_streq(nm_utils_parse_dns_domain(strv_domains[i], &is_routing), ".")) { + return TRUE; + } + } + } + /* AFAIK it should not be passible to pass "." through DHCP and thus we will check only + * searches */ + 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); + int addr_family = ip_data->addr_family; + guint IS_IPv4 = NM_IS_IPv4(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; + } + nm_inet_ntop(addr_family, route->network_ptr, s_address); + nm_sprintf_buf(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); + guint num_domains = nm_global_dns_config_get_num_domains(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 (guint i = 0; i < num_domains; 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 (gsize 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); + + 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); +} + +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"); + if (priv->plugin_state == DNSCONFD_PLUGIN_WAIT_UPDATE_DONE + || priv->plugin_state == DNSCONFD_PLUGIN_WAIT_SERIAL) { + /* We were waiting for either serial or confirmation of update and name + * disappeared, thus we need to retransmit */ + priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT; + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); + } + return; + } + + _LOGT("D-Bus name for dnsconfd got owner %s", name_owner); + + if (!subscribe_serial(self)) { + /* This means that in time between new name and subscribe serial call + * we lost the name again thus wait again */ + priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT; + _LOGT("subscription failed, waiting to connect"); + } else { + priv->plugin_state = DNSCONFD_PLUGIN_WAIT_UPDATE_DONE; + _LOGT("sending update and waiting for its finish"); + 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 (nm_utils_error_is_cancelled(error)) + 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, because if we are parsing this connection then we + * expect it to be active and exported on dbus */ + 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 (guint 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; + + ConnectionState all_connected; + const char *ca = NULL; + guint resolve_mode = 0; + gs_free char *debug_string = NULL; + + 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); + + /* Knowing how the update looks will be immensely helpful during debugging */ + _LOGT("arguments variant is composed like: %s", (debug_string = g_variant_print(args, TRUE))); + + 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); + + /* We need to consider only whether we are connected, because newer update call + * overrides the old one */ + if (all_connected != CONNECTION_SUCCESS) { + priv->plugin_state = DNSCONFD_PLUGIN_WAIT_CONNECT; + _LOGT("not connected, waiting to connect"); + } else { + priv->plugin_state = DNSCONFD_PLUGIN_WAIT_UPDATE_DONE; + _LOGT("connected, waiting for update to finish"); + } + + 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); + + _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(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_cancellable(&priv->serial_cancellable); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); + nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->properties_changed_id); +} + +static gboolean +_update_pending_detect(NMDnsDnsconfd *self) +{ + NMDnsDnsconfdPrivate *priv = NM_DNS_DNSCONFD_GET_PRIVATE(self); + + if (priv->plugin_state == DNSCONFD_PLUGIN_IDLE) { + /* We are waiting for nothing */ + return FALSE; + } + + /* Update in progress */ + return TRUE; +} + +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) { + nm_clear_g_free(&priv->name_owner); + } + if (priv->latest_update_args) { + nm_clear_pointer(&priv->latest_update_args, g_variant_unref); + } + + 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 00000000..1f71b507 --- /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-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c index 53e40f59..8e3c10c9 100644 --- a/src/core/dns/nm-dns-dnsmasq.c +++ b/src/core/dns/nm-dns-dnsmasq.c @@ -263,7 +263,7 @@ handle_kill: PIDFILE); gl_pid.kill_external_data = g_slice_new(GlPidKillExternalData); - *gl_pid.kill_external_data = (GlPidKillExternalData){ + *gl_pid.kill_external_data = (GlPidKillExternalData) { .shutdown_wait_handle = nm_shutdown_wait_obj_register_handle_full( g_strdup_printf("kill-external-dnsmasq-process-%" G_PID_FORMAT, pid), TRUE), @@ -623,7 +623,7 @@ _gl_pid_spawn(const char *dm_binary, nm_assert(notify); nm_assert(G_IS_CANCELLABLE(cancellable)); gl_pid.spawn_data = g_slice_new(GlPidSpawnAsyncData); - *gl_pid.spawn_data = (GlPidSpawnAsyncData){ + *gl_pid.spawn_data = (GlPidSpawnAsyncData) { .dm_binary = dm_binary, .notify = notify, .notify_user_data = notify_user_data, @@ -853,13 +853,16 @@ add_global_config(NMDnsDnsmasq *self, const char *const *servers = nm_global_dns_domain_get_servers(domain); const char *name = nm_global_dns_domain_get_name(domain); - g_return_if_fail(name); + nm_assert(name); for (j = 0; servers && servers[j]; j++) { - if (!strcmp(name, "*")) - add_dnsmasq_nameserver(self, dnsmasq_servers, servers[j], NULL); - else - add_dnsmasq_nameserver(self, dnsmasq_servers, servers[j], name); + char str[NM_INET_ADDRSTRLEN]; + + /* TODO: support IPv6 link-local addresses with scope id */ + if (!nm_dns_uri_parse_plain(AF_UNSPEC, servers[j], str, NULL)) + continue; + + add_dnsmasq_nameserver(self, dnsmasq_servers, str, nm_streq(name, "*") ? NULL : name); } } } @@ -881,7 +884,7 @@ add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsConfigIPD for (i = 0; i < num; i++) { NMIPAddr a; - if (!nm_utils_dnsname_parse_assert(ip_data->addr_family, strarr[i], NULL, &a, NULL)) + if (!nm_dns_uri_parse_plain(ip_data->addr_family, strarr[i], NULL, &a)) continue; ip_addr_to_string(ip_data->addr_family, &a, iface, ip_addr_to_string_buf); diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index 8f87fec1..d47590dc 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" @@ -401,7 +402,7 @@ _dns_config_ip_data_new(NMDnsConfigData *data, nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED); ip_data = g_slice_new(NMDnsConfigIPData); - *ip_data = (NMDnsConfigIPData){ + *ip_data = (NMDnsConfigIPData) { .data = data, .source_tag = source_tag, .l3cd = nm_l3_config_data_ref_and_seal(l3cd), @@ -600,7 +601,7 @@ merge_one_l3cd(NMResolvConfData *rc, int addr_family, int ifindex, const NML3Con for (i = 0; i < num_nameservers; i++) { NMIPAddr a; - if (!nm_utils_dnsname_parse_assert(addr_family, strarr[i], NULL, &a, NULL)) + if (!nm_dns_uri_parse_plain(addr_family, strarr[i], NULL, &a)) continue; if (addr_family == AF_INET) @@ -1291,8 +1292,15 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) if (!servers) return TRUE; - for (i = 0; servers[i]; i++) - add_string_item(rc->nameservers, servers[i], 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; } @@ -1300,7 +1308,6 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf) static const char * get_nameserver_list(int addr_family, const NML3ConfigData *l3cd, NMStrBuf *tmp_strbuf) { - char buf[NM_INET_ADDRSTRLEN]; guint num; guint i; const char *const *strarr; @@ -1309,15 +1316,9 @@ get_nameserver_list(int addr_family, const NML3ConfigData *l3cd, NMStrBuf *tmp_s strarr = nm_l3_config_data_get_nameservers(l3cd, addr_family, &num); for (i = 0; i < num; i++) { - NMIPAddr a; - - if (!nm_utils_dnsname_parse_assert(addr_family, strarr[i], NULL, &a, NULL)) - continue; - - nm_inet_ntop(addr_family, &a, buf); if (i > 0) nm_str_buf_append_c(tmp_strbuf, ' '); - nm_str_buf_append(tmp_strbuf, buf); + nm_str_buf_append(tmp_strbuf, strarr[i]); } nm_str_buf_maybe_expand(tmp_strbuf, 1, FALSE); @@ -2108,7 +2109,7 @@ nm_dns_manager_set_ip_config(NMDnsManager *self, if (!data) { data = g_slice_new(NMDnsConfigData); - *data = (NMDnsConfigData){ + *data = (NMDnsConfigData) { .ifindex = ifindex, .self = self, .data_lst_head = C_LIST_INIT(data->data_lst_head), @@ -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 @@ -2730,7 +2737,6 @@ _get_config_variant(NMDnsManager *self) guint num_domains; guint num_searches; guint i; - char buf[NM_INET_ADDRSTRLEN]; const char *ifname; const char *const *strarr; @@ -2742,12 +2748,7 @@ _get_config_variant(NMDnsManager *self) g_variant_builder_init(&strv_builder, G_VARIANT_TYPE("as")); for (i = 0; i < num; i++) { - NMIPAddr a; - - if (!nm_utils_dnsname_parse_assert(ip_data->addr_family, strarr[i], NULL, &a, NULL)) - continue; - - g_variant_builder_add(&strv_builder, "s", nm_inet_ntop(ip_data->addr_family, &a, buf)); + g_variant_builder_add(&strv_builder, "s", strarr[i]); } g_variant_builder_add(&entry_builder, "{sv}", diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c index 24c7c774..0701a1dc 100644 --- a/src/core/dns/nm-dns-systemd-resolved.c +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -248,7 +248,7 @@ _request_item_append(NMDnsSystemdResolved *self, RequestItem *request_item; request_item = g_slice_new(RequestItem); - *request_item = (RequestItem){ + *request_item = (RequestItem) { .ref_count = 1, .operation = operation, .argument = g_variant_ref_sink(argument), @@ -396,13 +396,24 @@ update_add_ip_config(NMDnsSystemdResolved *self, strarr = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &n); for (i = 0; i < n; i++) { - const char *server_name; - NMIPAddr a; + NMDnsServer dns_server; - if (!nm_utils_dnsname_parse_assert(ip_data->addr_family, strarr[i], NULL, &a, &server_name)) + if (!nm_dns_uri_parse(ip_data->addr_family, strarr[i], &dns_server)) continue; - if (server_name) { + if (!NM_IN_SET(dns_server.scheme, + NM_DNS_URI_SCHEME_TLS, + NM_DNS_URI_SCHEME_NONE, + NM_DNS_URI_SCHEME_UDP)) { + /* In systemd-resolved, the use of DNS-over-TLS can't be controlled + * for each name server; it is controlled via a per-link knob. + * Therefore, we pass all the addresses we know about and then let + * systemd-resolved decide whether to use DoT, based on the + * "connection.dns-over-tls" property. */ + continue; + } + + if (dns_server.servername) { NM_SET_OUT(out_require_dns_ex, TRUE); if (priv->has_set_link_dns_ex == FALSE) { /* The caller won't care about this result anymore. We can skip setting it. */ @@ -413,15 +424,19 @@ update_add_ip_config(NMDnsSystemdResolved *self, if (dns_ex) { g_variant_builder_open(dns_ex, G_VARIANT_TYPE("(iayqs)")); g_variant_builder_add(dns_ex, "i", ip_data->addr_family); - g_variant_builder_add_value(dns_ex, nm_g_variant_new_ay((gconstpointer) &a, addr_size)); + g_variant_builder_add_value( + dns_ex, + nm_g_variant_new_ay((gconstpointer) &dns_server.addr, addr_size)); g_variant_builder_add(dns_ex, "q", 0); - g_variant_builder_add(dns_ex, "s", server_name ?: ""); + g_variant_builder_add(dns_ex, "s", dns_server.servername ?: ""); g_variant_builder_close(dns_ex); } if (dns) { g_variant_builder_open(dns, G_VARIANT_TYPE("(iay)")); g_variant_builder_add(dns, "i", ip_data->addr_family); - g_variant_builder_add_value(dns, nm_g_variant_new_ay((gconstpointer) &a, addr_size)); + g_variant_builder_add_value( + dns, + nm_g_variant_new_ay((gconstpointer) &dns_server.addr, addr_size)); g_variant_builder_close(dns); } has_config = TRUE; @@ -803,7 +818,7 @@ update(NMDnsPlugin *plugin, ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(ifindex)); if (!ic) { ic = g_slice_new(InterfaceConfig); - *ic = (InterfaceConfig){ + *ic = (InterfaceConfig) { .ifindex = ifindex, .ip_data_list = g_ptr_array_sized_new(4), }; @@ -855,7 +870,7 @@ update(NMDnsPlugin *plugin, InterfaceConfig ic; _LOGT("clear previously configured ifindex %d", ifindex); - ic = (InterfaceConfig){ + ic = (InterfaceConfig) { .ifindex = ifindex, .ip_data_list = NULL, }; @@ -1053,7 +1068,7 @@ _resolve_handle_call_cb(GObject *source, GAsyncResult *result, gpointer user_dat NMDnsSystemdResolvedAddressResult *n; n = nm_g_array_append_new(v_names, NMDnsSystemdResolvedAddressResult); - *n = (NMDnsSystemdResolvedAddressResult){ + *n = (NMDnsSystemdResolvedAddressResult) { .name = g_steal_pointer(&v_name), .ifindex = v_ifindex, }; @@ -1167,7 +1182,7 @@ nm_dns_systemd_resolved_resolve_address(NMDnsSystemdResolved nm_assert(callback); handle = g_slice_new(NMDnsSystemdResolvedResolveHandle); - *handle = (NMDnsSystemdResolvedResolveHandle){ + *handle = (NMDnsSystemdResolvedResolveHandle) { .self = self, .timeout_msec = timeout_msec, .callback_user_data = user_data, |