mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-23 17:34:00 +03:00
resolvectl: add new "monitor" verb
This commit is contained in:
parent
72c2d39ecb
commit
fffbf1dc99
@ -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>
|
||||
|
||||
|
@ -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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user