mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 02:03:37 +08:00
resolve: provide service resolve over varlink
ported the d-bus implementation of service resolve to varlink extended TEST-75-RESOLVED to cover this use-case
This commit is contained in:
parent
92d1419eb8
commit
e1634bb832
@ -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,14 +177,62 @@ static bool validate_and_mangle_flags(
|
||||
return true;
|
||||
}
|
||||
|
||||
static int find_addr_records(
|
||||
JsonVariant **array,
|
||||
DnsQuestion *question,
|
||||
DnsQuery *q,
|
||||
DnsResourceRecord **canonical,
|
||||
const char *search_domain) {
|
||||
DnsResourceRecord *rr;
|
||||
int ifindex, r;
|
||||
|
||||
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, search_domain);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
if (rr->key->type == DNS_TYPE_A) {
|
||||
family = AF_INET;
|
||||
p = &rr->a.in_addr;
|
||||
} else if (rr->key->type == DNS_TYPE_AAAA) {
|
||||
family = AF_INET6;
|
||||
p = &rr->aaaa.in6_addr;
|
||||
} else {
|
||||
return -EAFNOSUPPORT;
|
||||
}
|
||||
|
||||
r = json_build(&entry,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)),
|
||||
JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)),
|
||||
JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family)))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_variant_append_array(array, entry);
|
||||
if (r < 0)
|
||||
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;
|
||||
DnsResourceRecord *rr;
|
||||
DnsQuestion *question;
|
||||
int ifindex, r;
|
||||
int r;
|
||||
|
||||
assert(q);
|
||||
|
||||
@ -199,43 +256,9 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) {
|
||||
|
||||
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));
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
if (r == 0)
|
||||
continue;
|
||||
|
||||
if (rr->key->type == DNS_TYPE_A) {
|
||||
family = AF_INET;
|
||||
p = &rr->a.in_addr;
|
||||
} else if (rr->key->type == DNS_TYPE_AAAA) {
|
||||
family = AF_INET6;
|
||||
p = &rr->aaaa.in6_addr;
|
||||
} else {
|
||||
r = -EAFNOSUPPORT;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = json_build(&entry,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)),
|
||||
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);
|
||||
|
||||
r = json_variant_append_array(&array, entry);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
}
|
||||
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);
|
||||
@ -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");
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user