resolved: beef up monitor protocol, include full query info

This commit is contained in:
Lennart Poettering 2022-09-28 12:46:09 +02:00
parent 4d593fb151
commit 72c2d39ecb
5 changed files with 120 additions and 42 deletions

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

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

View 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;
/* 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;
}

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

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