OpenWrt – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | From ab0f8bb80527928f513297ab93e3ec8c8b48dd50 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 |
||
5 | |||
6 | Add a new LEDE nslookup applet which is compatible with musl libc |
||
7 | and providing more features like ability to specify query type. |
||
8 | |||
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. |
||
12 | |||
13 | Signed-off-by: Jo-Philipp Wich <jo@mein.io> |
||
14 | --- |
||
15 | Makefile.flags | 6 + |
||
16 | networking/nslookup_lede.c | 915 +++++++++++++++++++++++++++++++++++++++++++++ |
||
17 | 2 files changed, 921 insertions(+) |
||
18 | create mode 100644 networking/nslookup_lede.c |
||
19 | |||
20 | --- a/Makefile.flags |
||
21 | +++ b/Makefile.flags |
||
22 | @@ -134,6 +134,12 @@ else |
||
23 | LDLIBS += m |
||
24 | endif |
||
25 | |||
26 | +# nslookup_lede might need the resolv library |
||
27 | +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) |
||
28 | +ifeq ($(RESOLV_AVAILABLE),y) |
||
29 | +LDLIBS += resolv |
||
30 | +endif |
||
31 | + |
||
32 | # libpam may use libpthread, libdl and/or libaudit. |
||
33 | # On some platforms that requires an explicit -lpthread, -ldl, -laudit. |
||
34 | # However, on *other platforms* it fails when some of those flags |
||
35 | --- /dev/null |
||
36 | +++ b/networking/nslookup_lede.c |
||
37 | @@ -0,0 +1,914 @@ |
||
38 | +/* |
||
39 | + * nslookup_lede - musl compatible replacement for busybox nslookup |
||
40 | + * |
||
41 | + * Copyright (C) 2017 Jo-Philipp Wich <jo@mein.io> |
||
42 | + * |
||
43 | + * Permission to use, copy, modify, and/or distribute this software for any |
||
44 | + * purpose with or without fee is hereby granted, provided that the above |
||
45 | + * copyright notice and this permission notice appear in all copies. |
||
46 | + * |
||
47 | + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||
48 | + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||
49 | + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
||
50 | + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||
51 | + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||
52 | + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||
53 | + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||
54 | + */ |
||
55 | + |
||
56 | +//config:config NSLOOKUP_OPENWRT |
||
57 | +//config: bool "nslookup_openwrt" |
||
58 | +//config: depends on !NSLOOKUP |
||
59 | +//config: default y |
||
60 | +//config: help |
||
61 | +//config: nslookup is a tool to query Internet name servers (LEDE flavor). |
||
62 | +//config: |
||
63 | +//config:config FEATURE_NSLOOKUP_OPENWRT_LONG_OPTIONS |
||
64 | +//config: bool "Enable long options" |
||
65 | +//config: default y |
||
66 | +//config: depends on NSLOOKUP_OPENWRT && LONG_OPTS |
||
67 | +//config: help |
||
68 | +//config: Support long options for the nslookup applet. |
||
69 | + |
||
70 | +//applet:IF_NSLOOKUP_OPENWRT(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP)) |
||
71 | + |
||
72 | +//kbuild:lib-$(CONFIG_NSLOOKUP_OPENWRT) += nslookup_lede.o |
||
73 | + |
||
74 | +//usage:#define nslookup_lede_trivial_usage |
||
75 | +//usage: "[HOST] [SERVER]" |
||
76 | +//usage:#define nslookup_lede_full_usage "\n\n" |
||
77 | +//usage: "Query the nameserver for the IP address of the given HOST\n" |
||
78 | +//usage: "optionally using a specified DNS server" |
||
79 | +//usage: |
||
80 | +//usage:#define nslookup_lede_example_usage |
||
81 | +//usage: "$ nslookup localhost\n" |
||
82 | +//usage: "Server: default\n" |
||
83 | +//usage: "Address: default\n" |
||
84 | +//usage: "\n" |
||
85 | +//usage: "Name: debian\n" |
||
86 | +//usage: "Address: 127.0.0.1\n" |
||
87 | + |
||
88 | +#include <stdio.h> |
||
89 | +#include <resolv.h> |
||
90 | +#include <string.h> |
||
91 | +#include <errno.h> |
||
92 | +#include <time.h> |
||
93 | +#include <poll.h> |
||
94 | +#include <unistd.h> |
||
95 | +#include <stdlib.h> |
||
96 | +#include <sys/socket.h> |
||
97 | +#include <arpa/inet.h> |
||
98 | +#include <net/if.h> |
||
99 | +#include <netdb.h> |
||
100 | + |
||
101 | +#include "libbb.h" |
||
102 | + |
||
103 | +struct ns { |
||
104 | + const char *name; |
||
105 | + len_and_sockaddr addr; |
||
106 | + int failures; |
||
107 | + int replies; |
||
108 | +}; |
||
109 | + |
||
110 | +struct query { |
||
111 | + const char *name; |
||
112 | + size_t qlen, rlen; |
||
113 | + unsigned char query[512], reply[512]; |
||
114 | + unsigned long latency; |
||
115 | + int rcode, n_ns; |
||
116 | +}; |
||
117 | + |
||
118 | +static struct { |
||
119 | + int type; |
||
120 | + const char *name; |
||
121 | +} qtypes[] = { |
||
122 | + { ns_t_soa, "SOA" }, |
||
123 | + { ns_t_ns, "NS" }, |
||
124 | + { ns_t_a, "A" }, |
||
125 | +#if ENABLE_FEATURE_IPV6 |
||
126 | + { ns_t_aaaa, "AAAA" }, |
||
127 | +#endif |
||
128 | + { ns_t_cname, "CNAME" }, |
||
129 | + { ns_t_mx, "MX" }, |
||
130 | + { ns_t_txt, "TXT" }, |
||
131 | + { ns_t_ptr, "PTR" }, |
||
132 | + { ns_t_any, "ANY" }, |
||
133 | + { } |
||
134 | +}; |
||
135 | + |
||
136 | +static const char *rcodes[] = { |
||
137 | + "NOERROR", |
||
138 | + "FORMERR", |
||
139 | + "SERVFAIL", |
||
140 | + "NXDOMAIN", |
||
141 | + "NOTIMP", |
||
142 | + "REFUSED", |
||
143 | + "YXDOMAIN", |
||
144 | + "YXRRSET", |
||
145 | + "NXRRSET", |
||
146 | + "NOTAUTH", |
||
147 | + "NOTZONE", |
||
148 | + "RESERVED11", |
||
149 | + "RESERVED12", |
||
150 | + "RESERVED13", |
||
151 | + "RESERVED14", |
||
152 | + "RESERVED15", |
||
153 | + "BADVERS" |
||
154 | +}; |
||
155 | + |
||
156 | +static unsigned int default_port = 53; |
||
157 | +static unsigned int default_retry = 2; |
||
158 | +static unsigned int default_timeout = 5; |
||
159 | + |
||
160 | + |
||
161 | +static int parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) |
||
162 | +{ |
||
163 | + ns_msg handle; |
||
164 | + ns_rr rr; |
||
165 | + int i, n, rdlen; |
||
166 | + const char *format = NULL; |
||
167 | + char astr[INET6_ADDRSTRLEN], dname[MAXDNAME]; |
||
168 | + const unsigned char *cp; |
||
169 | + |
||
170 | + if (ns_initparse(msg, len, &handle) != 0) { |
||
171 | + //fprintf(stderr, "Unable to parse reply: %s\n", strerror(errno)); |
||
172 | + return -1; |
||
173 | + } |
||
174 | + |
||
175 | + for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) { |
||
176 | + if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { |
||
177 | + //fprintf(stderr, "Unable to parse resource record: %s\n", strerror(errno)); |
||
178 | + return -1; |
||
179 | + } |
||
180 | + |
||
181 | + if (bb_style_counter && *bb_style_counter == 1) |
||
182 | + printf("Name: %s\n", ns_rr_name(rr)); |
||
183 | + |
||
184 | + rdlen = ns_rr_rdlen(rr); |
||
185 | + |
||
186 | + switch (ns_rr_type(rr)) |
||
187 | + { |
||
188 | + case ns_t_a: |
||
189 | + if (rdlen != 4) { |
||
190 | + //fprintf(stderr, "Unexpected A record length\n"); |
||
191 | + return -1; |
||
192 | + } |
||
193 | + inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr)); |
||
194 | + if (bb_style_counter) |
||
195 | + printf("Address %d: %s\n", (*bb_style_counter)++, astr); |
||
196 | + else |
||
197 | + printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); |
||
198 | + break; |
||
199 | + |
||
200 | +#if ENABLE_FEATURE_IPV6 |
||
201 | + case ns_t_aaaa: |
||
202 | + if (rdlen != 16) { |
||
203 | + //fprintf(stderr, "Unexpected AAAA record length\n"); |
||
204 | + return -1; |
||
205 | + } |
||
206 | + inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr)); |
||
207 | + if (bb_style_counter) |
||
208 | + printf("Address %d: %s\n", (*bb_style_counter)++, astr); |
||
209 | + else |
||
210 | + printf("%s\thas AAAA address %s\n", ns_rr_name(rr), astr); |
||
211 | + break; |
||
212 | +#endif |
||
213 | + |
||
214 | + case ns_t_ns: |
||
215 | + if (!format) |
||
216 | + format = "%s\tnameserver = %s\n"; |
||
217 | + /* fall through */ |
||
218 | + |
||
219 | + case ns_t_cname: |
||
220 | + if (!format) |
||
221 | + format = "%s\tcanonical name = %s\n"; |
||
222 | + /* fall through */ |
||
223 | + |
||
224 | + case ns_t_ptr: |
||
225 | + if (!format) |
||
226 | + format = "%s\tname = %s\n"; |
||
227 | + if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), |
||
228 | + ns_rr_rdata(rr), dname, sizeof(dname)) < 0) { |
||
229 | + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); |
||
230 | + return -1; |
||
231 | + } |
||
232 | + printf(format, ns_rr_name(rr), dname); |
||
233 | + break; |
||
234 | + |
||
235 | + case ns_t_mx: |
||
236 | + if (rdlen < 2) { |
||
237 | + fprintf(stderr, "MX record too short\n"); |
||
238 | + return -1; |
||
239 | + } |
||
240 | + n = ns_get16(ns_rr_rdata(rr)); |
||
241 | + if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), |
||
242 | + ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0) { |
||
243 | + //fprintf(stderr, "Cannot uncompress MX domain: %s\n", strerror(errno)); |
||
244 | + return -1; |
||
245 | + } |
||
246 | + printf("%s\tmail exchanger = %d %s\n", ns_rr_name(rr), n, dname); |
||
247 | + break; |
||
248 | + |
||
249 | + case ns_t_txt: |
||
250 | + if (rdlen < 1) { |
||
251 | + //fprintf(stderr, "TXT record too short\n"); |
||
252 | + return -1; |
||
253 | + } |
||
254 | + n = *(unsigned char *)ns_rr_rdata(rr); |
||
255 | + if (n > 0) { |
||
256 | + memset(dname, 0, sizeof(dname)); |
||
257 | + memcpy(dname, ns_rr_rdata(rr) + 1, n); |
||
258 | + printf("%s\ttext = \"%s\"\n", ns_rr_name(rr), dname); |
||
259 | + } |
||
260 | + break; |
||
261 | + |
||
262 | + case ns_t_soa: |
||
263 | + if (rdlen < 20) { |
||
264 | + //fprintf(stderr, "SOA record too short\n"); |
||
265 | + return -1; |
||
266 | + } |
||
267 | + |
||
268 | + printf("%s\n", ns_rr_name(rr)); |
||
269 | + |
||
270 | + cp = ns_rr_rdata(rr); |
||
271 | + n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), |
||
272 | + cp, dname, sizeof(dname)); |
||
273 | + |
||
274 | + if (n < 0) { |
||
275 | + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); |
||
276 | + return -1; |
||
277 | + } |
||
278 | + |
||
279 | + printf("\torigin = %s\n", dname); |
||
280 | + cp += n; |
||
281 | + |
||
282 | + n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), |
||
283 | + cp, dname, sizeof(dname)); |
||
284 | + |
||
285 | + if (n < 0) { |
||
286 | + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); |
||
287 | + return -1; |
||
288 | + } |
||
289 | + |
||
290 | + printf("\tmail addr = %s\n", dname); |
||
291 | + cp += n; |
||
292 | + |
||
293 | + printf("\tserial = %lu\n", ns_get32(cp)); |
||
294 | + cp += 4; |
||
295 | + |
||
296 | + printf("\trefresh = %lu\n", ns_get32(cp)); |
||
297 | + cp += 4; |
||
298 | + |
||
299 | + printf("\tretry = %lu\n", ns_get32(cp)); |
||
300 | + cp += 4; |
||
301 | + |
||
302 | + printf("\texpire = %lu\n", ns_get32(cp)); |
||
303 | + cp += 4; |
||
304 | + |
||
305 | + printf("\tminimum = %lu\n", ns_get32(cp)); |
||
306 | + break; |
||
307 | + |
||
308 | + default: |
||
309 | + break; |
||
310 | + } |
||
311 | + } |
||
312 | + |
||
313 | + return i; |
||
314 | +} |
||
315 | + |
||
316 | +static int parse_nsaddr(const char *addrstr, len_and_sockaddr *lsa) |
||
317 | +{ |
||
318 | + char *eptr, *hash, ifname[IFNAMSIZ]; |
||
319 | + unsigned int port = default_port; |
||
320 | + unsigned int scope = 0; |
||
321 | + |
||
322 | + hash = strchr(addrstr, '#'); |
||
323 | + |
||
324 | + if (hash) { |
||
325 | + *hash++ = '\0'; |
||
326 | + port = strtoul(hash, &eptr, 10); |
||
327 | + |
||
328 | + if (eptr == hash || *eptr != '\0' || port > 65535) { |
||
329 | + errno = EINVAL; |
||
330 | + return -1; |
||
331 | + } |
||
332 | + } |
||
333 | + |
||
334 | + hash = strchr(addrstr, '%'); |
||
335 | + |
||
336 | + if (hash) { |
||
337 | + for (eptr = ++hash; *eptr != '\0' && *eptr != '#'; eptr++) { |
||
338 | + if ((eptr - hash) >= IFNAMSIZ) { |
||
339 | + errno = ENODEV; |
||
340 | + return -1; |
||
341 | + } |
||
342 | + |
||
343 | + ifname[eptr - hash] = *eptr; |
||
344 | + } |
||
345 | + |
||
346 | + ifname[eptr - hash] = '\0'; |
||
347 | + scope = if_nametoindex(ifname); |
||
348 | + |
||
349 | + if (scope == 0) { |
||
350 | + errno = ENODEV; |
||
351 | + return -1; |
||
352 | + } |
||
353 | + } |
||
354 | + |
||
355 | +#if ENABLE_FEATURE_IPV6 |
||
356 | + if (inet_pton(AF_INET6, addrstr, &lsa->u.sin6.sin6_addr)) { |
||
357 | + lsa->u.sin6.sin6_family = AF_INET6; |
||
358 | + lsa->u.sin6.sin6_port = htons(port); |
||
359 | + lsa->u.sin6.sin6_scope_id = scope; |
||
360 | + lsa->len = sizeof(lsa->u.sin6); |
||
361 | + return 0; |
||
362 | + } |
||
363 | +#endif |
||
364 | + |
||
365 | + if (!scope && inet_pton(AF_INET, addrstr, &lsa->u.sin.sin_addr)) { |
||
366 | + lsa->u.sin.sin_family = AF_INET; |
||
367 | + lsa->u.sin.sin_port = htons(port); |
||
368 | + lsa->len = sizeof(lsa->u.sin); |
||
369 | + return 0; |
||
370 | + } |
||
371 | + |
||
372 | + errno = EINVAL; |
||
373 | + return -1; |
||
374 | +} |
||
375 | + |
||
376 | +static char *make_ptr(const char *addrstr) |
||
377 | +{ |
||
378 | + const char *hexdigit = "0123456789abcdef"; |
||
379 | + static char ptrstr[73]; |
||
380 | + unsigned char addr[16]; |
||
381 | + char *ptr = ptrstr; |
||
382 | + int i; |
||
383 | + |
||
384 | + if (inet_pton(AF_INET6, addrstr, addr)) { |
||
385 | + if (memcmp(addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) != 0) { |
||
386 | + for (i = 0; i < 16; i++) { |
||
387 | + *ptr++ = hexdigit[(unsigned char)addr[15 - i] & 0xf]; |
||
388 | + *ptr++ = '.'; |
||
389 | + *ptr++ = hexdigit[(unsigned char)addr[15 - i] >> 4]; |
||
390 | + *ptr++ = '.'; |
||
391 | + } |
||
392 | + strcpy(ptr, "ip6.arpa"); |
||
393 | + } |
||
394 | + else { |
||
395 | + sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", |
||
396 | + addr[15], addr[14], addr[13], addr[12]); |
||
397 | + } |
||
398 | + |
||
399 | + return ptrstr; |
||
400 | + } |
||
401 | + |
||
402 | + if (inet_pton(AF_INET, addrstr, addr)) { |
||
403 | + sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", |
||
404 | + addr[3], addr[2], addr[1], addr[0]); |
||
405 | + return ptrstr; |
||
406 | + } |
||
407 | + |
||
408 | + return NULL; |
||
409 | +} |
||
410 | + |
||
411 | +static unsigned long mtime(void) |
||
412 | +{ |
||
413 | + struct timespec ts; |
||
414 | + clock_gettime(CLOCK_REALTIME, &ts); |
||
415 | + return (unsigned long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; |
||
416 | +} |
||
417 | + |
||
418 | +#if ENABLE_FEATURE_IPV6 |
||
419 | +static void to_v4_mapped(len_and_sockaddr *a) |
||
420 | +{ |
||
421 | + if (a->u.sa.sa_family != AF_INET) |
||
422 | + return; |
||
423 | + |
||
424 | + memcpy(a->u.sin6.sin6_addr.s6_addr + 12, |
||
425 | + &a->u.sin.sin_addr, 4); |
||
426 | + |
||
427 | + memcpy(a->u.sin6.sin6_addr.s6_addr, |
||
428 | + "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); |
||
429 | + |
||
430 | + a->u.sin6.sin6_family = AF_INET6; |
||
431 | + a->u.sin6.sin6_flowinfo = 0; |
||
432 | + a->u.sin6.sin6_scope_id = 0; |
||
433 | + a->len = sizeof(a->u.sin6); |
||
434 | +} |
||
435 | +#endif |
||
436 | + |
||
437 | + |
||
438 | +/* |
||
439 | + * Function logic borrowed & modified from musl libc, res_msend.c |
||
440 | + */ |
||
441 | + |
||
442 | +static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_queries) |
||
443 | +{ |
||
444 | + int fd; |
||
445 | + int timeout = default_timeout * 1000, retry_interval, servfail_retry = 0; |
||
446 | + len_and_sockaddr from = { }; |
||
447 | +#if ENABLE_FEATURE_IPV6 |
||
448 | + int one = 1; |
||
449 | +#endif |
||
450 | + int recvlen = 0; |
||
451 | + int n_replies = 0; |
||
452 | + struct pollfd pfd; |
||
453 | + unsigned long t0, t1, t2; |
||
454 | + int nn, qn, next_query = 0; |
||
455 | + |
||
456 | + from.u.sa.sa_family = AF_INET; |
||
457 | + from.len = sizeof(from.u.sin); |
||
458 | + |
||
459 | +#if ENABLE_FEATURE_IPV6 |
||
460 | + for (nn = 0; nn < n_ns; nn++) { |
||
461 | + if (ns[nn].addr.u.sa.sa_family == AF_INET6) { |
||
462 | + from.u.sa.sa_family = AF_INET6; |
||
463 | + from.len = sizeof(from.u.sin6); |
||
464 | + break; |
||
465 | + } |
||
466 | + } |
||
467 | +#endif |
||
468 | + |
||
469 | + /* Get local address and open/bind a socket */ |
||
470 | + fd = socket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); |
||
471 | + |
||
472 | +#if ENABLE_FEATURE_IPV6 |
||
473 | + /* Handle case where system lacks IPv6 support */ |
||
474 | + if (fd < 0 && from.u.sa.sa_family == AF_INET6 && errno == EAFNOSUPPORT) { |
||
475 | + fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); |
||
476 | + from.u.sa.sa_family = AF_INET; |
||
477 | + } |
||
478 | +#endif |
||
479 | + |
||
480 | + if (fd < 0) |
||
481 | + return -1; |
||
482 | + |
||
483 | + if (bind(fd, &from.u.sa, from.len) < 0) { |
||
484 | + close(fd); |
||
485 | + return -1; |
||
486 | + } |
||
487 | + |
||
488 | +#if ENABLE_FEATURE_IPV6 |
||
489 | + /* Convert any IPv4 addresses in a mixed environment to v4-mapped */ |
||
490 | + if (from.u.sa.sa_family == AF_INET6) { |
||
491 | + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); |
||
492 | + |
||
493 | + for (nn = 0; nn < n_ns; nn++) |
||
494 | + to_v4_mapped(&ns[nn].addr); |
||
495 | + } |
||
496 | +#endif |
||
497 | + |
||
498 | + pfd.fd = fd; |
||
499 | + pfd.events = POLLIN; |
||
500 | + retry_interval = timeout / default_retry; |
||
501 | + t0 = t2 = mtime(); |
||
502 | + t1 = t2 - retry_interval; |
||
503 | + |
||
504 | + for (; t2 - t0 < timeout; t2 = mtime()) { |
||
505 | + if (t2 - t1 >= retry_interval) { |
||
506 | + for (qn = 0; qn < n_queries; qn++) { |
||
507 | + if (queries[qn].rlen) |
||
508 | + continue; |
||
509 | + |
||
510 | + for (nn = 0; nn < n_ns; nn++) { |
||
511 | + sendto(fd, queries[qn].query, queries[qn].qlen, |
||
512 | + MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len); |
||
513 | + } |
||
514 | + } |
||
515 | + |
||
516 | + t1 = t2; |
||
517 | + servfail_retry = 2 * n_queries; |
||
518 | + } |
||
519 | + |
||
520 | + /* Wait for a response, or until time to retry */ |
||
521 | + if (poll(&pfd, 1, t1+retry_interval-t2) <= 0) |
||
522 | + continue; |
||
523 | + |
||
524 | + while (1) { |
||
525 | + recvlen = recvfrom(fd, queries[next_query].reply, |
||
526 | + sizeof(queries[next_query].reply), 0, |
||
527 | + &from.u.sa, &from.len); |
||
528 | + |
||
529 | + /* read error */ |
||
530 | + if (recvlen < 0) |
||
531 | + break; |
||
532 | + |
||
533 | + /* Ignore non-identifiable packets */ |
||
534 | + if (recvlen < 4) |
||
535 | + continue; |
||
536 | + |
||
537 | + /* Ignore replies from addresses we didn't send to */ |
||
538 | + for (nn = 0; nn < n_ns; nn++) |
||
539 | + if (memcmp(&from.u.sa, &ns[nn].addr.u.sa, from.len) == 0) |
||
540 | + break; |
||
541 | + |
||
542 | + if (nn >= n_ns) |
||
543 | + continue; |
||
544 | + |
||
545 | + /* Find which query this answer goes with, if any */ |
||
546 | + for (qn = next_query; qn < n_queries; qn++) |
||
547 | + if (!memcmp(queries[next_query].reply, queries[qn].query, 2)) |
||
548 | + break; |
||
549 | + |
||
550 | + if (qn >= n_queries || queries[qn].rlen) |
||
551 | + continue; |
||
552 | + |
||
553 | + queries[qn].rcode = queries[next_query].reply[3] & 15; |
||
554 | + queries[qn].latency = mtime() - t0; |
||
555 | + queries[qn].n_ns = nn; |
||
556 | + |
||
557 | + ns[nn].replies++; |
||
558 | + |
||
559 | + /* Only accept positive or negative responses; |
||
560 | + * retry immediately on server failure, and ignore |
||
561 | + * all other codes such as refusal. */ |
||
562 | + switch (queries[qn].rcode) { |
||
563 | + case 0: |
||
564 | + case 3: |
||
565 | + break; |
||
566 | + |
||
567 | + case 2: |
||
568 | + if (servfail_retry && servfail_retry--) { |
||
569 | + ns[nn].failures++; |
||
570 | + sendto(fd, queries[qn].query, queries[qn].qlen, |
||
571 | + MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len); |
||
572 | + } |
||
573 | + /* fall through */ |
||
574 | + |
||
575 | + default: |
||
576 | + continue; |
||
577 | + } |
||
578 | + |
||
579 | + /* Store answer */ |
||
580 | + n_replies++; |
||
581 | + |
||
582 | + queries[qn].rlen = recvlen; |
||
583 | + |
||
584 | + if (qn == next_query) { |
||
585 | + while (next_query < n_queries) { |
||
586 | + if (!queries[next_query].rlen) |
||
587 | + break; |
||
588 | + |
||
589 | + next_query++; |
||
590 | + } |
||
591 | + } |
||
592 | + else { |
||
593 | + memcpy(queries[qn].reply, queries[next_query].reply, recvlen); |
||
594 | + } |
||
595 | + |
||
596 | + if (next_query >= n_queries) |
||
597 | + return n_replies; |
||
598 | + } |
||
599 | + } |
||
600 | + |
||
601 | + return n_replies; |
||
602 | +} |
||
603 | + |
||
604 | +static struct ns *add_ns(struct ns **ns, int *n_ns, const char *addr) |
||
605 | +{ |
||
606 | + char portstr[sizeof("65535")], *p; |
||
607 | + len_and_sockaddr a = { }; |
||
608 | + struct ns *tmp; |
||
609 | + struct addrinfo *ai, *aip, hints = { |
||
610 | + .ai_flags = AI_NUMERICSERV, |
||
611 | + .ai_socktype = SOCK_DGRAM |
||
612 | + }; |
||
613 | + |
||
614 | + if (parse_nsaddr(addr, &a)) { |
||
615 | + /* Maybe we got a domain name, attempt to resolve it using the standard |
||
616 | + * resolver routines */ |
||
617 | + |
||
618 | + p = strchr(addr, '#'); |
||
619 | + snprintf(portstr, sizeof(portstr), "%hu", |
||
620 | + (unsigned short)(p ? strtoul(p, NULL, 10) : default_port)); |
||
621 | + |
||
622 | + if (!getaddrinfo(addr, portstr, &hints, &ai)) { |
||
623 | + for (aip = ai; aip; aip = aip->ai_next) { |
||
624 | + if (aip->ai_addr->sa_family != AF_INET && |
||
625 | + aip->ai_addr->sa_family != AF_INET6) |
||
626 | + continue; |
||
627 | + |
||
628 | +#if ! ENABLE_FEATURE_IPV6 |
||
629 | + if (aip->ai_addr->sa_family != AF_INET) |
||
630 | + continue; |
||
631 | +#endif |
||
632 | + |
||
633 | + tmp = realloc(*ns, sizeof(**ns) * (*n_ns + 1)); |
||
634 | + |
||
635 | + if (!tmp) |
||
636 | + return NULL; |
||
637 | + |
||
638 | + *ns = tmp; |
||
639 | + |
||
640 | + (*ns)[*n_ns].name = addr; |
||
641 | + (*ns)[*n_ns].replies = 0; |
||
642 | + (*ns)[*n_ns].failures = 0; |
||
643 | + (*ns)[*n_ns].addr.len = aip->ai_addrlen; |
||
644 | + |
||
645 | + memcpy(&(*ns)[*n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen); |
||
646 | + |
||
647 | + (*n_ns)++; |
||
648 | + } |
||
649 | + |
||
650 | + freeaddrinfo(ai); |
||
651 | + |
||
652 | + return &(*ns)[*n_ns]; |
||
653 | + } |
||
654 | + |
||
655 | + return NULL; |
||
656 | + } |
||
657 | + |
||
658 | + tmp = realloc(*ns, sizeof(**ns) * (*n_ns + 1)); |
||
659 | + |
||
660 | + if (!tmp) |
||
661 | + return NULL; |
||
662 | + |
||
663 | + *ns = tmp; |
||
664 | + |
||
665 | + (*ns)[*n_ns].addr = a; |
||
666 | + (*ns)[*n_ns].name = addr; |
||
667 | + (*ns)[*n_ns].replies = 0; |
||
668 | + (*ns)[*n_ns].failures = 0; |
||
669 | + |
||
670 | + return &(*ns)[(*n_ns)++]; |
||
671 | +} |
||
672 | + |
||
673 | +static int parse_resolvconf(struct ns **ns, int *n_ns) |
||
674 | +{ |
||
675 | + int prev_n_ns = *n_ns; |
||
676 | + char line[128], *p; |
||
677 | + FILE *resolv; |
||
678 | + |
||
679 | + if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) { |
||
680 | + while (fgets(line, sizeof(line), resolv)) { |
||
681 | + p = strtok(line, " \t\n"); |
||
682 | + |
||
683 | + if (!p || strcmp(p, "nameserver")) |
||
684 | + continue; |
||
685 | + |
||
686 | + p = strtok(NULL, " \t\n"); |
||
687 | + |
||
688 | + if (!p) |
||
689 | + continue; |
||
690 | + |
||
691 | + if (!add_ns(ns, n_ns, strdup(p))) { |
||
692 | + free(p); |
||
693 | + break; |
||
694 | + } |
||
695 | + } |
||
696 | + |
||
697 | + fclose(resolv); |
||
698 | + } |
||
699 | + |
||
700 | + return *n_ns - prev_n_ns; |
||
701 | +} |
||
702 | + |
||
703 | +static struct query *add_query(struct query **queries, int *n_queries, |
||
704 | + int type, const char *dname) |
||
705 | +{ |
||
706 | + struct query *tmp; |
||
707 | + ssize_t qlen; |
||
708 | + |
||
709 | + tmp = realloc(*queries, sizeof(**queries) * (*n_queries + 1)); |
||
710 | + |
||
711 | + if (!tmp) |
||
712 | + return NULL; |
||
713 | + |
||
714 | + memset(&tmp[*n_queries], 0, sizeof(*tmp)); |
||
715 | + |
||
716 | + qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, |
||
717 | + tmp[*n_queries].query, sizeof(tmp[*n_queries].query)); |
||
718 | + |
||
719 | + tmp[*n_queries].qlen = qlen; |
||
720 | + tmp[*n_queries].name = dname; |
||
721 | + *queries = tmp; |
||
722 | + |
||
723 | + return &tmp[(*n_queries)++]; |
||
724 | +} |
||
725 | + |
||
726 | +static char *sal2str(len_and_sockaddr *a) |
||
727 | +{ |
||
728 | + static char buf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1 + 5 + 1]; |
||
729 | + char *p = buf; |
||
730 | + |
||
731 | +#if ENABLE_FEATURE_IPV6 |
||
732 | + if (a->u.sa.sa_family == AF_INET6) { |
||
733 | + inet_ntop(AF_INET6, &a->u.sin6.sin6_addr, buf, sizeof(buf)); |
||
734 | + p += strlen(p); |
||
735 | + |
||
736 | + if (a->u.sin6.sin6_scope_id) { |
||
737 | + if (if_indextoname(a->u.sin6.sin6_scope_id, p + 1)) { |
||
738 | + *p++ = '%'; |
||
739 | + p += strlen(p); |
||
740 | + } |
||
741 | + } |
||
742 | + } |
||
743 | + else |
||
744 | +#endif |
||
745 | + { |
||
746 | + inet_ntop(AF_INET, &a->u.sin.sin_addr, buf, sizeof(buf)); |
||
747 | + p += strlen(p); |
||
748 | + } |
||
749 | + |
||
750 | + sprintf(p, "#%hu", ntohs(a->u.sin.sin_port)); |
||
751 | + |
||
752 | + return buf; |
||
753 | +} |
||
754 | + |
||
755 | +int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
||
756 | +int nslookup_main(int argc, char **argv) |
||
757 | +{ |
||
758 | + int rc = 1; |
||
759 | + char *ptr, *chr; |
||
760 | + struct ns *ns = NULL; |
||
761 | + struct query *queries = NULL; |
||
762 | + llist_t *type_strings = NULL; |
||
763 | + int n_ns = 0, n_queries = 0; |
||
764 | + int c, opts, option_index = 0; |
||
765 | + int stats = 0, bb_style_counter = 0; |
||
766 | + unsigned int types = 0; |
||
767 | + HEADER *header; |
||
768 | + |
||
769 | +#if ENABLE_FEATURE_NSLOOKUP_OPENWRT_LONG_OPTIONS |
||
770 | + static const char nslookup_longopts[] ALIGN1 = |
||
771 | + "type\0" Required_argument "q" |
||
772 | + "querytype\0" Required_argument "q" |
||
773 | + "port\0" Required_argument "p" |
||
774 | + "retry\0" Required_argument "r" |
||
775 | + "timeout\0" Required_argument "t" |
||
776 | + "stats\0" No_argument "s" |
||
777 | + ; |
||
778 | + |
||
779 | + opts = getopt32long(argv, "^" "+q:*p:+r:+t:+s" "\0" "q::", |
||
780 | + nslookup_longopts, |
||
781 | + &type_strings, &default_port, |
||
782 | + &default_retry, &default_timeout); |
||
783 | +#else |
||
784 | + opts = getopt32(argv, "^" "+q:*p:+r:+t:+s" "\0" "q::", |
||
785 | + &type_strings, &default_port, |
||
786 | + &default_retry, &default_timeout); |
||
787 | +#endif |
||
788 | + |
||
789 | + while (type_strings) { |
||
790 | + ptr = llist_pop(&type_strings); |
||
791 | + |
||
792 | + /* skip leading text, e.g. when invoked with -querytype=AAAA */ |
||
793 | + if ((chr = strchr(ptr, '=')) != NULL) |
||
794 | + ptr = chr + 1; |
||
795 | + |
||
796 | + for (c = 0; qtypes[c].name; c++) |
||
797 | + if (!strcmp(qtypes[c].name, ptr)) |
||
798 | + break; |
||
799 | + |
||
800 | + if (!qtypes[c].name) { |
||
801 | + fprintf(stderr, "Invalid query type \"%s\"\n", ptr); |
||
802 | + goto out; |
||
803 | + } |
||
804 | + |
||
805 | + types |= (1 << c); |
||
806 | + } |
||
807 | + |
||
808 | + if (default_port > 65535) { |
||
809 | + fprintf(stderr, "Invalid server port\n"); |
||
810 | + goto out; |
||
811 | + } |
||
812 | + |
||
813 | + if (!default_retry) { |
||
814 | + fprintf(stderr, "Invalid retry value\n"); |
||
815 | + goto out; |
||
816 | + } |
||
817 | + |
||
818 | + if (!default_timeout) { |
||
819 | + fprintf(stderr, "Invalid timeout value\n"); |
||
820 | + goto out; |
||
821 | + } |
||
822 | + |
||
823 | + stats = (opts & 16); |
||
824 | + |
||
825 | + if (optind >= argc) |
||
826 | + bb_show_usage(); |
||
827 | + |
||
828 | + for (option_index = optind; |
||
829 | + option_index < ((argc - optind) > 1 ? argc - 1 : argc); |
||
830 | + option_index++) { |
||
831 | + |
||
832 | + /* No explicit type given, guess query type. |
||
833 | + * If we can convert the domain argument into a ptr (means that |
||
834 | + * inet_pton() could read it) we assume a PTR request, else |
||
835 | + * we issue A+AAAA queries and switch to an output format |
||
836 | + * mimicking the one of the traditional nslookup applet. */ |
||
837 | + if (types == 0) { |
||
838 | + ptr = make_ptr(argv[option_index]); |
||
839 | + |
||
840 | + if (ptr) { |
||
841 | + add_query(&queries, &n_queries, T_PTR, ptr); |
||
842 | + } |
||
843 | + else { |
||
844 | + bb_style_counter = 1; |
||
845 | + add_query(&queries, &n_queries, T_A, argv[option_index]); |
||
846 | +#if ENABLE_FEATURE_IPV6 |
||
847 | + add_query(&queries, &n_queries, T_AAAA, argv[option_index]); |
||
848 | +#endif |
||
849 | + } |
||
850 | + } |
||
851 | + else { |
||
852 | + for (c = 0; qtypes[c].name; c++) |
||
853 | + if (types & (1 << c)) |
||
854 | + add_query(&queries, &n_queries, qtypes[c].type, |
||
855 | + argv[option_index]); |
||
856 | + } |
||
857 | + } |
||
858 | + |
||
859 | + /* Use given DNS server if present */ |
||
860 | + if (option_index < argc) { |
||
861 | + if (!add_ns(&ns, &n_ns, argv[option_index])) { |
||
862 | + fprintf(stderr, "Invalid NS server address \"%s\": %s\n", |
||
863 | + argv[option_index], strerror(errno)); |
||
864 | + goto out; |
||
865 | + } |
||
866 | + } |
||
867 | + else { |
||
868 | + parse_resolvconf(&ns, &n_ns); |
||
869 | + } |
||
870 | + |
||
871 | + /* Fall back to localhost if we could not find NS in resolv.conf */ |
||
872 | + if (n_ns == 0) { |
||
873 | + add_ns(&ns, &n_ns, "127.0.0.1"); |
||
874 | + } |
||
875 | + |
||
876 | + for (c = 0; c < n_ns; c++) { |
||
877 | + rc = send_queries(&ns[c], 1, queries, n_queries); |
||
878 | + |
||
879 | + if (rc < 0) { |
||
880 | + fprintf(stderr, "Failed to send queries: %s\n", strerror(errno)); |
||
881 | + goto out; |
||
882 | + } else if (rc > 0) { |
||
883 | + break; |
||
884 | + } |
||
885 | + } |
||
886 | + |
||
887 | + if (c >= n_ns) { |
||
888 | + fprintf(stderr, |
||
889 | + ";; connection timed out; no servers could be reached\n\n"); |
||
890 | + |
||
891 | + return 1; |
||
892 | + } |
||
893 | + |
||
894 | + printf("Server:\t\t%s\n", ns[c].name); |
||
895 | + printf("Address:\t%s\n", sal2str(&ns[c].addr)); |
||
896 | + |
||
897 | + if (stats) { |
||
898 | + printf("Replies:\t%d\n", ns[c].replies); |
||
899 | + printf("Failures:\t%d\n", ns[c].failures); |
||
900 | + } |
||
901 | + |
||
902 | + printf("\n"); |
||
903 | + |
||
904 | + for (rc = 0; rc < n_queries; rc++) { |
||
905 | + if (stats) { |
||
906 | + printf("Query #%d completed in %lums:\n", rc, queries[rc].latency); |
||
907 | + } |
||
908 | + |
||
909 | + if (queries[rc].rcode != 0) { |
||
910 | + printf("** server can't find %s: %s\n", queries[rc].name, |
||
911 | + rcodes[queries[rc].rcode]); |
||
912 | + continue; |
||
913 | + } |
||
914 | + |
||
915 | + c = 0; |
||
916 | + |
||
917 | + if (queries[rc].rlen) { |
||
918 | + if (!bb_style_counter) { |
||
919 | + header = (HEADER *)queries[rc].reply; |
||
920 | + |
||
921 | + if (!header->aa) |
||
922 | + printf("Non-authoritative answer:\n"); |
||
923 | + |
||
924 | + c = parse_reply(queries[rc].reply, queries[rc].rlen, NULL); |
||
925 | + } |
||
926 | + else { |
||
927 | + c = parse_reply(queries[rc].reply, queries[rc].rlen, |
||
928 | + &bb_style_counter); |
||
929 | + } |
||
930 | + } |
||
931 | + |
||
932 | + if (c == 0) |
||
933 | + printf("*** Can't find %s: No answer\n", queries[rc].name); |
||
934 | + else if (c < 0) |
||
935 | + printf("*** Can't find %s: Parse error\n", queries[rc].name); |
||
936 | + |
||
937 | + if (!bb_style_counter) |
||
938 | + printf("\n"); |
||
939 | + } |
||
940 | + |
||
941 | + rc = 0; |
||
942 | + |
||
943 | +out: |
||
944 | + if (n_ns) |
||
945 | + free(ns); |
||
946 | + |
||
947 | + if (n_queries) |
||
948 | + free(queries); |
||
949 | + |
||
950 | + return rc; |
||
951 | +} |