]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve-host/resolve-host.c
068756cab1ea55c61cffa0a777e2de4ae4388093
[thirdparty/systemd.git] / src / resolve-host / resolve-host.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Zbigniew Jędrzejewski-Szmek
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <net/if.h>
23 #include <getopt.h>
24
25 #include "sd-bus.h"
26 #include "bus-util.h"
27 #include "bus-error.h"
28 #include "in-addr-util.h"
29 #include "af-list.h"
30 #include "build.h"
31
32 #include "resolved-dns-packet.h"
33 #include "resolved-def.h"
34
35 #define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
36
37 static int arg_family = AF_UNSPEC;
38 static int arg_ifindex = 0;
39 static int arg_type = 0;
40 static uint16_t arg_class = 0;
41 static bool arg_legend = true;
42 static uint64_t arg_flags = 0;
43
44 static void print_source(int ifindex, uint64_t flags) {
45
46 if (!arg_legend)
47 return;
48
49 if (ifindex <= 0 && flags == 0)
50 return;
51
52 fputs("\n-- Information acquired via", stdout);
53
54 if (flags != 0)
55 printf(" protocol%s%s%s",
56 flags & SD_RESOLVED_DNS ? " DNS" :"",
57 flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "",
58 flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "");
59
60 if (ifindex > 0) {
61 char ifname[IF_NAMESIZE] = "";
62 printf(" interface %s", strna(if_indextoname(ifindex, ifname)));
63 }
64
65 fputc('.', stdout);
66 fputc('\n', stdout);
67 }
68
69 static int resolve_host(sd_bus *bus, const char *name) {
70
71 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
72 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
73 const char *canonical = NULL;
74 unsigned c = 0;
75 int r, ifindex;
76 uint64_t flags;
77
78 assert(name);
79
80 log_debug("Resolving %s (family %s, ifindex %i).", name, af_to_name(arg_family) ?: "*", arg_ifindex);
81
82 r = sd_bus_message_new_method_call(
83 bus,
84 &req,
85 "org.freedesktop.resolve1",
86 "/org/freedesktop/resolve1",
87 "org.freedesktop.resolve1.Manager",
88 "ResolveHostname");
89 if (r < 0)
90 return bus_log_create_error(r);
91
92 r = sd_bus_message_set_auto_start(req, false);
93 if (r < 0)
94 return bus_log_create_error(r);
95
96 r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags);
97 if (r < 0)
98 return bus_log_create_error(r);
99
100 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
101 if (r < 0) {
102 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
103 return r;
104 }
105
106 r = sd_bus_message_read(reply, "i", &ifindex);
107 if (r < 0)
108 return bus_log_parse_error(r);
109
110 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
111 if (r < 0)
112 return bus_log_parse_error(r);
113
114 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
115 const void *a;
116 int family;
117 size_t sz;
118 _cleanup_free_ char *pretty = NULL;
119 char ifname[IF_NAMESIZE] = "";
120
121 r = sd_bus_message_read(reply, "i", &family);
122 if (r < 0)
123 return bus_log_parse_error(r);
124
125 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
126 if (r < 0)
127 return bus_log_parse_error(r);
128
129 r = sd_bus_message_exit_container(reply);
130 if (r < 0)
131 return bus_log_parse_error(r);
132
133 if (!IN_SET(family, AF_INET, AF_INET6)) {
134 log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown");
135 continue;
136 }
137
138 if (sz != FAMILY_ADDRESS_SIZE(family)) {
139 log_error("%s: systemd-resolved returned address of invalid size %zu for family %s",
140 name, sz, af_to_name(family) ?: "unknown");
141 continue;
142 }
143
144 if (ifindex > 0) {
145 char *t;
146
147 t = if_indextoname(ifindex, ifname);
148 if (!t) {
149 log_error("Failed to resolve interface name for index %i", ifindex);
150 continue;
151 }
152 }
153
154 r = in_addr_to_string(family, a, &pretty);
155 if (r < 0) {
156 log_error_errno(r, "%s: failed to print address: %m", name);
157 continue;
158 }
159
160 printf("%*s%s %s%s%s\n",
161 (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
162 pretty,
163 isempty(ifname) ? "" : "%", ifname);
164
165 c++;
166 }
167 if (r < 0)
168 return bus_log_parse_error(r);
169
170 r = sd_bus_message_exit_container(reply);
171 if (r < 0)
172 return bus_log_parse_error(r);
173
174 r = sd_bus_message_read(reply, "st", &canonical, &flags);
175 if (r < 0)
176 return bus_log_parse_error(r);
177
178 if (!streq(name, canonical)) {
179 printf("%*s%s (%s)\n",
180 (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
181 canonical);
182 }
183
184 if (c == 0) {
185 log_error("%s: no addresses found", name);
186 return -ESRCH;
187 }
188
189 print_source(ifindex, flags);
190
191 return 0;
192 }
193
194 static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) {
195 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
196 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
197 _cleanup_free_ char *pretty = NULL;
198 char ifname[IF_NAMESIZE] = "";
199 uint64_t flags;
200 unsigned c = 0;
201 const char *n;
202 int r;
203
204 assert(bus);
205 assert(IN_SET(family, AF_INET, AF_INET6));
206 assert(address);
207
208 r = in_addr_to_string(family, address, &pretty);
209 if (r < 0)
210 return log_oom();
211
212 if (ifindex > 0) {
213 char *t;
214
215 t = if_indextoname(ifindex, ifname);
216 if (!t) {
217 log_error("Failed to resolve interface name for index %i", ifindex);
218 return -errno;
219 }
220 }
221
222 log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname);
223
224 r = sd_bus_message_new_method_call(
225 bus,
226 &req,
227 "org.freedesktop.resolve1",
228 "/org/freedesktop/resolve1",
229 "org.freedesktop.resolve1.Manager",
230 "ResolveAddress");
231 if (r < 0)
232 return bus_log_create_error(r);
233
234 r = sd_bus_message_set_auto_start(req, false);
235 if (r < 0)
236 return bus_log_create_error(r);
237
238 r = sd_bus_message_append(req, "ii", ifindex, family);
239 if (r < 0)
240 return bus_log_create_error(r);
241
242 r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family));
243 if (r < 0)
244 return bus_log_create_error(r);
245
246 r = sd_bus_message_append(req, "t", arg_flags);
247 if (r < 0)
248 return bus_log_create_error(r);
249
250 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
251 if (r < 0) {
252 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
253 return r;
254 }
255
256 r = sd_bus_message_read(reply, "i", &ifindex);
257 if (r < 0)
258 return bus_log_parse_error(r);
259
260 r = sd_bus_message_enter_container(reply, 'a', "s");
261 if (r < 0)
262 return bus_log_create_error(r);
263
264 while ((r = sd_bus_message_read(reply, "s", &n)) > 0) {
265
266 printf("%*s%s%s%s %s\n",
267 (int) strlen(pretty), c == 0 ? pretty : "",
268 isempty(ifname) ? "" : "%", ifname,
269 c == 0 ? ":" : " ",
270 n);
271
272 c++;
273 }
274 if (r < 0)
275 return bus_log_parse_error(r);
276
277 r = sd_bus_message_exit_container(reply);
278 if (r < 0)
279 return bus_log_parse_error(r);
280
281 r = sd_bus_message_read(reply, "t", &flags);
282 if (r < 0)
283 return bus_log_parse_error(r);
284
285 if (c == 0) {
286 log_error("%s: no names found", pretty);
287 return -ESRCH;
288 }
289
290 print_source(ifindex, flags);
291
292 return 0;
293 }
294
295 static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
296 const char *percent, *a;
297 int ifi = 0;
298 int r;
299
300 percent = strchr(s, '%');
301 if (percent) {
302 r = safe_atoi(percent+1, &ifi);
303 if (r < 0 || ifi <= 0) {
304 ifi = if_nametoindex(percent+1);
305 if (ifi <= 0)
306 return -EINVAL;
307 }
308
309 a = strndupa(s, percent - s);
310 } else
311 a = s;
312
313 r = in_addr_from_string_auto(a, family, address);
314 if (r < 0)
315 return r;
316
317 *ifindex = ifi;
318 return 0;
319 }
320
321 static int resolve_record(sd_bus *bus, const char *name) {
322
323 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
324 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
325 unsigned n = 0;
326 uint64_t flags;
327 int r, ifindex;
328
329 assert(name);
330
331 log_debug("Resolving %s %s %s.", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type));
332
333 r = sd_bus_message_new_method_call(
334 bus,
335 &req,
336 "org.freedesktop.resolve1",
337 "/org/freedesktop/resolve1",
338 "org.freedesktop.resolve1.Manager",
339 "ResolveRecord");
340 if (r < 0)
341 return bus_log_create_error(r);
342
343 r = sd_bus_message_set_auto_start(req, false);
344 if (r < 0)
345 return bus_log_create_error(r);
346
347 assert((uint16_t) arg_type == arg_type);
348 r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, arg_class, arg_type, arg_flags);
349 if (r < 0)
350 return bus_log_create_error(r);
351
352 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
353 if (r < 0) {
354 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
355 return r;
356 }
357
358 r = sd_bus_message_read(reply, "i", &ifindex);
359 if (r < 0)
360 return bus_log_parse_error(r);
361
362 r = sd_bus_message_enter_container(reply, 'a', "(qqay)");
363 if (r < 0)
364 return bus_log_parse_error(r);
365
366 while ((r = sd_bus_message_enter_container(reply, 'r', "qqay")) > 0) {
367 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
368 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
369 _cleanup_free_ char *s = NULL;
370 uint16_t c, t;
371 const void *d;
372 size_t l;
373
374 r = sd_bus_message_read(reply, "qq", &c, &t);
375 if (r < 0)
376 return bus_log_parse_error(r);
377
378 r = sd_bus_message_read_array(reply, 'y', &d, &l);
379 if (r < 0)
380 return bus_log_parse_error(r);
381
382 r = sd_bus_message_exit_container(reply);
383 if (r < 0)
384 return bus_log_parse_error(r);
385
386 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
387 if (r < 0)
388 return log_oom();
389
390 r = dns_packet_append_blob(p, d, l, NULL);
391 if (r < 0)
392 return log_oom();
393
394 r = dns_packet_read_rr(p, &rr, NULL);
395 if (r < 0) {
396 log_error("Failed to parse RR.");
397 return r;
398 }
399
400 r = dns_resource_record_to_string(rr, &s);
401 if (r < 0) {
402 log_error("Failed to format RR.");
403 return r;
404 }
405
406 printf("%s\n", s);
407 n++;
408 }
409 if (r < 0)
410 return bus_log_parse_error(r);
411
412 r = sd_bus_message_exit_container(reply);
413 if (r < 0)
414 return bus_log_parse_error(r);
415
416 r = sd_bus_message_read(reply, "t", &flags);
417 if (r < 0)
418 return bus_log_parse_error(r);
419
420 if (n == 0) {
421 log_error("%s: no records found", name);
422 return -ESRCH;
423 }
424
425 print_source(ifindex, flags);
426
427 return 0;
428 }
429
430 static void help_dns_types(void) {
431 int i;
432 const char *t;
433
434 if (arg_legend)
435 puts("Known dns types:");
436 for (i = 0; i < _DNS_TYPE_MAX; i++) {
437 t = dns_type_to_string(i);
438 if (t)
439 puts(t);
440 }
441 }
442
443 static void help_dns_classes(void) {
444 int i;
445 const char *t;
446
447 if (arg_legend)
448 puts("Known dns classes:");
449 for (i = 0; i < _DNS_CLASS_MAX; i++) {
450 t = dns_class_to_string(i);
451 if (t)
452 puts(t);
453 }
454 }
455
456 static void help(void) {
457 printf("%s [OPTIONS...]\n\n"
458 "Resolve IPv4 or IPv6 addresses.\n\n"
459 " -h --help Show this help\n"
460 " --version Show package version\n"
461 " -4 Resolve IPv4 addresses\n"
462 " -6 Resolve IPv6 addresses\n"
463 " -i INTERFACE Look on interface\n"
464 " -p --protocol=PROTOCOL Look via protocol\n"
465 " -t --type=TYPE Query RR with DNS type\n"
466 " -c --class=CLASS Query RR with DNS class\n"
467 " --legend[=BOOL] Do [not] print column headers\n"
468 , program_invocation_short_name);
469 }
470
471 static int parse_argv(int argc, char *argv[]) {
472 enum {
473 ARG_VERSION = 0x100,
474 ARG_LEGEND,
475 };
476
477 static const struct option options[] = {
478 { "help", no_argument, NULL, 'h' },
479 { "version", no_argument, NULL, ARG_VERSION },
480 { "type", required_argument, NULL, 't' },
481 { "class", required_argument, NULL, 'c' },
482 { "legend", optional_argument, NULL, ARG_LEGEND },
483 { "protocol", required_argument, NULL, 'p' },
484 {}
485 };
486
487 int c, r;
488
489 assert(argc >= 0);
490 assert(argv);
491
492 while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0)
493 switch(c) {
494
495 case 'h':
496 help();
497 return 0; /* done */;
498
499 case ARG_VERSION:
500 puts(PACKAGE_STRING);
501 puts(SYSTEMD_FEATURES);
502 return 0 /* done */;
503
504 case '4':
505 arg_family = AF_INET;
506 break;
507
508 case '6':
509 arg_family = AF_INET6;
510 break;
511
512 case 'i':
513 arg_ifindex = if_nametoindex(optarg);
514 if (arg_ifindex <= 0)
515 return log_error_errno(errno, "Unknown interfaces %s: %m", optarg);
516 break;
517
518 case 't':
519 if (streq(optarg, "help")) {
520 help_dns_types();
521 return 0;
522 }
523
524 arg_type = dns_type_from_string(optarg);
525 if (arg_type < 0) {
526 log_error("Failed to parse RR record type %s", optarg);
527 return arg_type;
528 }
529 assert(arg_type > 0 && (uint16_t) arg_type == arg_type);
530
531 break;
532
533 case 'c':
534 if (streq(optarg, "help")) {
535 help_dns_classes();
536 return 0;
537 }
538
539 r = dns_class_from_string(optarg, &arg_class);
540 if (r < 0) {
541 log_error("Failed to parse RR record class %s", optarg);
542 return r;
543 }
544
545 break;
546
547 case ARG_LEGEND:
548 if (optarg) {
549 r = parse_boolean(optarg);
550 if (r < 0) {
551 log_error("Failed to parse --legend= argument");
552 return r;
553 }
554
555 arg_legend = !!r;
556 } else
557 arg_legend = false;
558 break;
559
560 case 'p':
561 if (streq(optarg, "dns"))
562 arg_flags |= SD_RESOLVED_DNS;
563 else if (streq(optarg, "llmnr"))
564 arg_flags |= SD_RESOLVED_LLMNR;
565 else if (streq(optarg, "llmnr-ipv4"))
566 arg_flags |= SD_RESOLVED_LLMNR_IPV4;
567 else if (streq(optarg, "llmnr-ipv6"))
568 arg_flags |= SD_RESOLVED_LLMNR_IPV6;
569 else {
570 log_error("Unknown protocol specifier: %s", optarg);
571 return -EINVAL;
572 }
573
574 break;
575
576 case '?':
577 return -EINVAL;
578
579 default:
580 assert_not_reached("Unhandled option");
581 }
582
583 if (arg_type == 0 && arg_class != 0) {
584 log_error("--class= may only be used in conjunction with --type=");
585 return -EINVAL;
586 }
587
588 if (arg_type != 0 && arg_class == 0)
589 arg_class = DNS_CLASS_IN;
590
591 return 1 /* work to do */;
592 }
593
594 int main(int argc, char **argv) {
595 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
596 int r;
597
598 log_parse_environment();
599 log_open();
600
601 r = parse_argv(argc, argv);
602 if (r <= 0)
603 goto finish;
604
605 if (optind >= argc) {
606 log_error("No arguments passed");
607 r = -EINVAL;
608 goto finish;
609 }
610
611 r = sd_bus_open_system(&bus);
612 if (r < 0) {
613 log_error_errno(r, "sd_bus_open_system: %m");
614 goto finish;
615 }
616
617 while (argv[optind]) {
618 int family, ifindex, k;
619 union in_addr_union a;
620
621 if (arg_type != 0)
622 k = resolve_record(bus, argv[optind]);
623 else {
624 k = parse_address(argv[optind], &family, &a, &ifindex);
625 if (k >= 0)
626 k = resolve_address(bus, family, &a, ifindex);
627 else
628 k = resolve_host(bus, argv[optind]);
629 }
630
631 if (r == 0)
632 r = k;
633
634 optind++;
635 }
636
637 finish:
638 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
639 }