diff --git a/TODO b/TODO index d70152e4c8..02058c26f9 100644 --- a/TODO +++ b/TODO @@ -119,6 +119,12 @@ Deprecations and removals: Features: +* tree-wide: convert as much as possible over to use sd_event_set_signal_exit(), instead + of manually hooking into SIGINT/SIGTERM + +* tree-wide: convert as much as possible over to SD_EVENT_SIGNAL_PROCMASK + instead of manual blocking. + * sd-boot: for each installed OS, grey out older entries (i.e. all but the newest), to indicate they are obsolete diff --git a/man/org.freedesktop.resolve1.xml b/man/org.freedesktop.resolve1.xml index d3aedbc13e..54f0a18418 100644 --- a/man/org.freedesktop.resolve1.xml +++ b/man/org.freedesktop.resolve1.xml @@ -149,7 +149,6 @@ node /org/freedesktop/resolve1 { readonly s DNSStubListener = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ResolvConfMode = '...'; - readonly b Monitor = ...; }; interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Introspectable { ... }; @@ -251,8 +250,6 @@ node /org/freedesktop/resolve1 { - - @@ -637,8 +634,6 @@ node /org/freedesktop/resolve1 { enabled. Possible values are yes (enabled), no (disabled), udp (only the UDP listener is enabled), and tcp (only the TCP listener is enabled). - - The Monitor boolean property reports whether DNS monitoring is enabled. diff --git a/man/resolvectl.xml b/man/resolvectl.xml index 19fb0780b5..a9cdfe9187 100644 --- a/man/resolvectl.xml +++ b/man/resolvectl.xml @@ -199,6 +199,19 @@ automatically, an explicit reverting is not necessary in that case. + + monitor + + Show a continous stream of local client resolution queries and their + responses. Whenever a local query is completed the query's DNS resource lookup key and resource + records are shown. Note that this displays queries issued locally only, and does not immediately + relate to DNS requests submitted to configured DNS servers or the LLMNR or MulticastDNS zones, as + lookups may be answered from the local cache, or might result in multiple DNS transactions (for + example to validate DNSSEC information). If CNAME/CNAME redirection chains are followed, a separate + query will be displayed for each element of the chain. Use to enable JSON + output. + + @@ -379,9 +392,17 @@ query response are shown. Otherwise, this output is suppressed. + + + + + + Short for + + + - diff --git a/man/rules/meson.build b/man/rules/meson.build index 2925dadc1e..4a497d59c4 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -555,7 +555,9 @@ manpages = [ ''], ['sd_event_add_signal', '3', - ['sd_event_signal_handler_t', 'sd_event_source_get_signal'], + ['SD_EVENT_SIGNAL_PROCMASK', + 'sd_event_signal_handler_t', + 'sd_event_source_get_signal'], ''], ['sd_event_add_time', '3', @@ -581,6 +583,7 @@ manpages = [ ''], ['sd_event_now', '3', [], ''], ['sd_event_run', '3', ['sd_event_loop'], ''], + ['sd_event_set_signal_exit', '3', [], ''], ['sd_event_set_watchdog', '3', ['sd_event_get_watchdog'], ''], ['sd_event_source_get_event', '3', [], ''], ['sd_event_source_get_pending', '3', [], ''], diff --git a/man/sd_event_add_signal.xml b/man/sd_event_add_signal.xml index b2aaff87c1..3e8536e961 100644 --- a/man/sd_event_add_signal.xml +++ b/man/sd_event_add_signal.xml @@ -19,6 +19,7 @@ sd_event_add_signal sd_event_source_get_signal sd_event_signal_handler_t + SD_EVENT_SIGNAL_PROCMASK Add a UNIX process signal event source to an event loop @@ -30,6 +31,8 @@ typedef struct sd_event_source sd_event_source; + SD_EVENT_SIGNAL_PROCMASK + typedef int (*sd_event_signal_handler_t) sd_event_source *s @@ -50,30 +53,26 @@ int sd_event_source_get_signal sd_event_source *source - Description - sd_event_add_signal() adds a new UNIX - process signal event source to an event loop. The event loop - object is specified in the event parameter, - and the event source object is returned in the - source parameter. The - signal parameter specifies the numeric - signal to be handled (see sd_event_add_signal() adds a new UNIX process signal event source to an event + loop. The event loop object is specified in the event parameter, and the event + source object is returned in the source parameter. The + signal parameter specifies the numeric signal to be handled (see signal7). The handler parameter is a function to call when the signal is received or NULL. The handler function will be passed the userdata pointer, which may be chosen freely by the caller. The handler also receives a pointer to a signalfd_siginfo structure containing information about the received signal. See - signalfd2 - for further information. The handler may return negative to signal an error (see below), other return - values are ignored. If handler is NULL, a default handler - that calls + signalfd2 for + further information. The handler may return negative to signal an error (see below), other return values + are ignored. If handler is NULL, a default handler that calls sd_event_exit3 will be used. @@ -81,14 +80,18 @@ threads before this function is called (using sigprocmask2 or pthread_sigmask3). + project='man-pages'>pthread_sigmask3). For + convenience, if the special flag SD_EVENT_SIGNAL_PROCMASK is ORed into the specified + signal the signal will be automatically masked as necessary, for the calling thread. Note that this only + works reliably if the signal is already masked in all other threads of the process, or if there are no + other threads at the moment of invocation. - By default, the event source is enabled permanently - (SD_EVENT_ON), but this may be changed with + By default, the event source is enabled permanently (SD_EVENT_ON), but this + may be changed with sd_event_source_set_enabled3. - If the handler function returns a negative error code, it will either be disabled after the - invocation, even if the SD_EVENT_ON mode was requested before, or it will cause the - loop to terminate, see + If the handler function returns a negative error code, it will either be disabled after the invocation, + even if the SD_EVENT_ON mode was requested before, or it will cause the loop to + terminate, see sd_event_source_set_exit_on_failure3. diff --git a/man/sd_event_set_signal_exit.xml b/man/sd_event_set_signal_exit.xml new file mode 100644 index 0000000000..e5e675beec --- /dev/null +++ b/man/sd_event_set_signal_exit.xml @@ -0,0 +1,101 @@ + + + + + + + + sd_event_set_signal_exit + systemd + + + + sd_event_set_signal_exit + 3 + + + + sd_event_set_signal_exit + + Automatically leave event loop on SIGINT and SIGTERM + + + + + #include <systemd/sd-event.h> + + + int sd_event_set_signal_exit + sd_event *event + int b + + + + + + + Description + + sd_event_set_signal_exit() may be used to ensure the event loop terminates + once a SIGINT or SIGTERM signal is received. It is a + convencience wrapper around invocations of + sd_event_add_signal3 + for both signals. The two signals are automatically added to the calling thread's signal mask (if a + program is multi-threaded care should be taken to either invoke this function before the first thread is + started or to manually block the two signals process-wide first). + + If the parameter b is specified as true, the event loop will terminate on + SIGINT and SIGTERM. If specified as false, it will no + longer. When this functionality is turned off the calling thread's signal mask is restored to match the + state before it was turned on, for the two signals. By default the two signals are not handled by the + event loop, and Linux' default signal handling for them is in effect. + + It's customary for UNIX programs to exit on either of these two signals, hence it's typically a + good idea to enable this functionality for the main event loop of a program. + + + + Return Value + + sd_event_set_signal_exit() returns a positive non-zero value when the setting + was successfully changed. It returns a zero when the specified setting was already in effect. On failure, + it returns a negative errno-style error code. + + + Errors + + Returned errors may indicate the following problems: + + + + + -ECHILD + + The event loop has been created in a different process. + + + + -EINVAL + + The passed event loop object was invalid. + + + + + + + + + + See Also + + + systemd1, + sd-event3, + sd_event_new3, + sd_event_add_signal3 + + + + diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c index 1bba139bfc..67f2270daf 100644 --- a/src/basic/glyph-util.c +++ b/src/basic/glyph-util.c @@ -53,6 +53,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_LIGHT_SHADE] = "-", [SPECIAL_GLYPH_DARK_SHADE] = "X", [SPECIAL_GLYPH_SIGMA] = "S", + [SPECIAL_GLYPH_ARROW_LEFT] = "<-", [SPECIAL_GLYPH_ARROW_RIGHT] = "->", [SPECIAL_GLYPH_ARROW_UP] = "^", [SPECIAL_GLYPH_ARROW_DOWN] = "v", @@ -99,6 +100,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_ARROW_DOWN] = u8"↓", /* actually called: DOWNWARDS ARROW */ /* Single glyph in Unicode, two in ASCII */ + [SPECIAL_GLYPH_ARROW_LEFT] = u8"←", /* actually called: LEFTWARDS ARROW */ [SPECIAL_GLYPH_ARROW_RIGHT] = u8"→", /* actually called: RIGHTWARDS ARROW */ /* Single glyph in Unicode, three in ASCII */ diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h index 065dde8a62..621d7a85b7 100644 --- a/src/basic/glyph-util.h +++ b/src/basic/glyph-util.h @@ -22,6 +22,7 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_MU, SPECIAL_GLYPH_CHECK_MARK, SPECIAL_GLYPH_CROSS_MARK, + SPECIAL_GLYPH_ARROW_LEFT, SPECIAL_GLYPH_ARROW_RIGHT, SPECIAL_GLYPH_ARROW_UP, SPECIAL_GLYPH_ARROW_DOWN, diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 992a79fcc4..3b72320f0c 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -790,6 +790,8 @@ global: sd_device_monitor_set_description; sd_device_monitor_get_description; + sd_event_set_signal_exit; + sd_id128_string_equal; sd_hwdb_new_from_path; diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index 74cbc26962..6092652d0f 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -99,6 +99,7 @@ struct sd_event_source { sd_event_signal_handler_t callback; struct signalfd_siginfo siginfo; int sig; + bool unblock; } signal; struct { sd_event_child_handler_t callback; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 6f99c5f0cd..778070a5fb 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -153,6 +153,8 @@ struct sd_event { LIST_HEAD(sd_event_source, sources); + sd_event_source *sigint_event_source, *sigterm_event_source; + usec_t last_run_usec, last_log_usec; unsigned delays[sizeof(usec_t) * 8]; }; @@ -323,6 +325,9 @@ static sd_event *event_free(sd_event *e) { assert(e); + e->sigterm_event_source = sd_event_source_unref(e->sigterm_event_source); + e->sigint_event_source = sd_event_source_unref(e->sigint_event_source); + while ((s = e->sources)) { assert(s->floating); source_disconnect(s); @@ -813,6 +818,7 @@ static void event_source_time_prioq_remove( static void source_disconnect(sd_event_source *s) { sd_event *event; + int r; assert(s); @@ -853,6 +859,20 @@ static void source_disconnect(sd_event_source *s) { s->event->signal_sources[s->signal.sig] = NULL; event_gc_signal_data(s->event, &s->priority, s->signal.sig); + + if (s->signal.unblock) { + sigset_t new_ss; + + if (sigemptyset(&new_ss) < 0) + log_debug_errno(errno, "Failed to reset signal set, ignoring: %m"); + else if (sigaddset(&new_ss, s->signal.sig) < 0) + log_debug_errno(errno, "Failed to add signal %i to signal mask, ignoring: %m", s->signal.sig); + else { + r = pthread_sigmask(SIG_UNBLOCK, &new_ss, NULL); + if (r != 0) + log_debug_errno(r, "Failed to unblock signal %i, ignoring: %m", s->signal.sig); + } + } } break; @@ -1328,23 +1348,38 @@ _public_ int sd_event_add_signal( _cleanup_(source_freep) sd_event_source *s = NULL; struct signal_data *d; + sigset_t new_ss; + bool block_it; int r; assert_return(e, -EINVAL); assert_return(e = event_resolve(e), -ENOPKG); - assert_return(SIGNAL_VALID(sig), -EINVAL); assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_pid_changed(e), -ECHILD); + /* Let's make sure our special flag stays outside of the valid signal range */ + assert_cc(_NSIG < SD_EVENT_SIGNAL_PROCMASK); + + if (sig & SD_EVENT_SIGNAL_PROCMASK) { + sig &= ~SD_EVENT_SIGNAL_PROCMASK; + assert_return(SIGNAL_VALID(sig), -EINVAL); + + block_it = true; + } else { + assert_return(SIGNAL_VALID(sig), -EINVAL); + + r = signal_is_blocked(sig); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; + + block_it = false; + } + if (!callback) callback = signal_exit_callback; - r = signal_is_blocked(sig); - if (r < 0) - return r; - if (r == 0) - return -EBUSY; - if (!e->signal_sources) { e->signal_sources = new0(sd_event_source*, _NSIG); if (!e->signal_sources) @@ -1363,9 +1398,34 @@ _public_ int sd_event_add_signal( e->signal_sources[sig] = s; + if (block_it) { + sigset_t old_ss; + + if (sigemptyset(&new_ss) < 0) + return -errno; + + if (sigaddset(&new_ss, sig) < 0) + return -errno; + + r = pthread_sigmask(SIG_BLOCK, &new_ss, &old_ss); + if (r != 0) + return -r; + + r = sigismember(&old_ss, sig); + if (r < 0) + return -errno; + + s->signal.unblock = !r; + } else + s->signal.unblock = false; + r = event_make_signal_data(e, sig, &d); - if (r < 0) + if (r < 0) { + if (s->signal.unblock) + (void) pthread_sigmask(SIG_UNBLOCK, &new_ss, NULL); + return r; + } /* Use the signal name as description for the event source by default */ (void) sd_event_source_set_description(s, signal_to_string(sig)); @@ -4558,3 +4618,55 @@ _public_ int sd_event_source_is_ratelimited(sd_event_source *s) { return s->ratelimited; } + +_public_ int sd_event_set_signal_exit(sd_event *e, int b) { + bool change = false; + int r; + + assert_return(e, -EINVAL); + + if (b) { + /* We want to maintain pointers to these event sources, so that we can destroy them when told + * so. But we also don't want them to pin the event loop itself. Hence we mark them as + * floating after creation (and undo this before deleting them again). */ + + if (!e->sigint_event_source) { + r = sd_event_add_signal(e, &e->sigint_event_source, SIGINT | SD_EVENT_SIGNAL_PROCMASK, NULL, NULL); + if (r < 0) + return r; + + assert(sd_event_source_set_floating(e->sigint_event_source, true) >= 0); + change = true; + } + + if (!e->sigterm_event_source) { + r = sd_event_add_signal(e, &e->sigterm_event_source, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, NULL); + if (r < 0) { + if (change) { + assert(sd_event_source_set_floating(e->sigint_event_source, false) >= 0); + e->sigint_event_source = sd_event_source_unref(e->sigint_event_source); + } + + return r; + } + + assert(sd_event_source_set_floating(e->sigterm_event_source, true) >= 0); + change = true; + } + + } else { + if (e->sigint_event_source) { + assert(sd_event_source_set_floating(e->sigint_event_source, false) >= 0); + e->sigint_event_source = sd_event_source_unref(e->sigint_event_source); + change = true; + } + + if (e->sigterm_event_source) { + assert(sd_event_source_set_floating(e->sigterm_event_source, false) >= 0); + e->sigterm_event_source = sd_event_source_unref(e->sigterm_event_source); + change = true; + } + } + + return change; +} diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index bda7ca2add..b2a5b7263f 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -15,11 +15,13 @@ #include "bus-map-properties.h" #include "bus-message-util.h" #include "dns-domain.h" +#include "errno-list.h" #include "escape.h" #include "format-table.h" #include "format-util.h" #include "gcrypt-util.h" #include "hostname-util.h" +#include "json.h" #include "main-func.h" #include "missing_network.h" #include "netlink-util.h" @@ -41,6 +43,7 @@ #include "strv.h" #include "terminal-util.h" #include "utf8.h" +#include "varlink.h" #include "verb-log-control.h" #include "verbs.h" @@ -51,6 +54,7 @@ static uint16_t arg_type = 0; static uint16_t arg_class = 0; static bool arg_legend = true; static uint64_t arg_flags = 0; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; bool arg_ifindex_permissive = false; /* If true, don't generate an error if the specified interface index doesn't exist */ static const char *arg_service_family = NULL; @@ -395,20 +399,9 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a static int output_rr_packet(const void *d, size_t l, int ifindex) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; int r; - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX); - if (r < 0) - return log_oom(); - - p->refuse_compression = true; - - r = dns_packet_append_blob(p, d, l, NULL); - if (r < 0) - return log_oom(); - - r = dns_packet_read_rr(p, &rr, NULL, NULL); + r = dns_resource_record_new_from_raw(&rr, d, l); if (r < 0) return log_error_errno(r, "Failed to parse RR: %m"); @@ -2514,6 +2507,227 @@ static int verb_log_level(int argc, char *argv[], void *userdata) { return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); } +static int monitor_rkey_from_json(JsonVariant *v, DnsResourceKey **ret_key) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + uint16_t type = 0, class = 0; + const char *name = NULL; + int r; + + JsonDispatch dispatch_table[] = { + { "class", JSON_VARIANT_INTEGER, json_dispatch_uint16, PTR_TO_SIZE(&class), JSON_MANDATORY }, + { "type", JSON_VARIANT_INTEGER, json_dispatch_uint16, PTR_TO_SIZE(&type), JSON_MANDATORY }, + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&name), JSON_MANDATORY }, + {} + }; + + assert(v); + assert(ret_key); + + r = json_dispatch(v, dispatch_table, NULL, 0, NULL); + if (r < 0) + return r; + + key = dns_resource_key_new(class, type, name); + if (!key) + return -ENOMEM; + + *ret_key = TAKE_PTR(key); + return 0; +} + +static int print_question(char prefix, const char *color, JsonVariant *question) { + JsonVariant *q = NULL; + int r; + + assert(color); + + JSON_VARIANT_ARRAY_FOREACH(q, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + char buf[DNS_RESOURCE_KEY_STRING_MAX]; + + r = monitor_rkey_from_json(q, &key); + if (r < 0) { + log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); + continue; + } + + printf("%s%s %c%s: %s\n", + color, + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + prefix, + ansi_normal(), + dns_resource_key_to_string(key, buf, sizeof(buf))); + } + + return 0; +} + +static int print_answer(JsonVariant *answer) { + JsonVariant *a; + int r; + + JSON_VARIANT_ARRAY_FOREACH(a, answer) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *d = NULL; + JsonVariant *jraw; + const char *s; + size_t l; + + jraw = json_variant_by_key(a, "raw"); + if (!jraw) { + log_warning("Received monitor answer lacking valid raw data, ignoring."); + continue; + } + + r = json_variant_unbase64(jraw, &d, &l); + if (r < 0) { + log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); + continue; + } + + r = dns_resource_record_new_from_raw(&rr, d, l); + if (r < 0) { + log_warning_errno(r, "Failed to parse monitor answer RR, ingoring: %m"); + continue; + } + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + printf("%s%s A%s: %s\n", + ansi_highlight_yellow(), + special_glyph(SPECIAL_GLYPH_ARROW_LEFT), + ansi_normal(), + s); + } + + return 0; +} + +static void monitor_query_dump(JsonVariant *v) { + _cleanup_(json_variant_unrefp) JsonVariant *question = NULL, *answer = NULL, *collected_questions = NULL; + int rcode = -1, error = 0, r; + const char *state = NULL; + + assert(v); + + JsonDispatch dispatch_table[] = { + { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY }, + { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 }, + { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 }, + { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY }, + { "rcode", JSON_VARIANT_INTEGER, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 }, + { "errno", JSON_VARIANT_INTEGER, json_dispatch_int, PTR_TO_SIZE(&error), 0 }, + {} + }; + + r = json_dispatch(v, dispatch_table, NULL, 0, NULL); + if (r < 0) + return (void) log_warning("Received malformed monitor message, ignoring."); + + /* First show the current question */ + print_question('Q', ansi_highlight_cyan(), question); + + /* And then show the questions that led to this one in case this was a CNAME chain */ + print_question('C', ansi_highlight_grey(), collected_questions); + + printf("%s%s S%s: %s\n", + streq_ptr(state, "success") ? ansi_highlight_green() : ansi_highlight_red(), + special_glyph(SPECIAL_GLYPH_ARROW_LEFT), + ansi_normal(), + strna(streq_ptr(state, "errno") ? errno_to_name(error) : + streq_ptr(state, "rcode-failure") ? dns_rcode_to_string(rcode) : + state)); + + print_answer(answer); +} + +static int monitor_reply( + Varlink *link, + JsonVariant *parameters, + const char *error_id, + VarlinkReplyFlags flags, + void *userdata) { + + assert(link); + + if (error_id) { + bool disconnect; + + disconnect = streq(error_id, VARLINK_ERROR_DISCONNECTED); + if (disconnect) + log_info("Disconnected."); + else + log_error("Varlink error: %s", error_id); + + (void) sd_event_exit(ASSERT_PTR(varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); + return 0; + } + + if (json_variant_by_key(parameters, "ready")) { + /* The first message coming in will just indicate that we are now subscribed. We let our + * caller know if they asked for it. Once the caller sees this they should know that we are + * not going to miss any queries anymore. */ + (void) sd_notify(/* unset_environment=false */ false, "READY=1"); + return 0; + } + + if (arg_json_format_flags & JSON_FORMAT_OFF) { + monitor_query_dump(parameters); + printf("\n"); + } else + json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); + + fflush(stdout); + + return 0; +} + +static int verb_monitor(int argc, char *argv[], void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r, c; + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + + r = varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ + if (r < 0) + return log_error_errno(r, "Failed to set varlink time-out: %m"); + + r = varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + r = varlink_bind_reply(vl, monitor_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); + + r = varlink_observe(vl, "io.systemd.Resolve.Monitor.SubscribeQueryResults", NULL); + if (r < 0) + return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + r = sd_event_get_exit_code(event, &c); + if (r < 0) + return log_error_errno(r, "Failed to get exit code: %m"); + + return c; +} + static void help_protocol_types(void) { if (arg_legend) puts("Known protocol types:"); @@ -2619,6 +2833,7 @@ static int native_help(void) { " reset-statistics Reset resolver statistics\n" " flush-caches Flush all local DNS caches\n" " reset-server-features Forget learnt DNS server feature levels\n" + " monitor Monitor DNS queries\n" " dns [LINK [SERVER...]] Get/set per-interface DNS server address\n" " domain [LINK [DOMAIN...]] Get/set per-interface search domain\n" " default-route [LINK [BOOL]] Get/set per-interface default route flag\n" @@ -2647,11 +2862,16 @@ static int native_help(void) { " --cache=BOOL Allow response from cache (default: yes)\n" " --zone=BOOL Allow response from locally registered mDNS/LLMNR\n" " records (default: yes)\n" - " --trust-anchor=BOOL Allow response from local trust anchor (default: yes)\n" + " --trust-anchor=BOOL Allow response from local trust anchor (default:\n" + " yes)\n" " --network=BOOL Allow response from network (default: yes)\n" - " --search=BOOL Use search domains for single-label names (default: yes)\n" + " --search=BOOL Use search domains for single-label names (default:\n" + " yes)\n" " --raw[=payload|packet] Dump the answer as binary data\n" " --legend=BOOL Print headers and additional info (default: yes)\n" + " --json=MODE Output as JSON\n" + " -j Same as --json=pretty on tty, --json=short\n" + " otherwise\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -2998,6 +3218,7 @@ static int native_parse_argv(int argc, char *argv[]) { ARG_RAW, ARG_SEARCH, ARG_NO_PAGER, + ARG_JSON, }; static const struct option options[] = { @@ -3020,6 +3241,7 @@ static int native_parse_argv(int argc, char *argv[]) { { "raw", optional_argument, NULL, ARG_RAW }, { "search", required_argument, NULL, ARG_SEARCH }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "json", required_argument, NULL, ARG_JSON }, {} }; @@ -3028,7 +3250,7 @@ static int native_parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "h46i:t:c:p:j", options, NULL)) >= 0) switch (c) { case 'h': @@ -3203,6 +3425,17 @@ static int native_parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + + case 'j': + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + break; + case '?': return -EINVAL; @@ -3246,6 +3479,7 @@ static int native_main(int argc, char *argv[], sd_bus *bus) { { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, { "revert", VERB_ANY, 2, 0, verb_revert_link }, { "log-level", VERB_ANY, 2, 0, verb_log_level }, + { "monitor", VERB_ANY, 1, 0, verb_monitor }, {} }; diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 51bb5b1ac7..2e3941da39 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -2096,7 +2096,6 @@ static const sd_bus_vtable resolve_vtable[] = { SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", bus_property_get_ntas, 0, 0), SD_BUS_PROPERTY("DNSStubListener", "s", bus_property_get_dns_stub_listener_mode, offsetof(Manager, dns_stub_listener_mode), 0), SD_BUS_PROPERTY("ResolvConfMode", "s", bus_property_get_resolv_conf_mode, 0, 0), - SD_BUS_PROPERTY("Monitor", "b", bus_property_get_bool, offsetof(Manager, enable_varlink_notifications), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_METHOD_WITH_ARGS("ResolveHostname", SD_BUS_ARGS("i", ifindex, "s", name, "i", family, "t", flags), diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 50c0e05ca6..58a7b2d878 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -397,6 +397,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { dns_question_unref(q->question_idna); dns_question_unref(q->question_utf8); dns_packet_unref(q->question_bypass); + dns_question_unref(q->collected_questions); dns_query_reset_answer(q); @@ -585,8 +586,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) { q->state = state; - if (q->question_utf8 && state == DNS_TRANSACTION_SUCCESS && set_size(q->manager->varlink_subscription) > 0) - (void) send_dns_notification(q->manager, q->answer, dns_question_first_name(q->question_utf8)); + (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->collected_questions, q->answer); dns_query_stop(q); if (q->complete) @@ -980,6 +980,26 @@ void dns_query_ready(DnsQuery *q) { dns_query_accept(q, bad); } +static int dns_query_collect_question(DnsQuery *q, DnsQuestion *question) { + _cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL; + int r; + + assert(q); + + if (dns_question_size(question) == 0) + return 0; + + /* When redirecting, save the first element in the chain, for informational purposes when monitoring */ + r = dns_question_merge(q->collected_questions, question, &merged); + if (r < 0) + return r; + + dns_question_unref(q->collected_questions); + q->collected_questions = TAKE_PTR(merged); + + return 0; +} + static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; int r, k; @@ -1029,6 +1049,14 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) /* Turn off searching for the new name */ q->flags |= SD_RESOLVED_NO_SEARCH; + r = dns_query_collect_question(q, q->question_idna); + if (r < 0) + return r; + r = dns_query_collect_question(q, q->question_utf8); + if (r < 0) + return r; + + /* Install the redirected question */ dns_question_unref(q->question_idna); q->question_idna = TAKE_PTR(nq_idna); diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 43a833a08a..2723299bee 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -52,6 +52,11 @@ struct DnsQuery { * here, and use that instead. */ DnsPacket *question_bypass; + /* When we follow a CNAME redirect, we save the original question here, for informational/monitoring + * purposes. We'll keep adding to this whenever we go one step in the redirect, so that in the end + * this will contain the complete set of CNAME questions. */ + DnsQuestion *collected_questions; + uint64_t flags; int ifindex; diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index aefdaa0eeb..8f5ebb4590 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -50,6 +50,19 @@ int dns_question_add_raw(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags f return 0; } +static int dns_question_add_raw_all(DnsQuestion *a, DnsQuestion *b) { + DnsQuestionItem *item; + int r; + + DNS_QUESTION_FOREACH_ITEM(item, b) { + r = dns_question_add_raw(a, item->key, item->flags); + if (r < 0) + return r; + } + + return 0; +} + int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags) { DnsQuestionItem *item; int r; @@ -71,6 +84,19 @@ int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags return dns_question_add_raw(q, key, flags); } +static int dns_question_add_all(DnsQuestion *a, DnsQuestion *b) { + DnsQuestionItem *item; + int r; + + DNS_QUESTION_FOREACH_ITEM(item, b) { + r = dns_question_add(a, item->key, item->flags); + if (r < 0) + return r; + } + + return 0; +} + int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { DnsResourceKey *key; int r; @@ -486,3 +512,35 @@ void dns_question_dump(DnsQuestion *question, FILE *f) { fputc('\n', f); } } + +int dns_question_merge(DnsQuestion *a, DnsQuestion *b, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *k = NULL; + int r; + + assert(ret); + + if (a == b || dns_question_size(b) <= 0) { + *ret = dns_question_ref(a); + return 0; + } + + if (dns_question_size(a) <= 0) { + *ret = dns_question_ref(b); + return 0; + } + + k = dns_question_new(dns_question_size(a) + dns_question_size(b)); + if (!k) + return -ENOMEM; + + r = dns_question_add_raw_all(k, a); + if (r < 0) + return r; + + r = dns_question_add_all(k, b); + if (r < 0) + return r; + + *ret = TAKE_PTR(k); + return 0; +} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index 31b8a2ec3e..91bbeaed5b 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -59,6 +59,8 @@ static inline bool dns_question_isempty(DnsQuestion *q) { return dns_question_size(q) <= 0; } +int dns_question_merge(DnsQuestion *a, DnsQuestion *b, DnsQuestion **ret); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); #define _DNS_QUESTION_FOREACH(u, k, q) \ diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index bf1b342b8d..8123ca1f98 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1832,6 +1832,265 @@ int dns_txt_item_new_empty(DnsTxtItem **ret) { return 0; } +int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX); + if (r < 0) + return r; + + p->refuse_compression = true; + + r = dns_packet_append_blob(p, data, size, NULL); + if (r < 0) + return r; + + return dns_packet_read_rr(p, ret, NULL, NULL); +} + +int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret) { + assert(key); + assert(ret); + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("class", JSON_BUILD_INTEGER(key->class)), + JSON_BUILD_PAIR("type", JSON_BUILD_INTEGER(key->type)), + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(dns_resource_key_name(key))))); +} + +static int type_bitmap_to_json(Bitmap *b, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL; + unsigned t; + int r; + + assert(b); + assert(ret); + + BITMAP_FOREACH(t, b) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = json_variant_new_unsigned(&v, t); + if (r < 0) + return r; + + r = json_variant_append_array(&l, v); + if (r < 0) + return r; + } + + if (!l) + return json_variant_new_array(ret, NULL, 0); + + *ret = TAKE_PTR(l); + return 0; +} + +int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *k = NULL; + int r; + + assert(rr); + assert(ret); + + r = dns_resource_key_to_json(rr->key, &k); + if (r < 0) + return r; + + switch (rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->srv.priority)), + JSON_BUILD_PAIR("weight", JSON_BUILD_UNSIGNED(rr->srv.weight)), + JSON_BUILD_PAIR("port", JSON_BUILD_UNSIGNED(rr->srv.port)), + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(rr->srv.name)))); + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(rr->ptr.name)))); + + case DNS_TYPE_HINFO: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("cpu", JSON_BUILD_STRING(rr->hinfo.cpu)), + JSON_BUILD_PAIR("os", JSON_BUILD_STRING(rr->hinfo.os)))); + + case DNS_TYPE_SPF: + case DNS_TYPE_TXT: { + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL; + + LIST_FOREACH(items, i, rr->txt.items) { + _cleanup_(json_variant_unrefp) JsonVariant *b = NULL; + + r = json_variant_new_octescape(&b, i->data, i->length); + if (r < 0) + return r; + + r = json_variant_append_array(&l, b); + if (r < 0) + return r; + } + + if (!l) { + r = json_variant_new_array(&l, NULL, 0); + if (r < 0) + return r; + } + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("items", JSON_BUILD_VARIANT(l)))); + } + + case DNS_TYPE_A: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("address", JSON_BUILD_IN4_ADDR(&rr->a.in_addr)))); + + case DNS_TYPE_AAAA: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("address", JSON_BUILD_IN6_ADDR(&rr->aaaa.in6_addr)))); + + case DNS_TYPE_SOA: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("mname", JSON_BUILD_STRING(rr->soa.mname)), + JSON_BUILD_PAIR("rname", JSON_BUILD_STRING(rr->soa.rname)), + JSON_BUILD_PAIR("serial", JSON_BUILD_UNSIGNED(rr->soa.serial)), + JSON_BUILD_PAIR("refresh", JSON_BUILD_UNSIGNED(rr->soa.refresh)), + JSON_BUILD_PAIR("expire", JSON_BUILD_UNSIGNED(rr->soa.retry)), + JSON_BUILD_PAIR("minimum", JSON_BUILD_UNSIGNED(rr->soa.minimum)))); + + case DNS_TYPE_MX: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->mx.priority)), + JSON_BUILD_PAIR("exchange", JSON_BUILD_STRING(rr->mx.exchange)))); + case DNS_TYPE_LOC: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("version", JSON_BUILD_UNSIGNED(rr->loc.version)), + JSON_BUILD_PAIR("size", JSON_BUILD_UNSIGNED(rr->loc.size)), + JSON_BUILD_PAIR("horiz_pre", JSON_BUILD_UNSIGNED(rr->loc.horiz_pre)), + JSON_BUILD_PAIR("vert_pre", JSON_BUILD_UNSIGNED(rr->loc.vert_pre)), + JSON_BUILD_PAIR("latitude", JSON_BUILD_UNSIGNED(rr->loc.latitude)), + JSON_BUILD_PAIR("longitude", JSON_BUILD_UNSIGNED(rr->loc.longitude)), + JSON_BUILD_PAIR("altitude", JSON_BUILD_UNSIGNED(rr->loc.altitude)))); + + case DNS_TYPE_DS: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("keyTag", JSON_BUILD_UNSIGNED(rr->ds.key_tag)), + JSON_BUILD_PAIR("algorithm", JSON_BUILD_UNSIGNED(rr->ds.algorithm)), + JSON_BUILD_PAIR("digestType", JSON_BUILD_UNSIGNED(rr->ds.digest_type)), + JSON_BUILD_PAIR("digest", JSON_BUILD_HEX(rr->ds.digest, rr->ds.digest_size)))); + + case DNS_TYPE_SSHFP: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("algorithm", JSON_BUILD_UNSIGNED(rr->sshfp.algorithm)), + JSON_BUILD_PAIR("fptype", JSON_BUILD_UNSIGNED(rr->sshfp.fptype)), + JSON_BUILD_PAIR("fingerprint", JSON_BUILD_HEX(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size)))); + + case DNS_TYPE_DNSKEY: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(rr->dnskey.flags)), + JSON_BUILD_PAIR("protocol", JSON_BUILD_UNSIGNED(rr->dnskey.protocol)), + JSON_BUILD_PAIR("algorithm", JSON_BUILD_UNSIGNED(rr->dnskey.algorithm)), + JSON_BUILD_PAIR("dnskey", JSON_BUILD_BASE64(rr->dnskey.key, rr->dnskey.key_size)))); + + + case DNS_TYPE_RRSIG: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("signer", JSON_BUILD_STRING(rr->rrsig.signer)), + JSON_BUILD_PAIR("typeCovered", JSON_BUILD_UNSIGNED(rr->rrsig.type_covered)), + JSON_BUILD_PAIR("algorithm", JSON_BUILD_UNSIGNED(rr->rrsig.algorithm)), + JSON_BUILD_PAIR("labels", JSON_BUILD_UNSIGNED(rr->rrsig.labels)), + JSON_BUILD_PAIR("originalTtl", JSON_BUILD_UNSIGNED(rr->rrsig.original_ttl)), + JSON_BUILD_PAIR("expiration", JSON_BUILD_UNSIGNED(rr->rrsig.expiration)), + JSON_BUILD_PAIR("inception", JSON_BUILD_UNSIGNED(rr->rrsig.inception)), + JSON_BUILD_PAIR("keyTag", JSON_BUILD_UNSIGNED(rr->rrsig.key_tag)), + JSON_BUILD_PAIR("signature", JSON_BUILD_BASE64(rr->rrsig.signature, rr->rrsig.signature_size)))); + + case DNS_TYPE_NSEC: { + _cleanup_(json_variant_unrefp) JsonVariant *bm = NULL; + + r = type_bitmap_to_json(rr->nsec.types, &bm); + if (r < 0) + return r; + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("nextDomain", JSON_BUILD_STRING(rr->nsec.next_domain_name)), + JSON_BUILD_PAIR("types", JSON_BUILD_VARIANT(bm)))); + } + + case DNS_TYPE_NSEC3: { + _cleanup_(json_variant_unrefp) JsonVariant *bm = NULL; + + r = type_bitmap_to_json(rr->nsec3.types, &bm); + if (r < 0) + return r; + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("algorithm", JSON_BUILD_UNSIGNED(rr->nsec3.algorithm)), + JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(rr->nsec3.flags)), + JSON_BUILD_PAIR("iterations", JSON_BUILD_UNSIGNED(rr->nsec3.iterations)), + JSON_BUILD_PAIR("salt", JSON_BUILD_HEX(rr->nsec3.salt, rr->nsec3.salt_size)), + JSON_BUILD_PAIR("hash", JSON_BUILD_BASE32HEX(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size)), + JSON_BUILD_PAIR("types", JSON_BUILD_VARIANT(bm)))); + } + + case DNS_TYPE_TLSA: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("certUsage", JSON_BUILD_UNSIGNED(rr->tlsa.cert_usage)), + JSON_BUILD_PAIR("selector", JSON_BUILD_UNSIGNED(rr->tlsa.selector)), + JSON_BUILD_PAIR("matchingType", JSON_BUILD_UNSIGNED(rr->tlsa.matching_type)), + JSON_BUILD_PAIR("data", JSON_BUILD_HEX(rr->tlsa.data, rr->tlsa.data_size)))); + + case DNS_TYPE_CAA: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(rr->caa.flags)), + JSON_BUILD_PAIR("tag", JSON_BUILD_STRING(rr->caa.tag)), + JSON_BUILD_PAIR("value", JSON_BUILD_OCTESCAPE(rr->caa.value, rr->caa.value_size)))); + + default: + /* Can't provide broken-down format */ + *ret = NULL; + return 0; + } +} + static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 91b1276f72..d558842c0e 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -8,6 +8,7 @@ #include "dns-type.h" #include "hashmap.h" #include "in-addr-util.h" +#include "json.h" #include "list.h" #include "string-util.h" #include "time-util.h" @@ -364,6 +365,11 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i); int dns_txt_item_new_empty(DnsTxtItem **ret); +int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size); + +int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret); +int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret); + void dns_resource_record_hash_func(const DnsResourceRecord *i, struct siphash *state); int dns_resource_record_compare_func(const DnsResourceRecord *x, const DnsResourceRecord *y); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index ee0c9b71e7..eab4c7ee14 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -32,4 +32,3 @@ Resolve.ReadEtcHosts, config_parse_bool, 0, Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label) Resolve.DNSStubListenerExtra, config_parse_dns_stub_listener_extra, 0, offsetof(Manager, dns_extra_stub_listeners) Resolve.CacheFromLocalhost, config_parse_bool, 0, offsetof(Manager, cache_from_localhost) -Resolve.Monitor, config_parse_bool, 0, offsetof(Manager, enable_varlink_notifications) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index dba75bda62..ce5935dc7a 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1043,62 +1043,100 @@ static int manager_ipv6_send( return sendmsg_loop(fd, &mh, 0); } -int send_dns_notification(Manager *m, DnsAnswer *answer, const char *query_name) { - _cleanup_free_ char *normalized = NULL; - DnsResourceRecord *rr; - int ifindex, r; - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; +static int dns_question_to_json(DnsQuestion *q, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL; + DnsResourceKey *key; + int r; + + assert(ret); + + DNS_QUESTION_FOREACH(key, q) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = dns_resource_key_to_json(key, &v); + if (r < 0) + return r; + + r = json_variant_append_array(&l, v); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(l); + return 0; +} + +int manager_monitor_send( + Manager *m, + int state, + int rcode, + int error, + DnsQuestion *question_idna, + DnsQuestion *question_utf8, + DnsQuestion *collected_questions, + DnsAnswer *answer) { + + _cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL; Varlink *connection; + DnsAnswerItem *rri; + int r; assert(m); if (set_isempty(m->varlink_subscription)) return 0; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, answer) { - _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; + /* Merge both questions format into one */ + r = dns_question_merge(question_idna, question_utf8, &merged); + if (r < 0) + return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m"); - if (rr->key->type == DNS_TYPE_A) { - struct in_addr *addr = &rr->a.in_addr; - r = json_build(&entry, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), - JSON_BUILD_PAIR_INTEGER("family", AF_INET), - JSON_BUILD_PAIR_IN4_ADDR("address", addr), - JSON_BUILD_PAIR_STRING("type", "A"))); - } else if (rr->key->type == DNS_TYPE_AAAA) { - struct in6_addr *addr6 = &rr->aaaa.in6_addr; - r = json_build(&entry, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), - JSON_BUILD_PAIR_INTEGER("family", AF_INET6), - JSON_BUILD_PAIR_IN6_ADDR("address", addr6), - JSON_BUILD_PAIR_STRING("type", "AAAA"))); - } else - continue; - if (r < 0) { - log_debug_errno(r, "Failed to build json object: %m"); - continue; - } + /* Convert the current primary question to JSON */ + r = dns_question_to_json(merged, &jquestion); + if (r < 0) + return log_error_errno(r, "Failed to convert question to JSON: %m"); - r = json_variant_append_array(&array, entry); + /* Generate a JSON array of the questions preceeding the current one in the CNAME chain */ + r = dns_question_to_json(collected_questions, &jcollected_questions); + if (r < 0) + return log_error_errno(r, "Failed to convert question to JSON: %m"); + + DNS_ANSWER_FOREACH_ITEM(rri, answer) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + + r = dns_resource_record_to_json(rri->rr, &v); + if (r < 0) + return log_error_errno(r, "Failed to convert answer resource record to JSON: %m"); + + r = dns_resource_record_to_wire_format(rri->rr, /* canonical= */ false); /* don't use DNSSEC canonical format, since it removes casing, but we want that for DNS_SD compat */ + if (r < 0) + return log_error_errno(r, "Failed to generate RR wire format: %m"); + + r = json_build(&w, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(v, "rr", JSON_BUILD_VARIANT(v)), + JSON_BUILD_PAIR("raw", JSON_BUILD_BASE64(rri->rr->wire_format, rri->rr->wire_format_size)), + JSON_BUILD_PAIR_CONDITION(rri->ifindex > 0, "ifindex", JSON_BUILD_INTEGER(rri->ifindex)))); + if (r < 0) + return log_error_errno(r, "Failed to make answer RR object: %m"); + + r = json_variant_append_array(&janswer, w); if (r < 0) return log_debug_errno(r, "Failed to append notification entry to array: %m"); } - if (json_variant_is_blank_object(array)) - return 0; - - r = dns_name_normalize(query_name, 0, &normalized); - if (r < 0) - return log_debug_errno(r, "Failed to normalize query name: %m"); - SET_FOREACH(connection, m->varlink_subscription) { r = varlink_notifyb(connection, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("addresses", - JSON_BUILD_VARIANT(array)), - JSON_BUILD_PAIR("name", JSON_BUILD_STRING(normalized)))); + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))), + JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)), + JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)), + JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)), + JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)), + JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer)))); if (r < 0) - log_debug_errno(r, "Failed to send notification, ignoring: %m"); + log_debug_errno(r, "Failed to send monitor event, ignoring: %m"); } + return 0; } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index a55ac90b8e..98d90e05b3 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -41,7 +41,6 @@ struct Manager { DnsOverTlsMode dns_over_tls_mode; DnsCacheMode enable_cache; bool cache_from_localhost; - bool enable_varlink_notifications; DnsStubListenerMode dns_stub_listener_mode; #if ENABLE_DNS_OVER_TLS @@ -148,7 +147,7 @@ struct Manager { Hashmap *polkit_registry; VarlinkServer *varlink_server; - VarlinkServer *varlink_notification_server; + VarlinkServer *varlink_monitor_server; Set *varlink_subscription; @@ -168,7 +167,7 @@ int manager_start(Manager *m); uint32_t manager_find_mtu(Manager *m); -int send_dns_notification(Manager *m, DnsAnswer *answer, const char *query_name); +int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsQuestion *collected_questions, DnsAnswer *answer); int manager_write(Manager *m, int fd, DnsPacket *p); int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p); diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 4d56e6b018..e344cf6dd6 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -546,6 +546,13 @@ static int vl_method_subscribe_dns_resolves(Varlink *link, JsonVariant *paramete if (json_variant_elements(parameters) > 0) return varlink_error_invalid_parameter(link, parameters); + /* Send a ready message to the connecting client, to indicate that we are now listinening, and all + * queries issued after the point the client sees this will also be reported to the client. */ + r = varlink_notifyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("ready", JSON_BUILD_BOOLEAN(true)))); + if (r < 0) + return log_error_errno(r, "Failed to report monitor to be established: %m"); + r = set_ensure_put(&m->varlink_subscription, NULL, link); if (r < 0) return log_error_errno(r, "Failed to add subscription to set: %m"); @@ -556,13 +563,13 @@ static int vl_method_subscribe_dns_resolves(Varlink *link, JsonVariant *paramete return 1; } -static int varlink_notification_server_init(Manager *m) { +static int varlink_monitor_server_init(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL; int r; assert(m); - if (!m->enable_varlink_notifications || m->varlink_notification_server) + if (m->varlink_monitor_server) return 0; r = varlink_server_new(&server, VARLINK_SERVER_ROOT_ONLY); @@ -590,12 +597,12 @@ static int varlink_notification_server_init(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - m->varlink_notification_server = TAKE_PTR(server); + m->varlink_monitor_server = TAKE_PTR(server); return 0; } -int manager_varlink_init(Manager *m) { +static int varlink_main_server_init(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -630,8 +637,17 @@ int manager_varlink_init(Manager *m) { return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); m->varlink_server = TAKE_PTR(s); + return 0; +} - r = varlink_notification_server_init(m); +int manager_varlink_init(Manager *m) { + int r; + + r = varlink_main_server_init(m); + if (r < 0) + return r; + + r = varlink_monitor_server_init(m); if (r < 0) return r; @@ -642,5 +658,5 @@ void manager_varlink_done(Manager *m) { assert(m); m->varlink_server = varlink_server_unref(m->varlink_server); - m->varlink_notification_server = varlink_server_unref(m->varlink_notification_server); + m->varlink_monitor_server = varlink_server_unref(m->varlink_monitor_server); } diff --git a/src/shared/json.c b/src/shared/json.c index 44a1838a7d..950be9485d 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "float.h" @@ -437,6 +438,19 @@ int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n) { return json_variant_new_stringn(ret, s, k); } +int json_variant_new_base32hex(JsonVariant **ret, const void *p, size_t n) { + _cleanup_free_ char *s = NULL; + + assert_return(ret, -EINVAL); + assert_return(n == 0 || p, -EINVAL); + + s = base32hexmem(p, n, false); + if (!s) + return -ENOMEM; + + return json_variant_new_string(ret, s); +} + int json_variant_new_hex(JsonVariant **ret, const void *p, size_t n) { _cleanup_free_ char *s = NULL; @@ -450,6 +464,19 @@ int json_variant_new_hex(JsonVariant **ret, const void *p, size_t n) { return json_variant_new_stringn(ret, s, n*2); } +int json_variant_new_octescape(JsonVariant **ret, const void *p, size_t n) { + _cleanup_free_ char *s = NULL; + + assert_return(ret, -EINVAL); + assert_return(n == 0 || p, -EINVAL); + + s = octescape(p, n); + if (!s) + return -ENOMEM; + + return json_variant_new_string(ret, s); +} + int json_variant_new_id128(JsonVariant **ret, sd_id128_t id) { return json_variant_new_string(ret, SD_ID128_TO_STRING(id)); } @@ -3543,7 +3570,10 @@ int json_buildv(JsonVariant **ret, va_list ap) { break; } - case _JSON_BUILD_BASE64: { + case _JSON_BUILD_BASE64: + case _JSON_BUILD_BASE32HEX: + case _JSON_BUILD_HEX: + case _JSON_BUILD_OCTESCAPE: { const void *p; size_t n; @@ -3556,37 +3586,10 @@ int json_buildv(JsonVariant **ret, va_list ap) { n = va_arg(ap, size_t); if (current->n_suppress == 0) { - r = json_variant_new_base64(&add, p, n); - if (r < 0) - goto finish; - } - - n_subtract = 1; - - if (current->expect == EXPECT_TOPLEVEL) - current->expect = EXPECT_END; - else if (current->expect == EXPECT_OBJECT_VALUE) - current->expect = EXPECT_OBJECT_KEY; - else - assert(current->expect == EXPECT_ARRAY_ELEMENT); - - break; - } - - case _JSON_BUILD_HEX: { - const void *p; - size_t n; - - if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { - r = -EINVAL; - goto finish; - } - - p = va_arg(ap, const void *); - n = va_arg(ap, size_t); - - if (current->n_suppress == 0) { - r = json_variant_new_hex(&add, p, n); + r = command == _JSON_BUILD_BASE64 ? json_variant_new_base64(&add, p, n) : + command == _JSON_BUILD_BASE32HEX ? json_variant_new_base32hex(&add, p, n) : + command == _JSON_BUILD_HEX ? json_variant_new_hex(&add, p, n) : + json_variant_new_octescape(&add, p, n); if (r < 0) goto finish; } @@ -4208,6 +4211,19 @@ int json_log_internal( NULL); } +static void *dispatch_userdata(const JsonDispatch *p, void *userdata) { + + /* When the the userdata pointer is passed in as NULL, then we'll just use the offset as a literal + * address, and convert it to a pointer. Note that might as well just add the offset to the NULL + * pointer, but UndefinedBehaviourSanitizer doesn't like pointer arithmetics based on NULL pointers, + * hence we code this explicitly here. */ + + if (userdata) + return (uint8_t*) userdata + p->offset; + + return SIZE_TO_PTR(p->offset); +} + int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata) { size_t m; int r, done = 0; @@ -4271,7 +4287,7 @@ int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallba found[p-table] = true; if (p->callback) { - r = p->callback(json_variant_string(key), value, merged_flags, (uint8_t*) userdata + p->offset); + r = p->callback(json_variant_string(key), value, merged_flags, dispatch_userdata(p, userdata)); if (r < 0) { if (merged_flags & JSON_PERMISSIVE) continue; @@ -4404,6 +4420,36 @@ int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlag return 0; } +int json_dispatch_int16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + int16_t *i = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_integer(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + + if (json_variant_integer(variant) < INT16_MIN || json_variant_integer(variant) > INT16_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); + + *i = (int16_t) json_variant_integer(variant); + return 0; +} + +int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + uint16_t *i = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an unsigned integer.", strna(name)); + + if (json_variant_unsigned(variant) > UINT16_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); + + *i = (uint16_t) json_variant_unsigned(variant); + return 0; +} + int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { char **s = ASSERT_PTR(userdata); int r; diff --git a/src/shared/json.h b/src/shared/json.h index c75dfe8741..1992170ed7 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -62,7 +62,9 @@ typedef enum JsonVariantType { int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n); int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n); +int json_variant_new_base32hex(JsonVariant **ret, const void *p, size_t n); int json_variant_new_hex(JsonVariant **ret, const void *p, size_t n); +int json_variant_new_octescape(JsonVariant **ret, const void *p, size_t n); int json_variant_new_integer(JsonVariant **ret, int64_t i); int json_variant_new_unsigned(JsonVariant **ret, uint64_t u); int json_variant_new_real(JsonVariant **ret, double d); @@ -245,7 +247,9 @@ enum { _JSON_BUILD_LITERAL, _JSON_BUILD_STRV, _JSON_BUILD_BASE64, + _JSON_BUILD_BASE32HEX, _JSON_BUILD_HEX, + _JSON_BUILD_OCTESCAPE, _JSON_BUILD_ID128, _JSON_BUILD_BYTE_ARRAY, _JSON_BUILD_HW_ADDR, @@ -280,7 +284,9 @@ enum { #define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, (const char*) { l } #define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, (char**) { l } #define JSON_BUILD_BASE64(p, n) _JSON_BUILD_BASE64, (const void*) { p }, (size_t) { n } +#define JSON_BUILD_BASE32HEX(p, n) _JSON_BUILD_BASE32HEX, (const void*) { p }, (size_t) { n } #define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n } +#define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n } #define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) } #define JSON_BUILD_BYTE_ARRAY(v, n) _JSON_BUILD_BYTE_ARRAY, (const void*) { v }, (size_t) { n } #define JSON_BUILD_CONST_STRING(s) _JSON_BUILD_VARIANT, JSON_VARIANT_STRING_CONST(s) @@ -369,6 +375,8 @@ int json_dispatch_int64(const char *name, JsonVariant *variant, JsonDispatchFlag int json_dispatch_uint64(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_int16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index e782339c4a..cae4c8672a 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -68,6 +68,8 @@ enum { SD_EVENT_PRIORITY_IDLE = 100 }; +#define SD_EVENT_SIGNAL_PROCMASK (1 << 30) + typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); @@ -114,6 +116,7 @@ int sd_event_get_exit_code(sd_event *e, int *code); int sd_event_set_watchdog(sd_event *e, int b); int sd_event_get_watchdog(sd_event *e); int sd_event_get_iteration(sd_event *e, uint64_t *ret); +int sd_event_set_signal_exit(sd_event *e, int b); sd_event_source* sd_event_source_ref(sd_event_source *s); sd_event_source* sd_event_source_unref(sd_event_source *s); diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index e58e1638e7..9f50c6227f 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -99,6 +99,7 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN); dump_glyph(SPECIAL_GLYPH_CIRCLE_ARROW); dump_glyph(SPECIAL_GLYPH_BULLET); + dump_glyph(SPECIAL_GLYPH_ARROW_LEFT); dump_glyph(SPECIAL_GLYPH_ARROW_RIGHT); dump_glyph(SPECIAL_GLYPH_ARROW_UP); dump_glyph(SPECIAL_GLYPH_ARROW_DOWN); diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh index 7823f46583..dac8cbe948 100755 --- a/test/units/testsuite-75.sh +++ b/test/units/testsuite-75.sh @@ -8,72 +8,21 @@ set -o pipefail : >/failed RUN_OUT="$(mktemp)" -NOTIFICATION_SUBSCRIPTION_SCRIPT="/tmp/subscribe.sh" -NOTIFICATION_LOGS="/tmp/notifications.txt" - -at_exit() { - set +e - cat "$NOTIFICATION_LOGS" -} - -trap at_exit EXIT run() { "$@" |& tee "$RUN_OUT" } -run_retry() { - local ntries="${1:?}" - local i +monitor_check_rr() { + local match="${1:?}" - shift - - for ((i = 0; i < ntries; i++)); do - "$@" && return 0 - sleep .5 - done - - return 1 -} - -notification_check_host() { - local host="${1:?}" - local address="${2:?}" - - # Attempt to parse the notification JSON returned over varlink and check - # if it contains the requested record. As this is an async operation, let's - # retry it a couple of times in case it fails. - # - # Example JSON: - # { - # "parameters": { - # "addresses": [ - # { - # "ifindex": 2, - # "family": 2, - # "address": [ - # 10, - # 0, - # 0, - # 121 - # ], - # "type": "A" - # } - # ], - # "name": "untrusted.test" - # }, - # "continues": true - # } - # - # Note: we need to do some post-processing of the $NOTIFICATION_LOGS file, - # since the JSON objects are concatenated with \0 instead of a newline - # shellcheck disable=SC2016 - run_retry 10 jq --slurp \ - --exit-status \ - --arg host "$host" \ - --arg address "$address" \ - '.[] | select(.parameters.name == $host) | .parameters.addresses[] | select(.address | join(".") == $address) | true' \ - <(tr '\0' '\n' <"$NOTIFICATION_LOGS") + # Wait until the first mention of the specified log message is + # displayed. We turn off pipefail for this, since we don't care about the + # lhs of this pipe expression, we only care about the rhs' result to be + # clean + set +o pipefail + journalctl -u resmontest.service -f --full | grep -m1 "$match" + set -o pipefail } ### SETUP ### @@ -97,22 +46,10 @@ DNSSEC=allow-downgrade DNS=10.0.0.1 EOF -# Script to dump DNS notifications to a txt file -cat >$NOTIFICATION_SUBSCRIPTION_SCRIPT < $NOTIFICATION_LOGS -EOF -chmod a+x $NOTIFICATION_SUBSCRIPTION_SCRIPT - { echo "FallbackDNS=" echo "DNSSEC=allow-downgrade" echo "DNSOverTLS=opportunistic" - echo "Monitor=yes" } >>/etc/systemd/resolved.conf ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf # Override the default NTA list, which turns off DNSSEC validation for (among @@ -153,12 +90,8 @@ networkctl status resolvectl status resolvectl log-level debug -# Verify that DNS notifications are enabled (Monitor=yes) -run busctl get-property org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager Monitor -grep -qF 'b true' "$RUN_OUT" - -# Start monitoring DNS notifications -systemd-run $NOTIFICATION_SUBSCRIPTION_SCRIPT +# Start monitoring queries +systemd-run -u resmontest.service -p Type=notify resolvectl monitor # We need to manually propagate the DS records of onlinesign.test. to the parent # zone, since they're generated online @@ -181,7 +114,7 @@ knotc reload # Sanity check run getent -s resolve hosts ns1.unsigned.test grep -qE "^10\.0\.0\.1\s+ns1\.unsigned\.test" "$RUN_OUT" -notification_check_host "ns1.unsigned.test" "10.0.0.1" +monitor_check_rr "ns1.unsigned.test IN A 10.0.0.1" # Issue: https://github.com/systemd/systemd/issues/18812 # PR: https://github.com/systemd/systemd/pull/18896 @@ -274,7 +207,13 @@ grep -qF "; fully validated" "$RUN_OUT" run resolvectl query -t A cname-chain.signed.test grep -qF "follow14.final.signed.test IN A 10.0.0.14" "$RUN_OUT" grep -qF "authenticated: yes" "$RUN_OUT" -notification_check_host "follow10.so.close.signed.test" "10.0.0.14" + +monitor_check_rr "follow10.so.close.signed.test IN CNAME follow11.yet.so.far.signed.test" +monitor_check_rr "follow11.yet.so.far.signed.test IN CNAME follow12.getting.hot.signed.test" +monitor_check_rr "follow12.getting.hot.signed.test IN CNAME follow13.almost.final.signed.test" +monitor_check_rr "follow13.almost.final.signed.test IN CNAME follow14.final.signed.test" +monitor_check_rr "follow14.final.signed.test IN A 10.0.0.14" + # Non-existing RR + CNAME chain run dig +dnssec AAAA cname-chain.signed.test grep -qF "status: NOERROR" "$RUN_OUT" @@ -313,7 +252,7 @@ grep -qF "authenticated: yes" "$RUN_OUT" # Resolve via dbus method run busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager ResolveHostname 'isit' 0 secondsub.onlinesign.test 0 0 grep -qF '10 0 0 134 "secondsub.onlinesign.test"' "$RUN_OUT" -notification_check_host "secondsub.onlinesign.test" "10.0.0.134" +monitor_check_rr "secondsub.onlinesign.test IN A 10.0.0.134" : "--- ZONE: untrusted.test (DNSSEC without propagated DS records) ---" run dig +short untrusted.test @@ -332,5 +271,7 @@ grep -qF "authenticated: no" "$RUN_OUT" #run dig +dnssec this.does.not.exist.untrusted.test #grep -qF "status: NXDOMAIN" "$RUN_OUT" +systemctl stop resmontest.service + touch /testok rm /failed