]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve-host/resolve-host.c
Merge pull request #530 from dvdhrm/resolve-host-dbus
[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_append(req, "isit", arg_ifindex, name, arg_family, arg_flags);
93 if (r < 0)
94 return bus_log_create_error(r);
95
96 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
97 if (r < 0) {
98 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
99 return r;
100 }
101
102 r = sd_bus_message_read(reply, "i", &ifindex);
103 if (r < 0)
104 return bus_log_parse_error(r);
105
106 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
107 if (r < 0)
108 return bus_log_parse_error(r);
109
110 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
111 const void *a;
112 int family;
113 size_t sz;
114 _cleanup_free_ char *pretty = NULL;
115 char ifname[IF_NAMESIZE] = "";
116
117 r = sd_bus_message_read(reply, "i", &family);
118 if (r < 0)
119 return bus_log_parse_error(r);
120
121 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
122 if (r < 0)
123 return bus_log_parse_error(r);
124
125 r = sd_bus_message_exit_container(reply);
126 if (r < 0)
127 return bus_log_parse_error(r);
128
129 if (!IN_SET(family, AF_INET, AF_INET6)) {
130 log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown");
131 continue;
132 }
133
134 if (sz != FAMILY_ADDRESS_SIZE(family)) {
135 log_error("%s: systemd-resolved returned address of invalid size %zu for family %s",
136 name, sz, af_to_name(family) ?: "unknown");
137 continue;
138 }
139
140 if (ifindex > 0) {
141 char *t;
142
143 t = if_indextoname(ifindex, ifname);
144 if (!t) {
145 log_error("Failed to resolve interface name for index %i", ifindex);
146 continue;
147 }
148 }
149
150 r = in_addr_to_string(family, a, &pretty);
151 if (r < 0) {
152 log_error_errno(r, "%s: failed to print address: %m", name);
153 continue;
154 }
155
156 printf("%*s%s %s%s%s\n",
157 (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
158 pretty,
159 isempty(ifname) ? "" : "%", ifname);
160
161 c++;
162 }
163 if (r < 0)
164 return bus_log_parse_error(r);
165
166 r = sd_bus_message_exit_container(reply);
167 if (r < 0)
168 return bus_log_parse_error(r);
169
170 r = sd_bus_message_read(reply, "st", &canonical, &flags);
171 if (r < 0)
172 return bus_log_parse_error(r);
173
174 if (!streq(name, canonical)) {
175 printf("%*s%s (%s)\n",
176 (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
177 canonical);
178 }
179
180 if (c == 0) {
181 log_error("%s: no addresses found", name);
182 return -ESRCH;
183 }
184
185 print_source(ifindex, flags);
186
187 return 0;
188 }
189
190 static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) {
191 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
192 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
193 _cleanup_free_ char *pretty = NULL;
194 char ifname[IF_NAMESIZE] = "";
195 uint64_t flags;
196 unsigned c = 0;
197 const char *n;
198 int r;
199
200 assert(bus);
201 assert(IN_SET(family, AF_INET, AF_INET6));
202 assert(address);
203
204 r = in_addr_to_string(family, address, &pretty);
205 if (r < 0)
206 return log_oom();
207
208 if (ifindex > 0) {
209 char *t;
210
211 t = if_indextoname(ifindex, ifname);
212 if (!t) {
213 log_error("Failed to resolve interface name for index %i", ifindex);
214 return -errno;
215 }
216 }
217
218 log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname);
219
220 r = sd_bus_message_new_method_call(
221 bus,
222 &req,
223 "org.freedesktop.resolve1",
224 "/org/freedesktop/resolve1",
225 "org.freedesktop.resolve1.Manager",
226 "ResolveAddress");
227 if (r < 0)
228 return bus_log_create_error(r);
229
230 r = sd_bus_message_set_auto_start(req, false);
231 if (r < 0)
232 return bus_log_create_error(r);
233
234 r = sd_bus_message_append(req, "ii", ifindex, family);
235 if (r < 0)
236 return bus_log_create_error(r);
237
238 r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family));
239 if (r < 0)
240 return bus_log_create_error(r);
241
242 r = sd_bus_message_append(req, "t", arg_flags);
243 if (r < 0)
244 return bus_log_create_error(r);
245
246 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
247 if (r < 0) {
248 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
249 return r;
250 }
251
252 r = sd_bus_message_read(reply, "i", &ifindex);
253 if (r < 0)
254 return bus_log_parse_error(r);
255
256 r = sd_bus_message_enter_container(reply, 'a', "s");
257 if (r < 0)
258 return bus_log_create_error(r);
259
260 while ((r = sd_bus_message_read(reply, "s", &n)) > 0) {
261
262 printf("%*s%s%s%s %s\n",
263 (int) strlen(pretty), c == 0 ? pretty : "",
264 isempty(ifname) ? "" : "%", ifname,
265 c == 0 ? ":" : " ",
266 n);
267
268 c++;
269 }
270 if (r < 0)
271 return bus_log_parse_error(r);
272
273 r = sd_bus_message_exit_container(reply);
274 if (r < 0)
275 return bus_log_parse_error(r);
276
277 r = sd_bus_message_read(reply, "t", &flags);
278 if (r < 0)
279 return bus_log_parse_error(r);
280
281 if (c == 0) {
282 log_error("%s: no names found", pretty);
283 return -ESRCH;
284 }
285
286 print_source(ifindex, flags);
287
288 return 0;
289 }
290
291 static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
292 const char *percent, *a;
293 int ifi = 0;
294 int r;
295
296 percent = strchr(s, '%');
297 if (percent) {
298 r = safe_atoi(percent+1, &ifi);
299 if (r < 0 || ifi <= 0) {
300 ifi = if_nametoindex(percent+1);
301 if (ifi <= 0)
302 return -EINVAL;
303 }
304
305 a = strndupa(s, percent - s);
306 } else
307 a = s;
308
309 r = in_addr_from_string_auto(a, family, address);
310 if (r < 0)
311 return r;
312
313 *ifindex = ifi;
314 return 0;
315 }
316
317 static int resolve_record(sd_bus *bus, const char *name) {
318
319 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
320 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
321 unsigned n = 0;
322 uint64_t flags;
323 int r, ifindex;
324
325 assert(name);
326
327 log_debug("Resolving %s %s %s.", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type));
328
329 r = sd_bus_message_new_method_call(
330 bus,
331 &req,
332 "org.freedesktop.resolve1",
333 "/org/freedesktop/resolve1",
334 "org.freedesktop.resolve1.Manager",
335 "ResolveRecord");
336 if (r < 0)
337 return bus_log_create_error(r);
338
339 r = sd_bus_message_set_auto_start(req, false);
340 if (r < 0)
341 return bus_log_create_error(r);
342
343 assert((uint16_t) arg_type == arg_type);
344 r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, arg_class, arg_type, arg_flags);
345 if (r < 0)
346 return bus_log_create_error(r);
347
348 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
349 if (r < 0) {
350 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
351 return r;
352 }
353
354 r = sd_bus_message_read(reply, "i", &ifindex);
355 if (r < 0)
356 return bus_log_parse_error(r);
357
358 r = sd_bus_message_enter_container(reply, 'a', "(qqay)");
359 if (r < 0)
360 return bus_log_parse_error(r);
361
362 while ((r = sd_bus_message_enter_container(reply, 'r', "qqay")) > 0) {
363 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
364 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
365 _cleanup_free_ char *s = NULL;
366 uint16_t c, t;
367 const void *d;
368 size_t l;
369
370 r = sd_bus_message_read(reply, "qq", &c, &t);
371 if (r < 0)
372 return bus_log_parse_error(r);
373
374 r = sd_bus_message_read_array(reply, 'y', &d, &l);
375 if (r < 0)
376 return bus_log_parse_error(r);
377
378 r = sd_bus_message_exit_container(reply);
379 if (r < 0)
380 return bus_log_parse_error(r);
381
382 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
383 if (r < 0)
384 return log_oom();
385
386 r = dns_packet_append_blob(p, d, l, NULL);
387 if (r < 0)
388 return log_oom();
389
390 r = dns_packet_read_rr(p, &rr, NULL);
391 if (r < 0) {
392 log_error("Failed to parse RR.");
393 return r;
394 }
395
396 r = dns_resource_record_to_string(rr, &s);
397 if (r < 0) {
398 log_error("Failed to format RR.");
399 return r;
400 }
401
402 printf("%s\n", s);
403 n++;
404 }
405 if (r < 0)
406 return bus_log_parse_error(r);
407
408 r = sd_bus_message_exit_container(reply);
409 if (r < 0)
410 return bus_log_parse_error(r);
411
412 r = sd_bus_message_read(reply, "t", &flags);
413 if (r < 0)
414 return bus_log_parse_error(r);
415
416 if (n == 0) {
417 log_error("%s: no records found", name);
418 return -ESRCH;
419 }
420
421 print_source(ifindex, flags);
422
423 return 0;
424 }
425
426 static void help_dns_types(void) {
427 int i;
428 const char *t;
429
430 if (arg_legend)
431 puts("Known dns types:");
432 for (i = 0; i < _DNS_TYPE_MAX; i++) {
433 t = dns_type_to_string(i);
434 if (t)
435 puts(t);
436 }
437 }
438
439 static void help_dns_classes(void) {
440 int i;
441 const char *t;
442
443 if (arg_legend)
444 puts("Known dns classes:");
445 for (i = 0; i < _DNS_CLASS_MAX; i++) {
446 t = dns_class_to_string(i);
447 if (t)
448 puts(t);
449 }
450 }
451
452 static void help(void) {
453 printf("%s [OPTIONS...]\n\n"
454 "Resolve IPv4 or IPv6 addresses.\n\n"
455 " -h --help Show this help\n"
456 " --version Show package version\n"
457 " -4 Resolve IPv4 addresses\n"
458 " -6 Resolve IPv6 addresses\n"
459 " -i INTERFACE Look on interface\n"
460 " -p --protocol=PROTOCOL Look via protocol\n"
461 " -t --type=TYPE Query RR with DNS type\n"
462 " -c --class=CLASS Query RR with DNS class\n"
463 " --legend[=BOOL] Do [not] print column headers\n"
464 , program_invocation_short_name);
465 }
466
467 static int parse_argv(int argc, char *argv[]) {
468 enum {
469 ARG_VERSION = 0x100,
470 ARG_LEGEND,
471 };
472
473 static const struct option options[] = {
474 { "help", no_argument, NULL, 'h' },
475 { "version", no_argument, NULL, ARG_VERSION },
476 { "type", required_argument, NULL, 't' },
477 { "class", required_argument, NULL, 'c' },
478 { "legend", optional_argument, NULL, ARG_LEGEND },
479 { "protocol", required_argument, NULL, 'p' },
480 {}
481 };
482
483 int c, r;
484
485 assert(argc >= 0);
486 assert(argv);
487
488 while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0)
489 switch(c) {
490
491 case 'h':
492 help();
493 return 0; /* done */;
494
495 case ARG_VERSION:
496 puts(PACKAGE_STRING);
497 puts(SYSTEMD_FEATURES);
498 return 0 /* done */;
499
500 case '4':
501 arg_family = AF_INET;
502 break;
503
504 case '6':
505 arg_family = AF_INET6;
506 break;
507
508 case 'i':
509 arg_ifindex = if_nametoindex(optarg);
510 if (arg_ifindex <= 0)
511 return log_error_errno(errno, "Unknown interfaces %s: %m", optarg);
512 break;
513
514 case 't':
515 if (streq(optarg, "help")) {
516 help_dns_types();
517 return 0;
518 }
519
520 arg_type = dns_type_from_string(optarg);
521 if (arg_type < 0) {
522 log_error("Failed to parse RR record type %s", optarg);
523 return arg_type;
524 }
525 assert(arg_type > 0 && (uint16_t) arg_type == arg_type);
526
527 break;
528
529 case 'c':
530 if (streq(optarg, "help")) {
531 help_dns_classes();
532 return 0;
533 }
534
535 r = dns_class_from_string(optarg, &arg_class);
536 if (r < 0) {
537 log_error("Failed to parse RR record class %s", optarg);
538 return r;
539 }
540
541 break;
542
543 case ARG_LEGEND:
544 if (optarg) {
545 r = parse_boolean(optarg);
546 if (r < 0) {
547 log_error("Failed to parse --legend= argument");
548 return r;
549 }
550
551 arg_legend = !!r;
552 } else
553 arg_legend = false;
554 break;
555
556 case 'p':
557 if (streq(optarg, "dns"))
558 arg_flags |= SD_RESOLVED_DNS;
559 else if (streq(optarg, "llmnr"))
560 arg_flags |= SD_RESOLVED_LLMNR;
561 else if (streq(optarg, "llmnr-ipv4"))
562 arg_flags |= SD_RESOLVED_LLMNR_IPV4;
563 else if (streq(optarg, "llmnr-ipv6"))
564 arg_flags |= SD_RESOLVED_LLMNR_IPV6;
565 else {
566 log_error("Unknown protocol specifier: %s", optarg);
567 return -EINVAL;
568 }
569
570 break;
571
572 case '?':
573 return -EINVAL;
574
575 default:
576 assert_not_reached("Unhandled option");
577 }
578
579 if (arg_type == 0 && arg_class != 0) {
580 log_error("--class= may only be used in conjunction with --type=");
581 return -EINVAL;
582 }
583
584 if (arg_type != 0 && arg_class == 0)
585 arg_class = DNS_CLASS_IN;
586
587 return 1 /* work to do */;
588 }
589
590 int main(int argc, char **argv) {
591 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
592 int r;
593
594 log_parse_environment();
595 log_open();
596
597 r = parse_argv(argc, argv);
598 if (r <= 0)
599 goto finish;
600
601 if (optind >= argc) {
602 log_error("No arguments passed");
603 r = -EINVAL;
604 goto finish;
605 }
606
607 r = sd_bus_open_system(&bus);
608 if (r < 0) {
609 log_error_errno(r, "sd_bus_open_system: %m");
610 goto finish;
611 }
612
613 while (argv[optind]) {
614 int family, ifindex, k;
615 union in_addr_union a;
616
617 if (arg_type != 0)
618 k = resolve_record(bus, argv[optind]);
619 else {
620 k = parse_address(argv[optind], &family, &a, &ifindex);
621 if (k >= 0)
622 k = resolve_address(bus, family, &a, ifindex);
623 else
624 k = resolve_host(bus, argv[optind]);
625 }
626
627 if (r == 0)
628 r = k;
629
630 optind++;
631 }
632
633 finish:
634 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
635 }