diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c index 2a8a228900..972ef643a3 100644 --- a/src/core/dns/nm-dns-systemd-resolved.c +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -52,19 +52,40 @@ typedef struct { int ifindex; } 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; + GSource * try_start_timeout_source; CList request_queue_lst_head; + char * dbus_owner; + CList handle_lst_head; guint name_owner_changed_id; bool send_updates_warn_ratelimited : 1; bool try_start_blocked : 1; - bool dbus_has_owner : 1; bool dbus_initied : 1; - bool request_queue_to_send : 1; + bool send_updates_waiting : 1; NMTernary has_link_default_route : 3; } NMDnsSystemdResolvedPrivate; @@ -84,9 +105,39 @@ G_DEFINE_TYPE(NMDnsSystemdResolved, nm_dns_systemd_resolved, NM_TYPE_DNS_PLUGIN) /*****************************************************************************/ -#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG_DOMAIN LOGD_DNS +#define _NMLOG_PREFIX_NAME "dns-sd-resolved" + #define _NMLOG(level, ...) \ - __NMLOG_DEFAULT_WITH_ADDR(level, _NMLOG_DOMAIN, "dns-sd-resolved", __VA_ARGS__) + __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); /*****************************************************************************/ @@ -323,57 +374,105 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic) return has_config; } -static void -send_updates(NMDnsSystemdResolved *self) +static gboolean +_ensure_resolved_running_timeout(gpointer user_data) +{ + NMDnsSystemdResolved * self = user_data; + NMDnsSystemdResolvedPrivate * priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + NMDnsSystemdResolvedResolveHandle *handle; + + nm_clear_g_source_inst(&priv->try_start_timeout_source); + + _LOGT("timeout waiting to D-Bus activate systemd-resolved. Systemd-resolved won't be " + "used until it appears on the bus"); + +again: + c_list_for_each_entry (handle, &priv->handle_lst_head, handle_lst) { + gs_free_error GError *error = NULL; + + if (handle->is_failing_on_idle) + continue; + + nm_utils_error_set_literal(&error, + NM_UTILS_ERROR_NOT_READY, + "timeout waiting for systemd-resolved to start"); + _resolve_complete_error(handle, error); + goto again; + } + + return G_SOURCE_CONTINUE; +} + +static NMTernary +ensure_resolved_running(NMDnsSystemdResolved *self) { NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); - RequestItem * request_item; - if (!priv->request_queue_to_send) { - /* nothing to do. */ - return; - } + if (!priv->dbus_initied) + return NM_TERNARY_DEFAULT; - if (!priv->dbus_initied) { - _LOGT("send-updates: D-Bus connection not ready"); - return; - } - - if (!priv->dbus_has_owner) { + if (!priv->dbus_owner) { if (priv->try_start_blocked) { /* we have no name owner and we already tried poking the service to * autostart. */ - _LOGT("send-updates: no name owner"); - return; + return NM_TERNARY_FALSE; } - _LOGT("send-updates: no name owner. Try start service..."); + _LOGT("try D-Bus activating systemd-resolved..."); priv->try_start_blocked = TRUE; + priv->try_start_timeout_source = + nm_g_source_attach(nm_g_timeout_source_new(4000, + G_PRIORITY_DEFAULT, + _ensure_resolved_running_timeout, + self, + NULL), + NULL); + nm_dbus_connection_call_start_service_by_name(priv->dbus_connection, SYSTEMD_RESOLVED_DBUS_SERVICE, -1, NULL, NULL, NULL); + 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->request_queue_to_send = FALSE; - return; + priv->send_updates_waiting = FALSE; + goto start_resolve; } - _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head)); - priv->cancellable = g_cancellable_new(); - priv->request_queue_to_send = FALSE; + priv->send_updates_waiting = FALSE; + + _LOGT("send-updates: start %lu 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_link_default_route == NM_TERNARY_FALSE) { /* The "SetLinkDefaultRoute" API is only supported since v240. @@ -383,16 +482,12 @@ send_updates(NMDnsSystemdResolved *self) continue; } - /* Above we explicitly call "StartServiceByName" trying to avoid D-Bus activating systmd-resolved - * multiple times. There is still a race, were we might hit this line although actually - * the service just quit this very moment. In that case, we would try to D-Bus activate the - * service multiple times during each call (something we wanted to avoid). - * - * But this is hard to avoid, because we'd have to check the error failure to detect the reason - * and retry. The race is not critical, because at worst it results in logging a warning - * about failure to start systemd.resolved. */ + _LOGT("send-updates: %s ( %s )", + request_item->operation, + (ss = g_variant_print(request_item->argument, FALSE))); + g_dbus_connection_call(priv->dbus_connection, - SYSTEMD_RESOLVED_DBUS_SERVICE, + priv->dbus_owner, SYSTEMD_RESOLVED_DBUS_PATH, SYSTEMD_RESOLVED_MANAGER_IFACE, request_item->operation, @@ -404,6 +499,15 @@ send_updates(NMDnsSystemdResolved *self) call_done, 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 @@ -476,7 +580,7 @@ update(NMDnsPlugin * plugin, } } - priv->request_queue_to_send = TRUE; + priv->send_updates_waiting = TRUE; send_updates(self); return TRUE; } @@ -495,10 +599,13 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner) else _LOGT("D-Bus name for systemd-resolved has owner %s", owner); - priv->dbus_has_owner = !!owner; + nm_clear_g_source_inst(&priv->try_start_timeout_source); + + nm_utils_strdup_reset(&priv->dbus_owner, owner); + if (owner) { - priv->try_start_blocked = FALSE; - priv->request_queue_to_send = TRUE; + priv->try_start_blocked = FALSE; + priv->send_updates_waiting = TRUE; } else priv->has_link_default_route = NM_TERNARY_DEFAULT; @@ -528,6 +635,7 @@ name_owner_changed_cb(GDBusConnection *connection, * returns. */ priv->dbus_initied = TRUE; nm_clear_g_cancellable(&priv->cancellable); + _LOGT("D-Bus connection is ready"); } name_owner_changed(user_data, new_owner); @@ -548,6 +656,7 @@ get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data) g_clear_object(&priv->cancellable); priv->dbus_initied = TRUE; + _LOGT("D-Bus connection is ready"); name_owner_changed(self, name_owner); } @@ -563,7 +672,253 @@ nm_dns_systemd_resolved_is_running(NMDnsSystemdResolved *self) priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); - return priv->dbus_initied && (priv->dbus_has_owner || !priv->try_start_blocked); + 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_strdup(v_name), + .ifindex = v_ifindex, + }; + } + g_variant_iter_free(v_names_iter); + + _resolve_complete(handle, + &g_array_index(v_names, NMDnsSystemdResolvedAddressResult, 0), + 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_source_attach( + nm_g_idle_source_new(G_PRIORITY_DEFAULT, _resolve_failing_on_idle, handle, NULL), + NULL); + return; + } + + if (!handle->timeout_source) { + handle->timeout_source = nm_g_source_attach(nm_g_timeout_source_new(handle->timeout_msec, + G_PRIORITY_DEFAULT, + _resolve_handle_timeout, + handle, + NULL), + NULL); + } + + 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_UTILS_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_utils_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); } /*****************************************************************************/ @@ -576,6 +931,7 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self) priv->has_link_default_route = 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); @@ -608,8 +964,18 @@ nm_dns_systemd_resolved_new(void) static void dispose(GObject *object) { - NMDnsSystemdResolved * self = NM_DNS_SYSTEMD_RESOLVED(object); - NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + NMDnsSystemdResolved * self = NM_DNS_SYSTEMD_RESOLVED(object); + NMDnsSystemdResolvedPrivate * priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self); + NMDnsSystemdResolvedResolveHandle *handle; + + 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); @@ -617,10 +983,14 @@ dispose(GObject *object) nm_clear_g_cancellable(&priv->cancellable); + nm_clear_g_source_inst(&priv->try_start_timeout_source); + g_clear_object(&priv->dbus_connection); nm_clear_pointer(&priv->dirty_interfaces, g_hash_table_unref); G_OBJECT_CLASS(nm_dns_systemd_resolved_parent_class)->dispose(object); + + nm_clear_g_free(&priv->dbus_owner); } static void diff --git a/src/core/dns/nm-dns-systemd-resolved.h b/src/core/dns/nm-dns-systemd-resolved.h index 4ab04ab49c..b7e30d81f7 100644 --- a/src/core/dns/nm-dns-systemd-resolved.h +++ b/src/core/dns/nm-dns-systemd-resolved.h @@ -30,4 +30,34 @@ NMDnsPlugin *nm_dns_systemd_resolved_new(void); gboolean nm_dns_systemd_resolved_is_running(NMDnsSystemdResolved *self); +/*****************************************************************************/ + +typedef struct _NMDnsSystemdResolvedResolveHandle NMDnsSystemdResolvedResolveHandle; + +typedef struct { + const char *name; + int ifindex; +} NMDnsSystemdResolvedAddressResult; + +typedef void (*NMDnsSystemdResolvedResolveAddressCallback)( + NMDnsSystemdResolved * self, + NMDnsSystemdResolvedResolveHandle * handle, + const NMDnsSystemdResolvedAddressResult *names, + guint names_len, + guint64 flags, + GError * error, + gpointer user_data); + +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); + +void nm_dns_systemd_resolved_resolve_cancel(NMDnsSystemdResolvedResolveHandle *handle); + #endif /* __NETWORKMANAGER_DNS_SYSTEMD_RESOLVED_H__ */