summaryrefslogtreecommitdiffstats
path: root/src/resolve/resolved-varlink.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve/resolved-varlink.c')
-rw-r--r--src/resolve/resolved-varlink.c786
1 files changed, 743 insertions, 43 deletions
diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c
index 3e178a6..25f85d8 100644
--- a/src/resolve/resolved-varlink.c
+++ b/src/resolve/resolved-varlink.c
@@ -15,8 +15,19 @@ typedef struct LookupParameters {
union in_addr_union address;
size_t address_size;
char *name;
+ uint16_t class;
+ uint16_t type;
} LookupParameters;
+typedef struct LookupParametersResolveService {
+ const char *name;
+ const char *type;
+ const char *domain;
+ int family;
+ int ifindex;
+ uint64_t flags;
+} LookupParametersResolveService;
+
static void lookup_parameters_destroy(LookupParameters *p) {
assert(p);
free(p->name);
@@ -49,7 +60,11 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_DNSSEC_FAILED:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result)))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NO_TRUST_ANCHOR:
return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL);
@@ -74,7 +89,11 @@ static int reply_query_state(DnsQuery *q) {
case DNS_TRANSACTION_RCODE_FAILURE:
return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError",
- JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode))));
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0,
+ "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)),
+ JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg),
+ "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg))));
case DNS_TRANSACTION_NULL:
case DNS_TRANSACTION_PENDING:
@@ -145,6 +164,7 @@ static bool validate_and_mangle_flags(
SD_RESOLVED_NO_TRUST_ANCHOR|
SD_RESOLVED_NO_NETWORK|
SD_RESOLVED_NO_STALE|
+ SD_RESOLVED_RELAX_SINGLE_LABEL|
ok))
return false;
@@ -160,45 +180,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;
@@ -209,8 +207,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,
@@ -219,16 +216,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;
@@ -530,6 +564,670 @@ 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_octescape(&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,
+ DnsResourceRecord *rr,
+ JsonVariant **array) {
+
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *normalized = NULL;
+ int r;
+
+ assert(q);
+ assert(rr);
+ assert(rr->key);
+ assert(array);
+
+ 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(&v,
+ 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 (canonical) {
+ normalized = mfree(normalized);
+
+ r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized);
+ if (r < 0)
+ return r;
+
+ r = json_variant_set_field_string(&v, "canonicalName", normalized);
+ if (r < 0)
+ return r;
+ }
+
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ _cleanup_(json_variant_unrefp) JsonVariant *addresses = NULL;
+
+ 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(&addresses, question, aux, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ r = json_variant_set_field(&v, "addresses", addresses);
+ if (r < 0)
+ return r;
+ }
+
+ r = json_variant_append_array(array, v);
+ if (r < 0)
+ return r;
+
+ return 1; /* added */
+}
+
+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, *txt = NULL;
+ _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+ _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
+ DnsQuestion *question;
+ DnsResourceRecord *rr;
+ 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, rr, &srv);
+ if (r < 0)
+ goto finish;
+ if (r == 0) /* not an SRV record */
+ continue;
+
+ if (!canonical)
+ canonical = dns_resource_record_ref(rr);
+ }
+
+ if (json_variant_is_blank_object(srv)) {
+ 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("services", JSON_BUILD_VARIANT(srv)),
+ JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_object(txt), "txt", JSON_BUILD_VARIANT(txt)),
+ 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)))),
+ JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(dns_query_reply_flags_make(query)))));
+
+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), 0 },
+ { "type", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, type), 0 },
+ { "domain", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, domain), JSON_MANDATORY },
+ { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, ifindex), 0 },
+ { "family", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, family), 0 },
+ { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParametersResolveService, 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 (p.name && !p.type) /* Service name cannot be specified without service type. */
+ return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type"));
+
+ if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS))
+ 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.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.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.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 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;
@@ -540,7 +1238,7 @@ static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *paramet
/* if the client didn't set the more flag, it is using us incorrectly */
if (!FLAGS_SET(flags, VARLINK_METHOD_MORE))
- return varlink_error_invalid_parameter(link, NULL);
+ return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL);
if (json_variant_elements(parameters) > 0)
return varlink_error_invalid_parameter(link, parameters);
@@ -753,8 +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.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");