]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolvectl: move verb implementations to match order in --help
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 7 May 2026 07:31:28 +0000 (09:31 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 7 May 2026 16:39:04 +0000 (18:39 +0200)
src/resolve/resolvectl.c

index fdce91344774b96e8796718c7a5f20133f111f38..4088c39b1da600808be7a2efe421c77f12a61c77 100644 (file)
@@ -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) {