1 /* 2 * nslookup_lede - musl compatible replacement for busybox nslookup 3 * 4 * Copyright (C) 2017 Jo-Philipp Wich <jo@mein.io> 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /** 20 * # DNS Resolution Module 21 * 22 * The `resolv` module provides DNS resolution functionality for ucode, allowing 23 * you to perform DNS queries for various record types and handle responses. 24 * 25 * Functions can be individually imported and directly accessed using the 26 * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} 27 * syntax: 28 * 29 * ``` 30 * import { query } from 'resolv'; 31 * 32 * let result = query('example.com', { type: ['A'] }); 33 * ``` 34 * 35 * Alternatively, the module namespace can be imported 36 * using a wildcard import statement: 37 * 38 * ``` 39 * import * as resolv from 'resolv'; 40 * 41 * let result = resolv.query('example.com', { type: ['A'] }); 42 * ``` 43 * 44 * Additionally, the resolv module namespace may also be imported by invoking 45 * the `ucode` interpreter with the `-lresolv` switch. 46 * 47 * ## Record Types 48 * 49 * The module supports the following DNS record types: 50 * 51 * | Type | Description | 52 * |---------|--------------------------------| 53 * | `A` | IPv4 address record | 54 * | `AAAA` | IPv6 address record | 55 * | `CNAME` | Canonical name record | 56 * | `MX` | Mail exchange record | 57 * | `NS` | Name server record | 58 * | `PTR` | Pointer record (reverse DNS) | 59 * | `SOA` | Start of authority record | 60 * | `SRV` | Service record | 61 * | `TXT` | Text record | 62 * | `ANY` | Any available record type | 63 * 64 * ## Response Codes 65 * 66 * DNS queries can return the following response codes: 67 * 68 * | Code | Description | 69 * |-------------|-------------------------------------------| 70 * | `NOERROR` | No error, query successful | 71 * | `FORMERR` | Format error in query | 72 * | `SERVFAIL` | Server failure | 73 * | `NXDOMAIN` | Non-existent domain | 74 * | `NOTIMP` | Not implemented | 75 * | `REFUSED` | Query refused | 76 * | `TIMEOUT` | Query timed out | 77 * 78 * ## Response Format 79 * 80 * DNS query results are returned as objects where: 81 * - Keys are the queried domain names 82 * - Values are objects containing arrays of records grouped by type 83 * - Special `rcode` property indicates query status for failed queries 84 * 85 * ### Record Format by Type 86 * 87 * **A and AAAA records:** 88 * ```javascript 89 * { 90 * "example.com": { 91 * "A": ["192.0.2.1", "192.0.2.2"], 92 * "AAAA": ["2001:db8::1", "2001:db8::2"] 93 * } 94 * } 95 * ``` 96 * 97 * **MX records:** 98 * ```javascript 99 * { 100 * "example.com": { 101 * "MX": [ 102 * [10, "mail1.example.com"], 103 * [20, "mail2.example.com"] 104 * ] 105 * } 106 * } 107 * ``` 108 * 109 * **SRV records:** 110 * ```javascript 111 * { 112 * "_http._tcp.example.com": { 113 * "SRV": [ 114 * [10, 5, 80, "web1.example.com"], 115 * [10, 10, 80, "web2.example.com"] 116 * ] 117 * } 118 * } 119 * ``` 120 * 121 * **SOA records:** 122 * ```javascript 123 * { 124 * "example.com": { 125 * "SOA": [ 126 * [ 127 * "ns1.example.com", // primary nameserver 128 * "admin.example.com", // responsible mailbox 129 * 2023010101, // serial number 130 * 3600, // refresh interval 131 * 1800, // retry interval 132 * 604800, // expire time 133 * 86400 // minimum TTL 134 * ] 135 * ] 136 * } 137 * } 138 * ``` 139 * 140 * **TXT, NS, CNAME, PTR records:** 141 * ```javascript 142 * { 143 * "example.com": { 144 * "TXT": ["v=spf1 include:_spf.example.com ~all"], 145 * "NS": ["ns1.example.com", "ns2.example.com"], 146 * "CNAME": ["alias.example.com"] 147 * } 148 * } 149 * ``` 150 * 151 * **Error responses:** 152 * ```javascript 153 * { 154 * "nonexistent.example.com": { 155 * "rcode": "NXDOMAIN" 156 * } 157 * } 158 * ``` 159 * 160 * ## Examples 161 * 162 * Basic A record lookup: 163 * 164 * ```javascript 165 * import { query } from 'resolv'; 166 * 167 * const result = query(['example.com']); 168 * print(result, "\n"); 169 * // { 170 * // "example.com": { 171 * // "A": ["192.0.2.1"], 172 * // "AAAA": ["2001:db8::1"] 173 * // } 174 * // } 175 * ``` 176 * 177 * Specific record type query: 178 * 179 * ```javascript 180 * const mxRecords = query(['example.com'], { type: ['MX'] }); 181 * print(mxRecords, "\n"); 182 * // { 183 * // "example.com": { 184 * // "MX": [[10, "mail.example.com"]] 185 * // } 186 * // } 187 * ``` 188 * 189 * Multiple domains and types: 190 * 191 * ```javascript 192 * const results = query( 193 * ['example.com', 'google.com'], 194 * { 195 * type: ['A', 'MX'], 196 * timeout: 10000, 197 * nameserver: ['8.8.8.8', '1.1.1.1'] 198 * } 199 * ); 200 * ``` 201 * 202 * Reverse DNS lookup: 203 * 204 * ```javascript 205 * const ptrResult = query(['192.0.2.1'], { type: ['PTR'] }); 206 * print(ptrResult, "\n"); 207 * // { 208 * // "1.2.0.192.in-addr.arpa": { 209 * // "PTR": ["example.com"] 210 * // } 211 * // } 212 * ``` 213 * 214 * @module resolv 215 */ 216 217 #include <stdio.h> 218 #include <resolv.h> 219 #include <string.h> 220 #include <errno.h> 221 #include <time.h> 222 #include <poll.h> 223 #include <unistd.h> 224 #include <stdlib.h> 225 #include <sys/socket.h> 226 #include <arpa/inet.h> 227 #include <net/if.h> 228 #include <netdb.h> 229 #include <fcntl.h> 230 231 #include "ucode/module.h" 232 233 #define for_each_item(arr, item) \ 234 for (uc_value_t *_idx = NULL, *item = (ucv_type(arr) == UC_ARRAY) ? ucv_array_get(arr, 0) : arr; \ 235 (uintptr_t)_idx < (ucv_type(arr) == UC_ARRAY ? ucv_array_length(arr) : (arr != NULL)); \ 236 _idx = (void *)((uintptr_t)_idx + 1), item = ucv_array_get(arr, (uintptr_t)_idx)) 237 238 #define err_return(code, ...) do { set_error(code, __VA_ARGS__); return NULL; } while(0) 239 240 static struct { 241 int code; 242 char *msg; 243 } last_error; 244 245 __attribute__((format(printf, 2, 3))) static void 246 set_error(int errcode, const char *fmt, ...) { 247 va_list ap; 248 249 free(last_error.msg); 250 251 last_error.code = errcode; 252 last_error.msg = NULL; 253 254 if (fmt) { 255 va_start(ap, fmt); 256 xvasprintf(&last_error.msg, fmt, ap); 257 va_end(ap); 258 } 259 } 260 261 typedef struct { 262 socklen_t len; 263 union { 264 struct sockaddr sa; 265 struct sockaddr_in sin; 266 struct sockaddr_in6 sin6; 267 } u; 268 } addr_t; 269 270 typedef struct { 271 const char *name; 272 addr_t addr; 273 } ns_t; 274 275 typedef struct { 276 char *name; 277 size_t qlen, rlen; 278 unsigned char query[512]; 279 int rcode; 280 } query_t; 281 282 typedef struct __attribute__((packed)) { 283 uint8_t root_domain; 284 uint16_t type; 285 uint16_t edns_maxsize; 286 uint8_t extended_rcode; 287 uint8_t edns_version; 288 uint16_t z; 289 uint16_t data_length; 290 } opt_rr_t; 291 292 typedef struct { 293 uint32_t qtypes; 294 size_t n_ns; 295 ns_t *ns; 296 size_t n_queries; 297 query_t *queries; 298 uint32_t retries; 299 uint32_t timeout; 300 uint16_t edns_maxsize; 301 bool txt_as_array; 302 } resolve_ctx_t; 303 304 305 static struct { 306 int type; 307 const char *name; 308 } qtypes[] = { 309 { ns_t_soa, "SOA" }, 310 { ns_t_ns, "NS" }, 311 { ns_t_a, "A" }, 312 { ns_t_aaaa, "AAAA" }, 313 { ns_t_cname, "CNAME" }, 314 { ns_t_mx, "MX" }, 315 { ns_t_txt, "TXT" }, 316 { ns_t_srv, "SRV" }, 317 { ns_t_ptr, "PTR" }, 318 { ns_t_any, "ANY" }, 319 { } 320 }; 321 322 static const char *rcodes[] = { 323 "NOERROR", 324 "FORMERR", 325 "SERVFAIL", 326 "NXDOMAIN", 327 "NOTIMP", 328 "REFUSED", 329 "YXDOMAIN", 330 "YXRRSET", 331 "NXRRSET", 332 "NOTAUTH", 333 "NOTZONE", 334 "RESERVED11", 335 "RESERVED12", 336 "RESERVED13", 337 "RESERVED14", 338 "RESERVED15", 339 "BADVERS" 340 }; 341 342 static unsigned int default_port = 53; 343 344 345 static uc_value_t * 346 init_obj(uc_vm_t *vm, uc_value_t *obj, const char *key, uc_type_t type) 347 { 348 uc_value_t *existing; 349 350 existing = ucv_object_get(obj, key, NULL); 351 352 if (existing == NULL) { 353 switch (type) { 354 case UC_ARRAY: 355 existing = ucv_array_new(vm); 356 break; 357 358 case UC_OBJECT: 359 existing = ucv_object_new(vm); 360 break; 361 362 default: 363 return NULL; 364 } 365 366 ucv_object_add(obj, key, existing); 367 } 368 369 return existing; 370 } 371 372 static int 373 parse_reply(uc_vm_t *vm, uc_value_t *res_obj, const unsigned char *msg, size_t len, bool txt_as_array) 374 { 375 ns_msg handle; 376 ns_rr rr; 377 int i, n, rdlen; 378 const char *key = NULL; 379 char astr[INET6_ADDRSTRLEN], dname[MAXDNAME]; 380 const unsigned char *cp; 381 uc_value_t *name_obj, *type_arr, *item; 382 383 if (ns_initparse(msg, len, &handle) != 0) { 384 set_error(errno, "Unable to parse reply packet"); 385 386 return -1; 387 } 388 389 for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) { 390 if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { 391 set_error(errno, "Unable to parse resource record"); 392 393 return -1; 394 } 395 396 name_obj = init_obj(vm, res_obj, ns_rr_name(rr), UC_OBJECT); 397 398 rdlen = ns_rr_rdlen(rr); 399 400 switch (ns_rr_type(rr)) 401 { 402 case ns_t_a: 403 if (rdlen != 4) { 404 set_error(EBADMSG, "Invalid A record length"); 405 406 return -1; 407 } 408 409 type_arr = init_obj(vm, name_obj, "A", UC_ARRAY); 410 411 inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr)); 412 ucv_array_push(type_arr, ucv_string_new(astr)); 413 break; 414 415 case ns_t_aaaa: 416 if (rdlen != 16) { 417 set_error(EBADMSG, "Invalid AAAA record length"); 418 419 return -1; 420 } 421 422 type_arr = init_obj(vm, name_obj, "AAAA", UC_ARRAY); 423 424 inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr)); 425 ucv_array_push(type_arr, ucv_string_new(astr)); 426 break; 427 428 case ns_t_ns: 429 if (!key) 430 key = "NS"; 431 /* fall through */ 432 433 case ns_t_cname: 434 if (!key) 435 key = "CNAME"; 436 /* fall through */ 437 438 case ns_t_ptr: 439 if (!key) 440 key = "PTR"; 441 442 if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), 443 ns_rr_rdata(rr), dname, sizeof(dname)) < 0) { 444 set_error(errno, "Unable to uncompress domain name"); 445 446 return -1; 447 } 448 449 type_arr = init_obj(vm, name_obj, key, UC_ARRAY); 450 n = ucv_array_length(type_arr); 451 item = n ? ucv_array_get(type_arr, n - 1) : NULL; 452 453 if (!n || strcmp(ucv_string_get(item), dname)) 454 ucv_array_push(type_arr, ucv_string_new(dname)); 455 456 break; 457 458 case ns_t_mx: 459 if (rdlen < 2) { 460 set_error(EBADMSG, "MX record too short"); 461 462 return -1; 463 } 464 465 n = ns_get16(ns_rr_rdata(rr)); 466 467 if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), 468 ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0) { 469 set_error(errno, "Unable to uncompress MX domain"); 470 471 return -1; 472 } 473 474 type_arr = init_obj(vm, name_obj, "MX", UC_ARRAY); 475 item = ucv_array_new_length(vm, 2); 476 ucv_array_push(item, ucv_int64_new(n)); 477 ucv_array_push(item, ucv_string_new(dname)); 478 ucv_array_push(type_arr, item); 479 break; 480 481 case ns_t_txt: 482 if (rdlen < 1) { 483 set_error(EBADMSG, "TXT record too short"); 484 485 return -1; 486 } 487 488 if (txt_as_array) { 489 uc_value_t *values = ucv_array_new(vm); 490 491 for (const unsigned char *p = ns_rr_rdata(rr); rdlen > 0; ) { 492 n = *p++; 493 rdlen--; 494 495 if (n > rdlen) { 496 set_error(EBADMSG, "TXT string exceeds record length"); 497 498 return -1; 499 } 500 501 ucv_array_push(values, 502 ucv_string_new_length((const char *)p, n)); 503 504 rdlen -= n; 505 p += n; 506 } 507 508 type_arr = init_obj(vm, name_obj, "TXT", UC_ARRAY); 509 ucv_array_push(type_arr, values); 510 } 511 else { 512 uc_stringbuf_t *buf = ucv_stringbuf_new(); 513 514 for (const unsigned char *p = ns_rr_rdata(rr); rdlen > 0; ) { 515 n = *p++; 516 rdlen--; 517 518 if (n > rdlen) { 519 set_error(EBADMSG, "TXT string exceeds record length"); 520 521 return -1; 522 } 523 524 if (buf->bpos > 0) 525 ucv_stringbuf_append(buf, " "); 526 527 ucv_stringbuf_addstr(buf, (const char *)p, n); 528 rdlen -= n; 529 p += n; 530 } 531 532 type_arr = init_obj(vm, name_obj, "TXT", UC_ARRAY); 533 ucv_array_push(type_arr, ucv_stringbuf_finish(buf)); 534 } 535 536 break; 537 538 case ns_t_srv: 539 if (rdlen < 6) { 540 set_error(EBADMSG, "SRV record too short"); 541 542 return -1; 543 } 544 545 cp = ns_rr_rdata(rr); 546 n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), 547 cp + 6, dname, sizeof(dname)); 548 549 if (n < 0) { 550 set_error(errno, "Unable to uncompress domain name"); 551 552 return -1; 553 } 554 555 type_arr = init_obj(vm, name_obj, "SRV", UC_ARRAY); 556 item = ucv_array_new_length(vm, 4); 557 ucv_array_push(item, ucv_int64_new(ns_get16(cp))); 558 ucv_array_push(item, ucv_int64_new(ns_get16(cp + 2))); 559 ucv_array_push(item, ucv_int64_new(ns_get16(cp + 4))); 560 ucv_array_push(item, ucv_string_new(dname)); 561 ucv_array_push(type_arr, item); 562 break; 563 564 case ns_t_soa: 565 if (rdlen < 20) { 566 set_error(EBADMSG, "SOA record too short"); 567 568 return -1; 569 } 570 571 type_arr = init_obj(vm, name_obj, "SOA", UC_ARRAY); 572 item = ucv_array_new_length(vm, 7); 573 574 cp = ns_rr_rdata(rr); 575 n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), 576 cp, dname, sizeof(dname)); 577 578 if (n < 0) { 579 set_error(errno, "Unable to uncompress domain name"); 580 ucv_put(item); 581 582 return -1; 583 } 584 585 ucv_array_push(item, ucv_string_new(dname)); /* origin */ 586 cp += n; 587 588 n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), 589 cp, dname, sizeof(dname)); 590 591 if (n < 0) { 592 set_error(errno, "Unable to uncompress domain name"); 593 ucv_put(item); 594 595 return -1; 596 } 597 598 ucv_array_push(item, ucv_string_new(dname)); /* mail addr */ 599 cp += n; 600 601 ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* serial */ 602 cp += 4; 603 604 ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* refresh */ 605 cp += 4; 606 607 ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* retry */ 608 cp += 4; 609 610 ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* expire */ 611 cp += 4; 612 613 ucv_array_push(item, ucv_int64_new(ns_get32(cp))); /* minimum */ 614 615 ucv_array_push(type_arr, item); 616 break; 617 618 default: 619 break; 620 } 621 } 622 623 return i; 624 } 625 626 static int 627 parse_nsaddr(const char *addrstr, addr_t *lsa) 628 { 629 char *eptr, ifname[IFNAMSIZ], ipaddr[INET6_ADDRSTRLEN] = { 0 }; 630 unsigned int port = default_port; 631 unsigned int scope = 0; 632 const char *sep, *p; 633 634 sep = strchr(addrstr, '#'); 635 636 if (sep) { 637 port = strtoul(sep + 1, &eptr, 10); 638 639 if ((size_t)(sep - addrstr) >= sizeof(ipaddr) || 640 eptr == sep + 1 || *eptr != '\0' || port > 65535) { 641 errno = EINVAL; 642 return -1; 643 } 644 645 memcpy(ipaddr, addrstr, sep - addrstr); 646 } 647 else { 648 strncpy(ipaddr, addrstr, sizeof(ipaddr) - 1); 649 } 650 651 sep = strchr(addrstr, '%'); 652 653 if (sep) { 654 for (p = ++sep; *p != '\0' && *p != '#'; p++) { 655 if ((p - sep) >= IFNAMSIZ) { 656 errno = ENODEV; 657 return -1; 658 } 659 660 ifname[p - sep] = *p; 661 } 662 663 ifname[p - sep] = '\0'; 664 scope = if_nametoindex(ifname); 665 666 if (scope == 0) { 667 errno = ENODEV; 668 return -1; 669 } 670 } 671 672 if (inet_pton(AF_INET6, ipaddr, &lsa->u.sin6.sin6_addr)) { 673 lsa->u.sin6.sin6_family = AF_INET6; 674 lsa->u.sin6.sin6_port = htons(port); 675 lsa->u.sin6.sin6_scope_id = scope; 676 lsa->len = sizeof(lsa->u.sin6); 677 return 0; 678 } 679 680 if (!scope && inet_pton(AF_INET, ipaddr, &lsa->u.sin.sin_addr)) { 681 lsa->u.sin.sin_family = AF_INET; 682 lsa->u.sin.sin_port = htons(port); 683 lsa->len = sizeof(lsa->u.sin); 684 return 0; 685 } 686 687 errno = EINVAL; 688 return -1; 689 } 690 691 static char * 692 make_ptr(const char *addrstr) 693 { 694 const char *hexdigit = "0123456789abcdef"; 695 static char ptrstr[73]; 696 unsigned char addr[16]; 697 char *ptr = ptrstr; 698 int i; 699 700 if (inet_pton(AF_INET6, addrstr, addr)) { 701 if (memcmp(addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) != 0) { 702 for (i = 0; i < 16; i++) { 703 *ptr++ = hexdigit[(unsigned char)addr[15 - i] & 0xf]; 704 *ptr++ = '.'; 705 *ptr++ = hexdigit[(unsigned char)addr[15 - i] >> 4]; 706 *ptr++ = '.'; 707 } 708 strcpy(ptr, "ip6.arpa"); 709 } 710 else { 711 sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", 712 addr[15], addr[14], addr[13], addr[12]); 713 } 714 715 return ptrstr; 716 } 717 718 if (inet_pton(AF_INET, addrstr, addr)) { 719 sprintf(ptr, "%u.%u.%u.%u.in-addr.arpa", 720 addr[3], addr[2], addr[1], addr[0]); 721 return ptrstr; 722 } 723 724 return NULL; 725 } 726 727 static unsigned long 728 mtime(void) 729 { 730 struct timespec ts; 731 clock_gettime(CLOCK_REALTIME, &ts); 732 733 return (unsigned long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 734 } 735 736 static void 737 to_v4_mapped(addr_t *a) 738 { 739 if (a->u.sa.sa_family != AF_INET) 740 return; 741 742 memcpy(a->u.sin6.sin6_addr.s6_addr + 12, 743 &a->u.sin.sin_addr, 4); 744 745 memcpy(a->u.sin6.sin6_addr.s6_addr, 746 "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); 747 748 a->u.sin6.sin6_family = AF_INET6; 749 a->u.sin6.sin6_flowinfo = 0; 750 a->u.sin6.sin6_scope_id = 0; 751 a->len = sizeof(a->u.sin6); 752 } 753 754 static void 755 add_status(uc_vm_t *vm, uc_value_t *res_obj, const char *name, const char *rcode) 756 { 757 uc_value_t *name_obj = init_obj(vm, res_obj, name, UC_OBJECT); 758 759 ucv_object_add(name_obj, "rcode", ucv_string_new(rcode)); 760 } 761 762 /* 763 * Function logic borrowed & modified from musl libc, res_msend.c 764 */ 765 766 static int 767 send_queries(resolve_ctx_t *ctx, uc_vm_t *vm, uc_value_t *res_obj) 768 { 769 int fd, flags; 770 int servfail_retry = 0; 771 addr_t from = { }; 772 int one = 1; 773 int recvlen = 0; 774 int n_replies = 0; 775 struct pollfd pfd; 776 unsigned long t0, t1, t2, timeout = ctx->timeout, retry_interval; 777 unsigned int nn, qn, next_query = 0; 778 struct { unsigned char *buf; size_t len; } reply_buf = { 0 }; 779 780 from.u.sa.sa_family = AF_INET; 781 from.len = sizeof(from.u.sin); 782 783 for (nn = 0; nn < ctx->n_ns; nn++) { 784 if (ctx->ns[nn].addr.u.sa.sa_family == AF_INET6) { 785 from.u.sa.sa_family = AF_INET6; 786 from.len = sizeof(from.u.sin6); 787 break; 788 } 789 } 790 791 #ifdef __APPLE__ 792 flags = SOCK_DGRAM; 793 #else 794 flags = SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK; 795 #endif 796 797 /* Get local address and open/bind a socket */ 798 fd = socket(from.u.sa.sa_family, flags, 0); 799 800 /* Handle case where system lacks IPv6 support */ 801 if (fd < 0 && from.u.sa.sa_family == AF_INET6 && errno == EAFNOSUPPORT) { 802 fd = socket(AF_INET, flags, 0); 803 from.u.sa.sa_family = AF_INET; 804 } 805 806 if (fd < 0) { 807 set_error(errno, "Unable to open UDP socket"); 808 809 return -1; 810 } 811 812 #ifdef __APPLE__ 813 flags = fcntl(fd, F_GETFD); 814 815 if (flags < 0) { 816 set_error(errno, "Unable to acquire socket descriptor flags"); 817 close(fd); 818 819 return -1; 820 } 821 822 if (fcntl(fd, F_SETFD, flags|O_CLOEXEC|O_NONBLOCK) < 0) { 823 set_error(errno, "Unable to set socket descriptor flags"); 824 close(fd); 825 826 return -1; 827 } 828 #endif 829 830 if (bind(fd, &from.u.sa, from.len) < 0) { 831 set_error(errno, "Unable to bind UDP socket"); 832 close(fd); 833 834 return -1; 835 } 836 837 /* Convert any IPv4 addresses in a mixed environment to v4-mapped */ 838 if (from.u.sa.sa_family == AF_INET6) { 839 setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 840 841 for (nn = 0; nn < ctx->n_ns; nn++) 842 to_v4_mapped(&ctx->ns[nn].addr); 843 } 844 845 pfd.fd = fd; 846 pfd.events = POLLIN; 847 retry_interval = timeout / ctx->retries; 848 t0 = t2 = mtime(); 849 t1 = t2 - retry_interval; 850 851 for (; t2 - t0 < timeout; t2 = mtime()) { 852 if (t2 - t1 >= retry_interval) { 853 for (qn = 0; qn < ctx->n_queries; qn++) { 854 if (ctx->queries[qn].rcode == 0 || ctx->queries[qn].rcode == 3) 855 continue; 856 857 for (nn = 0; nn < ctx->n_ns; nn++) { 858 sendto(fd, ctx->queries[qn].query, ctx->queries[qn].qlen, 859 MSG_NOSIGNAL, &ctx->ns[nn].addr.u.sa, ctx->ns[nn].addr.len); 860 } 861 } 862 863 t1 = t2; 864 servfail_retry = 2 * ctx->n_queries; 865 } 866 867 /* Wait for a response, or until time to retry */ 868 switch (poll(&pfd, 1, t1+retry_interval-t2)) { 869 case 0: 870 /* timeout */ 871 for (qn = 0; qn < ctx->n_queries; qn++) { 872 if (ctx->queries[qn].rcode != -1) 873 continue; 874 875 for (nn = 0; nn < ctx->n_ns; nn++) 876 add_status(vm, res_obj, ctx->queries[qn].name, "TIMEOUT"); 877 } 878 879 continue; 880 881 case -1: 882 /* error */ 883 continue; 884 } 885 886 while (1) { 887 recvlen = recvfrom(fd, NULL, 0, MSG_PEEK|MSG_TRUNC, &from.u.sa, &from.len); 888 889 /* read error */ 890 if (recvlen < 0) 891 break; 892 893 if ((size_t)recvlen > reply_buf.len) { 894 reply_buf.buf = xrealloc(reply_buf.buf, recvlen); 895 reply_buf.len = recvlen; 896 } 897 898 recvlen = recvfrom(fd, reply_buf.buf, recvlen, 0, &from.u.sa, &from.len); 899 900 /* Ignore non-identifiable packets */ 901 if (recvlen < 4) 902 continue; 903 904 /* Ignore replies from addresses we didn't send to */ 905 for (nn = 0; nn < ctx->n_ns; nn++) 906 if (memcmp(&from.u.sa, &ctx->ns[nn].addr.u.sa, from.len) == 0) 907 break; 908 909 if (nn >= ctx->n_ns) 910 continue; 911 912 /* Find which query this answer goes with, if any */ 913 for (qn = next_query; qn < ctx->n_queries; qn++) 914 if (!memcmp(reply_buf.buf, ctx->queries[qn].query, 2)) 915 break; 916 917 /* Do not overwrite previous replies from other servers 918 * but allow overwriting preexisting NXDOMAIN reply */ 919 if (qn >= ctx->n_queries || 920 ctx->queries[qn].rcode == 0 || 921 (ctx->queries[qn].rcode == 3 && (reply_buf.buf[3] & 15) != 0)) 922 continue; 923 924 ctx->queries[qn].rcode = reply_buf.buf[3] & 15; 925 926 switch (ctx->queries[qn].rcode) { 927 case 0: 928 ucv_object_delete( 929 ucv_object_get(res_obj, ctx->queries[qn].name, NULL), 930 "rcodes"); 931 932 break; 933 934 case 2: 935 /* Retry immediately on server failure. */ 936 if (servfail_retry && servfail_retry--) 937 sendto(fd, ctx->queries[qn].query, ctx->queries[qn].qlen, 938 MSG_NOSIGNAL, &ctx->ns[nn].addr.u.sa, ctx->ns[nn].addr.len); 939 940 /* fall through */ 941 942 default: 943 add_status(vm, res_obj, ctx->queries[qn].name, 944 rcodes[ctx->queries[qn].rcode]); 945 } 946 947 /* Store answer */ 948 n_replies++; 949 950 ctx->queries[qn].rlen = recvlen; 951 952 parse_reply(vm, res_obj, reply_buf.buf, recvlen, ctx->txt_as_array); 953 954 if (qn == next_query) { 955 while (next_query < ctx->n_queries) { 956 if (ctx->queries[next_query].rcode == -1) 957 break; 958 959 next_query++; 960 } 961 } 962 963 if (next_query >= ctx->n_queries) 964 goto out; 965 } 966 } 967 968 out: 969 free(reply_buf.buf); 970 close(fd); 971 972 return n_replies; 973 } 974 975 static ns_t * 976 add_ns(resolve_ctx_t *ctx, const char *addr) 977 { 978 char portstr[sizeof("65535")]; 979 addr_t a = { }; 980 const char *p; 981 struct addrinfo *ai, *aip, hints = { 982 .ai_flags = AI_NUMERICSERV, 983 .ai_socktype = SOCK_DGRAM 984 }; 985 986 if (parse_nsaddr(addr, &a)) { 987 /* Maybe we got a domain name, attempt to resolve it using the standard 988 * resolver routines */ 989 990 p = strchr(addr, '#'); 991 snprintf(portstr, sizeof(portstr), "%hu", 992 (unsigned short)(p ? strtoul(p, NULL, 10) : default_port)); 993 994 if (!getaddrinfo(addr, portstr, &hints, &ai)) { 995 for (aip = ai; aip; aip = aip->ai_next) { 996 if (aip->ai_addr->sa_family != AF_INET && 997 aip->ai_addr->sa_family != AF_INET6) 998 continue; 999 1000 ctx->ns = xrealloc(ctx->ns, sizeof(*ctx->ns) * (ctx->n_ns + 1)); 1001 ctx->ns[ctx->n_ns].name = addr; 1002 ctx->ns[ctx->n_ns].addr.len = aip->ai_addrlen; 1003 1004 memcpy(&ctx->ns[ctx->n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen); 1005 1006 ctx->n_ns++; 1007 } 1008 1009 freeaddrinfo(ai); 1010 1011 return &ctx->ns[ctx->n_ns]; 1012 } 1013 1014 return NULL; 1015 } 1016 1017 ctx->ns = xrealloc(ctx->ns, sizeof(*ctx->ns) * (ctx->n_ns + 1)); 1018 ctx->ns[ctx->n_ns].addr = a; 1019 ctx->ns[ctx->n_ns].name = addr; 1020 1021 return &ctx->ns[ctx->n_ns++]; 1022 } 1023 1024 static int 1025 parse_resolvconf(resolve_ctx_t *ctx) 1026 { 1027 int prev_n_ns = ctx->n_ns; 1028 char line[128], *p; 1029 FILE *resolv; 1030 bool ok; 1031 1032 if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) { 1033 while (fgets(line, sizeof(line), resolv)) { 1034 p = strtok(line, " \t\n"); 1035 1036 if (!p || strcmp(p, "nameserver")) 1037 continue; 1038 1039 p = strtok(NULL, " \t\n"); 1040 1041 if (!p) 1042 continue; 1043 1044 p = xstrdup(p); 1045 ok = add_ns(ctx, p); 1046 1047 free(p); 1048 1049 if (!ok) 1050 break; 1051 } 1052 1053 fclose(resolv); 1054 } 1055 1056 return ctx->n_ns - prev_n_ns; 1057 } 1058 1059 static query_t * 1060 add_query(resolve_ctx_t *ctx, int type, const char *dname) 1061 { 1062 opt_rr_t *opt; 1063 ssize_t qlen; 1064 1065 ctx->queries = xrealloc(ctx->queries, sizeof(*ctx->queries) * (ctx->n_queries + 1)); 1066 1067 memset(&ctx->queries[ctx->n_queries], 0, sizeof(*ctx->queries)); 1068 1069 qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, 1070 ctx->queries[ctx->n_queries].query, 1071 sizeof(ctx->queries[ctx->n_queries].query)); 1072 1073 /* add OPT record */ 1074 if (ctx->edns_maxsize != 0 && qlen + sizeof(opt_rr_t) <= sizeof(ctx->queries[ctx->n_queries].query)) { 1075 ctx->queries[ctx->n_queries].query[11] = 1; 1076 1077 opt = (opt_rr_t *)&ctx->queries[ctx->n_queries].query[qlen]; 1078 opt->root_domain = 0; 1079 opt->type = htons(41); 1080 opt->edns_maxsize = htons(ctx->edns_maxsize); 1081 opt->extended_rcode = 0; 1082 opt->edns_version = 0; 1083 opt->z = htons(0); 1084 opt->data_length = htons(0); 1085 1086 qlen += sizeof(opt_rr_t); 1087 } 1088 1089 ctx->queries[ctx->n_queries].qlen = qlen; 1090 ctx->queries[ctx->n_queries].name = xstrdup(dname); 1091 ctx->queries[ctx->n_queries].rcode = -1; 1092 1093 return &ctx->queries[ctx->n_queries++]; 1094 } 1095 1096 static bool 1097 check_types(uc_value_t *typenames, uint32_t *types) 1098 { 1099 size_t i; 1100 1101 *types = 0; 1102 1103 for_each_item(typenames, typename) { 1104 if (ucv_type(typename) != UC_STRING) 1105 err_return(EINVAL, "Query type value not a string"); 1106 1107 for (i = 0; qtypes[i].name; i++) { 1108 if (!strcasecmp(ucv_string_get(typename), qtypes[i].name)) { 1109 *types |= (1 << i); 1110 break; 1111 } 1112 } 1113 1114 if (!qtypes[i].name) 1115 err_return(EINVAL, "Unrecognized query type '%s'", 1116 ucv_string_get(typename)); 1117 } 1118 1119 return true; 1120 } 1121 1122 static void 1123 add_queries(resolve_ctx_t *ctx, uc_value_t *name) 1124 { 1125 char *s = ucv_string_get(name); 1126 char *ptr; 1127 size_t i; 1128 1129 if (ctx->qtypes == 0) { 1130 ptr = make_ptr(s); 1131 1132 if (ptr) { 1133 add_query(ctx, ns_t_ptr, ptr); 1134 } 1135 else { 1136 add_query(ctx, ns_t_a, s); 1137 add_query(ctx, ns_t_aaaa, s); 1138 } 1139 } 1140 else { 1141 for (i = 0; qtypes[i].name; i++) { 1142 if (ctx->qtypes & (1 << i)) { 1143 if (qtypes[i].type == ns_t_ptr) { 1144 ptr = make_ptr(s); 1145 add_query(ctx, ns_t_ptr, ptr ? ptr : s); 1146 } 1147 else { 1148 add_query(ctx, qtypes[i].type, s); 1149 } 1150 } 1151 } 1152 } 1153 } 1154 1155 static bool 1156 parse_options(resolve_ctx_t *ctx, uc_value_t *opts) 1157 { 1158 uc_value_t *v; 1159 1160 if (!check_types(ucv_object_get(opts, "type", NULL), &ctx->qtypes)) 1161 return false; 1162 1163 for_each_item(ucv_object_get(opts, "nameserver", NULL), server) { 1164 if (ucv_type(server) != UC_STRING) 1165 err_return(EINVAL, "Nameserver value not a string"); 1166 1167 if (!add_ns(ctx, ucv_string_get(server))) 1168 err_return(EINVAL, "Unable to resolve nameserver address '%s'", 1169 ucv_string_get(server)); 1170 } 1171 1172 /* Find NS servers in resolv.conf if none provided */ 1173 if (ctx->n_ns == 0) 1174 parse_resolvconf(ctx); 1175 1176 /* Fall back to localhost if we could not find NS in resolv.conf */ 1177 if (ctx->n_ns == 0) 1178 add_ns(ctx, "127.0.0.1"); 1179 1180 v = ucv_object_get(opts, "retries", NULL); 1181 1182 if (ucv_type(v) == UC_INTEGER) 1183 ctx->retries = ucv_uint64_get(v); 1184 else if (v) 1185 err_return(EINVAL, "Retries value not an integer"); 1186 1187 v = ucv_object_get(opts, "timeout", NULL); 1188 1189 if (ucv_type(v) == UC_INTEGER) 1190 ctx->timeout = ucv_uint64_get(v); 1191 else if (v) 1192 err_return(EINVAL, "Timeout value not an integer"); 1193 1194 v = ucv_object_get(opts, "edns_maxsize", NULL); 1195 1196 if (ucv_type(v) == UC_INTEGER) 1197 ctx->edns_maxsize = ucv_uint64_get(v); 1198 else if (v) 1199 err_return(EINVAL, "EDNS max size not an integer"); 1200 1201 v = ucv_object_get(opts, "txt_as_array", NULL); 1202 1203 if (ucv_type(v) == UC_BOOLEAN) 1204 ctx->txt_as_array = ucv_boolean_get(v); 1205 else if (v) 1206 err_return(EINVAL, "Array TXT record flag not a boolean"); 1207 1208 return true; 1209 } 1210 1211 /** 1212 * Perform DNS queries for specified domain names. 1213 * 1214 * The `query()` function performs DNS lookups for one or more domain names 1215 * according to the specified options. It returns a structured object containing 1216 * all resolved DNS records grouped by domain name and record type. 1217 * 1218 * If no record types are specified in the options, the function will perform 1219 * both A and AAAA record lookups for regular domain names, or PTR record 1220 * lookups for IP addresses (reverse DNS). 1221 * 1222 * Returns an object containing DNS query results organized by domain name. 1223 * 1224 * Raises a runtime exception if invalid arguments are provided or if DNS 1225 * resolution encounters critical errors. 1226 * 1227 * @function module:resolv#query 1228 * 1229 * @param {string|string[]} names 1230 * Domain name(s) to query. Can be a single domain name string or an array 1231 * of domain name strings. IP addresses can also be provided for reverse 1232 * DNS lookups. 1233 * 1234 * @param {object} [options] 1235 * Query options object. 1236 * 1237 * @param {string[]} [options.type] 1238 * Array of DNS record types to query for. Valid types are: 'A', 'AAAA', 1239 * 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT', 'ANY'. If not specified, 1240 * defaults to 'A' and 'AAAA' for domain names, or 'PTR' for IP addresses. 1241 * 1242 * @param {string[]} [options.nameserver] 1243 * Array of DNS nameserver addresses to query. Each address can optionally 1244 * include a port number using '#' separator (e.g., '8.8.8.8#53'). IPv6 1245 * addresses can include interface scope using '%' separator. If not specified, 1246 * nameservers are read from /etc/resolv.conf, falling back to '127.0.0.1'. 1247 * 1248 * @param {number} [options.timeout=5000] 1249 * Total timeout for all queries in milliseconds. 1250 * 1251 * @param {number} [options.retries=2] 1252 * Number of retry attempts for failed queries. 1253 * 1254 * @param {number} [options.edns_maxsize=4096] 1255 * Maximum UDP packet size for EDNS (Extension Mechanisms for DNS). Set to 0 1256 * to disable EDNS. 1257 * 1258 * @param {boolean} [options.txt_as_array=false] 1259 * Return TXT record strings as array elements instead of space-joining all 1260 * record strings into one single string per record. 1261 * 1262 * @returns {object} 1263 * Object containing DNS query results. Keys are domain names, values are 1264 * objects containing arrays of records grouped by type, or error information 1265 * for failed queries. 1266 * 1267 * @example 1268 * // Basic A and AAAA record lookup 1269 * const result = query('example.com'); 1270 * print(result, "\n"); 1271 * // { 1272 * // "example.com": { 1273 * // "A": ["192.0.2.1"], 1274 * // "AAAA": ["2001:db8::1"] 1275 * // } 1276 * // } 1277 * 1278 * @example 1279 * // Specific record type queries 1280 * const mxResult = query('example.com', { type: ['MX'] }); 1281 * print(mxResult, "\n"); 1282 * // { 1283 * // "example.com": { 1284 * // "MX": [[10, "mail.example.com"]] 1285 * // } 1286 * // } 1287 * 1288 * @example 1289 * // Multiple domains and types with custom nameserver 1290 * const results = query( 1291 * ['example.com', 'google.com'], 1292 * { 1293 * type: ['A', 'MX'], 1294 * nameserver: ['8.8.8.8', '1.1.1.1'], 1295 * timeout: 10000 1296 * } 1297 * ); 1298 * 1299 * @example 1300 * // Reverse DNS lookup 1301 * const ptrResult = query(['192.0.2.1'], { type: ['PTR'] }); 1302 * print(ptrResult, "\n"); 1303 * // { 1304 * // "1.2.0.192.in-addr.arpa": { 1305 * // "PTR": ["example.com"] 1306 * // } 1307 * // } 1308 * 1309 * @example 1310 * // TXT record with multiple elements 1311 * const txtResult = query(['_spf.facebook.com'], { type: ['TXT'], txt_as_array: true }); 1312 * printf(txtResult, "\n"); 1313 * // { 1314 * // "_spf.facebook.com": { 1315 * // "TXT": [ 1316 * // [ 1317 * // "v=spf1 ip4:66.220.144.128/25 ip4:66.220.155.0/24 ip4:66.220.157.0/25 ip4:69.63.178.128/25 ip4:69.63.181.0/24 ip4:69.63.184.0/25", 1318 * // " ip4:69.171.232.0/24 ip4:69.171.244.0/23 -all" 1319 * // ] 1320 * // ] 1321 * // } 1322 * // } 1323 * 1324 * @example 1325 * // Handling errors 1326 * const errorResult = query(['nonexistent.example.com']); 1327 * print(errorResult, "\n"); 1328 * // { 1329 * // "nonexistent.example.com": { 1330 * // "rcode": "NXDOMAIN" 1331 * // } 1332 * // } 1333 */ 1334 static uc_value_t * 1335 uc_resolv_query(uc_vm_t *vm, size_t nargs) 1336 { 1337 resolve_ctx_t ctx = { .retries = 2, .timeout = 5000, .edns_maxsize = 4096 }; 1338 uc_value_t *names = uc_fn_arg(0); 1339 uc_value_t *opts = uc_fn_arg(1); 1340 uc_value_t *res_obj = NULL; 1341 1342 if (!parse_options(&ctx, opts)) 1343 goto err; 1344 1345 for_each_item(names, name) { 1346 if (ucv_type(name) != UC_STRING) { 1347 set_error(EINVAL, "Domain name value not a string"); 1348 goto err; 1349 } 1350 1351 add_queries(&ctx, name); 1352 } 1353 1354 res_obj = ucv_object_new(vm); 1355 1356 if (send_queries(&ctx, vm, res_obj) == 0) 1357 set_error(ETIMEDOUT, "Server did not respond"); 1358 1359 err: 1360 while (ctx.n_queries) 1361 free(ctx.queries[--ctx.n_queries].name); 1362 1363 free(ctx.queries); 1364 free(ctx.ns); 1365 1366 return res_obj; 1367 } 1368 1369 /** 1370 * Get the last error message from DNS operations. 1371 * 1372 * The `error()` function returns a descriptive error message for the last 1373 * failed DNS operation, or `null` if no error occurred. This function is 1374 * particularly useful for debugging DNS resolution issues. 1375 * 1376 * After calling this function, the stored error state is cleared, so 1377 * subsequent calls will return `null` unless a new error occurs. 1378 * 1379 * Returns a string describing the last error, or `null` if no error occurred. 1380 * 1381 * @function module:resolv#error 1382 * 1383 * @returns {string|null} 1384 * A descriptive error message for the last failed operation, or `null` if 1385 * no error occurred. 1386 * 1387 * @example 1388 * // Check for errors after a failed query 1389 * const result = query("example.org", { nameserver: "invalid..domain" }); 1390 * const err = error(); 1391 * if (err) { 1392 * print("DNS query failed: ", err, "\n"); 1393 * } 1394 */ 1395 static uc_value_t * 1396 uc_resolv_error(uc_vm_t *vm, size_t nargs) 1397 { 1398 uc_stringbuf_t *buf; 1399 const char *s; 1400 1401 if (last_error.code == 0) 1402 return NULL; 1403 1404 buf = ucv_stringbuf_new(); 1405 1406 s = strerror(last_error.code); 1407 1408 ucv_stringbuf_addstr(buf, s, strlen(s)); 1409 1410 if (last_error.msg) 1411 ucv_stringbuf_printf(buf, ": %s", last_error.msg); 1412 1413 set_error(0, NULL); 1414 1415 return ucv_stringbuf_finish(buf); 1416 } 1417 1418 1419 static const uc_function_list_t resolv_fns[] = { 1420 { "query", uc_resolv_query }, 1421 { "error", uc_resolv_error }, 1422 }; 1423 1424 void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 1425 { 1426 uc_function_list_register(scope, resolv_fns); 1427 } 1428
This page was automatically generated by LXR 0.3.1. • OpenWrt