Merge pull request #24853 from poettering/resolved-monitor-fixes

resolved: various monitor fixes
This commit is contained in:
Luca Boccassi 2022-09-30 15:46:13 +01:00 committed by GitHub
commit 697f082697
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1103 additions and 214 deletions

6
TODO
View file

@ -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

View file

@ -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 {
<variablelist class="dbus-property" generated="True" extra-ref="ResolvConfMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="Monitor"/>
<!--End of Autogenerated section-->
<refsect2>
@ -637,8 +634,6 @@ node /org/freedesktop/resolve1 {
enabled. Possible values are <literal>yes</literal> (enabled), <literal>no</literal> (disabled),
<literal>udp</literal> (only the UDP listener is enabled), and <literal>tcp</literal> (only the TCP
listener is enabled).</para>
<para>The <varname>Monitor</varname> boolean property reports whether DNS monitoring is enabled.</para>
</refsect2>
</refsect1>

View file

@ -199,6 +199,19 @@
automatically, an explicit reverting is not necessary in that case.</para></listitem>
</varlistentry>
<varlistentry>
<term><command>monitor</command></term>
<listitem><para>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 <option>--json=</option> to enable JSON
output.</para></listitem>
</varlistentry>
<xi:include href="systemctl.xml" xpointer="log-level" />
</variablelist>
</refsect1>
@ -379,9 +392,17 @@
query response are shown. Otherwise, this output is suppressed.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
<varlistentry>
<term><option>-j</option></term>
<listitem><para>Short for <option>--json=auto</option></para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
</variablelist>
</refsect1>

View file

@ -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', [], ''],

View file

@ -19,6 +19,7 @@
<refname>sd_event_add_signal</refname>
<refname>sd_event_source_get_signal</refname>
<refname>sd_event_signal_handler_t</refname>
<refname>SD_EVENT_SIGNAL_PROCMASK</refname>
<refpurpose>Add a UNIX process signal event source to an event
loop</refpurpose>
@ -30,6 +31,8 @@
<funcsynopsisinfo><token>typedef</token> struct sd_event_source sd_event_source;</funcsynopsisinfo>
<funcsynopsisinfo><constant>SD_EVENT_SIGNAL_PROCMASK</constant></funcsynopsisinfo>
<funcprototype>
<funcdef>typedef int (*<function>sd_event_signal_handler_t</function>)</funcdef>
<paramdef>sd_event_source *<parameter>s</parameter></paramdef>
@ -50,30 +53,26 @@
<funcdef>int <function>sd_event_source_get_signal</function></funcdef>
<paramdef>sd_event_source *<parameter>source</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><function>sd_event_add_signal()</function> adds a new UNIX
process signal event source to an event loop. The event loop
object is specified in the <parameter>event</parameter> parameter,
and the event source object is returned in the
<parameter>source</parameter> parameter. The
<parameter>signal</parameter> parameter specifies the numeric
signal to be handled (see <citerefentry
<para><function>sd_event_add_signal()</function> adds a new UNIX process signal event source to an event
loop. The event loop object is specified in the <parameter>event</parameter> parameter, and the event
source object is returned in the <parameter>source</parameter> parameter. The
<parameter>signal</parameter> parameter specifies the numeric signal to be handled (see <citerefentry
project='man-pages'><refentrytitle>signal</refentrytitle><manvolnum>7</manvolnum></citerefentry>).</para>
<para>The <parameter>handler</parameter> parameter is a function to call when the signal is received or
<constant>NULL</constant>. The handler function will be passed the <parameter>userdata</parameter>
pointer, which may be chosen freely by the caller. The handler also receives a pointer to a
<structname>signalfd_siginfo</structname> structure containing information about the received signal. See
<citerefentry project='man-pages'><refentrytitle>signalfd</refentrytitle><manvolnum>2</manvolnum></citerefentry>
for further information. The handler may return negative to signal an error (see below), other return
values are ignored. If <parameter>handler</parameter> is <constant>NULL</constant>, a default handler
that calls
<citerefentry
project='man-pages'><refentrytitle>signalfd</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
further information. The handler may return negative to signal an error (see below), other return values
are ignored. If <parameter>handler</parameter> is <constant>NULL</constant>, a default handler that calls
<citerefentry><refentrytitle>sd_event_exit</refentrytitle><manvolnum>3</manvolnum></citerefentry> will be
used.</para>
@ -81,14 +80,18 @@
threads before this function is called (using <citerefentry
project='man-pages'><refentrytitle>sigprocmask</refentrytitle><manvolnum>2</manvolnum></citerefentry> or
<citerefentry
project='man-pages'><refentrytitle>pthread_sigmask</refentrytitle><manvolnum>3</manvolnum></citerefentry>).</para>
project='man-pages'><refentrytitle>pthread_sigmask</refentrytitle><manvolnum>3</manvolnum></citerefentry>). For
convenience, if the special flag <constant>SD_EVENT_SIGNAL_PROCMASK</constant> 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.</para>
<para>By default, the event source is enabled permanently
(<constant>SD_EVENT_ON</constant>), but this may be changed with
<para>By default, the event source is enabled permanently (<constant>SD_EVENT_ON</constant>), but this
may be changed with
<citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
If the handler function returns a negative error code, it will either be disabled after the
invocation, even if the <constant>SD_EVENT_ON</constant> 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 <constant>SD_EVENT_ON</constant> mode was requested before, or it will cause the loop to
terminate, see
<citerefentry><refentrytitle>sd_event_source_set_exit_on_failure</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
</para>

View file

@ -0,0 +1,101 @@
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="sd_event_set_signal_exit" xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>sd_event_set_signal_exit</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>sd_event_set_signal_exit</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>sd_event_set_signal_exit</refname>
<refpurpose>Automatically leave event loop on <constant>SIGINT</constant> and <constant>SIGTERM</constant></refpurpose>
</refnamediv>
<refsynopsisdiv>
<funcsynopsis>
<funcsynopsisinfo>#include &lt;systemd/sd-event.h&gt;</funcsynopsisinfo>
<funcprototype>
<funcdef>int <function>sd_event_set_signal_exit</function></funcdef>
<paramdef>sd_event *<parameter>event</parameter></paramdef>
<paramdef>int b</paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><function>sd_event_set_signal_exit()</function> may be used to ensure the event loop terminates
once a <constant>SIGINT</constant> or <constant>SIGTERM</constant> signal is received. It is a
convencience wrapper around invocations of
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>
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).</para>
<para>If the parameter <parameter>b</parameter> is specified as true, the event loop will terminate on
<constant>SIGINT</constant> and <constant>SIGTERM</constant>. 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.</para>
<para>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.</para>
</refsect1>
<refsect1>
<title>Return Value</title>
<para><function>sd_event_set_signal_exit()</function> 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.</para>
<refsect2>
<title>Errors</title>
<para>Returned errors may indicate the following problems:</para>
<variablelist>
<varlistentry>
<term><constant>-ECHILD</constant></term>
<listitem><para>The event loop has been created in a different process.</para></listitem>
</varlistentry>
<varlistentry>
<term><constant>-EINVAL</constant></term>
<listitem><para>The passed event loop object was invalid.</para></listitem>
</varlistentry>
</variablelist>
</refsect2>
</refsect1>
<xi:include href="libsystemd-pkgconfig.xml" />
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd-event</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_event_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sd_event_add_signal</refentrytitle><manvolnum>3</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View file

@ -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 */

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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 },
{}
};

View file

@ -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),

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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) \

View file

@ -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",

View file

@ -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);

View file

@ -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)

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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 <<EOF
#!/bin/sh
printf '
{
"method": "io.systemd.Resolve.Monitor.SubscribeQueryResults",
"more": true
}\0' | nc -U /run/systemd/resolve/io.systemd.Resolve.Monitor > $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