]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
9581bb84 | 2 | |
28e5e1e9 | 3 | #include "glyph-util.h" |
9581bb84 LP |
4 | #include "in-addr-util.h" |
5 | #include "resolved-dns-synthesize.h" | |
6 | #include "resolved-varlink.h" | |
7 | #include "socket-netlink.h" | |
abef4a7b LP |
8 | #include "varlink-io.systemd.Resolve.h" |
9 | #include "varlink-io.systemd.Resolve.Monitor.h" | |
9581bb84 LP |
10 | |
11 | typedef struct LookupParameters { | |
12 | int ifindex; | |
13 | uint64_t flags; | |
14 | int family; | |
15 | union in_addr_union address; | |
16 | size_t address_size; | |
17 | char *name; | |
18 | } LookupParameters; | |
19 | ||
20 | static void lookup_parameters_destroy(LookupParameters *p) { | |
21 | assert(p); | |
22 | free(p->name); | |
23 | } | |
24 | ||
25 | static int reply_query_state(DnsQuery *q) { | |
26 | ||
27 | assert(q); | |
28 | assert(q->varlink_request); | |
29 | ||
30 | switch (q->state) { | |
31 | ||
32 | case DNS_TRANSACTION_NO_SERVERS: | |
33 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NoNameServers", NULL); | |
34 | ||
35 | case DNS_TRANSACTION_TIMEOUT: | |
36 | return varlink_error(q->varlink_request, "io.systemd.Resolve.QueryTimedOut", NULL); | |
37 | ||
38 | case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: | |
39 | return varlink_error(q->varlink_request, "io.systemd.Resolve.MaxAttemptsReached", NULL); | |
40 | ||
41 | case DNS_TRANSACTION_INVALID_REPLY: | |
42 | return varlink_error(q->varlink_request, "io.systemd.Resolve.InvalidReply", NULL); | |
43 | ||
44 | case DNS_TRANSACTION_ERRNO: | |
45 | return varlink_error_errno(q->varlink_request, q->answer_errno); | |
46 | ||
47 | case DNS_TRANSACTION_ABORTED: | |
48 | return varlink_error(q->varlink_request, "io.systemd.Resolve.QueryAborted", NULL); | |
49 | ||
50 | case DNS_TRANSACTION_DNSSEC_FAILED: | |
51 | return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed", | |
52 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))))); | |
53 | ||
54 | case DNS_TRANSACTION_NO_TRUST_ANCHOR: | |
55 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL); | |
56 | ||
57 | case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: | |
58 | return varlink_error(q->varlink_request, "io.systemd.Resolve.ResourceRecordTypeUnsupported", NULL); | |
59 | ||
60 | case DNS_TRANSACTION_NETWORK_DOWN: | |
61 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NetworkDown", NULL); | |
62 | ||
775ae354 LP |
63 | case DNS_TRANSACTION_NO_SOURCE: |
64 | return varlink_error(q->varlink_request, "io.systemd.Resolve.NoSource", NULL); | |
65 | ||
49ef064c LP |
66 | case DNS_TRANSACTION_STUB_LOOP: |
67 | return varlink_error(q->varlink_request, "io.systemd.Resolve.StubLoop", NULL); | |
68 | ||
9581bb84 LP |
69 | case DNS_TRANSACTION_NOT_FOUND: |
70 | /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we | |
71 | * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ | |
72 | return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError", | |
73 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(DNS_RCODE_NXDOMAIN)))); | |
74 | ||
75 | case DNS_TRANSACTION_RCODE_FAILURE: | |
76 | return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError", | |
77 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)))); | |
78 | ||
79 | case DNS_TRANSACTION_NULL: | |
80 | case DNS_TRANSACTION_PENDING: | |
81 | case DNS_TRANSACTION_VALIDATING: | |
82 | case DNS_TRANSACTION_SUCCESS: | |
83 | default: | |
04499a70 | 84 | assert_not_reached(); |
9581bb84 LP |
85 | } |
86 | } | |
87 | ||
88 | static void vl_on_disconnect(VarlinkServer *s, Varlink *link, void *userdata) { | |
89 | DnsQuery *q; | |
90 | ||
91 | assert(s); | |
92 | assert(link); | |
93 | ||
94 | q = varlink_get_userdata(link); | |
95 | if (!q) | |
96 | return; | |
97 | ||
98 | if (!DNS_TRANSACTION_IS_LIVE(q->state)) | |
99 | return; | |
100 | ||
101 | log_debug("Client of active query vanished, aborting query."); | |
102 | dns_query_complete(q, DNS_TRANSACTION_ABORTED); | |
103 | } | |
104 | ||
cb456374 SK |
105 | static void vl_on_notification_disconnect(VarlinkServer *s, Varlink *link, void *userdata) { |
106 | Manager *m = ASSERT_PTR(userdata); | |
107 | ||
108 | assert(s); | |
109 | assert(link); | |
110 | ||
111 | Varlink *removed_link = set_remove(m->varlink_subscription, link); | |
112 | if (removed_link) { | |
113 | varlink_unref(removed_link); | |
114 | log_debug("%u monitor clients remain active", set_size(m->varlink_subscription)); | |
115 | } | |
116 | } | |
117 | ||
3354f500 LP |
118 | static bool validate_and_mangle_flags( |
119 | const char *name, | |
120 | uint64_t *flags, | |
121 | uint64_t ok) { | |
122 | ||
9581bb84 LP |
123 | assert(flags); |
124 | ||
125 | /* This checks that the specified client-provided flags parameter actually makes sense, and mangles | |
126 | * it slightly. Specifically: | |
127 | * | |
775ae354 | 128 | * 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the |
9581bb84 LP |
129 | * method-specific flags specified in 'ok'. |
130 | * | |
131 | * 2. If no protocols are enabled we automatically convert that to "all protocols are enabled". | |
132 | * | |
133 | * The second rule means that clients can just pass 0 as flags for the common case, and all supported | |
134 | * protocols are enabled. Moreover it's useful so that client's do not have to be aware of all | |
135 | * protocols implemented in resolved, but can use 0 as protocols flags set as indicator for | |
136 | * "everything". | |
137 | */ | |
138 | ||
775ae354 LP |
139 | if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| |
140 | SD_RESOLVED_NO_CNAME| | |
141 | SD_RESOLVED_NO_VALIDATE| | |
142 | SD_RESOLVED_NO_SYNTHESIZE| | |
143 | SD_RESOLVED_NO_CACHE| | |
144 | SD_RESOLVED_NO_ZONE| | |
145 | SD_RESOLVED_NO_TRUST_ANCHOR| | |
146 | SD_RESOLVED_NO_NETWORK| | |
5ed91481 | 147 | SD_RESOLVED_NO_STALE| |
775ae354 | 148 | ok)) |
9581bb84 LP |
149 | return false; |
150 | ||
151 | if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ | |
152 | *flags |= SD_RESOLVED_PROTOCOLS_ALL; | |
153 | ||
3354f500 LP |
154 | /* If the SD_RESOLVED_NO_SEARCH flag is acceptable, and the query name is dot-suffixed, turn off |
155 | * search domains. Note that DNS name normalization drops the dot suffix, hence we propagate this | |
156 | * into the flags field as early as we can. */ | |
157 | if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0) | |
158 | *flags |= SD_RESOLVED_NO_SEARCH; | |
159 | ||
9581bb84 LP |
160 | return true; |
161 | } | |
162 | ||
c704288c | 163 | static void vl_method_resolve_hostname_complete(DnsQuery *query) { |
9581bb84 LP |
164 | _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; |
165 | _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; | |
c704288c | 166 | _cleanup_(dns_query_freep) DnsQuery *q = query; |
9581bb84 LP |
167 | _cleanup_free_ char *normalized = NULL; |
168 | DnsResourceRecord *rr; | |
169 | DnsQuestion *question; | |
170 | int ifindex, r; | |
171 | ||
172 | assert(q); | |
173 | ||
174 | if (q->state != DNS_TRANSACTION_SUCCESS) { | |
175 | r = reply_query_state(q); | |
176 | goto finish; | |
177 | } | |
178 | ||
1db8e6d1 | 179 | r = dns_query_process_cname_many(q); |
9581bb84 LP |
180 | if (r == -ELOOP) { |
181 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); | |
182 | goto finish; | |
183 | } | |
184 | if (r < 0) | |
185 | goto finish; | |
c704288c YW |
186 | if (r == DNS_QUERY_CNAME) { |
187 | /* This was a cname, and the query was restarted. */ | |
188 | TAKE_PTR(q); | |
9581bb84 | 189 | return; |
c704288c | 190 | } |
9581bb84 LP |
191 | |
192 | question = dns_query_question_for_protocol(q, q->answer_protocol); | |
193 | ||
194 | DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { | |
195 | _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; | |
196 | int family; | |
197 | const void *p; | |
198 | ||
199 | r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); | |
200 | if (r < 0) | |
201 | goto finish; | |
202 | if (r == 0) | |
203 | continue; | |
204 | ||
205 | if (rr->key->type == DNS_TYPE_A) { | |
206 | family = AF_INET; | |
207 | p = &rr->a.in_addr; | |
208 | } else if (rr->key->type == DNS_TYPE_AAAA) { | |
209 | family = AF_INET6; | |
210 | p = &rr->aaaa.in6_addr; | |
211 | } else { | |
212 | r = -EAFNOSUPPORT; | |
213 | goto finish; | |
214 | } | |
215 | ||
216 | r = json_build(&entry, | |
217 | JSON_BUILD_OBJECT( | |
f8f5b8d8 | 218 | JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), |
9581bb84 LP |
219 | JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)), |
220 | JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family))))); | |
221 | if (r < 0) | |
222 | goto finish; | |
223 | ||
224 | if (!canonical) | |
225 | canonical = dns_resource_record_ref(rr); | |
226 | ||
227 | r = json_variant_append_array(&array, entry); | |
228 | if (r < 0) | |
229 | goto finish; | |
230 | } | |
231 | ||
232 | if (json_variant_is_blank_object(array)) { | |
233 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); | |
234 | goto finish; | |
235 | } | |
236 | ||
237 | assert(canonical); | |
238 | r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized); | |
239 | if (r < 0) | |
240 | goto finish; | |
241 | ||
242 | r = varlink_replyb(q->varlink_request, | |
243 | JSON_BUILD_OBJECT( | |
244 | JSON_BUILD_PAIR("addresses", JSON_BUILD_VARIANT(array)), | |
245 | JSON_BUILD_PAIR("name", JSON_BUILD_STRING(normalized)), | |
43fc4baa | 246 | JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); |
9581bb84 LP |
247 | finish: |
248 | if (r < 0) { | |
40557509 | 249 | log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to send hostname reply: %m"); |
9581bb84 LP |
250 | r = varlink_error_errno(q->varlink_request, r); |
251 | } | |
9581bb84 LP |
252 | } |
253 | ||
254 | static int parse_as_address(Varlink *link, LookupParameters *p) { | |
255 | _cleanup_free_ char *canonical = NULL; | |
256 | int r, ff, parsed_ifindex, ifindex; | |
257 | union in_addr_union parsed; | |
258 | ||
259 | assert(link); | |
260 | assert(p); | |
261 | ||
262 | /* Check if this parses as literal address. If so, just parse it and return that, do not involve networking */ | |
263 | r = in_addr_ifindex_from_string_auto(p->name, &ff, &parsed, &parsed_ifindex); | |
264 | if (r < 0) | |
265 | return 0; /* not a literal address */ | |
266 | ||
267 | /* Make sure the data we parsed matches what is requested */ | |
268 | if ((p->family != AF_UNSPEC && ff != p->family) || | |
269 | (p->ifindex > 0 && parsed_ifindex > 0 && parsed_ifindex != p->ifindex)) | |
270 | return varlink_error(link, "io.systemd.Resolve.NoSuchResourceRecord", NULL); | |
271 | ||
272 | ifindex = parsed_ifindex > 0 ? parsed_ifindex : p->ifindex; | |
273 | ||
274 | /* Reformat the address as string, to return as canonicalized name */ | |
275 | r = in_addr_ifindex_to_string(ff, &parsed, ifindex, &canonical); | |
276 | if (r < 0) | |
277 | return r; | |
278 | ||
279 | return varlink_replyb( | |
280 | link, | |
281 | JSON_BUILD_OBJECT( | |
282 | JSON_BUILD_PAIR("addresses", | |
283 | JSON_BUILD_ARRAY( | |
284 | JSON_BUILD_OBJECT( | |
285 | JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), | |
286 | JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(ff)), | |
287 | JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(&parsed, FAMILY_ADDRESS_SIZE(ff)))))), | |
288 | JSON_BUILD_PAIR("name", JSON_BUILD_STRING(canonical)), | |
5c1790d1 LP |
289 | JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(SD_RESOLVED_FLAGS_MAKE(dns_synthesize_protocol(p->flags), ff, true, true)| |
290 | SD_RESOLVED_SYNTHETIC)))); | |
9581bb84 LP |
291 | } |
292 | ||
293 | static int vl_method_resolve_hostname(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
294 | static const JsonDispatch dispatch_table[] = { | |
295 | { "ifindex", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, | |
296 | { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(LookupParameters, name), JSON_MANDATORY }, | |
297 | { "family", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, family), 0 }, | |
298 | { "flags", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, | |
299 | {} | |
300 | }; | |
301 | ||
302 | _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; | |
303 | _cleanup_(lookup_parameters_destroy) LookupParameters p = { | |
304 | .family = AF_UNSPEC, | |
305 | }; | |
c704288c | 306 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
9807fdc1 | 307 | Manager *m; |
9581bb84 LP |
308 | int r; |
309 | ||
310 | assert(link); | |
9807fdc1 LP |
311 | |
312 | m = varlink_server_get_userdata(varlink_get_server(link)); | |
9581bb84 LP |
313 | assert(m); |
314 | ||
315 | if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) | |
316 | return -EINVAL; | |
317 | ||
f1b622a0 LP |
318 | r = varlink_dispatch(link, parameters, dispatch_table, &p); |
319 | if (r != 0) | |
9581bb84 LP |
320 | return r; |
321 | ||
322 | if (p.ifindex < 0) | |
323 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); | |
324 | ||
325 | r = dns_name_is_valid(p.name); | |
326 | if (r < 0) | |
327 | return r; | |
328 | if (r == 0) | |
329 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); | |
330 | ||
331 | if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6)) | |
332 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); | |
333 | ||
3354f500 | 334 | if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) |
9581bb84 LP |
335 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); |
336 | ||
337 | r = parse_as_address(link, &p); | |
338 | if (r != 0) | |
339 | return r; | |
340 | ||
341 | r = dns_question_new_address(&question_utf8, p.family, p.name, false); | |
342 | if (r < 0) | |
343 | return r; | |
344 | ||
345 | r = dns_question_new_address(&question_idna, p.family, p.name, true); | |
346 | if (r < 0 && r != -EALREADY) | |
347 | return r; | |
348 | ||
775ae354 | 349 | r = dns_query_new(m, &q, question_utf8, question_idna ?: question_utf8, NULL, p.ifindex, p.flags); |
9581bb84 LP |
350 | if (r < 0) |
351 | return r; | |
352 | ||
353 | q->varlink_request = varlink_ref(link); | |
354 | varlink_set_userdata(link, q); | |
355 | q->request_family = p.family; | |
356 | q->complete = vl_method_resolve_hostname_complete; | |
357 | ||
358 | r = dns_query_go(q); | |
359 | if (r < 0) | |
c704288c | 360 | return r; |
9581bb84 | 361 | |
c704288c | 362 | TAKE_PTR(q); |
9581bb84 | 363 | return 1; |
9581bb84 LP |
364 | } |
365 | ||
366 | static int json_dispatch_address(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { | |
99534007 | 367 | LookupParameters *p = ASSERT_PTR(userdata); |
9581bb84 LP |
368 | union in_addr_union buf = {}; |
369 | JsonVariant *i; | |
370 | size_t n, k = 0; | |
371 | ||
372 | assert(variant); | |
9581bb84 LP |
373 | |
374 | if (!json_variant_is_array(variant)) | |
375 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); | |
376 | ||
377 | n = json_variant_elements(variant); | |
378 | if (!IN_SET(n, 4, 16)) | |
379 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); | |
380 | ||
381 | JSON_VARIANT_ARRAY_FOREACH(i, variant) { | |
718ca772 | 382 | int64_t b; |
9581bb84 LP |
383 | |
384 | if (!json_variant_is_integer(i)) | |
385 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an integer.", k, strna(name)); | |
386 | ||
387 | b = json_variant_integer(i); | |
388 | if (b < 0 || b > 0xff) | |
28e5e1e9 DT |
389 | return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), |
390 | "Element %zu of JSON field '%s' is out of range 0%s255.", | |
391 | k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); | |
9581bb84 LP |
392 | |
393 | buf.bytes[k++] = (uint8_t) b; | |
394 | } | |
395 | ||
396 | p->address = buf; | |
397 | p->address_size = k; | |
398 | ||
399 | return 0; | |
400 | } | |
401 | ||
c704288c | 402 | static void vl_method_resolve_address_complete(DnsQuery *query) { |
9581bb84 | 403 | _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; |
c704288c | 404 | _cleanup_(dns_query_freep) DnsQuery *q = query; |
9581bb84 LP |
405 | DnsQuestion *question; |
406 | DnsResourceRecord *rr; | |
407 | int ifindex, r; | |
408 | ||
409 | assert(q); | |
410 | ||
411 | if (q->state != DNS_TRANSACTION_SUCCESS) { | |
412 | r = reply_query_state(q); | |
413 | goto finish; | |
414 | } | |
415 | ||
1db8e6d1 | 416 | r = dns_query_process_cname_many(q); |
9581bb84 LP |
417 | if (r == -ELOOP) { |
418 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); | |
419 | goto finish; | |
420 | } | |
421 | if (r < 0) | |
422 | goto finish; | |
c704288c YW |
423 | if (r == DNS_QUERY_CNAME) { |
424 | /* This was a cname, and the query was restarted. */ | |
425 | TAKE_PTR(q); | |
9581bb84 | 426 | return; |
c704288c | 427 | } |
9581bb84 LP |
428 | |
429 | question = dns_query_question_for_protocol(q, q->answer_protocol); | |
430 | ||
431 | DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { | |
9581bb84 LP |
432 | _cleanup_free_ char *normalized = NULL; |
433 | ||
434 | r = dns_question_matches_rr(question, rr, NULL); | |
435 | if (r < 0) | |
436 | goto finish; | |
437 | if (r == 0) | |
438 | continue; | |
439 | ||
440 | r = dns_name_normalize(rr->ptr.name, 0, &normalized); | |
441 | if (r < 0) | |
442 | goto finish; | |
443 | ||
c91f581c LP |
444 | r = json_variant_append_arrayb( |
445 | &array, | |
446 | JSON_BUILD_OBJECT( | |
447 | JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), | |
448 | JSON_BUILD_PAIR("name", JSON_BUILD_STRING(normalized)))); | |
9581bb84 LP |
449 | if (r < 0) |
450 | goto finish; | |
451 | } | |
452 | ||
453 | if (json_variant_is_blank_object(array)) { | |
454 | r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); | |
455 | goto finish; | |
456 | } | |
457 | ||
458 | r = varlink_replyb(q->varlink_request, | |
459 | JSON_BUILD_OBJECT( | |
460 | JSON_BUILD_PAIR("names", JSON_BUILD_VARIANT(array)), | |
43fc4baa | 461 | JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); |
9581bb84 LP |
462 | finish: |
463 | if (r < 0) { | |
40557509 | 464 | log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to send address reply: %m"); |
9581bb84 LP |
465 | r = varlink_error_errno(q->varlink_request, r); |
466 | } | |
9581bb84 LP |
467 | } |
468 | ||
469 | static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
470 | static const JsonDispatch dispatch_table[] = { | |
471 | { "ifindex", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, | |
472 | { "family", JSON_VARIANT_UNSIGNED, json_dispatch_int, offsetof(LookupParameters, family), JSON_MANDATORY }, | |
473 | { "address", JSON_VARIANT_ARRAY, json_dispatch_address, 0, JSON_MANDATORY }, | |
474 | { "flags", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, | |
475 | {} | |
476 | }; | |
477 | ||
478 | _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; | |
479 | _cleanup_(lookup_parameters_destroy) LookupParameters p = { | |
480 | .family = AF_UNSPEC, | |
481 | }; | |
c704288c | 482 | _cleanup_(dns_query_freep) DnsQuery *q = NULL; |
9807fdc1 | 483 | Manager *m; |
9581bb84 LP |
484 | int r; |
485 | ||
486 | assert(link); | |
9807fdc1 LP |
487 | |
488 | m = varlink_server_get_userdata(varlink_get_server(link)); | |
9581bb84 LP |
489 | assert(m); |
490 | ||
491 | if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) | |
492 | return -EINVAL; | |
493 | ||
f1b622a0 LP |
494 | r = varlink_dispatch(link, parameters, dispatch_table, &p); |
495 | if (r != 0) | |
9581bb84 LP |
496 | return r; |
497 | ||
498 | if (p.ifindex < 0) | |
499 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); | |
500 | ||
0234f0c0 | 501 | if (!IN_SET(p.family, AF_INET, AF_INET6)) |
9581bb84 LP |
502 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); |
503 | ||
504 | if (FAMILY_ADDRESS_SIZE(p.family) != p.address_size) | |
6032283b | 505 | return varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL); |
9581bb84 | 506 | |
3354f500 | 507 | if (!validate_and_mangle_flags(NULL, &p.flags, 0)) |
9581bb84 LP |
508 | return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); |
509 | ||
510 | r = dns_question_new_reverse(&question, p.family, &p.address); | |
511 | if (r < 0) | |
512 | return r; | |
513 | ||
775ae354 | 514 | r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); |
9581bb84 LP |
515 | if (r < 0) |
516 | return r; | |
517 | ||
518 | q->varlink_request = varlink_ref(link); | |
519 | varlink_set_userdata(link, q); | |
520 | ||
521 | q->request_family = p.family; | |
522 | q->request_address = p.address; | |
523 | q->complete = vl_method_resolve_address_complete; | |
524 | ||
525 | r = dns_query_go(q); | |
526 | if (r < 0) | |
c704288c | 527 | return r; |
9581bb84 | 528 | |
c704288c | 529 | TAKE_PTR(q); |
9581bb84 | 530 | return 1; |
9581bb84 LP |
531 | } |
532 | ||
510b3b06 | 533 | static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { |
cb456374 SK |
534 | Manager *m; |
535 | int r; | |
536 | ||
537 | assert(link); | |
538 | ||
0e26016e LB |
539 | m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); |
540 | ||
43bd70d5 | 541 | /* if the client didn't set the more flag, it is using us incorrectly */ |
0e26016e | 542 | if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) |
43bd70d5 | 543 | return varlink_error_invalid_parameter(link, NULL); |
cb456374 SK |
544 | |
545 | if (json_variant_elements(parameters) > 0) | |
546 | return varlink_error_invalid_parameter(link, parameters); | |
547 | ||
72c2d39e LP |
548 | /* Send a ready message to the connecting client, to indicate that we are now listinening, and all |
549 | * queries issued after the point the client sees this will also be reported to the client. */ | |
550 | r = varlink_notifyb(link, | |
551 | JSON_BUILD_OBJECT(JSON_BUILD_PAIR("ready", JSON_BUILD_BOOLEAN(true)))); | |
552 | if (r < 0) | |
553 | return log_error_errno(r, "Failed to report monitor to be established: %m"); | |
554 | ||
cb456374 SK |
555 | r = set_ensure_put(&m->varlink_subscription, NULL, link); |
556 | if (r < 0) | |
557 | return log_error_errno(r, "Failed to add subscription to set: %m"); | |
558 | varlink_ref(link); | |
559 | ||
560 | log_debug("%u clients now attached for varlink notifications", set_size(m->varlink_subscription)); | |
561 | ||
cb456374 SK |
562 | return 1; |
563 | } | |
564 | ||
e0930aa6 LP |
565 | static int vl_method_dump_cache(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { |
566 | _cleanup_(json_variant_unrefp) JsonVariant *list = NULL; | |
567 | Manager *m; | |
568 | int r; | |
569 | ||
570 | assert(link); | |
571 | ||
572 | if (json_variant_elements(parameters) > 0) | |
573 | return varlink_error_invalid_parameter(link, parameters); | |
574 | ||
575 | m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); | |
576 | ||
577 | LIST_FOREACH(scopes, s, m->dns_scopes) { | |
578 | _cleanup_(json_variant_unrefp) JsonVariant *j = NULL; | |
579 | ||
580 | r = dns_scope_dump_cache_to_json(s, &j); | |
581 | if (r < 0) | |
582 | return r; | |
583 | ||
584 | r = json_variant_append_array(&list, j); | |
585 | if (r < 0) | |
586 | return r; | |
587 | } | |
588 | ||
589 | if (!list) { | |
590 | r = json_variant_new_array(&list, NULL, 0); | |
591 | if (r < 0) | |
592 | return r; | |
593 | } | |
594 | ||
595 | return varlink_replyb(link, JSON_BUILD_OBJECT( | |
596 | JSON_BUILD_PAIR("dump", JSON_BUILD_VARIANT(list)))); | |
597 | } | |
598 | ||
bc837621 KV |
599 | static int dns_server_dump_state_to_json_list(DnsServer *server, JsonVariant **list) { |
600 | _cleanup_(json_variant_unrefp) JsonVariant *j = NULL; | |
601 | int r; | |
602 | ||
603 | assert(list); | |
604 | assert(server); | |
605 | ||
606 | r = dns_server_dump_state_to_json(server, &j); | |
607 | if (r < 0) | |
608 | return r; | |
609 | ||
610 | return json_variant_append_array(list, j); | |
611 | } | |
612 | ||
613 | static int vl_method_dump_server_state(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
614 | _cleanup_(json_variant_unrefp) JsonVariant *list = NULL; | |
615 | Manager *m; | |
616 | int r; | |
617 | Link *l; | |
618 | ||
619 | assert(link); | |
620 | ||
621 | if (json_variant_elements(parameters) > 0) | |
622 | return varlink_error_invalid_parameter(link, parameters); | |
623 | ||
624 | m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); | |
625 | ||
626 | LIST_FOREACH(servers, server, m->dns_servers) { | |
627 | r = dns_server_dump_state_to_json_list(server, &list); | |
628 | if (r < 0) | |
629 | return r; | |
630 | } | |
631 | ||
632 | LIST_FOREACH(servers, server, m->fallback_dns_servers) { | |
633 | r = dns_server_dump_state_to_json_list(server, &list); | |
634 | if (r < 0) | |
635 | return r; | |
636 | } | |
637 | ||
638 | HASHMAP_FOREACH(l, m->links) | |
639 | LIST_FOREACH(servers, server, l->dns_servers) { | |
640 | r = dns_server_dump_state_to_json_list(server, &list); | |
641 | if (r < 0) | |
642 | return r; | |
643 | } | |
644 | ||
645 | if (!list) { | |
646 | r = json_variant_new_array(&list, NULL, 0); | |
647 | if (r < 0) | |
648 | return r; | |
649 | } | |
650 | ||
651 | return varlink_replyb(link, JSON_BUILD_OBJECT( | |
652 | JSON_BUILD_PAIR("dump", JSON_BUILD_VARIANT(list)))); | |
653 | } | |
654 | ||
655 | static int vl_method_dump_statistics(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
656 | _cleanup_(json_variant_unrefp) JsonVariant *j = NULL; | |
657 | Manager *m; | |
658 | int r; | |
659 | ||
660 | assert(link); | |
661 | ||
662 | if (json_variant_elements(parameters) > 0) | |
663 | return varlink_error_invalid_parameter(link, parameters); | |
664 | ||
665 | m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); | |
666 | ||
667 | r = dns_manager_dump_statistics_json(m, &j); | |
668 | if (r < 0) | |
669 | return r; | |
670 | ||
a67e5c6e | 671 | return varlink_replyb(link, JSON_BUILD_VARIANT(j)); |
bc837621 KV |
672 | } |
673 | ||
674 | static int vl_method_reset_statistics(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { | |
675 | Manager *m; | |
676 | ||
677 | assert(link); | |
678 | ||
679 | if (json_variant_elements(parameters) > 0) | |
680 | return varlink_error_invalid_parameter(link, parameters); | |
681 | ||
682 | m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); | |
683 | ||
a67e5c6e | 684 | dns_manager_reset_statistics(m); |
bc837621 | 685 | |
a67e5c6e | 686 | return varlink_replyb(link, JSON_BUILD_EMPTY_OBJECT); |
bc837621 KV |
687 | } |
688 | ||
227e1279 | 689 | static int varlink_monitor_server_init(Manager *m) { |
0e26016e LB |
690 | _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL; |
691 | int r; | |
692 | ||
693 | assert(m); | |
694 | ||
b25d819a | 695 | if (m->varlink_monitor_server) |
0e26016e LB |
696 | return 0; |
697 | ||
698 | r = varlink_server_new(&server, VARLINK_SERVER_ROOT_ONLY); | |
699 | if (r < 0) | |
700 | return log_error_errno(r, "Failed to allocate varlink server object: %m"); | |
701 | ||
702 | varlink_server_set_userdata(server, m); | |
703 | ||
abef4a7b LP |
704 | r = varlink_server_add_interface(server, &vl_interface_io_systemd_Resolve_Monitor); |
705 | if (r < 0) | |
706 | return log_error_errno(r, "Failed to add Resolve.Monitor interface to varlink server: %m"); | |
707 | ||
e0930aa6 | 708 | r = varlink_server_bind_method_many( |
0e26016e | 709 | server, |
510b3b06 | 710 | "io.systemd.Resolve.Monitor.SubscribeQueryResults", vl_method_subscribe_query_results, |
bc837621 KV |
711 | "io.systemd.Resolve.Monitor.DumpCache", vl_method_dump_cache, |
712 | "io.systemd.Resolve.Monitor.DumpServerState", vl_method_dump_server_state, | |
713 | "io.systemd.Resolve.Monitor.DumpStatistics", vl_method_dump_statistics, | |
714 | "io.systemd.Resolve.Monitor.ResetStatistics", vl_method_reset_statistics); | |
0e26016e LB |
715 | if (r < 0) |
716 | return log_error_errno(r, "Failed to register varlink methods: %m"); | |
717 | ||
718 | r = varlink_server_bind_disconnect(server, vl_on_notification_disconnect); | |
719 | if (r < 0) | |
720 | return log_error_errno(r, "Failed to register varlink disconnect handler: %m"); | |
721 | ||
1aefb25f | 722 | r = varlink_server_listen_address(server, "/run/systemd/resolve/io.systemd.Resolve.Monitor", 0600); |
0e26016e LB |
723 | if (r < 0) |
724 | return log_error_errno(r, "Failed to bind to varlink socket: %m"); | |
725 | ||
726 | r = varlink_server_attach_event(server, m->event, SD_EVENT_PRIORITY_NORMAL); | |
727 | if (r < 0) | |
728 | return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); | |
729 | ||
227e1279 | 730 | m->varlink_monitor_server = TAKE_PTR(server); |
0e26016e LB |
731 | |
732 | return 0; | |
733 | } | |
734 | ||
b497a958 | 735 | static int varlink_main_server_init(Manager *m) { |
9581bb84 LP |
736 | _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; |
737 | int r; | |
738 | ||
739 | assert(m); | |
740 | ||
741 | if (m->varlink_server) | |
742 | return 0; | |
743 | ||
744 | r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID); | |
745 | if (r < 0) | |
746 | return log_error_errno(r, "Failed to allocate varlink server object: %m"); | |
747 | ||
748 | varlink_server_set_userdata(s, m); | |
749 | ||
abef4a7b LP |
750 | r = varlink_server_add_interface(s, &vl_interface_io_systemd_Resolve); |
751 | if (r < 0) | |
752 | return log_error_errno(r, "Failed to add Resolve interface to varlink server: %m"); | |
753 | ||
9581bb84 LP |
754 | r = varlink_server_bind_method_many( |
755 | s, | |
756 | "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, | |
757 | "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address); | |
758 | if (r < 0) | |
759 | return log_error_errno(r, "Failed to register varlink methods: %m"); | |
760 | ||
761 | r = varlink_server_bind_disconnect(s, vl_on_disconnect); | |
762 | if (r < 0) | |
763 | return log_error_errno(r, "Failed to register varlink disconnect handler: %m"); | |
764 | ||
765 | r = varlink_server_listen_address(s, "/run/systemd/resolve/io.systemd.Resolve", 0666); | |
766 | if (r < 0) | |
767 | return log_error_errno(r, "Failed to bind to varlink socket: %m"); | |
768 | ||
769 | r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); | |
770 | if (r < 0) | |
771 | return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); | |
772 | ||
773 | m->varlink_server = TAKE_PTR(s); | |
b497a958 LP |
774 | return 0; |
775 | } | |
776 | ||
777 | int manager_varlink_init(Manager *m) { | |
778 | int r; | |
779 | ||
780 | r = varlink_main_server_init(m); | |
781 | if (r < 0) | |
782 | return r; | |
cb456374 | 783 | |
227e1279 | 784 | r = varlink_monitor_server_init(m); |
0e26016e LB |
785 | if (r < 0) |
786 | return r; | |
cb456374 | 787 | |
9581bb84 LP |
788 | return 0; |
789 | } | |
790 | ||
791 | void manager_varlink_done(Manager *m) { | |
792 | assert(m); | |
793 | ||
794 | m->varlink_server = varlink_server_unref(m->varlink_server); | |
227e1279 | 795 | m->varlink_monitor_server = varlink_server_unref(m->varlink_monitor_server); |
9581bb84 | 796 | } |