From: Zbigniew Jędrzejewski-Szmek Date: Thu, 7 May 2026 07:31:28 +0000 (+0200) Subject: resolvectl: move verb implementations to match order in --help X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=d865863929bd010392330a4358b005c9eb902880;p=thirdparty%2Fsystemd.git resolvectl: move verb implementations to match order in --help --- diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index fdce9134477..4088c39b1da 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -122,6 +122,18 @@ static const char* const status_mode_json_field_table[_STATUS_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(status_mode_json_field, StatusMode); +static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { + int r; + + if (value) { + r = parse_boolean(value); + if (r >= 0) + return strv_extendf(strv, "%s%s", plus_minus(r), name); + } + + return strv_extendf(strv, "%s=%s", name, value ?: "???"); +} + static int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; int r; @@ -1163,430 +1175,114 @@ static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } -static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(table_unrefp) Table *table = NULL; - sd_json_variant *reply = NULL; +static int varlink_dump_dns_configuration(sd_json_variant **ret) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + sd_json_variant *v; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(ret); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); if (r < 0) return r; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - - struct statistics { - sd_json_variant *transactions; - sd_json_variant *cache; - sd_json_variant *dnssec; - } statistics; - - static const sd_json_dispatch_field statistics_dispatch_table[] = { - { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, - { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); - if (r < 0) - return r; + v = sd_json_variant_by_key(reply, "configuration"); - struct transactions { - uint64_t n_current_transactions; - uint64_t n_transactions_total; - uint64_t n_timeouts_total; - uint64_t n_timeouts_served_stale_total; - uint64_t n_failure_responses_total; - uint64_t n_failure_responses_served_stale_total; - } transactions; + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - static const sd_json_dispatch_field transactions_dispatch_table[] = { - { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, - { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, - { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, - { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, - { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, - { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, - {}, - }; + TAKE_PTR(reply); + *ret = sd_json_variant_ref(v); + return 0; +} - r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); - if (r < 0) - return r; +static int status_json_filter_links(sd_json_variant **configuration, char **links) { + _cleanup_set_free_ Set *links_by_index = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + int r; - struct cache { - uint64_t cache_size; - uint64_t n_cache_hit; - uint64_t n_cache_miss; - } cache; + assert(configuration); - static const sd_json_dispatch_field cache_dispatch_table[] = { - { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, - { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, - { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, - {}, - }; + if (links) + STRV_FOREACH(ifname, links) { + int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); + if (ifindex < 0) + return ifindex; - r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); - if (r < 0) - return r; + r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); + if (r < 0) + return r; + } - struct dnsssec { - uint64_t n_dnssec_secure; - uint64_t n_dnssec_insecure; - uint64_t n_dnssec_bogus; - uint64_t n_dnssec_indeterminate; - } dnsssec; + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); - static const sd_json_dispatch_field dnssec_dispatch_table[] = { - { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, - { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, - { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, - { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, - {}, - }; + if (links_by_index) { + if (ifindex <= 0) + /* Possibly invalid, but most likely unset because this is global + * or delegate configuration. */ + continue; - r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); - if (r < 0) - return r; + if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) + continue; - table = table_new_vertical(); - if (!table) - return log_oom(); + } else if (ifindex == LOOPBACK_IFINDEX) + /* By default, exclude the loopback interface. */ + continue; - r = table_add_many(table, - TABLE_STRING, "Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Transactions", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_current_transactions, - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_FIELD, "Total Transactions", - TABLE_UINT64, transactions.n_transactions_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Cache", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Cache Size", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, cache.cache_size, - TABLE_FIELD, "Cache Hits", - TABLE_UINT64, cache.n_cache_hit, - TABLE_FIELD, "Cache Misses", - TABLE_UINT64, cache.n_cache_miss, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Failure Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Total Timeouts", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_timeouts_total, - TABLE_FIELD, "Total Timeouts (Stale Data Served)", - TABLE_UINT64, transactions.n_timeouts_served_stale_total, - TABLE_FIELD, "Total Failure Responses", - TABLE_UINT64, transactions.n_failure_responses_total, - TABLE_FIELD, "Total Failure Responses (Stale Data Served)", - TABLE_UINT64, transactions.n_failure_responses_served_stale_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "DNSSEC Verdicts", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Secure", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, dnsssec.n_dnssec_secure, - TABLE_FIELD, "Insecure", - TABLE_UINT64, dnsssec.n_dnssec_insecure, - TABLE_FIELD, "Bogus", - TABLE_UINT64, dnsssec.n_dnssec_bogus, - TABLE_FIELD, "Indeterminate", - TABLE_UINT64, dnsssec.n_dnssec_indeterminate - ); - if (r < 0) - return table_log_add_error(r); + r = sd_json_variant_append_array(&v, w); + if (r < 0) + return r; + } - return table_print_or_warn(table); + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); + return 0; } -static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_json_variant *reply = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; +static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + const char *field; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_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"); + assert(configuration); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.ResetStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return r; + field = status_mode_json_field_to_string(mode); + if (!field) + /* Nothing to filter for this mode. */ + return 0; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + /* Always include identifier fields like ifname or delegate, and include the requested + * field even if it is empty in the configuration. */ + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), + SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); + if (r < 0) + return r; + } + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); return 0; } -static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { + bool global; int r; - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); + assert(s); + assert(ret); - return 0; -} - -static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); - - return 0; -} - -static int status_print_strv(DNSConfiguration *c, char **p) { - const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ - int pos1, pos2; - - assert(c); - - if (c->ifname) - printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); - else if (c->delegate) - printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); - else - printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); - - size_t cols = columns(), position = pos2 - pos1 + 2; - - STRV_FOREACH(i, p) { - size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). - * If that happens, we'll just print one item per line. */ - - if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { - printf(" %s", *i); - position = size_add(size_add(position, 1), our_len); - } else { - printf("\n%*s%s", (int) indent, "", *i); - position = size_add(our_len, indent); - } - } - - printf("\n"); - - return 0; -} - -static void status_print_string(DNSConfiguration *c, const char *p) { - assert(c); - - if (c->ifname) - printf("%sLink %i (%s)%s: %s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal(), - p); - else if (c->delegate) - printf("%sDelegate %s%s: %s\n", - ansi_highlight(), - c->delegate, - ansi_normal(), - p); - else - printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); -} - -static void status_print_header(DNSConfiguration *c) { - assert(c); - - if (c->ifname) - printf("%sLink %i (%s)%s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal()); - else if (c->delegate) - printf("%sDelegate %s%s\n", - ansi_highlight(), - c->delegate, - ansi_normal()); - else - printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); -} - -static int dump_list(Table *table, const char *field, char * const *l) { - int r; - - if (strv_isempty(l)) - return 0; - - r = table_add_many(table, - TABLE_FIELD, field, - TABLE_STRV_WRAPPED, l); - if (r < 0) - return table_log_add_error(r); - - return 0; -} - -static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { - int r; - - if (value) { - r = parse_boolean(value); - if (r >= 0) - return strv_extendf(strv, "%s%s", plus_minus(r), name); - } - - return strv_extendf(strv, "%s=%s", name, value ?: "???"); -} - -static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - const char *field; - int r; - - assert(configuration); - - field = status_mode_json_field_to_string(mode); - if (!field) - /* Nothing to filter for this mode. */ - return 0; - - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - /* Always include identifier fields like ifname or delegate, and include the requested - * field even if it is empty in the configuration. */ - r = sd_json_variant_append_arraybo( - &v, - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), - SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); - if (r < 0) - return r; - } - - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; -} - -static int status_json_filter_links(sd_json_variant **configuration, char **links) { - _cleanup_set_free_ Set *links_by_index = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - int r; - - assert(configuration); - - if (links) - STRV_FOREACH(ifname, links) { - int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); - if (ifindex < 0) - return ifindex; - - r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); - if (r < 0) - return r; - } - - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); - - if (links_by_index) { - if (ifindex <= 0) - /* Possibly invalid, but most likely unset because this is global - * or delegate configuration. */ - continue; - - if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) - continue; - - } else if (ifindex == LOOPBACK_IFINDEX) - /* By default, exclude the loopback interface. */ - continue; - - r = sd_json_variant_append_array(&v, w); - if (r < 0) - return r; - } - - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; -} - -static int varlink_dump_dns_configuration(sd_json_variant **ret) { - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; - sd_json_variant *v; - int r; - - assert(ret); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); - if (r < 0) - return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); - - r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); - if (r < 0) - return r; - - v = sd_json_variant_by_key(reply, "configuration"); - - if (!sd_json_variant_is_array(v)) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - - TAKE_PTR(reply); - *ret = sd_json_variant_ref(v); - return 0; -} - -static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { - bool global; - int r; - - assert(s); - assert(ret); - - global = !(configuration->ifindex > 0 || configuration->delegate); + global = !(configuration->ifindex > 0 || configuration->delegate); if (global && s->ifindex > 0 && s->ifindex != LOOPBACK_IFINDEX) { /* This one has an (non-loopback) ifindex set, and we were told to suppress those. Hence do so. */ @@ -1735,27 +1431,113 @@ static int format_scopes_string(DNSConfiguration *configuration, char **ret) { return 0; } -static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { - _cleanup_(table_unrefp) Table *table = NULL; - int r; - - assert(configuration); - - pager_open(arg_pager_flags); +static void status_print_header(DNSConfiguration *c) { + assert(c); - bool global = !(configuration->ifindex > 0 || configuration->delegate); - if (mode == STATUS_DNS) { - _cleanup_strv_free_ char **l = NULL; - r = format_dns_servers(configuration, configuration->dns_servers, &l); - if (r < 0) - return r; + if (c->ifname) + printf("%sLink %i (%s)%s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal()); + else if (c->delegate) + printf("%sDelegate %s%s\n", + ansi_highlight(), + c->delegate, + ansi_normal()); + else + printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); +} - return status_print_strv(configuration, l); +static void status_print_string(DNSConfiguration *c, const char *p) { + assert(c); - } else if (mode == STATUS_DOMAIN) { - _cleanup_strv_free_ char **l = NULL; - r = format_search_domains(configuration, configuration->search_domains, &l); - if (r < 0) + if (c->ifname) + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal(), + p); + else if (c->delegate) + printf("%sDelegate %s%s: %s\n", + ansi_highlight(), + c->delegate, + ansi_normal(), + p); + else + printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); +} + +static int status_print_strv(DNSConfiguration *c, char **p) { + const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ + int pos1, pos2; + + assert(c); + + if (c->ifname) + printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); + else if (c->delegate) + printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); + else + printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); + + size_t cols = columns(), position = pos2 - pos1 + 2; + + STRV_FOREACH(i, p) { + size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). + * If that happens, we'll just print one item per line. */ + + if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { + printf(" %s", *i); + position = size_add(size_add(position, 1), our_len); + } else { + printf("\n%*s%s", (int) indent, "", *i); + position = size_add(our_len, indent); + } + } + + printf("\n"); + + return 0; +} + +static int dump_list(Table *table, const char *field, char * const *l) { + int r; + + if (strv_isempty(l)) + return 0; + + r = table_add_many(table, + TABLE_FIELD, field, + TABLE_STRV_WRAPPED, l); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + assert(configuration); + + pager_open(arg_pager_flags); + + bool global = !(configuration->ifindex > 0 || configuration->delegate); + if (mode == STATUS_DNS) { + _cleanup_strv_free_ char **l = NULL; + r = format_dns_servers(configuration, configuration->dns_servers, &l); + if (r < 0) + return r; + + return status_print_strv(configuration, l); + + } else if (mode == STATUS_DOMAIN) { + _cleanup_strv_free_ char **l = NULL; + r = format_search_domains(configuration, configuration->search_domains, &l); + if (r < 0) return r; return status_print_strv(configuration, l); @@ -1985,165 +1767,193 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return status_full(STATUS_ALL, strv_skip(argv, 1)); } -static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; +static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(table_unrefp) Table *table = NULL; + sd_json_variant *reply = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, extended ? "SetLinkDNSEx" : "SetLinkDNS"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", arg_ifindex); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = sd_bus_message_open_container(req, 'a', extended ? "(iayqs)" : "(iay)"); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); if (r < 0) - return bus_log_create_error(r); - - /* If only argument is the empty string, then call SetLinkDNS() with an - * empty list, which will clear the list of domains for an interface. */ - if (!strv_equal(dns, STRV_MAKE(""))) - STRV_FOREACH(p, dns) { - _cleanup_free_ char *name = NULL; - struct in_addr_data data; - uint16_t port; - int ifindex; - - r = in_addr_port_ifindex_name_from_string_auto(*p, &data.family, &data.address, &port, &ifindex, &name); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS server address: %s", *p); - - if (ifindex != 0 && ifindex != arg_ifindex) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifindex: %i", ifindex); - - r = sd_bus_message_open_container(req, 'r', extended ? "iayqs" : "iay"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", data.family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); - if (r < 0) - return bus_log_create_error(r); + return r; - if (extended) { - r = sd_bus_message_append(req, "q", port); - if (r < 0) - return bus_log_create_error(r); + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - r = sd_bus_message_append(req, "s", name); - if (r < 0) - return bus_log_create_error(r); - } + struct statistics { + sd_json_variant *transactions; + sd_json_variant *cache; + sd_json_variant *dnssec; + } statistics; - r = sd_bus_message_close_container(req); - if (r < 0) - return bus_log_create_error(r); - } + static const sd_json_dispatch_field statistics_dispatch_table[] = { + { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, + { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, + {}, + }; - r = sd_bus_message_close_container(req); + r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_call(bus, req, 0, error, NULL); - if (r < 0 && extended && sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_METHOD)) { - sd_bus_error_free(error); - return call_dns(bus, dns, locator, error, false); - } - return r; -} + struct transactions { + uint64_t n_current_transactions; + uint64_t n_transactions_total; + uint64_t n_timeouts_total; + uint64_t n_timeouts_served_stale_total; + uint64_t n_failure_responses_total; + uint64_t n_failure_responses_served_stale_total; + } transactions; -static int verb_dns(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; + static const sd_json_dispatch_field transactions_dispatch_table[] = { + { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, + { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, + { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, + { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, + { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, + { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, + {}, + }; - r = acquire_bus(&bus); + r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DNS); + struct cache { + uint64_t cache_size; + uint64_t n_cache_hit; + uint64_t n_cache_miss; + } cache; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNS); + static const sd_json_dispatch_field cache_dispatch_table[] = { + { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, + { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, + { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, + {}, + }; - char **args = strv_skip(argv, 2); - r = call_dns(bus, args, bus_resolve_mgr, &error, true); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); + if (r < 0) + return r; - r = call_dns(bus, args, bus_network_mgr, &error, true); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + struct dnsssec { + uint64_t n_dnssec_secure; + uint64_t n_dnssec_insecure; + uint64_t n_dnssec_bogus; + uint64_t n_dnssec_indeterminate; + } dnsssec; - return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); - } + static const sd_json_dispatch_field dnssec_dispatch_table[] = { + { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, + { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, + { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, + { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, + {}, + }; - return 0; -} + r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); + if (r < 0) + return r; -static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; - int r; + table = table_new_vertical(); + if (!table) + return log_oom(); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = bus_message_new_method_call(bus, &req, locator, "SetLinkDomains"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", arg_ifindex); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(req, 'a', "(sb)"); + r = table_add_many(table, + TABLE_STRING, "Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Transactions", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_current_transactions, + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_FIELD, "Total Transactions", + TABLE_UINT64, transactions.n_transactions_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Cache", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Cache Size", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, cache.cache_size, + TABLE_FIELD, "Cache Hits", + TABLE_UINT64, cache.n_cache_hit, + TABLE_FIELD, "Cache Misses", + TABLE_UINT64, cache.n_cache_miss, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Failure Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Total Timeouts", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_timeouts_total, + TABLE_FIELD, "Total Timeouts (Stale Data Served)", + TABLE_UINT64, transactions.n_timeouts_served_stale_total, + TABLE_FIELD, "Total Failure Responses", + TABLE_UINT64, transactions.n_failure_responses_total, + TABLE_FIELD, "Total Failure Responses (Stale Data Served)", + TABLE_UINT64, transactions.n_failure_responses_served_stale_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "DNSSEC Verdicts", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Secure", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, dnsssec.n_dnssec_secure, + TABLE_FIELD, "Insecure", + TABLE_UINT64, dnsssec.n_dnssec_insecure, + TABLE_FIELD, "Bogus", + TABLE_UINT64, dnsssec.n_dnssec_bogus, + TABLE_FIELD, "Indeterminate", + TABLE_UINT64, dnsssec.n_dnssec_indeterminate + ); if (r < 0) - return bus_log_create_error(r); + return table_log_add_error(r); - /* If only argument is the empty string, then call SetLinkDomains() with an - * empty list, which will clear the list of domains for an interface. */ - if (!strv_equal(domain, STRV_MAKE(""))) - STRV_FOREACH(p, domain) { - const char *n; + return table_print_or_warn(table); +} - n = **p == '~' ? *p + 1 : *p; +static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_json_variant *reply = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; - r = dns_name_is_valid(n); - if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", n); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Domain not valid: %s", - n); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_bus_message_append(req, "(sb)", n, **p == '~'); - if (r < 0) - return bus_log_create_error(r); - } + r = sd_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 = sd_bus_message_close_container(req); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.ResetStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); if (r < 0) - return bus_log_create_error(r); + return r; - return sd_bus_call(bus, req, 0, error, NULL); + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + + return 0; } -static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) { +static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2152,295 +1962,268 @@ static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DOMAIN); - - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DOMAIN); - - char **args = strv_skip(argv, 2); - r = call_domain(bus, args, bus_resolve_mgr, &error); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); - - r = call_domain(bus, args, bus_network_mgr, &error); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - - return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); - } + r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); return 0; } -static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *userdata) { +static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r, b; + int r; r = acquire_bus(&bus); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DEFAULT_ROUTE); + r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DEFAULT_ROUTE); + return 0; +} - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); +static int print_question(char prefix, const char *color, sd_json_variant *question) { + sd_json_variant *q = NULL; + int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(color); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + JSON_VARIANT_ARRAY_FOREACH(q, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + char buf[DNS_RESOURCE_KEY_STRING_MAX]; - r = bus_call_method(bus, bus_network_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = dns_resource_key_from_json(q, &key); + if (r < 0) { + log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); + continue; + } - return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + printf("%s%s %c%s: %s\n", + color, + glyph(GLYPH_ARROW_RIGHT), + prefix, + ansi_normal(), + dns_resource_key_to_string(key, buf, sizeof(buf))); } return 0; } -static int verb_llmnr(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *global_llmnr_support_str = NULL; - ResolveSupport global_llmnr_support, llmnr_support; +static int print_answer(sd_json_variant *answer) { + sd_json_variant *a; int r; - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_LLMNR); - - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_LLMNR); + JSON_VARIANT_ARRAY_FOREACH(a, answer) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *d = NULL; + sd_json_variant *jraw; + const char *s; + size_t l; - llmnr_support = resolve_support_from_string(argv[2]); - if (llmnr_support < 0) - return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]); + jraw = sd_json_variant_by_key(a, "raw"); + if (!jraw) { + log_warning("Received monitor answer lacking valid raw data, ignoring."); + continue; + } - r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str); - if (r < 0) - return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r)); + r = sd_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; + } - global_llmnr_support = resolve_support_from_string(global_llmnr_support_str); - if (global_llmnr_support < 0) - return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str); + r = dns_resource_record_new_from_raw(&rr, d, l); + if (r < 0) { + log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); + continue; + } - if (global_llmnr_support < llmnr_support) - log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".", - argv[2], arg_ifname, global_llmnr_support_str); + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + printf("%s%s A%s: %s\n", + ansi_highlight_yellow(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + s); + } - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + return 0; +} - r = bus_call_method(bus, bus_network_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; +typedef struct MonitorQueryParams { + sd_json_variant *question; + sd_json_variant *answer; + sd_json_variant *collected_questions; + int rcode; + int error; + int ede_code; + const char *state; + const char *result; + const char *ede_msg; +} MonitorQueryParams; - return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); - } +static void monitor_query_params_done(MonitorQueryParams *p) { + assert(p); - return 0; + sd_json_variant_unref(p->question); + sd_json_variant_unref(p->answer); + sd_json_variant_unref(p->collected_questions); } -static int verb_mdns(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *global_mdns_support_str = NULL; - ResolveSupport global_mdns_support, mdns_support; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_MDNS); +static void monitor_query_dump(sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, + { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, + { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, + { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, + { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, + {} + }; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_MDNS); + _cleanup_(monitor_query_params_done) MonitorQueryParams p = { + .rcode = -1, + .ede_code = -1, + }; - mdns_support = resolve_support_from_string(argv[2]); - if (mdns_support < 0) - return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]); + assert(v); - r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str); - if (r < 0) - return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r)); + if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) + return; - global_mdns_support = resolve_support_from_string(global_mdns_support_str); - if (global_mdns_support < 0) - return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str); + /* First show the current question */ + print_question('Q', ansi_highlight_cyan(), p.question); - if (global_mdns_support < mdns_support) - log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".", - argv[2], arg_ifname, global_mdns_support_str); + /* And then show the questions that led to this one in case this was a CNAME chain */ + print_question('C', ansi_highlight_grey(), p.collected_questions); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + printf("%s%s S%s: %s", + streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : + streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : + strna(p.state)); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + if (!isempty(p.result)) + printf(": %s", p.result); - r = bus_call_method( - bus, - bus_network_mgr, - "SetLinkMulticastDNS", - &error, - NULL, - "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + if (p.ede_code >= 0) + printf(" (%s%s%s)", + FORMAT_DNS_EDE_RCODE(p.ede_code), + !isempty(p.ede_msg) ? ": " : "", + strempty(p.ede_msg)); - return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); - } + puts(""); - return 0; + print_answer(p.answer); } -static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DNS_OVER_TLS); +static int monitor_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNS_OVER_TLS); + assert(link); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + if (error_id) { + bool disconnect; - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSOverTLS", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); + if (disconnect) + log_info("Disconnected."); + else + log_error("Varlink error: %s", error_id); - r = bus_call_method( - bus, - bus_network_mgr, - "SetLinkDNSOverTLS", - &error, - NULL, - "is", arg_ifindex, argv[2]); + (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); + return 0; } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - return log_error_errno(r, "Failed to set DNSOverTLS configuration: %s", bus_error_message(&error, r)); + if (sd_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 (!sd_json_format_enabled(arg_json_format_flags)) { + monitor_query_dump(parameters); + printf("\n"); + } else + sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); + + fflush(stdout); + return 0; } -static int verb_dnssec(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; +static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r, c; - r = acquire_bus(&bus); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_event_default(&event); if (r < 0) - return r; + return log_error_errno(r, "Failed to get event loop: %m"); - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); - if (arg_ifindex <= 0) - return status_all(STATUS_DNSSEC); + r = sd_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"); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNSSEC); + r = sd_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 timeout: %m"); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = sd_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 = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_varlink_bind_reply(vl, monitor_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); - r = bus_call_method(bus, bus_network_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = sd_varlink_observebo( + vl, + "io.systemd.Resolve.Monitor.SubscribeQueryResults", + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); - return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); - } + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); - return 0; + 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 int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_error *error) { +static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; int r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, "SetLinkDNSSECNegativeTrustAnchors"); + r = bus_message_new_method_call(bus, &req, locator, extended ? "SetLinkDNSEx" : "SetLinkDNS"); if (r < 0) return bus_log_create_error(r); @@ -2448,695 +2231,911 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append_strv(req, nta); + r = sd_bus_message_open_container(req, 'a', extended ? "(iayqs)" : "(iay)"); if (r < 0) return bus_log_create_error(r); - return sd_bus_call(bus, req, 0, error, NULL); -} - -static int verb_nta(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char **args; - bool clear; - int r; + /* If only argument is the empty string, then call SetLinkDNS() with an + * empty list, which will clear the list of domains for an interface. */ + if (!strv_equal(dns, STRV_MAKE(""))) + STRV_FOREACH(p, dns) { + _cleanup_free_ char *name = NULL; + struct in_addr_data data; + uint16_t port; + int ifindex; - r = acquire_bus(&bus); - if (r < 0) - return r; + r = in_addr_port_ifindex_name_from_string_auto(*p, &data.family, &data.address, &port, &ifindex, &name); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS server address: %s", *p); - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + if (ifindex != 0 && ifindex != arg_ifindex) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifindex: %i", ifindex); - if (arg_ifindex <= 0) - return status_all(STATUS_NTA); + r = sd_bus_message_open_container(req, 'r', extended ? "iayqs" : "iay"); + if (r < 0) + return bus_log_create_error(r); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_NTA); + r = sd_bus_message_append(req, "i", data.family); + if (r < 0) + return bus_log_create_error(r); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); + if (r < 0) + return bus_log_create_error(r); - /* If only argument is the empty string, then call SetLinkDNSSECNegativeTrustAnchors() - * with an empty list, which will clear the list of domains for an interface. */ - args = strv_skip(argv, 2); - clear = strv_equal(args, STRV_MAKE("")); + if (extended) { + r = sd_bus_message_append(req, "q", port); + if (r < 0) + return bus_log_create_error(r); - if (!clear) - STRV_FOREACH(p, args) { - r = dns_name_is_valid(*p); + r = sd_bus_message_append(req, "s", name); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", *p); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Domain not valid: %s", - *p); + return bus_log_create_error(r); } - r = call_nta(bus, clear ? NULL : args, bus_resolve_mgr, &error); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); - r = call_nta(bus, clear ? NULL : args, bus_network_mgr, &error); + r = sd_bus_call(bus, req, 0, error, NULL); + if (r < 0 && extended && sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + sd_bus_error_free(error); + return call_dns(bus, dns, locator, error, false); } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + return r; +} - return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); - } +static int dump_cache_item(sd_json_variant *item) { - return 0; -} + struct item_info { + sd_json_variant *key; + sd_json_variant *rrs; + const char *type; + uint64_t until; + } item_info = {}; -static int verb_revert_link(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; + static const sd_json_dispatch_field dispatch_table[] = { + { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, + { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, + { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, + {}, + }; - r = acquire_bus(&bus); + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + int r, c = 0; + + r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + r = dns_resource_key_from_json(item_info.key, &k); + if (r < 0) + return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); - if (arg_ifindex <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface argument required."); + if (item_info.type) + printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); + else { + sd_json_variant *i; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *data = NULL; + sd_json_variant *raw; + size_t size; - r = bus_call_method(bus, bus_resolve_mgr, "RevertLink", &error, NULL, "i", arg_ifindex); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + raw = sd_json_variant_by_key(i, "raw"); + if (!raw) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); - r = bus_call_method(bus, bus_network_mgr, "RevertLinkDNS", &error, NULL, "i", arg_ifindex); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = sd_json_variant_unbase64(raw, &data, &size); + if (r < 0) + return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); - return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); + r = dns_resource_record_new_from_raw(&rr, data, size); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS data: %m"); + + printf("%s\n", dns_resource_record_to_string(rr)); + c++; + } } - return 0; + return c; } -static int verb_log_level(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; +static int dump_cache_scope(sd_json_variant *scope) { + struct scope_info { + const char *protocol; + int family; + int ifindex; + const char *ifname; + sd_json_variant *cache; + const char *dnssec_mode; + const char *dns_over_tls_mode; + } scope_info = { + .family = AF_UNSPEC, + }; + sd_json_variant *i; + int r, c = 0; - r = acquire_bus(&bus); + static const sd_json_dispatch_field dispatch_table[] = { + { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, + { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, + { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, + {}, + }; + + r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); if (r < 0) return r; - assert(IN_SET(argc, 1, 2)); + printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); - return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); -} + if (scope_info.family != AF_UNSPEC) + printf(" family=%s", af_to_name(scope_info.family)); -static int print_question(char prefix, const char *color, sd_json_variant *question) { - sd_json_variant *q = NULL; - int r; + if (scope_info.ifindex > 0) + printf(" ifindex=%i", scope_info.ifindex); + if (scope_info.ifname) + printf(" ifname=%s", scope_info.ifname); - assert(color); + if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { + if (scope_info.dnssec_mode) + printf(" DNSSEC=%s", scope_info.dnssec_mode); + if (scope_info.dns_over_tls_mode) + printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); + } - JSON_VARIANT_ARRAY_FOREACH(q, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - char buf[DNS_RESOURCE_KEY_STRING_MAX]; + printf("%s\n", ansi_normal()); - r = dns_resource_key_from_json(q, &key); - if (r < 0) { - log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); - continue; - } + JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { + r = dump_cache_item(i); + if (r < 0) + return r; - printf("%s%s %c%s: %s\n", - color, - glyph(GLYPH_ARROW_RIGHT), - prefix, - ansi_normal(), - dns_resource_key_to_string(key, buf, sizeof(buf))); + c += r; } + if (c == 0) + printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); + else + printf("\n"); + return 0; } -static int print_answer(sd_json_variant *answer) { - sd_json_variant *a; +static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_json_variant *reply = NULL, *d = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; int r; - JSON_VARIANT_ARRAY_FOREACH(a, answer) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *d = NULL; - sd_json_variant *jraw; - const char *s; - size_t l; - - jraw = sd_json_variant_by_key(a, "raw"); - if (!jraw) { - log_warning("Received monitor answer lacking valid raw data, ignoring."); - continue; - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_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 = sd_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 = dns_resource_record_new_from_raw(&rr, d, l); - if (r < 0) { - log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); - continue; - } + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpCache", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); - printf("%s%s A%s: %s\n", - ansi_highlight_yellow(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - s); + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_cache_scope(i); + if (r < 0) + return r; + } + + return 0; } - return 0; + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } -typedef struct MonitorQueryParams { - sd_json_variant *question; - sd_json_variant *answer; - sd_json_variant *collected_questions; - int rcode; - int error; - int ede_code; - const char *state; - const char *result; - const char *ede_msg; -} MonitorQueryParams; +static int dump_server_state(sd_json_variant *server) { + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; -static void monitor_query_params_done(MonitorQueryParams *p) { - assert(p); + struct server_state { + const char *server_name; + const char *type; + const char *ifname; + int ifindex; + const char *verified_feature_level; + const char *possible_feature_level; + const char *dnssec_mode; + bool dnssec_supported; + size_t received_udp_fragment_max; + uint64_t n_failed_udp; + uint64_t n_failed_tcp; + bool packet_truncated; + bool packet_bad_opt; + bool packet_rrsig_missing; + bool packet_invalid; + bool packet_do_off; + } server_state = { + .ifindex = -1, + }; - sd_json_variant_unref(p->question); - sd_json_variant_unref(p->answer); - sd_json_variant_unref(p->collected_questions); -} + int r; -static void monitor_query_dump(sd_json_variant *v) { static const sd_json_dispatch_field dispatch_table[] = { - { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, - { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, - { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, - { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, - { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, - { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, - { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, - { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, - { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, - {} + { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, + { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, + { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, + { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, + { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, + { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, + { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, + { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, + { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, + { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, + { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, + { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, + { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, + { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, + { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, + { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, + {}, }; - _cleanup_(monitor_query_params_done) MonitorQueryParams p = { - .rcode = -1, - .ede_code = -1, - }; + r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + if (r < 0) + return r; - assert(v); + table = table_new_vertical(); + if (!table) + return log_oom(); - if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) - return; + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + (void) table_set_align_percent(table, cell, 0); - /* First show the current question */ - print_question('Q', ansi_highlight_cyan(), p.question); + r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); + if (r < 0) + return table_log_add_error(r); - /* And then show the questions that led to this one in case this was a CNAME chain */ - print_question('C', ansi_highlight_grey(), p.collected_questions); + r = table_add_many(table, + TABLE_EMPTY, + TABLE_FIELD, "Type", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_STRING, server_state.type); + if (r < 0) + return table_log_add_error(r); - printf("%s%s S%s: %s", - streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : - streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : - strna(p.state)); + if (server_state.ifname) { + r = table_add_many(table, + TABLE_FIELD, "Interface", + TABLE_STRING, server_state.ifname); + if (r < 0) + return table_log_add_error(r); + } - if (!isempty(p.result)) - printf(": %s", p.result); + if (server_state.ifindex >= 0) { + r = table_add_many(table, + TABLE_FIELD, "Interface Index", + TABLE_INT, server_state.ifindex); + if (r < 0) + return table_log_add_error(r); + } - if (p.ede_code >= 0) - printf(" (%s%s%s)", - FORMAT_DNS_EDE_RCODE(p.ede_code), - !isempty(p.ede_msg) ? ": " : "", - strempty(p.ede_msg)); + if (server_state.verified_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Verified feature level", + TABLE_STRING, server_state.verified_feature_level); + if (r < 0) + return table_log_add_error(r); + } - puts(""); + if (server_state.possible_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Possible feature level", + TABLE_STRING, server_state.possible_feature_level); + if (r < 0) + return table_log_add_error(r); + } - print_answer(p.answer); + r = table_add_many(table, + TABLE_FIELD, "DNSSEC Mode", + TABLE_STRING, server_state.dnssec_mode, + TABLE_FIELD, "DNSSEC Supported", + TABLE_STRING, yes_no(server_state.dnssec_supported), + TABLE_FIELD, "Maximum UDP fragment size received", + TABLE_UINT64, server_state.received_udp_fragment_max, + TABLE_FIELD, "Failed UDP attempts", + TABLE_UINT64, server_state.n_failed_udp, + TABLE_FIELD, "Failed TCP attempts", + TABLE_UINT64, server_state.n_failed_tcp, + TABLE_FIELD, "Seen truncated packet", + TABLE_STRING, yes_no(server_state.packet_truncated), + TABLE_FIELD, "Seen OPT RR getting lost", + TABLE_STRING, yes_no(server_state.packet_bad_opt), + TABLE_FIELD, "Seen RRSIG RR missing", + TABLE_STRING, yes_no(server_state.packet_rrsig_missing), + TABLE_FIELD, "Seen invalid packet", + TABLE_STRING, yes_no(server_state.packet_invalid), + TABLE_FIELD, "Server dropped DO flag", + TABLE_STRING, yes_no(server_state.packet_do_off), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, TABLE_EMPTY); + + if (r < 0) + return table_log_add_error(r); + + return table_print_or_warn(table); } -static int monitor_reply( - sd_varlink *link, - sd_json_variant *parameters, - const char *error_id, - sd_varlink_reply_flags_t flags, - void *userdata) { +static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_json_variant *reply = NULL, *d = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; - assert(link); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - if (error_id) { - bool disconnect; + r = sd_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"); - disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); - if (disconnect) - log_info("Disconnected."); - else - log_error("Varlink error: %s", error_id); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpServerState", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); + + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_server_state(i); + if (r < 0) + return r; + } - (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); return 0; } - if (sd_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; + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); +} + +static int verb_dns(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; } - if (!sd_json_format_enabled(arg_json_format_flags)) { - monitor_query_dump(parameters); - printf("\n"); - } else - sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); + if (arg_ifindex <= 0) + return status_all(STATUS_DNS); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNS); + + char **args = strv_skip(argv, 2); + r = call_dns(bus, args, bus_resolve_mgr, &error, true); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = call_dns(bus, args, bus_network_mgr, &error, true); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_message_new_method_call(bus, &req, locator, "SetLinkDomains"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", arg_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(req, 'a', "(sb)"); + if (r < 0) + return bus_log_create_error(r); + + /* If only argument is the empty string, then call SetLinkDomains() with an + * empty list, which will clear the list of domains for an interface. */ + if (!strv_equal(domain, STRV_MAKE(""))) + STRV_FOREACH(p, domain) { + const char *n; + + n = **p == '~' ? *p + 1 : *p; + + r = dns_name_is_valid(n); + if (r < 0) + return log_error_errno(r, "Failed to validate specified domain %s: %m", n); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Domain not valid: %s", + n); + + r = sd_bus_message_append(req, "(sb)", n, **p == '~'); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + + return sd_bus_call(bus, req, 0, error, NULL); +} + +static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_DOMAIN); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DOMAIN); + + char **args = strv_skip(argv, 2); + r = call_domain(bus, args, bus_resolve_mgr, &error); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = call_domain(bus, args, bus_network_mgr, &error); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, b; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_DEFAULT_ROUTE); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DEFAULT_ROUTE); + + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = bus_call_method(bus, bus_network_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_llmnr(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *global_llmnr_support_str = NULL; + ResolveSupport global_llmnr_support, llmnr_support; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_LLMNR); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_LLMNR); + + llmnr_support = resolve_support_from_string(argv[2]); + if (llmnr_support < 0) + return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]); + + r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str); + if (r < 0) + return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r)); + + global_llmnr_support = resolve_support_from_string(global_llmnr_support_str); + if (global_llmnr_support < 0) + return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str); + + if (global_llmnr_support < llmnr_support) + log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".", + argv[2], arg_ifname, global_llmnr_support_str); + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); + + r = bus_call_method(bus, bus_network_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - fflush(stdout); + return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); + } return 0; } -static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r, c; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); +static int verb_mdns(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *global_mdns_support_str = NULL; + ResolveSupport global_mdns_support, mdns_support; + int r; - r = sd_event_default(&event); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); + return r; - r = sd_event_set_signal_exit(event, true); - if (r < 0) - return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - r = sd_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"); + if (arg_ifindex <= 0) + return status_all(STATUS_MDNS); - r = sd_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 timeout: %m"); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_MDNS); - r = sd_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"); + mdns_support = resolve_support_from_string(argv[2]); + if (mdns_support < 0) + return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]); - r = sd_varlink_bind_reply(vl, monitor_reply); + r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str); if (r < 0) - return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); + return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r)); - r = sd_varlink_observebo( - vl, - "io.systemd.Resolve.Monitor.SubscribeQueryResults", - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); + global_mdns_support = resolve_support_from_string(global_mdns_support_str); + if (global_mdns_support < 0) + return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str); - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); + if (global_mdns_support < mdns_support) + log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".", + argv[2], arg_ifname, global_mdns_support_str); - r = sd_event_get_exit_code(event, &c); - if (r < 0) - return log_error_errno(r, "Failed to get exit code: %m"); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - return c; -} + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); -static int dump_cache_item(sd_json_variant *item) { + r = bus_call_method( + bus, + bus_network_mgr, + "SetLinkMulticastDNS", + &error, + NULL, + "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - struct item_info { - sd_json_variant *key; - sd_json_variant *rrs; - const char *type; - uint64_t until; - } item_info = {}; + return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); + } - static const sd_json_dispatch_field dispatch_table[] = { - { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, - { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, - { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, - { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, - {}, - }; + return 0; +} - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - int r, c = 0; +static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; - r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); + r = acquire_bus(&bus); if (r < 0) return r; - r = dns_resource_key_from_json(item_info.key, &k); - if (r < 0) - return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (item_info.type) - printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); - else { - sd_json_variant *i; + if (arg_ifindex <= 0) + return status_all(STATUS_DNS_OVER_TLS); - JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *data = NULL; - sd_json_variant *raw; - size_t size; + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNS_OVER_TLS); - raw = sd_json_variant_by_key(i, "raw"); - if (!raw) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_json_variant_unbase64(raw, &data, &size); - if (r < 0) - return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSOverTLS", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - r = dns_resource_record_new_from_raw(&rr, data, size); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS data: %m"); + r = bus_call_method( + bus, + bus_network_mgr, + "SetLinkDNSOverTLS", + &error, + NULL, + "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - printf("%s\n", dns_resource_record_to_string(rr)); - c++; - } + return log_error_errno(r, "Failed to set DNSOverTLS configuration: %s", bus_error_message(&error, r)); } - return c; + return 0; } -static int dump_cache_scope(sd_json_variant *scope) { - - struct scope_info { - const char *protocol; - int family; - int ifindex; - const char *ifname; - sd_json_variant *cache; - const char *dnssec_mode; - const char *dns_over_tls_mode; - } scope_info = { - .family = AF_UNSPEC, - }; - sd_json_variant *i; - int r, c = 0; - - static const sd_json_dispatch_field dispatch_table[] = { - { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, - { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, - { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, - { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, - {}, - }; +static int verb_dnssec(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; - r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); + r = acquire_bus(&bus); if (r < 0) return r; - printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (scope_info.family != AF_UNSPEC) - printf(" family=%s", af_to_name(scope_info.family)); + if (arg_ifindex <= 0) + return status_all(STATUS_DNSSEC); - if (scope_info.ifindex > 0) - printf(" ifindex=%i", scope_info.ifindex); - if (scope_info.ifname) - printf(" ifname=%s", scope_info.ifname); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNSSEC); - if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { - if (scope_info.dnssec_mode) - printf(" DNSSEC=%s", scope_info.dnssec_mode); - if (scope_info.dns_over_tls_mode) - printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - printf("%s\n", ansi_normal()); + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { - r = dump_cache_item(i); - if (r < 0) - return r; + r = bus_call_method(bus, bus_network_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - c += r; + return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); } - if (c == 0) - printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); - else - printf("\n"); + return 0; +} + +static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_message_new_method_call(bus, &req, locator, "SetLinkDNSSECNegativeTrustAnchors"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", arg_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(req, nta); + if (r < 0) + return bus_log_create_error(r); - return 0; + return sd_bus_call(bus, req, 0, error, NULL); } -static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_json_variant *reply = NULL, *d = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; +static int verb_nta(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char **args; + bool clear; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_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_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpCache", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = acquire_bus(&bus); if (r < 0) return r; - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); + if (arg_ifindex <= 0) + return status_all(STATUS_NTA); - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_NTA); - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_cache_scope(i); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + /* If only argument is the empty string, then call SetLinkDNSSECNegativeTrustAnchors() + * with an empty list, which will clear the list of domains for an interface. */ + args = strv_skip(argv, 2); + clear = strv_equal(args, STRV_MAKE("")); + + if (!clear) + STRV_FOREACH(p, args) { + r = dns_name_is_valid(*p); if (r < 0) - return r; + return log_error_errno(r, "Failed to validate specified domain %s: %m", *p); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Domain not valid: %s", + *p); } - return 0; - } + r = call_nta(bus, clear ? NULL : args, bus_resolve_mgr, &error); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); -} + r = call_nta(bus, clear ? NULL : args, bus_network_mgr, &error); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; -static int dump_server_state(sd_json_variant *server) { - _cleanup_(table_unrefp) Table *table = NULL; - TableCell *cell; + return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); + } - struct server_state { - const char *server_name; - const char *type; - const char *ifname; - int ifindex; - const char *verified_feature_level; - const char *possible_feature_level; - const char *dnssec_mode; - bool dnssec_supported; - size_t received_udp_fragment_max; - uint64_t n_failed_udp; - uint64_t n_failed_tcp; - bool packet_truncated; - bool packet_bad_opt; - bool packet_rrsig_missing; - bool packet_invalid; - bool packet_do_off; - } server_state = { - .ifindex = -1, - }; + return 0; +} +static int verb_revert_link(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; - static const sd_json_dispatch_field dispatch_table[] = { - { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, - { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, - { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, - { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, - { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, - { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, - { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, - { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, - { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, - { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, - { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, - { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, - { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, - { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, - { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, - { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + r = acquire_bus(&bus); if (r < 0) return r; - table = table_new_vertical(); - if (!table) - return log_oom(); - - assert_se(cell = table_get_cell(table, 0, 0)); - (void) table_set_ellipsize_percent(table, cell, 100); - (void) table_set_align_percent(table, cell, 0); - - r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); - if (r < 0) - return table_log_add_error(r); - - r = table_add_many(table, - TABLE_EMPTY, - TABLE_FIELD, "Type", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_STRING, server_state.type); - if (r < 0) - return table_log_add_error(r); - - if (server_state.ifname) { - r = table_add_many(table, - TABLE_FIELD, "Interface", - TABLE_STRING, server_state.ifname); + if (argc >= 2) { + r = ifname_mangle(argv[1]); if (r < 0) - return table_log_add_error(r); + return r; } - if (server_state.ifindex >= 0) { - r = table_add_many(table, - TABLE_FIELD, "Interface Index", - TABLE_INT, server_state.ifindex); - if (r < 0) - return table_log_add_error(r); - } + if (arg_ifindex <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface argument required."); - if (server_state.verified_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Verified feature level", - TABLE_STRING, server_state.verified_feature_level); - if (r < 0) - return table_log_add_error(r); - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - if (server_state.possible_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Possible feature level", - TABLE_STRING, server_state.possible_feature_level); - if (r < 0) - return table_log_add_error(r); - } + r = bus_call_method(bus, bus_resolve_mgr, "RevertLink", &error, NULL, "i", arg_ifindex); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - r = table_add_many(table, - TABLE_FIELD, "DNSSEC Mode", - TABLE_STRING, server_state.dnssec_mode, - TABLE_FIELD, "DNSSEC Supported", - TABLE_STRING, yes_no(server_state.dnssec_supported), - TABLE_FIELD, "Maximum UDP fragment size received", - TABLE_UINT64, server_state.received_udp_fragment_max, - TABLE_FIELD, "Failed UDP attempts", - TABLE_UINT64, server_state.n_failed_udp, - TABLE_FIELD, "Failed TCP attempts", - TABLE_UINT64, server_state.n_failed_tcp, - TABLE_FIELD, "Seen truncated packet", - TABLE_STRING, yes_no(server_state.packet_truncated), - TABLE_FIELD, "Seen OPT RR getting lost", - TABLE_STRING, yes_no(server_state.packet_bad_opt), - TABLE_FIELD, "Seen RRSIG RR missing", - TABLE_STRING, yes_no(server_state.packet_rrsig_missing), - TABLE_FIELD, "Seen invalid packet", - TABLE_STRING, yes_no(server_state.packet_invalid), - TABLE_FIELD, "Server dropped DO flag", - TABLE_STRING, yes_no(server_state.packet_do_off), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, TABLE_EMPTY); + r = bus_call_method(bus, bus_network_mgr, "RevertLinkDNS", &error, NULL, "i", arg_ifindex); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - if (r < 0) - return table_log_add_error(r); + return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); + } - return table_print_or_warn(table); + return 0; } -static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_json_variant *reply = NULL, *d = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; +static int verb_log_level(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_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_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpServerState", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = acquire_bus(&bus); if (r < 0) return r; - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); - - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; - - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_server_state(i); - if (r < 0) - return r; - } - - return 0; - } + assert(IN_SET(argc, 1, 2)); - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); + return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); } static int parse_protocol(const char *arg) {