From: Lennart Poettering Date: Mon, 4 Mar 2024 10:22:41 +0000 (+0100) Subject: resolved: expose raw RR resolver via Varlink too X-Git-Tag: v256-rc1~614^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F31616%2Fhead;p=thirdparty%2Fsystemd.git resolved: expose raw RR resolver via Varlink too Now that we have an address, hostname, and service resolve, at the last kind of resovler we expose over D-Bus also to Varlink. --- diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index cdd5ca41fde..6b835904be1 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -15,6 +15,8 @@ typedef struct LookupParameters { union in_addr_union address; size_t address_size; char *name; + uint16_t class; + uint16_t type; } LookupParameters; typedef struct LookupParametersResolveService { @@ -1074,6 +1076,158 @@ static int vl_method_resolve_service(Varlink* link, JsonVariant* parameters, Var return 1; } +static void vl_method_resolve_record_complete(DnsQuery *query) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + _cleanup_(dns_query_freep) DnsQuery *q = query; + DnsQuestion *question; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + unsigned added = 0; + int ifindex; + DnsResourceRecord *rr; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = dns_resource_record_to_json(rr, &v); + if (r < 0) + goto finish; + + r = dns_resource_record_to_wire_format(rr, /* canonical= */ false); /* don't use DNSSEC canonical format, since it removes casing, but we want that for DNS_SD compat */ + if (r < 0) + goto finish; + + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), + JSON_BUILD_PAIR_CONDITION(v, "rr", JSON_BUILD_VARIANT(v)), + JSON_BUILD_PAIR("raw", JSON_BUILD_BASE64(rr->wire_format, rr->wire_format_size)))); + if (r < 0) + goto finish; + + added++; + } + + if (added <= 0) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + r = varlink_replyb(q->varlink_request, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("rrs", JSON_BUILD_VARIANT(array)), + JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); +finish: + if (r < 0) { + log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to send record reply: %m"); + varlink_error_errno(q->varlink_request, r); + } +} + +static int vl_method_resolve_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, + { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(LookupParameters, name), JSON_MANDATORY }, + { "class", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, class), 0 }, + { "type", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, type), JSON_MANDATORY }, + { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, + {} + }; + + _cleanup_(lookup_parameters_destroy) LookupParameters p = { + .class = DNS_CLASS_IN, + .type = _DNS_TYPE_INVALID, + }; + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + Manager *m; + int r; + + assert(link); + + m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); + + if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) + return -EINVAL; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.ifindex < 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); + + r = dns_name_is_valid(p.name); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); + + if (!dns_type_is_valid_query(p.type)) + return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeInvalidForQuery", NULL); + if (dns_type_is_zone_transfer(p.type)) + return varlink_error(link, "io.systemd.Resolve.ZoneTransfersNotPermitted", NULL); + if (dns_type_is_obsolete(p.type)) + return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeObsolete", NULL); + + if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); + + _cleanup_(dns_question_unrefp) DnsQuestion *question = dns_question_new(1); + if (!question) + return -ENOMEM; + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + key = dns_resource_key_new(p.class, p.type, p.name); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key, /* flags= */ 0); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL); + if (r < 0) + return r; + + q->varlink_request = varlink_ref(link); + varlink_set_userdata(link, q); + q->complete = vl_method_resolve_record_complete; + + r = dns_query_go(q); + if (r < 0) + return r; + + TAKE_PTR(q); + return 1; +} + static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { Manager *m; int r; @@ -1297,9 +1451,10 @@ static int varlink_main_server_init(Manager *m) { r = varlink_server_bind_method_many( s, - "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, - "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, - "io.systemd.Resolve.ResolveService", vl_method_resolve_service); + "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, + "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, + "io.systemd.Resolve.ResolveService", vl_method_resolve_service, + "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index c7d934ba77a..a6c501ab011 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -127,6 +127,22 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_OUTPUT_BY_TYPE(canonical, ResolvedCanonical, 0), VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedRecord, + VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + ResolveRecord, + VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(class, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(type, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(rrs, ResolvedRecord, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); + static VARLINK_DEFINE_ERROR(NoNameServers); static VARLINK_DEFINE_ERROR(NoSuchResourceRecord); static VARLINK_DEFINE_ERROR(QueryTimedOut); @@ -150,6 +166,9 @@ static VARLINK_DEFINE_ERROR( VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE)); static VARLINK_DEFINE_ERROR(CNAMELoop); static VARLINK_DEFINE_ERROR(BadAddressSize); +static VARLINK_DEFINE_ERROR(ResourceRecordTypeInvalidForQuery); +static VARLINK_DEFINE_ERROR(ZoneTransfersNotPermitted); +static VARLINK_DEFINE_ERROR(ResourceRecordTypeObsolete); VARLINK_DEFINE_INTERFACE( io_systemd_Resolve, @@ -157,12 +176,14 @@ VARLINK_DEFINE_INTERFACE( &vl_method_ResolveHostname, &vl_method_ResolveAddress, &vl_method_ResolveService, + &vl_method_ResolveRecord, &vl_type_ResolvedAddress, &vl_type_ResolvedName, &vl_type_ResolvedService, &vl_type_ResolvedCanonical, &vl_type_ResourceKey, &vl_type_ResourceRecord, + &vl_type_ResolvedRecord, &vl_error_NoNameServers, &vl_error_NoSuchResourceRecord, &vl_error_QueryTimedOut, @@ -177,4 +198,7 @@ VARLINK_DEFINE_INTERFACE( &vl_error_StubLoop, &vl_error_DNSError, &vl_error_CNAMELoop, - &vl_error_BadAddressSize); + &vl_error_BadAddressSize, + &vl_error_ResourceRecordTypeInvalidForQuery, + &vl_error_ZoneTransfersNotPermitted, + &vl_error_ResourceRecordTypeObsolete); diff --git a/test/units/testsuite-75.sh b/test/units/testsuite-75.sh index 71f299a406b..7bfc067e051 100755 --- a/test/units/testsuite-75.sh +++ b/test/units/testsuite-75.sh @@ -846,6 +846,10 @@ run resolvectl reset-statistics --json=short test "$(resolvectl --json=short query -t AAAA localhost)" == '{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]}' test "$(resolvectl --json=short query -t A localhost)" == '{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]}' +# Test ResolveRecord RR resolving via Varlink +test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":1}' --json=short)" == '{"rrs":[{"ifindex":1,"rr":{"key":{"class":1,"type":1,"name":"localhost"},"address":[127,0,0,1]},"raw":"CWxvY2FsaG9zdAAAAQABAAAAAAAEfwAAAQ=="}],"flags":786945}' +test "$(varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveRecord '{"name":"localhost","type":28}' --json=short)" == '{"rrs":[{"ifindex":1,"rr":{"key":{"class":1,"type":28,"name":"localhost"},"address":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]},"raw":"CWxvY2FsaG9zdAAAHAABAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQ=="}],"flags":786945}' + # Check if resolved exits cleanly. restart_resolved