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