From 24fe5e9807a1da8542a182fa65716e82e9a49da7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 11:34:46 +0200 Subject: [PATCH 01/16] glyph-util: add left arrow special glyph entry --- src/basic/glyph-util.c | 2 ++ src/basic/glyph-util.h | 1 + src/test/test-locale-util.c | 1 + 3 files changed, 4 insertions(+) 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/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); From e085625f099ce8cf39be512b411d7032fc9af219 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 11:35:47 +0200 Subject: [PATCH 02/16] json: add dispatchers for 16bit integers --- src/shared/json.c | 30 ++++++++++++++++++++++++++++++ src/shared/json.h | 2 ++ 2 files changed, 32 insertions(+) diff --git a/src/shared/json.c b/src/shared/json.c index 44a1838a7d..87e87091b0 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -4404,6 +4404,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..06311cbf4b 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -369,6 +369,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); From 84738d864b6d96389ae6c5500be9a06f5a2d5927 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 17:13:00 +0200 Subject: [PATCH 03/16] json: add helper for json builder for octescape/base32hex These encodings for binary data are mandated by DNS RFCs, so let's give make them nice and easy to use with json builder logic. --- src/shared/json.c | 67 +++++++++++++++++++++++++---------------------- src/shared/json.h | 6 +++++ 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/shared/json.c b/src/shared/json.c index 87e87091b0..40c6f723ee 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; } diff --git a/src/shared/json.h b/src/shared/json.h index 06311cbf4b..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) From 0b8218b901a44f3e6a7d76f25038fb0526e8b1d7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 30 Sep 2022 14:09:54 +0200 Subject: [PATCH 04/16] json: explicitly support offsets relative to NULL when dispatching Let's trick out UndefinedBehaviourSanitizer: https://github.com/systemd/systemd/pull/24853#issuecomment-1263380745 --- src/shared/json.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/shared/json.c b/src/shared/json.c index 40c6f723ee..950be9485d 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -4211,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; @@ -4274,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; From 897448bd3753eab2c7b411221cfc33b283ae67a5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 11:39:25 +0200 Subject: [PATCH 05/16] sd-event: if signal nr has high bit set sd_event_add_signal() auto-block it via sigprocmask() So far we expected callers to block the signals manually. Which is usually a good idea, since they should do that before forking off threads and similar. But let's add a mode where we automatically block it for the caller, to simplify things. --- man/rules/meson.build | 4 +- man/sd_event_add_signal.xml | 39 +++++++------- src/libsystemd/sd-event/event-source.h | 1 + src/libsystemd/sd-event/sd-event.c | 71 +++++++++++++++++++++++--- src/systemd/sd-event.h | 2 + 5 files changed, 90 insertions(+), 27 deletions(-) diff --git a/man/rules/meson.build b/man/rules/meson.build index 2925dadc1e..7f4a42b139 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', 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/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..890b62b1f9 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -813,6 +813,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 +854,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 +1343,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 +1393,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)); diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index e782339c4a..d2886b8038 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); From baf3fdec27f0b3a1f3d39c7def2a778824cbee51 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 11:42:05 +0200 Subject: [PATCH 06/16] sd-event: add helper for exiting event loop on SIGTERM/SIGINT In many (most?) of our event loops we want to exit once SIGTERM/SIGINT is seen. Add a common helper for that, that does the right things in a single call. --- man/rules/meson.build | 1 + man/sd_event_set_signal_exit.xml | 101 +++++++++++++++++++++++++++++ src/libsystemd/libsystemd.sym | 2 + src/libsystemd/sd-event/sd-event.c | 57 ++++++++++++++++ src/systemd/sd-event.h | 1 + 5 files changed, 162 insertions(+) create mode 100644 man/sd_event_set_signal_exit.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index 7f4a42b139..4a497d59c4 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -583,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_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/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/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 890b62b1f9..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); @@ -4613,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/systemd/sd-event.h b/src/systemd/sd-event.h index d2886b8038..cae4c8672a 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -116,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); From 227e127943c477d9771f2c10a25cbc949a79d7ab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Sep 2022 14:28:32 +0200 Subject: [PATCH 07/16] =?UTF-8?q?resolved:=20rename=20'notification'=20?= =?UTF-8?q?=E2=86=92=20'monitor'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We expose this externally already as "monitor", name it internally the same way. (Which is nice since it mirrors busctl monitor) --- src/resolve/resolved-dns-query.c | 2 +- src/resolve/resolved-manager.c | 2 +- src/resolve/resolved-manager.h | 4 ++-- src/resolve/resolved-varlink.c | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 50c0e05ca6..edd62fa068 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -586,7 +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->answer, dns_question_first_name(q->question_utf8)); dns_query_stop(q); if (q->complete) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index dba75bda62..df3a3fff04 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1043,7 +1043,7 @@ static int manager_ipv6_send( return sendmsg_loop(fd, &mh, 0); } -int send_dns_notification(Manager *m, DnsAnswer *answer, const char *query_name) { +int manager_monitor_send(Manager *m, DnsAnswer *answer, const char *query_name) { _cleanup_free_ char *normalized = NULL; DnsResourceRecord *rr; int ifindex, r; diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index a55ac90b8e..75cd432724 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -148,7 +148,7 @@ struct Manager { Hashmap *polkit_registry; VarlinkServer *varlink_server; - VarlinkServer *varlink_notification_server; + VarlinkServer *varlink_monitor_server; Set *varlink_subscription; @@ -168,7 +168,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, DnsAnswer *answer, const char *query_name); 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..1389737960 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -556,13 +556,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->enable_varlink_notifications || m->varlink_monitor_server) return 0; r = varlink_server_new(&server, VARLINK_SERVER_ROOT_ONLY); @@ -590,7 +590,7 @@ 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; } @@ -631,7 +631,7 @@ int manager_varlink_init(Manager *m) { m->varlink_server = TAKE_PTR(s); - r = varlink_notification_server_init(m); + r = varlink_monitor_server_init(m); if (r < 0) return r; @@ -642,5 +642,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); } From b25d819aee10b79a1c972d25be81a238448134dd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Sep 2022 14:29:18 +0200 Subject: [PATCH 08/16] resolved: don't make monitoring optional The socket is only accessible to privileged clients anyway, no need to add another (user unfriendly) restriction via opt-in setting. let's just allow this for privileged clients, mirroring "busctl monitor", or "tcpdump" and similar, which all just work if you have privs. (This does not break API, since we never did a release witht the "Monitor" dbus property or config setting in place, i.e. with cb456374e096f0ebe9b70d7ddd98e16a4be24ee6) --- man/org.freedesktop.resolve1.xml | 5 ----- src/resolve/resolved-bus.c | 1 - src/resolve/resolved-gperf.gperf | 1 - src/resolve/resolved-manager.h | 1 - src/resolve/resolved-varlink.c | 2 +- 5 files changed, 1 insertion(+), 9 deletions(-) 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/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-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.h b/src/resolve/resolved-manager.h index 75cd432724..844405c252 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 diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 1389737960..fd3f6262a7 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -562,7 +562,7 @@ static int varlink_monitor_server_init(Manager *m) { assert(m); - if (!m->enable_varlink_notifications || m->varlink_monitor_server) + if (m->varlink_monitor_server) return 0; r = varlink_server_new(&server, VARLINK_SERVER_ROOT_ONLY); From b497a958d651dc41c6c0551842a52c8a59b85930 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Sep 2022 14:50:53 +0200 Subject: [PATCH 09/16] resolved: split out main server listening code from manager_varlink_init() Just some minor refactoring, to make binding the two sockets (main + minitor) symmetric --- src/resolve/resolved-varlink.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index fd3f6262a7..cde406f40e 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -595,7 +595,7 @@ static int varlink_monitor_server_init(Manager *m) { 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,6 +630,15 @@ 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; +} + +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) From ab26cdf730e7d5907d9c93cc24f9e7bad2dbbf18 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 12:44:43 +0200 Subject: [PATCH 10/16] resolved: split out helper for allocating DnsResourceRecord object from raw data No change in behaviour, just some refactoring. --- src/resolve/resolvectl.c | 13 +------------ src/resolve/resolved-dns-rr.c | 17 +++++++++++++++++ src/resolve/resolved-dns-rr.h | 2 ++ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index bda7ca2add..c069763e15 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -395,20 +395,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"); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index bf1b342b8d..6fdc3b4faf 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1832,6 +1832,23 @@ 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); +} + 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..1eaa8e566b 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -364,6 +364,8 @@ 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); + void dns_resource_record_hash_func(const DnsResourceRecord *i, struct siphash *state); int dns_resource_record_compare_func(const DnsResourceRecord *x, const DnsResourceRecord *y); From 1482c86a50cb9dadedd4cef6829717ebb3dfdfb1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 17:17:23 +0200 Subject: [PATCH 11/16] resolved: add generic formatters for RRs into JSON For now we can use it to send broken-down records of JSON via the monitor logic, but one day we can certainly reuse for dumping the caches, or to implement a ResolveRecord() varlink call one day. --- src/resolve/resolved-dns-rr.c | 242 ++++++++++++++++++++++++++++++++++ src/resolve/resolved-dns-rr.h | 4 + 2 files changed, 246 insertions(+) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 6fdc3b4faf..8123ca1f98 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1849,6 +1849,248 @@ int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, 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 1eaa8e566b..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" @@ -366,6 +367,9 @@ 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); From 4d593fb151317f9054f308f4656cce63c1abbe52 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 29 Sep 2022 18:26:44 +0200 Subject: [PATCH 12/16] resolved: add dns_question_merge() helper Very similar to dns_answer_merge(), but for DnsQuestion objects instead of DnsAnswer. --- src/resolve/resolved-dns-question.c | 58 +++++++++++++++++++++++++++++ src/resolve/resolved-dns-question.h | 2 + 2 files changed, 60 insertions(+) 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) \ From 72c2d39ecb2fcd4d6c78b65c56b7a9eab02a3048 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 12:46:09 +0200 Subject: [PATCH 13/16] resolved: beef up monitor protocol, include full query info --- src/resolve/resolved-dns-query.c | 32 ++++++++- src/resolve/resolved-dns-query.h | 5 ++ src/resolve/resolved-manager.c | 116 ++++++++++++++++++++----------- src/resolve/resolved-manager.h | 2 +- src/resolve/resolved-varlink.c | 7 ++ 5 files changed, 120 insertions(+), 42 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index edd62fa068..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) manager_monitor_send(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-manager.c b/src/resolve/resolved-manager.c index df3a3fff04..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 manager_monitor_send(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 844405c252..98d90e05b3 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -167,7 +167,7 @@ int manager_start(Manager *m); uint32_t manager_find_mtu(Manager *m); -int manager_monitor_send(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 cde406f40e..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"); From fffbf1dc996691298fd5e53f15c98e7d7257235a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 12:46:21 +0200 Subject: [PATCH 14/16] resolvectl: add new "monitor" verb --- man/resolvectl.xml | 23 +++- src/resolve/resolvectl.c | 251 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 270 insertions(+), 4 deletions(-) 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/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index c069763e15..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; @@ -2503,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:"); @@ -2608,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" @@ -2636,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(), @@ -2987,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[] = { @@ -3009,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 }, {} }; @@ -3017,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': @@ -3192,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; @@ -3235,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 }, {} }; From b968890a87075ae43124e154b6c967e61dc57672 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 30 Sep 2022 10:04:37 +0200 Subject: [PATCH 15/16] test: rework resolved monitoring test Let's remove some sleep loops, and instead: 1. Use Type=notify to wait until "resolvectl monitor" successfully installed its monitor, so that we know that queries enqueued later will definitely be seen. 2. Use "grep -m1" to watch "journalctl -f" output to wait precisely for the RR data we want to see, and immediately exit. This shortens code quite a bit, and should make it more robust. --- test/units/testsuite-75.sh | 103 ++++++++----------------------------- 1 file changed, 22 insertions(+), 81 deletions(-) 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 From de76643b3fbd49dbd3713e59d59e206fc28cc3f0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 30 Sep 2022 14:14:33 +0200 Subject: [PATCH 16/16] update TODO --- TODO | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO b/TODO index f6be4ec545..a52cac5629 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