1 From 8c1440a0934dd8b3ad6aae48d6653b5ba6fce8a1 Mon Sep 17 00:00:00 2001
2 From: Jo-Philipp Wich <jo@mein.io>
3 Date: Tue, 14 Mar 2017 22:21:34 +0100
4 Subject: [PATCH] networking: add LEDE nslookup applet
6 Add a new LEDE nslookup applet which is compatible with musl libc
7 and providing more features like ability to specify query type.
9 In contrast to busybox' builtin nslookup applet, this variant does
10 not rely on libc resolver internals but uses explicit send logic
11 and the libresolv primitives to parse received DNS responses.
13 Signed-off-by: Jo-Philipp Wich <jo@mein.io>
16 networking/nslookup_lede.c | 869 +++++++++++++++++++++++++++++++++++++++++++++
17 2 files changed, 875 insertions(+)
18 create mode 100644 networking/nslookup_lede.c
20 diff --git a/Makefile.flags b/Makefile.flags
21 index 65021de..096ab77 100644
24 @@ -134,6 +134,12 @@ else
28 +# nslookup_lede might need the resolv library
29 +RESOLV_AVAILABLE := $(shell echo 'int main(void){res_init();return 0;}' >resolvtest.c; $(CC) $(CFLAGS) -include resolv.h -lresolv -o /dev/null resolvtest.c >/dev/null 2>&1 && echo "y"; rm resolvtest.c)
30 +ifeq ($(RESOLV_AVAILABLE),y)
34 # libpam may use libpthread, libdl and/or libaudit.
35 # On some platforms that requires an explicit -lpthread, -ldl, -laudit.
36 # However, on *other platforms* it fails when some of those flags
37 diff --git a/networking/nslookup_lede.c b/networking/nslookup_lede.c
39 index 0000000..fe927ad
41 +++ b/networking/nslookup_lede.c
44 + * nslookup_lede - musl compatible replacement for busybox nslookup
46 + * Copyright (C) 2017 Jo-Philipp Wich <jo@mein.io>
48 + * Permission to use, copy, modify, and/or distribute this software for any
49 + * purpose with or without fee is hereby granted, provided that the above
50 + * copyright notice and this permission notice appear in all copies.
52 + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
53 + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
54 + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
55 + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
56 + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
57 + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
58 + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
61 +//config:config NSLOOKUP_LEDE
62 +//config: bool "nslookup_lede"
63 +//config: depends on !NSLOOKUP
66 +//config: nslookup is a tool to query Internet name servers (LEDE flavor).
68 +//config:config FEATURE_NSLOOKUP_LEDE_LONG_OPTIONS
69 +//config: bool "Enable long options"
71 +//config: depends on NSLOOKUP_LEDE && LONG_OPTS
73 +//config: Support long options for the nslookup applet.
75 +//applet:IF_NSLOOKUP_LEDE(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP))
77 +//kbuild:lib-$(CONFIG_NSLOOKUP_LEDE) += nslookup_lede.o
79 +//usage:#define nslookup_lede_trivial_usage
80 +//usage: "[HOST] [SERVER]"
81 +//usage:#define nslookup_lede_full_usage "\n\n"
82 +//usage: "Query the nameserver for the IP address of the given HOST\n"
83 +//usage: "optionally using a specified DNS server"
85 +//usage:#define nslookup_lede_example_usage
86 +//usage: "$ nslookup localhost\n"
87 +//usage: "Server: default\n"
88 +//usage: "Address: default\n"
90 +//usage: "Name: debian\n"
91 +//usage: "Address: 127.0.0.1\n"
101 +#include <sys/socket.h>
102 +#include <arpa/inet.h>
110 + len_and_sockaddr addr;
118 + unsigned char query[512], reply[512];
119 + unsigned long latency;
127 + { ns_t_soa, "SOA" },
130 + { ns_t_aaaa, "AAAA" },
131 + { ns_t_cname, "CNAME" },
133 + { ns_t_txt, "TXT" },
134 + { ns_t_ptr, "PTR" },
135 + { ns_t_any, "ANY" },
139 +static const char *rcodes[] = {
159 +static unsigned int default_port = 53;
160 +static unsigned int default_retry = 2;
161 +static unsigned int default_timeout = 5;
164 +static int parse_reply(const unsigned char *msg, size_t len)
169 + const char *format = NULL;
170 + char astr[INET6_ADDRSTRLEN], dname[MAXDNAME];
171 + const unsigned char *cp;
173 + if (ns_initparse(msg, len, &handle) != 0) {
174 + //fprintf(stderr, "Unable to parse reply: %s\n", strerror(errno));
178 + for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) {
179 + if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) {
180 + //fprintf(stderr, "Unable to parse resource record: %s\n", strerror(errno));
184 + rdlen = ns_rr_rdlen(rr);
186 + switch (ns_rr_type(rr))
190 + //fprintf(stderr, "Unexpected A record length\n");
193 + inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr));
194 + printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr);
199 + //fprintf(stderr, "Unexpected AAAA record length\n");
202 + inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr));
203 + printf("%s\thas AAAA address %s\n", ns_rr_name(rr), astr);
208 + format = "%s\tnameserver = %s\n";
213 + format = "%s\tcanonical name = %s\n";
218 + format = "%s\tname = %s\n";
219 + if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
220 + ns_rr_rdata(rr), dname, sizeof(dname)) < 0) {
221 + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno));
224 + printf(format, ns_rr_name(rr), dname);
229 + fprintf(stderr, "MX record too short\n");
232 + n = ns_get16(ns_rr_rdata(rr));
233 + if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
234 + ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0) {
235 + //fprintf(stderr, "Cannot uncompress MX domain: %s\n", strerror(errno));
238 + printf("%s\tmail exchanger = %d %s\n", ns_rr_name(rr), n, dname);
243 + //fprintf(stderr, "TXT record too short\n");
246 + n = *(unsigned char *)ns_rr_rdata(rr);
248 + memset(dname, 0, sizeof(dname));
249 + memcpy(dname, ns_rr_rdata(rr) + 1, n);
250 + printf("%s\ttext = \"%s\"\n", ns_rr_name(rr), dname);
256 + //fprintf(stderr, "SOA record too short\n");
260 + printf("%s\n", ns_rr_name(rr));
262 + cp = ns_rr_rdata(rr);
263 + n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
264 + cp, dname, sizeof(dname));
267 + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno));
271 + printf("\torigin = %s\n", dname);
274 + n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle),
275 + cp, dname, sizeof(dname));
278 + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno));
282 + printf("\tmail addr = %s\n", dname);
285 + printf("\tserial = %lu\n", ns_get32(cp));
288 + printf("\trefresh = %lu\n", ns_get32(cp));
291 + printf("\tretry = %lu\n", ns_get32(cp));
294 + printf("\texpire = %lu\n", ns_get32(cp));
297 + printf("\tminimum = %lu\n", ns_get32(cp));
308 +static int parse_nsaddr(const char *addrstr, len_and_sockaddr *lsa)
310 + char *eptr, *hash, ifname[IFNAMSIZ];
311 + unsigned int port = default_port;
312 + unsigned int scope = 0;
314 + hash = strchr(addrstr, '#');
318 + port = strtoul(hash, &eptr, 10);
320 + if (eptr == hash || *eptr != '\0' || port > 65535) {
326 + hash = strchr(addrstr, '%');
329 + for (eptr = ++hash; *eptr != '\0' && *eptr != '#'; eptr++) {
330 + if ((eptr - hash) >= IFNAMSIZ) {
335 + ifname[eptr - hash] = *eptr;
338 + ifname[eptr - hash] = '\0';
339 + scope = if_nametoindex(ifname);
347 + if (inet_pton(AF_INET6, addrstr, &lsa->u.sin6.sin6_addr)) {
348 + lsa->u.sin6.sin6_family = AF_INET6;
349 + lsa->u.sin6.sin6_port = htons(port);
350 + lsa->u.sin6.sin6_scope_id = scope;
351 + lsa->len = sizeof(lsa->u.sin6);
355 + if (!scope && inet_pton(AF_INET, addrstr, &lsa->u.sin.sin_addr)) {
356 + lsa->u.sin.sin_family = AF_INET;
357 + lsa->u.sin.sin_port = htons(port);
358 + lsa->len = sizeof(lsa->u.sin);
366 +static char *make_ptr(const char *addrstr)
368 + const char *hexdigit = "0123456789abcdef";
369 + static char ptrstr[73];
370 + unsigned char addr[16];
371 + char *ptr = ptrstr;
374 + if (inet_pton(AF_INET6, addrstr, addr)) {
375 + if (memcmp(addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) != 0) {
376 + for (i = 0; i < 16; i++) {
377 + *ptr++ = hexdigit[(unsigned char)addr[15 - i] & 0xf];
379 + *ptr++ = hexdigit[(unsigned char)addr[15 - i] >> 4];
382 + strcpy(ptr, "ip6.arpa");
385 + sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa",
386 + addr[15], addr[14], addr[13], addr[12]);
392 + if (inet_pton(AF_INET, addrstr, addr)) {
393 + sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa",
394 + addr[3], addr[2], addr[1], addr[0]);
401 +static unsigned long mtime(void)
403 + struct timespec ts;
404 + clock_gettime(CLOCK_REALTIME, &ts);
405 + return (unsigned long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
408 +static void to_v4_mapped(len_and_sockaddr *a)
410 + if (a->u.sa.sa_family != AF_INET)
413 + memcpy(a->u.sin6.sin6_addr.s6_addr + 12,
414 + &a->u.sin.sin_addr, 4);
416 + memcpy(a->u.sin6.sin6_addr.s6_addr,
417 + "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
419 + a->u.sin6.sin6_family = AF_INET6;
420 + a->u.sin6.sin6_flowinfo = 0;
421 + a->u.sin6.sin6_scope_id = 0;
422 + a->len = sizeof(a->u.sin6);
427 + * Function logic borrowed & modified from musl libc, res_msend.c
430 +static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_queries)
433 + int timeout = default_timeout * 1000, retry_interval, servfail_retry = 0;
434 + len_and_sockaddr from = { };
439 + unsigned long t0, t1, t2;
440 + int nn, qn, next_query = 0;
442 + from.u.sa.sa_family = AF_INET;
443 + from.len = sizeof(from.u.sin);
445 + for (nn = 0; nn < n_ns; nn++) {
446 + if (ns[nn].addr.u.sa.sa_family == AF_INET6) {
447 + from.u.sa.sa_family = AF_INET6;
448 + from.len = sizeof(from.u.sin6);
453 + /* Get local address and open/bind a socket */
454 + fd = socket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
456 + /* Handle case where system lacks IPv6 support */
457 + if (fd < 0 && from.u.sa.sa_family == AF_INET6 && errno == EAFNOSUPPORT) {
458 + fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
459 + from.u.sa.sa_family = AF_INET;
465 + if (bind(fd, &from.u.sa, from.len) < 0) {
470 + /* Convert any IPv4 addresses in a mixed environment to v4-mapped */
471 + if (from.u.sa.sa_family == AF_INET6) {
472 + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
474 + for (nn = 0; nn < n_ns; nn++)
475 + to_v4_mapped(&ns[nn].addr);
479 + pfd.events = POLLIN;
480 + retry_interval = timeout / default_retry;
482 + t1 = t2 - retry_interval;
484 + for (; t2 - t0 < timeout; t2 = mtime()) {
485 + if (t2 - t1 >= retry_interval) {
486 + for (qn = 0; qn < n_queries; qn++) {
487 + if (queries[qn].rlen)
490 + for (nn = 0; nn < n_ns; nn++) {
491 + sendto(fd, queries[qn].query, queries[qn].qlen,
492 + MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len);
497 + servfail_retry = 2 * n_queries;
500 + /* Wait for a response, or until time to retry */
501 + if (poll(&pfd, 1, t1+retry_interval-t2) <= 0)
505 + recvlen = recvfrom(fd, queries[next_query].reply,
506 + sizeof(queries[next_query].reply), 0,
507 + &from.u.sa, &from.len);
513 + /* Ignore non-identifiable packets */
517 + /* Ignore replies from addresses we didn't send to */
518 + for (nn = 0; nn < n_ns; nn++)
519 + if (memcmp(&from.u.sa, &ns[nn].addr.u.sa, from.len) == 0)
525 + /* Find which query this answer goes with, if any */
526 + for (qn = next_query; qn < n_queries; qn++)
527 + if (!memcmp(queries[next_query].reply, queries[qn].query, 2))
530 + if (qn >= n_queries || queries[qn].rlen)
533 + queries[qn].rcode = queries[next_query].reply[3] & 15;
534 + queries[qn].latency = mtime() - t0;
535 + queries[qn].n_ns = nn;
539 + /* Only accept positive or negative responses;
540 + * retry immediately on server failure, and ignore
541 + * all other codes such as refusal. */
542 + switch (queries[qn].rcode) {
548 + if (servfail_retry && servfail_retry--) {
550 + sendto(fd, queries[qn].query, queries[qn].qlen,
551 + MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len);
562 + queries[qn].rlen = recvlen;
564 + if (qn == next_query) {
565 + while (next_query < n_queries) {
566 + if (!queries[next_query].rlen)
573 + memcpy(queries[qn].reply, queries[next_query].reply, recvlen);
576 + if (next_query >= n_queries)
584 +static struct ns *add_ns(struct ns **ns, int *n_ns, const char *addr)
586 + char portstr[sizeof("65535")], *p;
587 + len_and_sockaddr a = { };
589 + struct addrinfo *ai, *aip, hints = {
590 + .ai_flags = AI_NUMERICSERV,
591 + .ai_socktype = SOCK_DGRAM
594 + if (parse_nsaddr(addr, &a)) {
595 + /* Maybe we got a domain name, attempt to resolve it using the standard
596 + * resolver routines */
598 + p = strchr(addr, '#');
599 + snprintf(portstr, sizeof(portstr), "%hu",
600 + (unsigned short)(p ? strtoul(p, NULL, 10) : default_port));
602 + if (!getaddrinfo(addr, portstr, &hints, &ai)) {
603 + for (aip = ai; aip; aip = aip->ai_next) {
604 + if (aip->ai_addr->sa_family != AF_INET &&
605 + aip->ai_addr->sa_family != AF_INET6)
608 + tmp = realloc(*ns, sizeof(**ns) * (*n_ns + 1));
615 + (*ns)[*n_ns].name = addr;
616 + (*ns)[*n_ns].replies = 0;
617 + (*ns)[*n_ns].failures = 0;
618 + (*ns)[*n_ns].addr.len = aip->ai_addrlen;
620 + memcpy(&(*ns)[*n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen);
627 + return &(*ns)[*n_ns];
633 + tmp = realloc(*ns, sizeof(**ns) * (*n_ns + 1));
640 + (*ns)[*n_ns].addr = a;
641 + (*ns)[*n_ns].name = addr;
642 + (*ns)[*n_ns].replies = 0;
643 + (*ns)[*n_ns].failures = 0;
645 + return &(*ns)[(*n_ns)++];
648 +static int parse_resolvconf(struct ns **ns, int *n_ns)
650 + int prev_n_ns = *n_ns;
651 + char line[128], *p;
654 + if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) {
655 + while (fgets(line, sizeof(line), resolv)) {
656 + p = strtok(line, " \t\n");
658 + if (!p || strcmp(p, "nameserver"))
661 + p = strtok(NULL, " \t\n");
666 + if (!add_ns(ns, n_ns, strdup(p))) {
675 + return *n_ns - prev_n_ns;
678 +static struct query *add_query(struct query **queries, int *n_queries,
679 + int type, const char *dname)
684 + tmp = realloc(*queries, sizeof(**queries) * (*n_queries + 1));
689 + memset(&tmp[*n_queries], 0, sizeof(*tmp));
691 + qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL,
692 + tmp[*n_queries].query, sizeof(tmp[*n_queries].query));
694 + tmp[*n_queries].qlen = qlen;
695 + tmp[*n_queries].name = dname;
698 + return &tmp[(*n_queries)++];
701 +static char *sal2str(len_and_sockaddr *a)
703 + static char buf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1 + 5 + 1];
706 + if (a->u.sa.sa_family == AF_INET6) {
707 + inet_ntop(AF_INET6, &a->u.sin6.sin6_addr, buf, sizeof(buf));
710 + if (a->u.sin6.sin6_scope_id) {
711 + if (if_indextoname(a->u.sin6.sin6_scope_id, p + 1)) {
718 + inet_ntop(AF_INET, &a->u.sin.sin_addr, buf, sizeof(buf));
722 + sprintf(p, "#%hu", ntohs(a->u.sin6.sin6_port));
728 +#if ENABLE_FEATURE_NSLOOKUP_LEDE_LONG_OPTIONS
729 +static const char nslookup_longopts[] ALIGN1 =
730 + "type\0" Required_argument "q"
731 + "querytype\0" Required_argument "q"
732 + "port\0" Required_argument "p"
733 + "retry\0" Required_argument "r"
734 + "timeout\0" Required_argument "t"
735 + "stats\0" Required_argument "s"
739 +int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
740 +int nslookup_main(int argc, char **argv)
744 + struct ns *ns = NULL;
745 + struct query *queries = NULL;
746 + llist_t *type_strings = NULL;
747 + int n_ns = 0, n_queries = 0;
748 + int c, opts, option_index = 0;
750 + unsigned int types = 0;
753 +#if ENABLE_FEATURE_NSLOOKUP_LEDE_LONG_OPTIONS
754 + applet_long_options = nslookup_longopts;
757 + opts = getopt32(argv, "+q:*p:+r:+t:+s",
758 + &type_strings, &default_port,
759 + &default_retry, &default_timeout);
761 + while (type_strings) {
762 + ptr = llist_pop(&type_strings);
764 + /* skip leading text, e.g. when invoked with -querytype=AAAA */
765 + if ((chr = strchr(ptr, '=')) != NULL) {
770 + for (c = 0; qtypes[c].name; c++)
771 + if (!strcmp(qtypes[c].name, ptr))
774 + if (!qtypes[c].name) {
775 + fprintf(stderr, "Invalid query type \"%s\"\n", ptr);
782 + if (default_port > 65535) {
783 + fprintf(stderr, "Invalid server port\n");
787 + if (!default_retry) {
788 + fprintf(stderr, "Invalid retry value\n");
792 + if (!default_timeout) {
793 + fprintf(stderr, "Invalid timeout value\n");
797 + stats = (opts & 16);
799 + if (optind >= argc)
802 + for (option_index = optind;
803 + option_index < ((argc - optind) > 1 ? argc - 1 : argc);
806 + /* No explicit type given, guess query type.
807 + * If we can convert the domain argument into a ptr (means that
808 + * inet_pton() could read it) we assume a PTR request, else
809 + * we issue A queries. */
811 + ptr = make_ptr(argv[option_index]);
814 + add_query(&queries, &n_queries, T_PTR, ptr);
816 + add_query(&queries, &n_queries, T_A, argv[option_index]);
819 + for (c = 0; qtypes[c].name; c++)
820 + if (types & (1 << c))
821 + add_query(&queries, &n_queries, qtypes[c].type,
822 + argv[option_index]);
826 + /* Use given DNS server if present */
827 + if (option_index < argc) {
828 + if (!add_ns(&ns, &n_ns, argv[option_index])) {
829 + fprintf(stderr, "Invalid NS server address \"%s\": %s\n",
830 + argv[option_index], strerror(errno));
835 + parse_resolvconf(&ns, &n_ns);
838 + /* Fall back to localhost if we could not find NS in resolv.conf */
840 + add_ns(&ns, &n_ns, "127.0.0.1");
843 + for (c = 0; c < n_ns; c++) {
844 + rc = send_queries(&ns[c], 1, queries, n_queries);
847 + fprintf(stderr, "Failed to send queries: %s\n", strerror(errno));
849 + } else if (rc > 0) {
856 + ";; connection timed out; no servers could be reached\n\n");
861 + printf("Server:\t\t%s\n", ns[c].name);
862 + printf("Address:\t%s\n", sal2str(&ns[c].addr));
865 + printf("Replies:\t%d\n", ns[c].replies);
866 + printf("Failures:\t%d\n", ns[c].failures);
871 + for (rc = 0; rc < n_queries; rc++) {
873 + printf("Query #%d completed in %lums:\n", rc, queries[rc].latency);
876 + if (queries[rc].rcode != 0) {
877 + printf("** server can't find %s: %s\n", queries[rc].name,
878 + rcodes[queries[rc].rcode]);
884 + if (queries[rc].rlen) {
885 + header = (HEADER *)queries[rc].reply;
888 + printf("Non-authoritative answer:\n");
890 + c = parse_reply(queries[rc].reply, queries[rc].rlen);
894 + printf("*** Can't find %s: No answer\n", queries[rc].name);
896 + printf("*** Can't find %s: Parse error\n", queries[rc].name);