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);
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;
family = AF_INET6;
p = &rr->aaaa.in6_addr;
} else {
- r = -EAFNOSUPPORT;
- goto finish;
+ return -EAFNOSUPPORT;
}
r = json_build(&entry,
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;
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;
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");