From 72c2d39ecb2fcd4d6c78b65c56b7a9eab02a3048 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 28 Sep 2022 12:46:09 +0200 Subject: [PATCH] resolved: beef up monitor protocol, include full query info --- src/resolve/resolved-dns-query.c | 32 ++++++++- src/resolve/resolved-dns-query.h | 5 ++ src/resolve/resolved-manager.c | 116 ++++++++++++++++++++----------- src/resolve/resolved-manager.h | 2 +- src/resolve/resolved-varlink.c | 7 ++ 5 files changed, 120 insertions(+), 42 deletions(-) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index edd62fa068..58a7b2d878 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -397,6 +397,7 @@ DnsQuery *dns_query_free(DnsQuery *q) { dns_question_unref(q->question_idna); dns_question_unref(q->question_utf8); dns_packet_unref(q->question_bypass); + dns_question_unref(q->collected_questions); dns_query_reset_answer(q); @@ -585,8 +586,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) { q->state = state; - if (q->question_utf8 && state == DNS_TRANSACTION_SUCCESS && set_size(q->manager->varlink_subscription) > 0) - (void) manager_monitor_send(q->manager, q->answer, dns_question_first_name(q->question_utf8)); + (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->collected_questions, q->answer); dns_query_stop(q); if (q->complete) @@ -980,6 +980,26 @@ void dns_query_ready(DnsQuery *q) { dns_query_accept(q, bad); } +static int dns_query_collect_question(DnsQuery *q, DnsQuestion *question) { + _cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL; + int r; + + assert(q); + + if (dns_question_size(question) == 0) + return 0; + + /* When redirecting, save the first element in the chain, for informational purposes when monitoring */ + r = dns_question_merge(q->collected_questions, question, &merged); + if (r < 0) + return r; + + dns_question_unref(q->collected_questions); + q->collected_questions = TAKE_PTR(merged); + + return 0; +} + static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; int r, k; @@ -1029,6 +1049,14 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) /* Turn off searching for the new name */ q->flags |= SD_RESOLVED_NO_SEARCH; + r = dns_query_collect_question(q, q->question_idna); + if (r < 0) + return r; + r = dns_query_collect_question(q, q->question_utf8); + if (r < 0) + return r; + + /* Install the redirected question */ dns_question_unref(q->question_idna); q->question_idna = TAKE_PTR(nq_idna); diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 43a833a08a..2723299bee 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -52,6 +52,11 @@ struct DnsQuery { * here, and use that instead. */ DnsPacket *question_bypass; + /* When we follow a CNAME redirect, we save the original question here, for informational/monitoring + * purposes. We'll keep adding to this whenever we go one step in the redirect, so that in the end + * this will contain the complete set of CNAME questions. */ + DnsQuestion *collected_questions; + uint64_t flags; int ifindex; diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index df3a3fff04..ce5935dc7a 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1043,62 +1043,100 @@ static int manager_ipv6_send( return sendmsg_loop(fd, &mh, 0); } -int manager_monitor_send(Manager *m, DnsAnswer *answer, const char *query_name) { - _cleanup_free_ char *normalized = NULL; - DnsResourceRecord *rr; - int ifindex, r; - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; +static int dns_question_to_json(DnsQuestion *q, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL; + DnsResourceKey *key; + int r; + + assert(ret); + + DNS_QUESTION_FOREACH(key, q) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = dns_resource_key_to_json(key, &v); + if (r < 0) + return r; + + r = json_variant_append_array(&l, v); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(l); + return 0; +} + +int manager_monitor_send( + Manager *m, + int state, + int rcode, + int error, + DnsQuestion *question_idna, + DnsQuestion *question_utf8, + DnsQuestion *collected_questions, + DnsAnswer *answer) { + + _cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL; Varlink *connection; + DnsAnswerItem *rri; + int r; assert(m); if (set_isempty(m->varlink_subscription)) return 0; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, answer) { - _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; + /* Merge both questions format into one */ + r = dns_question_merge(question_idna, question_utf8, &merged); + if (r < 0) + return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m"); - if (rr->key->type == DNS_TYPE_A) { - struct in_addr *addr = &rr->a.in_addr; - r = json_build(&entry, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), - JSON_BUILD_PAIR_INTEGER("family", AF_INET), - JSON_BUILD_PAIR_IN4_ADDR("address", addr), - JSON_BUILD_PAIR_STRING("type", "A"))); - } else if (rr->key->type == DNS_TYPE_AAAA) { - struct in6_addr *addr6 = &rr->aaaa.in6_addr; - r = json_build(&entry, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), - JSON_BUILD_PAIR_INTEGER("family", AF_INET6), - JSON_BUILD_PAIR_IN6_ADDR("address", addr6), - JSON_BUILD_PAIR_STRING("type", "AAAA"))); - } else - continue; - if (r < 0) { - log_debug_errno(r, "Failed to build json object: %m"); - continue; - } + /* Convert the current primary question to JSON */ + r = dns_question_to_json(merged, &jquestion); + if (r < 0) + return log_error_errno(r, "Failed to convert question to JSON: %m"); - r = json_variant_append_array(&array, entry); + /* Generate a JSON array of the questions preceeding the current one in the CNAME chain */ + r = dns_question_to_json(collected_questions, &jcollected_questions); + if (r < 0) + return log_error_errno(r, "Failed to convert question to JSON: %m"); + + DNS_ANSWER_FOREACH_ITEM(rri, answer) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + + r = dns_resource_record_to_json(rri->rr, &v); + if (r < 0) + return log_error_errno(r, "Failed to convert answer resource record to JSON: %m"); + + r = dns_resource_record_to_wire_format(rri->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 log_error_errno(r, "Failed to generate RR wire format: %m"); + + r = json_build(&w, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(v, "rr", JSON_BUILD_VARIANT(v)), + JSON_BUILD_PAIR("raw", JSON_BUILD_BASE64(rri->rr->wire_format, rri->rr->wire_format_size)), + JSON_BUILD_PAIR_CONDITION(rri->ifindex > 0, "ifindex", JSON_BUILD_INTEGER(rri->ifindex)))); + if (r < 0) + return log_error_errno(r, "Failed to make answer RR object: %m"); + + r = json_variant_append_array(&janswer, w); if (r < 0) return log_debug_errno(r, "Failed to append notification entry to array: %m"); } - if (json_variant_is_blank_object(array)) - return 0; - - r = dns_name_normalize(query_name, 0, &normalized); - if (r < 0) - return log_debug_errno(r, "Failed to normalize query name: %m"); - SET_FOREACH(connection, m->varlink_subscription) { r = varlink_notifyb(connection, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("addresses", - JSON_BUILD_VARIANT(array)), - JSON_BUILD_PAIR("name", JSON_BUILD_STRING(normalized)))); + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))), + JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)), + JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)), + JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)), + JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)), + JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer)))); if (r < 0) - log_debug_errno(r, "Failed to send notification, ignoring: %m"); + log_debug_errno(r, "Failed to send monitor event, ignoring: %m"); } + return 0; } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 844405c252..98d90e05b3 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -167,7 +167,7 @@ int manager_start(Manager *m); uint32_t manager_find_mtu(Manager *m); -int manager_monitor_send(Manager *m, DnsAnswer *answer, const char *query_name); +int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsQuestion *collected_questions, DnsAnswer *answer); int manager_write(Manager *m, int fd, DnsPacket *p); int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p); diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index cde406f40e..e344cf6dd6 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -546,6 +546,13 @@ static int vl_method_subscribe_dns_resolves(Varlink *link, JsonVariant *paramete if (json_variant_elements(parameters) > 0) return varlink_error_invalid_parameter(link, parameters); + /* Send a ready message to the connecting client, to indicate that we are now listinening, and all + * queries issued after the point the client sees this will also be reported to the client. */ + r = varlink_notifyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("ready", JSON_BUILD_BOOLEAN(true)))); + if (r < 0) + return log_error_errno(r, "Failed to report monitor to be established: %m"); + r = set_ensure_put(&m->varlink_subscription, NULL, link); if (r < 0) return log_error_errno(r, "Failed to add subscription to set: %m");