resolve: provide service resolve over varlink
authorVishal Chillara Srinivas <vishal.chillarasrinivas@philips.com>
Wed, 15 Nov 2023 07:08:23 +0000 (12:38 +0530)
committerLennart Poettering <lennart@poettering.net>
Fri, 16 Feb 2024 15:24:08 +0000 (16:24 +0100)
ported the d-bus implementation of service resolve to varlink
extended TEST-75-RESOLVED to cover this use-case

src/resolve/resolved-varlink.c
src/shared/varlink-io.systemd.Resolve.c
test/knot-data/zones/signed.test.zone
test/units/testsuite-75.sh

index 4e7e91bdfb75b0a0e540f317f7cc309906188ce9..2f0971a7f0e1e8f130ce0101fd3dded1da44cbbd 100644 (file)
@@ -17,6 +17,15 @@ typedef struct LookupParameters {
         char *name;
 } LookupParameters;
 
+typedef struct LookupParametersResolveService {
+        const char *name;
+        const char *type;
+        const char *domain;
+        int family;
+        int ifindex;
+        uint64_t in_flags;
+} LookupParametersResolveService;
+
 static void lookup_parameters_destroy(LookupParameters *p) {
         assert(p);
         free(p->name);
@@ -168,45 +177,23 @@ static bool validate_and_mangle_flags(
         return true;
 }
 
-static void vl_method_resolve_hostname_complete(DnsQuery *query) {
-        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
-        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
-        _cleanup_(dns_query_freep) DnsQuery *q = query;
-        _cleanup_free_ char *normalized = NULL;
+static int find_addr_records(
+                JsonVariant **array,
+                DnsQuestion *question,
+                DnsQuery *q,
+                DnsResourceRecord **canonical,
+                const char *search_domain) {
         DnsResourceRecord *rr;
-        DnsQuestion *question;
         int ifindex, 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);
-
         DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
                 _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL;
                 int family;
                 const void *p;
 
-                r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+                r = dns_question_matches_rr(question, rr, search_domain);
                 if (r < 0)
-                        goto finish;
+                        return r;
                 if (r == 0)
                         continue;
 
@@ -217,8 +204,7 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) {
                         family = AF_INET6;
                         p = &rr->aaaa.in6_addr;
                 } else {
-                        r = -EAFNOSUPPORT;
-                        goto finish;
+                        return -EAFNOSUPPORT;
                 }
 
                 r = json_build(&entry,
@@ -227,16 +213,53 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) {
                                                JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)),
                                                JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family)))));
                 if (r < 0)
-                        goto finish;
-
-                if (!canonical)
-                        canonical = dns_resource_record_ref(rr);
+                        return r;
 
-                r = json_variant_append_array(&array, entry);
+                r = json_variant_append_array(array, entry);
                 if (r < 0)
-                        goto finish;
+                        return r;
+
+                if (canonical && !*canonical)
+                        *canonical = dns_resource_record_ref(rr);
         }
 
+        return 0;
+}
+
+static void vl_method_resolve_hostname_complete(DnsQuery *query) {
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+        _cleanup_(dns_query_freep) DnsQuery *q = query;
+        _cleanup_free_ char *normalized = NULL;
+        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);
+
+        r = find_addr_records(&array, question, q, &canonical, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+        if (r < 0)
+                goto finish;
+
         if (json_variant_is_blank_object(array)) {
                 r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
                 goto finish;
@@ -538,6 +561,509 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var
         return 1;
 }
 
+static int append_txt(JsonVariant **txt, DnsResourceRecord *rr) {
+        int r;
+
+        assert(txt);
+        assert(rr);
+        assert(rr->key);
+
+        if (rr->key->type != DNS_TYPE_TXT)
+                return 0;
+
+        LIST_FOREACH(items, i, rr->txt.items) {
+                _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL;
+
+                if (i->length <= 0)
+                        continue;
+
+                r = json_variant_new_base64(&entry, i->data, i->length);
+                if (r < 0)
+                        return r;
+
+                r = json_variant_append_array(txt, entry);
+                if (r < 0)
+                        return r;
+        }
+
+        return 1;
+}
+
+static int append_srv(DnsQuery *q,
+                      JsonVariant **ret_srv,
+                      JsonVariant **ret_addr,
+                      char **ret_norm,
+                      DnsResourceRecord *rr) {
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+        _cleanup_free_ char *normalized = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *srv = NULL, *addr = NULL;
+
+        int r;
+
+        assert(q);
+        assert(rr);
+        assert(rr->key);
+
+        if (rr->key->type != DNS_TYPE_SRV)
+                return 0;
+
+        if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+                /* First, let's see if we could find an appropriate A or AAAA
+                 * record for the SRV record */
+                LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+                        DnsResourceRecord *zz;
+                        DnsQuestion *question;
+
+                        if (aux->state != DNS_TRANSACTION_SUCCESS)
+                                continue;
+                        if (aux->auxiliary_result != 0)
+                                continue;
+
+                        question = dns_query_question_for_protocol(aux, aux->answer_protocol);
+
+                        r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                continue;
+
+                        DNS_ANSWER_FOREACH(zz, aux->answer) {
+                                r = dns_question_matches_rr(question, zz, NULL);
+                                if (r < 0)
+                                        return r;
+                                if (r == 0)
+                                        continue;
+
+                                canonical = dns_resource_record_ref(zz);
+                                break;
+                        }
+
+                        if (canonical)
+                                break;
+                }
+
+                /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */
+                if (!canonical)
+                        return 0;
+        }
+
+        r = dns_name_normalize(rr->srv.name, 0, &normalized);
+        if (r < 0)
+                return r;
+
+        r = json_build(&srv,
+                       JSON_BUILD_OBJECT(
+                                        JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->srv.priority)),
+                                        JSON_BUILD_PAIR("weight", JSON_BUILD_UNSIGNED(rr->srv.weight)),
+                                        JSON_BUILD_PAIR("port", JSON_BUILD_UNSIGNED(rr->srv.port)),
+                                        JSON_BUILD_PAIR("hostname", JSON_BUILD_STRING(normalized))));
+        if (r < 0)
+                return r;
+
+        if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+                LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+                        DnsQuestion *question;
+
+                        if (aux->state != DNS_TRANSACTION_SUCCESS)
+                                continue;
+                        if (aux->auxiliary_result != 0)
+                                continue;
+
+                        question = dns_query_question_for_protocol(aux, aux->answer_protocol);
+
+                        r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                continue;
+
+                        r = find_addr_records(&addr, question, aux, NULL, NULL);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (canonical) {
+                normalized = mfree(normalized);
+
+                r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized);
+                if (r < 0)
+                        return r;
+        }
+
+        *ret_srv = TAKE_PTR(srv);
+        *ret_addr = TAKE_PTR(addr);
+        *ret_norm = TAKE_PTR(normalized);
+
+        return 1;
+}
+
+static Varlink *get_vl_link_aux_query(DnsQuery *aux) {
+        assert(aux);
+
+        /* Find the main query */
+        while (aux->auxiliary_for)
+                aux = aux->auxiliary_for;
+
+        return aux->varlink_request;
+}
+
+static void resolve_service_all_complete(DnsQuery *query) {
+        _cleanup_(dns_query_freep) DnsQuery *q = query;
+        _cleanup_(json_variant_unrefp) JsonVariant *srv = NULL, *addr = NULL, *txt = NULL;
+        _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL, *norm = NULL;
+        _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+        DnsQuestion *question;
+        DnsResourceRecord *rr;
+        unsigned added = 0;
+        int r;
+
+        assert(q);
+
+        if (q->block_all_complete > 0) {
+                TAKE_PTR(q);
+                return;
+        }
+
+        if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+                DnsQuery *bad = NULL;
+                bool have_success = false;
+
+                LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
+                        switch (aux->state) {
+
+                        case DNS_TRANSACTION_PENDING:
+                                /* If an auxiliary query is still pending, let's wait */
+                                TAKE_PTR(q);
+                                return;
+
+                        case DNS_TRANSACTION_SUCCESS:
+                                if (aux->auxiliary_result == 0)
+                                        have_success = true;
+                                else
+                                        bad = aux;
+                                break;
+
+                        default:
+                                bad = aux;
+                                break;
+                        }
+                }
+                if (!have_success) {
+                        /* We can only return one error, hence pick the last error we encountered */
+
+                        assert(bad);
+                        if (bad->state == DNS_TRANSACTION_SUCCESS) {
+                                assert(bad->auxiliary_result != 0);
+
+                                if (bad->auxiliary_result == -ELOOP) {
+                                        r = varlink_error(query->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL);
+                                        goto finish;
+                                }
+
+                                assert(bad->auxiliary_result < 0);
+                                r = bad->auxiliary_result;
+                                goto finish;
+                        }
+
+                        bad->varlink_request = get_vl_link_aux_query(bad);
+                        r = reply_query_state(bad);
+                        bad->varlink_request = NULL;
+                        goto finish;
+                }
+        }
+
+        question = dns_query_question_for_protocol(q, q->answer_protocol);
+
+        DNS_ANSWER_FOREACH(rr, q->answer) {
+                r = dns_question_matches_rr(question, rr, NULL);
+                if (r < 0)
+                        goto finish;
+                if (r == 0)
+                        continue;
+
+                r = append_srv(q, &srv, &addr, &norm, rr);
+                if (r < 0)
+                        goto finish;
+                if (r == 0) /* not an SRV record */
+                        continue;
+
+                if (!canonical)
+                        canonical = dns_resource_record_ref(rr);
+
+                added++;
+        }
+
+        if (added <= 0) {
+                r = varlink_error(query->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
+                goto finish;
+        }
+
+        DNS_ANSWER_FOREACH(rr, q->answer) {
+                r = dns_question_matches_rr(question, rr, NULL);
+                if (r < 0)
+                        goto finish;
+                if (r == 0)
+                        continue;
+
+                if (rr->key->type != DNS_TYPE_TXT)
+                        continue;
+
+                r = append_txt(&txt, rr);
+                if (r < 0)
+                        goto finish;
+        }
+
+        assert(canonical);
+        r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain);
+        if (r < 0)
+                goto finish;
+
+        r = varlink_replyb(query->varlink_request, JSON_BUILD_OBJECT(
+                                        JSON_BUILD_PAIR("srv", JSON_BUILD_VARIANT(srv)),
+                                        JSON_BUILD_PAIR("addr", JSON_BUILD_VARIANT(addr)),
+                                        JSON_BUILD_PAIR("txt", JSON_BUILD_VARIANT(txt)),
+                                        JSON_BUILD_PAIR("normalized", JSON_BUILD_STRING(norm)),
+                                        JSON_BUILD_PAIR("canonical", JSON_BUILD_OBJECT(
+                                                                        JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)),
+                                                                        JSON_BUILD_PAIR("type", JSON_BUILD_STRING(type)),
+                                                                        JSON_BUILD_PAIR("domain", JSON_BUILD_STRING(domain))))));
+
+finish:
+        if (r < 0) {
+                log_error_errno(r, "Failed to resolve service: %m");
+                r = varlink_error_errno(q->varlink_request, r);
+        }
+}
+
+static void resolve_service_hostname_complete(DnsQuery *q) {
+        int r;
+
+        assert(q);
+        assert(q->auxiliary_for);
+
+        if (q->state != DNS_TRANSACTION_SUCCESS) {
+                resolve_service_all_complete(q->auxiliary_for);
+                return;
+        }
+
+        r = dns_query_process_cname_many(q);
+        if (r == DNS_QUERY_CNAME) /* This was a cname, and the query was restarted. */
+                return;
+
+        /* This auxiliary lookup is finished or failed, let's see if all are finished now. */
+        q->auxiliary_result = r < 0 ? r : 0;
+        resolve_service_all_complete(q->auxiliary_for);
+}
+
+static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) {
+        _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+        _cleanup_(dns_query_freep) DnsQuery *aux = NULL;
+        int r;
+
+        assert(q);
+        assert(rr);
+        assert(rr->key);
+        assert(rr->key->type == DNS_TYPE_SRV);
+
+        /* OK, we found an SRV record for the service. Let's resolve
+         * the hostname included in it */
+
+        r = dns_question_new_address(&question, q->request_family, rr->srv.name, false);
+        if (r < 0)
+                return r;
+
+        r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
+        if (r < 0)
+                return r;
+
+        aux->request_family = q->request_family;
+        aux->complete = resolve_service_hostname_complete;
+
+        r = dns_query_make_auxiliary(aux, q);
+        if (r == -EAGAIN)
+                /* Too many auxiliary lookups? If so, don't complain,
+                 * let's just not add this one, we already have more
+                 * than enough */
+                return 0;
+        if (r < 0)
+                return r;
+
+        /* Note that auxiliary queries do not track the original
+         * client, only the primary request does that. */
+
+        r = dns_query_go(aux);
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(aux);
+        return 1;
+}
+
+static void vl_method_resolve_service_complete(DnsQuery *query) {
+        _cleanup_(dns_query_freep) DnsQuery *q = query;
+        bool has_root_domain = false;
+        DnsResourceRecord *rr;
+        DnsQuestion *question;
+        unsigned found = 0;
+        int ifindex, 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);
+
+        DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+                r = dns_question_matches_rr(question, rr, NULL);
+                if (r < 0)
+                        goto finish;
+                if (r == 0)
+                        continue;
+
+                if (rr->key->type != DNS_TYPE_SRV)
+                        continue;
+
+                if (dns_name_is_root(rr->srv.name)) {
+                        has_root_domain = true;
+                        continue;
+                }
+
+                if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+                        q->block_all_complete++;
+                        r = resolve_service_hostname(q, rr, ifindex);
+                        q->block_all_complete--;
+
+                        if (r < 0)
+                                goto finish;
+                }
+
+                found++;
+        }
+
+        if (has_root_domain && found <= 0) {
+                /* If there's exactly one SRV RR and it uses the root domain as hostname, then the service is
+                 * explicitly not offered on the domain. Report this as a recognizable error. See RFC 2782,
+                 * Section "Usage Rules". */
+                r = varlink_error(q->varlink_request, "io.systemd.Resolve.ServiceNotProvided", NULL);
+                goto finish;
+        }
+
+        if (found <= 0) {
+                r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL);
+                goto finish;
+        }
+
+        /* Maybe we are already finished? check now... */
+        resolve_service_all_complete(TAKE_PTR(q));
+        return;
+
+finish:
+        if (r < 0) {
+                log_error_errno(r, "Failed to send address reply: %m");
+                r = varlink_error_errno(q->varlink_request, r);
+        }
+}
+
+static int vl_method_resolve_service(Varlink* link, JsonVariant* parameters, VarlinkMethodFlags flags, void* userdata) {
+        static const JsonDispatch dispatch_table[] = {
+                { "name",    JSON_VARIANT_STRING,   json_dispatch_const_string, offsetof(LookupParametersResolveService, name),     JSON_MANDATORY },
+                { "type",    JSON_VARIANT_STRING,   json_dispatch_const_string, offsetof(LookupParametersResolveService, type),     JSON_MANDATORY },
+                { "domain",  JSON_VARIANT_STRING,   json_dispatch_const_string, offsetof(LookupParametersResolveService, domain),   JSON_MANDATORY },
+                { "ifindex", JSON_VARIANT_UNSIGNED, json_dispatch_int,          offsetof(LookupParametersResolveService, ifindex),  0              },
+                { "family",  JSON_VARIANT_INTEGER,  json_dispatch_int,          offsetof(LookupParametersResolveService, family),   0              },
+                { "flags",   JSON_VARIANT_UNSIGNED, json_dispatch_uint64,       offsetof(LookupParametersResolveService, in_flags), 0              },
+                {}
+        };
+
+        _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
+        LookupParametersResolveService p = {
+                .family = AF_UNSPEC,
+        };
+
+        _cleanup_(dns_query_freep) DnsQuery *q = NULL;
+        Manager *m;
+        int r;
+
+        assert(link);
+
+        m = varlink_server_get_userdata(varlink_get_server(link));
+        assert(m);
+
+        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"));
+
+        if (!IN_SET(p.family, AF_INET, AF_INET6, AF_UNSPEC))
+                return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family"));
+
+        if (isempty(p.name))
+                p.name = NULL;
+        else if (!dns_service_name_is_valid(p.name))
+                return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name"));
+
+        if (isempty(p.type))
+                p.type = NULL;
+        else if (!dns_srv_type_is_valid(p.type))
+                return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type"));
+
+        r = dns_name_is_valid(p.domain);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("domain"));
+
+        if (!validate_and_mangle_flags(p.name, &p.in_flags, 0))
+                return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
+
+        r = dns_question_new_service(&question_utf8, p.name, p.type, p.domain, !(p.in_flags & SD_RESOLVED_NO_TXT), false);
+        if (r < 0)
+                return r;
+
+        r = dns_question_new_service(&question_idna, p.name, p.type, p.domain, !(p.in_flags & SD_RESOLVED_NO_TXT), true);
+        if (r < 0)
+                return r;
+
+        r = dns_query_new(m, &q, question_utf8, question_idna, NULL, p.ifindex, p.in_flags|SD_RESOLVED_NO_SEARCH);
+        if (r < 0)
+                return r;
+
+        q->varlink_request = varlink_ref(link);
+        q->request_family = p.family;
+        q->complete = vl_method_resolve_service_complete;
+
+        varlink_set_userdata(link, q);
+
+        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;
@@ -762,7 +1288,8 @@ 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.ResolveAddress", vl_method_resolve_address,
+                        "io.systemd.Resolve.ResolveService", vl_method_resolve_service);
         if (r < 0)
                 return log_error_errno(r, "Failed to register varlink methods: %m");
 
index 627b062ab0cb8088084f4d1c47d798bdcb65f40e..5e27080abc26f9617fd9fa51c73ff795d9004eda 100644 (file)
@@ -32,6 +32,33 @@ static VARLINK_DEFINE_METHOD(
                 VARLINK_DEFINE_OUTPUT_BY_TYPE(names, ResolvedName, VARLINK_ARRAY),
                 VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0));
 
+static VARLINK_DEFINE_STRUCT_TYPE(
+                ResolvedService,
+                VARLINK_DEFINE_FIELD(priority, VARLINK_INT, 0),
+                VARLINK_DEFINE_FIELD(weight, VARLINK_INT, 0),
+                VARLINK_DEFINE_FIELD(port, VARLINK_INT, 0),
+                VARLINK_DEFINE_FIELD(hostname, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_STRUCT_TYPE(
+                ResolvedCanonical,
+                VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0),
+                VARLINK_DEFINE_FIELD(type, VARLINK_STRING, 0),
+                VARLINK_DEFINE_FIELD(domain, VARLINK_STRING, 0));
+
+static VARLINK_DEFINE_METHOD(
+                ResolveService,
+                VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
+                VARLINK_DEFINE_INPUT(type, VARLINK_STRING, 0),
+                VARLINK_DEFINE_INPUT(domain, VARLINK_STRING, 0),
+                VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(family, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE),
+                VARLINK_DEFINE_OUTPUT_BY_TYPE(srv, ResolvedService, 0),
+                VARLINK_DEFINE_OUTPUT_BY_TYPE(addr, ResolvedAddress, VARLINK_ARRAY),
+                VARLINK_DEFINE_OUTPUT(txt, VARLINK_STRING, VARLINK_ARRAY),
+                VARLINK_DEFINE_OUTPUT(normalized, VARLINK_STRING, 0),
+                VARLINK_DEFINE_OUTPUT_BY_TYPE(canonical, ResolvedCanonical, 0));
+
 static VARLINK_DEFINE_ERROR(NoNameServers);
 static VARLINK_DEFINE_ERROR(NoSuchResourceRecord);
 static VARLINK_DEFINE_ERROR(QueryTimedOut);
@@ -61,8 +88,11 @@ VARLINK_DEFINE_INTERFACE(
                 "io.systemd.Resolve",
                 &vl_method_ResolveHostname,
                 &vl_method_ResolveAddress,
+                &vl_method_ResolveService,
                 &vl_type_ResolvedAddress,
                 &vl_type_ResolvedName,
+                &vl_type_ResolvedService,
+                &vl_type_ResolvedCanonical,
                 &vl_error_NoNameServers,
                 &vl_error_NoSuchResourceRecord,
                 &vl_error_QueryTimedOut,
index a2baac42841a729aa981a0d590eb93da00ebc15d..5d75aa0a74eef93a3c52450b0009d07206857413 100644 (file)
@@ -53,6 +53,7 @@ follow14.final        A     10.0.0.14
 myservice             A     10.0.0.20
 myservice             AAAA  fd00:dead:beef:cafe::17
 _mysvc._tcp           SRV   10 5 1234 myservice
+_mysvc._tcp           TXT   "This is TXT for myservice"
 
 _invalidsvc._udp      SRV   5 5 1111 invalidservice
 
index 818a23736d530969db5cc47a6a6f1dd279fafb13..4b7a8063253d2736c0ae568f786f8832b0dc2a17 100755 (executable)
@@ -434,6 +434,18 @@ grep -qF "myservice.signed.test:1234" "$RUN_OUT"
 grep -qF "10.0.0.20" "$RUN_OUT"
 grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT"
 grep -qF "authenticated: yes" "$RUN_OUT"
+
+# Test service resolve over Varlink
+run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}'
+grep -qF '"srv":{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test"}' "$RUN_OUT"
+grep -qF '"addr":[{"ifindex":' "$RUN_OUT"
+grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT"
+grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT"
+grep -qF '"normalized":"myservice.signed.test"' "$RUN_OUT"
+grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT"
+TXT_OUT=$(grep -a -o -P '(?<=\"txt\"\:\[\").*(?=\"\])' "$RUN_OUT" | base64 --decode)
+assert_in "This is TXT for myservice" "$TXT_OUT"
+
 (! run resolvectl service _invalidsvc._udp signed.test)
 grep -qE "invalidservice\.signed\.test' not found" "$RUN_OUT"
 run resolvectl service _untrustedsvc._udp signed.test