]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: beef up monitor protocol, include full query info
authorLennart Poettering <lennart@poettering.net>
Wed, 28 Sep 2022 10:46:09 +0000 (12:46 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 30 Sep 2022 12:23:30 +0000 (14:23 +0200)
src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-query.h
src/resolve/resolved-manager.c
src/resolve/resolved-manager.h
src/resolve/resolved-varlink.c

index edd62fa0687a54d99335bebfeda4ffb0ad0e3370..58a7b2d878db4d6ccb049adfaf714700e5bcabce 100644 (file)
@@ -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);
 
index 43a833a08a2bd6ef012b7e8f1cc0e2bac57ae6a8..2723299bee5818e514c02afe2ccfc246d1eea148 100644 (file)
@@ -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;
 
index df3a3fff0477b74b59c9c30b93ebbf7dbf1e6396..ce5935dc7af51e8c049e9bc4a733a4723993de0c 100644 (file)
@@ -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;
-
-                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;
-                }
+        /* 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");
+
+        /* 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_debug_errno(r, "Failed to append notification entry to array: %m");
-        }
+                        return log_error_errno(r, "Failed to convert answer resource record to JSON: %m");
 
-        if (json_variant_is_blank_object(array))
-                return 0;
+                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 = dns_name_normalize(query_name, 0, &normalized);
-        if (r < 0)
-                return log_debug_errno(r, "Failed to normalize query name: %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");
+        }
 
         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;
 }
 
index 844405c2529f0103c7ded33d7516741aa9e990e8..98d90e05b38d7493cde479ca43aea1e75fb5af60 100644 (file)
@@ -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);
index cde406f40e6daa9f89b3e36042572efa6d974732..e344cf6dd6b886cdea1bce5ba377516abb510426 100644 (file)
@@ -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");