diff --git a/man/resolvectl.xml b/man/resolvectl.xml
index c966ca67bd..37a51b4760 100644
--- a/man/resolvectl.xml
+++ b/man/resolvectl.xml
@@ -212,6 +212,13 @@
output.
+
+ show-cache
+
+ Show current cache content, per scope. Use to enable JSON
+ output.
+
+
diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c
index e65fbbcbab..c7ec3de588 100644
--- a/src/resolve/resolvectl.c
+++ b/src/resolve/resolvectl.c
@@ -2586,34 +2586,6 @@ static int verb_log_level(int argc, char *argv[], void *userdata) {
return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL);
}
-static int monitor_rkey_from_json(JsonVariant *v, DnsResourceKey **ret_key) {
- _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
- uint16_t type = 0, class = 0;
- const char *name = NULL;
- int r;
-
- JsonDispatch dispatch_table[] = {
- { "class", JSON_VARIANT_INTEGER, json_dispatch_uint16, PTR_TO_SIZE(&class), JSON_MANDATORY },
- { "type", JSON_VARIANT_INTEGER, json_dispatch_uint16, PTR_TO_SIZE(&type), JSON_MANDATORY },
- { "name", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&name), JSON_MANDATORY },
- {}
- };
-
- assert(v);
- assert(ret_key);
-
- r = json_dispatch(v, dispatch_table, NULL, 0, NULL);
- if (r < 0)
- return r;
-
- key = dns_resource_key_new(class, type, name);
- if (!key)
- return -ENOMEM;
-
- *ret_key = TAKE_PTR(key);
- return 0;
-}
-
static int print_question(char prefix, const char *color, JsonVariant *question) {
JsonVariant *q = NULL;
int r;
@@ -2624,7 +2596,7 @@ static int print_question(char prefix, const char *color, JsonVariant *question)
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
char buf[DNS_RESOURCE_KEY_STRING_MAX];
- r = monitor_rkey_from_json(q, &key);
+ 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;
@@ -2807,6 +2779,157 @@ static int verb_monitor(int argc, char *argv[], void *userdata) {
return c;
}
+static int dump_cache_item(JsonVariant *item) {
+
+ struct item_info {
+ JsonVariant *key;
+ JsonVariant *rrs;
+ const char *type;
+ uint64_t until;
+ } item_info = {};
+
+ static const JsonDispatch dispatch_table[] = {
+ { "key", JSON_VARIANT_OBJECT, json_dispatch_variant_noref, offsetof(struct item_info, key), JSON_MANDATORY },
+ { "rrs", JSON_VARIANT_ARRAY, json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 },
+ { "type", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct item_info, type), 0 },
+ { "until", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(struct item_info, until), 0 },
+ {},
+ };
+
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
+ int r, c = 0;
+
+ r = json_dispatch(item, dispatch_table, NULL, JSON_LOG, &item_info);
+ 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 (item_info.type)
+ printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal());
+ else {
+ JsonVariant *i;
+
+ JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) {
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
+ _cleanup_free_ void *data = NULL;
+ JsonVariant *raw;
+ size_t size;
+
+ raw = json_variant_by_key(i, "raw");
+ if (!raw)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data.");
+
+ r = json_variant_unbase64(raw, &data, &size);
+ if (r < 0)
+ return log_error_errno(r, "Unable to decode raw RR JSON data: %m");
+
+ 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 c;
+}
+
+static int dump_cache_scope(JsonVariant *scope) {
+
+ struct scope_info {
+ const char *protocol;
+ int family;
+ int ifindex;
+ const char *ifname;
+ JsonVariant *cache;
+ } scope_info = {
+ .family = AF_UNSPEC,
+ };
+ JsonVariant *i;
+ int r, c = 0;
+
+ static const JsonDispatch dispatch_table[] = {
+ { "protocol", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct scope_info, protocol), JSON_MANDATORY },
+ { "family", JSON_VARIANT_INTEGER, json_dispatch_int, offsetof(struct scope_info, family), 0 },
+ { "ifindex", JSON_VARIANT_INTEGER, json_dispatch_int, offsetof(struct scope_info, ifindex), 0 },
+ { "ifname", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 },
+ { "cache", JSON_VARIANT_ARRAY, json_dispatch_variant_noref, offsetof(struct scope_info, cache), JSON_MANDATORY },
+ {},
+ };
+
+ r = json_dispatch(scope, dispatch_table, NULL, JSON_LOG, &scope_info);
+ if (r < 0)
+ return r;
+
+ printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol);
+
+ if (scope_info.family != AF_UNSPEC)
+ printf(" family=%s", af_to_name(scope_info.family));
+
+ if (scope_info.ifindex > 0)
+ printf(" ifindex=%i", scope_info.ifindex);
+ if (scope_info.ifname)
+ printf(" ifname=%s", scope_info.ifname);
+
+ printf("%s\n", ansi_normal());
+
+ JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) {
+ r = dump_cache_item(i);
+ if (r < 0)
+ return r;
+
+ c += r;
+ }
+
+ if (c == 0)
+ printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal());
+ else
+ printf("\n");
+
+ return 0;
+}
+
+static int verb_show_cache(int argc, char *argv[], void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *d = NULL, *reply = NULL;
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ int r;
+
+ r = varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m");
+
+ r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpCache", NULL, &reply, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue DumpCache() varlink call: %m");
+
+ d = json_variant_by_key(reply, "dump");
+ if (!d)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "DumpCache() response is missing 'dump' key.");
+
+ if (!json_variant_is_array(d))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "DumpCache() response 'dump' field not an array");
+
+ if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
+ JsonVariant *i;
+
+ JSON_VARIANT_ARRAY_FOREACH(i, d) {
+ r = dump_cache_scope(i);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+ }
+
+ return json_variant_dump(d, arg_json_format_flags, NULL, NULL);
+}
+
static void help_protocol_types(void) {
if (arg_legend)
puts("Known protocol types:");
@@ -2913,6 +3036,7 @@ static int native_help(void) {
" flush-caches Flush all local DNS caches\n"
" reset-server-features Forget learnt DNS server feature levels\n"
" monitor Monitor DNS queries\n"
+ " show-cache Show cache contents\n"
" dns [LINK [SERVER...]] Get/set per-interface DNS server address\n"
" domain [LINK [DOMAIN...]] Get/set per-interface search domain\n"
" default-route [LINK [BOOL]] Get/set per-interface default route flag\n"
@@ -3559,6 +3683,7 @@ static int native_main(int argc, char *argv[], sd_bus *bus) {
{ "revert", VERB_ANY, 2, 0, verb_revert_link },
{ "log-level", VERB_ANY, 2, 0, verb_log_level },
{ "monitor", VERB_ANY, 1, 0, verb_monitor },
+ { "show-cache", VERB_ANY, 1, 0, verb_show_cache },
{}
};
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 2f97cf7730..089ed3aa0a 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -1351,6 +1351,86 @@ void dns_cache_dump(DnsCache *cache, FILE *f) {
}
}
+int dns_cache_dump_to_json(DnsCache *cache, JsonVariant **ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *c = NULL;
+ DnsCacheItem *i;
+ int r;
+
+ assert(cache);
+ assert(ret);
+
+ HASHMAP_FOREACH(i, cache->by_key) {
+ _cleanup_(json_variant_unrefp) JsonVariant *d = NULL, *k = NULL;
+
+ r = dns_resource_key_to_json(i->key, &k);
+ if (r < 0)
+ return r;
+
+ if (i->rr) {
+ _cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
+
+ LIST_FOREACH(by_key, j, i) {
+ _cleanup_(json_variant_unrefp) JsonVariant *rj = NULL, *item = NULL;
+
+ assert(j->rr);
+
+ r = dns_resource_record_to_json(j->rr, &rj);
+ if (r < 0)
+ return r;
+
+ r = dns_resource_record_to_wire_format(j->rr, /* canonical= */ false); /* don't use DNSSEC canonical format, since it removes casing, but we want that for DNS_SD compat */
+ if (r < 0)
+ return r;
+
+ r = json_build(&item, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("rr", rj),
+ JSON_BUILD_PAIR_BASE64("raw", j->rr->wire_format, j->rr->wire_format_size)));
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(&l, item);
+ if (r < 0)
+ return r;
+ }
+
+ if (!l) {
+ r = json_variant_new_array(&l, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ r = json_build(&d,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("key", k),
+ JSON_BUILD_PAIR_VARIANT("rrs", l),
+ JSON_BUILD_PAIR_UNSIGNED("until", i->until)));
+ } else if (i->type == DNS_CACHE_NODATA) {
+ r = json_build(&d,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("key", k),
+ JSON_BUILD_PAIR_EMPTY_ARRAY("rrs"),
+ JSON_BUILD_PAIR_UNSIGNED("until", i->until)));
+ } else
+ r = json_build(&d,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_VARIANT("key", k),
+ JSON_BUILD_PAIR_STRING("type", dns_cache_item_type_to_string(i)),
+ JSON_BUILD_PAIR_UNSIGNED("until", i->until)));
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(&c, d);
+ if (r < 0)
+ return r;
+ }
+
+ if (!c)
+ return json_variant_new_array(ret, NULL, 0);
+
+ *ret = TAKE_PTR(c);
+ return 0;
+}
+
bool dns_cache_is_empty(DnsCache *cache) {
if (!cache)
return true;
diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h
index 42e744a910..bc045bc80c 100644
--- a/src/resolve/resolved-dns-cache.h
+++ b/src/resolve/resolved-dns-cache.h
@@ -50,6 +50,8 @@ int dns_cache_lookup(
int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address);
void dns_cache_dump(DnsCache *cache, FILE *f);
+int dns_cache_dump_to_json(DnsCache *cache, JsonVariant **ret);
+
bool dns_cache_is_empty(DnsCache *cache);
unsigned dns_cache_size(DnsCache *cache);
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index 603bb1a10d..f6344542d6 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -1853,6 +1853,34 @@ int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret) {
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(dns_resource_key_name(key)))));
}
+int dns_resource_key_from_json(JsonVariant *v, DnsResourceKey **ret) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
+ uint16_t type = 0, class = 0;
+ const char *name = NULL;
+ int r;
+
+ JsonDispatch dispatch_table[] = {
+ { "class", JSON_VARIANT_INTEGER, json_dispatch_uint16, PTR_TO_SIZE(&class), JSON_MANDATORY },
+ { "type", JSON_VARIANT_INTEGER, json_dispatch_uint16, PTR_TO_SIZE(&type), JSON_MANDATORY },
+ { "name", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&name), JSON_MANDATORY },
+ {}
+ };
+
+ assert(v);
+ assert(ret);
+
+ r = json_dispatch(v, dispatch_table, NULL, 0, NULL);
+ if (r < 0)
+ return r;
+
+ key = dns_resource_key_new(class, type, name);
+ if (!key)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(key);
+ return 0;
+}
+
static int type_bitmap_to_json(Bitmap *b, JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
unsigned t;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 024cfb8744..fd15cc343d 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -317,6 +317,9 @@ int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *
char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size);
ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out);
+#define DNS_RESOURCE_KEY_TO_STRING(key) \
+ dns_resource_key_to_string(key, (char[DNS_RESOURCE_KEY_STRING_MAX]) {}, DNS_RESOURCE_KEY_STRING_MAX)
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref);
static inline bool dns_key_is_shared(const DnsResourceKey *key) {
@@ -368,6 +371,7 @@ int dns_txt_item_new_empty(DnsTxtItem **ret);
int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size);
int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret);
+int dns_resource_key_from_json(JsonVariant *v, DnsResourceKey **ret);
int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret);
void dns_resource_record_hash_func(const DnsResourceRecord *i, struct siphash *state);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 45f1d36311..35085a6ef5 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -1643,3 +1643,23 @@ bool dns_scope_is_default_route(DnsScope *scope) {
* volunteer as default route. */
return !dns_scope_has_route_only_domains(scope);
}
+
+int dns_scope_dump_cache_to_json(DnsScope *scope, JsonVariant **ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *cache = NULL;
+ int r;
+
+ assert(scope);
+ assert(ret);
+
+ r = dns_cache_dump_to_json(&scope->cache, &cache);
+ if (r < 0)
+ return r;
+
+ return json_build(ret,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_STRING("protocol", dns_protocol_to_string(scope->protocol)),
+ JSON_BUILD_PAIR_CONDITION(scope->family != AF_UNSPEC, "family", JSON_BUILD_INTEGER(scope->family)),
+ JSON_BUILD_PAIR_CONDITION(scope->link, "ifindex", JSON_BUILD_INTEGER(scope->link ? scope->link->ifindex : 0)),
+ JSON_BUILD_PAIR_CONDITION(scope->link, "ifname", JSON_BUILD_STRING(scope->link ? scope->link->ifname : NULL)),
+ JSON_BUILD_PAIR_VARIANT("cache", cache)));
+}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 1f9d22b7d1..ca33fd007a 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -110,3 +110,5 @@ int dns_scope_add_dnssd_services(DnsScope *scope);
int dns_scope_remove_dnssd_services(DnsScope *scope);
bool dns_scope_is_default_route(DnsScope *scope);
+
+int dns_scope_dump_cache_to_json(DnsScope *scope, JsonVariant **ret);
diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c
index f878d9ee3f..fc224f627e 100644
--- a/src/resolve/resolved-varlink.c
+++ b/src/resolve/resolved-varlink.c
@@ -563,6 +563,40 @@ static int vl_method_subscribe_dns_resolves(Varlink *link, JsonVariant *paramete
return 1;
}
+static int vl_method_dump_cache(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *list = NULL;
+ Manager *m;
+ int r;
+
+ assert(link);
+
+ if (json_variant_elements(parameters) > 0)
+ return varlink_error_invalid_parameter(link, parameters);
+
+ m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link)));
+
+ LIST_FOREACH(scopes, s, m->dns_scopes) {
+ _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+
+ r = dns_scope_dump_cache_to_json(s, &j);
+ if (r < 0)
+ return r;
+
+ r = json_variant_append_array(&list, j);
+ if (r < 0)
+ return r;
+ }
+
+ if (!list) {
+ r = json_variant_new_array(&list, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ return varlink_replyb(link, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("dump", JSON_BUILD_VARIANT(list))));
+}
+
static int varlink_monitor_server_init(Manager *m) {
_cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL;
int r;
@@ -578,10 +612,10 @@ static int varlink_monitor_server_init(Manager *m) {
varlink_server_set_userdata(server, m);
- r = varlink_server_bind_method(
+ r = varlink_server_bind_method_many(
server,
- "io.systemd.Resolve.Monitor.SubscribeQueryResults",
- vl_method_subscribe_dns_resolves);
+ "io.systemd.Resolve.Monitor.SubscribeQueryResults", vl_method_subscribe_dns_resolves,
+ "io.systemd.Resolve.Monitor.DumpCache", vl_method_dump_cache);
if (r < 0)
return log_error_errno(r, "Failed to register varlink methods: %m");
diff --git a/src/shared/json.c b/src/shared/json.c
index 26d99a5fba..417d17df34 100644
--- a/src/shared/json.c
+++ b/src/shared/json.c
@@ -4642,10 +4642,20 @@ int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFl
JsonVariant **p = ASSERT_PTR(userdata);
assert(variant);
+ /* Takes a reference */
JSON_VARIANT_REPLACE(*p, json_variant_ref(variant));
return 0;
}
+int json_dispatch_variant_noref(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
+ JsonVariant **p = ASSERT_PTR(userdata);
+ assert(variant);
+
+ /* Doesn't take a reference */
+ *p = variant;
+ return 0;
+}
+
int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uid_t *uid = userdata;
uint64_t k;
diff --git a/src/shared/json.h b/src/shared/json.h
index 5d79472351..1356d0827b 100644
--- a/src/shared/json.h
+++ b/src/shared/json.h
@@ -387,6 +387,7 @@ int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags
int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
+int json_dispatch_variant_noref(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_int64(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_uint64(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);