/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2010 Dan Williams * Copyright (C) 2016 Sjoerd Simons */ #include "src/core/nm-default-daemon.h" #include "nm-dns-systemd-resolved.h" #include #include #include #include #include #include #include #include "libnm-glib-aux/nm-c-list.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 "nm-manager.h" #include "nm-l3-config-data.h" #include "nm-setting-connection.h" #include "devices/nm-device.h" #include "NetworkManagerUtils.h" #include "libnm-std-aux/nm-dbus-compat.h" #define SYSTEMD_RESOLVED_DBUS_SERVICE "org.freedesktop.resolve1" #define SYSTEMD_RESOLVED_MANAGER_IFACE "org.freedesktop.resolve1.Manager" #define SYSTEMD_RESOLVED_DBUS_PATH "/org/freedesktop/resolve1" /* define a variable, so that we can compare the operation with pointer equality. */ static const char *const DBUS_OP_SET_LINK_DEFAULT_ROUTE = "SetLinkDefaultRoute"; static const char *const DBUS_OP_SET_LINK_DNS_OVER_TLS = "SetLinkDNSOverTLS"; static const char *const DBUS_OP_SET_LINK_DNS_EX = "SetLinkDNSEx"; static const char *const DBUS_OP_SET_LINK_DNSSEC = "SetLinkDNSSEC"; /*****************************************************************************/ typedef struct { int ifindex; GPtrArray *ip_data_list; } InterfaceConfig; typedef struct { CList request_queue_lst; const char *operation; GVariant *argument; NMDnsSystemdResolved *self; int ifindex; int ref_count; } RequestItem; struct _NMDnsSystemdResolvedResolveHandle { CList handle_lst; NMDnsSystemdResolved *self; GSource *timeout_source; GCancellable *handle_cancellable; gpointer callback_user_data; guint timeout_msec; bool is_failing_on_idle; union { struct { NMDnsSystemdResolvedResolveAddressCallback callback; guint64 flags; int ifindex; int addr_family; NMIPAddr addr; } r_address; }; }; /*****************************************************************************/ typedef struct { GDBusConnection *dbus_connection; GHashTable *dirty_interfaces; GCancellable *cancellable; GCancellable *service_start_cancellable; CList request_queue_lst_head; char *dbus_owner; CList handle_lst_head; guint name_owner_changed_id; guint n_pending; bool send_updates_warn_ratelimited : 1; bool try_start_blocked : 1; bool stopped : 1; bool dbus_initied : 1; bool send_updates_waiting : 1; bool update_pending : 1; /* Detect support for the respective D-Bus API. */ NMTernary has_set_link_default_route : 3; NMTernary has_set_link_dns_over_tls : 3; NMTernary has_set_link_dns_ex : 3; } NMDnsSystemdResolvedPrivate; struct _NMDnsSystemdResolved { NMDnsPlugin parent; NMDnsSystemdResolvedPrivate _priv; }; struct _NMDnsSystemdResolvedClass { NMDnsPluginClass parent; }; G_DEFINE_TYPE(NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN) #define NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self) \ _NM_GET_PRIVATE(self, NMDnsSystemdResolved, NM_IS_DNS_SYSTEMD_RESOLVED, NMDnsPlugin) /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_DNS #define _NMLOG_PREFIX_NAME "dns-sd-resolved" #define _NMLOG(level, ...) \ __NMLOG_DEFAULT_WITH_ADDR(level, _NMLOG_DOMAIN, _NMLOG_PREFIX_NAME, __VA_ARGS__) #define _NMLOG2(level, handle, ...) \ G_STMT_START \ { \ const NMLogLevel _level = (level); \ \ if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) { \ const NMDnsSystemdResolvedResolveHandle *const _handle = (handle); \ \ _nm_log(_level, \ (_NMLOG_DOMAIN), \ 0, \ NULL, \ NULL, \ "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]: request[" NM_HASH_OBFUSCATE_PTR_FMT \ "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ NM_HASH_OBFUSCATE_PTR(self), \ NM_HASH_OBFUSCATE_PTR(_handle) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ } \ G_STMT_END /*****************************************************************************/ static void _resolve_complete_error(NMDnsSystemdResolvedResolveHandle *handle, GError *error); static void _resolve_start(NMDnsSystemdResolved *self, NMDnsSystemdResolvedResolveHandle *handle); static void send_updates(NMDnsSystemdResolved *self); /*****************************************************************************/ static gboolean _update_pending_detect(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); if (priv->n_pending > 0) { /* we have pending calls. We definitely want to wait for them to complete. */ return TRUE; } if (!priv->dbus_initied) { if (!priv->dbus_connection) return FALSE; /* D-Bus not yet initialized (and we don't know the name owner yet). Pending. */ return TRUE; } if (priv->service_start_cancellable) { /* We are waiting to D-Bus activate resolved. Pending. */ return TRUE; } if (priv->try_start_blocked) { /* We earlier tried to start resolved, but are rate limited. We are not pending an update * (that we expect to complete any time soon). */ return FALSE; } if (priv->send_updates_waiting) { /* we wait to send updates. We are pending. */ return TRUE; } return FALSE; } static void _update_pending_maybe_changed(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); gboolean update_pending; /* Important: we need to make sure that we call _update_pending_maybe_changed(), when * the state changes. */ update_pending = _update_pending_detect(self); if (priv->update_pending != update_pending) { priv->update_pending = update_pending; _nm_dns_plugin_update_pending_maybe_changed(NM_DNS_PLUGIN(self)); } } static gboolean get_update_pending(NMDnsPlugin *plugin) { NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin); NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); nm_assert(priv->update_pending == _update_pending_detect(self)); return priv->update_pending; } /*****************************************************************************/ static RequestItem * _request_item_ref(RequestItem *request_item) { nm_assert(request_item); nm_assert(request_item->ref_count > 0); nm_assert(request_item->ref_count < G_MAXINT); nm_assert(!c_list_is_empty(&request_item->request_queue_lst)); request_item->ref_count++; return request_item; } static void _request_item_unref(RequestItem *request_item) { nm_assert(request_item); nm_assert(request_item->ref_count > 0); if (--request_item->ref_count > 0) return; nm_assert(c_list_is_empty(&request_item->request_queue_lst)); g_variant_unref(request_item->argument); nm_g_slice_free(request_item); } static void _request_item_append(NMDnsSystemdResolved *self, const char *operation, int ifindex, GVariant *argument) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); RequestItem *request_item; request_item = g_slice_new(RequestItem); *request_item = (RequestItem) { .ref_count = 1, .operation = operation, .argument = g_variant_ref_sink(argument), .self = self, .ifindex = ifindex, }; c_list_link_tail(&priv->request_queue_lst_head, &request_item->request_queue_lst); } /*****************************************************************************/ static void _interface_config_free(InterfaceConfig *config) { nm_g_ptr_array_unref(config->ip_data_list); nm_g_slice_free(config); } static void call_done(GObject *source, GAsyncResult *r, gpointer user_data) { gs_unref_variant GVariant *v = NULL; gs_free_error GError *error = NULL; NMDnsSystemdResolved *self; NMDnsSystemdResolvedPrivate *priv; RequestItem *request_item; NMLogLevel log_level; const char *operation; int ifindex; gboolean reconfigure = FALSE; request_item = user_data; self = request_item->self; operation = request_item->operation; ifindex = request_item->ifindex; _request_item_unref(request_item); priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), r, &error); if (nm_utils_error_is_cancelled(error)) goto out_dec_pending; if (v) { if (operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) { if (priv->has_set_link_default_route == NM_TERNARY_DEFAULT) { priv->has_set_link_default_route = NM_TERNARY_TRUE; _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported"); } } else if (operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) { if (priv->has_set_link_dns_over_tls == NM_TERNARY_DEFAULT) { priv->has_set_link_dns_over_tls = NM_TERNARY_TRUE; _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API supported"); } } else if (operation == DBUS_OP_SET_LINK_DNS_EX) { if (priv->has_set_link_dns_ex == NM_TERNARY_DEFAULT) { priv->has_set_link_dns_ex = NM_TERNARY_TRUE; _LOGD("systemd-resolved support for SetLinkDNSEx(): API supported"); } } priv->send_updates_warn_ratelimited = FALSE; goto out_dec_pending; } if (nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { if (operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) { if (priv->has_set_link_default_route == NM_TERNARY_DEFAULT) { priv->has_set_link_default_route = NM_TERNARY_FALSE; _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported"); } } else if (operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) { if (priv->has_set_link_dns_over_tls == NM_TERNARY_DEFAULT) { priv->has_set_link_dns_over_tls = NM_TERNARY_FALSE; _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API not supported"); } } else if (operation == DBUS_OP_SET_LINK_DNS_EX) { if (priv->has_set_link_dns_ex == NM_TERNARY_DEFAULT) { priv->has_set_link_dns_ex = NM_TERNARY_FALSE; _LOGD("systemd-resolved support for SetLinkDNSEx(): API not supported"); _LOGW("systemd-resolved does not support SetLinkDNSEx API (v246). " "Cannot set DoT server name (SNI)"); /* We need to reconfigure with the SetLinkDNS fallback. * * In the other cases above, there is no need to reconfigure anything. * We won't retry SetLinkDefaultRoute/SetLinkDNSOverTLS anymore, but there * is nothing else we can do about that. */ reconfigure = TRUE; } } goto out_dec_pending; } log_level = LOGL_DEBUG; if (!priv->send_updates_warn_ratelimited) { priv->send_updates_warn_ratelimited = TRUE; log_level = LOGL_WARN; } _NMLOG(log_level, "send-updates %s@%d failed: %s", operation, ifindex, error->message); out_dec_pending: nm_assert(priv->n_pending > 0); if (--priv->n_pending <= 0) { _update_pending_maybe_changed(self); /* We keep @self alive while pending operations are in progress. It's simpler * to implement. But this requires that we implement "stop()" signal to cancel * all pending requests. Cancelling is necessary, because during shutdown, * we must wrap up fast, and not hang an undefined amount time. */ g_object_unref(self); } if (reconfigure) { priv->send_updates_waiting = TRUE; send_updates(self); } } static gboolean update_add_ip_config(NMDnsSystemdResolved *self, const NMDnsConfigIPData *ip_data, GVariantBuilder *dns, GVariantBuilder *dns_ex, GVariantBuilder *domains, gboolean *out_require_dns_ex) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); gsize addr_size; guint n; guint i; gboolean is_routing; const char *domain; gboolean has_config = FALSE; const char *const *strarr; addr_size = nm_utils_addr_family_to_size(ip_data->addr_family); if ((!ip_data->domains.search || !ip_data->domains.search[0]) && !ip_data->domains.has_default_route_exclusive && !ip_data->domains.has_default_route) { /* we have no search domain (which systemd-resolved uses to routing the request), but * also the "DefaultRoute" is not set on the interface. This setting has no effect and * gets ignored. */ return FALSE; } strarr = nm_l3_config_data_get_nameservers(ip_data->l3cd, ip_data->addr_family, &n); for (i = 0; i < n; i++) { NMDnsServer dns_server; if (!nm_dns_uri_parse(ip_data->addr_family, strarr[i], &dns_server, NULL)) continue; 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. */ dns = NULL; } } 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) &dns_server.addr, addr_size)); g_variant_builder_add(dns_ex, "q", 0); 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) &dns_server.addr, addr_size)); g_variant_builder_close(dns); } has_config = TRUE; } if (!has_config || domains) { if (!ip_data->domains.has_default_route_explicit && ip_data->domains.has_default_route_exclusive) { g_variant_builder_add(domains, "(sb)", ".", TRUE); has_config = TRUE; } if (ip_data->domains.search) { for (i = 0; ip_data->domains.search[i]; i++) { domain = nm_utils_parse_dns_domain(ip_data->domains.search[i], &is_routing); g_variant_builder_add(domains, "(sb)", domain[0] ? domain : ".", is_routing); has_config = TRUE; } } } return has_config; } static void free_pending_updates(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); RequestItem *request_item; while ( (request_item = c_list_first_entry(&priv->request_queue_lst_head, RequestItem, request_queue_lst))) { c_list_unlink(&request_item->request_queue_lst); _request_item_unref(request_item); } } static gboolean prepare_one_interface(NMDnsSystemdResolved *self, const InterfaceConfig *ic) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); GVariantBuilder dns; GVariantBuilder dns_ex; GVariantBuilder domains; NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT; NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT; NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT; NMSettingConnectionDnssec dnssec = NM_SETTING_CONNECTION_DNSSEC_DEFAULT; const char *mdns_arg = NULL; const char *llmnr_arg = NULL; const char *dns_over_tls_arg = NULL; const char *dnssec_arg = NULL; gboolean has_config = FALSE; gboolean has_default_route = FALSE; guint i; gboolean require_dns_ex = FALSE; nm_assert(ic->ifindex != NM_LOOPBACK_IFINDEX); g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))")); g_variant_builder_add(&dns, "i", ic->ifindex); g_variant_builder_open(&dns, G_VARIANT_TYPE("a(iay)")); g_variant_builder_init(&domains, G_VARIANT_TYPE("(ia(sb))")); g_variant_builder_add(&domains, "i", ic->ifindex); g_variant_builder_open(&domains, G_VARIANT_TYPE("a(sb)")); if (ic->ip_data_list) { for (i = 0; i < ic->ip_data_list->len; i++) { const NMDnsConfigIPData *ip_data = ic->ip_data_list->pdata[i]; if (update_add_ip_config(self, ip_data, &dns, NULL, &domains, &require_dns_ex)) has_config = TRUE; if (ip_data->domains.has_default_route) has_default_route = TRUE; if (NM_IS_IPv4(ip_data->addr_family)) { mdns = NM_MAX(mdns, nm_l3_config_data_get_mdns(ip_data->l3cd)); llmnr = NM_MAX(llmnr, nm_l3_config_data_get_llmnr(ip_data->l3cd)); dns_over_tls = NM_MAX(dns_over_tls, nm_l3_config_data_get_dns_over_tls(ip_data->l3cd)); dnssec = NM_MAX(dnssec, nm_l3_config_data_get_dnssec(ip_data->l3cd)); } } } g_variant_builder_close(&dns); g_variant_builder_close(&domains); if (!require_dns_ex) { /* No need to use the new API. SetLinkDNS() is sufficient. */ } else if (!priv->has_set_link_dns_ex) { /* API to set server name is not supported. Nothing we can do. */ require_dns_ex = FALSE; } else { g_variant_builder_init(&dns_ex, G_VARIANT_TYPE("(ia(iayqs))")); g_variant_builder_add(&dns_ex, "i", ic->ifindex); g_variant_builder_open(&dns_ex, G_VARIANT_TYPE("a(iayqs)")); for (i = 0; i < ic->ip_data_list->len; i++) { const NMDnsConfigIPData *ip_data = ic->ip_data_list->pdata[i]; update_add_ip_config(self, ip_data, NULL, &dns_ex, NULL, NULL); } g_variant_builder_close(&dns_ex); } switch (mdns) { case NM_SETTING_CONNECTION_MDNS_NO: mdns_arg = "no"; break; case NM_SETTING_CONNECTION_MDNS_RESOLVE: mdns_arg = "resolve"; break; case NM_SETTING_CONNECTION_MDNS_YES: mdns_arg = "yes"; break; case NM_SETTING_CONNECTION_MDNS_DEFAULT: mdns_arg = ""; break; } nm_assert(mdns_arg); switch (llmnr) { case NM_SETTING_CONNECTION_LLMNR_NO: llmnr_arg = "no"; break; case NM_SETTING_CONNECTION_LLMNR_RESOLVE: llmnr_arg = "resolve"; break; case NM_SETTING_CONNECTION_LLMNR_YES: llmnr_arg = "yes"; break; case NM_SETTING_CONNECTION_LLMNR_DEFAULT: llmnr_arg = ""; break; } nm_assert(llmnr_arg); switch (dns_over_tls) { case NM_SETTING_CONNECTION_DNS_OVER_TLS_NO: dns_over_tls_arg = "no"; break; case NM_SETTING_CONNECTION_DNS_OVER_TLS_OPPORTUNISTIC: dns_over_tls_arg = "opportunistic"; break; case NM_SETTING_CONNECTION_DNS_OVER_TLS_YES: dns_over_tls_arg = "yes"; break; case NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT: dns_over_tls_arg = ""; break; } nm_assert(dns_over_tls_arg); switch (dnssec) { case NM_SETTING_CONNECTION_DNSSEC_NO: dnssec_arg = "no"; break; case NM_SETTING_CONNECTION_DNSSEC_ALLOW_DOWNGRADE: dnssec_arg = "allow-downgrade"; break; case NM_SETTING_CONNECTION_DNSSEC_YES: dnssec_arg = "yes"; break; case NM_SETTING_CONNECTION_DNSSEC_DEFAULT: dnssec_arg = ""; break; } nm_assert(dnssec_arg); if (!nm_str_is_empty(mdns_arg) || !nm_str_is_empty(llmnr_arg) || !nm_str_is_empty(dns_over_tls_arg) || !nm_str_is_empty(dnssec_arg)) has_config = TRUE; _request_item_append(self, "SetLinkDomains", ic->ifindex, g_variant_builder_end(&domains)); _request_item_append(self, DBUS_OP_SET_LINK_DEFAULT_ROUTE, ic->ifindex, g_variant_new("(ib)", ic->ifindex, has_default_route)); _request_item_append(self, "SetLinkMulticastDNS", ic->ifindex, g_variant_new("(is)", ic->ifindex, mdns_arg ?: "")); _request_item_append(self, "SetLinkLLMNR", ic->ifindex, g_variant_new("(is)", ic->ifindex, llmnr_arg ?: "")); if (require_dns_ex) { _request_item_append(self, DBUS_OP_SET_LINK_DNS_EX, ic->ifindex, g_variant_builder_end(&dns_ex)); g_variant_builder_clear(&dns); } else _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns)); _request_item_append(self, DBUS_OP_SET_LINK_DNS_OVER_TLS, ic->ifindex, g_variant_new("(is)", ic->ifindex, dns_over_tls_arg ?: "")); _request_item_append(self, DBUS_OP_SET_LINK_DNSSEC, ic->ifindex, g_variant_new("(is)", ic->ifindex, dnssec_arg ?: "")); return has_config; } static void start_resolved_cb(GObject *source, GAsyncResult *result, gpointer user_data) { gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; NMDnsSystemdResolved *self; NMDnsSystemdResolvedPrivate *priv; NMDnsSystemdResolvedResolveHandle *handle; res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; self = user_data; priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); nm_clear_g_cancellable(&priv->service_start_cancellable); if (!res) { g_dbus_error_strip_remote_error(error); _LOGD("error activating systemd-resolved: %s", error->message); again: c_list_for_each_entry (handle, &priv->handle_lst_head, handle_lst) { gs_free_error GError *local = NULL; if (handle->is_failing_on_idle) continue; nm_utils_error_set(&local, NM_UTILS_ERROR_NOT_READY, "error activating systemd-resolved: %s", error->message); _resolve_complete_error(handle, local); goto again; } } else _LOGD("systemd-resolved successfully started"); _update_pending_maybe_changed(self); } static NMTernary ensure_resolved_running(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); if (priv->stopped) return NM_TERNARY_FALSE; if (!priv->dbus_initied) return NM_TERNARY_DEFAULT; if (!priv->dbus_owner) { if (priv->try_start_blocked) { /* we have no name owner and we already tried poking the service to * autostart. */ return NM_TERNARY_FALSE; } _LOGT("try D-Bus activating systemd-resolved..."); priv->try_start_blocked = TRUE; nm_clear_g_cancellable(&priv->service_start_cancellable); priv->service_start_cancellable = g_cancellable_new(); nm_dbus_connection_call_start_service_by_name(priv->dbus_connection, SYSTEMD_RESOLVED_DBUS_SERVICE, 4000, priv->service_start_cancellable, start_resolved_cb, self); _update_pending_maybe_changed(self); return NM_TERNARY_DEFAULT; } return NM_TERNARY_TRUE; } static void send_updates(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); RequestItem *request_item; NMDnsSystemdResolvedResolveHandle *handle; if (!priv->send_updates_waiting) { /* nothing to do. */ return; } if (ensure_resolved_running(self) != NM_TERNARY_TRUE) return; nm_clear_g_cancellable(&priv->cancellable); if (c_list_is_empty(&priv->request_queue_lst_head)) { _LOGT("send-updates: no requests to send"); priv->send_updates_waiting = FALSE; goto start_resolve; } priv->cancellable = g_cancellable_new(); priv->send_updates_waiting = FALSE; _LOGT("send-updates: start %zu requests", c_list_length(&priv->request_queue_lst_head)); c_list_for_each_entry (request_item, &priv->request_queue_lst_head, request_queue_lst) { gs_free char *ss = NULL; if ((request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE && priv->has_set_link_default_route == NM_TERNARY_FALSE) || (request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS && priv->has_set_link_dns_over_tls == NM_TERNARY_FALSE)) { /* The "SetLinkDefaultRoute" API is only supported since v240. * The "SetLinkDNSOverTLS" API is only supported since v239. * We detected whether they are supported, and skip the calls. There * is no special workaround, because in this case we rely on systemd-resolved * to do the right thing automatically. */ continue; } _LOGT("send-updates: %s ( %s )", request_item->operation, (ss = g_variant_print(request_item->argument, FALSE))); if (priv->n_pending++ == 0) { /* We are inside send_updates(). All callers are already calling * _update_pending_maybe_changed() afterwards. */ g_object_ref(self); } g_dbus_connection_call(priv->dbus_connection, priv->dbus_owner, SYSTEMD_RESOLVED_DBUS_PATH, SYSTEMD_RESOLVED_MANAGER_IFACE, request_item->operation, request_item->argument, NULL, G_DBUS_CALL_FLAGS_NONE, -1, priv->cancellable, call_done, _request_item_ref(request_item)); } start_resolve: c_list_for_each_entry (handle, &priv->handle_lst_head, handle_lst) { if (handle->handle_cancellable) continue; if (handle->is_failing_on_idle) continue; _resolve_start(self, handle); } } static gboolean update(NMDnsPlugin *plugin, const NMGlobalDnsConfig *global_config, const CList *ip_data_lst_head, const char *hostdomain, GError **error) { NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin); NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); gs_unref_hashtable GHashTable *interfaces = NULL; const NMUtilsNamedValue *interfaces_arr; NMUtilsNamedValue interfaces_arr_stack[50]; gs_free NMUtilsNamedValue *interfaces_arr_heap = NULL; guint interfaces_len; gpointer pointer; NMDnsConfigIPData *ip_data; GHashTableIter iter; gs_unref_array GArray *dirty_array = NULL; guint i; nm_assert(!priv->stopped); /* Group configs by ifindex/interfaces. */ 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) { InterfaceConfig *ic = NULL; int ifindex = ip_data->data->ifindex; nm_assert(ifindex == nm_l3_config_data_get_ifindex(ip_data->l3cd)); if (ifindex == NM_LOOPBACK_IFINDEX) { /* systemd-resolved API is per-link, and loopback is not supported. * Unclear what to do about DNS configuration on loopback. Just skip * it here. */ continue; } ic = g_hash_table_lookup(interfaces, GINT_TO_POINTER(ifindex)); if (!ic) { ic = g_slice_new(InterfaceConfig); *ic = (InterfaceConfig) { .ifindex = ifindex, .ip_data_list = g_ptr_array_sized_new(4), }; g_hash_table_insert(interfaces, GINT_TO_POINTER(ifindex), ic); } g_ptr_array_add(ic->ip_data_list, ip_data); } free_pending_updates(self); interfaces_arr = nm_utils_hash_to_array_with_buffer(interfaces, &interfaces_len, nm_cmp_int2ptr_p_with_data, NULL, interfaces_arr_stack, &interfaces_arr_heap); for (i = 0; i < interfaces_len; i++) { const InterfaceConfig *ic = interfaces_arr[i].value_ptr; if (prepare_one_interface(self, ic)) g_hash_table_add(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex)); else g_hash_table_remove(priv->dirty_interfaces, GINT_TO_POINTER(ic->ifindex)); } /* If we previously configured an ifindex with non-empty values in * resolved, and the current update doesn't contain that interface, * reset the resolved configuration for that ifindex. */ g_hash_table_iter_init(&iter, priv->dirty_interfaces); while (g_hash_table_iter_next(&iter, &pointer, NULL)) { int ifindex = GPOINTER_TO_INT(pointer); if (g_hash_table_contains(interfaces, GINT_TO_POINTER(ifindex))) { /* the interface is still tracked and still dirty. Keep. */ continue; } if (!dirty_array) dirty_array = g_array_new(FALSE, FALSE, sizeof(int)); g_array_append_val(dirty_array, ifindex); g_hash_table_iter_remove(&iter); } if (dirty_array) { g_array_sort_with_data(dirty_array, nm_cmp_int2ptr_p_with_data, NULL); for (i = 0; i < dirty_array->len; i++) { int ifindex = nm_g_array_index(dirty_array, int, i); InterfaceConfig ic; _LOGT("clear previously configured ifindex %d", ifindex); ic = (InterfaceConfig) { .ifindex = ifindex, .ip_data_list = NULL, }; prepare_one_interface(self, &ic); } } priv->send_updates_waiting = TRUE; send_updates(self); _update_pending_maybe_changed(self); return TRUE; } /*****************************************************************************/ static void name_owner_changed(NMDnsSystemdResolved *self, const char *owner) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); owner = nm_str_not_empty(owner); if (!owner) _LOGT("D-Bus name for systemd-resolved has no owner"); else _LOGT("D-Bus name for systemd-resolved has owner %s", owner); nm_clear_g_cancellable(&priv->service_start_cancellable); nm_strdup_reset(&priv->dbus_owner, owner); if (owner) { priv->try_start_blocked = FALSE; priv->send_updates_waiting = TRUE; } else { priv->has_set_link_default_route = NM_TERNARY_DEFAULT; priv->has_set_link_dns_over_tls = NM_TERNARY_DEFAULT; priv->has_set_link_dns_ex = NM_TERNARY_DEFAULT; } send_updates(self); _update_pending_maybe_changed(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) { NMDnsSystemdResolved *self = user_data; NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); 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); if (!priv->dbus_initied) { /* There was a race and we got a NameOwnerChanged signal before GetNameOwner * returns. */ priv->dbus_initied = TRUE; nm_clear_g_cancellable(&priv->cancellable); _LOGT("D-Bus connection is ready"); } name_owner_changed(user_data, new_owner); } static void get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) { NMDnsSystemdResolved *self; NMDnsSystemdResolvedPrivate *priv; if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; self = user_data; priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); g_clear_object(&priv->cancellable); priv->dbus_initied = TRUE; _LOGT("D-Bus connection is ready"); name_owner_changed(self, name_owner); } /*****************************************************************************/ gboolean nm_dns_systemd_resolved_is_running(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv; g_return_val_if_fail(NM_IS_DNS_SYSTEMD_RESOLVED(self), FALSE); priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); return priv->dbus_initied && (priv->dbus_owner || !priv->try_start_blocked); } /*****************************************************************************/ static void _resolve_complete(NMDnsSystemdResolvedResolveHandle *handle, const NMDnsSystemdResolvedAddressResult *names, guint names_len, guint64 flags, GError *error) { NMDnsSystemdResolved *self; NMDnsSystemdResolvedPrivate *priv; g_return_if_fail(handle && NM_IS_DNS_SYSTEMD_RESOLVED(handle->self)); self = handle->self; priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); nm_assert(c_list_contains(&priv->handle_lst_head, &handle->handle_lst)); c_list_unlink(&handle->handle_lst); handle->self = NULL; nm_clear_g_source_inst(&handle->timeout_source); nm_clear_g_cancellable(&handle->handle_cancellable); handle->r_address .callback(self, handle, names, names_len, flags, error, handle->callback_user_data); nm_g_slice_free(handle); } static void _resolve_complete_error(NMDnsSystemdResolvedResolveHandle *handle, GError *error) { NMDnsSystemdResolved *self = handle->self; nm_assert(error); _LOG2T(handle, "request failed: %s", error->message); _resolve_complete(handle, NULL, 0, 0, error); } static void _resolve_handle_call_cb(GObject *source, GAsyncResult *result, gpointer user_data) { gs_unref_variant GVariant *v = NULL; gs_free_error GError *error = NULL; NMDnsSystemdResolvedResolveHandle *handle; NMDnsSystemdResolved *self; GVariantIter *v_names_iter; guint64 v_flags; int v_ifindex; char *v_name; gs_unref_array GArray *v_names = NULL; gs_free char *ss = NULL; v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); if (nm_utils_error_is_cancelled(error)) return; handle = user_data; self = handle->self; if (error) { gs_free char *remote_error = NULL; remote_error = g_dbus_error_get_remote_error(error); if (nm_streq0(remote_error, "org.freedesktop.DBus.Error.ServiceUnknown")) { _LOG2T(handle, "request failed due to service stop. Retry"); g_clear_object(&handle->handle_cancellable); _resolve_start(self, handle); return; } _resolve_complete_error(handle, error); return; } _LOG2T(handle, "request completed: %s", (ss = g_variant_print(v, FALSE))); v_names = g_array_new(FALSE, FALSE, sizeof(NMDnsSystemdResolvedAddressResult)); G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMDnsSystemdResolvedAddressResult, name) == 0); g_array_set_clear_func(v_names, nm_indirect_g_free); g_variant_get(v, "(a(is)t)", &v_names_iter, &v_flags); while (g_variant_iter_next(v_names_iter, "(is)", &v_ifindex, &v_name)) { NMDnsSystemdResolvedAddressResult *n; n = nm_g_array_append_new(v_names, NMDnsSystemdResolvedAddressResult); *n = (NMDnsSystemdResolvedAddressResult) { .name = g_steal_pointer(&v_name), .ifindex = v_ifindex, }; } g_variant_iter_free(v_names_iter); _resolve_complete(handle, nm_g_array_first_p(v_names, NMDnsSystemdResolvedAddressResult), v_names->len, v_flags, NULL); } static gboolean _resolve_failing_on_idle(gpointer user_data) { NMDnsSystemdResolvedResolveHandle *handle = user_data; gs_free_error GError *error = NULL; nm_utils_error_set_literal(&error, NM_UTILS_ERROR_NOT_READY, "systemd-resolved is not available"); _resolve_complete_error(handle, error); return G_SOURCE_CONTINUE; } static gboolean _resolve_handle_timeout(gpointer user_data) { NMDnsSystemdResolvedResolveHandle *handle = user_data; gs_free_error GError *error = NULL; nm_utils_error_set_literal(&error, NM_UTILS_ERROR_UNKNOWN, "timeout for request"); _resolve_complete_error(handle, error); return G_SOURCE_CONTINUE; } static void _resolve_start(NMDnsSystemdResolved *self, NMDnsSystemdResolvedResolveHandle *handle) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); NMTernary is_running; nm_assert(!handle->handle_cancellable); nm_assert(!handle->is_failing_on_idle); is_running = ensure_resolved_running(self); if (is_running == NM_TERNARY_FALSE) { /* Systemd-resolved is not is_running and shall not be used. We fail * on an idle handler. */ _LOG2T(handle, "systemd-resolved not running. Failing on idle..."); nm_assert(!handle->timeout_source); handle->is_failing_on_idle = TRUE; handle->timeout_source = nm_g_idle_add_source(_resolve_failing_on_idle, handle); return; } if (!handle->timeout_source) { handle->timeout_source = nm_g_timeout_add_source(handle->timeout_msec, _resolve_handle_timeout, handle); } if (is_running == NM_TERNARY_DEFAULT) { /* we are D-Bus activating systemd-resolved. Wait for it... */ _LOG2T(handle, "waiting for systemd-resolved to start..."); return; } nm_assert(!priv->send_updates_waiting); handle->handle_cancellable = g_cancellable_new(); _LOG2T(handle, "start D-Bus request..."); g_dbus_connection_call(priv->dbus_connection, priv->dbus_owner, SYSTEMD_RESOLVED_DBUS_PATH, SYSTEMD_RESOLVED_MANAGER_IFACE, "ResolveAddress", g_variant_new("(ii@ayt)", handle->r_address.ifindex, handle->r_address.addr_family, nm_g_variant_new_ay_inaddr(handle->r_address.addr_family, &handle->r_address.addr), handle->r_address.flags), G_VARIANT_TYPE("(a(is)t)"), G_DBUS_CALL_FLAGS_NONE, handle->timeout_msec + 1000u, handle->handle_cancellable, _resolve_handle_call_cb, handle); } NMDnsSystemdResolvedResolveHandle * nm_dns_systemd_resolved_resolve_address(NMDnsSystemdResolved *self, int ifindex, int addr_family, const NMIPAddr *addr, guint64 flags, guint timeout_msec, NMDnsSystemdResolvedResolveAddressCallback callback, gpointer user_data) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); NMDnsSystemdResolvedResolveHandle *handle; char addr_str[NM_INET_ADDRSTRLEN]; g_return_val_if_fail(NM_IS_DNS_SYSTEMD_RESOLVED(self), NULL); nm_assert_addr_family(addr_family); nm_assert(addr); nm_assert(callback); handle = g_slice_new(NMDnsSystemdResolvedResolveHandle); *handle = (NMDnsSystemdResolvedResolveHandle) { .self = self, .timeout_msec = timeout_msec, .callback_user_data = user_data, .r_address = { .ifindex = ifindex, .addr_family = addr_family, .addr = *addr, .flags = flags, .callback = callback, }, }; c_list_link_tail(&priv->handle_lst_head, &handle->handle_lst); _LOG2T(handle, "resolve-address(ifindex=%d, %s, flags=%" G_GINT64_MODIFIER "x): new request", handle->r_address.ifindex, nm_inet_ntop(handle->r_address.addr_family, &handle->r_address.addr, addr_str), handle->r_address.flags); _resolve_start(self, handle); return handle; } void nm_dns_systemd_resolved_resolve_cancel(NMDnsSystemdResolvedResolveHandle *handle) { gs_free_error GError *error = NULL; nm_utils_error_set_cancelled(&error, FALSE, "NMDnsSystemdResolved"); _resolve_complete_error(handle, error); } /*****************************************************************************/ static void stop(NMDnsPlugin *plugin) { NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(plugin); NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); NMDnsSystemdResolvedResolveHandle *handle; /* This function must be re-entrant!! * * Currently there is no concept of unregistering/shutting down. It's not * clear whether we should de-configure anything in systemd-resolved, we * don't. * * Implementing stop() is important because pending operations take a * reference on @self. We can only cancel (fast shutdown) the instance * by cancelling those requests. */ priv->stopped = TRUE; priv->try_start_blocked = TRUE; nm_clear_g_cancellable(&priv->cancellable); nm_clear_g_free(&priv->dbus_owner); while ((handle = c_list_first_entry(&priv->handle_lst_head, NMDnsSystemdResolvedResolveHandle, handle_lst))) { gs_free_error GError *error = NULL; nm_utils_error_set_cancelled(&error, TRUE, "NMDnsSystemdResolved"); _resolve_complete_error(handle, error); } free_pending_updates(self); nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id); nm_clear_g_cancellable(&priv->service_start_cancellable); } /*****************************************************************************/ static void nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); priv->has_set_link_default_route = NM_TERNARY_DEFAULT; priv->has_set_link_dns_over_tls = NM_TERNARY_DEFAULT; priv->has_set_link_dns_ex = NM_TERNARY_DEFAULT; c_list_init(&priv->request_queue_lst_head); c_list_init(&priv->handle_lst_head); priv->dirty_interfaces = g_hash_table_new(nm_direct_hash, NULL); priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET); if (!priv->dbus_connection) { _LOGD("no D-Bus connection"); return; } priv->update_pending = TRUE; priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection, SYSTEMD_RESOLVED_DBUS_SERVICE, name_owner_changed_cb, self, NULL); priv->cancellable = g_cancellable_new(); nm_dbus_connection_call_get_name_owner(priv->dbus_connection, SYSTEMD_RESOLVED_DBUS_SERVICE, -1, priv->cancellable, get_name_owner_cb, self); } NMDnsPlugin * nm_dns_systemd_resolved_new(void) { return g_object_new(NM_TYPE_DNS_SYSTEMD_RESOLVED, NULL); } static void dispose(GObject *object) { NMDnsSystemdResolved *self = NM_DNS_SYSTEMD_RESOLVED(object); NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); stop(NM_DNS_PLUGIN(self)); g_clear_object(&priv->dbus_connection); nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_destroy); G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object); } static void nm_dns_systemd_resolved_class_init(NMDnsSystemdResolvedClass *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 = "systemd-resolved"; plugin_class->is_caching = TRUE; plugin_class->stop = stop; plugin_class->update = update; plugin_class->get_update_pending = get_update_pending; }